diff --git a/.github/workflows/fastgpt-home-image.yml b/.github/workflows/fastgpt-home-image.yml new file mode 100644 index 000000000..681bcc96b --- /dev/null +++ b/.github/workflows/fastgpt-home-image.yml @@ -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 \ + . diff --git a/.prettierignore b/.prettierignore index 40e5b33e1..33bf7e6cf 100644 --- a/.prettierignore +++ b/.prettierignore @@ -2,4 +2,5 @@ dist .vscode **/.DS_Store node_modules -docSite/ \ No newline at end of file +docSite/ +*.md \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index eaf1fd95f..2260a5cea 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -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", diff --git a/Dockerfile b/Dockerfile index 46ee0db93..b93a6da81 100644 --- a/Dockerfile +++ b/Dockerfile @@ -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 diff --git a/docSite/assets/imgs/dataset_search_params1.png b/docSite/assets/imgs/dataset_search_params1.png new file mode 100644 index 000000000..aedf03286 Binary files /dev/null and b/docSite/assets/imgs/dataset_search_params1.png differ diff --git a/docSite/content/docs/course/data_search.md b/docSite/content/docs/course/data_search.md index 63867663c..36682672b 100644 --- a/docSite/content/docs/course/data_search.md +++ b/docSite/content/docs/course/data_search.md @@ -9,32 +9,50 @@ weight: 106 在知识库搜索的方式上,FastGPT提供了三种方式,分别为“语义检索”“增强语义检索”“混合检索”。 -![](/imgs/data_search1.png) +![](/imgs/dataset_search_params1.png) -## 语义检索 +## 搜索模式 -语义检索就是向量检索,同时把用户的问题和知识库内容向量化,然后通过“语义相关度匹配”的方式从知识库中查找到匹配的知识点。 +### 语义检索 + +语义检索是通过向量距离,计算用户问题与知识库内容的距离,从而得出“相似度”,当然这并不是语文上的相似度,而是数学上的。 优点: - 相近语义理解 - 跨多语言理解(例如输入中文问题匹配英文知识点) - 多模态理解(文本,图片,音视频等) -## 增强语义检索 +缺点: +- 依赖模型训练效果 +- 精度不稳定 +- 受关键词和句子完整度影响 -在语义检索的基础上,增强“语义相关度匹配”并在搜索结束后进行 Rerank(重排)。 +### 全文检索 -Rerank(重排):把检索结果按“与用户问题语义”相关性,从高到低排序,简单的说就是把最匹配用户问题的检索结果排在前面。 +才用传统的全文检索方式。适合查找关键的主谓语等。 -## 混合检索(推荐) +### 混合检索 + +同时使用向量检索和全文检索,并通过 RRF 公式进行两个搜索结果合并,一般情况下搜索结果会更加丰富准确。 + +由于混合检索后的查找范围很大,并且无法直接进行相似度过滤,通常需要进行利用重排模型进行一次结果重新排序,并利用重排的得分进行过滤。 -在向量检索的同时进行全文检索,并把两项检索的结果混合一起重排,以便选中匹配用户问题的最佳结果。 -全文检索:理解为全文关键词检索,通过关键词查询知识库,并返回包含关键词的文本片段。 +## 结果重排 -优点: -- 精确匹配(姓名,编号,ID等) -- 少量关键词匹配(当用户问题字数过少时向量检索效果非常不好) +利用`ReRank`模型对搜索结果进行重排,绝大多数情况下,可以有效提高搜索结果的准确率。不过,重排模型与问题的完整度(主谓语齐全)有一些关系,通常会先走问题补全后再进行搜索-重排。重排后可以得到一个`0-1`的得分,代表着搜索内容与问题的相关度,该分数通常比向量的得分更加精确,可以根据得分进行过滤。 -混合检索结合了向量检索和全文检索的优点,并且对查询结果进行了重排,大大提高了命中率,推荐使用。 \ No newline at end of file +FastGPT 会使用 `RRF` 对重排结果、向量搜索结果、全文检索结果进行合并,得到最终的搜索结果。 + +## 引用上限 + +每次搜索最多引用`n`个`tokens`的内容。 + +之所以不采用`top k`,是发现在混合知识库(问答库、文档库)时,不同`chunk`的长度差距很大,会导致`top k`的结果不稳定,因此采用了`tokens`的方式进行引用上限的控制。 + +## 最低相关度 + +一个`0-1`的数值,会过滤掉一些低相关度的搜索结果。 + +该值仅在`语义检索`或使用`结果重排`时生效。 \ No newline at end of file diff --git a/docSite/content/docs/course/websync.md b/docSite/content/docs/course/websync.md index bedbbf6fe..9278ace82 100644 --- a/docSite/content/docs/course/websync.md +++ b/docSite/content/docs/course/websync.md @@ -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/ ``` ## 如何使用 diff --git a/docSite/content/docs/development/docker.md b/docSite/content/docs/development/docker.md index aac712059..b78442a49 100644 --- a/docSite/content/docs/development/docker.md +++ b/docSite/content/docs/development/docker.md @@ -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`即可。 diff --git a/docSite/content/docs/development/qa.md b/docSite/content/docs/development/qa.md new file mode 100644 index 000000000..169a9159f --- /dev/null +++ b/docSite/content/docs/development/qa.md @@ -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`即可。 diff --git a/docSite/content/docs/development/upgrading/466.md b/docSite/content/docs/development/upgrading/466.md index 5504aa5a1..8659b536f 100644 --- a/docSite/content/docs/development/upgrading/466.md +++ b/docSite/content/docs/development/upgrading/466.md @@ -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 --- **版本仍在开发中……** diff --git a/packages/global/common/system/types/index.d.ts b/packages/global/common/system/types/index.d.ts index b266fb431..918b7cec9 100644 --- a/packages/global/common/system/types/index.d.ts +++ b/packages/global/common/system/types/index.d.ts @@ -48,6 +48,8 @@ export type FastGPTFeConfigsType = { }; scripts?: { [key: string]: string }[]; favicon?: string; + customApiDomain?: string; + customSharePageDomain?: string; }; export type SystemEnvType = { diff --git a/packages/global/common/vectorStore/constants.ts b/packages/global/common/vectorStore/constants.ts new file mode 100644 index 000000000..92db0e358 --- /dev/null +++ b/packages/global/common/vectorStore/constants.ts @@ -0,0 +1 @@ +export const PgDatasetTableName = 'modeldata'; diff --git a/packages/global/core/ai/model.d.ts b/packages/global/core/ai/model.d.ts index b7a9aa073..98943560c 100644 --- a/packages/global/core/ai/model.d.ts +++ b/packages/global/core/ai/model.d.ts @@ -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; }; diff --git a/packages/global/core/ai/model.ts b/packages/global/core/ai/model.ts index 886353348..7e0a07e39 100644 --- a/packages/global/core/ai/model.ts +++ b/packages/global/core/ai/model.ts @@ -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 diff --git a/packages/global/core/app/type.d.ts b/packages/global/core/app/type.d.ts index da64ed9b7..8e29426c0 100644 --- a/packages/global/core/app/type.d.ts +++ b/packages/global/core/app/type.d.ts @@ -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?: { diff --git a/packages/global/core/app/utils.ts b/packages/global/core/app/utils.ts index e65511e58..48f22335a 100644 --- a/packages/global/core/app/utils.ts +++ b/packages/global/core/app/utils.ts @@ -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 = diff --git a/packages/global/core/chat/type.d.ts b/packages/global/core/chat/type.d.ts index f29271d29..f1469aec8 100644 --- a/packages/global/core/chat/type.d.ts +++ b/packages/global/core/chat/type.d.ts @@ -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 & { diff --git a/packages/global/core/dataset/constant.ts b/packages/global/core/dataset/constant.ts index 4dfb618d8..c9ff7f806 100644 --- a/packages/global/core/dataset/constant.ts +++ b/packages/global/core/dataset/constant.ts @@ -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'; diff --git a/packages/global/core/dataset/type.d.ts b/packages/global/core/dataset/type.d.ts index 2cf4d9caf..a6831ce53 100644 --- a/packages/global/core/dataset/type.d.ts +++ b/packages/global/core/dataset/type.d.ts @@ -6,6 +6,7 @@ import { DatasetDataIndexTypeEnum, DatasetStatusEnum, DatasetTypeEnum, + SearchScoreTypeEnum, TrainingModeEnum } from './constant'; @@ -161,5 +162,6 @@ export type DatasetFileSchema = { /* ============= search =============== */ export type SearchDataResponseItemType = Omit & { - score: number; + score: { type: `${SearchScoreTypeEnum}`; value: number; index: number }[]; + // score: number; }; diff --git a/packages/global/core/module/constants.ts b/packages/global/core/module/constants.ts index 9873aeb14..a9bf7db72 100644 --- a/packages/global/core/module/constants.ts +++ b/packages/global/core/module/constants.ts @@ -63,6 +63,7 @@ export enum ModuleInputKeyEnum { datasetSimilarity = 'similarity', datasetLimit = 'limit', datasetSearchMode = 'searchMode', + datasetSearchUsingReRank = 'usingReRank', datasetParamsModal = 'datasetParamsModal', // context extract diff --git a/packages/global/core/module/template/system/datasetSearch.ts b/packages/global/core/module/template/system/datasetSearch.ts index 823a2b4f8..50c20ff14 100644 --- a/packages/global/core/module/template/system/datasetSearch.ts +++ b/packages/global/core/module/template/system/datasetSearch.ts @@ -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, diff --git a/packages/global/support/user/team/type.d.ts b/packages/global/support/user/team/type.d.ts index 17f2c520b..540e1777e 100644 --- a/packages/global/support/user/team/type.d.ts +++ b/packages/global/support/user/team/type.d.ts @@ -9,6 +9,7 @@ export type TeamSchema = { createTime: Date; balance: number; maxSize: number; + lastDatasetBillTime: Date; }; export type TeamMemberSchema = { diff --git a/packages/global/support/wallet/bill/api.d.ts b/packages/global/support/wallet/bill/api.d.ts index fbd20025d..6314ab164 100644 --- a/packages/global/support/wallet/bill/api.d.ts +++ b/packages/global/support/wallet/bill/api.d.ts @@ -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 = { diff --git a/packages/global/support/wallet/bill/constants.ts b/packages/global/support/wallet/bill/constants.ts index bb13498b5..319bd5ff8 100644 --- a/packages/global/support/wallet/bill/constants.ts +++ b/packages/global/support/wallet/bill/constants.ts @@ -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]: '知识库存储' }; diff --git a/packages/global/support/wallet/bill/tools.ts b/packages/global/support/wallet/bill/tools.ts index 50efa74ee..300875e94 100644 --- a/packages/global/support/wallet/bill/tools.ts +++ b/packages/global/support/wallet/bill/tools.ts @@ -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, diff --git a/packages/global/support/wallet/bill/type.d.ts b/packages/global/support/wallet/bill/type.d.ts index a676f8d28..5b0606c8a 100644 --- a/packages/global/support/wallet/bill/type.d.ts +++ b/packages/global/support/wallet/bill/type.d.ts @@ -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 & { diff --git a/packages/service/common/pg/type.d.ts b/packages/service/common/pg/type.d.ts deleted file mode 100644 index fed5fc8b7..000000000 --- a/packages/service/common/pg/type.d.ts +++ /dev/null @@ -1,5 +0,0 @@ -import type { Pool } from 'pg'; - -declare global { - var pgClient: Pool | null; -} diff --git a/packages/service/common/system/log.ts b/packages/service/common/system/log.ts index 26b0a4598..4035c4b19 100644 --- a/packages/service/common/system/log.ts +++ b/packages/service/common/system/log.ts @@ -4,11 +4,13 @@ import dayjs from 'dayjs'; export const addLog = { log(level: 'info' | 'warn' | 'error', msg: string, obj: Record = {}) { 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; diff --git a/packages/service/common/vectorStore/controller.d.ts b/packages/service/common/vectorStore/controller.d.ts new file mode 100644 index 000000000..3ddd01fa0 --- /dev/null +++ b/packages/service/common/vectorStore/controller.d.ts @@ -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[]; +}; diff --git a/packages/service/common/vectorStore/controller.ts b/packages/service/common/vectorStore/controller.ts new file mode 100644 index 000000000..0307c2da4 --- /dev/null +++ b/packages/service/common/vectorStore/controller.ts @@ -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 + }; +}; diff --git a/packages/service/common/vectorStore/pg/class.ts b/packages/service/common/vectorStore/pg/class.ts new file mode 100644 index 000000000..3685bf41f --- /dev/null +++ b/packages/service/common/vectorStore/pg/class.ts @@ -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; +} diff --git a/packages/service/common/vectorStore/pg/controller.ts b/packages/service/common/vectorStore/pg/controller.ts new file mode 100644 index 000000000..d502b4389 --- /dev/null +++ b/packages/service/common/vectorStore/pg/controller.ts @@ -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 => { + 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 => { + 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(); + 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 + })); +}; diff --git a/packages/service/common/pg/index.ts b/packages/service/common/vectorStore/pg/index.ts similarity index 82% rename from packages/service/common/pg/index.ts rename to packages/service/common/vectorStore/pg/index.ts index 080a3f0df..567b5541b 100644 --- a/packages/service/common/pg/index.ts +++ b/packages/service/common/vectorStore/pg/index.ts @@ -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 => { 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; diff --git a/packages/service/common/vectorStore/type.d.ts b/packages/service/common/vectorStore/type.d.ts new file mode 100644 index 000000000..99807dcee --- /dev/null +++ b/packages/service/common/vectorStore/type.d.ts @@ -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; +}; diff --git a/projects/app/src/service/core/ai/vector.ts b/packages/service/core/ai/embedding/index.ts similarity index 74% rename from projects/app/src/service/core/ai/vector.ts rename to packages/service/core/ai/embedding/index.ts index 17c1abf8f..501130923 100644 --- a/projects/app/src/service/core/ai/vector.ts +++ b/packages/service/core/ai/embedding/index.ts @@ -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))) }; }); diff --git a/packages/service/core/ai/functions/createQuestionGuide.ts b/packages/service/core/ai/functions/createQuestionGuide.ts index bf2ba6f50..8bd6dd5cd 100644 --- a/packages/service/core/ai/functions/createQuestionGuide.ts +++ b/packages/service/core/ai/functions/createQuestionGuide.ts @@ -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 }; } } diff --git a/packages/service/core/dataset/data/controller.ts b/packages/service/core/dataset/data/controller.ts index 607f30cc2..bc0f84daa 100644 --- a/packages/service/core/dataset/data/controller.ts +++ b/packages/service/core/dataset/data/controller.ts @@ -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); } diff --git a/packages/service/core/dataset/data/pg.ts b/packages/service/core/dataset/data/pg.ts deleted file mode 100644 index 2379be3f9..000000000 --- a/packages/service/core/dataset/data/pg.ts +++ /dev/null @@ -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 { - 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 - }; -} diff --git a/packages/service/core/dataset/data/schema.ts b/packages/service/core/dataset/data/schema.ts index b68953f36..f79b730b5 100644 --- a/packages/service/core/dataset/data/schema.ts +++ b/packages/service/core/dataset/data/schema.ts @@ -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 }); diff --git a/packages/service/support/openapi/schema.ts b/packages/service/support/openapi/schema.ts index d8f51f5a9..4c75bd054 100644 --- a/packages/service/support/openapi/schema.ts +++ b/packages/service/support/openapi/schema.ts @@ -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) } } }, diff --git a/packages/service/support/user/team/teamMemberSchema.ts b/packages/service/support/user/team/teamMemberSchema.ts index 183f435f2..043f1c334 100644 --- a/packages/service/support/user/team/teamMemberSchema.ts +++ b/packages/service/support/user/team/teamMemberSchema.ts @@ -43,6 +43,7 @@ const TeamMemberSchema = new Schema({ }); try { + TeamMemberSchema.index({ teamId: 1 }); } catch (error) { console.log(error); } diff --git a/packages/service/support/user/team/teamSchema.ts b/packages/service/support/user/team/teamSchema.ts index 2098a4389..e2b790ac3 100644 --- a/packages/service/support/user/team/teamSchema.ts +++ b/packages/service/support/user/team/teamSchema.ts @@ -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); } diff --git a/packages/service/support/wallet/bill/controller.ts b/packages/service/support/wallet/bill/controller.ts index b5c094100..9f9f10488 100644 --- a/packages/service/support/wallet/bill/controller.ts +++ b/packages/service/support/wallet/bill/controller.ts @@ -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 diff --git a/packages/service/support/wallet/bill/schema.ts b/packages/service/support/wallet/bill/schema.ts index bd2e4138e..e5822efc7 100644 --- a/packages/service/support/wallet/bill/schema.ts +++ b/packages/service/support/wallet/bill/schema.ts @@ -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); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3b94c7c38..0bc845e98 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -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} diff --git a/projects/app/data/simpleTemplates/fastgpt-simple.json b/projects/app/data/simpleTemplates/fastgpt-simple.json index 3acb6ad2f..819691ff6 100644 --- a/projects/app/data/simpleTemplates/fastgpt-simple.json +++ b/projects/app/data/simpleTemplates/fastgpt-simple.json @@ -18,6 +18,7 @@ "similarity": false, "limit": false, "searchMode": false, + "usingReRank": false, "searchEmptyText": false }, "userGuide": { diff --git a/projects/app/public/js/iframe.js b/projects/app/public/js/iframe.js index 0c645b60d..55603d636 100644 --- a/projects/app/public/js/iframe.js +++ b/projects/app/public/js/iframe.js @@ -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; diff --git a/projects/app/public/locales/en/common.json b/projects/app/public/locales/en/common.json index b7768c501..55a5c31d1 100644 --- a/projects/app/public/locales/en/common.json +++ b/projects/app/public/locales/en/common.json @@ -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" }, diff --git a/projects/app/public/locales/zh/common.json b/projects/app/public/locales/zh/common.json index 4d57f6660..4c9d2b3f7 100644 --- a/projects/app/public/locales/zh/common.json +++ b/projects/app/public/locales/zh/common.json @@ -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": "用户" }, diff --git a/projects/app/src/components/ChatBox/QuoteModal.tsx b/projects/app/src/components/ChatBox/QuoteModal.tsx index 1435d1a86..c177ab0e5 100644 --- a/projects/app/src/components/ChatBox/QuoteModal.tsx +++ b/projects/app/src/components/ChatBox/QuoteModal.tsx @@ -151,7 +151,7 @@ export const QuoteList = React.memo(function QuoteList({ {item.q.length + (item.a?.length || 0)} - {!isShare && item.score && ( + {/* {!isShare && item.score && ( @@ -167,7 +167,7 @@ export const QuoteList = React.memo(function QuoteList({ {item.score.toFixed(4)} - )} + )} */} {item.id && ( diff --git a/projects/app/src/components/ChatBox/ResponseTags.tsx b/projects/app/src/components/ChatBox/ResponseTags.tsx index bb4313815..308176073 100644 --- a/projects/app/src/components/ChatBox/ResponseTags.tsx +++ b/projects/app/src/components/ChatBox/ResponseTags.tsx @@ -205,9 +205,9 @@ const ResponseTags = ({ )} - + - {t('chat.Complete Response')} + {t('core.chat.response.Read complete response')} diff --git a/projects/app/src/components/ChatBox/WholeResponseModal.tsx b/projects/app/src/components/ChatBox/WholeResponseModal.tsx index 60e51bed9..cb1112443 100644 --- a/projects/app/src/components/ChatBox/WholeResponseModal.tsx +++ b/projects/app/src/components/ChatBox/WholeResponseModal.tsx @@ -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={ - {t('chat.Complete Response')} + {t('core.chat.response.Complete Response')} @@ -133,15 +133,16 @@ const ResponseBox = React.memo(function ResponseBox({ {activeModule?.price !== undefined && ( )} - + + + {/* classify question */} \ No newline at end of file diff --git a/projects/app/src/components/Icon/icons/support/pay/priceLight.svg b/projects/app/src/components/Icon/icons/support/pay/priceLight.svg new file mode 100644 index 000000000..c7f83cd7e --- /dev/null +++ b/projects/app/src/components/Icon/icons/support/pay/priceLight.svg @@ -0,0 +1,11 @@ + + + + + \ No newline at end of file diff --git a/projects/app/src/components/Icon/index.tsx b/projects/app/src/components/Icon/index.tsx index 0aedac07a..ae342d3fe 100644 --- a/projects/app/src/components/Icon/index.tsx +++ b/projects/app/src/components/Icon/index.tsx @@ -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; diff --git a/projects/app/src/components/Layout/auth.tsx b/projects/app/src/components/Layout/auth.tsx index 064d4a53b..19a591e5e 100644 --- a/projects/app/src/components/Layout/auth.tsx +++ b/projects/app/src/components/Layout/auth.tsx @@ -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'; diff --git a/projects/app/src/components/Markdown/index.module.scss b/projects/app/src/components/Markdown/index.module.scss index af4e114fb..552151dec 100644 --- a/projects/app/src/components/Markdown/index.module.scss +++ b/projects/app/src/components/Markdown/index.module.scss @@ -218,6 +218,9 @@ .markdown blockquote > *:last-child { margin-bottom: 0; } +.markdown table { + width: 100%; +} .markdown table th { font-weight: bold; } diff --git a/projects/app/src/components/Select/SelectAiModel.tsx b/projects/app/src/components/Select/SelectAiModel.tsx new file mode 100644 index 000000000..28045e95f --- /dev/null +++ b/projects/app/src/components/Select/SelectAiModel.tsx @@ -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 ( + <> + { + if (e === 'price') { + onOpenPriceBox(); + return; + } + props.onchange?.(e); + }} + /> + {isOpenPriceBox && } + + ); +}; + +export default SelectAiModel; diff --git a/projects/app/src/components/Select/index.tsx b/projects/app/src/components/Select/index.tsx index 9aa2f4919..eb9ea4545 100644 --- a/projects/app/src/components/Select/index.tsx +++ b/projects/app/src/components/Select/index.tsx @@ -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(null); diff --git a/projects/app/src/components/SideTabs/index.tsx b/projects/app/src/components/SideTabs/index.tsx index 523a751f4..196bdeb7d 100644 --- a/projects/app/src/components/SideTabs/index.tsx +++ b/projects/app/src/components/SideTabs/index.tsx @@ -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; diff --git a/projects/app/src/components/core/module/DatasetParamsModal.tsx b/projects/app/src/components/core/module/DatasetParamsModal.tsx index 33ab4d369..1819ab36c 100644 --- a/projects/app/src/components/core/module/DatasetParamsModal.tsx +++ b/projects/app/src/components/core/module/DatasetParamsModal.tsx @@ -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({ 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 ( @@ -71,9 +95,73 @@ const DatasetParamsModal = ({ setRefresh(!refresh); }} /> + {usingReRank !== undefined && reRankModelList.length > 0 && ( + <> + + { + setValue('usingReRank', !getValues('usingReRank')); + setRefresh((state) => !state); + }} + > + + + {t('core.dataset.search.ReRank')} + + {t('core.dataset.search.ReRank desc')} + + + + + + + + + )} - {similarity !== undefined && ( + {limit !== undefined && ( + + {t('core.dataset.search.Max Tokens')} + + + + + + { + setValue(ModuleInputKeyEnum.datasetLimit, val); + setRefresh(!refresh); + }} + /> + + + )} + {showSimilarity && ( + {t('core.dataset.search.Min Similarity')} @@ -98,32 +186,7 @@ const DatasetParamsModal = ({ )} - {limit !== undefined && ( - - - {t('core.dataset.search.Max Tokens')} - - - - - - { - setValue(ModuleInputKeyEnum.datasetLimit, val); - setRefresh(!refresh); - }} - /> - - - )} + {searchEmptyText !== undefined && ( diff --git a/projects/app/src/components/core/module/Flow/components/modules/TTSSelect.tsx b/projects/app/src/components/core/module/Flow/components/modules/TTSSelect.tsx index 9b9303a6a..5d06c63d8 100644 --- a/projects/app/src/components/core/module/Flow/components/modules/TTSSelect.tsx +++ b/projects/app/src/components/core/module/Flow/components/modules/TTSSelect.tsx @@ -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) { diff --git a/projects/app/src/components/core/module/Flow/components/render/RenderInput/templates/SelectAiModel.tsx b/projects/app/src/components/core/module/Flow/components/render/RenderInput/templates/SelectAiModel.tsx index 51e1ab3f1..1594fdda6 100644 --- a/projects/app/src/components/core/module/Flow/components/render/RenderInput/templates/SelectAiModel.tsx +++ b/projects/app/src/components/core/module/Flow/components/render/RenderInput/templates/SelectAiModel.tsx @@ -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 ( - { const [data, setData] = useState({ searchMode: DatasetSearchModeEnum.embedding, limit: 5, - similarity: 0.5 + similarity: 0.5, + usingReRank: false }); const tokenLimit = useMemo(() => { diff --git a/projects/app/src/components/support/apikey/Table.tsx b/projects/app/src/components/support/apikey/Table.tsx index 35bd1103d..aca9e1014 100644 --- a/projects/app/src/components/support/apikey/Table.tsx +++ b/projects/app/src/components/support/apikey/Table.tsx @@ -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 }) => { diff --git a/projects/app/src/components/support/wallet/Price.tsx b/projects/app/src/components/support/wallet/Price.tsx new file mode 100644 index 000000000..f398977c4 --- /dev/null +++ b/projects/app/src/components/support/wallet/Price.tsx @@ -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( + + + + {list.map((item) => ( + + + {item.title} + + + + + + ))} + + , + // @ts-ignore + document.querySelector('body') + ); +}; + +export default Price; diff --git a/projects/app/src/global/common/api/systemRes.d.ts b/projects/app/src/global/common/api/systemRes.d.ts index 621391c9b..4a266fba3 100644 --- a/projects/app/src/global/common/api/systemRes.d.ts +++ b/projects/app/src/global/common/api/systemRes.d.ts @@ -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[]; }; diff --git a/projects/app/src/global/core/app/constants.ts b/projects/app/src/global/core/app/constants.ts index b08acc58b..0eb6f7bdf 100644 --- a/projects/app/src/global/core/app/constants.ts +++ b/projects/app/src/global/core/app/constants.ts @@ -22,6 +22,7 @@ export const SimpleModeTemplate_FastGPT_Universal: AppSimpleEditConfigTemplateTy similarity: true, limit: true, searchMode: DatasetSearchModeEnum.embedding, + usingReRank: true, searchEmptyText: true }, userGuide: { diff --git a/projects/app/src/global/core/dataset/api.d.ts b/projects/app/src/global/core/dataset/api.d.ts index 1e5727ea5..f122baf84 100644 --- a/projects/app/src/global/core/dataset/api.d.ts +++ b/projects/app/src/global/core/dataset/api.d.ts @@ -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[]; diff --git a/projects/app/src/pages/account/components/BillDetail.tsx b/projects/app/src/pages/account/components/BillDetail.tsx index 58759dcfc..6ade1f896 100644 --- a/projects/app/src/pages/account/components/BillDetail.tsx +++ b/projects/app/src/pages/account/components/BillDetail.tsx @@ -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 ( - 用户: + {t('wallet.bill.bill username')}: {t(bill.memberName)} - 订单号: + {t('wallet.bill.Number')}: {bill.id} - 生成时间: + {t('wallet.bill.Time')}: {dayjs(bill.time).format('YYYY/MM/DD HH:mm:ss')} - 应用名: + {t('wallet.bill.App name')}: {t(bill.appName) || '-'} - 来源: + {t('wallet.bill.Source')}: {BillSourceMap[bill.source]} - 总金额: + {t('wallet.bill.Total')}: {bill.total}元 - 扣费模块 + {t('wallet.bill.Bill Module')} - - - + + {hasModel && } + {hasTokens && } + {hasInputTokens && } + {hasOutputTokens && } + {hasTextLen && } + {hasDuration && } + {hasDataLen && } @@ -75,9 +133,15 @@ const BillDetail = ({ bill, onClose }: { bill: BillItemType; onClose: () => void {filterBillList.map((item, i) => ( - - - + {hasModel && } + {hasTokens && } + {hasInputTokens && } + {hasOutputTokens && } + {hasTextLen && } + {hasDuration && } + {hasDataLen && } + + ))} diff --git a/projects/app/src/pages/account/components/Info.tsx b/projects/app/src/pages/account/components/Info.tsx index 82485f79e..47b9b42d6 100644 --- a/projects/app/src/pages/account/components/Info.tsx +++ b/projects/app/src/pages/account/components/Info.tsx @@ -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')}:  - {formatPrice(userInfo?.team?.balance).toFixed(3)} 元 + {formatStorePrice2Read(userInfo?.team?.balance).toFixed(3)} {feConfigs?.show_pay && userInfo?.team?.canWrite && ( ))} - + void }) => { }} > - - - )} {/* 付费二维码 */} diff --git a/projects/app/src/pages/account/components/PayRecordTable.tsx b/projects/app/src/pages/account/components/PayRecordTable.tsx index ef9b08985..d4e29754d 100644 --- a/projects/app/src/pages/account/components/PayRecordTable.tsx +++ b/projects/app/src/pages/account/components/PayRecordTable.tsx @@ -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 = () => { - +
模块名AI模型Token长度{t('wallet.bill.Module name')}{t('wallet.bill.Ai model')}{t('wallet.bill.Token Length')}{t('wallet.bill.Input Token Length')}{t('wallet.bill.Output Token Length')}{t('wallet.bill.Text Length')}{t('wallet.bill.Duration')}{t('wallet.bill.Data Length')}费用(¥)
{t(item.moduleName)}{item.model || '-'}{item.tokenLen || '-'}{formatPrice(item.amount)}{item.model ?? '-'}{item.tokenLen ?? '-'}{item.inputTokens ?? '-'}{item.outputTokens ?? '-'}{item.textLen ?? '-'}{item.duration ?? '-'}{item.dataLen ?? '-'}{formatStorePrice2Read(item.amount)}
{item.createTime ? dayjs(item.createTime).format('YYYY/MM/DD HH:mm:ss') : '-'} {formatPrice(item.price)}元{formatStorePrice2Read(item.price)}元 {item.status} {item.status === 'NOTPAY' && ( diff --git a/projects/app/src/pages/account/index.tsx b/projects/app/src/pages/account/index.tsx index 6fcf94c22..1d929108a 100644 --- a/projects/app/src/pages/account/index.tsx +++ b/projects/app/src/pages/account/index.tsx @@ -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}` }) => { + + {isOpenPriceBox && } ); }; diff --git a/projects/app/src/pages/api/admin/initv46-2.ts b/projects/app/src/pages/api/admin/initv46-2.ts index f0831cdb3..e99de6a22 100644 --- a/projects/app/src/pages/api/admin/initv46-2.ts +++ b/projects/app/src/pages/api/admin/initv46-2.ts @@ -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'; diff --git a/projects/app/src/pages/api/admin/initv46-fix.ts b/projects/app/src/pages/api/admin/initv46-fix.ts index 6703eee3a..8ddd8e070 100644 --- a/projects/app/src/pages/api/admin/initv46-fix.ts +++ b/projects/app/src/pages/api/admin/initv46-fix.ts @@ -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'; diff --git a/projects/app/src/pages/api/admin/initv46.ts b/projects/app/src/pages/api/admin/initv46.ts index 3d9756a2d..c86c5e83a 100644 --- a/projects/app/src/pages/api/admin/initv46.ts +++ b/projects/app/src/pages/api/admin/initv46.ts @@ -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'; diff --git a/projects/app/src/pages/api/admin/initv462-2.ts b/projects/app/src/pages/api/admin/initv462-2.ts deleted file mode 100644 index ee6c54eff..000000000 --- a/projects/app/src/pages/api/admin/initv462-2.ts +++ /dev/null @@ -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 { - 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); - } -} diff --git a/projects/app/src/pages/api/admin/initv462.ts b/projects/app/src/pages/api/admin/initv462.ts index f9616ca42..7678e778c 100644 --- a/projects/app/src/pages/api/admin/initv462.ts +++ b/projects/app/src/pages/api/admin/initv462.ts @@ -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 中,并做映射 */ diff --git a/projects/app/src/pages/api/admin/initv463-2.ts b/projects/app/src/pages/api/admin/initv463-2.ts index 1681f98f5..ae9d33788 100644 --- a/projects/app/src/pages/api/admin/initv463-2.ts +++ b/projects/app/src/pages/api/admin/initv463-2.ts @@ -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 中,并做映射 */ diff --git a/projects/app/src/pages/api/admin/initv464.ts b/projects/app/src/pages/api/admin/initv464.ts index 9b1df0a33..71057e79e 100644 --- a/projects/app/src/pages/api/admin/initv464.ts +++ b/projects/app/src/pages/api/admin/initv464.ts @@ -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'; diff --git a/projects/app/src/pages/api/common/system/getInitData.ts b/projects/app/src/pages/api/common/system/getInitData.ts index 5f584ecf3..23be2bc4b 100644 --- a/projects/app/src/pages/api/common/system/getInitData.ts +++ b/projects/app/src/pages/api/common/system/getInitData.ts @@ -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; diff --git a/projects/app/src/pages/api/common/system/refreshConfig.ts b/projects/app/src/pages/api/common/system/refreshConfig.ts index 261e4a33a..da3f1756a 100644 --- a/projects/app/src/pages/api/common/system/refreshConfig.ts +++ b/projects/app/src/pages/api/common/system/refreshConfig.ts @@ -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) { 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); diff --git a/projects/app/src/pages/api/core/ai/agent/createQuestionGuide.ts b/projects/app/src/pages/api/core/ai/agent/createQuestionGuide.ts index 494842473..cd441c55d 100644 --- a/projects/app/src/pages/api/core/ai/agent/createQuestionGuide.ts +++ b/projects/app/src/pages/api/core/ai/agent/createQuestionGuide.ts @@ -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 }); diff --git a/projects/app/src/pages/api/core/app/form2Modules/fastgpt-simple.ts b/projects/app/src/pages/api/core/app/form2Modules/fastgpt-simple.ts index 88e5c9be0..ff93cac97 100644 --- a/projects/app/src/pages/api/core/app/form2Modules/fastgpt-simple.ts +++ b/projects/app/src/pages/api/core/app/form2Modules/fastgpt-simple.ts @@ -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 }, { diff --git a/projects/app/src/pages/api/core/app/form2Modules/fastgpt-universal.ts b/projects/app/src/pages/api/core/app/form2Modules/fastgpt-universal.ts index bdbf2ce56..fa75ca327 100644 --- a/projects/app/src/pages/api/core/app/form2Modules/fastgpt-universal.ts +++ b/projects/app/src/pages/api/core/app/form2Modules/fastgpt-universal.ts @@ -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', diff --git a/projects/app/src/pages/api/core/chat/item/getSpeech.ts b/projects/app/src/pages/api/core/chat/item/getSpeech.ts index d157a2ece..4c8629a19 100644 --- a/projects/app/src/pages/api/core/chat/item/getSpeech.ts +++ b/projects/app/src/pages/api/core/chat/item/getSpeech.ts @@ -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 }) diff --git a/projects/app/src/pages/api/core/dataset/collection/list.ts b/projects/app/src/pages/api/core/dataset/collection/list.ts index e1c0f0024..153113184 100644 --- a/projects/app/src/pages/api/core/dataset/collection/list.ts +++ b/projects/app/src/pages/api/core/dataset/collection/list.ts @@ -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'; diff --git a/projects/app/src/pages/api/core/dataset/data/insertData.ts b/projects/app/src/pages/api/core/dataset/data/insertData.ts index 904e0d364..fbf3f411e 100644 --- a/projects/app/src/pages/api/core/dataset/data/insertData.ts +++ b/projects/app/src/pages/api/core/dataset/data/insertData.ts @@ -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 }); diff --git a/projects/app/src/pages/api/core/dataset/data/update.ts b/projects/app/src/pages/api/core/dataset/data/update.ts index 6ca9b4314..05bdbbd6b 100644 --- a/projects/app/src/pages/api/core/dataset/data/update.ts +++ b/projects/app/src/pages/api/core/dataset/data/update.ts @@ -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) { diff --git a/projects/app/src/pages/api/core/dataset/searchTest.ts b/projects/app/src/pages/api/core/dataset/searchTest.ts index 381add32f..7cd74dd8b 100644 --- a/projects/app/src/pages/api/core/dataset/searchTest.ts +++ b/projects/app/src/pages/api/core/dataset/searchTest.ts @@ -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) { 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 }); diff --git a/projects/app/src/pages/api/core/dataset/training/getQueueLen.ts b/projects/app/src/pages/api/core/dataset/training/getQueueLen.ts index 3a0542cae..4aec475e4 100644 --- a/projects/app/src/pages/api/core/dataset/training/getQueueLen.ts +++ b/projects/app/src/pages/api/core/dataset/training/getQueueLen.ts @@ -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, { diff --git a/projects/app/src/pages/api/v1/embeddings.ts b/projects/app/src/pages/api/v1/embeddings.ts index d3a0b2338..4937570f2 100644 --- a/projects/app/src/pages/api/v1/embeddings.ts +++ b/projects/app/src/pages/api/v1/embeddings.ts @@ -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 }) diff --git a/projects/app/src/pages/api/v1/rerank.ts b/projects/app/src/pages/api/v1/rerank.ts index 964e52281..c73ab0b6c 100644 --- a/projects/app/src/pages/api/v1/rerank.ts +++ b/projects/app/src/pages/api/v1/rerank.ts @@ -42,11 +42,9 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex data: result }); } catch (err) { - console.log(err); - jsonRes(res, { - data: inputs.map((input) => ({ - id: input.id - })) + jsonRes(res, { + code: 500, + error: err }); } }); diff --git a/projects/app/src/pages/app/detail/components/Charts/TotalUsage.tsx b/projects/app/src/pages/app/detail/components/Charts/TotalUsage.tsx index 13650ccb5..8822483ef 100644 --- a/projects/app/src/pages/app/detail/components/Charts/TotalUsage.tsx +++ b/projects/app/src/pages/app/detail/components/Charts/TotalUsage.tsx @@ -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 `
${dayjs(data.axisValue).format('YYYY/MM/DD')}
-
${formatPrice(e[0]?.value || 0)}元
+
${formatStorePrice2Read(e[0]?.value || 0)}元
`; } diff --git a/projects/app/src/pages/app/detail/components/AdEdit/Header.tsx b/projects/app/src/pages/app/detail/components/FlowEdit/Header.tsx similarity index 100% rename from projects/app/src/pages/app/detail/components/AdEdit/Header.tsx rename to projects/app/src/pages/app/detail/components/FlowEdit/Header.tsx diff --git a/projects/app/src/pages/app/detail/components/AdEdit/index.tsx b/projects/app/src/pages/app/detail/components/FlowEdit/index.tsx similarity index 97% rename from projects/app/src/pages/app/detail/components/AdEdit/index.tsx rename to projects/app/src/pages/app/detail/components/FlowEdit/index.tsx index baaca24c5..11dbd0761 100644 --- a/projects/app/src/pages/app/detail/components/AdEdit/index.tsx +++ b/projects/app/src/pages/app/detail/components/FlowEdit/index.tsx @@ -47,7 +47,7 @@ const Render = ({ app, onClose }: Props) => { return } />; }; -export default React.memo(function AdEdit(props: Props) { +export default React.memo(function FlowEdit(props: Props) { return ( diff --git a/projects/app/src/pages/app/detail/components/OutLink/SelectUsingWayModal.tsx b/projects/app/src/pages/app/detail/components/OutLink/SelectUsingWayModal.tsx index a2c255e35..de8be2e47 100644 --- a/projects/app/src/pages/app/detail/components/OutLink/SelectUsingWayModal.tsx +++ b/projects/app/src/pages/app/detail/components/OutLink/SelectUsingWayModal.tsx @@ -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: `)} + + + + + + + ); +} + +// @ts-ignore +export default appWithTranslation(App); diff --git a/projects/home/src/pages/_document.tsx b/projects/home/src/pages/_document.tsx new file mode 100644 index 000000000..23b445ffc --- /dev/null +++ b/projects/home/src/pages/_document.tsx @@ -0,0 +1,13 @@ +import { Html, Head, Main, NextScript } from 'next/document'; + +export default function Document() { + return ( + + + +
+ + + + ); +} diff --git a/projects/home/src/pages/_error.tsx b/projects/home/src/pages/_error.tsx new file mode 100644 index 000000000..277ac6e08 --- /dev/null +++ b/projects/home/src/pages/_error.tsx @@ -0,0 +1,19 @@ +import { serviceSideProps } from '@/web/common/utils/i18n'; + +function Error() { + return ( +

+ 部分系统不兼容,导致页面崩溃。如果可以,请联系作者,反馈下具体操作和页面。 大部分是 苹果 的 + safari 浏览器导致,可以尝试更换 chrome + 浏览器。或者是因为开了中文翻译导致,请检查并关闭中文翻译。 +

+ ); +} + +export async function getServerSideProps(context: any) { + return { + props: { ...(await serviceSideProps(context)) } + }; +} + +export default Error; diff --git a/projects/home/src/pages/api/getFeConfig.ts b/projects/home/src/pages/api/getFeConfig.ts new file mode 100644 index 000000000..faef4a382 --- /dev/null +++ b/projects/home/src/pages/api/getFeConfig.ts @@ -0,0 +1,15 @@ +import { readFileSync } from 'fs'; +import type { NextApiRequest, NextApiResponse } from 'next'; + +export default async function handler(req: NextApiRequest, res: NextApiResponse) { + if (global.feConfigs) { + return res.send(global.feConfigs); + } + const filename = + process.env.NODE_ENV === 'development' ? 'data/config.local.json' : '/app/data/config.json'; + const content = JSON.parse(readFileSync(filename, 'utf-8')); + + global.feConfigs = content; + + res.send(global.feConfigs); +} diff --git a/projects/home/src/pages/components/Ability.tsx b/projects/home/src/pages/components/Ability.tsx new file mode 100644 index 000000000..141158cc7 --- /dev/null +++ b/projects/home/src/pages/components/Ability.tsx @@ -0,0 +1,101 @@ +import { Box, BoxProps, Grid, useTheme } from '@chakra-ui/react'; +import React from 'react'; +import { useTranslation } from 'next-i18next'; +import { MyImage } from '@/components/MyImage'; + +const Ability = () => { + const theme = useTheme(); + const { t } = useTranslation(); + + const CardStyles: BoxProps = { + pt: 4, + borderRadius: 'xl', + overflow: 'hidden', + border: theme.borders.base + }; + const TitleStyles: BoxProps = { + px: 4, + fontSize: ['xl', '28px'], + fontWeight: 'bold' + }; + const DescStyles: BoxProps = { + px: 4, + mt: 2, + mb: 5, + fontSize: ['sm', 'md'], + whiteSpace: 'pre-wrap' + }; + + return ( + + + {t('home.FastGPT Ability')} + + + + + {t('home.AI Assistant')} + + + {t('home.AI Assistant Desc')} + + + + + + {t('home.Dateset')} + + + {t('home.Dateset Desc')} + + + + + + + {t('home.Advanced Settings')} + + {t('home.Advanced Settings Desc')} + + + + + {t('home.OpenAPI')} + {t('home.OpenAPI Desc')} + + + + + ); +}; + +export default Ability; diff --git a/projects/home/src/pages/components/Choice.tsx b/projects/home/src/pages/components/Choice.tsx new file mode 100644 index 000000000..d39707210 --- /dev/null +++ b/projects/home/src/pages/components/Choice.tsx @@ -0,0 +1,97 @@ +import { Box, Image, Flex, Grid, useTheme } from '@chakra-ui/react'; +import React from 'react'; +import { useTranslation } from 'next-i18next'; +import MyTooltip from '@/components/MyTooltip'; + +const Choice = () => { + const theme = useTheme(); + const { t } = useTranslation(); + + const list = [ + { + icon: '/imgs/home/icon_1.svg', + title: t('home.Choice Open'), + desc: t('home.Choice Open Desc'), + tooltip: '前往 GitHub', + onClick: () => window.open('https://github.com/labring/FastGPT', '_blank') + }, + { + icon: '/imgs/home/icon_2.svg', + title: t('home.Choice QA'), + desc: t('home.Choice QA Desc') + }, + { + icon: '/imgs/home/icon_3.svg', + title: t('home.Choice Visual'), + desc: t('home.Choice Visual Desc') + }, + { + icon: '/imgs/home/icon_4.svg', + title: t('home.Choice Extension'), + desc: t('home.Choice Extension Desc') + }, + { + icon: '/imgs/home/icon_5.svg', + title: t('home.Choice Debug'), + desc: t('home.Choice Debug Desc') + }, + { + icon: '/imgs/home/icon_6.svg', + title: t('home.Choice Models'), + desc: t('home.Choice Models Desc') + } + ]; + + return ( + + + {t('home.Why FastGPT')} + + + {list.map((item) => ( + + item.onClick?.()} + > + + {''} + + + + {item.title} + + + {item.desc} + + + + + ))} + + + ); +}; + +export default Choice; diff --git a/projects/home/src/pages/components/Footer.tsx b/projects/home/src/pages/components/Footer.tsx new file mode 100644 index 000000000..7143f794b --- /dev/null +++ b/projects/home/src/pages/components/Footer.tsx @@ -0,0 +1,121 @@ +import React, { useMemo } from 'react'; +import { Box, Flex, Link, useDisclosure } from '@chakra-ui/react'; +import { feConfigs } from '@/web/common/system/staticData'; +import { useTranslation } from 'next-i18next'; +import Avatar from '@/components/Avatar'; +import CommunityModal from '@/components/CommunityModal'; + +const Footer = () => { + const { t } = useTranslation(); + const { isOpen, onOpen, onClose } = useDisclosure(); + const list = useMemo( + () => [ + { + label: t('home.Footer Product'), + child: [ + { + label: t('home.Footer FastGPT Cloud'), + url: feConfigs?.loginUrl + }, + { + label: 'Sealos', + url: 'https://github.com/labring/sealos' + }, + { + label: 'Laf', + url: 'https://github.com/labring/laf' + } + ] + }, + { + label: t('home.Footer Developer'), + child: [ + { + label: t('home.Footer Git'), + url: ' https://github.com/labring/FastGPT' + }, + { + label: t('home.Footer Docs'), + url: feConfigs?.docUrl + } + ] + }, + { + label: t('home.Footer Support'), + child: [ + { + label: t('home.Footer Feedback'), + url: 'https://github.com/labring/FastGPT/issues' + }, + { + label: t('home.Community'), + onClick: () => { + onOpen(); + } + } + ] + } + ], + [onOpen, t] + ); + + return ( + + + + + + FastGPT + + + + {t('home.FastGPT Desc')} + + + {list.map((item) => ( + + {item.label} + {item.child.map((child) => + child.url ? ( + + {child.label} + + ) : ( + + {child.label} + + ) + )} + + ))} + {isOpen && } + + ); +}; + +export default Footer; diff --git a/projects/home/src/pages/components/Hero.tsx b/projects/home/src/pages/components/Hero.tsx new file mode 100644 index 000000000..6a983bf19 --- /dev/null +++ b/projects/home/src/pages/components/Hero.tsx @@ -0,0 +1,122 @@ +import { Box, Flex, Button, Image, useMediaQuery } from '@chakra-ui/react'; +import React, { useEffect, useState } from 'react'; +import { useTranslation } from 'next-i18next'; +import MyIcon from '@/components/Icon'; +import { useRouter } from 'next/router'; +import axios from 'axios'; +import { feConfigs } from '@/web/common/system/staticData'; + +const Hero = () => { + const router = useRouter(); + const { t } = useTranslation(); + const [star, setStar] = useState(6900); + const [showVideo, setShowVide] = useState(false); + const [isPc] = useMediaQuery('(min-width: 900px)'); + + useEffect(() => { + (async () => { + const { data: git } = await axios.get('https://api.github.com/repos/labring/FastGPT'); + setStar(git.stargazers_count); + })(); + }, []); + + return ( + + + {t('home.slogan')} + + + {t('home.desc')} + + + + + + + + setShowVide(true)} + /> + + {showVideo && ( + setShowVide(false)} + > + e.preventDefault()} + > + + + )} + + ); +}; + +export default Hero; diff --git a/projects/home/src/pages/components/Navbar.tsx b/projects/home/src/pages/components/Navbar.tsx new file mode 100644 index 000000000..b7db7870a --- /dev/null +++ b/projects/home/src/pages/components/Navbar.tsx @@ -0,0 +1,154 @@ +import React, { useEffect, useMemo, useState } from 'react'; +import { Flex, Box, type BoxProps, Button, useDisclosure, useMediaQuery } from '@chakra-ui/react'; +import { feConfigs } from '@/web/common/system/staticData'; +import { useTranslation } from 'next-i18next'; +import Avatar from '@/components/Avatar'; +import CommunityModal from '@/components/CommunityModal'; +import MyIcon from '@/components/Icon'; + +const Navbar = () => { + const { t } = useTranslation(); + const [scrollTop, setScrollTop] = useState(0); + const { + isOpen: isOpenCommunity, + onOpen: onOpenCommunity, + onClose: onCloseCommunity + } = useDisclosure(); + const { isOpen: isOpenMenu, onOpen: onOpenMenu, onClose: onCloseMenu } = useDisclosure(); + const [isPc] = useMediaQuery('(min-width: 900px)'); + + const menuList = [ + { + label: t('home.Commercial'), + key: 'Commercial', + onClick: () => { + window.open(feConfigs?.commercialDocUrl); + } + }, + { + label: t('home.Community'), + key: 'community', + onClick: () => { + onOpenCommunity(); + } + }, + { + label: t('home.Docs'), + key: 'docs', + onClick: () => { + window.open(feConfigs?.docUrl); + } + } + ]; + const bgOpacity = useMemo(() => { + const rate = scrollTop / 120; + if (rate > 0.7) { + return 0.7; + } + return rate; + }, [scrollTop]); + + const menuStyles: BoxProps = { + mr: 4, + px: 5, + py: 2, + cursor: 'pointer', + transition: '0.5s', + borderRadius: 'xl', + fontSize: 'lg', + _hover: { + bg: 'myGray.200' + } + }; + + useEffect(() => { + const scrollListen = (e: any) => { + setScrollTop(e?.target?.scrollTop); + }; + const dom = document.getElementById('home'); + if (!dom) return; + + dom.addEventListener('scroll', scrollListen); + + return () => { + dom.removeEventListener('scroll', scrollListen); + }; + }, []); + + return ( + + + + + FastGPT + + + {isPc ? ( + <> + {menuList.map((item) => ( + + {item.label} + + ))} + + | + + window.open(feConfigs?.loginUrl, '_self')}> + {t('home.Login')} + + + + ) : ( + (isOpenMenu ? onCloseMenu() : onOpenMenu())} + /> + )} + + {isOpenMenu && !isPc && ( + + {menuList.map((item) => ( + + {item.label} + + ))} + + window.open(feConfigs?.loginUrl, '_self')}> + {t('home.Login')} + + + + )} + {isOpenCommunity && } + + ); +}; + +export default Navbar; diff --git a/projects/home/src/pages/index.module.scss b/projects/home/src/pages/index.module.scss new file mode 100644 index 000000000..ef4de9b3d --- /dev/null +++ b/projects/home/src/pages/index.module.scss @@ -0,0 +1,17 @@ +.home { + * { + position: relative; + } + + .textlg { + background: linear-gradient( + to bottom right, + #1237b3 0%, + #3370ff 40%, + #4e83fd 80%, + #85b1ff 100% + ); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + } +} diff --git a/projects/home/src/pages/index.tsx b/projects/home/src/pages/index.tsx new file mode 100644 index 000000000..532ac6a25 --- /dev/null +++ b/projects/home/src/pages/index.tsx @@ -0,0 +1,49 @@ +import React, { useEffect } from 'react'; +import { Box } from '@chakra-ui/react'; +import { serviceSideProps } from '@/web/common/utils/i18n'; +import { useRouter } from 'next/router'; + +import Navbar from './components/Navbar'; +import Hero from './components/Hero'; +import Ability from './components/Ability'; +import Choice from './components/Choice'; +import Footer from './components/Footer'; + +const Home = () => { + const router = useRouter(); + + useEffect(() => { + router.prefetch('/app/list'); + router.prefetch('/login'); + }, [router]); + + return ( + <> + + + + + + + + + + + + +