This commit is contained in:
Archer
2023-12-31 14:12:51 +08:00
committed by GitHub
parent ccca0468da
commit 9ccfda47b7
270 changed files with 8182 additions and 1295 deletions

View File

@@ -0,0 +1,50 @@
name: Build Home page images in Personal warehouse
on:
workflow_dispatch:
push:
paths:
- 'projects/home/**'
branches:
- 'main'
jobs:
build-fastgpt-images:
runs-on: ubuntu-20.04
steps:
- name: Checkout
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
with:
driver-opts: network=host
- name: Cache Docker layers
uses: actions/cache@v3
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
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GH_PAT }}
- name: Set DOCKER_REPO_TAGGED based on branch or tag
run: |
echo "DOCKER_REPO_TAGGED=ghcr.io/${{ github.repository_owner }}/fastgpt-home:latest" >> $GITHUB_ENV
- 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=home \
--label "org.opencontainers.image.source=https://github.com/${{ github.repository_owner }}/FastGPT" \
--label "org.opencontainers.image.description=fastgpt-home image" \
--push \
--cache-from=type=local,src=/tmp/.buildx-cache \
--cache-to=type=local,dest=/tmp/.buildx-cache \
-t ${DOCKER_REPO_TAGGED} \
-f Dockerfile \
.

View File

@@ -3,3 +3,4 @@ dist
**/.DS_Store
node_modules
docSite/
*.md

View File

@@ -4,7 +4,8 @@
"typescript.tsdk": "node_modules/typescript/lib",
"prettier.prettierPath": "./node_modules/prettier",
"i18n-ally.localesPaths": [
"projects/app/public/locales"
"projects/app/public/locales",
"projects/home/public/locales"
],
"i18n-ally.enabledParsers": ["json"],
"i18n-ally.keystyle": "nested",

View File

@@ -77,9 +77,7 @@ COPY --from=builder /app/projects/$name/package.json ./package.json
# copy woker
COPY --from=workerDeps /app/worker /app/worker
# copy config
COPY ./projects/$name/data/config.json /app/data/config.json
COPY ./projects/$name/data/pluginTemplates /app/data/pluginTemplates
COPY ./projects/$name/data/simpleTemplates /app/data/simpleTemplates
COPY ./projects/$name/data /app/data
ENV NODE_ENV production

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

View File

@@ -9,32 +9,50 @@ weight: 106
在知识库搜索的方式上FastGPT提供了三种方式分别为“语义检索”“增强语义检索”“混合检索”。
![](/imgs/data_search1.png)
![](/imgs/dataset_search_params1.png)
## 语义检索
## 搜索模式
语义检索就是向量检索,同时把用户的问题和知识库内容向量化,然后通过“语义相关度匹配”的方式从知识库中查找到匹配的知识点。
### 语义检索
语义检索是通过向量距离,计算用户问题与知识库内容的距离,从而得出“相似度”,当然这并不是语文上的相似度,而是数学上的。
优点:
- 相近语义理解
- 跨多语言理解(例如输入中文问题匹配英文知识点)
- 多模态理解(文本,图片,音视频等)
## 增强语义检索
缺点:
- 依赖模型训练效果
- 精度不稳定
- 受关键词和句子完整度影响
在语义检索的基础上,增强“语义相关度匹配”并在搜索结束后进行 Rerank重排
### 全文检索
Rerank重排把检索结果按“与用户问题语义”相关性从高到低排序简单的说就是把最匹配用户问题的检索结果排在前面
才用传统的全文检索方式。适合查找关键的主谓语等
## 混合检索(推荐)
### 混合检索
同时使用向量检索和全文检索,并通过 RRF 公式进行两个搜索结果合并,一般情况下搜索结果会更加丰富准确。
由于混合检索后的查找范围很大,并且无法直接进行相似度过滤,通常需要进行利用重排模型进行一次结果重新排序,并利用重排的得分进行过滤。
在向量检索的同时进行全文检索,并把两项检索的结果混合一起重排,以便选中匹配用户问题的最佳结果。
全文检索:理解为全文关键词检索,通过关键词查询知识库,并返回包含关键词的文本片段。
## 结果重排
优点:
- 精确匹配姓名编号ID等
- 少量关键词匹配(当用户问题字数过少时向量检索效果非常不好)
利用`ReRank`模型对搜索结果进行重排,绝大多数情况下,可以有效提高搜索结果的准确率。不过,重排模型与问题的完整度(主谓语齐全)有一些关系,通常会先走问题补全后再进行搜索-重排。重排后可以得到一个`0-1`的得分,代表着搜索内容与问题的相关度,该分数通常比向量的得分更加精确,可以根据得分进行过滤。
混合检索结合了向量检索和全文检索的优点,并且对查询结果进行了重排,大大提高了命中率,推荐使用
FastGPT 会使用 `RRF` 对重排结果、向量搜索结果、全文检索结果进行合并,得到最终的搜索结果
## 引用上限
每次搜索最多引用`n``tokens`的内容。
之所以不采用`top k`,是发现在混合知识库(问答库、文档库)时,不同`chunk`的长度差距很大,会导致`top k`的结果不稳定,因此采用了`tokens`的方式进行引用上限的控制。
## 最低相关度
一个`0-1`的数值,会过滤掉一些低相关度的搜索结果。
该值仅在`语义检索`或使用`结果重排`时生效。

View File

@@ -9,6 +9,8 @@ weight: 105
![](/imgs/webSync1.jpg)
该功能目前仅向商业版用户开放。
## 什么是 Web 站点同步
Web 站点同步利用爬虫的技术,可以通过一个入口网站,自动捕获`同域名`下的所有网站,目前最多支持`200`个子页面。出于合规与安全角度FastGPT 仅支持`静态站点`的爬取,主要用于各个文档站点快速构建知识库。
@@ -16,7 +18,7 @@ Web 站点同步利用爬虫的技术,可以通过一个入口网站,自动
Tips: 国内的媒体站点基本不可用公众号、csdn、知乎等。可以通过终端发送`curl`请求检测是否为静态站点,例如:
```bash
curl ai.fastgpt.in
curl https://doc.fastgpt.in/docs/intro/
```
## 如何使用

View File

@@ -99,64 +99,3 @@ docker-compose up -d
目前可以通过 `ip:3000` 直接访问(注意防火墙)。登录用户名为 `root`,密码为`docker-compose.yml`环境变量里设置的 `DEFAULT_ROOT_PSW`
如果需要域名访问,请自行安装并配置 Nginx。
## QA
### 如何更新?
执行下面命令会自动拉取最新镜像,一般情况下不需要执行额外操作。
```bash
docker-compose pull
docker-compose up -d
```
### 如何自定义配置文件?
修改`config.json`文件,并执行`docker-compose up -d`重起容器。具体配置,参考[配置详解](/docs/development/configuration)。
### 如何检查自定义配置文件是否挂载
1. `docker logs fastgpt` 可以查看日志,在启动容器后,第一次请求网页,会进行配置文件读取,可以看看有没有读取成功以及有无错误日志。
2. `docker exec -it fastgpt sh` 进入 FastGPT 容器,可以通过`ls data`查看目录下是否成功挂载`config.json`文件。可通过`cat data/config.json`查看配置文件。
**可能不生效的原因**
1. 挂载目录不正确
2. 配置文件不正确,日志中会提示`invalid json`,配置文件需要是标准的 JSON 文件。
### 为什么无法连接`本地模型`镜像。
`docker-compose.yml`中使用了桥接的模式建立了`fastgpt`网络如想通过0.0.0.0或镜像名访问其它镜像,需将其它镜像也加入到网络中。
### 端口冲突怎么解决?
docker-compose 端口定义为:`映射端口:运行端口`
桥接模式下,容器运行端口不会有冲突,但是会有映射端口冲突,只需将映射端口修改成不同端口即可。
如果`容器1`需要连接`容器2`,使用`容器2:运行端口`来进行连接即可。
(自行补习 docker 基本知识)
### relation "modeldata" does not exist
PG 数据库没有连接上/初始化失败可以查看日志。FastGPT 会在每次连接上 PG 时进行表初始化,如果报错会有对应日志。
1. 检查数据库容器是否正常启动
2. 非 docker 部署的,需要手动安装 pg vector 插件
3. 查看 fastgpt 日志,有没有相关报错
### Operation `auth_codes.findOne()` buffering timed out after 10000ms
mongo连接失败检查
1. mongo 服务有没有起来(有些 cpu 不支持 AVX无法用 mongo5需要换成 mongo4.x可以dockerhub找个最新的4.x修改镜像版本重新运行
2. 环境变量账号密码注意host和port
### 错误排查方式
遇到问题先按下面方式排查。
1. `docker ps -a` 查看所有容器运行状态,检查是否全部 running如有异常尝试`docker logs 容器名`查看对应日志。
2. 不懂 docker 不要瞎改端口,只需要改`OPENAI_BASE_URL``CHAT_API_KEY`即可。

View File

@@ -0,0 +1,76 @@
---
weight: 749
title: "常见开发 & 部署问题"
description: "FastGPT 常见开发 & 部署问题"
icon: upgrade
draft: false
images: []
---
## 通用问题
### insufficient_user_quota user quota is not enough
OneAPI 账号的余额不足,默认 root 用户只有 200 刀,可以手动修改。
## Docker 部署常见问题
### 如何更新?
执行下面命令会自动拉取最新镜像,一般情况下不需要执行额外操作。
```bash
docker-compose pull
docker-compose up -d
```
### 如何自定义配置文件?
修改`config.json`文件,并执行`docker-compose up -d`重起容器。具体配置,参考[配置详解](/docs/development/configuration)。
### 如何检查自定义配置文件是否挂载
1. `docker logs fastgpt` 可以查看日志,在启动容器后,第一次请求网页,会进行配置文件读取,可以看看有没有读取成功以及有无错误日志。
2. `docker exec -it fastgpt sh` 进入 FastGPT 容器,可以通过`ls data`查看目录下是否成功挂载`config.json`文件。可通过`cat data/config.json`查看配置文件。
**可能不生效的原因**
1. 挂载目录不正确
2. 配置文件不正确,日志中会提示`invalid json`,配置文件需要是标准的 JSON 文件。
### 为什么无法连接`本地模型`镜像。
`docker-compose.yml`中使用了桥接的模式建立了`fastgpt`网络如想通过0.0.0.0或镜像名访问其它镜像,需将其它镜像也加入到网络中。
### 端口冲突怎么解决?
docker-compose 端口定义为:`映射端口:运行端口`
桥接模式下,容器运行端口不会有冲突,但是会有映射端口冲突,只需将映射端口修改成不同端口即可。
如果`容器1`需要连接`容器2`,使用`容器2:运行端口`来进行连接即可。
(自行补习 docker 基本知识)
### relation "modeldata" does not exist
PG 数据库没有连接上/初始化失败可以查看日志。FastGPT 会在每次连接上 PG 时进行表初始化,如果报错会有对应日志。
1. 检查数据库容器是否正常启动
2. 非 docker 部署的,需要手动安装 pg vector 插件
3. 查看 fastgpt 日志,有没有相关报错
### Operation `auth_codes.findOne()` buffering timed out after 10000ms
mongo连接失败检查
1. mongo 服务有没有起来(有些 cpu 不支持 AVX无法用 mongo5需要换成 mongo4.x可以dockerhub找个最新的4.x修改镜像版本重新运行
2. 环境变量账号密码注意host和port
### 错误排查方式
遇到问题先按下面方式排查。
1. `docker ps -a` 查看所有容器运行状态,检查是否全部 running如有异常尝试`docker logs 容器名`查看对应日志。
2. 不懂 docker 不要瞎改端口,只需要改`OPENAI_BASE_URL``CHAT_API_KEY`即可。

View File

@@ -1,10 +1,10 @@
---
title: 'V4.6.6(需要改配置文件)'
title: 'V4.6.6-alpha(需要改配置文件)'
description: 'FastGPT V4.6.6'
icon: 'upgrade'
draft: false
toc: true
weight: 831
weight: 830
---
**版本仍在开发中……**

View File

@@ -48,6 +48,8 @@ export type FastGPTFeConfigsType = {
};
scripts?: { [key: string]: string }[];
favicon?: string;
customApiDomain?: string;
customSharePageDomain?: string;
};
export type SystemEnvType = {

View File

@@ -0,0 +1 @@
export const PgDatasetTableName = 'modeldata';

View File

@@ -3,7 +3,8 @@ export type LLMModelItemType = {
name: string;
maxContext: number;
maxResponse: number;
price: number;
inputPrice: number;
outputPrice: number;
};
export type ChatModelItemType = LLMModelItemType & {
quoteMaxToken: number;
@@ -22,7 +23,8 @@ export type VectorModelItemType = {
model: string;
name: string;
defaultToken: number;
price: number;
inputPrice: number;
outputPrice: number;
maxToken: number;
weight: number;
};
@@ -30,7 +32,8 @@ export type VectorModelItemType = {
export type ReRankModelItemType = {
model: string;
name: string;
price: number;
inputPrice: number;
outputPrice?: number;
requestUrl?: string;
requestAuth?: string;
};
@@ -38,12 +41,14 @@ export type ReRankModelItemType = {
export type AudioSpeechModelType = {
model: string;
name: string;
price: number;
inputPrice: number;
outputPrice?: number;
voices: { label: string; value: string; bufferId: string }[];
};
export type WhisperModelType = {
model: string;
name: string;
price: number;
inputPrice: number;
outputPrice?: number;
};

View File

@@ -6,7 +6,8 @@ export const defaultQAModels: LLMModelItemType[] = [
name: 'GPT35-16k',
maxContext: 16000,
maxResponse: 16000,
price: 0
inputPrice: 0,
outputPrice: 0
}
];
@@ -14,7 +15,8 @@ export const defaultVectorModels: VectorModelItemType[] = [
{
model: 'text-embedding-ada-002',
name: 'Embedding-2',
price: 0,
inputPrice: 0,
outputPrice: 0,
defaultToken: 500,
maxToken: 3000,
weight: 100

View File

@@ -65,6 +65,7 @@ export type AppSimpleEditFormType = {
similarity: number;
limit: number;
searchMode: `${DatasetSearchModeEnum}`;
usingReRank: boolean;
searchEmptyText: string;
};
cfr: {
@@ -112,6 +113,7 @@ export type AppSimpleEditConfigTemplateType = {
similarity?: boolean;
limit?: boolean;
searchMode: `${DatasetSearchModeEnum}`;
usingReRank: boolean;
searchEmptyText?: boolean;
};
cfr?: {

View File

@@ -26,7 +26,8 @@ export const getDefaultAppForm = (templateId = 'fastgpt-universal'): AppSimpleEd
similarity: 0.4,
limit: 1500,
searchEmptyText: '',
searchMode: DatasetSearchModeEnum.embedding
searchMode: DatasetSearchModeEnum.embedding,
usingReRank: false
},
userGuide: {
welcomeText: '',
@@ -95,6 +96,10 @@ export const appModules2Form = ({
defaultAppForm.dataset.searchMode =
findInputValueByKey(module.inputs, ModuleInputKeyEnum.datasetSearchMode) ||
DatasetSearchModeEnum.embedding;
defaultAppForm.dataset.usingReRank = !!findInputValueByKey(
module.inputs,
ModuleInputKeyEnum.datasetSearchUsingReRank
);
// empty text
const emptyOutputs =

View File

@@ -89,7 +89,8 @@ export type moduleDispatchResType = {
moduleLogo?: string;
price?: number;
runningTime?: number;
tokens?: number;
inputTokens?: number;
outputTokens?: number;
model?: string;
query?: string;
contextTotalLen?: number;
@@ -105,6 +106,7 @@ export type moduleDispatchResType = {
similarity?: number;
limit?: number;
searchMode?: `${DatasetSearchModeEnum}`;
searchUsingReRank?: boolean;
// cq
cqList?: ClassifyQuestionAgentItemType[];
@@ -124,6 +126,9 @@ export type moduleDispatchResType = {
// tf switch
tfSwitchResult?: boolean;
// abandon
tokens?: number;
};
export type ChatHistoryItemResType = moduleDispatchResType & {

View File

@@ -1,5 +1,3 @@
export const PgDatasetTableName = 'modeldata';
/* ------------ dataset -------------- */
export enum DatasetTypeEnum {
folder = 'folder',
@@ -119,8 +117,8 @@ export const TrainingTypeMap = {
/* ------------ search -------------- */
export enum DatasetSearchModeEnum {
embedding = 'embedding',
embeddingReRank = 'embeddingReRank',
embFullTextReRank = 'embFullTextReRank'
fullTextRecall = 'fullTextRecall',
mixedRecall = 'mixedRecall'
}
export const DatasetSearchModeMap = {
@@ -130,18 +128,25 @@ export const DatasetSearchModeMap = {
desc: 'core.dataset.search.mode.embedding desc',
value: DatasetSearchModeEnum.embedding
},
[DatasetSearchModeEnum.embeddingReRank]: {
icon: 'core/dataset/modeEmbeddingRerank',
title: 'core.dataset.search.mode.embeddingReRank',
desc: 'core.dataset.search.mode.embeddingReRank desc',
value: DatasetSearchModeEnum.embeddingReRank
[DatasetSearchModeEnum.fullTextRecall]: {
icon: 'core/dataset/fullTextRecall',
title: 'core.dataset.search.mode.fullTextRecall',
desc: 'core.dataset.search.mode.fullTextRecall desc',
value: DatasetSearchModeEnum.fullTextRecall
},
[DatasetSearchModeEnum.embFullTextReRank]: {
icon: 'core/dataset/modeEmbFTRerank',
title: 'core.dataset.search.mode.embFullTextReRank',
desc: 'core.dataset.search.mode.embFullTextReRank desc',
value: DatasetSearchModeEnum.embFullTextReRank
[DatasetSearchModeEnum.mixedRecall]: {
icon: 'core/dataset/mixedRecall',
title: 'core.dataset.search.mode.mixedRecall',
desc: 'core.dataset.search.mode.mixedRecall desc',
value: DatasetSearchModeEnum.mixedRecall
}
};
export enum SearchScoreTypeEnum {
embedding = 'embedding',
fullText = 'fullText',
reRank = 'reRank',
rrf = 'rrf'
}
export const FolderAvatarSrc = '/imgs/files/folder.svg';

View File

@@ -6,6 +6,7 @@ import {
DatasetDataIndexTypeEnum,
DatasetStatusEnum,
DatasetTypeEnum,
SearchScoreTypeEnum,
TrainingModeEnum
} from './constant';
@@ -161,5 +162,6 @@ export type DatasetFileSchema = {
/* ============= search =============== */
export type SearchDataResponseItemType = Omit<DatasetDataItemType, 'isOwner' | 'canWrite'> & {
score: number;
score: { type: `${SearchScoreTypeEnum}`; value: number; index: number }[];
// score: number;
};

View File

@@ -63,6 +63,7 @@ export enum ModuleInputKeyEnum {
datasetSimilarity = 'similarity',
datasetLimit = 'limit',
datasetSearchMode = 'searchMode',
datasetSearchUsingReRank = 'usingReRank',
datasetParamsModal = 'datasetParamsModal',
// context extract

View File

@@ -64,12 +64,21 @@ export const DatasetSearchModule: FlowModuleTemplateType = {
{
key: ModuleInputKeyEnum.datasetSearchMode,
type: FlowNodeInputTypeEnum.hidden,
label: 'core.dataset.search.Mode',
label: '',
valueType: ModuleIOValueTypeEnum.string,
showTargetInApp: false,
showTargetInPlugin: false,
value: DatasetSearchModeEnum.embedding
},
{
key: ModuleInputKeyEnum.datasetSearchUsingReRank,
type: FlowNodeInputTypeEnum.hidden,
label: '',
valueType: ModuleIOValueTypeEnum.boolean,
showTargetInApp: false,
showTargetInPlugin: false,
value: false
},
{
key: ModuleInputKeyEnum.datasetParamsModal,
type: FlowNodeInputTypeEnum.selectDatasetParamsModal,

View File

@@ -9,6 +9,7 @@ export type TeamSchema = {
createTime: Date;
balance: number;
maxSize: number;
lastDatasetBillTime: Date;
};
export type TeamMemberSchema = {

View File

@@ -1,5 +1,5 @@
import { BillSourceEnum } from './constants';
import { BillListItemType } from './type';
import { BillListItemCountType, BillListItemType } from './type';
export type CreateTrainingBillProps = {
name: string;
@@ -7,13 +7,12 @@ export type CreateTrainingBillProps = {
agentModel?: string;
};
export type ConcatBillProps = {
export type ConcatBillProps = BillListItemCountType & {
teamId: string;
tmbId: string;
billId?: string;
total: number;
listIndex?: number;
tokens?: number;
};
export type CreateBillProps = {

View File

@@ -1,16 +1,19 @@
// ¥1 = 100000
// model price: xxx/1k tokens
// ¥1 = 100000.
export const PRICE_SCALE = 100000;
export enum BillSourceEnum {
fastgpt = 'fastgpt',
api = 'api',
shareLink = 'shareLink',
training = 'training'
training = 'training',
datasetStore = 'datasetStore'
}
export const BillSourceMap: Record<`${BillSourceEnum}`, string> = {
[BillSourceEnum.fastgpt]: '在线使用',
[BillSourceEnum.api]: 'Api',
[BillSourceEnum.shareLink]: '免登录链接',
[BillSourceEnum.training]: '数据训练'
[BillSourceEnum.training]: '数据训练',
[BillSourceEnum.datasetStore]: '知识库存储'
};

View File

@@ -6,9 +6,12 @@ import { AuthUserTypeEnum } from '../../permission/constant';
/**
* dataset price / PRICE_SCALE = real price
*/
export const formatPrice = (val = 0, multiple = 1) => {
export const formatStorePrice2Read = (val = 0, multiple = 1) => {
return Number(((val / PRICE_SCALE) * multiple).toFixed(10));
};
export const formatModelPrice2Read = (val = 0) => {
return Number((val / 1000).toFixed(10));
};
export const getBillSourceByAuthType = ({
shareId,

View File

@@ -1,11 +1,20 @@
import { CreateBillProps } from './api';
import { BillSourceEnum } from './constants';
export type BillListItemType = {
export type BillListItemCountType = {
inputTokens?: number;
outputTokens?: number;
textLen?: number;
duration?: number;
dataLen?: number;
// abandon
tokenLen?: number;
};
export type BillListItemType = BillListItemCountType & {
moduleName: string;
amount: number;
model?: string;
tokenLen?: number;
};
export type BillSchema = CreateBillProps & {

View File

@@ -1,5 +0,0 @@
import type { Pool } from 'pg';
declare global {
var pgClient: Pool | null;
}

View File

@@ -4,11 +4,13 @@ import dayjs from 'dayjs';
export const addLog = {
log(level: 'info' | 'warn' | 'error', msg: string, obj: Record<string, any> = {}) {
console.log(
`[${level.toLocaleUpperCase()}] ${dayjs().format(
'YYYY-MM-DD HH:mm:ss'
)} ${msg}: ${JSON.stringify(obj)}`
`[${level.toLocaleUpperCase()}] ${dayjs().format('YYYY-MM-DD HH:mm:ss')} ${msg} ${
level !== 'error' ? JSON.stringify(obj) : ''
}`
);
level === 'error' && console.error(obj);
const lokiUrl = process.env.LOKI_LOG_URL as string;
if (!lokiUrl) return;

View File

@@ -0,0 +1,19 @@
export type DeleteDatasetVectorProps = {
id?: string;
datasetIds?: string[];
collectionIds?: string[];
dataIds?: string[];
};
export type InsertVectorProps = {
teamId: string;
tmbId: string;
datasetId: string;
collectionId: string;
dataId: string;
};
export type EmbeddingRecallProps = {
similarity?: number;
datasetIds: string[];
};

View File

@@ -0,0 +1,62 @@
/* vector crud */
import { PgVector } from './pg/class';
import { getVectorsByText } from '../../core/ai/embedding';
import { InsertVectorProps } from './controller.d';
const getVectorObj = () => {
return new PgVector();
};
export const initVectorStore = getVectorObj().init;
export const deleteDatasetDataVector = getVectorObj().delete;
export const recallFromVectorStore = getVectorObj().recall;
export const getVectorDataByTime = getVectorObj().getVectorDataByTime;
export const getVectorCountByTeamId = getVectorObj().getVectorCountByTeamId;
export const insertDatasetDataVector = async ({
model,
query,
...props
}: InsertVectorProps & {
query: string;
model: string;
}) => {
const { vectors, tokens } = await getVectorsByText({
model,
input: query
});
const { insertId } = await getVectorObj().insert({
...props,
vectors
});
return {
tokens,
insertId
};
};
export const updateDatasetDataVector = async ({
id,
query,
model
}: {
id: string;
query: string;
model: string;
}) => {
// get vector
const { vectors, tokens } = await getVectorsByText({
model,
input: [query]
});
await getVectorObj().update({
id,
vectors
});
return {
tokens
};
};

View File

@@ -0,0 +1,20 @@
import {
initPg,
insertDatasetDataVector,
updateDatasetDataVector,
deleteDatasetDataVector,
embeddingRecall,
getVectorDataByTime,
getVectorCountByTeamId
} from './controller';
export class PgVector {
constructor() {}
init = initPg;
insert = insertDatasetDataVector;
update = updateDatasetDataVector;
delete = deleteDatasetDataVector;
recall = embeddingRecall;
getVectorCountByTeamId = getVectorCountByTeamId;
getVectorDataByTime = getVectorDataByTime;
}

View File

@@ -0,0 +1,199 @@
/* pg vector crud */
import { PgDatasetTableName } from '@fastgpt/global/common/vectorStore/constants';
import { delay } from '@fastgpt/global/common/system/utils';
import { PgClient, connectPg } from './index';
import { PgSearchRawType } from '@fastgpt/global/core/dataset/api';
import { EmbeddingRecallItemType } from '../type';
import { DeleteDatasetVectorProps, EmbeddingRecallProps } from '../controller.d';
import dayjs from 'dayjs';
export async function initPg() {
try {
await connectPg();
await PgClient.query(`
CREATE EXTENSION IF NOT EXISTS vector;
CREATE TABLE IF NOT EXISTS ${PgDatasetTableName} (
id BIGSERIAL PRIMARY KEY,
vector VECTOR(1536) NOT NULL,
team_id VARCHAR(50) NOT NULL,
tmb_id VARCHAR(50) NOT NULL,
dataset_id VARCHAR(50) NOT NULL,
collection_id VARCHAR(50) NOT NULL,
data_id VARCHAR(50) NOT NULL,
createTime TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX IF NOT EXISTS vector_index ON ${PgDatasetTableName} USING hnsw (vector vector_ip_ops) WITH (m = 32, ef_construction = 64);
`);
console.log('init pg successful');
} catch (error) {
console.log('init pg error', error);
}
}
export const insertDatasetDataVector = async (props: {
teamId: string;
tmbId: string;
datasetId: string;
collectionId: string;
dataId: string;
vectors: number[][];
retry?: number;
}): Promise<{ insertId: string }> => {
const { dataId, teamId, tmbId, datasetId, collectionId, vectors, retry = 3 } = props;
try {
const { rows } = await PgClient.insert(PgDatasetTableName, {
values: [
[
{ key: 'vector', value: `[${vectors[0]}]` },
{ key: 'team_id', value: String(teamId) },
{ key: 'tmb_id', value: String(tmbId) },
{ key: 'dataset_id', value: datasetId },
{ key: 'collection_id', value: collectionId },
{ key: 'data_id', value: String(dataId) }
]
]
});
return {
insertId: rows[0].id
};
} catch (error) {
if (retry <= 0) {
return Promise.reject(error);
}
await delay(500);
return insertDatasetDataVector({
...props,
retry: retry - 1
});
}
};
export const updateDatasetDataVector = async (props: {
id: string;
vectors: number[][];
retry?: number;
}): Promise<void> => {
const { id, vectors, retry = 2 } = props;
try {
// update pg
await PgClient.update(PgDatasetTableName, {
where: [['id', id]],
values: [{ key: 'vector', value: `[${vectors[0]}]` }]
});
} catch (error) {
if (retry <= 0) {
return Promise.reject(error);
}
await delay(500);
return updateDatasetDataVector({
...props,
retry: retry - 1
});
}
};
export const deleteDatasetDataVector = async (
props: DeleteDatasetVectorProps & {
retry?: number;
}
): Promise<any> => {
const { id, datasetIds, collectionIds, dataIds, retry = 2 } = props;
const where = await (() => {
if (id) return `id=${id}`;
if (datasetIds) return `dataset_id IN (${datasetIds.map((id) => `'${String(id)}'`).join(',')})`;
if (collectionIds)
return `collection_id IN (${collectionIds.map((id) => `'${String(id)}'`).join(',')})`;
if (dataIds) return `data_id IN (${dataIds.map((id) => `'${String(id)}'`).join(',')})`;
return Promise.reject('deleteDatasetData: no where');
})();
try {
await PgClient.delete(PgDatasetTableName, {
where: [where]
});
} catch (error) {
if (retry <= 0) {
return Promise.reject(error);
}
await delay(500);
return deleteDatasetDataVector({
...props,
retry: retry - 1
});
}
};
export const embeddingRecall = async (
props: EmbeddingRecallProps & {
vectors: number[][];
limit: number;
retry?: number;
}
): Promise<{
results: EmbeddingRecallItemType[];
}> => {
const { vectors, limit, similarity = 0, datasetIds, retry = 2 } = props;
try {
const results: any = await PgClient.query(
`BEGIN;
SET LOCAL hnsw.ef_search = ${global.systemEnv.pgHNSWEfSearch || 100};
select id, collection_id, data_id, (vector <#> '[${vectors[0]}]') * -1 AS score
from ${PgDatasetTableName}
where dataset_id IN (${datasetIds.map((id) => `'${String(id)}'`).join(',')})
AND vector <#> '[${vectors[0]}]' < -${similarity}
order by score desc limit ${limit};
COMMIT;`
);
const rows = results?.[2]?.rows as PgSearchRawType[];
// concat same data_id
const filterRows: PgSearchRawType[] = [];
let set = new Set<string>();
for (const row of rows) {
if (!set.has(row.data_id)) {
filterRows.push(row);
set.add(row.data_id);
}
}
return {
results: filterRows.map((item) => ({
id: item.id,
collectionId: item.collection_id,
dataId: item.data_id,
score: item.score
}))
};
} catch (error) {
if (retry <= 0) {
return Promise.reject(error);
}
return embeddingRecall(props);
}
};
// bill
export const getVectorCountByTeamId = async (teamId: string) => {
const total = await PgClient.count(PgDatasetTableName, {
where: [['team_id', String(teamId)]]
});
return total;
};
export const getVectorDataByTime = async (start: Date, end: Date) => {
const { rows } = await PgClient.query<{ id: string; data_id: string }>(`SELECT id, data_id
FROM ${PgDatasetTableName}
WHERE createTime BETWEEN '${dayjs(start).format('YYYY-MM-DD')}' AND '${dayjs(end).format(
'YYYY-MM-DD 23:59:59'
)}';
`);
return rows.map((item) => ({
id: item.id,
dataId: item.data_id
}));
};

View File

@@ -1,6 +1,5 @@
import { Pool } from 'pg';
import type { QueryResultRow } from 'pg';
import { PgDatasetTableName } from '@fastgpt/global/core/dataset/constant';
export const connectPg = async (): Promise<Pool> => {
if (global.pgClient) {
@@ -117,6 +116,7 @@ class PgClass {
FROM ${table}
${this.getWhereStr(props.where)}
`;
const pg = await connectPg();
return pg.query(sql).then((res) => Number(res.rows[0]?.count || 0));
}
@@ -160,29 +160,5 @@ class PgClass {
}
}
export async function initPg() {
try {
await connectPg();
await PgClient.query(`
CREATE EXTENSION IF NOT EXISTS vector;
CREATE TABLE IF NOT EXISTS ${PgDatasetTableName} (
id BIGSERIAL PRIMARY KEY,
vector VECTOR(1536) NOT NULL,
team_id VARCHAR(50) NOT NULL,
tmb_id VARCHAR(50) NOT NULL,
dataset_id VARCHAR(50) NOT NULL,
collection_id VARCHAR(50) NOT NULL,
data_id VARCHAR(50) NOT NULL,
createTime TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX IF NOT EXISTS vector_index ON ${PgDatasetTableName} USING hnsw (vector vector_ip_ops) WITH (m = 32, ef_construction = 64);
`);
console.log('init pg successful');
} catch (error) {
console.log('init pg error', error);
}
}
export const PgClient = new PgClass();
export const Pg = global.pgClient;

View File

@@ -0,0 +1,12 @@
import type { Pool } from 'pg';
declare global {
var pgClient: Pool | null;
}
export type EmbeddingRecallItemType = {
id: string;
collectionId: string;
dataId: string;
score: number;
};

View File

@@ -1,4 +1,4 @@
import { getAIApi } from '@fastgpt/service/core/ai/config';
import { getAIApi } from '../config';
export type GetVectorProps = {
model: string;
@@ -10,23 +10,23 @@ export async function getVectorsByText({
model = 'text-embedding-ada-002',
input
}: GetVectorProps) {
try {
if (typeof input === 'string' && !input) {
return Promise.reject({
code: 500,
message: 'input is empty'
});
} else if (Array.isArray(input)) {
for (let i = 0; i < input.length; i++) {
if (!input[i]) {
return Promise.reject({
code: 500,
message: 'input array is empty'
});
}
if (typeof input === 'string' && !input) {
return Promise.reject({
code: 500,
message: 'input is empty'
});
} else if (Array.isArray(input)) {
for (let i = 0; i < input.length; i++) {
if (!input[i]) {
return Promise.reject({
code: 500,
message: 'input array is empty'
});
}
}
}
try {
// 获取 chatAPI
const ai = getAIApi();
@@ -46,7 +46,7 @@ export async function getVectorsByText({
return Promise.reject(res.data?.err?.message || 'Embedding API Error');
}
return {
tokenLen: res.usage.total_tokens || 0,
tokens: res.usage.total_tokens || 0,
vectors: await Promise.all(res.data.map((item) => unityDimensional(item.embedding)))
};
});

View File

@@ -26,7 +26,8 @@ export async function createQuestionGuide({
});
const answer = data.choices?.[0]?.message?.content || '';
const totalTokens = data.usage?.total_tokens || 0;
const inputTokens = data.usage?.prompt_tokens || 0;
const outputTokens = data.usage?.completion_tokens || 0;
const start = answer.indexOf('[');
const end = answer.lastIndexOf(']');
@@ -34,7 +35,8 @@ export async function createQuestionGuide({
if (start === -1 || end === -1) {
return {
result: [],
tokens: totalTokens
inputTokens,
outputTokens
};
}
@@ -46,12 +48,14 @@ export async function createQuestionGuide({
try {
return {
result: JSON.parse(jsonStr),
tokens: totalTokens
inputTokens,
outputTokens
};
} catch (error) {
return {
result: [],
tokens: totalTokens
inputTokens,
outputTokens
};
}
}

View File

@@ -1,11 +1,11 @@
import { MongoDatasetData } from './schema';
import { deletePgDataById } from './pg';
import { MongoDatasetTraining } from '../training/schema';
import { delFileByFileIdList, delFileByMetadata } from '../../../common/file/gridfs/controller';
import { BucketNameEnum } from '@fastgpt/global/common/file/constants';
import { MongoDatasetCollection } from '../collection/schema';
import { delay } from '@fastgpt/global/common/system/utils';
import { delImgByFileIdList } from '../../../common/file/image/controller';
import { deleteDatasetDataVector } from '../../../common/vectorStore/controller';
/* delete all data by datasetIds */
export async function delDatasetRelevantData({ datasetIds }: { datasetIds: string[] }) {
@@ -21,7 +21,7 @@ export async function delDatasetRelevantData({ datasetIds }: { datasetIds: strin
// delete dataset.datas
await MongoDatasetData.deleteMany({ datasetId: { $in: datasetIds } });
// delete pg data
await deletePgDataById(`dataset_id IN ('${datasetIds.join("','")}')`);
await deleteDatasetDataVector({ datasetIds });
// delete collections
await MongoDatasetCollection.deleteMany({
@@ -56,7 +56,7 @@ export async function delCollectionRelevantData({
// delete dataset.datas
await MongoDatasetData.deleteMany({ collectionId: { $in: collectionIds } });
// delete pg data
await deletePgDataById(`collection_id IN ('${collectionIds.join("','")}')`);
await deleteDatasetDataVector({ collectionIds });
// delete collections
await MongoDatasetCollection.deleteMany({
@@ -76,6 +76,6 @@ export async function delCollectionRelevantData({
* delete one data by mongoDataId
*/
export async function delDatasetDataByDataId(mongoDataId: string) {
await deletePgDataById(['data_id', mongoDataId]);
await deleteDatasetDataVector({ dataIds: [mongoDataId] });
await MongoDatasetData.findByIdAndDelete(mongoDataId);
}

View File

@@ -1,28 +0,0 @@
import { PgDatasetTableName } from '@fastgpt/global/core/dataset/constant';
import { delay } from '@fastgpt/global/common/system/utils';
import { PgClient } from '../../../common/pg';
export async function deletePgDataById(
where: ['id' | 'dataset_id' | 'collection_id' | 'data_id', string] | string
) {
let retry = 2;
async function deleteData(): Promise<any> {
try {
await PgClient.delete(PgDatasetTableName, {
where: [where]
});
} catch (error) {
if (--retry < 0) {
return Promise.reject(error);
}
await delay(500);
return deleteData();
}
}
await deleteData();
return {
tokenLen: 0
};
}

View File

@@ -85,7 +85,6 @@ const DatasetDataSchema = new Schema({
});
try {
DatasetDataSchema.index({ teamId: 1 });
DatasetDataSchema.index({ datasetId: 1 });
DatasetDataSchema.index({ collectionId: 1 });
DatasetDataSchema.index({ updateTime: -1 });

View File

@@ -2,7 +2,7 @@ import { connectionMongo, type Model } from '../../common/mongo';
const { Schema, model, models } = connectionMongo;
import type { OpenApiSchema } from '@fastgpt/global/support/openapi/type';
import { PRICE_SCALE } from '@fastgpt/global/support/wallet/bill/constants';
import { formatPrice } from '@fastgpt/global/support/wallet/bill/tools';
import { formatStorePrice2Read } from '@fastgpt/global/support/wallet/bill/tools';
import {
TeamCollectionName,
TeamMemberCollectionName
@@ -48,7 +48,7 @@ const OpenApiSchema = new Schema(
// total usage. value from bill total
type: Number,
default: 0,
get: (val: number) => formatPrice(val)
get: (val: number) => formatStorePrice2Read(val)
},
limit: {
expiredTime: {
@@ -59,7 +59,7 @@ const OpenApiSchema = new Schema(
type: Number,
default: -1,
set: (val: number) => val * PRICE_SCALE,
get: (val: number) => formatPrice(val)
get: (val: number) => formatStorePrice2Read(val)
}
}
},

View File

@@ -43,6 +43,7 @@ const TeamMemberSchema = new Schema({
});
try {
TeamMemberSchema.index({ teamId: 1 });
} catch (error) {
console.log(error);
}

View File

@@ -29,10 +29,14 @@ const TeamSchema = new Schema({
maxSize: {
type: Number,
default: 5
},
lastDatasetBillTime: {
type: Date
}
});
try {
TeamSchema.index({ lastDatasetBillTime: -1 });
} catch (error) {
console.log(error);
}

View File

@@ -25,14 +25,16 @@ export const createTrainingBill = async ({
{
moduleName: 'wallet.moduleName.index',
model: vectorModel,
amount: 0,
tokenLen: 0
inputTokens: 0,
outputTokens: 0,
amount: 0
},
{
moduleName: 'wallet.moduleName.qa',
model: agentModel,
amount: 0,
tokenLen: 0
inputTokens: 0,
outputTokens: 0,
amount: 0
}
],
total: 0

View File

@@ -52,7 +52,8 @@ const BillSchema = new Schema({
});
try {
BillSchema.index({ userId: 1 });
BillSchema.index({ teamId: 1 });
BillSchema.index({ tmbId: 1 });
BillSchema.index({ time: 1 }, { expireAfterSeconds: 90 * 24 * 60 * 60 });
} catch (error) {
console.log(error);

107
pnpm-lock.yaml generated
View File

@@ -346,6 +346,112 @@ importers:
specifier: 4.9.5
version: registry.npmmirror.com/typescript@4.9.5
projects/home:
dependencies:
'@chakra-ui/anatomy':
specifier: ^2.2.1
version: registry.npmmirror.com/@chakra-ui/anatomy@2.2.1
'@chakra-ui/icons':
specifier: ^2.1.1
version: registry.npmmirror.com/@chakra-ui/icons@2.1.1(@chakra-ui/system@2.6.1)(react@18.2.0)
'@chakra-ui/next-js':
specifier: ^2.1.5
version: registry.npmmirror.com/@chakra-ui/next-js@2.1.5(@chakra-ui/react@2.8.1)(@emotion/react@11.11.1)(next@13.5.2)(react@18.2.0)
'@chakra-ui/react':
specifier: ^2.8.1
version: registry.npmmirror.com/@chakra-ui/react@2.8.1(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(@types/react@18.2.0)(framer-motion@9.0.6)(react-dom@18.2.0)(react@18.2.0)
'@chakra-ui/styled-system':
specifier: ^2.9.1
version: registry.npmmirror.com/@chakra-ui/styled-system@2.9.1
'@chakra-ui/system':
specifier: ^2.6.1
version: registry.npmmirror.com/@chakra-ui/system@2.6.1(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(react@18.2.0)
'@emotion/react':
specifier: ^11.11.1
version: registry.npmmirror.com/@emotion/react@11.11.1(@types/react@18.2.0)(react@18.2.0)
'@emotion/styled':
specifier: ^11.11.0
version: registry.npmmirror.com/@emotion/styled@11.11.0(@emotion/react@11.11.1)(@types/react@18.2.0)(react@18.2.0)
axios:
specifier: ^1.5.1
version: registry.npmmirror.com/axios@1.5.1
framer-motion:
specifier: ^9.0.6
version: registry.npmmirror.com/framer-motion@9.0.6(react-dom@18.2.0)(react@18.2.0)
hyperdown:
specifier: ^2.4.29
version: registry.npmmirror.com/hyperdown@2.4.29
i18next:
specifier: ^22.5.1
version: registry.npmmirror.com/i18next@22.5.1
next:
specifier: 13.5.2
version: registry.npmmirror.com/next@13.5.2(@babel/core@7.23.6)(react-dom@18.2.0)(react@18.2.0)(sass@1.58.3)
next-i18next:
specifier: ^13.3.0
version: registry.npmmirror.com/next-i18next@13.3.0(i18next@22.5.1)(next@13.5.2)(react-i18next@12.3.1)(react@18.2.0)
nprogress:
specifier: ^0.2.0
version: registry.npmmirror.com/nprogress@0.2.0
react:
specifier: 18.2.0
version: registry.npmmirror.com/react@18.2.0
react-dom:
specifier: 18.2.0
version: registry.npmmirror.com/react-dom@18.2.0(react@18.2.0)
react-i18next:
specifier: ^12.3.1
version: registry.npmmirror.com/react-i18next@12.3.1(i18next@22.5.1)(react-dom@18.2.0)(react@18.2.0)
react-markdown:
specifier: ^8.0.7
version: registry.npmmirror.com/react-markdown@8.0.7(@types/react@18.2.0)(react@18.2.0)
remark-breaks:
specifier: ^3.0.3
version: registry.npmmirror.com/remark-breaks@3.0.3
remark-gfm:
specifier: ^3.0.1
version: registry.npmmirror.com/remark-gfm@3.0.1
request-ip:
specifier: ^3.3.0
version: registry.npmmirror.com/request-ip@3.3.0
sass:
specifier: ^1.58.3
version: registry.npmmirror.com/sass@1.58.3
devDependencies:
'@svgr/webpack':
specifier: ^6.5.1
version: registry.npmmirror.com/@svgr/webpack@6.5.1
'@types/lodash':
specifier: ^4.14.191
version: registry.npmmirror.com/@types/lodash@4.14.191
'@types/node':
specifier: ^20.8.5
version: registry.npmmirror.com/@types/node@20.8.5
'@types/nprogress':
specifier: ^0.2.0
version: registry.npmmirror.com/@types/nprogress@0.2.0
'@types/react':
specifier: 18.2.0
version: registry.npmmirror.com/@types/react@18.2.0
'@types/react-dom':
specifier: 18.2.0
version: registry.npmmirror.com/@types/react-dom@18.2.0
'@types/react-syntax-highlighter':
specifier: ^15.5.6
version: registry.npmmirror.com/@types/react-syntax-highlighter@15.5.6
'@types/request-ip':
specifier: ^0.0.37
version: registry.npmmirror.com/@types/request-ip@0.0.37
eslint:
specifier: 8.34.0
version: registry.npmmirror.com/eslint@8.34.0
eslint-config-next:
specifier: 13.1.6
version: registry.npmmirror.com/eslint-config-next@13.1.6(eslint@8.34.0)(typescript@4.9.5)
typescript:
specifier: 4.9.5
version: registry.npmmirror.com/typescript@4.9.5
packages:
registry.npmmirror.com/@aashutoshrathi/word-wrap@1.2.6:
@@ -5042,7 +5148,6 @@ packages:
resolution: {integrity: sha512-1cYJrqq9GezNFPsWTZpFut/d4CjpZqA0vhqDUPFWYKF1oIyBz5qnoYMzR+0C/T96t3ebLAC1SSnwrVOm5/j74A==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@types/nprogress/-/nprogress-0.2.0.tgz}
name: '@types/nprogress'
version: 0.2.0
dev: false
registry.npmmirror.com/@types/papaparse@5.3.7:
resolution: {integrity: sha512-f2HKmlnPdCvS0WI33WtCs5GD7X1cxzzS/aduaxSu3I7TbhWlENjSPs6z5TaB9K0J+BH1jbmqTaM+ja5puis4wg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@types/papaparse/-/papaparse-5.3.7.tgz}

View File

@@ -18,6 +18,7 @@
"similarity": false,
"limit": false,
"searchMode": false,
"usingReRank": false,
"searchEmptyText": false
},
"userGuide": {

View File

@@ -34,6 +34,7 @@ function embedChatbot() {
const iframe = document.createElement('iframe');
iframe.allow = 'fullscreen;microphone';
iframe.referrerPolicy = 'no-referrer';
iframe.title = 'FastGPT Chat Window';
iframe.id = chatWindowId;
iframe.src = botSrc;

View File

@@ -59,7 +59,6 @@
},
"chat": {
"Admin Mark Content": "Corrected response",
"Complete Response": "Complete Response",
"Confirm to clear history": "Confirm to clear history?",
"Confirm to clear share chat history": " Are you sure to delete all chats?",
"Converting to text": "Converting to text...",
@@ -134,6 +133,7 @@
"Name is empty": "Name is empty",
"New Create": "Create",
"Next Step": "Next",
"OK": "OK",
"Output": "Output",
"Params": "Params",
"Password inconsistency": "Password inconsistency",
@@ -322,7 +322,10 @@
"Read Source": "Read Source"
},
"response": {
"Complete Response": "Complete Response",
"Plugin Resonse Detail": "Plugin Detail",
"Read complete response": "Read Detail",
"Read complete response tips": "Click to see the detailed process",
"context total length": "Context Length",
"module cq": "Question classification list",
"module cq result": "Classification Result",
@@ -348,6 +351,7 @@
"module time": "Running Time",
"module tokens": "Tokens",
"plugin output": "Plugin Output",
"search using reRank": "ReRank",
"text output": "Text Output"
},
"tts": {
@@ -449,11 +453,13 @@
"Chunk Range": "Range: 100~{{max}}",
"Chunk Split": "Chunk Split",
"Chunk Split Tip": "Select the files and split the by sentences",
"Chunk length": "Chunk length",
"Csv format error": "The csv file format is incorrect, please ensure that the index and content columns are two",
"Custom split char": "Custom split char",
"Custom split char Tips": "Allows you to block according to custom delimiters. It is usually used for processed data, using specific delimiters to precisely block it.",
"Embedding Estimated Price Tips": "Index billing: {{price}}/1k tokens",
"Estimated Price": "Estimated Price",
"Estimated Price Tips": "Index generation is billed as: {{price}}/1k tokens",
"Estimated Price Tips": "QA charges\nInput: {{inputPrice}}/1k tokens\nOutput: {{outputPrice}}/1k tokens",
"Fetch Error": "Get link failed",
"Fetch Url": "Url",
"Fetch url placeholder": "Up to 10 links, one per line.",
@@ -464,11 +470,13 @@
"Import Success Tip": "The {{num}} group data is imported successfully. Please wait for training.",
"Import Tip": "This task cannot be terminated and takes some time to generate indexes. Please confirm the import. If the balance is insufficient, the unfinished task will be suspended and can continue after topping up.",
"Only Show First 50 Chunk": "Show only part",
"QA Estimated Price Tips": "QA charges\nInput: {{inputPrice}}/1k tokens\nOutput: {{outputPrice}}/1k tokens",
"QA Import": "QA Split",
"QA Import Tip": "Select the files and let the LLM automatically generate QA",
"Re Preview": "RePreview",
"Set Chunk Error": "Split chunks error",
"Total Chunk Preview": "Chunk Preview: {{totalChunks}} "
"Total Chunk Preview": "Chunk Preview: {{totalChunks}} ",
"Total tokens": "Tokens"
},
"link": "Link",
"search": {
@@ -480,14 +488,16 @@
"Min Similarity": "Min Similarity",
"Min Similarity Tips": "The similarity of different index models is different, please use the search test to select the appropriate value",
"Params Setting": "Params Setting",
"ReRank": "ReRank",
"ReRank desc": "The rearrangement model is used for secondary ranking to enhance the overall ranking.",
"Top K": "Top K",
"mode": {
"embFullTextReRank": "Hybrid search ",
"embFullTextReRank desc": "Reordering with a mixture of vector search and full-text search results by Rerank usually works best",
"embedding": "Vector search",
"embedding desc": "Direct vector topk correlation query ",
"embeddingReRank": "Enhanced semantic retrieval ",
"embeddingReRank desc": "Sort using Rerank after overperforming vector topk queries "
"embedding desc": "Use vectors for text correlation queries",
"fullTextRecall": "Full text search ",
"fullTextRecall desc": "Using traditional full-text search, suitable for finding data with specific keywords and main predicates",
"mixedRecall": "Mixedrecall",
"mixedRecall desc": "Returns the combined results of vector and full-text searches, sorted using the RRF algorithm."
},
"search mode": "Search Mode"
},
@@ -507,6 +517,11 @@
"test result tip": "The contents of the knowledge base are sorted according to their similarity to the test text, and you can adjust the corresponding text according to the test results. Note: The data in the test record may have been modified, clicking on a test data will show the latest data."
},
"training": {
"Agent queue": "QA wait list",
"Full": "Expect more than 5 minutes",
"Leisure": "Leisure",
"Vector queue": "Vector wait list",
"Waiting": "Waiting",
"Website Sync": "Website Sync",
"type chunk": "Chunk",
"type qa": "QA"
@@ -858,6 +873,7 @@
},
"support": {
"user": {
"Price": "Price",
"auth": {
"Sending Code": "Sending"
},
@@ -980,8 +996,24 @@
},
"wallet": {
"bill": {
"Ai model": "Ai Model",
"App name": "App name",
"Audio Speech": "Audio Speech",
"Bill Module": "Bill Detail",
"Data Length": "Data length",
"Dataset store": "",
"Duration": "Duration(s)",
"Input Token Length": "Input tokens",
"Module name": "Module name",
"Next Step Guide": "",
"Number": "Bill ID",
"Output Token Length": "Output tokens",
"ReRank": "ReRank",
"Source": "Source",
"Text Length": "Text length",
"Time": "Time",
"Token Length": "Tokens",
"Total": "Total",
"Whisper": "Whisper",
"bill username": "User"
},

View File

@@ -59,7 +59,6 @@
},
"chat": {
"Admin Mark Content": "纠正后的回复",
"Complete Response": "完整响应",
"Confirm to clear history": "确认清空该应用的在线聊天记录?分享和 API 调用的记录不会被清空。",
"Confirm to clear share chat history": "确认删除所有聊天记录?",
"Converting to text": "正在转换为文本...",
@@ -134,6 +133,7 @@
"Name is empty": "名称不能为空",
"New Create": "新建",
"Next Step": "下一步",
"OK": "好的",
"Output": "输出",
"Params": "参数",
"Password inconsistency": "两次密码不一致",
@@ -322,7 +322,10 @@
"Read Source": "查看来源"
},
"response": {
"Complete Response": "完整响应",
"Plugin Resonse Detail": "插件详情",
"Read complete response": "查看详情",
"Read complete response tips": "点击查看详细流程",
"context total length": "上下文总长度",
"module cq": "问题分类列表",
"module cq result": "分类结果",
@@ -348,6 +351,7 @@
"module time": "运行时长",
"module tokens": "Tokens",
"plugin output": "插件输出值",
"search using reRank": "结果重排",
"text output": "文本输出"
},
"tts": {
@@ -449,11 +453,13 @@
"Chunk Range": "范围: 100~{{max}}",
"Chunk Split": "直接分段",
"Chunk Split Tip": "选择文本文件,直接将其按分段进行处理",
"Chunk length": "分块总量",
"Csv format error": "csv 文件格式有误,请确保 index 和 content 两列",
"Custom split char": "自定义分隔符",
"Custom split char Tips": "允许你根据自定义的分隔符进行分块。通常用于已处理好的数据,使用特定的分隔符来精确分块。",
"Embedding Estimated Price Tips": "索引计费: {{price}}/1k tokens",
"Estimated Price": "预估价格",
"Estimated Price Tips": "索引生成计费为: {{price}}/1k tokens",
"Estimated Price Tips": "QA计费为\n输入: {{inputPrice}}/1k tokens\n输出: {{outputPrice}}/1k tokens",
"Fetch Error": "获取链接失败",
"Fetch Url": "网络链接",
"Fetch url placeholder": "最多10个链接每行一个。",
@@ -464,11 +470,13 @@
"Import Success Tip": "共成功导入 {{num}} 组数据,请耐心等待训练.",
"Import Tip": "该任务无法终止,需要一定时间生成索引,请确认导入。如果余额不足,未完成的任务会被暂停,充值后可继续进行。",
"Only Show First 50 Chunk": "仅展示部分",
"QA Estimated Price Tips": "QA计费为\n输入: {{inputPrice}}/1k tokens\n输出: {{outputPrice}}/1k tokens",
"QA Import": "QA拆分",
"QA Import Tip": "选择文本文件,让大模型自动生成问答对",
"Re Preview": "重新生成预览",
"Set Chunk Error": "文本分段异常",
"Total Chunk Preview": "分段预览({{totalChunks}}组)"
"Total Chunk Preview": "分段预览({{totalChunks}}组)",
"Total tokens": "总Tokens"
},
"link": "链接",
"search": {
@@ -480,14 +488,16 @@
"Min Similarity": "最低相关度",
"Min Similarity Tips": "不同索引模型的相关度有区别,请通过搜索测试来选择合适的数值,使用 ReRank 时,相关度可能会很低。",
"Params Setting": "搜索参数设置",
"ReRank": "结果重排",
"ReRank desc": "使用重排模型来进行二次排序,可增强综合排名。",
"Top K": "单次搜索上限",
"mode": {
"embFullTextReRank": "混合检索",
"embFullTextReRank desc": "使用向量检索与全文检索混合结果进行 Rerank 进行重排,相关度通常差异明显,推荐。",
"embedding": "语义检索",
"embedding desc": "直接进行向量 topk 相关性查询",
"embeddingReRank": "增强语义检索",
"embeddingReRank desc": "超额进行向量 topk 查询后再使用 Rerank 进行排序,相关度通常差异明显。"
"embedding desc": "使用向量进行文本相关性查询",
"fullTextRecall": "全文检索",
"fullTextRecall desc": "使用传统的全文检索,适合查找一些关键词和主谓语特殊的数据",
"mixedRecall": "混合检索",
"mixedRecall desc": "使用向量检索与全文检索的综合结果返回使用RRF算法进行排序。"
},
"search mode": "搜索模式"
},
@@ -507,6 +517,11 @@
"test result tip": "根据知识库内容与测试文本的相似度进行排序,你可以根据测试结果调整对应的文本。\n注意测试记录中的数据可能已经被修改过点击某条测试数据后将展示最新的数据。"
},
"training": {
"Agent queue": "QA训练排队",
"Full": "预计5分钟以上",
"Leisure": "空闲",
"Vector queue": "索引排队",
"Waiting": "预计5分钟",
"Website Sync": "Web 站点同步",
"type chunk": "直接分段",
"type qa": "问答拆分"
@@ -858,6 +873,7 @@
},
"support": {
"user": {
"Price": "计费标准",
"auth": {
"Sending Code": "正在发送"
},
@@ -980,8 +996,24 @@
},
"wallet": {
"bill": {
"Ai model": "AI模型",
"App name": "应用名",
"Audio Speech": "语音播报",
"Bill Module": "扣费模块",
"Data Length": "数据长度",
"Dataset store": "知识库存储",
"Duration": "时长(秒)",
"Input Token Length": "输入 Tokens",
"Module name": "模块名",
"Next Step Guide": "下一步指引",
"Number": "订单号",
"Output Token Length": "输出 Tokens",
"ReRank": "结果重排",
"Source": "来源",
"Text Length": "文本长度",
"Time": "生成时间",
"Token Length": "Token长度",
"Total": "总金额",
"Whisper": "语音输入",
"bill username": "用户"
},

View File

@@ -151,7 +151,7 @@ export const QuoteList = React.memo(function QuoteList({
{item.q.length + (item.a?.length || 0)}
</Flex>
</MyTooltip>
{!isShare && item.score && (
{/* {!isShare && item.score && (
<MyTooltip label={t('core.dataset.Similarity')}>
<Flex alignItems={'center'}>
<MyIcon name={'kbTest'} w={'12px'} />
@@ -167,7 +167,7 @@ export const QuoteList = React.memo(function QuoteList({
<Box>{item.score.toFixed(4)}</Box>
</Flex>
</MyTooltip>
)}
)} */}
<Box flex={1} />
{item.id && (
<MyTooltip label={t('core.dataset.data.Edit')}>

View File

@@ -205,9 +205,9 @@ const ResponseTags = ({
</Tag>
</MyTooltip>
)}
<MyTooltip label={'点击查看完整响应'}>
<MyTooltip label={t('core.chat.response.Read complete response tips')}>
<Tag colorSchema="gray" cursor={'pointer'} {...TagStyles} onClick={onOpenWholeModal}>
{t('chat.Complete Response')}
{t('core.chat.response.Read complete response')}
</Tag>
</MyTooltip>

View File

@@ -8,7 +8,7 @@ import Tabs from '../Tabs';
import MyModal from '../MyModal';
import MyTooltip from '../MyTooltip';
import { QuestionOutlineIcon } from '@chakra-ui/icons';
import { formatPrice } from '@fastgpt/global/support/wallet/bill/tools';
import { formatStorePrice2Read } from '@fastgpt/global/support/wallet/bill/tools';
import Markdown from '../Markdown';
import { QuoteList } from './QuoteModal';
import { DatasetSearchModeMap } from '@fastgpt/global/core/dataset/constant';
@@ -19,7 +19,7 @@ function Row({
rawDom
}: {
label: string;
value?: string | number;
value?: string | number | boolean;
rawDom?: React.ReactNode;
}) {
const { t } = useTranslation();
@@ -70,7 +70,7 @@ const WholeResponseModal = ({
iconSrc="/imgs/modal/wholeRecord.svg"
title={
<Flex alignItems={'center'}>
{t('chat.Complete Response')}
{t('core.chat.response.Complete Response')}
<MyTooltip label={'从左往右,为各个模块的响应顺序'}>
<QuestionOutlineIcon ml={2} />
</MyTooltip>
@@ -133,15 +133,16 @@ const ResponseBox = React.memo(function ResponseBox({
{activeModule?.price !== undefined && (
<Row
label={t('core.chat.response.module price')}
value={`${formatPrice(activeModule?.price)}`}
value={`${formatStorePrice2Read(activeModule?.price)}`}
/>
)}
<Row
label={t('core.chat.response.module time')}
value={`${activeModule?.runningTime || 0}s`}
/>
<Row label={t('core.chat.response.module tokens')} value={`${activeModule?.tokens}`} />
<Row label={t('core.chat.response.module model')} value={activeModule?.model} />
<Row label={t('wallet.bill.Input Token Length')} value={`${activeModule?.inputTokens}`} />
<Row label={t('wallet.bill.Output Token Length')} value={`${activeModule?.outputTokens}`} />
<Row label={t('core.chat.response.module query')} value={activeModule?.query} />
<Row
label={t('core.chat.response.context total length')}
@@ -193,6 +194,10 @@ const ResponseBox = React.memo(function ResponseBox({
)}
<Row label={t('core.chat.response.module similarity')} value={activeModule?.similarity} />
<Row label={t('core.chat.response.module limit')} value={activeModule?.limit} />
<Row
label={t('core.chat.response.search using reRank')}
value={activeModule?.searchUsingReRank}
/>
{/* classify question */}
<Row

View File

@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1703840539554" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="7163" xmlns:xlink="http://www.w3.org/1999/xlink" width="128" height="128"><path d="M224.63064 14.207408h566.760385a4.266489 4.266489 0 0 1 4.735803 4.735803v227.915837a4.266489 4.266489 0 0 1-4.735803 4.735802H224.63064a4.266489 4.266489 0 0 1-4.735802-4.735802V18.943211a4.266489 4.266489 0 0 1 4.735802-4.735803z" fill="#4A8BFD" p-id="7164"></path><path d="M781.876755 28.414816v209.057956H234.059581v-209.057956h547.817174M791.34836 0H224.417316a18.943211 18.943211 0 0 0-18.943211 18.943211v227.915837a18.943211 18.943211 0 0 0 18.943211 18.94321h566.760385a18.943211 18.943211 0 0 0 18.943211-18.94321V18.943211a18.943211 18.943211 0 0 0-18.772552-18.943211z" fill="#333333" p-id="7165"></path><path d="M224.63064 393.028957h566.760385a4.266489 4.266489 0 0 1 4.735803 4.735803v227.915837a4.266489 4.266489 0 0 1-4.735803 4.735802H224.63064a4.266489 4.266489 0 0 1-4.735802-4.735802V397.76476a4.266489 4.266489 0 0 1 4.735802-4.735803z" fill="#EDEDED" p-id="7166"></path><path d="M781.876755 407.535019v208.503313H234.059581V407.535019h547.817174m9.471605-28.414816H224.417316a18.943211 18.943211 0 0 0-18.943211 18.943211v227.446523a18.943211 18.943211 0 0 0 18.943211 18.943211h566.760385a18.943211 18.943211 0 0 0 18.943211-18.943211V398.063414a18.943211 18.943211 0 0 0-18.772552-18.943211z" fill="#333333" p-id="7167"></path><path d="M224.63064 771.935836h566.760385a4.266489 4.266489 0 0 1 4.735803 4.735803v227.958501a4.266489 4.266489 0 0 1-4.735803 4.735803H224.63064a4.266489 4.266489 0 0 1-4.735802-4.735803v-227.958501a4.266489 4.266489 0 0 1 4.735802-4.735803z" fill="#4A8BFD" p-id="7168"></path><path d="M781.876755 786.185909v209.057956H234.059581v-209.057956h547.817174m9.471605-28.414816H224.417316a18.943211 18.943211 0 0 0-18.943211 18.943211v227.915836a18.943211 18.943211 0 0 0 18.943211 18.943211h566.760385a18.943211 18.943211 0 0 0 18.943211-18.943211v-227.915836a18.943211 18.943211 0 0 0-18.772552-18.943211z m56.573643-251.722845a14.207408 14.207408 0 1 1 0-28.414816 147.577851 147.577851 0 0 0 0-295.113037 14.207408 14.207408 0 1 1 0-28.414816 175.992667 175.992667 0 0 1 0 351.985334z" fill="#333333" p-id="7169"></path><path d="M884.443148 530.751219a14.122078 14.122078 0 0 1-7.295696-2.00525l-41.512937-24.916295a14.207408 14.207408 0 0 1-1.791925-23.2097l41.512937-33.150619a14.207408 14.207408 0 0 1 17.705929 22.185743l-25.598934 20.393817 23.977668 14.420732a14.207408 14.207408 0 0 1-7.295696 26.452231z m-708.237157 326.215741a175.992667 175.992667 0 0 1 0-351.985334 14.207408 14.207408 0 0 1 0 28.414816 147.577851 147.577851 0 1 0 0 295.113037 14.207408 14.207408 0 0 1 0 28.414816z" fill="#333333" p-id="7170"></path><path d="M139.428857 881.413941a14.207408 14.207408 0 0 1-7.295696-26.452231l23.977668-14.420733-25.598934-20.393816a14.207408 14.207408 0 0 1 17.705929-22.185743l41.043623 33.662598a14.207408 14.207408 0 0 1-1.621265 23.295029l-41.512937 24.916295a14.079413 14.079413 0 0 1-6.698388 1.578601z" fill="#333333" p-id="7171"></path></svg>

After

Width:  |  Height:  |  Size: 3.2 KiB

View File

@@ -0,0 +1,11 @@
<?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="1703750094429"
class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4262"
xmlns:xlink="http://www.w3.org/1999/xlink" width="128" height="128">
<path
d="M512 0C229.312 0 0 229.312 0 512s229.312 512 512 512 512-229.312 512-512-229.312-512-512-512z m311.04 823.04a437.76 437.76 0 0 1-139.84 94.336c-54.208 23.04-111.808 34.624-171.2 34.624a439.36 439.36 0 0 1-311.04-128.896 437.76 437.76 0 0 1-94.336-139.904A435.776 435.776 0 0 1 72 512a439.36 439.36 0 0 1 128.896-311.04 437.76 437.76 0 0 1 139.904-94.336A435.776 435.776 0 0 1 512 72a439.36 439.36 0 0 1 311.04 128.896 437.76 437.76 0 0 1 94.336 139.904c23.04 54.208 34.624 111.808 34.624 171.2 0 59.392-11.584 116.992-34.56 171.2a436.096 436.096 0 0 1-94.336 139.904z"
p-id="4263"></path>
<path
d="M701.824 436.992h-167.04c0.704 0 1.408-0.32 2.112-1.024l118.208-117.76a35.904 35.904 0 0 0-50.816-50.56L511.872 360.064l-92.864-92.8a36.16 36.16 0 0 0-51.136 0.192 36.16 36.16 0 0 0-0.064 51.072l118.208 118.08c0.64 0.704 1.408 1.728 2.112 1.728H321.6a34.688 34.688 0 0 0-34.304 34.56v3.2c0 18.816 15.488 33.856 34.304 33.856h154.368v64H353.536c-18.752 0-33.472 16.256-33.472 35.072v4.032c0 18.752 14.72 32.896 33.472 32.896h122.56v149.184a35.968 35.968 0 1 0 72 0v-149.184h121.792c18.752 0 35.2-14.08 35.2-32.896v-4.032a35.968 35.968 0 0 0-35.2-35.072H548.032v-64h153.792c18.752 0 35.2-14.208 35.2-33.088v-4.032a36.672 36.672 0 0 0-35.2-35.84z"
p-id="4264"></path>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@@ -113,8 +113,8 @@ const iconPaths = {
'core/chat/speaking': () => import('./icons/core/chat/speaking.svg'),
'core/chat/fileSelect': () => import('./icons/core/chat/fileSelect.svg'),
'core/dataset/modeEmbedding': () => import('./icons/core/dataset/modeEmbedding.svg'),
'core/dataset/modeEmbeddingRerank': () => import('./icons/core/dataset/modeEmbeddingRerank.svg'),
'core/dataset/modeEmbFTRerank': () => import('./icons/core/dataset/modeEmbFTRerank.svg'),
'core/dataset/fullTextRecall': () => import('./icons/core/dataset/fullTextRecall.svg'),
'core/dataset/mixedRecall': () => import('./icons/core/dataset/mixedRecall.svg'),
'core/app/variable/input': () => import('./icons/core/app/variable/input.svg'),
'core/app/variable/textarea': () => import('./icons/core/app/variable/textarea.svg'),
'core/app/variable/select': () => import('./icons/core/app/variable/select.svg'),
@@ -125,7 +125,9 @@ const iconPaths = {
'common/confirm/commonTip': () => import('./icons/common/confirm/commonTip.svg'),
'common/routePushLight': () => import('./icons/common/routePushLight.svg'),
'common/viewLight': () => import('./icons/common/viewLight.svg'),
'core/app/customFeedback': () => import('./icons/core/app/customFeedback.svg')
'core/app/customFeedback': () => import('./icons/core/app/customFeedback.svg'),
'support/pay/priceLight': () => import('./icons/support/pay/priceLight.svg'),
'core/dataset/rerank': () => import('./icons/core/dataset/rerank.svg')
};
export type IconName = keyof typeof iconPaths;

View File

@@ -1,4 +1,3 @@
import React from 'react';
import { useRouter } from 'next/router';
import { useToast } from '@chakra-ui/react';
import { useUserStore } from '@/web/support/user/useUserStore';

View File

@@ -218,6 +218,9 @@
.markdown blockquote > *:last-child {
margin-bottom: 0;
}
.markdown table {
width: 100%;
}
.markdown table th {
font-weight: bold;
}

View File

@@ -0,0 +1,44 @@
import React, { useMemo } from 'react';
import MySelect, { type SelectProps } from './index';
import { useTranslation } from 'next-i18next';
import dynamic from 'next/dynamic';
import { useDisclosure } from '@chakra-ui/react';
const PriceBox = dynamic(() => import('@/components/support/wallet/Price'));
const SelectAiModel = ({ list, ...props }: SelectProps) => {
const { t } = useTranslation();
const expandList = useMemo(
() =>
list.concat({
label: t('support.user.Price'),
value: 'price'
}),
[list, t]
);
const {
isOpen: isOpenPriceBox,
onOpen: onOpenPriceBox,
onClose: onClosePriceBox
} = useDisclosure();
return (
<>
<MySelect
list={expandList}
{...props}
onchange={(e) => {
if (e === 'price') {
onOpenPriceBox();
return;
}
props.onchange?.(e);
}}
/>
{isOpenPriceBox && <PriceBox onClose={onClosePriceBox} />}
</>
);
};
export default SelectAiModel;

View File

@@ -1,17 +1,9 @@
import React, { useRef, forwardRef, useMemo } from 'react';
import {
Menu,
Box,
MenuList,
MenuItem,
Button,
useDisclosure,
useOutsideClick,
MenuButton
} from '@chakra-ui/react';
import { Menu, MenuList, MenuItem, Button, useDisclosure, MenuButton } from '@chakra-ui/react';
import type { ButtonProps } from '@chakra-ui/react';
import { ChevronDownIcon } from '@chakra-ui/icons';
interface Props extends ButtonProps {
export type SelectProps = ButtonProps & {
value?: string;
placeholder?: string;
list: {
@@ -20,10 +12,10 @@ interface Props extends ButtonProps {
value: string;
}[];
onchange?: (val: any) => void;
}
};
const MySelect = (
{ placeholder, value, width = '100%', list, onchange, ...props }: Props,
{ placeholder, value, width = '100%', list, onchange, ...props }: SelectProps,
selectRef: any
) => {
const ref = useRef<HTMLButtonElement>(null);

View File

@@ -53,7 +53,7 @@ const SideTabs = ({ list, size = 'md', activeId, onChange, ...props }: Props) =>
cursor: 'pointer'
})}
_hover={{
bg: 'myWhite.600'
bg: 'myGray.05'
}}
onClick={() => {
if (activeId === item.id) return;

View File

@@ -1,5 +1,15 @@
import React, { useMemo, useState } from 'react';
import { Box, Button, ModalBody, ModalFooter, Textarea } from '@chakra-ui/react';
import {
Box,
Button,
Checkbox,
Divider,
Flex,
ModalBody,
ModalFooter,
Textarea,
useTheme
} from '@chakra-ui/react';
import { useForm } from 'react-hook-form';
import { QuestionOutlineIcon } from '@chakra-ui/icons';
import MySlider from '@/components/Slider';
@@ -12,43 +22,58 @@ import { reRankModelList } from '@/web/common/system/staticData';
import { ModuleInputKeyEnum } from '@fastgpt/global/core/module/constants';
import { DatasetSearchModeMap } from '@fastgpt/global/core/dataset/constant';
import MyRadio from '@/components/common/MyRadio';
import MyIcon from '@/components/Icon';
type DatasetParamsProps = {
similarity?: number;
limit?: number;
searchMode: `${DatasetSearchModeEnum}`;
searchEmptyText?: string;
limit?: number;
similarity?: number;
usingReRank?: boolean;
maxTokens?: number;
};
const DatasetParamsModal = ({
searchMode = DatasetSearchModeEnum.embedding,
searchEmptyText,
limit,
similarity,
searchMode = DatasetSearchModeEnum.embedding,
usingReRank,
maxTokens = 3000,
onClose,
onSuccess
}: DatasetParamsProps & { onClose: () => void; onSuccess: (e: DatasetParamsProps) => void }) => {
const { t } = useTranslation();
const theme = useTheme();
const [refresh, setRefresh] = useState(false);
const { register, setValue, getValues, handleSubmit } = useForm<DatasetParamsProps>({
defaultValues: {
searchEmptyText,
limit,
similarity,
searchMode
searchMode,
usingReRank
}
});
const searchModeList = useMemo(() => {
const list = Object.values(DatasetSearchModeMap);
if (reRankModelList.length > 0) {
return list;
}
return list.slice(0, 1);
return list;
}, []);
const showSimilarity = useMemo(() => {
if (similarity === undefined) return false;
if (
getValues('searchMode') === DatasetSearchModeEnum.fullTextRecall &&
!getValues('usingReRank')
)
return false;
if (getValues('searchMode') === DatasetSearchModeEnum.mixedRecall && !getValues('usingReRank'))
return false;
return true;
}, [getValues, similarity, refresh]);
return (
<MyModal
isOpen={true}
@@ -57,7 +82,6 @@ const DatasetParamsModal = ({
title={t('core.dataset.search.Dataset Search Params')}
w={['90vw', '550px']}
h={['90vh', 'auto']}
overflow={'unset'}
isCentered={searchEmptyText !== undefined}
>
<ModalBody flex={['1 0 0', 'auto']} overflow={'auto'}>
@@ -71,9 +95,73 @@ const DatasetParamsModal = ({
setRefresh(!refresh);
}}
/>
{usingReRank !== undefined && reRankModelList.length > 0 && (
<>
<Divider my={4} />
<Flex
alignItems={'center'}
cursor={'pointer'}
userSelect={'none'}
py={3}
pl={'14px'}
pr={'16px'}
border={theme.borders.sm}
borderWidth={'1.5px'}
borderRadius={'md'}
position={'relative'}
{...(getValues('usingReRank')
? {
borderColor: 'primary.400'
}
: {})}
onClick={(e) => {
setValue('usingReRank', !getValues('usingReRank'));
setRefresh((state) => !state);
}}
>
<MyIcon name="core/dataset/rerank" w={'18px'} mr={'14px'} />
<Box pr={2} color={'myGray.800'} flex={'1 0 0'}>
<Box>{t('core.dataset.search.ReRank')}</Box>
<Box fontSize={['xs', 'sm']} color={'myGray.500'}>
{t('core.dataset.search.ReRank desc')}
</Box>
</Box>
<Box position={'relative'} w={'18px'} h={'18px'}>
<Checkbox colorScheme="primary" isChecked={getValues('usingReRank')} size="lg" />
<Box position={'absolute'} top={0} right={0} bottom={0} left={0} zIndex={1}></Box>
</Box>
</Flex>
</>
)}
{similarity !== undefined && (
{limit !== undefined && (
<Box display={['block', 'flex']} py={8} mt={3}>
<Box flex={'0 0 100px'} mb={[8, 0]}>
{t('core.dataset.search.Max Tokens')}
<MyTooltip label={t('core.dataset.search.Max Tokens Tips')} forceShow>
<QuestionOutlineIcon ml={1} />
</MyTooltip>
</Box>
<Box flex={1} mx={4}>
<MySlider
markList={[
{ label: '100', value: 100 },
{ label: maxTokens, value: maxTokens }
]}
min={100}
max={maxTokens}
step={50}
value={getValues(ModuleInputKeyEnum.datasetLimit) ?? 1000}
onChange={(val) => {
setValue(ModuleInputKeyEnum.datasetLimit, val);
setRefresh(!refresh);
}}
/>
</Box>
</Box>
)}
{showSimilarity && (
<Box display={['block', 'flex']} py={8}>
<Box flex={'0 0 100px'} mb={[8, 0]}>
{t('core.dataset.search.Min Similarity')}
<MyTooltip label={t('core.dataset.search.Min Similarity Tips')} forceShow>
@@ -98,32 +186,7 @@ const DatasetParamsModal = ({
</Box>
</Box>
)}
{limit !== undefined && (
<Box display={['block', 'flex']} py={8}>
<Box flex={'0 0 100px'} mb={[8, 0]}>
{t('core.dataset.search.Max Tokens')}
<MyTooltip label={t('core.dataset.search.Max Tokens Tips')} forceShow>
<QuestionOutlineIcon ml={1} />
</MyTooltip>
</Box>
<Box flex={1} mx={4}>
<MySlider
markList={[
{ label: '300', value: 300 },
{ label: maxTokens, value: maxTokens }
]}
min={300}
max={maxTokens}
step={10}
value={getValues(ModuleInputKeyEnum.datasetLimit) ?? 1000}
onChange={(val) => {
setValue(ModuleInputKeyEnum.datasetLimit, val);
setRefresh(!refresh);
}}
/>
</Box>
</Box>
)}
{searchEmptyText !== undefined && (
<Box display={['block', 'flex']} pt={3}>
<Box flex={'0 0 100px'} mb={[2, 0]}>

View File

@@ -8,7 +8,7 @@ import MySelect from '@/components/Select';
import { TTSTypeEnum } from '@/constants/app';
import type { AppTTSConfigType } from '@fastgpt/global/core/module/type.d';
import { useAudioPlay } from '@/web/common/utils/voice';
import { audioSpeechModels } from '@/web/common/system/staticData';
import { audioSpeechModelList } from '@/web/common/system/staticData';
import MyModal from '@/components/MyModal';
import MySlider from '@/components/Slider';
@@ -26,7 +26,7 @@ const TTSSelect = ({
() => [
{ label: t('core.app.tts.Close'), value: TTSTypeEnum.none },
{ label: t('core.app.tts.Web'), value: TTSTypeEnum.web },
...audioSpeechModels.map((item) => item?.voices || []).flat()
...audioSpeechModelList.map((item) => item?.voices || []).flat()
],
[t]
);
@@ -52,7 +52,7 @@ const TTSSelect = ({
if (e === TTSTypeEnum.none || e === TTSTypeEnum.web) {
onChange({ type: e as `${TTSTypeEnum}` });
} else {
const audioModel = audioSpeechModels.find(
const audioModel = audioSpeechModelList.find(
(item) => item.voices?.find((voice) => voice.value === e)
);
if (!audioModel) {

View File

@@ -1,10 +1,9 @@
import React, { useCallback, useEffect } from 'react';
import type { RenderInputProps } from '../type';
import { onChangeNode } from '../../../../FlowProvider';
import MySelect from '@/components/Select';
import SelectAiModel from '@/components/Select/SelectAiModel';
import { FlowNodeInputTypeEnum } from '@fastgpt/global/core/module/node/constant';
import { chatModelList, cqModelList, extractModelList } from '@/web/common/system/staticData';
import { formatPrice } from '@fastgpt/global/support/wallet/bill/tools';
const SelectAiModelRender = ({ item, inputs = [], moduleId }: RenderInputProps) => {
const modelList = (() => {
@@ -16,8 +15,7 @@ const SelectAiModelRender = ({ item, inputs = [], moduleId }: RenderInputProps)
})().map((item) => ({
model: item.model,
name: item.name,
maxResponse: item.maxResponse,
price: item.price
maxResponse: item.maxResponse
}));
const onChangeModel = useCallback(
@@ -55,11 +53,9 @@ const SelectAiModelRender = ({ item, inputs = [], moduleId }: RenderInputProps)
);
const list = modelList.map((item) => {
const priceStr = `(${formatPrice(item.price, 1000)}元/1k Tokens)`;
return {
value: item.model,
label: `${item.name}${priceStr}`
label: item.name
};
});
@@ -70,7 +66,7 @@ const SelectAiModelRender = ({ item, inputs = [], moduleId }: RenderInputProps)
}, [item.value, list, onChangeModel]);
return (
<MySelect
<SelectAiModel
minW={'350px'}
width={'100%'}
value={item.value}

View File

@@ -17,7 +17,8 @@ const SelectDatasetParam = ({ inputs = [], moduleId }: RenderInputProps) => {
const [data, setData] = useState({
searchMode: DatasetSearchModeEnum.embedding,
limit: 5,
similarity: 0.5
similarity: 0.5,
usingReRank: false
});
const tokenLimit = useMemo(() => {

View File

@@ -72,7 +72,7 @@ const ApiKeyTable = ({ tips, appId }: { tips: string; appId?: string }) => {
} = useQuery(['getOpenApiKeys', appId], () => getOpenApiKeys({ appId }));
useEffect(() => {
setBaseUrl(`${location.origin}/api`);
setBaseUrl(feConfigs?.customApiDomain || `${location.origin}/api`);
}, []);
return (
@@ -255,7 +255,7 @@ const ApiKeyTable = ({ tips, appId }: { tips: string; appId?: string }) => {
</ModalBody>
<ModalFooter>
<Button variant="whiteBase" onClick={() => setApiKey('')}>
{t('common.OK')}
</Button>
</ModalFooter>
</MyModal>

View File

@@ -0,0 +1,168 @@
import React from 'react';
import { Box, CloseButton } from '@chakra-ui/react';
import {
chatModelList,
vectorModelList,
qaModelList,
cqModelList,
extractModelList,
qgModelList,
audioSpeechModelList,
reRankModelList,
whisperModel
} from '@/web/common/system/staticData';
import ReactDOM from 'react-dom';
import Markdown from '@/components/Markdown';
const Price = ({ onClose }: { onClose: () => void }) => {
const list = [
{
title: '知识库存储',
describe: '',
md: `
| 计费项 | 价格(¥) |
| --- | --- |
| 知识库索引数量 | 0/1000条/天 |`
},
{
title: '对话模型',
describe: '',
md: `
| 模型 | 输入价格(¥) | 输出价格(¥) |
| --- | --- | --- |
${chatModelList
?.map((item) => `| ${item.name} | ${item.inputPrice}/1k tokens | ${item.outputPrice}/1k tokens |`)
.join('\n')}`
},
{
title: '索引模型(文档训练 & 文档检索)',
describe: '',
md: `
| 模型 | 价格(¥) |
| --- | --- |
${vectorModelList?.map((item) => `| ${item.name} | ${item.inputPrice}/1k tokens |`).join('\n')}
`
},
{
title: '文件预处理模型(QA 拆分)',
describe: '',
md: `
| 模型 | 输入价格(¥) | 输出价格(¥) |
| --- | --- | --- |
${qaModelList
?.map(
(item) => `| ${item.name} | ${item.inputPrice}/1k tokens | ${item.outputPrice}/1k tokens |`
)
.join('\n')}
`
},
{
title: '问题分类',
describe: '',
md: `
| 模型 | 输入价格(¥) | 输出价格(¥) |
| --- | --- | --- |
${cqModelList
?.map(
(item) => `| ${item.name} | ${item.inputPrice}/1k tokens | ${item.outputPrice}/1k tokens |`
)
.join('\n')}`
},
{
title: '内容提取',
describe: '',
md: `
| 模型 | 输入价格(¥) | 输出价格(¥) |
| --- | --- | --- |
${extractModelList
?.map(
(item) => `| ${item.name} | ${item.inputPrice}/1k tokens | ${item.outputPrice}/1k tokens |`
)
.join('\n')}`
},
{
title: '下一步指引',
describe: '',
md: `
| 模型 | 输入价格(¥) | 输出价格(¥) |
| --- | --- | --- |
${qgModelList
?.map(
(item) => `| ${item.name} | ${item.inputPrice}/1k tokens | ${item.outputPrice}/1k tokens |`
)
.join('\n')}`
},
{
title: '重排模型(增强检索 & 混合检索)',
describe: '',
md: `
| 模型 | 价格(¥) |
| --- | --- |
${reRankModelList?.map((item) => `| ${item.name} | ${item.inputPrice}/1k 字符 |`).join('\n')}`
},
{
title: '语音播放',
describe: '',
md: `
| 模型 | 价格(¥) |
| --- | --- |
${audioSpeechModelList
?.map((item) => `| ${item.name} | ${item.inputPrice}/1k 字符 | - |`)
.join('\n')}`
},
...(whisperModel
? [
{
title: '语音输入',
describe: '',
md: `
| 模型 | 价格(¥) |
| --- | --- |
| ${whisperModel.name} | ${whisperModel.inputPrice}/分钟 | - |`
}
]
: [])
];
return ReactDOM.createPortal(
<Box position={'fixed'} top={0} right={0} bottom={0} left={0} zIndex={99999} bg={'white'}>
<CloseButton
position={'absolute'}
top={'10px'}
right={'20px'}
bg={'myGray.200'}
w={'30px'}
h={'30px'}
borderRadius={'50%'}
onClick={onClose}
/>
<Box py={[0, 10]} px={[5, '50px']} overflow={'overlay'} h={'100%'}>
{list.map((item) => (
<Box
display={['block', 'flex']}
key={item.title}
w={'100%'}
mb={4}
pb={6}
_notLast={{
borderBottom: '1px',
borderBottomColor: 'borderColor.high'
}}
>
<Box fontSize={'xl'} fontWeight={'bold'} mb={1} flex={'1 0 0'}>
{item.title}
</Box>
<Box w={['100%', '410px']}>
<Markdown source={item.md}></Markdown>
</Box>
</Box>
))}
</Box>
</Box>,
// @ts-ignore
document.querySelector('body')
);
};
export default Price;

View File

@@ -19,8 +19,9 @@ export type InitDateResponse = {
vectorModels: VectorModelItemType[];
audioSpeechModels: AudioSpeechModels[];
reRankModels: ReRankModelItemType[];
qgModes: LLMModelItemType[];
whisperModel: WhisperModelType;
feConfigs: FastGPTFeConfigsType;
priceMd: string;
systemVersion: string;
simpleModeTemplates: AppSimpleEditConfigTemplateType[];
};

View File

@@ -22,6 +22,7 @@ export const SimpleModeTemplate_FastGPT_Universal: AppSimpleEditConfigTemplateTy
similarity: true,
limit: true,
searchMode: DatasetSearchModeEnum.embedding,
usingReRank: true,
searchEmptyText: true
},
userGuide: {

View File

@@ -43,12 +43,22 @@ export type UpdateDatasetDataProps = {
})[];
};
export type GetTrainingQueueProps = {
vectorModel: string;
agentModel: string;
};
export type GetTrainingQueueResponse = {
vectorTrainingCount: number;
agentTrainingCount: number;
};
/* -------------- search ---------------- */
export type SearchTestProps = {
datasetId: string;
text: string;
limit?: number;
searchMode?: `${DatasetSearchModeEnum}`;
usingReRank: boolean;
};
export type SearchTestResponse = {
list: SearchDataResponseItemType[];

View File

@@ -14,7 +14,7 @@ import {
import { BillItemType } from '@fastgpt/global/support/wallet/bill/type.d';
import dayjs from 'dayjs';
import { BillSourceMap } from '@fastgpt/global/support/wallet/bill/constants';
import { formatPrice } from '@fastgpt/global/support/wallet/bill/tools';
import { formatStorePrice2Read } from '@fastgpt/global/support/wallet/bill/tools';
import MyModal from '@/components/MyModal';
import { useTranslation } from 'next-i18next';
@@ -25,49 +25,107 @@ const BillDetail = ({ bill, onClose }: { bill: BillItemType; onClose: () => void
[bill.list]
);
const {
hasModel,
hasTokens,
hasInputTokens,
hasOutputTokens,
hasTextLen,
hasDuration,
hasDataLen
} = useMemo(() => {
let hasModel = false;
let hasTokens = false;
let hasInputTokens = false;
let hasOutputTokens = false;
let hasTextLen = false;
let hasDuration = false;
let hasDataLen = false;
bill.list.forEach((item) => {
if (item.model !== undefined) {
hasModel = true;
}
if (item.tokenLen !== undefined) {
hasTokens = true;
}
if (item.inputTokens !== undefined) {
hasInputTokens = true;
}
if (item.outputTokens !== undefined) {
hasOutputTokens = true;
}
if (item.textLen !== undefined) {
hasTextLen = true;
}
if (item.duration !== undefined) {
hasDuration = true;
}
if (item.dataLen !== undefined) {
hasDataLen = true;
}
});
return {
hasModel,
hasTokens,
hasInputTokens,
hasOutputTokens,
hasTextLen,
hasDuration,
hasDataLen
};
}, [bill.list]);
return (
<MyModal
isOpen={true}
onClose={onClose}
iconSrc="/imgs/modal/bill.svg"
title={t('user.Bill Detail')}
maxW={['90vw', '700px']}
>
<ModalBody>
<Flex alignItems={'center'} pb={4}>
<Box flex={'0 0 80px'}>:</Box>
<Box flex={'0 0 80px'}>{t('wallet.bill.bill username')}:</Box>
<Box>{t(bill.memberName)}</Box>
</Flex>
<Flex alignItems={'center'} pb={4}>
<Box flex={'0 0 80px'}>:</Box>
<Box flex={'0 0 80px'}>{t('wallet.bill.Number')}:</Box>
<Box>{bill.id}</Box>
</Flex>
<Flex alignItems={'center'} pb={4}>
<Box flex={'0 0 80px'}>:</Box>
<Box flex={'0 0 80px'}>{t('wallet.bill.Time')}:</Box>
<Box>{dayjs(bill.time).format('YYYY/MM/DD HH:mm:ss')}</Box>
</Flex>
<Flex alignItems={'center'} pb={4}>
<Box flex={'0 0 80px'}>:</Box>
<Box flex={'0 0 80px'}>{t('wallet.bill.App name')}:</Box>
<Box>{t(bill.appName) || '-'}</Box>
</Flex>
<Flex alignItems={'center'} pb={4}>
<Box flex={'0 0 80px'}>:</Box>
<Box flex={'0 0 80px'}>{t('wallet.bill.Source')}:</Box>
<Box>{BillSourceMap[bill.source]}</Box>
</Flex>
<Flex alignItems={'center'} pb={4}>
<Box flex={'0 0 80px'}>:</Box>
<Box flex={'0 0 80px'}>{t('wallet.bill.Total')}:</Box>
<Box fontWeight={'bold'}>{bill.total}</Box>
</Flex>
<Box pb={4}>
<Box flex={'0 0 80px'} mb={1}>
{t('wallet.bill.Bill Module')}
</Box>
<TableContainer>
<Table>
<Thead>
<Tr>
<Th></Th>
<Th>AI模型</Th>
<Th>Token长度</Th>
<Th>{t('wallet.bill.Module name')}</Th>
{hasModel && <Th>{t('wallet.bill.Ai model')}</Th>}
{hasTokens && <Th>{t('wallet.bill.Token Length')}</Th>}
{hasInputTokens && <Th>{t('wallet.bill.Input Token Length')}</Th>}
{hasOutputTokens && <Th>{t('wallet.bill.Output Token Length')}</Th>}
{hasTextLen && <Th>{t('wallet.bill.Text Length')}</Th>}
{hasDuration && <Th>{t('wallet.bill.Duration')}</Th>}
{hasDataLen && <Th>{t('wallet.bill.Data Length')}</Th>}
<Th>()</Th>
</Tr>
</Thead>
@@ -75,9 +133,15 @@ const BillDetail = ({ bill, onClose }: { bill: BillItemType; onClose: () => void
{filterBillList.map((item, i) => (
<Tr key={i}>
<Td>{t(item.moduleName)}</Td>
<Td>{item.model || '-'}</Td>
<Td>{item.tokenLen || '-'}</Td>
<Td>{formatPrice(item.amount)}</Td>
{hasModel && <Td>{item.model ?? '-'}</Td>}
{hasTokens && <Td>{item.tokenLen ?? '-'}</Td>}
{hasInputTokens && <Td>{item.inputTokens ?? '-'}</Td>}
{hasOutputTokens && <Td>{item.outputTokens ?? '-'}</Td>}
{hasTextLen && <Td>{item.textLen ?? '-'}</Td>}
{hasDuration && <Td>{item.duration ?? '-'}</Td>}
{hasDataLen && <Td>{item.dataLen ?? '-'}</Td>}
<Td>{formatStorePrice2Read(item.amount)}</Td>
</Tr>
))}
</Tbody>

View File

@@ -29,7 +29,7 @@ import MyTooltip from '@/components/MyTooltip';
import { langMap, setLngStore } from '@/web/common/utils/i18n';
import { useRouter } from 'next/router';
import MySelect from '@/components/Select';
import { formatPrice } from '@fastgpt/global/support/wallet/bill/tools';
import { formatStorePrice2Read } from '@fastgpt/global/support/wallet/bill/tools';
import { putUpdateMemberName } from '@/web/support/user/team/api';
import { getDocPath } from '@/web/common/system/doc';
@@ -239,7 +239,7 @@ const UserInfo = () => {
{t('user.team.Balance')}:&nbsp;
</Box>
<Box flex={1}>
<strong>{formatPrice(userInfo?.team?.balance).toFixed(3)}</strong>
<strong>{formatStorePrice2Read(userInfo?.team?.balance).toFixed(3)}</strong>
</Box>
{feConfigs?.show_pay && userInfo?.team?.canWrite && (
<Button size={['sm', 'md']} ml={5} onClick={onOpenPayModal}>

View File

@@ -8,7 +8,6 @@ import { getErrText } from '@fastgpt/global/common/error/utils';
import { useTranslation } from 'next-i18next';
import Markdown from '@/components/Markdown';
import MyModal from '@/components/MyModal';
import { priceMd } from '@/web/common/system/staticData';
const PayModal = ({ onClose }: { onClose: () => void }) => {
const router = useRouter();
@@ -68,13 +67,12 @@ const PayModal = ({ onClose }: { onClose: () => void }) => {
onClose={payId ? undefined : onClose}
title={t('user.Pay')}
iconSrc="/imgs/modal/pay.svg"
isCentered={!payId}
>
<ModalBody px={0} minH={payId ? 'auto' : '70vh'} display={'flex'} flexDirection={'column'}>
<ModalBody px={0} display={'flex'} flexDirection={'column'}>
{!payId && (
<>
<Grid gridTemplateColumns={'repeat(4,1fr)'} gridGap={5} mb={4} px={6}>
{[10, 20, 50, 100].map((item) => (
<Grid gridTemplateColumns={'repeat(3,1fr)'} gridGap={5} mb={4} px={6}>
{[10, 20, 50, 100, 200, 500].map((item) => (
<Button
key={item}
variant={item === inputVal ? 'solid' : 'outline'}
@@ -84,7 +82,7 @@ const PayModal = ({ onClose }: { onClose: () => void }) => {
</Button>
))}
</Grid>
<Box mb={4} px={6}>
<Box px={6}>
<Input
value={inputVal}
type={'number'}
@@ -95,9 +93,6 @@ const PayModal = ({ onClose }: { onClose: () => void }) => {
}}
></Input>
</Box>
<Box flex={[1, '1 0 0']} overflow={'overlay'} px={6}>
<Markdown source={priceMd} />
</Box>
</>
)}
{/* 付费二维码 */}

View File

@@ -15,7 +15,7 @@ import { getPayOrders, checkPayResult } from '@/web/support/wallet/pay/api';
import type { PaySchema } from '@fastgpt/global/support/wallet/pay/type.d';
import dayjs from 'dayjs';
import { useQuery } from '@tanstack/react-query';
import { formatPrice } from '@fastgpt/global/support/wallet/bill/tools';
import { formatStorePrice2Read } from '@fastgpt/global/support/wallet/bill/tools';
import { useToast } from '@/web/common/hooks/useToast';
import { useLoading } from '@/web/common/hooks/useLoading';
import MyIcon from '@/components/Icon';
@@ -85,7 +85,7 @@ const PayRecordTable = () => {
<Td>
{item.createTime ? dayjs(item.createTime).format('YYYY/MM/DD HH:mm:ss') : '-'}
</Td>
<Td>{formatPrice(item.price)}</Td>
<Td>{formatStorePrice2Read(item.price)}</Td>
<Td>{item.status}</Td>
<Td>
{item.status === 'NOTPAY' && (

View File

@@ -1,9 +1,8 @@
import React, { useCallback, useMemo, useRef } from 'react';
import { Box, Flex, useTheme } from '@chakra-ui/react';
import React, { useCallback } from 'react';
import { Box, Flex, useDisclosure, useTheme } from '@chakra-ui/react';
import { useSystemStore } from '@/web/common/system/useSystemStore';
import { useRouter } from 'next/router';
import dynamic from 'next/dynamic';
import { clearToken } from '@/web/support/user/auth';
import { useUserStore } from '@/web/support/user/useUserStore';
import { useConfirm } from '@/web/common/hooks/useConfirm';
import PageContainer from '@/components/PageContainer';
@@ -20,11 +19,13 @@ const BillTable = dynamic(() => import('./components/BillTable'));
const PayRecordTable = dynamic(() => import('./components/PayRecordTable'));
const InformTable = dynamic(() => import('./components/InformTable'));
const ApiKeyTable = dynamic(() => import('./components/ApiKeyTable'));
const PriceBox = dynamic(() => import('@/components/support/wallet/Price'));
enum TabEnum {
'info' = 'info',
'promotion' = 'promotion',
'bill' = 'bill',
'price' = 'price',
'pay' = 'pay',
'inform' = 'inform',
'apikey' = 'apikey',
@@ -50,6 +51,15 @@ const Account = ({ currentTab }: { currentTab: `${TabEnum}` }) => {
}
]
: []),
...(feConfigs?.isPlus && feConfigs?.show_pay
? [
{
icon: 'support/pay/priceLight',
label: t('support.user.Price'),
id: TabEnum.price
}
]
: []),
...(feConfigs?.show_promotion
? [
{
@@ -97,6 +107,11 @@ const Account = ({ currentTab }: { currentTab: `${TabEnum}` }) => {
const { openConfirm, ConfirmModal } = useConfirm({
content: '确认退出登录?'
});
const {
isOpen: isOpenPriceBox,
onOpen: onOpenPriceBox,
onClose: onClosePriceBox
} = useDisclosure();
const router = useRouter();
const theme = useTheme();
@@ -109,6 +124,8 @@ const Account = ({ currentTab }: { currentTab: `${TabEnum}` }) => {
setUserInfo(null);
router.replace('/login');
})();
} else if (tab === TabEnum.price) {
onOpenPriceBox();
} else {
router.replace({
query: {
@@ -117,7 +134,7 @@ const Account = ({ currentTab }: { currentTab: `${TabEnum}` }) => {
});
}
},
[openConfirm, router, setUserInfo]
[onOpenPriceBox, openConfirm, router, setUserInfo]
);
return (
@@ -169,6 +186,8 @@ const Account = ({ currentTab }: { currentTab: `${TabEnum}` }) => {
</Flex>
<ConfirmModal />
</PageContainer>
{isOpenPriceBox && <PriceBox onClose={onClosePriceBox} />}
</>
);
};

View File

@@ -2,11 +2,9 @@ import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@fastgpt/service/common/response';
import { connectToDatabase } from '@/service/mongo';
import { delay } from '@fastgpt/global/common/system/utils';
import { PgClient } from '@fastgpt/service/common/pg';
import {
DatasetDataIndexTypeEnum,
PgDatasetTableName
} from '@fastgpt/global/core/dataset/constant';
import { PgClient } from '@fastgpt/service/common/vectorStore/pg';
import { DatasetDataIndexTypeEnum } from '@fastgpt/global/core/dataset/constant';
import { PgDatasetTableName } from '@fastgpt/global/common/vectorStore/constants';
import { authCert } from '@fastgpt/service/support/permission/auth/common';
import { MongoDatasetData } from '@fastgpt/service/core/dataset/data/schema';

View File

@@ -2,8 +2,8 @@ import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@fastgpt/service/common/response';
import { connectToDatabase } from '@/service/mongo';
import { delay } from '@fastgpt/global/common/system/utils';
import { PgClient } from '@fastgpt/service/common/pg';
import { PgDatasetTableName } from '@fastgpt/global/core/dataset/constant';
import { PgClient } from '@fastgpt/service/common/vectorStore/pg';
import { PgDatasetTableName } from '@fastgpt/global/common/vectorStore/constants';
import { authCert } from '@fastgpt/service/support/permission/auth/common';
import { MongoDatasetData } from '@fastgpt/service/core/dataset/data/schema';

View File

@@ -13,8 +13,8 @@ import { MongoDataset } from '@fastgpt/service/core/dataset/schema';
import { PermissionTypeEnum } from '@fastgpt/global/support/permission/constant';
import { MongoDatasetCollection } from '@fastgpt/service/core/dataset/collection/schema';
import { MongoDatasetTraining } from '@fastgpt/service/core/dataset/training/schema';
import { PgClient } from '@fastgpt/service/common/pg';
import { PgDatasetTableName } from '@fastgpt/global/core/dataset/constant';
import { PgClient } from '@fastgpt/service/common/vectorStore/pg';
import { PgDatasetTableName } from '@fastgpt/global/common/vectorStore/constants';
import { MongoOutLink } from '@fastgpt/service/support/outLink/schema';
import { MongoOpenApi } from '@fastgpt/service/support/openapi/schema';
import { MongoApp } from '@fastgpt/service/core/app/schema';

View File

@@ -1,80 +0,0 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@fastgpt/service/common/response';
import { connectToDatabase } from '@/service/mongo';
import { delay } from '@fastgpt/global/common/system/utils';
import { authCert } from '@fastgpt/service/support/permission/auth/common';
import { MongoApp } from '@fastgpt/service/core/app/schema';
import { FlowNodeInputTypeEnum, FlowNodeTypeEnum } from '@fastgpt/global/core/module/node/constant';
import { DatasetSearchModeEnum } from '@fastgpt/global/core/dataset/constant';
import { ModuleIOValueTypeEnum, ModuleInputKeyEnum } from '@fastgpt/global/core/module/constants';
import { ModuleItemType } from '@fastgpt/global/core/module/type';
let success = 0;
/* pg 中的数据搬到 mongo dataset.datas 中,并做映射 */
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
const { limit = 50 } = req.body as { limit: number };
await authCert({ req, authRoot: true });
await connectToDatabase();
success = 0;
console.log('total', await MongoApp.countDocuments());
await initApp(limit);
jsonRes(res, {
message: 'success'
});
} catch (error) {
console.log(error);
jsonRes(res, {
code: 500,
error
});
}
}
export async function initApp(limit = 50): Promise<any> {
try {
const apps = await MongoApp.find({ inited: false }).limit(limit);
if (apps.length === 0) return;
const result = await Promise.allSettled(
apps.map(async (app) => {
// 遍历app的modules找到 datasetSearch, 如果 rerank=true searchMode = embFullTextReRank, 否则等于embedding
const modules = JSON.parse(JSON.stringify(app.modules)) as ModuleItemType[];
modules.forEach((module) => {
if (module.flowType === FlowNodeTypeEnum.datasetSearchNode) {
module.inputs.forEach((input, i) => {
if (input.key === 'rerank') {
const val = !!input.value as boolean;
module.inputs.splice(i, 1, {
key: ModuleInputKeyEnum.datasetSearchMode,
type: FlowNodeInputTypeEnum.hidden,
label: 'core.dataset.search.Mode',
valueType: ModuleIOValueTypeEnum.string,
showTargetInApp: false,
showTargetInPlugin: false,
value: val
? DatasetSearchModeEnum.embFullTextReRank
: DatasetSearchModeEnum.embedding
});
}
});
}
});
app.modules = modules;
app.inited = true;
await app.save();
})
);
success += result.filter((item) => item.status === 'fulfilled').length;
console.log(`success: ${success}`);
return initApp(limit);
} catch (error) {
console.log(error);
await delay(1000);
return initApp(limit);
}
}

View File

@@ -4,7 +4,7 @@ import { connectToDatabase } from '@/service/mongo';
import { delay } from '@fastgpt/global/common/system/utils';
import { authCert } from '@fastgpt/service/support/permission/auth/common';
import { MongoDatasetData } from '@fastgpt/service/core/dataset/data/schema';
import { jiebaSplit } from '@/service/core/dataset/utils';
import { jiebaSplit } from '@/service/common/string/jieba';
let success = 0;
/* pg 中的数据搬到 mongo dataset.datas 中,并做映射 */

View File

@@ -4,7 +4,7 @@ import { connectToDatabase } from '@/service/mongo';
import { delay } from '@fastgpt/global/common/system/utils';
import { authCert } from '@fastgpt/service/support/permission/auth/common';
import { MongoDatasetData } from '@fastgpt/service/core/dataset/data/schema';
import { jiebaSplit } from '@/service/core/dataset/utils';
import { jiebaSplit } from '@/service/common/string/jieba';
let success = 0;
/* pg 中的数据搬到 mongo dataset.datas 中,并做映射 */

View File

@@ -2,8 +2,8 @@ import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@fastgpt/service/common/response';
import { authCert } from '@fastgpt/service/support/permission/auth/common';
import { PgClient } from '@fastgpt/service/common/pg';
import { PgDatasetTableName } from '@fastgpt/global/core/dataset/constant';
import { PgClient } from '@fastgpt/service/common/vectorStore/pg';
import { PgDatasetTableName } from '@fastgpt/global/common/vectorStore/constants';
import { MongoChatItem } from '@fastgpt/service/core/chat/chatItemSchema';
import { connectToDatabase } from '@/service/mongo';

View File

@@ -4,7 +4,6 @@ import { jsonRes } from '@fastgpt/service/common/response';
import { readFileSync, readdirSync } from 'fs';
import type { InitDateResponse } from '@/global/common/api/systemRes';
import type { FastGPTConfigFileType } from '@fastgpt/global/common/system/types/index.d';
import { formatPrice } from '@fastgpt/global/support/wallet/bill/tools';
import { getTikTokenEnc } from '@fastgpt/global/common/string/tiktoken';
import { initHttpAgent } from '@fastgpt/service/common/middle/httpAgent';
import { SimpleModeTemplate_FastGPT_Universal } from '@/global/core/app/constants';
@@ -33,8 +32,9 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
requestUrl: undefined,
requestAuth: undefined
})) || [],
qgModes: global.qgModels,
whisperModel: global.whisperModel,
audioSpeechModels: global.audioSpeechModels,
priceMd: global.priceMd,
systemVersion: global.systemVersion || '0.0.0',
simpleModeTemplates: global.simpleModeTemplates
}
@@ -73,7 +73,6 @@ export async function getInitConfig() {
await getSimpleModeTemplates();
getSystemVersion();
countModelPrice();
getSystemPlugin();
console.log({
@@ -88,7 +87,6 @@ export async function getInitConfig() {
reRankModels: global.reRankModels,
audioSpeechModels: global.audioSpeechModels,
whisperModel: global.whisperModel,
price: global.priceMd,
simpleModeTemplates: global.simpleModeTemplates,
communityPlugins: global.communityPlugins
});
@@ -123,22 +121,20 @@ export async function initSystemConfig() {
// set config
global.feConfigs = {
isPlus: !!config.systemEnv.pluginBaseUrl,
isPlus: !!config.systemEnv?.pluginBaseUrl,
...config.feConfigs
};
global.systemEnv = config.systemEnv;
global.chatModels = config.chatModels || [];
global.qaModels = config.qaModels || [];
global.cqModels = config.cqModels || [];
global.extractModels = config.extractModels || [];
global.qgModels = config.qgModels || [];
global.vectorModels = config.vectorModels || [];
global.reRankModels = config.reRankModels || [];
global.audioSpeechModels = config.audioSpeechModels || [];
global.chatModels = config.chatModels;
global.qaModels = config.qaModels;
global.cqModels = config.cqModels;
global.extractModels = config.extractModels;
global.qgModels = config.qgModels;
global.vectorModels = config.vectorModels;
global.reRankModels = config.reRankModels;
global.audioSpeechModels = config.audioSpeechModels;
global.whisperModel = config.whisperModel;
global.priceMd = '';
}
export function initGlobal() {
@@ -168,38 +164,6 @@ export function getSystemVersion() {
}
}
export function countModelPrice() {
global.priceMd = `| 计费项 | 价格: 元/ 1K tokens(包含上下文)|
| --- | --- |
${global.vectorModels
?.map((item) => `| 索引-${item.name} | ${formatPrice(item.price, 1000)} |`)
.join('\n')}
${global.chatModels
?.map((item) => `| 对话-${item.name} | ${formatPrice(item.price, 1000)} |`)
.join('\n')}
${global.qaModels
?.map((item) => `| 文件QA拆分-${item.name} | ${formatPrice(item.price, 1000)} |`)
.join('\n')}
${global.cqModels
?.map((item) => `| 问题分类-${item.name} | ${formatPrice(item.price, 1000)} |`)
.join('\n')}
${global.extractModels
?.map((item) => `| 内容提取-${item.name} | ${formatPrice(item.price, 1000)} |`)
.join('\n')}
${global.qgModels
?.map((item) => `| 下一步指引-${item.name} | ${formatPrice(item.price, 1000)} |`)
.join('\n')}
${global.audioSpeechModels
?.map((item) => `| 语音播放-${item.name} | ${formatPrice(item.price, 1000)} |`)
.join('\n')}
${
global.whisperModel
? `| 语音输入-${global.whisperModel.name} | ${global.whisperModel.price}/分钟 |`
: ''
}
`;
}
async function getSimpleModeTemplates() {
if (global.simpleModeTemplates && global.simpleModeTemplates.length > 0) return;

View File

@@ -2,14 +2,13 @@ import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@fastgpt/service/common/response';
import { connectToDatabase } from '@/service/mongo';
import { authCert } from '@fastgpt/service/support/permission/auth/common';
import { countModelPrice, initSystemConfig } from './getInitData';
import { initSystemConfig } from './getInitData';
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
try {
await connectToDatabase();
await authCert({ req, authRoot: true });
await initSystemConfig();
countModelPrice();
console.log(`refresh config`);
console.log({
@@ -23,8 +22,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
vectorModels: global.vectorModels,
reRankModels: global.reRankModels,
audioSpeechModels: global.audioSpeechModels,
whisperModel: global.whisperModel,
price: global.priceMd
whisperModel: global.whisperModel
});
} catch (error) {
console.log(error);

View File

@@ -19,7 +19,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
const qgModel = global.qgModels[0];
const { result, tokens } = await createQuestionGuide({
const { result, inputTokens, outputTokens } = await createQuestionGuide({
messages,
model: qgModel.model
});
@@ -29,7 +29,8 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
});
pushQuestionGuideBill({
tokens: tokens,
inputTokens,
outputTokens,
teamId,
tmbId
});

View File

@@ -374,11 +374,21 @@ function datasetTemplate({ formData, maxToken }: Props): ModuleItemType[] {
{
key: 'searchMode',
type: 'hidden',
label: 'core.dataset.search.Mode',
label: '',
valueType: 'string',
showTargetInApp: false,
showTargetInPlugin: false,
value: DatasetSearchModeEnum.embFullTextReRank,
value: DatasetSearchModeEnum.mixedRecall,
connected: false
},
{
key: 'usingReRank',
type: 'hidden',
label: '',
valueType: 'string',
showTargetInApp: false,
showTargetInPlugin: false,
value: true,
connected: false
},
{

View File

@@ -377,6 +377,16 @@ function datasetTemplate(formData: AppSimpleEditFormType): ModuleItemType[] {
value: formData.dataset.searchMode,
connected: false
},
{
key: 'usingReRank',
type: 'hidden',
label: '',
valueType: 'string',
showTargetInApp: false,
showTargetInPlugin: false,
value: formData.dataset.usingReRank,
connected: false
},
{
key: 'datasetParamsModal',
type: 'selectDatasetParamsModal',

View File

@@ -56,7 +56,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
try {
pushAudioSpeechBill({
model: model,
textLength: input.length,
textLen: input.length,
tmbId,
teamId,
source: authType2BillSource({ authType })

View File

@@ -6,10 +6,7 @@ import { Types } from '@fastgpt/service/common/mongo';
import type { DatasetCollectionsListItemType } from '@/global/core/dataset/type.d';
import type { GetDatasetCollectionsProps } from '@/global/core/api/datasetReq';
import { PagingData } from '@/types';
import {
DatasetColCollectionName,
MongoDatasetCollection
} from '@fastgpt/service/core/dataset/collection/schema';
import { MongoDatasetCollection } from '@fastgpt/service/core/dataset/collection/schema';
import { DatasetCollectionTypeEnum } from '@fastgpt/global/core/dataset/constant';
import { startQueue } from '@/service/utils/tools';
import { authDataset } from '@fastgpt/service/support/permission/auth/dataset';

View File

@@ -69,7 +69,7 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
a: formatA
});
const { insertId, tokenLen } = await insertData2Dataset({
const { insertId, tokens } = await insertData2Dataset({
teamId,
tmbId,
datasetId,
@@ -84,7 +84,7 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
pushGenerateVectorBill({
teamId,
tmbId,
tokenLen: tokenLen,
tokens,
model: vectorModelData.model
});

View File

@@ -30,7 +30,7 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
// auth team balance
await authTeamBalance(teamId);
const { tokenLen } = await updateData2Dataset({
const { tokens } = await updateData2Dataset({
dataId: id,
q,
a,
@@ -38,14 +38,12 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
model: vectorModel
});
if (tokenLen) {
pushGenerateVectorBill({
teamId,
tmbId,
tokenLen: tokenLen,
model: vectorModel
});
}
pushGenerateVectorBill({
teamId,
tmbId,
tokens,
model: vectorModel
});
jsonRes(res);
} catch (err) {

View File

@@ -6,7 +6,7 @@ import { connectToDatabase } from '@/service/mongo';
import { authDataset } from '@fastgpt/service/support/permission/auth/dataset';
import { authTeamBalance } from '@/service/support/permission/auth/bill';
import { pushGenerateVectorBill } from '@/service/support/wallet/bill/push';
import { searchDatasetData } from '@/service/core/dataset/data/pg';
import { searchDatasetData } from '@/service/core/dataset/data/controller';
import { updateApiKeyUsage } from '@fastgpt/service/support/openapi/tools';
import { BillSourceEnum } from '@fastgpt/global/support/wallet/bill/constants';
import { searchQueryExtension } from '@fastgpt/service/core/ai/functions/queryExtension';
@@ -14,7 +14,7 @@ import { searchQueryExtension } from '@fastgpt/service/core/ai/functions/queryEx
export default withNextCors(async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
try {
await connectToDatabase();
const { datasetId, text, limit = 20, searchMode } = req.body as SearchTestProps;
const { datasetId, text, limit = 20, searchMode, usingReRank } = req.body as SearchTestProps;
if (!datasetId || !text) {
throw new Error('缺少参数');
@@ -40,20 +40,21 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
// model: global.chatModels[0].model
// });
const { searchRes, tokenLen } = await searchDatasetData({
const { searchRes, tokens } = await searchDatasetData({
rawQuery: text,
queries: [text],
model: dataset.vectorModel,
limit: Math.min(limit * 800, 30000),
datasetIds: [datasetId],
searchMode
searchMode,
usingReRank
});
// push bill
const { total } = pushGenerateVectorBill({
teamId,
tmbId,
tokenLen: tokenLen,
tokens,
model: dataset.vectorModel,
source: apikey ? BillSourceEnum.api : BillSourceEnum.fastgpt
});

View File

@@ -3,20 +3,39 @@ import { jsonRes } from '@fastgpt/service/common/response';
import { connectToDatabase } from '@/service/mongo';
import { MongoDatasetTraining } from '@fastgpt/service/core/dataset/training/schema';
import { authCert } from '@fastgpt/service/support/permission/auth/common';
import { GetTrainingQueueProps } from '@/global/core/dataset/api';
/* 拆分数据成QA */
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
await connectToDatabase();
await authCert({ req, authToken: true });
const { vectorModel, agentModel } = req.query as GetTrainingQueueProps;
// split queue data
const result = await MongoDatasetTraining.countDocuments({
lockTime: { $lt: new Date('2040/1/1') }
});
// get queue data
// 分别统计 model = vectorModel和agentModel的数量
const data = await MongoDatasetTraining.aggregate([
{
$match: {
lockTime: { $lt: new Date('2040/1/1') },
$or: [{ model: { $eq: vectorModel } }, { model: { $eq: agentModel } }]
}
},
{
$group: {
_id: '$model',
count: { $sum: 1 }
}
}
]);
const vectorTrainingCount = data.find((item) => item._id === vectorModel)?.count || 0;
const agentTrainingCount = data.find((item) => item._id === agentModel)?.count || 0;
jsonRes(res, {
data: result
data: {
vectorTrainingCount,
agentTrainingCount
}
});
} catch (err) {
jsonRes(res, {

View File

@@ -5,7 +5,7 @@ import { withNextCors } from '@fastgpt/service/common/middle/cors';
import { pushGenerateVectorBill } from '@/service/support/wallet/bill/push';
import { connectToDatabase } from '@/service/mongo';
import { authTeamBalance } from '@/service/support/permission/auth/bill';
import { getVectorsByText, GetVectorProps } from '@/service/core/ai/vector';
import { getVectorsByText, GetVectorProps } from '@fastgpt/service/core/ai/embedding';
import { updateApiKeyUsage } from '@fastgpt/service/support/openapi/tools';
import { getBillSourceByAuthType } from '@fastgpt/global/support/wallet/bill/tools';
@@ -30,7 +30,7 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
await authTeamBalance(teamId);
const { tokenLen, vectors } = await getVectorsByText({ input, model });
const { tokens, vectors } = await getVectorsByText({ input, model });
jsonRes(res, {
data: {
@@ -42,8 +42,8 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
})),
model,
usage: {
prompt_tokens: tokenLen,
total_tokens: tokenLen
prompt_tokens: tokens,
total_tokens: tokens
}
}
});
@@ -51,7 +51,7 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
const { total } = pushGenerateVectorBill({
teamId,
tmbId,
tokenLen,
tokens,
model,
billId,
source: getBillSourceByAuthType({ authType })

View File

@@ -42,11 +42,9 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
data: result
});
} catch (err) {
console.log(err);
jsonRes<PostReRankResponse>(res, {
data: inputs.map((input) => ({
id: input.id
}))
jsonRes(res, {
code: 500,
error: err
});
}
});

View File

@@ -4,7 +4,7 @@ import { useSystemStore } from '@/web/common/system/useSystemStore';
import { getAppTotalUsage } from '@/web/core/app/api';
import { useQuery } from '@tanstack/react-query';
import dayjs from 'dayjs';
import { formatPrice } from '@fastgpt/global/support/wallet/bill/tools';
import { formatStorePrice2Read } from '@fastgpt/global/support/wallet/bill/tools';
import Loading from '@/components/Loading';
import { Box } from '@chakra-ui/react';
@@ -135,7 +135,7 @@ const TokenUsage = ({ appId }: { appId: string }) => {
return `
<div>
<div>${dayjs(data.axisValue).format('YYYY/MM/DD')}</div>
<div>${formatPrice(e[0]?.value || 0)}元</div>
<div>${formatStorePrice2Read(e[0]?.value || 0)}元</div>
</div>
`;
}

View File

@@ -47,7 +47,7 @@ const Render = ({ app, onClose }: Props) => {
return <Flow templates={moduleTemplates} Header={<Header app={app} onClose={onClose} />} />;
};
export default React.memo(function AdEdit(props: Props) {
export default React.memo(function FlowEdit(props: Props) {
return (
<FlowProvider mode={'app'} filterAppIds={[props.app._id]}>
<Render {...props} />

View File

@@ -9,6 +9,7 @@ import MyIcon from '@/components/Icon';
import { useCopyData } from '@/web/common/hooks/useCopyData';
import { useSelectFile } from '@/web/common/file/hooks/useSelectFile';
import { fileToBase64 } from '@/web/common/file/utils';
import { feConfigs } from '@/web/common/system/staticData';
enum UsingWayEnum {
link = 'link',
@@ -70,7 +71,8 @@ const SelectUsingWayModal = ({ share, onClose }: { share: OutLinkSchema; onClose
setRefresh(!refresh);
});
const linkUrl = `${location?.origin}/chat/share?shareId=${share?.shareId}${
const baseUrl = feConfigs?.customSharePageDomain || location?.origin;
const linkUrl = `${baseUrl}/chat/share?shareId=${share?.shareId}${
getValues('showHistory') ? '' : '&showHistory=0'
}`;
@@ -91,7 +93,7 @@ const SelectUsingWayModal = ({ share, onClose }: { share: OutLinkSchema; onClose
[UsingWayEnum.script]: {
blockTitle: t('core.app.outLink.Script block title'),
code: `<script
src="${location?.origin}/js/iframe.js"
src="${baseUrl}/js/iframe.js"
id="chatbot-iframe"
data-bot-src="${linkUrl}"
data-default-open="${getValues('scriptDefaultOpen') ? 'true' : 'false'}"

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