diff --git a/README.md b/README.md index 6535a4919a3b..8d15476afbf1 100644 --- a/README.md +++ b/README.md @@ -128,7 +128,7 @@ https://github.com/labring/FastGPT/assets/15308462/7d3a38df-eb0e-4388-9250-2409b ## 🏘️ 加入我们 -我们正在寻找志同道合的小伙伴,加速 FastGPT 的发展。你可以通过 [FastGPT 2025 招聘](https://fael3z0zfze.feishu.cn/wiki/P7FOwEmPziVcaYkvVaacnVX1nvg) 了解 FastGPT 的招聘信息。 +我们正在寻找志同道合的小伙伴,加速 FastGPT 的发展。你可以通过 [FastGPT 2025 招聘](https://fael3z0zfze.feishu.cn/wiki/P7FOwEmPziVcaYkvVaacnVX1nvg)了解 FastGPT 的招聘信息。 ## 💪 相关项目 diff --git a/docSite/assets/imgs/api-dataset-1.png b/docSite/assets/imgs/api-dataset-1.png new file mode 100644 index 000000000000..6116f95f3e5c Binary files /dev/null and b/docSite/assets/imgs/api-dataset-1.png differ diff --git a/docSite/assets/imgs/htmlRendering1.png b/docSite/assets/imgs/htmlRendering1.png new file mode 100644 index 000000000000..b1c5e4562ec0 Binary files /dev/null and b/docSite/assets/imgs/htmlRendering1.png differ diff --git a/docSite/assets/imgs/htmlRendering2.png b/docSite/assets/imgs/htmlRendering2.png new file mode 100644 index 000000000000..19c7517ddb07 Binary files /dev/null and b/docSite/assets/imgs/htmlRendering2.png differ diff --git a/docSite/assets/imgs/htmlRendering3.png b/docSite/assets/imgs/htmlRendering3.png new file mode 100644 index 000000000000..31bf7f41d426 Binary files /dev/null and b/docSite/assets/imgs/htmlRendering3.png differ diff --git a/docSite/assets/imgs/image-10.png b/docSite/assets/imgs/image-10.png new file mode 100644 index 000000000000..55abef8fdc43 Binary files /dev/null and b/docSite/assets/imgs/image-10.png differ diff --git a/docSite/assets/imgs/image-11.png b/docSite/assets/imgs/image-11.png new file mode 100644 index 000000000000..fa33fa8928c9 Binary files /dev/null and b/docSite/assets/imgs/image-11.png differ diff --git a/docSite/assets/imgs/image-12.png b/docSite/assets/imgs/image-12.png new file mode 100644 index 000000000000..516c5dc58aff Binary files /dev/null and b/docSite/assets/imgs/image-12.png differ diff --git a/docSite/assets/imgs/image-13.png b/docSite/assets/imgs/image-13.png new file mode 100644 index 000000000000..e6096a9deca4 Binary files /dev/null and b/docSite/assets/imgs/image-13.png differ diff --git a/docSite/assets/imgs/image-14.png b/docSite/assets/imgs/image-14.png new file mode 100644 index 000000000000..1c8750fdc2ec Binary files /dev/null and b/docSite/assets/imgs/image-14.png differ diff --git a/docSite/assets/imgs/image-15.png b/docSite/assets/imgs/image-15.png new file mode 100644 index 000000000000..eec7cc75301f Binary files /dev/null and b/docSite/assets/imgs/image-15.png differ diff --git a/docSite/assets/imgs/image-16.png b/docSite/assets/imgs/image-16.png new file mode 100644 index 000000000000..cda0a8bd92d3 Binary files /dev/null and b/docSite/assets/imgs/image-16.png differ diff --git a/docSite/assets/imgs/image-17.png b/docSite/assets/imgs/image-17.png new file mode 100644 index 000000000000..888a0644dd0a Binary files /dev/null and b/docSite/assets/imgs/image-17.png differ diff --git a/docSite/assets/imgs/image-18.png b/docSite/assets/imgs/image-18.png new file mode 100644 index 000000000000..39c037003162 Binary files /dev/null and b/docSite/assets/imgs/image-18.png differ diff --git a/docSite/assets/imgs/image-19.png b/docSite/assets/imgs/image-19.png new file mode 100644 index 000000000000..86216b2b631f Binary files /dev/null and b/docSite/assets/imgs/image-19.png differ diff --git a/docSite/content/zh-cn/docs/development/configuration.md b/docSite/content/zh-cn/docs/development/configuration.md index eeee58b706ab..06e4e2b799ad 100644 --- a/docSite/content/zh-cn/docs/development/configuration.md +++ b/docSite/content/zh-cn/docs/development/configuration.md @@ -43,7 +43,7 @@ weight: 708 "usedInExtractFields": true, // 是否用于内容提取(务必保证至少有一个为true) "usedInToolCall": true, // 是否用于工具调用(务必保证至少有一个为true) "usedInQueryExtension": true, // 是否用于问题优化(务必保证至少有一个为true) - "toolChoice": true, // 是否支持工具选择(分类,内容提取,工具调用会用到。目前只有gpt支持) + "toolChoice": true, // 是否支持工具选择(分类,内容提取,工具调用会用到。) "functionCall": false, // 是否支持函数调用(分类,内容提取,工具调用会用到。会优先使用 toolChoice,如果为false,则使用 functionCall,如果仍为 false,则使用提示词模式) "customCQPrompt": "", // 自定义文本分类提示词(不支持工具和函数调用的模型 "customExtractPrompt": "", // 自定义内容提取提示词 @@ -95,9 +95,7 @@ weight: 708 "customExtractPrompt": "", "defaultSystemChatPrompt": "", "defaultConfig": { - "temperature": 1, - "max_tokens": null, - "stream": false + "temperature": 1 } }, { @@ -122,9 +120,7 @@ weight: 708 "customExtractPrompt": "", "defaultSystemChatPrompt": "", "defaultConfig": { - "temperature": 1, - "max_tokens": null, - "stream": false + "temperature": 1 } } ], diff --git a/docSite/content/zh-cn/docs/development/custom-models/marker.md b/docSite/content/zh-cn/docs/development/custom-models/marker.md new file mode 100644 index 000000000000..4cccf48f58ee --- /dev/null +++ b/docSite/content/zh-cn/docs/development/custom-models/marker.md @@ -0,0 +1,64 @@ +--- +title: '接入 Marker PDF 文档解析' +description: '使用 Marker 解析 PDF 文档,可实现图片提取和布局识别' +icon: 'api' +draft: false +toc: true +weight: 909 +--- + +## 背景 + +PDF 是一个相对复杂的文件格式,在 FastGPT 内置的 pdf 解析器中,依赖的是 pdfjs 库解析,该库基于逻辑解析,无法有效的理解复杂的 pdf 文件。所以我们在解析 pdf 时候,如果遇到图片、表格、公式等非简单文本内容,会发现解析效果不佳。 + +市面上目前有多种解析 PDF 的方法,比如使用 [Marker](https://github.com/VikParuchuri/marker),该项目使用了 Surya 模型,基于视觉解析,可以有效提取图片、表格、公式等复杂内容。为了可以让 Marker 快速接入 FastGPT,我们做了一个自定义解析的拓展 Demo。 + +在 FastGPT 4.8.15 版本中,你可以通过增加一个环境变量,来替换掉 FastGPT 系统内置解析器,实现自定义的文档解析服务。该功能只是 Demo 阶段,后期配置模式和交互规则会发生改动。 + +## 使用教程 + +### 1. 按照 Marker + +参考文档 [Marker 安装教程](https://github.com/labring/FastGPT/tree/main/python/pdf-marker),安装 Marker 模型。封装的 API 已经适配了 FastGPT 自定义解析服务。 + +这里介绍快速 Docker 按照的方法: + +``` +``` + +### 2. 添加 FastGPT 环境变量 + +``` +CUSTOM_READ_FILE_URL=http://xxxx.com/v1/parse/file +CUSTOM_READ_FILE_EXTENSION=pdf +``` + +* CUSTOM_READ_FILE_URL - 自定义解析服务的地址, host改成解析服务的访问地址,path 不能变动。 +* CUSTOM_READ_FILE_EXTENSION - 支持的文件后缀,多个文件类型,可用逗号隔开。 + +### 3. 测试效果 + +通过知识库上传一个 pdf 文件,并确认上传,可以在日志中看到 LOG (LOG_LEVEL需要设置 info 或者 debug): + +``` +[Info] 2024-12-05 15:04:42 Parsing files from an external service +[Info] 2024-12-05 15:07:08 Custom file parsing is complete, time: 1316ms +``` + +然后你就可以发现,通过 Marker 解析出来的 pdf 会携带图片链接: + +![alt text](/imgs/image-10.png) + + +## 效果展示 + +以清华的 [ChatDev Communicative Agents for Software Develop.pdf](https://arxiv.org/abs/2307.07924) 为例,展示 Marker 解析的效果: + +| | | | +| --- | --- | --- | +| ![alt text](/imgs/image-11.png) | ![alt text](/imgs/image-12.png) | ![alt text](/imgs/image-13.png) | +| ![alt text](/imgs/image-14.png) | ![alt text](/imgs/image-15.png) | ![alt text](/imgs/image-16.png) | + +上图是分块后的结果,下图是 pdf 原文。整体图片、公式、表格都可以提取出来,效果还是杠杠的。 + +不过要注意的是,[Marker](https://github.com/VikParuchuri/marker) 的协议是`GPL-3.0 license`,请在遵守协议的前提下使用。 \ No newline at end of file diff --git a/docSite/content/zh-cn/docs/development/custom-models/xinference.md b/docSite/content/zh-cn/docs/development/custom-models/xinference.md index e579fbb55ce9..6cd1cdee4ae8 100644 --- a/docSite/content/zh-cn/docs/development/custom-models/xinference.md +++ b/docSite/content/zh-cn/docs/development/custom-models/xinference.md @@ -145,7 +145,7 @@ curl --location --request POST 'https:///v1/chat/completions' \ "usedInExtractFields": true, // 是否用于内容提取(务必保证至少有一个为true) "usedInToolCall": true, // 是否用于工具调用(务必保证至少有一个为true) "usedInQueryExtension": true, // 是否用于问题优化(务必保证至少有一个为true) - "toolChoice": true, // 是否支持工具选择(分类,内容提取,工具调用会用到。目前只有gpt支持) + "toolChoice": true, // 是否支持工具选择(分类,内容提取,工具调用会用到。) "functionCall": false, // 是否支持函数调用(分类,内容提取,工具调用会用到。会优先使用 toolChoice,如果为false,则使用 functionCall,如果仍为 false,则使用提示词模式) "customCQPrompt": "", // 自定义文本分类提示词(不支持工具和函数调用的模型 "customExtractPrompt": "", // 自定义内容提取提示词 diff --git a/docSite/content/zh-cn/docs/development/openapi/dataset.md b/docSite/content/zh-cn/docs/development/openapi/dataset.md index 0e9b27a8cf4e..15f5145a1eef 100644 --- a/docSite/content/zh-cn/docs/development/openapi/dataset.md +++ b/docSite/content/zh-cn/docs/development/openapi/dataset.md @@ -407,9 +407,7 @@ curl --location --request POST 'http://localhost:3000/api/core/dataset/collectio - parentId: 父级ID,不填则默认为根目录 - name: 集合名称(必填) - metadata: 元数据(暂时没啥用) -- trainingType:(必填) - - chunk: 按文本长度进行分割 - - qa: QA拆分 +- trainingType: 训练模式(必填) - chunkSize: 每个 chunk 的长度(可选). chunk模式:100~3000; qa模式: 4000~模型最大token(16k模型通常建议不超过10000) - chunkSplitter: 自定义最高优先分割符号(可选) - qaPrompt: qa拆分自定义提示词(可选) @@ -483,9 +481,7 @@ curl --location --request POST 'http://localhost:3000/api/core/dataset/collectio - datasetId: 知识库的ID(必填) - parentId: 父级ID,不填则默认为根目录 - metadata.webPageSelector: 网页选择器,用于指定网页中的哪个元素作为文本(可选) -- trainingType:(必填) - - chunk: 按文本长度进行分割 - - qa: QA拆分 +- trainingType:训练模式(必填) - chunkSize: 每个 chunk 的长度(可选). chunk模式:100~3000; qa模式: 4000~模型最大token(16k模型通常建议不超过10000) - chunkSplitter: 自定义最高优先分割符号(可选) - qaPrompt: qa拆分自定义提示词(可选) @@ -505,7 +501,13 @@ data 为集合的 ID。 "statusText": "", "message": "", "data": { - "collectionId": "65abd0ad9d1448617cba6031" + "collectionId": "65abd0ad9d1448617cba6031", + "results": { + "insertLen": 1, + "overToken": [], + "repeat": [], + "error": [] + } } } ``` @@ -544,9 +546,7 @@ curl --location --request POST 'http://localhost:3000/api/core/dataset/collectio - data: 知识库相关信息(json序列化后传入) - datasetId: 知识库的ID(必填) - parentId: 父级ID,不填则默认为根目录 - - trainingType:(必填) - - chunk: 按文本长度进行分割 - - qa: QA拆分 + - trainingType:训练模式(必填) - chunkSize: 每个 chunk 的长度(可选). chunk模式:100~3000; qa模式: 4000~模型最大token(16k模型通常建议不超过10000) - chunkSplitter: 自定义最高优先分割符号(可选) - qaPrompt: qa拆分自定义提示词(可选) @@ -581,6 +581,82 @@ data 为集合的 ID。 {{< /tab >}} {{< /tabs >}} +### 创建一个API集合 + +传入一个文件的 id,创建一个集合,会读取文件内容进行分割。目前支持:pdf, docx, md, txt, html, csv。 + +{{< tabs tabTotal="3" >}} +{{< tab tabName="请求示例" >}} +{{< markdownify >}} + +使用代码上传时,请注意中文 filename 需要进行 encode 处理,否则容易乱码。 + +```bash +curl --location --request POST 'http://localhost:3000/api/core/dataset/collection/create/apiCollection' \ +--header 'Authorization: Bearer fastgpt-xxx' \ +--header 'Content-Type: application/json' \ +--data-raw '{ + "name": "A Quick Guide to Building a Discord Bot.pdf", + "apiFileId":"A Quick Guide to Building a Discord Bot.pdf", + + "datasetId": "674e9e479c3503c385495027", + "parentId": null, + + "trainingType": "chunk", + "chunkSize":512, + "chunkSplitter":"", + "qaPrompt":"" +}' +``` + +{{< /markdownify >}} +{{< /tab >}} + +{{< tab tabName="参数说明" >}} +{{< markdownify >}} + +需要使用 POST form-data 的格式上传。包含 file 和 data 两个字段。 + +{{% alert icon=" " context="success" %}} +- name: 集合名,建议就用文件名,必填。 +- apiFileId: 文件的ID,必填。 +- datasetId: 知识库的ID(必填) +- parentId: 父级ID,不填则默认为根目录 +- trainingType:训练模式(必填) +- chunkSize: 每个 chunk 的长度(可选). chunk模式:100~3000; qa模式: 4000~模型最大token(16k模型通常建议不超过10000) +- chunkSplitter: 自定义最高优先分割符号(可选) +- qaPrompt: qa拆分自定义提示词(可选) +{{% /alert %}} + +{{< /markdownify >}} +{{< /tab >}} + +{{< tab tabName="响应示例" >}} +{{< markdownify >}} + +data 为集合的 ID。 + +```json +{ + "code": 200, + "statusText": "", + "message": "", + "data": { + "collectionId": "65abc044e4704bac793fbd81", + "results": { + "insertLen": 1, + "overToken": [], + "repeat": [], + "error": [] + } + } +} +``` + +{{< /markdownify >}} +{{< /tab >}} +{{< /tabs >}} + ### 创建一个外部文件库集合(商业版) {{< tabs tabTotal="3" >}} @@ -637,7 +713,12 @@ data 为集合的 ID。 "message": "", "data": { "collectionId": "6646fcedfabd823cdc6de746", - "insertLen": 3 + "results": { + "insertLen": 1, + "overToken": [], + "repeat": [], + "error": [] + } } } ``` @@ -1017,9 +1098,7 @@ curl --location --request POST 'https://api.fastgpt.in/api/core/dataset/data/pus {{% alert icon=" " context="success" %}} - collectionId: 集合ID(必填) -- trainingType:(必填) - - chunk: 按文本长度进行分割 - - qa: QA拆分 +- trainingType:训练模式(必填) - prompt: 自定义 QA 拆分提示词,需严格按照模板,建议不要传入。(选填) - data:(具体数据) - q: 主要数据(必填) diff --git a/docSite/content/zh-cn/docs/development/upgrading/4811.md b/docSite/content/zh-cn/docs/development/upgrading/4811.md index 24ffbe90cb8a..548dfdccf87f 100644 --- a/docSite/content/zh-cn/docs/development/upgrading/4811.md +++ b/docSite/content/zh-cn/docs/development/upgrading/4811.md @@ -38,11 +38,7 @@ weight: 813 "customExtractPrompt": "", "defaultSystemChatPrompt": "", "defaultConfig": { - "temperature": 1, - "stream": false - }, - "fieldMap": { - "max_tokens": "max_completion_tokens" + "temperature": 1 } }, { @@ -67,11 +63,7 @@ weight: 813 "customExtractPrompt": "", "defaultSystemChatPrompt": "", "defaultConfig": { - "temperature": 1, - "stream": false - }, - "fieldMap": { - "max_tokens": "max_completion_tokens" + "temperature": 1 } } ``` diff --git a/docSite/content/zh-cn/docs/development/upgrading/4815.md b/docSite/content/zh-cn/docs/development/upgrading/4815.md new file mode 100644 index 000000000000..33d10a7893b8 --- /dev/null +++ b/docSite/content/zh-cn/docs/development/upgrading/4815.md @@ -0,0 +1,27 @@ +--- +title: 'V4.8.15(进行中)' +description: 'FastGPT V4.8.15 更新说明' +icon: 'upgrade' +draft: false +toc: true +weight: 809 +--- + + +## 完整更新内容 + +1. 新增 - API 知识库, 见 [API 知识库介绍](/docs/guide/knowledge_base/api_dataset/),外部文件库会被弃用。 +2. 新增 - 工具箱页面,展示所有可用的系统资源。商业版后台可更便捷的配置系统插件和自定义分类。 +3. 新增 - Markdown 中,HTML代码会被额外渲染,可以选择预览模式,会限制所有 script 脚本,仅做展示。 +4. 新增 - 自定义系统级文件解析服务, 见 [接入 Marker PDF 文档解析](/docs/development/custom-models/marker/) +5. 新增 - 集合直接重新调整参数,无需删除再导入。 +6. 新增 - 商业版后台支持配置侧边栏跳转链接。 +7. 优化 - base64 图片截取判断。 +8. 优化 - i18n cookie 判断。 +9. 优化 - 支持 Markdown 文本分割时,只有标题,无内容。 +10. 优化 - 字符串变量替换,未赋值的变量会转成 undefined,而不是保留原来 id 串。 +11. 优化 - 全局变量默认值在 API 生效,并且自定义变量支持默认值。 +12. 修复 - 分享链接点赞鉴权问题。 +13. 修复 - 对话页面切换自动执行应用时,会误触发非自动执行应用。 +14. 修复 - 语言播放鉴权问题。 +15. 修复 - 插件应用知识库引用上限始终为 3000 \ No newline at end of file diff --git a/docSite/content/zh-cn/docs/guide/DialogBoxes/_index.md b/docSite/content/zh-cn/docs/guide/DialogBoxes/_index.md new file mode 100644 index 000000000000..5e2ef9f57e99 --- /dev/null +++ b/docSite/content/zh-cn/docs/guide/DialogBoxes/_index.md @@ -0,0 +1,9 @@ +--- +weight: 470 +title: '对话框' +description: '对话框组件,支持多种交互方式,提升用户在应用中的交互体验。' +icon: 'chat_bubble' +draft: false +images: [] +--- + diff --git a/docSite/content/zh-cn/docs/guide/DialogBoxes/htmlRendering.md b/docSite/content/zh-cn/docs/guide/DialogBoxes/htmlRendering.md new file mode 100644 index 000000000000..4d0484522478 --- /dev/null +++ b/docSite/content/zh-cn/docs/guide/DialogBoxes/htmlRendering.md @@ -0,0 +1,57 @@ +--- +title: "对话框与HTML渲染" +description: "如何在FastGPT中通过Markdown嵌入HTML代码块,并提供全屏、源代码切换等交互功能" +icon: "group" +draft: false +toc: true +weight: 470 +--- + +| 源码模式 | 预览模式 | 全屏模式 | +| --- | --- | --- | +| ![](/imgs/htmlRendering1.png) | ![](/imgs/htmlRendering2.png) | ![](/imgs/htmlRendering3.png) | + + +### 1. **设计背景** + + 尽管Markdown本身支持嵌入HTML标签,但由于安全问题,许多平台和环境对HTML的渲染进行了限制,特别是在渲染动态内容、交互式元素以及外部资源时。这些限制大大降低了用户在撰写和展示复杂文档时的灵活性,尤其是当需要嵌入外部HTML内容时。为了应对这一问题,我们通过使用 `iframe` 来嵌入和渲染HTML内容,并结合 `sandbox` 属性,保障了外部HTML的安全渲染。 + +### 2. 功能简介 + + 该功能模块的主要目的是扩展FastGPT在Markdown渲染中的能力,支持嵌入和渲染HTML内容。由于是利用 Iframe 渲染,所以无法确认内容的高度,FastGPT 中会给 Iframe 设置一个固定高度来进行渲染。并且不支持 HTML 中执行 js 脚本。 + +### 3. 技术实现 + + 本模块通过以下方式实现了HTML渲染和互动功能: + + - **组件设计**:该模块通过渲染 `iframe` 类型的代码块展示HTML内容。使用自定义的 `IframeBlock` 组件,结合 `sandbox` 属性来保障嵌入内容的安全性。`sandbox` 限制了外部HTML中的行为,如禁用脚本执行、限制表单提交等,确保HTML内容的安全性。通过辅助函数与渲染Markdown内容的部分结合,处理 `iframe` 嵌入的HTML内容。 + - **安全机制**:通过 `iframe` 的 `sandbox` 属性和 `referrerPolicy` 来防止潜在的安全风险。`sandbox` 属性提供了细粒度的控制,允许特定的功能(如脚本、表单、弹出窗口等)在受限的环境中执行,以确保渲染的HTML内容不会对系统造成威胁。 + - **展示与互动功能**:用户可以通过不同的展示模式(如全屏、预览、源代码模式)自由切换,以便更灵活地查看和控制嵌入的HTML内容。嵌入的 `iframe` 自适应父容器的宽度,同时保证 `iframe`嵌入的内容能够适当显示。 + +### 4. 如何使用 + +你只需要通过 Markdown 代码块格式,并标记语言为 `html` 即可。例如: + +```md +```html + + + + + + + 欢迎使用FastGPT + + + + + + +``` \ No newline at end of file diff --git a/docSite/content/zh-cn/docs/guide/knowledge_base/api_dataset.md b/docSite/content/zh-cn/docs/guide/knowledge_base/api_dataset.md new file mode 100644 index 000000000000..465164e85581 --- /dev/null +++ b/docSite/content/zh-cn/docs/guide/knowledge_base/api_dataset.md @@ -0,0 +1,183 @@ +--- +title: 'API 文件库' +description: 'FastGPT API 文件库功能介绍和使用方式' +icon: 'language' +draft: false +toc: true +weight: 405 +--- + +| | | +| --- | --- | +| ![](/imgs/image-18.png) | ![](/imgs/image-19.png) | + +## 背景 + +目前 FastGPT 支持本地文件导入,但是很多时候,用户自身已经有了一套文档库,如果把文件重复导入一遍,会造成二次存储,并且不方便管理。因为 FastGPT 提供了一个 API 文件库的概念,可以通过简单的 API 接口,去拉取已有的文档库,并且可以灵活配置是否导入。 + +API 文件库能够让用户轻松对接已有的文档库,只需要按照 FastGPT 的 API 文件库规范,提供相应文件接口,然后将服务接口的 baseURL 和 token 填入知识库创建参数中,就能直接在页面上拿到文件库的内容,并选择性导入 + +## 如何使用 API 文件库 + +创建知识库时,选择 API 文件库类型,然后需要配置两个关键参数:文件服务接口的 baseURL 和用于身份验证的请求头信息。只要提供的接口规范符合 FastGPT 的要求,系统就能自动获取并展示完整的文件列表,可以根据需要选择性地将文件导入到知识库中。 + +你需要提供两个参数: +- baseURL: 文件服务接口的 baseURL +- authorization: 用于身份验证的请求头信息,实际请求格式为 `Authorization: Bearer ` + +## 接口规范 + +接口响应格式: + +```ts +type ResponseType = { + success: boolean; + message: string; + data: any; +} +``` + +数据类型: + +```ts +// 文件列表中,单项的文件类型 +type FileListItem = { + id: string; + parentId: string | null; + name: string; + type: 'file' | 'folder'; + updateTime: Date; + createTime: Date; +} +``` + + +### 1. 获取文件树 + +{{< tabs tabTotal="2" >}} +{{< tab tabName="请求示例" >}} +{{< markdownify >}} + +{{% alert icon=" " context="success" %}} +- parentId - 父级 id,可选,或者 null。 +- searchKey - 检索词,可选 +{{% /alert %}} + +```bash +curl --location --request POST '{{baseURL}}/v1/file/list' \ +--header 'Authorization: Bearer {{authorization}}' \ +--header 'Content-Type: application/json' \ +--data-raw '{ + "parentId": null, + "searchKey": "" +}' +``` + +{{< /markdownify >}} +{{< /tab >}} + +{{< tab tabName="响应示例" >}} +{{< markdownify >}} + +```json +{ + "code": 200, + "success": true, + "message": "", + "data": [ + { + "id": "xxxx", + "parentId": "xxxx", + "type": "file", // file | folder + "name":"test.json", + "updateTime":"2024-11-26T03:05:24.759Z", + "createTime":"2024-11-26T03:05:24.759Z" + } + ] +} +``` + +{{< /markdownify >}} +{{< /tab >}} +{{< /tabs >}} + +### 2. 获取单个文件内容(文本内容或访问链接) + +{{< tabs tabTotal="3" >}} +{{< tab tabName="请求示例" >}} +{{< markdownify >}} + +```bash +curl --location --request GET '{{baseURL}}/v1/file/content?id=xx' \ +--header 'Authorization: Bearer {{authorization}}' +``` + +{{< /markdownify >}} +{{< /tab >}} + +{{< tab tabName="响应示例" >}} +{{< markdownify >}} + +```json +{ + "code": 200, + "success": true, + "message": "", + "data": { + "content": "FastGPT 是一个基于 LLM 大语言模型的知识库问答系统,提供开箱即用的数据处理、模型调用等能力。同时可以通过 Flow 可视化进行工作流编排,从而实现复杂的问答场景!\n", + "previewUrl": "xxxx" + } +} +``` + +{{% alert icon=" " context="success" %}} +二选一返回,如果同时返回则 content 优先级更高。 + +- content - 文件内容,直接拿来用。 +- previewUrl - 文件链接,系统会请求该地址获取文件内容。 +{{% /alert %}} + +{{< /markdownify >}} +{{< /tab >}} +{{< /tabs >}} + + +### 3. 获取文件阅读链接(用于查看原文) + +{{< tabs tabTotal="2" >}} +{{< tab tabName="请求示例" >}} +{{< markdownify >}} + +id 为文件的 id。 + +```bash +curl --location --request GET '{{baseURL}}/v1/file/read?id=xx' \ +--header 'Authorization: Bearer {{authorization}}' +``` + +{{< /markdownify >}} +{{< /tab >}} + +{{< tab tabName="响应示例" >}} +{{< markdownify >}} + +```json +{ + "code": 200, + "success": true, + "message": "", + "data": { + "url": "xxxx" + } +} +``` + +{{% alert icon=" " context="success" %}} +- url - 文件访问链接,拿到后会自动打开。 +{{% /alert %}} + +{{< /markdownify >}} +{{< /tab >}} +{{< /tabs >}} + + diff --git a/docSite/content/zh-cn/docs/guide/knowledge_base/externalFile.md b/docSite/content/zh-cn/docs/guide/knowledge_base/externalFile.md index f7b8dc9b32c8..413d2c351efc 100644 --- a/docSite/content/zh-cn/docs/guide/knowledge_base/externalFile.md +++ b/docSite/content/zh-cn/docs/guide/knowledge_base/externalFile.md @@ -23,4 +23,17 @@ weight: 408 - 文件阅读ID:通常情况下,文件访问URL是临时的。如果希望永久可以访问,你需要使用该文件阅读ID,并配合上“外部预览地址”,跳转至新的阅读地址进行原文件访问。 - 文件名:默认会自动解析文件访问URL上的文件名。如果你手动填写,将会以手动填写的值为准。 -[点击查看API导入文档](/docs/development/openapi/dataset/#创建一个外部文件库集合商业版) \ No newline at end of file +[点击查看API导入文档](/docs/development/openapi/dataset/#创建一个外部文件库集合商业版) + +## API 文件库替代方案 + +4.8.15 提供了新的知识库类型 - API 文件库,对外部文件知识库做了进一步的拓展 + +通过对接口进行简单的调整,就能使用 API 文件库代替外部文件知识库的功能 + +你可以直接将外部文件知识库中的外部预览地址,作为 API 文件库接口规范中获取文件阅读链接的接口返回 + +然后再以相同的 baseURL 实现获取文件列表和获取单个文件内容这两个接口 + +这样就能轻松地使用 API 文件库替代原有的外部文件知识库,更多详细的内容见 API 文件库的文档 + diff --git a/package.json b/package.json index f3152c06c987..d53a1c056821 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,8 @@ "postinstall": "sh ./scripts/postinstall.sh", "initIcon": "node ./scripts/icon/init.js", "previewIcon": "node ./scripts/icon/index.js", - "api:gen": "tsc ./scripts/openapi/index.ts && node ./scripts/openapi/index.js && npx @redocly/cli build-docs ./scripts/openapi/openapi.json -o ./projects/app/public/openapi/index.html" + "api:gen": "tsc ./scripts/openapi/index.ts && node ./scripts/openapi/index.js && npx @redocly/cli build-docs ./scripts/openapi/openapi.json -o ./projects/app/public/openapi/index.html", + "create:i18n": "node ./scripts/i18n/index.js" }, "devDependencies": { "@chakra-ui/cli": "^2.4.1", diff --git a/packages/global/common/error/code/dataset.ts b/packages/global/common/error/code/dataset.ts index adbdfadd818c..e5b8abd836d0 100644 --- a/packages/global/common/error/code/dataset.ts +++ b/packages/global/common/error/code/dataset.ts @@ -1,3 +1,4 @@ +import { i18nT } from '../../../../web/i18n/utils'; import { ErrType } from '../errorCode'; /* dataset: 501000 */ @@ -9,9 +10,19 @@ export enum DatasetErrEnum { unAuthDatasetData = 'unAuthDatasetData', unAuthDatasetFile = 'unAuthDatasetFile', unLinkCollection = 'unLinkCollection', - invalidVectorModelOrQAModel = 'invalidVectorModelOrQAModel' + invalidVectorModelOrQAModel = 'invalidVectorModelOrQAModel', + notSupportSync = 'notSupportSync', + sameApiCollection = 'sameApiCollection' } const datasetErr = [ + { + statusText: DatasetErrEnum.sameApiCollection, + message: i18nT('dataset:same_api_collection') + }, + { + statusText: DatasetErrEnum.notSupportSync, + message: i18nT('dataset:collection_not_support_sync') + }, { statusText: DatasetErrEnum.unExist, message: 'core.dataset.error.unExistDataset' diff --git a/packages/global/common/error/code/user.ts b/packages/global/common/error/code/user.ts index 88808c3979d0..e58136041b80 100644 --- a/packages/global/common/error/code/user.ts +++ b/packages/global/common/error/code/user.ts @@ -5,7 +5,8 @@ export enum UserErrEnum { unAuthUser = 'unAuthUser', unAuthRole = 'unAuthRole', binVisitor = 'binVisitor', - balanceNotEnough = 'balanceNotEnough' + balanceNotEnough = 'balanceNotEnough', + unAuthSso = 'unAuthSso' } const errList = [ { @@ -23,6 +24,10 @@ const errList = [ { statusText: UserErrEnum.balanceNotEnough, message: i18nT('common:code_error.user_error.balance_not_enough') + }, + { + statusText: UserErrEnum.unAuthSso, + message: i18nT('user:sso_auth_failed') } ]; export default errList.reduce((acc, cur, index) => { diff --git a/packages/global/common/string/markdown.ts b/packages/global/common/string/markdown.ts index 4c26c6ff09bd..410ca4c75600 100644 --- a/packages/global/common/string/markdown.ts +++ b/packages/global/common/string/markdown.ts @@ -95,20 +95,23 @@ export const markdownProcess = async ({ }; export const matchMdImgTextAndUpload = (text: string) => { - const base64Regex = /"(data:image\/[^;]+;base64[^"]+)"/g; + const base64Regex = /!\[([^\]]*)\]\((data:image\/[^;]+;base64[^)]+)\)/g; const imageList: ImageType[] = []; - const images = Array.from(text.match(base64Regex) || []); - for (const image of images) { + + text = text.replace(base64Regex, (match, altText, base64Url) => { const uuid = `IMAGE_${getNanoid(12)}_IMAGE`; - const mime = image.split(';')[0].split(':')[1]; - const base64 = image.split(',')[1]; - text = text.replace(image, uuid); + const mime = base64Url.split(';')[0].split(':')[1]; + const base64 = base64Url.split(',')[1]; + imageList.push({ uuid, base64, mime }); - } + + // 保持原有的 alt 文本,只替换 base64 部分 + return `![${altText}](${uuid})`; + }); return { text, diff --git a/packages/global/common/string/textSplitter.ts b/packages/global/common/string/textSplitter.ts index 940aece50568..c88023213065 100644 --- a/packages/global/common/string/textSplitter.ts +++ b/packages/global/common/string/textSplitter.ts @@ -182,7 +182,7 @@ const commonSplit = (props: SplitProps): SplitResponse => { title: matchTitle }; }) - .filter((item) => item.text?.trim()); + .filter((item) => !!item.title || !!item.text?.trim()); }; /* Gets the overlap at the end of a text as the beginning of the next block */ @@ -267,8 +267,10 @@ const commonSplit = (props: SplitProps): SplitResponse => { parentTitle: parentTitle + item.title }); - const lastChunk = innerChunks[innerChunks.length - 1]; - if (!lastChunk) continue; + if (innerChunks.length === 0) { + chunks.push(`${parentTitle}${item.title}`); + continue; + } chunks.push( ...innerChunks.map( diff --git a/packages/global/common/system/types/index.d.ts b/packages/global/common/system/types/index.d.ts index 77658db33faa..c3e4c80524a2 100644 --- a/packages/global/common/system/types/index.d.ts +++ b/packages/global/common/system/types/index.d.ts @@ -10,6 +10,14 @@ import type { } from '../../../core/ai/model.d'; import { SubTypeEnum } from '../../../support/wallet/sub/constants'; +export type NavbarItemType = { + id: string; + name: string; + avatar: string; + url: string; + isActive: boolean; +}; + /* fastgpt main */ export type FastGPTConfigFileType = { feConfigs: FastGPTFeConfigsType; @@ -38,7 +46,6 @@ export type FastGPTFeConfigsType = { concatMd?: string; docUrl?: string; - chatbotUrl?: string; openAPIDocUrl?: string; systemPluginCourseUrl?: string; appTemplateCourse?: string; @@ -74,6 +81,7 @@ export type FastGPTFeConfigsType = { uploadFileMaxAmount?: number; uploadFileMaxSize?: number; lafEnv?: string; + navbarItems?: NavbarItemType[]; }; export type SystemEnvType = { diff --git a/packages/global/core/app/constants.ts b/packages/global/core/app/constants.ts index d9bc42abb496..62d1f58740b4 100644 --- a/packages/global/core/app/constants.ts +++ b/packages/global/core/app/constants.ts @@ -48,3 +48,5 @@ export enum AppTemplateTypeEnum { roleplay = 'roleplay', officeServices = 'office-services' } + +export const defaultDatasetMaxTokens = 16000; diff --git a/packages/global/core/app/type.d.ts b/packages/global/core/app/type.d.ts index 916720591843..b735a1e30dc3 100644 --- a/packages/global/core/app/type.d.ts +++ b/packages/global/core/app/type.d.ts @@ -170,3 +170,9 @@ export type AppFileSelectConfigType = { canSelectImg: boolean; maxFiles: number; }; + +export type SystemPluginListItemType = { + _id: string; + name: string; + avatar: string; +}; diff --git a/packages/global/core/dataset/api.d.ts b/packages/global/core/dataset/api.d.ts index 00c20cb1893d..63e931a85646 100644 --- a/packages/global/core/dataset/api.d.ts +++ b/packages/global/core/dataset/api.d.ts @@ -16,6 +16,7 @@ export type DatasetUpdateBody = { websiteConfig?: DatasetSchemaType['websiteConfig']; externalReadUrl?: DatasetSchemaType['externalReadUrl']; defaultPermission?: DatasetSchemaType['defaultPermission']; + apiServer?: DatasetSchemaType['apiServer']; }; /* ================= collection ===================== */ @@ -34,15 +35,18 @@ export type CreateDatasetCollectionParams = DatasetCollectionChunkMetadataType & name: string; type: DatasetCollectionTypeEnum; - tags?: string[]; - fileId?: string; rawLink?: string; externalFileId?: string; - externalFileUrl?: string; + apiFileId?: string; + rawTextLength?: number; hashRawText?: string; + + tags?: string[]; + + createTime?: Date; }; export type ApiCreateDatasetCollectionParams = DatasetCollectionChunkMetadataType & { @@ -56,9 +60,17 @@ export type TextCreateDatasetCollectionParams = ApiCreateDatasetCollectionParams export type LinkCreateDatasetCollectionParams = ApiCreateDatasetCollectionParams & { link: string; }; +export type ApiDatasetCreateDatasetCollectionParams = ApiCreateDatasetCollectionParams & { + name: string; + apiFileId: string; +}; export type FileIdCreateDatasetCollectionParams = ApiCreateDatasetCollectionParams & { fileId: string; }; +export type reTrainingDatasetFileCollectionParams = DatasetCollectionChunkMetadataType & { + datasetId: string; + collectionId: string; +}; export type FileCreateDatasetCollectionParams = ApiCreateDatasetCollectionParams & { fileMetadata?: Record; collectionMetadata?: Record; diff --git a/packages/global/core/dataset/apiDataset.d.ts b/packages/global/core/dataset/apiDataset.d.ts new file mode 100644 index 000000000000..152fae12640a --- /dev/null +++ b/packages/global/core/dataset/apiDataset.d.ts @@ -0,0 +1,24 @@ +export type APIFileItem = { + id: string; + parentId: string | null; + name: string; + type: 'file' | 'folder'; + updateTime: Date; + createTime: Date; +}; + +export type APIFileServer = { + baseUrl: string; + authorization: string; +}; + +export type APIFileListResponse = APIFileItem[]; + +export type APIFileContentResponse = { + content?: string; + previewUrl?: string; +}; + +export type APIFileReadResponse = { + url: string; +}; diff --git a/packages/global/core/dataset/collection/utils.ts b/packages/global/core/dataset/collection/utils.ts index eb054aa65d9f..97aec9ac907b 100644 --- a/packages/global/core/dataset/collection/utils.ts +++ b/packages/global/core/dataset/collection/utils.ts @@ -9,7 +9,8 @@ export const getCollectionSourceData = ( collection?.fileId || collection?.rawLink || collection?.externalFileId || - collection?.externalFileUrl, + collection?.externalFileUrl || + collection?.apiFileId, sourceName: collection?.name || '' }; }; diff --git a/packages/global/core/dataset/constants.ts b/packages/global/core/dataset/constants.ts index 337d1c44699d..5a5f578b680d 100644 --- a/packages/global/core/dataset/constants.ts +++ b/packages/global/core/dataset/constants.ts @@ -1,9 +1,12 @@ +import { i18nT } from '../../../web/i18n/utils'; + /* ------------ dataset -------------- */ export enum DatasetTypeEnum { folder = 'folder', dataset = 'dataset', websiteDataset = 'websiteDataset', // depp link - externalFile = 'externalFile' + externalFile = 'externalFile', + apiDataset = 'apiDataset' } export const DatasetTypeMap = { [DatasetTypeEnum.folder]: { @@ -25,6 +28,11 @@ export const DatasetTypeMap = { icon: 'core/dataset/externalDatasetOutline', label: 'external_file', collectionLabel: 'common.File' + }, + [DatasetTypeEnum.apiDataset]: { + icon: 'core/dataset/externalDatasetOutline', + label: 'api_file', + collectionLabel: 'common.File' } }; @@ -34,10 +42,10 @@ export enum DatasetStatusEnum { } export const DatasetStatusMap = { [DatasetStatusEnum.active]: { - label: 'core.dataset.status.active' + label: i18nT('common:core.dataset.status.active') }, [DatasetStatusEnum.syncing]: { - label: 'core.dataset.status.syncing' + label: i18nT('common:core.dataset.status.syncing') } }; @@ -48,23 +56,27 @@ export enum DatasetCollectionTypeEnum { file = 'file', link = 'link', // one link - externalFile = 'externalFile' + externalFile = 'externalFile', + apiFile = 'apiFile' } export const DatasetCollectionTypeMap = { [DatasetCollectionTypeEnum.folder]: { - name: 'core.dataset.folder' + name: i18nT('common:core.dataset.folder') }, [DatasetCollectionTypeEnum.file]: { - name: 'core.dataset.file' + name: i18nT('common:core.dataset.file') }, [DatasetCollectionTypeEnum.externalFile]: { - name: 'core.dataset.externalFile' + name: i18nT('common:core.dataset.externalFile') }, [DatasetCollectionTypeEnum.link]: { - name: 'core.dataset.link' + name: i18nT('common:core.dataset.link') }, [DatasetCollectionTypeEnum.virtual]: { - name: 'core.dataset.Manual collection' + name: i18nT('common:core.dataset.Manual collection') + }, + [DatasetCollectionTypeEnum.apiFile]: { + name: i18nT('common:core.dataset.apiFile') } }; @@ -74,10 +86,10 @@ export enum DatasetCollectionSyncResultEnum { } export const DatasetCollectionSyncResultMap = { [DatasetCollectionSyncResultEnum.sameRaw]: { - label: 'core.dataset.collection.sync.result.sameRaw' + label: i18nT('common:core.dataset.collection.sync.result.sameRaw') }, [DatasetCollectionSyncResultEnum.success]: { - label: 'core.dataset.collection.sync.result.success' + label: i18nT('common:core.dataset.collection.sync.result.success') } }; @@ -89,7 +101,9 @@ export enum ImportDataSourceEnum { fileLink = 'fileLink', fileCustom = 'fileCustom', csvTable = 'csvTable', - externalFile = 'externalFile' + externalFile = 'externalFile', + apiDataset = 'apiDataset', + reTraining = 'reTraining' } export enum TrainingModeEnum { @@ -100,18 +114,18 @@ export enum TrainingModeEnum { export const TrainingTypeMap = { [TrainingModeEnum.chunk]: { - label: 'core.dataset.training.Chunk mode', - tooltip: 'core.dataset.import.Chunk Split Tip', + label: i18nT('common:core.dataset.training.Chunk mode'), + tooltip: i18nT('common:core.dataset.import.Chunk Split Tip'), openSource: true }, [TrainingModeEnum.auto]: { - label: 'core.dataset.training.Auto mode', - tooltip: 'core.dataset.training.Auto mode Tip', + label: i18nT('common:core.dataset.training.Auto mode'), + tooltip: i18nT('common:core.dataset.training.Auto mode Tip'), openSource: false }, [TrainingModeEnum.qa]: { - label: 'core.dataset.training.QA mode', - tooltip: 'core.dataset.import.QA Import Tip', + label: i18nT('common:core.dataset.training.QA mode'), + tooltip: i18nT('common:core.dataset.import.QA Import Tip'), openSource: true } }; @@ -126,20 +140,20 @@ export enum DatasetSearchModeEnum { export const DatasetSearchModeMap = { [DatasetSearchModeEnum.embedding]: { icon: 'core/dataset/modeEmbedding', - title: 'core.dataset.search.mode.embedding', - desc: 'core.dataset.search.mode.embedding desc', + title: i18nT('common:core.dataset.search.mode.embedding'), + desc: i18nT('common:core.dataset.search.mode.embedding desc'), value: DatasetSearchModeEnum.embedding }, [DatasetSearchModeEnum.fullTextRecall]: { icon: 'core/dataset/fullTextRecall', - title: 'core.dataset.search.mode.fullTextRecall', - desc: 'core.dataset.search.mode.fullTextRecall desc', + title: i18nT('common:core.dataset.search.mode.fullTextRecall'), + desc: i18nT('common:core.dataset.search.mode.fullTextRecall desc'), value: DatasetSearchModeEnum.fullTextRecall }, [DatasetSearchModeEnum.mixedRecall]: { icon: 'core/dataset/mixedRecall', - title: 'core.dataset.search.mode.mixedRecall', - desc: 'core.dataset.search.mode.mixedRecall desc', + title: i18nT('common:core.dataset.search.mode.mixedRecall'), + desc: i18nT('common:core.dataset.search.mode.mixedRecall desc'), value: DatasetSearchModeEnum.mixedRecall } }; @@ -152,23 +166,23 @@ export enum SearchScoreTypeEnum { } export const SearchScoreTypeMap = { [SearchScoreTypeEnum.embedding]: { - label: 'core.dataset.search.score.embedding', - desc: 'core.dataset.search.score.embedding desc', + label: i18nT('common:core.dataset.search.score.embedding'), + desc: i18nT('common:core.dataset.search.score.embedding desc'), showScore: true }, [SearchScoreTypeEnum.fullText]: { - label: 'core.dataset.search.score.fullText', - desc: 'core.dataset.search.score.fullText desc', + label: i18nT('common:core.dataset.search.score.fullText'), + desc: i18nT('common:core.dataset.search.score.fullText desc'), showScore: false }, [SearchScoreTypeEnum.reRank]: { - label: 'core.dataset.search.score.reRank', - desc: 'core.dataset.search.score.reRank desc', + label: i18nT('common:core.dataset.search.score.reRank'), + desc: i18nT('common:core.dataset.search.score.reRank desc'), showScore: true }, [SearchScoreTypeEnum.rrf]: { - label: 'core.dataset.search.score.rrf', - desc: 'core.dataset.search.score.rrf desc', + label: i18nT('common:core.dataset.search.score.rrf'), + desc: i18nT('common:core.dataset.search.score.rrf desc'), showScore: false } }; @@ -180,5 +194,7 @@ export const LinkCollectionIcon = 'common/linkBlue'; export enum DatasetSourceReadTypeEnum { fileLocal = 'fileLocal', link = 'link', - externalFile = 'externalFile' + externalFile = 'externalFile', + apiFile = 'apiFile', + reTraining = 'reTraining' } diff --git a/packages/global/core/dataset/read.ts b/packages/global/core/dataset/read.ts deleted file mode 100644 index 0a4b3e3954f8..000000000000 --- a/packages/global/core/dataset/read.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { DatasetSourceReadTypeEnum, ImportDataSourceEnum } from './constants'; - -export const importType2ReadType = (type: ImportDataSourceEnum) => { - if (type === ImportDataSourceEnum.csvTable || type === ImportDataSourceEnum.fileLocal) { - return DatasetSourceReadTypeEnum.fileLocal; - } - if (type === ImportDataSourceEnum.fileLink) { - return DatasetSourceReadTypeEnum.link; - } - if (type === ImportDataSourceEnum.externalFile) { - return DatasetSourceReadTypeEnum.externalFile; - } - return DatasetSourceReadTypeEnum.link; -}; diff --git a/packages/global/core/dataset/type.d.ts b/packages/global/core/dataset/type.d.ts index f215d9ef55d6..960981a2b8c5 100644 --- a/packages/global/core/dataset/type.d.ts +++ b/packages/global/core/dataset/type.d.ts @@ -10,6 +10,7 @@ import { } from './constants'; import { DatasetPermission } from '../../support/permission/dataset/controller'; import { Permission } from '../../support/permission/controller'; +import { APIFileServer } from './apiDataset'; export type DatasetSchemaType = { _id: string; @@ -30,10 +31,11 @@ export type DatasetSchemaType = { url: string; selector: string; }; - externalReadUrl?: string; inheritPermission: boolean; + apiServer?: APIFileServer; // abandon + externalReadUrl?: string; defaultPermission?: number; }; @@ -64,6 +66,7 @@ export type DatasetCollectionSchemaType = { rawTextLength?: number; hashRawText?: string; externalFileUrl?: string; // external import url + apiFileId?: string; // api file id metadata?: { webPageSelector?: string; relatedImgId?: string; // The id of the associated image collections diff --git a/packages/global/core/plugin/type.d.ts b/packages/global/core/plugin/type.d.ts index 38215640e4e6..925e4549bce1 100644 --- a/packages/global/core/plugin/type.d.ts +++ b/packages/global/core/plugin/type.d.ts @@ -39,6 +39,7 @@ export type PluginTemplateType = PluginRuntimeType & { }; export type PluginRuntimeType = { + id: string; teamId?: string; name: string; avatar: string; @@ -46,4 +47,6 @@ export type PluginRuntimeType = { isTool?: boolean; nodes: StoreNodeItemType[]; edges: StoreEdgeItemType[]; + currentCost?: number; + hasTokenFee?: boolean; }; diff --git a/packages/global/core/workflow/runtime/type.d.ts b/packages/global/core/workflow/runtime/type.d.ts index ae0d29bda16d..de469cfe14e8 100644 --- a/packages/global/core/workflow/runtime/type.d.ts +++ b/packages/global/core/workflow/runtime/type.d.ts @@ -82,17 +82,6 @@ export type RuntimeNodeItemType = { version: string; }; -export type PluginRuntimeType = { - id: string; - teamId?: string; - name: string; - avatar: string; - showStatus?: boolean; - currentCost?: number; - nodes: StoreNodeItemType[]; - edges: StoreEdgeItemType[]; -}; - export type RuntimeEdgeItemType = StoreEdgeItemType & { status: 'waiting' | 'active' | 'skipped'; }; diff --git a/packages/global/core/workflow/runtime/utils.ts b/packages/global/core/workflow/runtime/utils.ts index 8ce3c8bb9eba..655cd6db37e5 100644 --- a/packages/global/core/workflow/runtime/utils.ts +++ b/packages/global/core/workflow/runtime/utils.ts @@ -283,68 +283,47 @@ export const getReferenceVariableValue = ({ export function replaceEditorVariable({ text, nodes, - variables, - runningNode + variables }: { text: any; nodes: RuntimeNodeItemType[]; variables: Record; // global variables - runningNode: RuntimeNodeItemType; }) { if (typeof text !== 'string') return text; - const globalVariables = Object.keys(variables).map((key) => { - return { - nodeId: VARIABLE_NODE_ID, - id: key, - value: variables[key] - }; - }); + const variablePattern = /\{\{\$([^.]+)\.([^$]+)\$\}\}/g; + const matches = [...text.matchAll(variablePattern)]; + if (matches.length === 0) return text; - // Upstream node outputs - const nodeVariables = nodes - .map((node) => { - return node.outputs.map((output) => { - return { - nodeId: node.nodeId, - id: output.id, - value: output.value - }; - }); - }) - .flat(); + matches.forEach((match) => { + const nodeId = match[1]; + const id = match[2]; - // Get runningNode inputs(Will be replaced with reference) - const customInputs = runningNode.inputs.flatMap((item) => { - return [ - { - id: item.key, - value: getReferenceVariableValue({ - value: item.value, - nodes, - variables - }), - nodeId: runningNode.nodeId + const variableVal = (() => { + if (nodeId === VARIABLE_NODE_ID) { + return variables[id]; } - ]; - }); + // Find upstream node input/output + const node = nodes.find((node) => node.nodeId === nodeId); + if (!node) return; - const allVariables = [...globalVariables, ...nodeVariables, ...customInputs]; + const output = node.outputs.find((output) => output.id === id); + if (output) return output.value; - // Replace {{$xxx.xxx$}} to value - for (const key in allVariables) { - const variable = allVariables[key]; - const val = variable.value; - const formatVal = (() => { - if (val === undefined) return ''; - if (val === null) return 'null'; + const input = node.inputs.find((input) => input.key === id); + if (input) return getReferenceVariableValue({ value: input.value, nodes, variables }); + })(); - return typeof val === 'object' ? JSON.stringify(val) : String(val); + const formatVal = (() => { + if (variableVal === undefined) return 'undefined'; + if (variableVal === null) return 'null'; + return typeof variableVal === 'object' ? JSON.stringify(variableVal) : String(variableVal); })(); - const regex = new RegExp(`\\{\\{\\$(${variable.nodeId}\\.${variable.id})\\$\\}\\}`, 'g'); + const regex = new RegExp(`\\{\\{\\$(${nodeId}\\.${id})\\$\\}\\}`, 'g'); text = text.replace(regex, formatVal); - } + }); + return text || ''; } diff --git a/packages/global/core/workflow/type/index.d.ts b/packages/global/core/workflow/type/index.d.ts index 99b1f67a81a1..5e246cb9c042 100644 --- a/packages/global/core/workflow/type/index.d.ts +++ b/packages/global/core/workflow/type/index.d.ts @@ -63,15 +63,20 @@ export type TemplateMarketListItemType = { // system plugin export type SystemPluginTemplateItemType = WorkflowTemplateType & { customWorkflow?: string; + associatedPluginId?: string; + userGuide?: string; - templateType: FlowNodeTemplateTypeEnum; + templateType: string; isTool?: boolean; // commercial plugin config originCost: number; // n points/one time currentCost: number; + hasTokenFee: boolean; + pluginOrder: number; isActive?: boolean; + isOfficial?: boolean; inputConfig?: { // Render config input form. Find the corresponding node and replace the variable directly key: string; diff --git a/packages/global/core/workflow/type/node.d.ts b/packages/global/core/workflow/type/node.d.ts index 227e9f4f26cc..2359ac0ee70a 100644 --- a/packages/global/core/workflow/type/node.d.ts +++ b/packages/global/core/workflow/type/node.d.ts @@ -54,7 +54,7 @@ type HandleType = { // system template export type FlowNodeTemplateType = FlowNodeCommonType & { id: string; // node id, unique - templateType: FlowNodeTemplateTypeEnum; + templateType: string; // show handle sourceHandle?: HandleType; @@ -76,7 +76,7 @@ export type NodeTemplateListItemType = { flowNodeType: FlowNodeTypeEnum; // render node card parentId?: ParentIdType; isFolder?: boolean; - templateType: FlowNodeTemplateTypeEnum; + templateType: string; avatar?: string; name: string; intro?: string; // template list intro @@ -85,10 +85,12 @@ export type NodeTemplateListItemType = { author?: string; unique?: boolean; // 唯一的 currentCost?: number; // 当前积分消耗 + hasTokenFee?: boolean; // 是否配置积分 + instructions?: string; // 使用说明 }; export type NodeTemplateListType = { - type: FlowNodeTemplateTypeEnum; + type: string; label: string; list: NodeTemplateListItemType[]; }[]; diff --git a/packages/global/support/user/constant.ts b/packages/global/support/user/constant.ts index 4f8e7926b395..0fae675cba59 100644 --- a/packages/global/support/user/constant.ts +++ b/packages/global/support/user/constant.ts @@ -15,5 +15,6 @@ export enum OAuthEnum { github = 'github', google = 'google', wechat = 'wechat', - microsoft = 'microsoft' + microsoft = 'microsoft', + sso = 'sso' } diff --git a/packages/global/support/wallet/sub/api.d.ts b/packages/global/support/wallet/sub/api.d.ts index 2db4b6d0d0bd..dfa71fbc5629 100644 --- a/packages/global/support/wallet/sub/api.d.ts +++ b/packages/global/support/wallet/sub/api.d.ts @@ -12,6 +12,7 @@ export type StandardSubPlanUpdateResponse = { payPrice?: number; planPrice: number; planPointPrice: number; + name?: string; currentMode: `${SubModeEnum}`; nextMode: `${SubModeEnum}`; diff --git a/packages/global/support/wallet/sub/type.d.ts b/packages/global/support/wallet/sub/type.d.ts index 2a2af2ed8f46..9054487e0099 100644 --- a/packages/global/support/wallet/sub/type.d.ts +++ b/packages/global/support/wallet/sub/type.d.ts @@ -2,6 +2,7 @@ import { StandardSubLevelEnum, SubModeEnum, SubTypeEnum } from './constants'; // Content of plan export type TeamStandardSubPlanItemType = { + name?: string; price: number; // read price / month pointPrice: number; // read price/ one thousand totalPoints: number; // n @@ -24,6 +25,7 @@ export type StandSubPlanLevelMapType = Record< export type SubPlanType = { [SubTypeEnum.standard]: StandSubPlanLevelMapType; + planDescriptionUrl?: string; [SubTypeEnum.extraDatasetSize]: { price: number; }; diff --git a/packages/plugins/register.ts b/packages/plugins/register.ts index 826a417218e8..3940ff1a1f71 100644 --- a/packages/plugins/register.ts +++ b/packages/plugins/register.ts @@ -40,7 +40,8 @@ export const getCommunityPlugins = () => { id: `${PluginSourceEnum.community}-${name}`, isFolder, parentId, - isActive: true + isActive: true, + isOfficial: true }; }); }; diff --git a/packages/plugins/type.d.ts b/packages/plugins/type.d.ts index 201c6fdd0a75..c8257656d806 100644 --- a/packages/plugins/type.d.ts +++ b/packages/plugins/type.d.ts @@ -1,6 +1,7 @@ import { PluginTemplateType } from '@fastgpt/global/core/plugin/type.d'; import { systemPluginResponseEnum } from '@fastgpt/global/core/workflow/runtime/constants'; import { SystemPluginTemplateItemType } from '@fastgpt/global/core/workflow/type'; +import { PluginGroupSchemaType } from '@fastgpt/service/core/app/plugin/type'; export type SystemPluginResponseType = Promise>; export type SystemPluginSpecialResponse = { @@ -10,6 +11,7 @@ export type SystemPluginSpecialResponse = { }; declare global { + var pluginGroups: PluginGroupSchemaType[]; var systemPlugins: SystemPluginTemplateItemType[]; var systemPluginCb: Record SystemPluginResponseType>; } diff --git a/packages/service/common/api/serverRequest.ts b/packages/service/common/api/serverRequest.ts index b812ef0edf51..ce2bd6d8c9e3 100644 --- a/packages/service/common/api/serverRequest.ts +++ b/packages/service/common/api/serverRequest.ts @@ -83,8 +83,8 @@ export function request(url: string, data: any, config: ConfigType, method: Meth baseURL: serverRequestBaseUrl, url, method, - data: ['POST', 'PUT'].includes(method) ? data : null, - params: !['POST', 'PUT'].includes(method) ? data : null, + data: ['POST', 'PUT'].includes(method) ? data : undefined, + params: !['POST', 'PUT'].includes(method) ? data : undefined, ...config // custom config }) .then((res) => checkRes(res.data)) diff --git a/packages/service/common/file/read/utils.ts b/packages/service/common/file/read/utils.ts index ee898c1a6403..0c3cb1ba9f20 100644 --- a/packages/service/common/file/read/utils.ts +++ b/packages/service/common/file/read/utils.ts @@ -66,6 +66,7 @@ export const readRawContentByFileBuffer = async ({ return; const start = Date.now(); + addLog.info('Parsing files from an external service'); const data = new FormData(); data.append('file', buffer, { @@ -88,7 +89,7 @@ export const readRawContentByFileBuffer = async ({ } }); - addLog.info(`Use custom read file service, time: ${Date.now() - start}ms`); + addLog.info(`Custom file parsing is complete, time: ${Date.now() - start}ms`); const rawText = response.data.markdown; const { text, imageList } = matchMdImgTextAndUpload(rawText); diff --git a/packages/service/common/response/index.ts b/packages/service/common/response/index.ts index 8a804e7fa859..a6586d0775a8 100644 --- a/packages/service/common/response/index.ts +++ b/packages/service/common/response/index.ts @@ -31,6 +31,8 @@ export const jsonRes = ( clearCookie(res); } + addLog.error(`Api response error: ${url}`, ERROR_RESPONSE[errResponseKey]); + return res.json(ERROR_RESPONSE[errResponseKey]); } diff --git a/packages/service/core/ai/functions/queryExtension.ts b/packages/service/core/ai/functions/queryExtension.ts index 519e03f1bde6..37ae19a621c8 100644 --- a/packages/service/core/ai/functions/queryExtension.ts +++ b/packages/service/core/ai/functions/queryExtension.ts @@ -5,6 +5,7 @@ import { countGptMessagesTokens } from '../../../common/string/tiktoken/index'; import { chatValue2RuntimePrompt } from '@fastgpt/global/core/chat/adapt'; import { getLLMModel } from '../model'; import { llmCompletionsBodyFormat } from '../utils'; +import { addLog } from '../../../common/system/log'; /* query extension - 问题扩展 @@ -183,7 +184,7 @@ A: ${chatBg} tokens: await countGptMessagesTokens(messages) }; } catch (error) { - console.log(error); + addLog.error(`Query extension error`, error); return { rawQuery: query, extensionQueries: [], diff --git a/packages/service/core/ai/rerank/index.ts b/packages/service/core/ai/rerank/index.ts index 0027fb82f3e4..b7eb54588f7f 100644 --- a/packages/service/core/ai/rerank/index.ts +++ b/packages/service/core/ai/rerank/index.ts @@ -51,7 +51,6 @@ export function reRankRecall({ })); }) .catch((err) => { - console.log(err); addLog.error('rerank error', err); return []; diff --git a/packages/service/core/app/controller.ts b/packages/service/core/app/controller.ts index 75e26625593e..02cdd08f4880 100644 --- a/packages/service/core/app/controller.ts +++ b/packages/service/core/app/controller.ts @@ -5,39 +5,44 @@ import { getLLMModel } from '../ai/model'; import { MongoApp } from './schema'; export const beforeUpdateAppFormat = ({ - nodes + nodes, + isPlugin }: { nodes: T; + isPlugin: boolean; }) => { if (nodes) { - let maxTokens = 3000; + // Check dataset maxTokens + if (isPlugin) { + let maxTokens = 16000; - nodes.forEach((item) => { - if ( - item.flowNodeType === FlowNodeTypeEnum.chatNode || - item.flowNodeType === FlowNodeTypeEnum.tools - ) { - const model = - item.inputs.find((item) => item.key === NodeInputKeyEnum.aiModel)?.value || ''; - const chatModel = getLLMModel(model); - const quoteMaxToken = chatModel.quoteMaxToken || 3000; + nodes.forEach((item) => { + if ( + item.flowNodeType === FlowNodeTypeEnum.chatNode || + item.flowNodeType === FlowNodeTypeEnum.tools + ) { + const model = + item.inputs.find((item) => item.key === NodeInputKeyEnum.aiModel)?.value || ''; + const chatModel = getLLMModel(model); + const quoteMaxToken = chatModel.quoteMaxToken || 16000; - maxTokens = Math.max(maxTokens, quoteMaxToken); - } - }); + maxTokens = Math.max(maxTokens, quoteMaxToken); + } + }); - nodes.forEach((item) => { - if (item.flowNodeType === FlowNodeTypeEnum.datasetSearchNode) { - item.inputs.forEach((input) => { - if (input.key === NodeInputKeyEnum.datasetMaxTokens) { - const val = input.value as number; - if (val > maxTokens) { - input.value = maxTokens; + nodes.forEach((item) => { + if (item.flowNodeType === FlowNodeTypeEnum.datasetSearchNode) { + item.inputs.forEach((input) => { + if (input.key === NodeInputKeyEnum.datasetMaxTokens) { + const val = input.value as number; + if (val > maxTokens) { + input.value = maxTokens; + } } - } - }); - } - }); + }); + } + }); + } } return { diff --git a/packages/service/core/app/plugin/controller.ts b/packages/service/core/app/plugin/controller.ts index e94ce67a0590..00c2eea94b12 100644 --- a/packages/service/core/app/plugin/controller.ts +++ b/packages/service/core/app/plugin/controller.ts @@ -2,7 +2,6 @@ import { FlowNodeTemplateType } from '@fastgpt/global/core/workflow/type/node.d' import { FlowNodeTypeEnum, defaultNodeVersion } from '@fastgpt/global/core/workflow/node/constant'; import { appData2FlowNodeIO, pluginData2FlowNodeIO } from '@fastgpt/global/core/workflow/utils'; import { PluginSourceEnum } from '@fastgpt/global/core/plugin/constants'; -import type { PluginRuntimeType } from '@fastgpt/global/core/workflow/runtime/type'; import { FlowNodeTemplateTypeEnum } from '@fastgpt/global/core/workflow/constants'; import { getHandleConfig } from '@fastgpt/global/core/workflow/template/utils'; import { getNanoid } from '@fastgpt/global/common/string/tools'; @@ -11,6 +10,9 @@ import { MongoApp } from '../schema'; import { SystemPluginTemplateItemType } from '@fastgpt/global/core/workflow/type'; import { getSystemPluginTemplates } from '../../../../plugins/register'; import { getAppLatestVersion, getAppVersionById } from '../version/controller'; +import { PluginRuntimeType } from '@fastgpt/global/core/plugin/type'; +import { MongoSystemPlugin } from './systemPluginSchema'; +import { PluginErrEnum } from '@fastgpt/global/common/error/code/plugin'; /* plugin id rule: @@ -37,15 +39,45 @@ export async function splitCombinePluginId(id: string) { type ChildAppType = SystemPluginTemplateItemType & { teamId?: string }; const getSystemPluginTemplateById = async ( - pluginId: string + pluginId: string, + versionId?: string ): Promise => { const item = getSystemPluginTemplates().find((plugin) => plugin.id === pluginId); - if (!item) return Promise.reject('plugin not found'); - - return cloneDeep(item); + if (!item) return Promise.reject(PluginErrEnum.unAuth); + + const plugin = cloneDeep(item); + + if (plugin.associatedPluginId) { + // The verification plugin is set as a system plugin + const systemPlugin = await MongoSystemPlugin.findOne( + { pluginId: plugin.id, 'customConfig.associatedPluginId': plugin.associatedPluginId }, + 'associatedPluginId' + ).lean(); + if (!systemPlugin) return Promise.reject(PluginErrEnum.unAuth); + + const app = await MongoApp.findById(plugin.associatedPluginId).lean(); + if (!app) return Promise.reject(PluginErrEnum.unAuth); + + const version = versionId + ? await getAppVersionById({ + appId: plugin.associatedPluginId, + versionId, + app + }) + : await getAppLatestVersion(plugin.associatedPluginId, app); + if (!version.versionId) return Promise.reject('App version not found'); + + plugin.workflow = { + nodes: version.nodes, + edges: version.edges, + chatConfig: version.chatConfig + }; + plugin.version = versionId || String(version.versionId); + } + return plugin; }; -/* format plugin modules to plugin preview module */ +/* Format plugin to workflow preview node data */ export async function getChildAppPreviewNode({ id }: { @@ -77,7 +109,9 @@ export async function getChildAppPreviewNode({ templateType: FlowNodeTemplateTypeEnum.teamApp, version: version.versionId, originCost: 0, - currentCost: 0 + currentCost: 0, + hasTokenFee: false, + pluginOrder: 0 }; } else { return getSystemPluginTemplateById(pluginId); @@ -147,10 +181,12 @@ export async function getChildAppRuntimeById( // 用不到 version: item?.pluginData?.nodeVersion || defaultNodeVersion, originCost: 0, - currentCost: 0 + currentCost: 0, + hasTokenFee: false, + pluginOrder: 0 }; } else { - return getSystemPluginTemplateById(pluginId); + return getSystemPluginTemplateById(pluginId, versionId); } })(); @@ -162,6 +198,7 @@ export async function getChildAppRuntimeById( showStatus: app.showStatus, currentCost: app.currentCost, nodes: app.workflow.nodes, - edges: app.workflow.edges + edges: app.workflow.edges, + hasTokenFee: app.hasTokenFee }; } diff --git a/packages/service/core/app/plugin/pluginGroupSchema.ts b/packages/service/core/app/plugin/pluginGroupSchema.ts new file mode 100644 index 000000000000..cecc4c3836ce --- /dev/null +++ b/packages/service/core/app/plugin/pluginGroupSchema.ts @@ -0,0 +1,35 @@ +import { connectionMongo, getMongoModel } from '../../../common/mongo/index'; +import { PluginGroupSchemaType, TGroupType } from './type'; +const { Schema } = connectionMongo; + +export const collectionName = 'app_plugin_groups'; + +const PluginGroupSchema = new Schema({ + groupId: { + type: String, + required: true + }, + groupAvatar: { + type: String, + default: '' + }, + groupName: { + type: String, + required: true + }, + groupTypes: { + type: Array, + default: [] + }, + groupOrder: { + type: Number, + default: 0 + } +}); + +PluginGroupSchema.index({ groupId: 1 }, { unique: true }); + +export const MongoPluginGroups = getMongoModel( + collectionName, + PluginGroupSchema +); diff --git a/packages/service/core/app/plugin/systemPluginSchema.ts b/packages/service/core/app/plugin/systemPluginSchema.ts index 56a165c414c7..72ba7e6589f9 100644 --- a/packages/service/core/app/plugin/systemPluginSchema.ts +++ b/packages/service/core/app/plugin/systemPluginSchema.ts @@ -25,12 +25,20 @@ const SystemPluginSchema = new Schema({ type: Number, default: 0 }, + hasTokenFee: { + type: Boolean, + default: false + }, + pluginOrder: { + type: Number, + default: 0 + }, customConfig: Object }); SystemPluginSchema.index({ pluginId: 1 }); -export const MongoSystemPluginSchema = getMongoModel( +export const MongoSystemPlugin = getMongoModel( collectionName, SystemPluginSchema ); diff --git a/packages/service/core/app/plugin/type.d.ts b/packages/service/core/app/plugin/type.d.ts index a72f3d29756e..90889bb9e758 100644 --- a/packages/service/core/app/plugin/type.d.ts +++ b/packages/service/core/app/plugin/type.d.ts @@ -1,3 +1,4 @@ +import { SystemPluginListItemType } from '@fastgpt/global/core/app/type'; import { FlowNodeTemplateTypeEnum } from '@fastgpt/global/core/workflow/constants'; import { SystemPluginTemplateItemType, @@ -9,7 +10,9 @@ export type SystemPluginConfigSchemaType = { originCost: number; // n points/one time currentCost: number; + hasTokenFee: boolean; isActive: boolean; + pluginOrder: number; inputConfig: SystemPluginTemplateItemType['inputConfig']; customConfig?: { @@ -19,6 +22,21 @@ export type SystemPluginConfigSchemaType = { version: string; weight?: number; workflow: WorkflowTemplateBasicType; - templateType: FlowNodeTemplateTypeEnum; + templateType: string; + associatedPluginId: string; + userGuide: string; }; }; + +export type TGroupType = { + typeName: string; + typeId: string; +}; + +export type PluginGroupSchemaType = { + groupId: string; + groupAvatar: string; + groupName: string; + groupTypes: TGroupType[]; + groupOrder: number; +}; diff --git a/packages/service/core/app/plugin/utils.ts b/packages/service/core/app/plugin/utils.ts index 4ea86b06eff3..3ec4ecc5620b 100644 --- a/packages/service/core/app/plugin/utils.ts +++ b/packages/service/core/app/plugin/utils.ts @@ -1,11 +1,11 @@ -import { PluginRuntimeType } from '@fastgpt/global/core/workflow/runtime/type'; import { ChatNodeUsageType } from '@fastgpt/global/support/wallet/bill/type'; -import { splitCombinePluginId } from './controller'; -import { PluginSourceEnum } from '@fastgpt/global/core/plugin/constants'; +import { PluginRuntimeType } from '@fastgpt/global/core/plugin/type'; /* - 1. Commercial plugin: n points per times - 2. Other plugin: sum of children points + Plugin points calculation: + 1. Return 0 if error + 2. Add configured points if commercial plugin + 3. Add sum of child nodes points */ export const computedPluginUsage = async ({ plugin, @@ -16,13 +16,13 @@ export const computedPluginUsage = async ({ childrenUsage: ChatNodeUsageType[]; error?: boolean; }) => { - const { source } = await splitCombinePluginId(plugin.id); - - // Commercial plugin: n points per times - if (source === PluginSourceEnum.commercial) { - if (error) return 0; - return plugin.currentCost ?? 0; + if (error) { + return 0; } - return childrenUsage.reduce((sum, item) => sum + (item.totalPoints || 0), 0); + const childrenIUsages = childrenUsage.reduce((sum, item) => sum + (item.totalPoints || 0), 0); + + const pluginCurrentCose = plugin.currentCost ?? 0; + + return plugin.hasTokenFee ? pluginCurrentCose + childrenIUsages : pluginCurrentCose; }; diff --git a/packages/service/core/dataset/apiDataset/api.ts b/packages/service/core/dataset/apiDataset/api.ts new file mode 100644 index 000000000000..19818b32fc71 --- /dev/null +++ b/packages/service/core/dataset/apiDataset/api.ts @@ -0,0 +1,143 @@ +import type { + APIFileContentResponse, + APIFileListResponse, + APIFileReadResponse, + APIFileServer +} from '@fastgpt/global/core/dataset/apiDataset'; +import axios, { Method } from 'axios'; +import { addLog } from '../../../common/system/log'; +import { readFileRawTextByUrl } from '../read'; +import { ParentIdType } from '@fastgpt/global/common/parentFolder/type'; + +type ResponseDataType = { + success: boolean; + message: string; + data: any; +}; + +export const useApiDatasetRequest = ({ apiServer }: { apiServer: APIFileServer }) => { + const instance = axios.create({ + baseURL: apiServer.baseUrl, + timeout: 60000, // 超时时间 + headers: { + 'content-type': 'application/json', + Authorization: `Bearer ${apiServer.authorization}` + } + }); + + /** + * 响应数据检查 + */ + const checkRes = (data: ResponseDataType) => { + if (data === undefined) { + addLog.info('Api dataset data is empty'); + return Promise.reject('服务器异常'); + } else if (!data.success) { + return Promise.reject(data); + } + return data.data; + }; + const responseError = (err: any) => { + console.log('error->', '请求错误', err); + + if (!err) { + return Promise.reject({ message: '未知错误' }); + } + if (typeof err === 'string') { + return Promise.reject({ message: err }); + } + if (typeof err.message === 'string') { + return Promise.reject({ message: err.message }); + } + if (typeof err.data === 'string') { + return Promise.reject({ message: err.data }); + } + if (err?.response?.data) { + return Promise.reject(err?.response?.data); + } + return Promise.reject(err); + }; + + const request = (url: string, data: any, method: Method): Promise => { + /* 去空 */ + for (const key in data) { + if (data[key] === undefined) { + delete data[key]; + } + } + + return instance + .request({ + url, + method, + data: ['POST', 'PUT'].includes(method) ? data : undefined, + params: !['POST', 'PUT'].includes(method) ? data : undefined + }) + .then((res) => checkRes(res.data)) + .catch((err) => responseError(err)); + }; + + const listFiles = async ({ + searchKey, + parentId + }: { + searchKey?: string; + parentId?: ParentIdType; + }) => { + const files = await request( + `/v1/file/list`, + { + searchKey, + parentId + }, + 'POST' + ); + + if (!Array.isArray(files)) { + return Promise.reject('Invalid file list format'); + } + if (files.some((file) => !file.id || !file.name || typeof file.type === 'undefined')) { + return Promise.reject('Invalid file data format'); + } + return files; + }; + + const getFileContent = async ({ teamId, apiFileId }: { teamId: string; apiFileId: string }) => { + const data = await request( + `/v1/file/content`, + { id: apiFileId }, + 'GET' + ); + const content = data.content; + const previewUrl = data.previewUrl; + + if (content) { + return content; + } + if (previewUrl) { + const rawText = await readFileRawTextByUrl({ + teamId, + url: previewUrl, + relatedId: apiFileId + }); + return rawText; + } + return Promise.reject('Invalid content type: content or previewUrl is required'); + }; + + const getFilePreviewUrl = async ({ apiFileId }: { apiFileId: string }) => { + const { url } = await request(`/v1/file/read`, { id: apiFileId }, 'GET'); + + if (!url || typeof url !== 'string') { + return Promise.reject('Invalid response url'); + } + + return url; + }; + + return { + getFileContent, + listFiles, + getFilePreviewUrl + }; +}; diff --git a/packages/service/core/dataset/collection/controller.ts b/packages/service/core/dataset/collection/controller.ts index efd667464188..df040b87b046 100644 --- a/packages/service/core/dataset/collection/controller.ts +++ b/packages/service/core/dataset/collection/controller.ts @@ -3,7 +3,8 @@ import type { CreateDatasetCollectionParams } from '@fastgpt/global/core/dataset import { MongoDatasetCollection } from './schema'; import { CollectionWithDatasetType, - DatasetCollectionSchemaType + DatasetCollectionSchemaType, + DatasetSchemaType } from '@fastgpt/global/core/dataset/type'; import { MongoDatasetTraining } from '../training/schema'; import { MongoDatasetData } from '../data/schema'; @@ -13,7 +14,132 @@ import { delFileByFileIdList } from '../../../common/file/gridfs/controller'; import { BucketNameEnum } from '@fastgpt/global/common/file/constants'; import { ClientSession } from '../../../common/mongo'; import { createOrGetCollectionTags } from './utils'; +import { rawText2Chunks } from '../read'; +import { checkDatasetLimit } from '../../../support/permission/teamLimit'; +import { predictDataLimitLength } from '../../../../global/core/dataset/utils'; +import { mongoSessionRun } from '../../../common/mongo/sessionRun'; +import { createTrainingUsage } from '../../../support/wallet/usage/controller'; +import { UsageSourceEnum } from '@fastgpt/global/support/wallet/usage/constants'; +import { getLLMModel, getVectorModel } from '../../ai/model'; +import { pushDataListToTrainingQueue } from '../training/controller'; +import { MongoImage } from '../../../common/file/image/schema'; +import { hashStr } from '@fastgpt/global/common/string/tools'; +export const createCollectionAndInsertData = async ({ + dataset, + rawText, + relatedId, + createCollectionParams, + isQAImport = false, + session +}: { + dataset: DatasetSchemaType; + rawText: string; + relatedId?: string; + createCollectionParams: CreateOneCollectionParams; + + isQAImport?: boolean; + session?: ClientSession; +}) => { + const teamId = createCollectionParams.teamId; + const tmbId = createCollectionParams.tmbId; + // Chunk split params + const trainingType = createCollectionParams.trainingType || TrainingModeEnum.chunk; + const chunkSize = createCollectionParams.chunkSize; + const chunkSplitter = createCollectionParams.chunkSplitter; + const qaPrompt = createCollectionParams.qaPrompt; + const usageName = createCollectionParams.name; + + // 1. split chunks + const chunks = rawText2Chunks({ + rawText, + chunkLen: chunkSize, + overlapRatio: trainingType === TrainingModeEnum.chunk ? 0.2 : 0, + customReg: chunkSplitter ? [chunkSplitter] : [], + isQAImport + }); + + // 2. auth limit + await checkDatasetLimit({ + teamId, + insertLen: predictDataLimitLength(trainingType, chunks) + }); + + const fn = async (session: ClientSession) => { + // 3. create collection + const { _id: collectionId } = await createOneCollection({ + ...createCollectionParams, + + hashRawText: hashStr(rawText), + rawTextLength: rawText.length, + session + }); + + // 4. create training bill + const { billId } = await createTrainingUsage({ + teamId, + tmbId, + appName: usageName, + billSource: UsageSourceEnum.training, + vectorModel: getVectorModel(dataset.vectorModel)?.name, + agentModel: getLLMModel(dataset.agentModel)?.name, + session + }); + + // 5. insert to training queue + const insertResults = await pushDataListToTrainingQueue({ + teamId, + tmbId, + datasetId: dataset._id, + collectionId, + agentModel: dataset.agentModel, + vectorModel: dataset.vectorModel, + trainingMode: trainingType, + prompt: qaPrompt, + billId, + data: chunks.map((item, index) => ({ + ...item, + chunkIndex: index + })), + session + }); + + // 6. remove related image ttl + if (relatedId) { + await MongoImage.updateMany( + { + teamId, + 'metadata.relatedId': relatedId + }, + { + // Remove expiredTime to avoid ttl expiration + $unset: { + expiredTime: 1 + } + }, + { + session + } + ); + } + + return { + collectionId, + insertResults + }; + }; + + if (session) { + return fn(session); + } + return mongoSessionRun(fn); +}; + +export type CreateOneCollectionParams = CreateDatasetCollectionParams & { + teamId: string; + tmbId: string; + session?: ClientSession; +}; export async function createOneCollection({ teamId, tmbId, @@ -33,18 +159,15 @@ export async function createOneCollection({ externalFileId, externalFileUrl, + apiFileId, + hashRawText, rawTextLength, metadata = {}, session, tags, - ...props -}: CreateDatasetCollectionParams & { - teamId: string; - tmbId: string; - [key: string]: any; - session?: ClientSession; -}) { + createTime +}: CreateOneCollectionParams) { // Create collection tags const collectionTags = await createOrGetCollectionTags({ tags, teamId, datasetId, session }); @@ -52,7 +175,6 @@ export async function createOneCollection({ const [collection] = await MongoDatasetCollection.create( [ { - ...props, teamId, tmbId, parentId: parentId || null, @@ -64,16 +186,18 @@ export async function createOneCollection({ chunkSize, chunkSplitter, qaPrompt, + metadata, - fileId, - rawLink, + ...(fileId ? { fileId } : {}), + ...(rawLink ? { rawLink } : {}), ...(externalFileId ? { externalFileId } : {}), - externalFileUrl, + ...(externalFileUrl ? { externalFileUrl } : {}), + ...(apiFileId ? { apiFileId } : {}), rawTextLength, hashRawText, - metadata, - tags: collectionTags + tags: collectionTags, + createTime } ], { session } @@ -116,12 +240,14 @@ export const delCollectionRelatedSource = async ({ /** * delete collection and it related data */ -export async function delCollectionAndRelatedSources({ +export async function delCollection({ collections, - session + session, + delRelatedSource }: { collections: (CollectionWithDatasetType | DatasetCollectionSchemaType)[]; session: ClientSession; + delRelatedSource: boolean; }) { if (collections.length === 0) return; @@ -149,7 +275,63 @@ export async function delCollectionAndRelatedSources({ }); /* file and imgs */ - await delCollectionRelatedSource({ collections, session }); + if (delRelatedSource) { + await delCollectionRelatedSource({ collections, session }); + } + + // delete dataset.datas + await MongoDatasetData.deleteMany( + { teamId, datasetIds: { $in: datasetIds }, collectionId: { $in: collectionIds } }, + { session } + ); + + // delete collections + await MongoDatasetCollection.deleteMany( + { + teamId, + _id: { $in: collectionIds } + }, + { session } + ); + + // no session delete: delete files, vector data + await deleteDatasetDataVector({ teamId, datasetIds, collectionIds }); +} + +/** + * delete delOnlyCollection + */ +export async function delOnlyCollection({ + collections, + session +}: { + collections: (CollectionWithDatasetType | DatasetCollectionSchemaType)[]; + session: ClientSession; +}) { + if (collections.length === 0) return; + + const teamId = collections[0].teamId; + + if (!teamId) return Promise.reject('teamId is not exist'); + + const datasetIds = Array.from( + new Set( + collections.map((item) => { + if (typeof item.datasetId === 'string') { + return String(item.datasetId); + } + return String(item.datasetId._id); + }) + ) + ); + const collectionIds = collections.map((item) => String(item._id)); + + // delete training data + await MongoDatasetTraining.deleteMany({ + teamId, + datasetIds: { $in: datasetIds }, + collectionId: { $in: collectionIds } + }); // delete dataset.datas await MongoDatasetData.deleteMany( diff --git a/packages/service/core/dataset/collection/schema.ts b/packages/service/core/dataset/collection/schema.ts index 00df3dc05dbf..a6f7628611c7 100644 --- a/packages/service/core/dataset/collection/schema.ts +++ b/packages/service/core/dataset/collection/schema.ts @@ -10,90 +10,100 @@ import { export const DatasetColCollectionName = 'dataset_collections'; -const DatasetCollectionSchema = new Schema({ - parentId: { - type: Schema.Types.ObjectId, - ref: DatasetColCollectionName, - default: null - }, - teamId: { - type: Schema.Types.ObjectId, - ref: TeamCollectionName, - required: true - }, - tmbId: { - type: Schema.Types.ObjectId, - ref: TeamMemberCollectionName, - required: true - }, - datasetId: { - type: Schema.Types.ObjectId, - ref: DatasetCollectionName, - required: true - }, - type: { - type: String, - enum: Object.keys(DatasetCollectionTypeMap), - required: true - }, - name: { - type: String, - required: true - }, - createTime: { - type: Date, - default: () => new Date() - }, - updateTime: { - type: Date, - default: () => new Date() - }, - forbid: { - type: Boolean, - default: false - }, +const DatasetCollectionSchema = new Schema( + { + parentId: { + type: Schema.Types.ObjectId, + ref: DatasetColCollectionName, + default: null + }, + teamId: { + type: Schema.Types.ObjectId, + ref: TeamCollectionName, + required: true + }, + tmbId: { + type: Schema.Types.ObjectId, + ref: TeamMemberCollectionName, + required: true + }, + datasetId: { + type: Schema.Types.ObjectId, + ref: DatasetCollectionName, + required: true + }, + type: { + type: String, + enum: Object.keys(DatasetCollectionTypeMap), + required: true + }, + name: { + type: String, + required: true + }, + createTime: { + type: Date, + default: () => new Date() + }, + updateTime: { + type: Date, + default: () => new Date() + }, + forbid: { + type: Boolean, + default: false + }, - // chunk filed - trainingType: { - type: String, - enum: Object.keys(TrainingTypeMap) - }, - chunkSize: { - type: Number, - required: true - }, - chunkSplitter: { - type: String - }, - qaPrompt: { - type: String - }, - ocrParse: Boolean, + // chunk filed + trainingType: { + type: String, + enum: Object.keys(TrainingTypeMap) + }, + chunkSize: { + type: Number, + required: true + }, + chunkSplitter: { + type: String + }, + qaPrompt: { + type: String + }, + ocrParse: Boolean, - tags: { - type: [String], - default: [] - }, + tags: { + type: [String], + default: [] + }, - // local file collection - fileId: { - type: Schema.Types.ObjectId, - ref: 'dataset.files' - }, - // web link collection - rawLink: String, - // external collection - externalFileId: String, + // local file collection + fileId: { + type: Schema.Types.ObjectId, + ref: 'dataset.files' + }, + // web link collection + rawLink: String, + // api collection + apiFileId: String, + // external collection + externalFileId: String, + externalFileUrl: String, // external import url - // metadata - rawTextLength: Number, - hashRawText: String, - externalFileUrl: String, // external import url - metadata: { - type: Object, - default: {} + // metadata + rawTextLength: Number, + hashRawText: String, + metadata: { + type: Object, + default: {} + } + }, + { + // Auto update updateTime + timestamps: { + updatedAt: 'updateTime' + } } -}); +); try { // auth file diff --git a/packages/service/core/dataset/collection/utils.ts b/packages/service/core/dataset/collection/utils.ts index a1275924e93b..3a257c229026 100644 --- a/packages/service/core/dataset/collection/utils.ts +++ b/packages/service/core/dataset/collection/utils.ts @@ -1,17 +1,19 @@ -import type { CollectionWithDatasetType } from '@fastgpt/global/core/dataset/type.d'; import { MongoDatasetCollection } from './schema'; -import { splitText2Chunks } from '@fastgpt/global/common/string/textSplitter'; -import { MongoDatasetTraining } from '../training/schema'; -import { urlsFetch } from '../../../common/string/cheerio'; +import { ClientSession } from '../../../common/mongo'; +import { MongoDatasetCollectionTags } from '../tag/schema'; +import { readFromSecondary } from '../../../common/mongo/utils'; +import { CollectionWithDatasetType } from '@fastgpt/global/core/dataset/type'; import { + DatasetCollectionSyncResultEnum, DatasetCollectionTypeEnum, - TrainingModeEnum + DatasetSourceReadTypeEnum, + DatasetTypeEnum } from '@fastgpt/global/core/dataset/constants'; +import { DatasetErrEnum } from '@fastgpt/global/common/error/code/dataset'; +import { readDatasetSourceRawText } from '../read'; import { hashStr } from '@fastgpt/global/common/string/tools'; -import { ClientSession } from '../../../common/mongo'; -import { PushDatasetDataResponse } from '@fastgpt/global/core/dataset/api'; -import { MongoDatasetCollectionTags } from '../tag/schema'; -import { readFromSecondary } from '../../../common/mongo/utils'; +import { mongoSessionRun } from '../../../common/mongo/sessionRun'; +import { createCollectionAndInsertData, delCollection } from './controller'; /** * get all collection by top collectionId @@ -61,148 +63,6 @@ export function getCollectionUpdateTime({ name, time }: { time?: Date; name: str return new Date(); } -/** - * Get collection raw text by Collection or collectionId - */ -export const getCollectionAndRawText = async ({ - collectionId, - collection, - newRawText -}: { - collectionId?: string; - collection?: CollectionWithDatasetType; - newRawText?: string; -}) => { - const col = await (async () => { - if (collection) return collection; - if (collectionId) { - return (await MongoDatasetCollection.findById(collectionId).populate( - 'datasetId' - )) as CollectionWithDatasetType; - } - - return null; - })(); - - if (!col) { - return Promise.reject('Collection not found'); - } - - const { title, rawText } = await (async () => { - if (newRawText) - return { - title: '', - rawText: newRawText - }; - // link - if (col.type === DatasetCollectionTypeEnum.link && col.rawLink) { - // crawl new data - const result = await urlsFetch({ - urlList: [col.rawLink], - selector: col.datasetId?.websiteConfig?.selector || col?.metadata?.webPageSelector - }); - - return { - title: result[0]?.title, - rawText: result[0]?.content - }; - } - - // file - - return { - title: '', - rawText: '' - }; - })(); - - const hashRawText = hashStr(rawText); - const isSameRawText = rawText && col.hashRawText === hashRawText; - - return { - collection: col, - title, - rawText, - isSameRawText - }; -}; - -/* link collection start load data */ -export const reloadCollectionChunks = async ({ - collection, - tmbId, - billId, - rawText, - session -}: { - collection: CollectionWithDatasetType; - tmbId: string; - billId?: string; - rawText?: string; - session: ClientSession; -}): Promise => { - const { - title, - rawText: newRawText, - collection: col, - isSameRawText - } = await getCollectionAndRawText({ - collection, - newRawText: rawText - }); - - if (isSameRawText) - return { - insertLen: 0 - }; - - // split data - const { chunks } = splitText2Chunks({ - text: newRawText, - chunkLen: col.chunkSize || 512, - customReg: col.chunkSplitter ? [col.chunkSplitter] : [] - }); - - // insert to training queue - const model = await (() => { - if (col.trainingType === TrainingModeEnum.chunk) return col.datasetId.vectorModel; - if (col.trainingType === TrainingModeEnum.qa) return col.datasetId.agentModel; - return Promise.reject('Training model error'); - })(); - - const result = await MongoDatasetTraining.insertMany( - chunks.map((item, i) => ({ - teamId: col.teamId, - tmbId, - datasetId: col.datasetId._id, - collectionId: col._id, - billId, - mode: col.trainingType, - prompt: '', - model, - q: item, - a: '', - chunkIndex: i - })), - { session } - ); - - // update raw text - await MongoDatasetCollection.findByIdAndUpdate( - col._id, - { - ...(title && { name: title }), - rawTextLength: newRawText.length, - hashRawText: hashStr(newRawText) - }, - { session } - ); - - return { - insertLen: result.length - }; -}; - export const createOrGetCollectionTags = async ({ tags, datasetId, @@ -268,3 +128,88 @@ export const collectionTagsToTagLabel = async ({ }) .filter(Boolean); }; + +export const syncCollection = async (collection: CollectionWithDatasetType) => { + const dataset = collection.datasetId; + + if ( + collection.type !== DatasetCollectionTypeEnum.link && + dataset.type !== DatasetTypeEnum.apiDataset + ) { + return Promise.reject(DatasetErrEnum.notSupportSync); + } + + // Get new text + const sourceReadType = await (async () => { + if (collection.type === DatasetCollectionTypeEnum.link) { + if (!collection.rawLink) return Promise.reject('rawLink is missing'); + return { + type: DatasetSourceReadTypeEnum.link, + sourceId: collection.rawLink, + selector: collection.metadata?.webPageSelector + }; + } + + if (!collection.apiFileId) return Promise.reject('apiFileId is missing'); + if (!dataset.apiServer) return Promise.reject('apiServer not found'); + return { + type: DatasetSourceReadTypeEnum.apiFile, + sourceId: collection.apiFileId, + apiServer: dataset.apiServer + }; + })(); + const rawText = await readDatasetSourceRawText({ + teamId: collection.teamId, + ...sourceReadType + }); + + // Check if the original text is the same: skip if same + const hashRawText = hashStr(rawText); + if (collection.hashRawText && hashRawText === collection.hashRawText) { + return DatasetCollectionSyncResultEnum.sameRaw; + } + + await mongoSessionRun(async (session) => { + // Create new collection + await createCollectionAndInsertData({ + session, + dataset, + rawText: rawText, + createCollectionParams: { + teamId: collection.teamId, + tmbId: collection.tmbId, + datasetId: collection.datasetId._id, + name: collection.name, + type: collection.type, + + fileId: collection.fileId, + rawLink: collection.rawLink, + externalFileId: collection.externalFileId, + externalFileUrl: collection.externalFileUrl, + apiFileId: collection.apiFileId, + + rawTextLength: rawText.length, + hashRawText, + + tags: collection.tags, + createTime: collection.createTime, + + parentId: collection.parentId, + trainingType: collection.trainingType, + chunkSize: collection.chunkSize, + chunkSplitter: collection.chunkSplitter, + qaPrompt: collection.qaPrompt, + metadata: collection.metadata + } + }); + + // Delete old collection + await delCollection({ + collections: [collection], + delRelatedSource: false, + session + }); + }); + + return DatasetCollectionSyncResultEnum.success; +}; diff --git a/packages/service/core/dataset/read.ts b/packages/service/core/dataset/read.ts index b76d0d3c2f42..d870f39235a5 100644 --- a/packages/service/core/dataset/read.ts +++ b/packages/service/core/dataset/read.ts @@ -7,6 +7,8 @@ import { TextSplitProps, splitText2Chunks } from '@fastgpt/global/common/string/ import axios from 'axios'; import { readRawContentByFileBuffer } from '../../common/file/read/utils'; import { parseFileExtensionFromUrl } from '@fastgpt/global/common/string/tools'; +import { APIFileServer } from '@fastgpt/global/core/dataset/apiDataset'; +import { useApiDatasetRequest } from './apiDataset/api'; export const readFileRawTextByUrl = async ({ teamId, @@ -15,7 +17,7 @@ export const readFileRawTextByUrl = async ({ }: { teamId: string; url: string; - relatedId?: string; + relatedId: string; // externalFileId / apiFileId }) => { const response = await axios({ method: 'get', @@ -40,9 +42,9 @@ export const readFileRawTextByUrl = async ({ }; /* - fileId - local file, read from mongo - link - request - externalFile = request read + fileId - local file, read from mongo + link - request + externalFile/apiFile = request read */ export const readDatasetSourceRawText = async ({ teamId, @@ -50,14 +52,17 @@ export const readDatasetSourceRawText = async ({ sourceId, isQAImport, selector, - relatedId + externalFileId, + apiServer }: { teamId: string; type: DatasetSourceReadTypeEnum; sourceId: string; - isQAImport?: boolean; - selector?: string; - relatedId?: string; + + isQAImport?: boolean; // csv data + selector?: string; // link selector + externalFileId?: string; // external file dataset + apiServer?: APIFileServer; // api dataset }): Promise => { if (type === DatasetSourceReadTypeEnum.fileLocal) { const { rawText } = await readFileContentFromMongo({ @@ -75,10 +80,19 @@ export const readDatasetSourceRawText = async ({ return result[0]?.content || ''; } else if (type === DatasetSourceReadTypeEnum.externalFile) { + if (!externalFileId) return Promise.reject('FileId not found'); const rawText = await readFileRawTextByUrl({ teamId, url: sourceId, - relatedId + relatedId: externalFileId + }); + return rawText; + } else if (type === DatasetSourceReadTypeEnum.apiFile) { + if (!apiServer) return Promise.reject('apiServer not found'); + const rawText = await readApiServerFileContent({ + apiServer, + apiFileId: sourceId, + teamId }); return rawText; } @@ -86,6 +100,18 @@ export const readDatasetSourceRawText = async ({ return ''; }; +export const readApiServerFileContent = async ({ + apiServer, + apiFileId, + teamId +}: { + apiServer: APIFileServer; + apiFileId: string; + teamId: string; +}) => { + return useApiDatasetRequest({ apiServer }).getFileContent({ teamId, apiFileId }); +}; + export const rawText2Chunks = ({ rawText, isQAImport, diff --git a/packages/service/core/dataset/schema.ts b/packages/service/core/dataset/schema.ts index c0143a39cd94..6312211adc8b 100644 --- a/packages/service/core/dataset/schema.ts +++ b/packages/service/core/dataset/schema.ts @@ -83,15 +83,18 @@ const DatasetSchema = new Schema({ } } }, - externalReadUrl: { - type: String - }, inheritPermission: { type: Boolean, default: true }, + apiServer: { + type: Object + }, // abandoned + externalReadUrl: { + type: String + }, defaultPermission: Number }); diff --git a/packages/service/core/dataset/training/utils.ts b/packages/service/core/dataset/training/utils.ts index 7a94b052697e..35ff6aabdb26 100644 --- a/packages/service/core/dataset/training/utils.ts +++ b/packages/service/core/dataset/training/utils.ts @@ -28,8 +28,7 @@ export const checkInvalidChunkAndLock = async ({ err?.type === 'invalid_request_error' || err?.code === 500 ) { - addLog.info('Lock training data'); - console.log(err); + addLog.error('Lock training data', err); try { await MongoDatasetTraining.findByIdAndUpdate(data._id, { diff --git a/packages/service/core/workflow/dispatch/index.ts b/packages/service/core/workflow/dispatch/index.ts index acbf726cc1df..cf1bc39068f4 100644 --- a/packages/service/core/workflow/dispatch/index.ts +++ b/packages/service/core/workflow/dispatch/index.ts @@ -72,7 +72,6 @@ import { dispatchLoopEnd } from './loop/runLoopEnd'; import { dispatchLoopStart } from './loop/runLoopStart'; import { dispatchFormInput } from './interactive/formInput'; import { dispatchToolParams } from './agent/runTool/toolParams'; -import { responseWrite } from '../../../common/response'; const callbackMap: Record = { [FlowNodeTypeEnum.workflowStart]: dispatchWorkflowStart, @@ -500,8 +499,7 @@ export async function dispatchWorkFlow(data: Props): Promise>((acc, item) => { + acc[item.key] = valueTypeFormat(item.defaultValue, item.valueType); + return acc; + }, {}); + return { + ...variablesMap, userId: uid, appId: String(runningAppInfo.id), chatId, diff --git a/packages/service/core/workflow/dispatch/plugin/run.ts b/packages/service/core/workflow/dispatch/plugin/run.ts index 6d5fe3164a66..cd75aee1d623 100644 --- a/packages/service/core/workflow/dispatch/plugin/run.ts +++ b/packages/service/core/workflow/dispatch/plugin/run.ts @@ -23,7 +23,6 @@ type RunPluginProps = ModuleDispatchProps<{ [key: string]: any; }>; type RunPluginResponse = DispatchNodeResultType<{}>; - export const dispatchRunPlugin = async (props: RunPluginProps): Promise => { const { node: { pluginId, version }, @@ -31,7 +30,6 @@ export const dispatchRunPlugin = async (props: RunPluginProps): Promise item.moduleType === FlowNodeTypeEnum.pluginOutput); - if (output) { output.moduleLogo = plugin.avatar; } @@ -117,7 +111,6 @@ export const dispatchRunPlugin = async (props: RunPluginProps): Promise): AnswerResponse => { } = props as AnswerProps; const formatText = typeof text === 'string' ? text : JSON.stringify(text, null, 2); - const responseText = `\n${formatText}`.replaceAll('\\n', '\n'); + const responseText = `\n${formatText}`; workflowStreamResponse?.({ event: SseResponseEventEnum.fastAnswer, diff --git a/packages/service/core/workflow/dispatch/tools/http468.ts b/packages/service/core/workflow/dispatch/tools/http468.ts index 307edd3505f3..ba76fa096958 100644 --- a/packages/service/core/workflow/dispatch/tools/http468.ts +++ b/packages/service/core/workflow/dispatch/tools/http468.ts @@ -110,8 +110,7 @@ export const dispatchHttp468Request = async (props: HttpRequestProps): Promise => ? replaceEditorVariable({ text: formatValue, nodes: runtimeNodes, - variables, - runningNode: node + variables }) : formatValue; } else { diff --git a/packages/service/core/workflow/dispatch/utils.ts b/packages/service/core/workflow/dispatch/utils.ts index 5c625013c373..4186d89ac27d 100644 --- a/packages/service/core/workflow/dispatch/utils.ts +++ b/packages/service/core/workflow/dispatch/utils.ts @@ -134,7 +134,7 @@ export const checkQuoteQAValue = (quoteQA?: SearchDataResponseItemType[]) => { if (quoteQA.length === 0) { return []; } - if (quoteQA.some((item) => !item.q || !item.datasetId)) { + if (quoteQA.some((item) => !item.q)) { return undefined; } return quoteQA; diff --git a/packages/service/support/permission/app/auth.ts b/packages/service/support/permission/app/auth.ts index 4ef73a85410e..c50b9da75120 100644 --- a/packages/service/support/permission/app/auth.ts +++ b/packages/service/support/permission/app/auth.ts @@ -34,6 +34,8 @@ export const authPluginByTmbId = async ({ return app; } + + // commercial custom plugin already checked in "getSystemPluginTemplateById" }; export const authAppByTmbId = async ({ diff --git a/packages/service/support/permission/memberGroup/memberGroupSchema.ts b/packages/service/support/permission/memberGroup/memberGroupSchema.ts index c962a0968dd4..6964785bc56a 100644 --- a/packages/service/support/permission/memberGroup/memberGroupSchema.ts +++ b/packages/service/support/permission/memberGroup/memberGroupSchema.ts @@ -25,6 +25,7 @@ export const MemberGroupSchema = new Schema( } }, { + // Auto update updateTime timestamps: { updatedAt: 'updateTime' } diff --git a/packages/web/components/common/DndDrag/index.tsx b/packages/web/components/common/DndDrag/index.tsx index 44d7ccd9a49e..a072292ad182 100644 --- a/packages/web/components/common/DndDrag/index.tsx +++ b/packages/web/components/common/DndDrag/index.tsx @@ -48,7 +48,7 @@ function DndDrag({ children, renderClone, onDragEndCb, dataList }: Props) return ( {children(provided, snapshot)} - {snapshot.isDraggingOver && } + {snapshot.isDraggingOver && } ); }} diff --git a/packages/web/components/common/Icon/constants.ts b/packages/web/components/common/Icon/constants.ts index e6d3d82bbc47..4b42069924d9 100644 --- a/packages/web/components/common/Icon/constants.ts +++ b/packages/web/components/common/Icon/constants.ts @@ -14,9 +14,11 @@ export const iconPaths = { 'common/addCircleLight': () => import('./icons/common/addCircleLight.svg'), 'common/addLight': () => import('./icons/common/addLight.svg'), 'common/addUser': () => import('./icons/common/addUser.svg'), + 'common/administrator': () => import('./icons/common/administrator.svg'), 'common/arrowLeft': () => import('./icons/common/arrowLeft.svg'), 'common/backFill': () => import('./icons/common/backFill.svg'), 'common/backLight': () => import('./icons/common/backLight.svg'), + 'common/billing': () => import('./icons/common/billing.svg'), 'common/check': () => import('./icons/common/check.svg'), 'common/clearLight': () => import('./icons/common/clearLight.svg'), 'common/closeLight': () => import('./icons/common/closeLight.svg'), @@ -52,7 +54,10 @@ export const iconPaths = { 'common/loading': () => import('./icons/common/loading.svg'), 'common/logLight': () => import('./icons/common/logLight.svg'), 'common/microsoft': () => import('./icons/common/microsoft.svg'), + 'common/modal': () => import('./icons/common/modal.svg'), 'common/monitor': () => import('./icons/common/monitor.svg'), + 'common/more': () => import('./icons/common/more.svg'), + 'common/moreFill': () => import('./icons/common/moreFill.svg'), 'common/navbar/pluginFill': () => import('./icons/common/navbar/pluginFill.svg'), 'common/navbar/pluginLight': () => import('./icons/common/navbar/pluginLight.svg'), 'common/openai': () => import('./icons/common/openai.svg'), @@ -74,20 +79,27 @@ export const iconPaths = { 'common/selectLight': () => import('./icons/common/selectLight.svg'), 'common/setting': () => import('./icons/common/setting.svg'), 'common/settingLight': () => import('./icons/common/settingLight.svg'), + 'common/solidChevronRight': () => import('./icons/common/solidChevronRight.svg'), 'common/subtract': () => import('./icons/common/subtract.svg'), 'common/text/t': () => import('./icons/common/text/t.svg'), 'common/tickFill': () => import('./icons/common/tickFill.svg'), + 'common/toolkit': () => import('./icons/common/toolkit.svg'), 'common/trash': () => import('./icons/common/trash.svg'), 'common/upRightArrowLight': () => import('./icons/common/upRightArrowLight.svg'), 'common/uploadFileFill': () => import('./icons/common/uploadFileFill.svg'), + 'common/userInfo': () => import('./icons/common/userInfo.svg'), 'common/viewLight': () => import('./icons/common/viewLight.svg'), 'common/voiceLight': () => import('./icons/common/voiceLight.svg'), 'common/warn': () => import('./icons/common/warn.svg'), 'common/wechatFill': () => import('./icons/common/wechatFill.svg'), configmap: () => import('./icons/configmap.svg'), copy: () => import('./icons/copy.svg'), + code: () => import('./icons/code.svg'), + preview: () => import('./icons/preview.svg'), + fullScreen: () => import('./icons/fullScreen.svg'), 'core/app/aiFill': () => import('./icons/core/app/aiFill.svg'), 'core/app/aiLight': () => import('./icons/core/app/aiLight.svg'), + 'core/app/aiLightSmall': () => import('./icons/core/app/aiLightSmall.svg'), 'core/app/appApiLight': () => import('./icons/core/app/appApiLight.svg'), 'core/app/customFeedback': () => import('./icons/core/app/customFeedback.svg'), 'core/app/headphones': () => import('./icons/core/app/headphones.svg'), @@ -153,6 +165,7 @@ export const iconPaths = { import('./icons/core/dataset/commonDatasetOutline.svg'), 'core/dataset/datasetFill': () => import('./icons/core/dataset/datasetFill.svg'), 'core/dataset/datasetLight': () => import('./icons/core/dataset/datasetLight.svg'), + 'core/dataset/datasetLightSmall': () => import('./icons/core/dataset/datasetLightSmall.svg'), 'core/dataset/externalDataset': () => import('./icons/core/dataset/externalDataset.svg'), 'core/dataset/externalDatasetColor': () => import('./icons/core/dataset/externalDatasetColor.svg'), @@ -361,6 +374,9 @@ export const iconPaths = { 'support/user/informLight': () => import('./icons/support/user/informLight.svg'), 'support/user/userFill': () => import('./icons/support/user/userFill.svg'), 'support/user/userLight': () => import('./icons/support/user/userLight.svg'), + 'support/user/userLightSmall': () => import('./icons/support/user/userLightSmall.svg'), + 'support/user/usersFill': () => import('./icons/support/user/usersFill.svg'), + 'support/user/usersLight': () => import('./icons/support/user/usersLight.svg'), text: () => import('./icons/text.svg'), union: () => import('./icons/union.svg'), user: () => import('./icons/user.svg'), diff --git a/packages/web/components/common/Icon/icons/book.svg b/packages/web/components/common/Icon/icons/book.svg index 043acdc65387..a187b7ff4ceb 100644 --- a/packages/web/components/common/Icon/icons/book.svg +++ b/packages/web/components/common/Icon/icons/book.svg @@ -1,3 +1,3 @@ - - - + + + \ No newline at end of file diff --git a/packages/web/components/common/Icon/icons/code.svg b/packages/web/components/common/Icon/icons/code.svg new file mode 100644 index 000000000000..ba4051ecc16f --- /dev/null +++ b/packages/web/components/common/Icon/icons/code.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/packages/web/components/common/Icon/icons/common/administrator.svg b/packages/web/components/common/Icon/icons/common/administrator.svg new file mode 100644 index 000000000000..c0466f0449a8 --- /dev/null +++ b/packages/web/components/common/Icon/icons/common/administrator.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/packages/web/components/common/Icon/icons/common/billing.svg b/packages/web/components/common/Icon/icons/common/billing.svg new file mode 100644 index 000000000000..42824bd2203b --- /dev/null +++ b/packages/web/components/common/Icon/icons/common/billing.svg @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/packages/web/components/common/Icon/icons/common/modal.svg b/packages/web/components/common/Icon/icons/common/modal.svg new file mode 100644 index 000000000000..02d68691cbc0 --- /dev/null +++ b/packages/web/components/common/Icon/icons/common/modal.svg @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/packages/web/components/common/Icon/icons/common/more.svg b/packages/web/components/common/Icon/icons/common/more.svg new file mode 100644 index 000000000000..e7dae2a41758 --- /dev/null +++ b/packages/web/components/common/Icon/icons/common/more.svg @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/packages/web/components/common/Icon/icons/common/moreFill.svg b/packages/web/components/common/Icon/icons/common/moreFill.svg new file mode 100644 index 000000000000..0119b97b8a45 --- /dev/null +++ b/packages/web/components/common/Icon/icons/common/moreFill.svg @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/packages/web/components/common/Icon/icons/common/navbar/pluginLight.svg b/packages/web/components/common/Icon/icons/common/navbar/pluginLight.svg index dc9d8da06a33..2a866c8758e2 100644 --- a/packages/web/components/common/Icon/icons/common/navbar/pluginLight.svg +++ b/packages/web/components/common/Icon/icons/common/navbar/pluginLight.svg @@ -1,4 +1,3 @@ - - + + \ No newline at end of file diff --git a/packages/web/components/common/Icon/icons/common/solidChevronRight.svg b/packages/web/components/common/Icon/icons/common/solidChevronRight.svg new file mode 100644 index 000000000000..e621a8dd2e21 --- /dev/null +++ b/packages/web/components/common/Icon/icons/common/solidChevronRight.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/packages/web/components/common/Icon/icons/common/toolkit.svg b/packages/web/components/common/Icon/icons/common/toolkit.svg new file mode 100644 index 000000000000..93eaac1fabaa --- /dev/null +++ b/packages/web/components/common/Icon/icons/common/toolkit.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/packages/web/components/common/Icon/icons/common/userInfo.svg b/packages/web/components/common/Icon/icons/common/userInfo.svg new file mode 100644 index 000000000000..48af4a49b223 --- /dev/null +++ b/packages/web/components/common/Icon/icons/common/userInfo.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/packages/web/components/common/Icon/icons/core/app/aiLightSmall.svg b/packages/web/components/common/Icon/icons/core/app/aiLightSmall.svg new file mode 100644 index 000000000000..1db1e20cfc73 --- /dev/null +++ b/packages/web/components/common/Icon/icons/core/app/aiLightSmall.svg @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/packages/web/components/common/Icon/icons/core/dataset/datasetLightSmall.svg b/packages/web/components/common/Icon/icons/core/dataset/datasetLightSmall.svg new file mode 100644 index 000000000000..1cec156fbf23 --- /dev/null +++ b/packages/web/components/common/Icon/icons/core/dataset/datasetLightSmall.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/packages/web/components/common/Icon/icons/fullScreen.svg b/packages/web/components/common/Icon/icons/fullScreen.svg new file mode 100644 index 000000000000..70bd4e46d49a --- /dev/null +++ b/packages/web/components/common/Icon/icons/fullScreen.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/web/components/common/Icon/icons/preview.svg b/packages/web/components/common/Icon/icons/preview.svg new file mode 100644 index 000000000000..d09b502b8acf --- /dev/null +++ b/packages/web/components/common/Icon/icons/preview.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/packages/web/components/common/Icon/icons/support/account/plans.svg b/packages/web/components/common/Icon/icons/support/account/plans.svg index e8aa856725d8..4cfc42f19b92 100644 --- a/packages/web/components/common/Icon/icons/support/account/plans.svg +++ b/packages/web/components/common/Icon/icons/support/account/plans.svg @@ -1,5 +1,5 @@ - - - - - + + + + + \ No newline at end of file diff --git a/packages/web/components/common/Icon/icons/support/bill/priceLight.svg b/packages/web/components/common/Icon/icons/support/bill/priceLight.svg index c7f83cd7e9d3..0dceea7a2545 100644 --- a/packages/web/components/common/Icon/icons/support/bill/priceLight.svg +++ b/packages/web/components/common/Icon/icons/support/bill/priceLight.svg @@ -1,11 +1,3 @@ - - - - + + \ No newline at end of file diff --git a/packages/web/components/common/Icon/icons/support/user/userLightSmall.svg b/packages/web/components/common/Icon/icons/support/user/userLightSmall.svg new file mode 100644 index 000000000000..1b5f0a814f63 --- /dev/null +++ b/packages/web/components/common/Icon/icons/support/user/userLightSmall.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/packages/web/components/common/Icon/icons/support/user/usersFill.svg b/packages/web/components/common/Icon/icons/support/user/usersFill.svg new file mode 100644 index 000000000000..4d189afdc739 --- /dev/null +++ b/packages/web/components/common/Icon/icons/support/user/usersFill.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/packages/web/components/common/Icon/icons/support/user/usersLight.svg b/packages/web/components/common/Icon/icons/support/user/usersLight.svg new file mode 100644 index 000000000000..62b8819b00ff --- /dev/null +++ b/packages/web/components/common/Icon/icons/support/user/usersLight.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/packages/web/components/common/Input/NumberInput/index.tsx b/packages/web/components/common/Input/NumberInput/index.tsx index 58b788ebf26c..7c6cab7c780d 100644 --- a/packages/web/components/common/Input/NumberInput/index.tsx +++ b/packages/web/components/common/Input/NumberInput/index.tsx @@ -26,8 +26,9 @@ const MyNumberInput = (props: Props) => { {...restProps} onChange={(e) => { if (!onChange) return; - if (isNaN(Number(e))) { - onChange(); + if (e === '') { + // @ts-ignore + onChange(''); } else { onChange(Number(e)); } diff --git a/packages/web/components/common/MySelect/CronSelector.tsx b/packages/web/components/common/MySelect/CronSelector.tsx new file mode 100644 index 000000000000..b7266f7d1961 --- /dev/null +++ b/packages/web/components/common/MySelect/CronSelector.tsx @@ -0,0 +1,230 @@ +import React, { useCallback, useRef } from 'react'; +import MultipleRowSelect from './MultipleRowSelect'; +import { useTranslation } from 'next-i18next'; +import { MultipleSelectProps } from './type'; +import { cronParser2Fields } from '@fastgpt/global/common/string/time'; + +type CronType = 'month' | 'week' | 'day' | 'interval'; + +type CronFieldType = [CronType, number, number]; + +enum CronJobTypeEnum { + month = 'month', + week = 'week', + day = 'day', + interval = 'interval' +} + +export const defaultCronString = '0 0 * * *'; + +export const defaultValue = [CronJobTypeEnum.day, 0, 0]; + +export const cronString2Fields = (cronString?: string) => { + if (!cronString) { + return undefined; + } + const cronField = cronParser2Fields(cronString); + + if (!cronField) { + return defaultValue; + } + + if (cronField.dayOfMonth.length !== 31) { + return [CronJobTypeEnum.month, cronField.dayOfMonth[0], cronField.hour[0]]; + } + if (cronField.dayOfWeek.length !== 8) { + return [CronJobTypeEnum.week, cronField.dayOfWeek[0], cronField.hour[0]]; + } + if (cronField.hour.length === 1) { + return [CronJobTypeEnum.day, cronField.hour[0], 0]; + } + return [CronJobTypeEnum.interval, 24 / cronField.hour.length, 0]; +}; + +export const cronString2Label = ( + cronString: string, + t: any // i18nT +) => { + const cronField = cronString2Fields(cronString); + if (!cronField) { + return t('common:common.Not open'); + } + + if (cronField[0] === 'month') { + return t('common:core.app.schedule.Every month', { + day: cronField[1], + hour: cronField[2] + }); + } + if (cronField[0] === 'week') { + const weekMap = { + 0: t('app:week.Sunday'), + 1: t('app:week.Monday'), + 2: t('app:week.Tuesday'), + 3: t('app:week.Wednesday'), + 4: t('app:week.Thursday'), + 5: t('app:week.Friday'), + 6: t('app:week.Saturday') + }; + return t('common:core.app.schedule.Every week', { + day: weekMap[cronField[1] as keyof typeof weekMap], + hour: cronField[2] + }); + } + if (cronField[0] === 'day') { + return t('common:core.app.schedule.Every day', { + hour: cronField[1] + }); + } + if (cronField[0] === 'interval') { + return t('common:core.app.schedule.Interval', { + interval: cronField[1] + }); + } + + return t('common:common.Not open'); +}; + +const CronSelector = ({ + cronString, + onChange +}: { + cronString?: string; + onChange: (e: string) => void; +}) => { + const { t } = useTranslation(); + + const get24HoursOptions = () => { + return Array.from({ length: 24 }, (_, i) => ({ + label: `${i < 10 ? '0' : ''}${i}:00`, + value: i + })); + }; + const getRoute = (i: number) => { + const { t } = useTranslation(); + switch (i) { + case 0: + return t('app:week.Sunday'); + case 1: + return t('app:week.Monday'); + case 2: + return t('app:week.Tuesday'); + case 3: + return t('app:week.Wednesday'); + case 4: + return t('app:week.Thursday'); + case 5: + return t('app:week.Friday'); + case 6: + return t('app:week.Saturday'); + default: + return t('app:week.Sunday'); + } + }; + const getWeekOptions = () => { + return Array.from({ length: 7 }, (_, i) => { + return { + label: getRoute(i), + value: i, + children: get24HoursOptions() + }; + }); + }; + const getMonthOptions = () => { + return Array.from({ length: 28 }, (_, i) => ({ + label: i + 1 + t('app:month.unit'), + value: i + 1, + children: get24HoursOptions() + })); + }; + const getInterValOptions = () => { + // 每n小时 + return [ + { + label: t('app:interval.per_hour'), + value: 1 + }, + { + label: t('app:interval.2_hours'), + value: 2 + }, + { + label: t('app:interval.3_hours'), + value: 3 + }, + { + label: t('app:interval.4_hours'), + value: 4 + }, + { + label: t('app:interval.6_hours'), + value: 6 + }, + { + label: t('app:interval.12_hours'), + value: 12 + } + ]; + }; + + const cronField = cronString2Fields(cronString) as CronFieldType; + + const formatLabel = cronString2Label(cronString ?? '', t); + + const cronConfig2cronString = useCallback( + (e: CronFieldType) => { + const str = (() => { + if (e[0] === CronJobTypeEnum.month) { + return `0 ${e[2]} ${e[1]} * *`; + } else if (e[0] === CronJobTypeEnum.week) { + return `0 ${e[2]} * * ${e[1]}`; + } else if (e[0] === CronJobTypeEnum.day) { + return `0 ${e[1]} * * *`; + } else if (e[0] === CronJobTypeEnum.interval) { + return `0 */${e[1]} * * *`; + } else { + return ''; + } + })(); + onChange(str); + }, + [onChange] + ); + + const cronSelectList = useRef([ + { + label: t('app:cron.every_day'), + value: CronJobTypeEnum.day, + children: get24HoursOptions() + }, + { + label: t('app:cron.every_week'), + value: CronJobTypeEnum.week, + children: getWeekOptions() + }, + { + label: t('app:cron.every_month'), + value: CronJobTypeEnum.month, + children: getMonthOptions() + }, + { + label: t('app:cron.interval'), + value: CronJobTypeEnum.interval, + children: getInterValOptions() + } + ]); + + return ( + { + cronConfig2cronString(e as CronFieldType); + }} + changeOnEverySelect + /> + ); +}; + +export default React.memo(CronSelector); diff --git a/packages/web/components/common/MySelect/MultipleRowSelect.tsx b/packages/web/components/common/MySelect/MultipleRowSelect.tsx index 545938fb8c4d..e21af4abe390 100644 --- a/packages/web/components/common/MySelect/MultipleRowSelect.tsx +++ b/packages/web/components/common/MySelect/MultipleRowSelect.tsx @@ -14,7 +14,8 @@ export const MultipleRowSelect = ({ maxH = 300, onSelect, popDirection = 'bottom', - styles + styles, + changeOnEverySelect = false }: MultipleSelectProps) => { const { t } = useTranslation(); const ref = useRef(null); @@ -68,8 +69,12 @@ export const MultipleRowSelect = ({ } else { newValue[index] = item.value; setCloneValue(newValue); - if (!hasChildren) { + + if (changeOnEverySelect || !hasChildren) { onSelect(newValue); + } + + if (!hasChildren) { onClose(); } } diff --git a/packages/web/components/common/MySelect/index.tsx b/packages/web/components/common/MySelect/index.tsx index bbb9890ade9e..36a3eb59f191 100644 --- a/packages/web/components/common/MySelect/index.tsx +++ b/packages/web/components/common/MySelect/index.tsx @@ -18,9 +18,9 @@ import { Flex } from '@chakra-ui/react'; import type { ButtonProps, MenuItemProps } from '@chakra-ui/react'; -import { ChevronDownIcon } from '@chakra-ui/icons'; import MyIcon from '../Icon'; import { useRequest2 } from '../../../hooks/useRequest'; +import MyDivider from '../MyDivider'; export type SelectProps = ButtonProps & { value?: T; @@ -30,6 +30,7 @@ export type SelectProps = ButtonProps & { label: string | React.ReactNode; description?: string; value: T; + showBorder?: boolean; }[]; isLoading?: boolean; onchange?: (val: T) => any | Promise; @@ -59,8 +60,7 @@ const MySelect = ( display: 'flex', alignItems: 'center', _hover: { - backgroundColor: 'myGray.100', - color: 'primary.700' + backgroundColor: 'myGray.100' }, _notLast: { mb: 1 @@ -155,34 +155,37 @@ const MySelect = ( overflowY={'auto'} > {list.map((item, i) => ( - + { + if (onChange && value !== item.value) { + onChange(item.value); } - : { - color: 'myGray.900' - })} - onClick={() => { - if (onChange && value !== item.value) { - onChange(item.value); - } - }} - whiteSpace={'pre-wrap'} - fontSize={'sm'} - display={'block'} - > - {item.label} - {item.description && ( - - {item.description} - - )} - + }} + whiteSpace={'pre-wrap'} + fontSize={'sm'} + display={'block'} + > + {item.label} + {item.description && ( + + {item.description} + + )} + + {item.showBorder && } + ))} diff --git a/packages/web/components/common/MySelect/type.d.ts b/packages/web/components/common/MySelect/type.d.ts index 2b4831c6372b..10dc2de2a815 100644 --- a/packages/web/components/common/MySelect/type.d.ts +++ b/packages/web/components/common/MySelect/type.d.ts @@ -14,6 +14,7 @@ export type MultipleSelectProps = { onSelect: (val: any[]) => void; styles?: ButtonProps; popDirection?: 'top' | 'bottom'; + changeOnEverySelect?: boolean; }; export type MultipleArraySelectProps = Omit & { value?: any[][]; diff --git a/packages/web/core/workflow/constants.ts b/packages/web/core/workflow/constants.ts index 7fde865b5051..d3a8b55b394d 100644 --- a/packages/web/core/workflow/constants.ts +++ b/packages/web/core/workflow/constants.ts @@ -34,7 +34,7 @@ export const workflowNodeTemplateList = [ }, { type: FlowNodeTemplateTypeEnum.communication, - label: i18nT('app:workflow.template.communication'), + label: i18nT('common:workflow.template.communication'), list: [] }, { @@ -48,3 +48,36 @@ export const workflowNodeTemplateList = [ list: [] } ]; + +export const systemPluginTemplateList: { + typeId: string; + typeName: string; +}[] = [ + { + typeId: FlowNodeTemplateTypeEnum.tools, + typeName: i18nT('common:navbar.Tools') + }, + { + typeId: FlowNodeTemplateTypeEnum.search, + typeName: i18nT('common:common.Search') + }, + { + typeId: FlowNodeTemplateTypeEnum.multimodal, + typeName: i18nT('common:core.workflow.template.Multimodal') + }, + { + typeId: FlowNodeTemplateTypeEnum.communication, + typeName: i18nT('common:workflow.template.communication') + }, + { + typeId: FlowNodeTemplateTypeEnum.other, + typeName: i18nT('common:common.Other') + } +]; +export const defaultGroup = { + groupId: 'systemPlugin', + groupAvatar: 'common/navbar/pluginLight', + groupName: i18nT('common:core.module.template.System Plugin'), + groupOrder: 0, + groupTypes: systemPluginTemplateList +}; diff --git a/packages/web/hooks/useI18n.ts b/packages/web/hooks/useI18n.ts index 849064fc2170..aaf0617ff4cc 100644 --- a/packages/web/hooks/useI18n.ts +++ b/packages/web/hooks/useI18n.ts @@ -24,13 +24,19 @@ export const useI18nLng = () => { 'en-US': LangEnum.en }; - const onChangeLng = (lng: string) => { + const onChangeLng = async (lng: string) => { const lang = languageMap[lng] || 'en'; setCookie(LANG_KEY, lang, { expires: 30 }); - i18n?.changeLanguage(lang); + + const currentLng = i18n?.language; + await i18n?.changeLanguage?.(lang); + + if (currentLng !== lang) { + window?.location?.reload?.(); + } }; const setUserDefaultLng = () => { diff --git a/packages/web/i18n/en/account.json b/packages/web/i18n/en/account.json new file mode 100644 index 000000000000..aa449be12236 --- /dev/null +++ b/packages/web/i18n/en/account.json @@ -0,0 +1,12 @@ +{ + "api_key": "API key", + "bills_and_invoices": "Bills", + "confirm_logout": "Confirm to log out?", + "logout": "Sign out", + "notifications": "Notify", + "personal_information": "Personal", + "personalization": "Personalization", + "promotion_records": "Promotions", + "team": "Team", + "usage_records": "Usage" +} diff --git a/packages/web/i18n/en/account_apikey.json b/packages/web/i18n/en/account_apikey.json new file mode 100644 index 000000000000..47bb633b9e7a --- /dev/null +++ b/packages/web/i18n/en/account_apikey.json @@ -0,0 +1,3 @@ +{ + "key_tips": "You can use API keys to access some specific interfaces (you cannot access the application, you need to use the API key in the application to access the application)" +} \ No newline at end of file diff --git a/packages/web/i18n/en/account_bill.json b/packages/web/i18n/en/account_bill.json new file mode 100644 index 000000000000..ad5d61eb11c8 --- /dev/null +++ b/packages/web/i18n/en/account_bill.json @@ -0,0 +1,51 @@ +{ + "all": "all", + "back": "return", + "bank_account": "Account opening account", + "bank_name": "Bank of deposit", + "bill_detail": "Bill details", + "bill_record": "billing records", + "company_address": "Company address", + "company_phone": "Company phone number", + "completed": "Completed", + "confirm": "confirm", + "default_header": "Default header", + "detail": "Details", + "email_address": "Email address", + "extra_ai_points": "AI points calculation standard", + "extra_dataset_size": "Additional knowledge base capacity", + "generation_time": "Generation time", + "has_invoice": "Whether the invoice has been issued", + "invoice_amount": "Invoice amount", + "invoice_detail": "Invoice details", + "invoice_sending_info": "The invoice will be sent to your mailbox within 3-7 working days, please be patient.", + "mm": "mm", + "need_special_invoice": "Do you need a special ticket?", + "no": "no", + "no_invoice_record": "No bill record~", + "no_invoice_record_tip": "No invoicing record yet", + "order_number": "Order number", + "order_type": "Order type", + "organization_name": "Organization name", + "payment_method": "Payment method", + "save": "save", + "save_failed": "Save exception", + "save_success": "Saved successfully", + "status": "state", + "submit_failed": "Submission failed", + "submit_success": "Submission successful", + "submitted": "Submitted", + "subscription_mode_month": "by month", + "subscription_package": "Subscription package", + "subscription_period": "Subscription cycle", + "support_wallet_amount": "Amount", + "support_wallet_apply_invoice": "Billable bills", + "support_wallet_bill_tag_invoice": "bill invoice", + "support_wallet_invoicing": "Invoicing", + "time": "time", + "type": "type", + "unit_code": "unified credit code", + "update": "renew", + "yes": "yes", + "yuan": "¥{{amount}}" +} \ No newline at end of file diff --git a/packages/web/i18n/en/account_individuation.json b/packages/web/i18n/en/account_individuation.json new file mode 100644 index 000000000000..70faef92a7ac --- /dev/null +++ b/packages/web/i18n/en/account_individuation.json @@ -0,0 +1,6 @@ +{ + "language": "language", + "personalization": "personalization", + "timezone": "time zone", + "update_data_success": "Update data successfully" +} \ No newline at end of file diff --git a/packages/web/i18n/en/account_info.json b/packages/web/i18n/en/account_info.json new file mode 100644 index 000000000000..1d715432b9ab --- /dev/null +++ b/packages/web/i18n/en/account_info.json @@ -0,0 +1,83 @@ +{ + "account_duplicate": "account", + "account_knowledge_base_cleanup_warning": "When the free version team does not log in to the system for 30 consecutive days, the system will automatically clean up the account knowledge base.", + "active": "Taking effect", + "ai_points": "AI points", + "ai_points_calculation_standard": "AI points", + "ai_points_usage": "AI points", + "ai_points_usage_tip": "Each time the AI ​​model is called, a certain amount of AI points will be consumed. \nFor specific calculation standards, please refer to the \"Billing Standards\" above.", + "avatar": "Avatar", + "avatar_selection_exception": "Abnormal avatar selection", + "balance": "balance", + "billing_standard": "Standards", + "bind_notification_error": "Abnormal binding notification account", + "bind_notification_hint": "Please bind the notification receiving account to ensure that you can normally receive notifications such as package expiration reminders and ensure the normal operation of your services.", + "bind_notification_success": "Binding notification account successful", + "cancel": "Cancel", + "change": "change", + "choose_avatar": "Click to select avatar", + "click_modify_nickname": "Click to modify nickname", + "code_required": "Verification code cannot be empty", + "confirm": "confirm", + "confirm_password": "Confirm Password", + "contact_customer_service": "Contact customer service", + "contact_us": "Contact us", + "current_package": "Current plan", + "current_token_price": "Current points price", + "effective_time": "Effective time", + "email_label": "Mail", + "exchange": "Exchange", + "exchange_failure": "Redemption failed", + "exchange_success": "Redemption successful", + "expiration_time": "Expiration time", + "expired": "Expired", + "group": "Group", + "help_chatbot": "robot assistant", + "help_document": "Help documentation", + "knowledge_base_capacity": "Dataset usages", + "manage": "Manage", + "member_name": "Name", + "month": "moon", + "new_password": "New Password", + "notification_receiving": "Notify", + "notification_receiving_hint": "Notification reception", + "old_password": "Old Password", + "open_api_notice": "You can fill in the relevant key of OpenAI/OneAPI. \nIf you fill in this content, the online platform using [AI Dialogue], [Problem Classification] and [Content Extraction] will use the Key you filled in, and there will be no charge. \nPlease pay attention to whether your Key has permission to access the corresponding model. \nGPT models can choose FastAI.", + "openai_account_configuration": "OpenAI account configuration", + "openai_account_setting_exception": "Setting OpenAI account exception", + "package_and_usage": "Plans", + "package_details": "Details", + "package_expiry_time": "Expired", + "package_usage_rules": "Package usage rules: The system will give priority to using more advanced packages, and the original unused packages will take effect later.", + "password": "Password", + "password_length_error": "Password must be at least 4 characters and at most 60 characters", + "password_mismatch": "Password Inconsistency: Two passwords are inconsistent", + "password_update_error": "Exception when changing password", + "password_update_success": "Password changed successfully", + "pending_usage": "To be used", + "personal_information": "My info", + "phone_label": "Phone number", + "please_bind_notification_receiving_path": "Please bind the notification receiving method first", + "purchase_extra_package": "Upgrade", + "reminder_create_bound_notification_account": "Remind the creator to bind the notification account", + "request_address_notice": "Request address, default is openai official. \nThe forwarding address can be filled in, but \"v1\" is not automatically completed.", + "resource_usage": "Usages", + "select_avatar": "Click to select avatar", + "standard_package_and_extra_resource_package": "Includes standard and extra plans", + "storage_capacity": "Storage capacity", + "team_balance": "Balance", + "token_validity_period": "Points are valid for one year", + "tokens": "integral", + "type": "type", + "unlimited": "Unlimited", + "update_password": "Change password", + "update_success_tip": "Update data successfully", + "upgrade_package": "Upgrade", + "usage_balance": "Use balance: Use balance", + "usage_balance_notice": "Due to the system upgrade, the original \"automatic renewal and deduction from balance\" mode has been cancelled, and the balance recharge entrance has been closed. \nYour balance can be used to purchase points", + "user_account": "Username", + "user_team_team_name": "Team", + "verification_code_required": "Verification code cannot be empty", + "you_can_convert": "you can redeem", + "yuan": "Yuan" +} diff --git a/packages/web/i18n/en/account_inform.json b/packages/web/i18n/en/account_inform.json new file mode 100644 index 000000000000..e85843dfe392 --- /dev/null +++ b/packages/web/i18n/en/account_inform.json @@ -0,0 +1,4 @@ +{ + "no_notifications": "No notification yet", + "read": "Read" +} \ No newline at end of file diff --git a/packages/web/i18n/en/account_promotion.json b/packages/web/i18n/en/account_promotion.json new file mode 100644 index 000000000000..6162052bfe17 --- /dev/null +++ b/packages/web/i18n/en/account_promotion.json @@ -0,0 +1,13 @@ +{ + "amount": "Amount", + "cashback_ratio": "Cash back ratio", + "cashback_ratio_description": "When your friends recharge, you will receive a certain percentage of your balance as a reward.", + "copy_invite_link": "Copy invitation link", + "earnings": "Income (¥)", + "invite_url": "Invitation link", + "invite_url_tip": "Friends who register through this link will be permanently bound to you, and you will receive a certain balance reward when they recharge.\n \nIn addition, when your friends register using their mobile phone number, you will immediately receive a 5 yuan reward.\n \nRewards are sent to your default team.", + "no_invite_records": "No invitation record yet", + "time": "time", + "total_invited": "Cumulative number of invitees", + "type": "type" +} \ No newline at end of file diff --git a/packages/web/i18n/en/account_team.json b/packages/web/i18n/en/account_team.json new file mode 100644 index 000000000000..453b0a14f1ee --- /dev/null +++ b/packages/web/i18n/en/account_team.json @@ -0,0 +1,27 @@ +{ + "action": "operate", + "confirm_delete_group": "Confirm to delete group?", + "confirm_leave_team": "Confirmed to leave the team? \n \nAfter you log out, all your resources in the team (applications, knowledge bases, folders, managed groups, etc.) will be transferred to the team owner.", + "create_group": "Create group", + "delete": "delete", + "edit_info": "Edit information", + "group": "group", + "group_name": "Group name", + "label_sync": "Tag sync", + "leave_team_failed": "Leaving the team exception", + "manage_member": "Managing members", + "member": "member", + "member_group": "Belonging to member group", + "owner": "owner", + "permission": "Permissions", + "remove_tip": "Confirm to remove {{username}} from the team?", + "retain_admin_permissions": "Keep administrator rights", + "search_member_group_name": "Search member/group name", + "total_team_members": "{{amount}} members in total", + "transfer_ownership": "transfer owner", + "user_name": "username", + "user_team_invite_member": "Invite members", + "user_team_leave_team": "Leave the team", + "user_team_leave_team_failed": "Failure to leave the team", + "waiting": "To be accepted" +} diff --git a/packages/web/i18n/en/account_usage.json b/packages/web/i18n/en/account_usage.json new file mode 100644 index 000000000000..e8b2e4a3e007 --- /dev/null +++ b/packages/web/i18n/en/account_usage.json @@ -0,0 +1,23 @@ +{ + "ai_model": "AI model", + "all": "all", + "app_name": "Application name", + "billing_module": "Deduction module", + "details": "Details", + "duration_seconds": "Duration (seconds)", + "generation_time": "Generation time", + "member": "member", + "member_name": "Member name", + "module_name": "module name", + "month": "moon", + "no_usage_records": "No usage record yet", + "order_number": "Order number", + "project_name": "Project name", + "source": "source", + "text_length": "text length", + "token_length": "token length", + "total_points": "AI points consumption", + "total_points_consumed": "AI points consumption", + "usage_detail": "Usage details", + "user_type": "type" +} \ No newline at end of file diff --git a/packages/web/i18n/en/app.json b/packages/web/i18n/en/app.json index 713eedccbe59..0c5019654f87 100644 --- a/packages/web/i18n/en/app.json +++ b/packages/web/i18n/en/app.json @@ -16,6 +16,7 @@ "app_detail": "Application Details", "auto_execute": "Automatic execution", "auto_execute_default_prompt_placeholder": "Default questions sent when executing automatically", + "auto_execute_tip": "After turning it on, the workflow will be automatically triggered when the user enters the conversation interface. \nExecution order: 1. Dialogue starter; 2. Global variables; 3. Automatic execution.", "chat_debug": "Chat Preview", "chat_logs": "Conversation Logs", "chat_logs_tips": "Logs will record the online, shared, and API (requires chatId) conversation records of this app.", @@ -47,7 +48,6 @@ "file_recover": "File will overwrite current content", "file_upload": "File Upload", "file_upload_tip": "Once enabled, documents/images can be uploaded. Documents are retained for 7 days, images for 15 days. Using this feature may incur additional costs. To ensure a good experience, please choose an AI model with a larger context length when using this feature.", - "global_variables_desc": "Variable description", "go_to_chat": "Go to Conversation", "go_to_run": "Go to Execution", "image_upload": "Image Upload", @@ -86,7 +86,9 @@ "permission.des.manage": "Based on write permissions, you can configure publishing channels, view conversation logs, and assign permissions to the application.", "permission.des.read": "Use the app to have conversations", "permission.des.write": "Can view and edit apps", - "plugin_cost_per_times": "{{cost}}/time", + "plugin.Instructions": "Instructions", + "plugin_cost_by_token": "Charged based on token usage", + "plugin_cost_per_times": "{{cost}} points/time", "plugin_dispatch": "Plugin Invocation", "plugin_dispatch_tip": "Adds extra capabilities to the model. The specific plugins to be invoked will be autonomously decided by the model.\nIf a plugin is selected, the Dataset invocation will automatically be treated as a special plugin.", "publish_channel": "Publish Channel", @@ -105,7 +107,6 @@ "template.standard_template": "Standard template", "template.standard_template_des": "Standard prompt words for knowledge bases with unfixed structures.", "templateMarket.Search_template": "Search Template", - "templateMarket.Template_market": "Template Market", "templateMarket.Use": "Use", "templateMarket.no_intro": "No introduction yet~", "templateMarket.templateTags.Image_generation": "Image Generation", @@ -114,7 +115,7 @@ "templateMarket.templateTags.Roleplay": "Roleplay", "templateMarket.templateTags.Web_search": "Web Search", "templateMarket.templateTags.Writing": "Writing", - "template_market": "Template Market", + "template_market": "Templates", "template_market_description": "Explore more features in the template market, with configuration tutorials and usage guides to help you understand and get started with various applications.", "template_market_empty_data": "No suitable templates found", "time_zone": "Time Zone", diff --git a/packages/web/i18n/en/chat.json b/packages/web/i18n/en/chat.json index 5d5345485a13..fff5912945af 100644 --- a/packages/web/i18n/en/chat.json +++ b/packages/web/i18n/en/chat.json @@ -48,4 +48,4 @@ "upload": "Upload", "view_citations": "View References", "web_site_sync": "Web Site Sync" -} +} \ No newline at end of file diff --git a/packages/web/i18n/en/common.json b/packages/web/i18n/en/common.json index c594859a43b0..717352ac0d84 100644 --- a/packages/web/i18n/en/common.json +++ b/packages/web/i18n/en/common.json @@ -18,6 +18,7 @@ "FAQ.switch_package_a": "The package usage rule is to prioritize the use of higher-level packages. Therefore, if the newly purchased package is higher than the current package, the new package will take effect immediately; otherwise, the current package will continue to be used.", "FAQ.switch_package_q": "Will the subscription package be switched?", "Folder": "Folder", + "Instructions": "Instruction", "Login": "Login", "Move": "Move", "Name": "Name", @@ -25,6 +26,7 @@ "Rename": "Rename", "Resume": "Resume", "Running": "Running", + "Select_all": "Select all", "Submit": "Submit", "UnKnow": "Unknown", "Warning": "Warning", @@ -96,6 +98,7 @@ "common.Cancel": "Cancel", "common.Choose": "Choose", "common.Close": "Close", + "common.Code": "Code", "common.Config": "Configuration", "common.Confirm": "Confirm", "common.Confirm Create": "Confirm Creation", @@ -108,7 +111,7 @@ "common.Copy Successful": "Copied Successfully", "common.Copy_failed": "Copy Failed, Please Copy Manually", "common.Create Failed": "Creation Failed", - "common.Create New": "Create New", + "common.Create New": "Create", "common.Create Success": "Created Successfully", "common.Create Time": "Creation Time", "common.Creating": "Creating", @@ -128,6 +131,8 @@ "common.Expired Time": "Expiration Time", "common.File": "File", "common.Finish": "Finish", + "common.FullScreen": "FullScreen", + "common.FullScreenLight": "FullScreenLight", "common.Import": "Import", "common.Import failed": "Import Failed", "common.Import success": "Imported Successfully", @@ -135,7 +140,7 @@ "common.Input folder description": "Folder Description", "common.Input name": "Enter a Name", "common.Intro": "Introduction", - "common.Last Step": "Previous Step", + "common.Last Step": "Previous", "common.Last use time": "Last Use Time", "common.Load Failed": "Load Failed", "common.Loading": "Loading...", @@ -143,7 +148,7 @@ "common.Move": "Move", "common.MultipleRowSelect.No data": "No Data Available", "common.Name": "Name", - "common.Next Step": "Next Step", + "common.Next Step": "Next", "common.No more data": "No More Data", "common.Not open": "Not Open", "common.OK": "OK", @@ -156,6 +161,7 @@ "common.Permission": "Permission", "common.Permission_tip": "Individual permissions are greater than group permissions", "common.Please Input Name": "Please Enter a Name", + "common.Preview": "Preview", "common.Read document": "Read Document", "common.Read intro": "Read Introduction", "common.Remove": "Remove", @@ -212,7 +218,7 @@ "common.folder.Move to": "Move to", "common.folder.No Folder": "No Subdirectories, Place Here", "common.folder.Open folder": "Open Folder", - "common.folder.Root Path": "Root Directory", + "common.folder.Root Path": "Root", "common.folder.empty": "No More Items in This Directory", "common.folder.open_dataset": "Open Dataset", "common.have_done": "Completed", @@ -261,8 +267,8 @@ "core.ai.Not deploy rerank model": "Re-rank Model Not Deployed", "core.ai.Prompt": "Prompt", "core.ai.Support tool": "Function Call", - "core.ai.model.Dataset Agent Model": "File Processing Model", - "core.ai.model.Vector Model": "Index Model", + "core.ai.model.Dataset Agent Model": "File read model", + "core.ai.model.Vector Model": "Index model", "core.ai.model.doc_index_and_dialog": "Document Index & Dialog Index", "core.app.Ai response": "AI Response", "core.app.Api request": "API Request", @@ -492,6 +498,7 @@ "core.dataset.Start export": "Export Started", "core.dataset.Table collection": "Table Dataset", "core.dataset.Text collection": "Text Dataset", + "core.dataset.apiFile": "API File", "core.dataset.collection.Click top config website": "Click to Configure Website", "core.dataset.collection.Collection name": "Dataset Name", "core.dataset.collection.Collection raw text": "Dataset Content", @@ -506,7 +513,6 @@ "core.dataset.collection.metadata.Chunk Size": "Chunk Size", "core.dataset.collection.metadata.Createtime": "Creation Time", "core.dataset.collection.metadata.Raw text length": "Raw Text Length", - "core.dataset.collection.metadata.Training Type": "Training Mode", "core.dataset.collection.metadata.Updatetime": "Update Time", "core.dataset.collection.metadata.Web page selector": "Web Page Selector", "core.dataset.collection.metadata.metadata": "Metadata", @@ -549,7 +555,7 @@ "core.dataset.import.Auto process": "Automatic", "core.dataset.import.Auto process desc": "Automatically set segmentation and preprocessing rules", "core.dataset.import.Chunk Range": "Range: {{min}}~{{max}}", - "core.dataset.import.Chunk Split": "Direct Segmentation", + "core.dataset.import.Chunk Split": "Chunks", "core.dataset.import.Chunk Split Tip": "Segment the text according to certain rules and convert it into a format that can be semantically searched. Suitable for most scenarios. No additional model processing is required, and the cost is low.", "core.dataset.import.Continue upload": "Continue upload", "core.dataset.import.Custom process": "Custom Rules", @@ -579,10 +585,9 @@ "core.dataset.import.Select file": "Select File", "core.dataset.import.Select source": "Select Source", "core.dataset.import.Source name": "Source Name", - "core.dataset.import.Sources list": "Source List", + "core.dataset.import.Sources list": "Sources", "core.dataset.import.Start upload": "Start Upload", "core.dataset.import.Total files": "Total {{total}} Files", - "core.dataset.import.Training mode": "Training Mode", "core.dataset.import.Upload complete": "Upload complete", "core.dataset.import.Upload data": "Confirm Upload", "core.dataset.import.Upload file progress": "File Upload Progress", @@ -636,12 +641,12 @@ "core.dataset.test.test result placeholder": "Test results will be displayed here", "core.dataset.test.test result tip": "Sort based on the similarity between the Dataset content and the test text. You can adjust the corresponding text based on the test results.\nNote: The data in the test records may have been modified. Clicking on a test data will display the latest data.", "core.dataset.training.Agent queue": "QA Training Queue", - "core.dataset.training.Auto mode": "Auto Processing", + "core.dataset.training.Auto mode": "Auto index", "core.dataset.training.Auto mode Tip": "Increase the semantic richness of data blocks by generating related questions and summaries through sub-indexes and calling models, making it more conducive to retrieval. Requires more storage space and increases AI call times.", - "core.dataset.training.Chunk mode": "Direct Segmentation", + "core.dataset.training.Chunk mode": "Default", "core.dataset.training.Full": "Estimated Over 5 Minutes", "core.dataset.training.Leisure": "Idle", - "core.dataset.training.QA mode": "QA Split", + "core.dataset.training.QA mode": "QA Chunks", "core.dataset.training.Vector queue": "Index Queue", "core.dataset.training.Waiting": "Estimated 5 Minutes", "core.dataset.training.Website Sync": "Website Sync", @@ -736,7 +741,7 @@ "core.module.template.AI support tool tip": "Models that support function calls can better use tool calls.", "core.module.template.Basic Node": "Basic", "core.module.template.Query extension": "Question Optimization", - "core.module.template.System Plugin": "System", + "core.module.template.System Plugin": "System Plugin", "core.module.template.System input module": "System Input", "core.module.template.Team app": "Team", "core.module.template.Tool module": "Tool", @@ -832,6 +837,7 @@ "dataset.Create Folder": "Create Folder", "dataset.Create manual collection": "Create Manual Dataset", "dataset.Delete Dataset Error": "Delete Dataset Error", + "dataset.Edit API Service": "Edit API Service", "dataset.Edit Folder": "Edit Folder", "dataset.Edit Info": "Edit Information", "dataset.Export": "Export", @@ -899,8 +905,9 @@ "move.confirm": "Confirm move", "navbar.Account": "Account", "navbar.Chat": "Chat", - "navbar.Datasets": "Datasets", + "navbar.Datasets": "Dataset", "navbar.Studio": "Studio", + "navbar.Toolkit": "Toolkit", "navbar.Tools": "Tools", "new_create": "Create New", "no": "No", @@ -1091,14 +1098,14 @@ "support.wallet.subscription.mode.Year sale": "Two Months Free", "support.wallet.subscription.point": "Points", "support.wallet.subscription.rerank": "Result Re-rank", - "support.wallet.subscription.standardSubLevel.custom": "Custom Version", - "support.wallet.subscription.standardSubLevel.enterprise": "Enterprise Version", + "support.wallet.subscription.standardSubLevel.custom": "Custom", + "support.wallet.subscription.standardSubLevel.enterprise": "Enterprise", "support.wallet.subscription.standardSubLevel.enterprise_desc": "Suitable for small and medium-sized enterprises to build Dataset applications in production environments", - "support.wallet.subscription.standardSubLevel.experience": "Experience Version", + "support.wallet.subscription.standardSubLevel.experience": "Experience", "support.wallet.subscription.standardSubLevel.experience_desc": "Unlock the full functionality of FastGPT", - "support.wallet.subscription.standardSubLevel.free": "Free Version", + "support.wallet.subscription.standardSubLevel.free": "Free", "support.wallet.subscription.standardSubLevel.free desc": "Basic functions can be used for free every month. If the system is not logged in for 30 consecutive days, the Dataset will be automatically cleared.", - "support.wallet.subscription.standardSubLevel.team": "Team Version", + "support.wallet.subscription.standardSubLevel.team": "Team", "support.wallet.subscription.standardSubLevel.team_desc": "Suitable for small teams to build Dataset applications and provide external services", "support.wallet.subscription.status.active": "Active", "support.wallet.subscription.status.expired": "Expired", @@ -1215,6 +1222,7 @@ "user.team.role.writer": "writable member", "user.type": "Type", "verification": "Verification", + "workflow.template.communication": "Communication", "xx_search_result": "{{key}} Search Results", "yes": "Yes", "yesterday": "yesterday", diff --git a/packages/web/i18n/en/dataset.json b/packages/web/i18n/en/dataset.json index 7db8c2f9c6fc..07a490bd77cb 100644 --- a/packages/web/i18n/en/dataset.json +++ b/packages/web/i18n/en/dataset.json @@ -1,18 +1,28 @@ { "Enable": "Enable", + "add_file": "Import", + "api_file": "API Dataset", + "api_url": "API Url", "collection.Create update time": "Creation/Update Time", - "collection.Training type": "Training Mode", + "collection.Training type": "Training", + "collection_not_support_retraining": "This collection type does not support retuning parameters", + "collection_not_support_sync": "This collection does not support synchronization", + "collection_sync": "Sync data", + "collection_sync_confirm_tip": "Confirm to start synchronizing data? \nThe system will pull the latest data for comparison. If the contents are different, a new collection will be created and the old collection will be deleted. Please confirm!", "collection_tags": "Collection Tags", "common_dataset": "General Dataset", "common_dataset_desc": "Build a Dataset by importing files, web links, or manual input.", "confirm_to_rebuild_embedding_tip": "Are you sure you want to switch the index for the Dataset?\nSwitching the index is a significant operation that requires re-indexing all data in your Dataset, which may take a long time. Please ensure your account has sufficient remaining points.\n\nAdditionally, you need to update the applications that use this Dataset to avoid conflicts with other indexed model Datasets.", + "core.dataset.import.Adjust parameters": "Adjust parameters", "custom_data_process_params": "Custom", "custom_data_process_params_desc": "Customize data processing rules", "data.ideal_chunk_length": "ideal block length", - "data_process_params": "Processing parameters", - "data_process_setting": "Data processing configuration", + "data_process_params": "Params", + "data_process_setting": "Processing config", + "dataset.Unsupported operation": "dataset.Unsupported operation", "dataset.no_collections": "No datasets available", "dataset.no_tags": "No tags available", + "error.collectionNotFound": "Collection not found~", "external_file": "External File Library", "external_file_dataset_desc": "Import files from an external file library to build a Dataset. The files will not be stored again.", "external_id": "File Reading ID", @@ -32,6 +42,11 @@ "permission.des.write": "Ability to add and change knowledge base content", "rebuild_embedding_start_tip": "Index model switching task has started", "rebuilding_index_count": "Number of indexes being rebuilt: {{count}}", + "request_headers": "Request headers, will automatically append 'Bearer '", + "retain_collection": "Adjust Training Parameters", + "retrain_task_submitted": "The retraining task has been submitted", + "same_api_collection": "The same API set exists", + "start_sync_website_tip": "Confirm to start synchronizing data? \nThe old data will be deleted and retrieved again, please confirm!", "tag.Add New": "Add New", "tag.Add_new_tag": "Add New Tag", "tag.Edit_tag": "Edit Tag", diff --git a/packages/web/i18n/en/publish.json b/packages/web/i18n/en/publish.json index c1dd0f127470..69925a1bab37 100644 --- a/packages/web/i18n/en/publish.json +++ b/packages/web/i18n/en/publish.json @@ -24,10 +24,8 @@ "publish_name": "Name", "qpm_is_empty": "QPM cannot be empty", "qpm_tips": "Maximum number of queries per minute per IP", - "quote_content": "Quote content", "request_address": "Request URL", "show_node": "real-time running status", - "show_origin_content": "View original source", "show_share_link_modal_title": "Get Started", "token_auth": "Token Authentication", "token_auth_tips": "Token authentication server URL. If provided, a request will be sent to the specified server for authentication before each conversation.", @@ -38,4 +36,4 @@ "wecom.create_modal_title": "Create WeCom Bot", "wecom.edit_modal_title": "Edit WeCom Bot", "wecom.title": "Publish to WeCom Bot" -} +} \ No newline at end of file diff --git a/packages/web/i18n/en/user.json b/packages/web/i18n/en/user.json index 6a59c1f963f8..354c8aa325c4 100644 --- a/packages/web/i18n/en/user.json +++ b/packages/web/i18n/en/user.json @@ -17,9 +17,6 @@ "bill.valid_time": "Effective Time", "bill.you_can_convert": "You can convert", "bill.yuan": "Yuan", - "bill_and_invoices": "Bill & Invoice", - "bind_inform_account_error": "Failed to Bind Notification Account", - "bind_inform_account_success": "Notification Account Bound Successfully", "delete.admin_failed": "Failed to Delete Admin", "delete.admin_success": "Admin Deleted Successfully", "delete.failed": "Delete failed", @@ -29,8 +26,8 @@ "login.error": "Login Error", "login.password_condition": "Password can be up to 60 characters", "login.success": "Login Successful", + "manage_team": "Manage team", "name": "Name", - "notice": "Notice", "notification.Bind Notification Pipe Hint": "Please bind a notification receiving account to ensure you receive notifications such as plan expiration reminders, ensuring your service runs smoothly.", "notification.remind_owner_bind": "Please remind the creator to bind a notification account", "operations": "Actions", @@ -73,11 +70,19 @@ "register.success": "Registration Successful", "register.to_login": "Already have an account? Login", "search_user": "Search Username", - "sign_out": "Sign out", + "sso_auth_failed": "SSO authentication failed", "synchronization.button": "Sync Now", "synchronization.placeholder": "Enter Sync Tag", "synchronization.title": "Enter the sync tag link and click the sync button to synchronize", "team.Add manager": "Add Admin", + "team.Confirm Invite": "Confirm invitation", + "team.Create Team": "Create new team", + "team.Invite Member Failed Tip": "An exception occurred when inviting members", + "team.Invite Member Result Tip": "Invitation result prompt", + "team.Invite Member Success Tip": "Invite members to complete\n\nSuccess: {{success}} people\n\nInvalid username: {{inValid}}\n\nAlready in team: {{inTeam}}", + "team.Set Name": "Give the team a name", + "team.Team Name": "Team name", + "team.Update Team": "Update team information", "team.add_collaborator": "Add Collaborator", "team.add_writer": "Add writable members", "team.avatar_and_name": "avatar", @@ -111,6 +116,5 @@ "usage.feishu": "Feishu", "usage.official_account": "Official Account", "usage.share": "Share Link", - "usage.wecom": "WeCom", - "usage_record": "Usages" + "usage.wecom": "WeCom" } diff --git a/packages/web/i18n/en/workflow.json b/packages/web/i18n/en/workflow.json index 7940b4b44374..c43dae1d8ff7 100644 --- a/packages/web/i18n/en/workflow.json +++ b/packages/web/i18n/en/workflow.json @@ -39,7 +39,6 @@ "dataset_quote_role_tip": "When set to System, the knowledge base reference content will be placed in the system message, which can ensure the continuity of the history record, but the constraint effect may not be good.\n\nWhen set to User, the knowledge base reference content will be placed in the user message, and the {{question}} variable location needs to be specified. \nIt will have a certain impact on the consistency of historical records, but usually the constraint effect is better.", "dataset_quote_role_user_option_desc": "Strong constraints take precedence", "dynamic_input_description": "Receive the output value of the previous node as a variable, which can be used by Laf request parameters.", - "dynamic_input_description_concat": "You can reference the output of other nodes as variables for text concatenation. Type / to invoke the variable list.", "edit_input": "Edit Input", "edit_output": "Edit output", "end_with": "Ends With", @@ -111,7 +110,6 @@ "loop_input_array": "array", "loop_result": "Array execution results", "loop_start": "The loop body begins", - "loop_start_tip": "Not input array", "max_dialog_rounds": "Maximum Number of Dialog Rounds", "max_tokens": "Maximum Tokens", "mouse_priority": "Mouse first\n- Press the left button to drag the canvas\n- Hold down shift and left click to select batches", @@ -195,4 +193,4 @@ "workflow.Switch_success": "Switch Successful", "workflow.Team cloud": "Team Cloud", "workflow.exit_tips": "Your changes have not been saved. 'Exit directly' will not save your edits." -} +} \ No newline at end of file diff --git a/packages/web/i18n/zh-CN/account.json b/packages/web/i18n/zh-CN/account.json new file mode 100644 index 000000000000..5e4b488476c2 --- /dev/null +++ b/packages/web/i18n/zh-CN/account.json @@ -0,0 +1,12 @@ +{ + "personal_information": "个人信息", + "usage_records": "使用记录", + "bills_and_invoices": "账单与发票", + "promotion_records": "促销记录", + "api_key": "API 密钥", + "personalization": "个性化", + "notifications": "通知", + "logout": "登出", + "confirm_logout": "确认退出登录?", + "team": "团队管理" +} diff --git a/packages/web/i18n/zh-CN/account_apikey.json b/packages/web/i18n/zh-CN/account_apikey.json new file mode 100644 index 000000000000..2db97fcc9004 --- /dev/null +++ b/packages/web/i18n/zh-CN/account_apikey.json @@ -0,0 +1,3 @@ +{ + "key_tips": "你可以使用 API 密钥访问一些特定的接口(无法访问应用,访问应用需使用应用内的 API key)" +} \ No newline at end of file diff --git a/packages/web/i18n/zh-CN/account_bill.json b/packages/web/i18n/zh-CN/account_bill.json new file mode 100644 index 000000000000..d2039acf3797 --- /dev/null +++ b/packages/web/i18n/zh-CN/account_bill.json @@ -0,0 +1,51 @@ +{ + "all": "全部", + "back": "返回", + "bank_account": "开户账号", + "bank_name": "开户银行", + "bill_detail": "账单详情", + "bill_record": "账单记录", + "company_address": "公司地址", + "company_phone": "公司电话", + "completed": "已完成", + "confirm": "确认", + "default_header": "默认抬头", + "detail": "详情", + "email_address": "邮箱地址", + "extra_ai_points": "额外 AI 积分", + "extra_dataset_size": "额外知识库容量", + "generation_time": "生成时间", + "has_invoice": "是否已开票", + "invoice_amount": "开票金额", + "invoice_detail": "发票详情", + "invoice_sending_info": "发票将在 3-7 个工作日内发送至邮箱,请耐心等待", + "mm": "毫米", + "need_special_invoice": "是否需要专票", + "no": "否", + "no_invoice_record": "无账单记录~", + "no_invoice_record_tip": "暂无开票记录", + "order_number": "订单号", + "order_type": "订单类型", + "organization_name": "组织名称", + "payment_method": "支付方式", + "save": "保存", + "save_failed": "保存异常", + "save_success": "保存成功", + "status": "状态", + "submit_failed": "提交失败", + "submit_success": "提交成功", + "submitted": "已提交", + "subscription_mode_month": "按月", + "subscription_package": "订阅套餐", + "subscription_period": "订阅周期", + "support_wallet_amount": "金额", + "support_wallet_apply_invoice": "可开票账单", + "support_wallet_bill_tag_invoice": "账单发票", + "support_wallet_invoicing": "开票", + "time": "时间", + "type": "类型", + "unit_code": "统一信用代码", + "update": "更新", + "yes": "是", + "yuan": "{{amount}}元" +} diff --git a/packages/web/i18n/zh-CN/account_individuation.json b/packages/web/i18n/zh-CN/account_individuation.json new file mode 100644 index 000000000000..38a6935a9bd9 --- /dev/null +++ b/packages/web/i18n/zh-CN/account_individuation.json @@ -0,0 +1,6 @@ +{ + "update_data_success": "更新数据成功", + "personalization": "个性化", + "language": "语言", + "timezone": "时区" +} \ No newline at end of file diff --git a/packages/web/i18n/zh-CN/account_info.json b/packages/web/i18n/zh-CN/account_info.json new file mode 100644 index 000000000000..06bee0c7d9f0 --- /dev/null +++ b/packages/web/i18n/zh-CN/account_info.json @@ -0,0 +1,83 @@ +{ + "account_duplicate": "账号", + "account_knowledge_base_cleanup_warning": "免费版团队连续 30 天未登录系统时,系统会自动清理账号知识库。", + "active": "生效中", + "ai_points": "AI 积分", + "ai_points_calculation_standard": "AI 积分", + "ai_points_usage": "AI 积分使用量", + "ai_points_usage_tip": "每次调用 AI 模型时,都会消耗一定的 AI 积分。具体的计算标准可参考上方的“计费标准”。", + "avatar": "头像", + "avatar_selection_exception": "头像选择异常", + "balance": "余额", + "billing_standard": "计费标准", + "bind_notification_error": "绑定通知账号异常", + "bind_notification_hint": "请绑定通知接收账号,以确保您能正常接收套餐过期提醒等通知,保障您的服务正常运行。", + "bind_notification_success": "绑定通知账号成功", + "cancel": "取消", + "change": "变更", + "choose_avatar": "点击选择头像", + "click_modify_nickname": "点击修改昵称", + "code_required": "验证码不能为空", + "confirm": "确认", + "confirm_password": "确认密码", + "contact_customer_service": "联系客服", + "contact_us": "联系我们", + "current_package": "当前套餐", + "current_token_price": "当前积分价格", + "effective_time": "生效时间", + "email_label": "邮箱", + "exchange": "兑换", + "exchange_failure": "兑换失败", + "exchange_success": "兑换成功", + "expiration_time": "到期时间", + "expired": "已过期", + "group": "组", + "help_chatbot": "机器人助手", + "help_document": "帮助文档", + "knowledge_base_capacity": "知识库容量", + "manage": "管理", + "member_name": "成员名", + "month": "月", + "new_password": "新密码", + "notification_receiving": "通知接收", + "notification_receiving_hint": "通知接收", + "old_password": "旧密码", + "open_api_notice": "可以填写 OpenAI/OneAPI 的相关密钥。如果你填写了该内容,在线上平台使用【 AI 对话】、【问题分类】和【内容提取】将会走你填写的 Key,不会计费。请注意你的 Key 是否有访问对应模型的权限。 GPT 模型可以选择 FastAI 。", + "openai_account_configuration": "OpenAI 账号配置", + "openai_account_setting_exception": "设置 OpenAI 账号异常", + "package_and_usage": "套餐与用量", + "package_details": "套餐详情", + "package_expiry_time": "套餐到期时间", + "package_usage_rules": "套餐使用规则:系统优先使用更高级的套餐,原未用完的套餐将延后生效", + "password": "密码", + "password_length_error": "密码最少 4 位最多 60 位", + "password_mismatch": "密码不一致: 两次密码不一致", + "password_update_error": "修改密码异常", + "password_update_success": "修改密码成功", + "pending_usage": "待使用", + "personal_information": "个人信息", + "phone_label": "手机号", + "please_bind_notification_receiving_path": "请先绑定通知接收途径", + "purchase_extra_package": "购买额外套餐", + "reminder_create_bound_notification_account": "提醒创建者绑定通知账号", + "request_address_notice": "请求地址,默认为 openai 官方。可填中转地址,未自动补全 \"v1\"", + "resource_usage": "资源用量", + "select_avatar": "点击选择头像", + "standard_package_and_extra_resource_package": "包含标准套餐与额外资源包", + "storage_capacity": "存储量", + "team_balance": "团队余额", + "token_validity_period": "积分有效期一年", + "tokens": "积分", + "type": "类型", + "unlimited": "无限制", + "update_password": "修改密码", + "update_success_tip": "更新数据成功", + "upgrade_package": "升级套餐", + "usage_balance": "使用余额: 使用余额", + "usage_balance_notice": "由于系统升级,原“自动续费从余额扣款”模式取消,余额充值入口关闭。您的余额可用于购买积分", + "user_account": "账号", + "user_team_team_name": "团队名", + "verification_code_required": "验证码不能为空", + "you_can_convert": "您可以兑换", + "yuan": "元" +} diff --git a/packages/web/i18n/zh-CN/account_inform.json b/packages/web/i18n/zh-CN/account_inform.json new file mode 100644 index 000000000000..3c8888271a30 --- /dev/null +++ b/packages/web/i18n/zh-CN/account_inform.json @@ -0,0 +1,4 @@ +{ + "read": "已读", + "no_notifications": "暂无通知" +} \ No newline at end of file diff --git a/packages/web/i18n/zh-CN/account_promotion.json b/packages/web/i18n/zh-CN/account_promotion.json new file mode 100644 index 000000000000..2e512e23c568 --- /dev/null +++ b/packages/web/i18n/zh-CN/account_promotion.json @@ -0,0 +1,13 @@ +{ + "total_invited": "累计邀请人数", + "earnings": "收益(¥)", + "cashback_ratio": "返现比例", + "cashback_ratio_description": "好友充值时你将获得一定比例的余额奖励", + "invite_url": "邀请链接", + "invite_url_tip": "通过该链接注册的好友将永久与你绑定,其充值时你会获得一定余额奖励。\n 此外,好友使用手机号注册时,你将立即获得 5 元奖励。\n 奖励会发送到您的默认团队中。", + "copy_invite_link": "复制邀请链接", + "time": "时间", + "type": "类型", + "amount": "金额", + "no_invite_records": "暂无邀请记录" +} \ No newline at end of file diff --git a/packages/web/i18n/zh-CN/account_team.json b/packages/web/i18n/zh-CN/account_team.json new file mode 100644 index 000000000000..720dff39f1ab --- /dev/null +++ b/packages/web/i18n/zh-CN/account_team.json @@ -0,0 +1,29 @@ +{ + "total_team_members": "共 {{amount}} 名成员", + "member": "成员", + "group": "群组", + "permission": "权限", + "user_name": "用户名", + "member_group": "所属成员组", + "action": "操作", + "waiting": "待接受", + "remove_tip": "确认将 {{username}} 移出团队?", + + "confirm_leave_team": "确认离开该团队? \n 退出后,您在该团队所有的资源( 应用、知识库、文件夹、管理的群组等)均转让给团队所有者。", + "leave_team_failed": "离开团队异常", + "label_sync": "标签同步", + "user_team_invite_member": "邀请成员", + "user_team_leave_team": "离开团队", + "user_team_leave_team_failed": "离开团队失败", + "create_group": "创建群组", + "search_member_group_name": "搜索成员/群组名称", + "confirm_delete_group": "确认删除群组?", + "group_name": "群组名称", + "owner": "所有者", + "manage_member": "管理成员", + "edit_info": "编辑信息", + + "transfer_ownership": "转让所有者", + "delete": "删除", + "retain_admin_permissions": "保留管理员权限" +} diff --git a/packages/web/i18n/zh-CN/account_usage.json b/packages/web/i18n/zh-CN/account_usage.json new file mode 100644 index 000000000000..84a132fd3711 --- /dev/null +++ b/packages/web/i18n/zh-CN/account_usage.json @@ -0,0 +1,23 @@ +{ + "usage_detail": "使用详情", + "order_number": "订单号", + "generation_time": "生成时间", + "month": "月", + "app_name": "应用名", + "source": "来源", + "total_points_consumed": "AI 积分消耗", + "billing_module": "扣费模块", + "module_name": "模块名", + "ai_model": "AI 模型", + "token_length": "token 长度", + "text_length": "文本长度", + "duration_seconds": "时长(秒)", + "all": "所有", + "member": "成员", + "member_name": "成员名", + "user_type": "类型", + "project_name": "项目名", + "total_points": "AI 积分消耗", + "details": "详情", + "no_usage_records": "暂无使用记录" +} \ No newline at end of file diff --git a/packages/web/i18n/zh-CN/app.json b/packages/web/i18n/zh-CN/app.json index ce3341b945f0..da12548f8d02 100644 --- a/packages/web/i18n/zh-CN/app.json +++ b/packages/web/i18n/zh-CN/app.json @@ -48,7 +48,6 @@ "file_recover": "文件将覆盖当前内容", "file_upload": "文件上传", "file_upload_tip": "开启后,可以上传文档/图片。文档保留7天,图片保留15天。使用该功能可能产生较多额外费用。为保证使用体验,使用该功能时,请选择上下文长度较大的AI模型。", - "global_variables_desc": "变量描述", "go_to_chat": "去对话", "go_to_run": "去运行", "image_upload": "图片上传", @@ -87,7 +86,9 @@ "permission.des.manage": "写权限基础上,可配置发布渠道、查看对话日志、分配该应用权限", "permission.des.read": "可使用该应用进行对话", "permission.des.write": "可查看和编辑应用", - "plugin_cost_per_times": "{{cost}}/次", + "plugin.Instructions": "使用说明", + "plugin_cost_by_token": "依据 token 消耗计费", + "plugin_cost_per_times": "{{cost}} 积分/次", "plugin_dispatch": "插件调用", "plugin_dispatch_tip": "给模型附加获取外部数据的能力,具体调用哪些插件,将由模型自主决定,所有插件都将以非流模式运行。\n若选择了插件,知识库调用将自动作为一个特殊的插件。", "publish_channel": "发布渠道", @@ -106,7 +107,6 @@ "template.standard_template": "标准模板", "template.standard_template_des": "标准提示词,用于结构不固定的知识库。", "templateMarket.Search_template": "搜索模板", - "templateMarket.Template_market": "模板市场", "templateMarket.Use": "使用", "templateMarket.no_intro": "还没有介绍~", "templateMarket.templateTags.Image_generation": "图片生成", diff --git a/packages/web/i18n/zh-CN/chat.json b/packages/web/i18n/zh-CN/chat.json index bf3d21e0e373..7f2ee3add7c0 100644 --- a/packages/web/i18n/zh-CN/chat.json +++ b/packages/web/i18n/zh-CN/chat.json @@ -48,4 +48,4 @@ "upload": "上传", "view_citations": "查看引用", "web_site_sync": "Web站点同步" -} +} \ No newline at end of file diff --git a/packages/web/i18n/zh-CN/common.json b/packages/web/i18n/zh-CN/common.json index 47900073e3b0..e8113e7c98b4 100644 --- a/packages/web/i18n/zh-CN/common.json +++ b/packages/web/i18n/zh-CN/common.json @@ -18,6 +18,7 @@ "FAQ.switch_package_a": "套餐使用规则为优先使用更高级的套餐,因此,购买的新套餐若比当前套餐更高级,则新套餐立即生效:否则将继续使用当前套餐。", "FAQ.switch_package_q": "是否切换订阅套餐?", "Folder": "文件夹", + "Instructions": "使用说明", "Login": "登录", "Move": "移动", "Name": "名称", @@ -25,6 +26,7 @@ "Rename": "重命名", "Resume": "恢复", "Running": "运行中", + "Select_all": "全选", "Submit": "提交", "UnKnow": "未知", "Warning": "提示", @@ -96,6 +98,7 @@ "common.Cancel": "取消", "common.Choose": "选择", "common.Close": "关闭", + "common.Code": "源码", "common.Config": "配置", "common.Confirm": "确认", "common.Confirm Create": "确认创建", @@ -128,6 +131,8 @@ "common.Expired Time": "过期时间", "common.File": "文件", "common.Finish": "完成", + "common.FullScreen": "全屏", + "common.FullScreenLight": "全屏预览", "common.Import": "导入", "common.Import failed": "导入失败", "common.Import success": "导入成功", @@ -156,6 +161,7 @@ "common.Permission": "权限", "common.Permission_tip": "个人权限大于群组权限", "common.Please Input Name": "请输入名称", + "common.Preview": "预览", "common.Read document": "查看文档", "common.Read intro": "查看说明", "common.Remove": "移除", @@ -339,7 +345,7 @@ "core.app.schedule.Default prompt placeholder": "执行应用时的默认问题", "core.app.schedule.Every day": "每天 {{hour}}:00", "core.app.schedule.Every month": "每月 {{day}} 号 {{hour}}:00", - "core.app.schedule.Every week": "每周{{day}} {{hour}}:00", + "core.app.schedule.Every week": "每周 {{day}} {{hour}}:00", "core.app.schedule.Interval": "每 {{interval}} 小时", "core.app.schedule.Open schedule": "定时执行", "core.app.setting": "应用信息设置", @@ -491,6 +497,7 @@ "core.dataset.Start export": "已开始导出", "core.dataset.Table collection": "表格数据集", "core.dataset.Text collection": "文本数据集", + "core.dataset.apiFile": "API 文件", "core.dataset.collection.Click top config website": "点击配置网站", "core.dataset.collection.Collection name": "数据集名称", "core.dataset.collection.Collection raw text": "数据集内容", @@ -505,7 +512,6 @@ "core.dataset.collection.metadata.Chunk Size": "分割大小", "core.dataset.collection.metadata.Createtime": "创建时间", "core.dataset.collection.metadata.Raw text length": "原文长度", - "core.dataset.collection.metadata.Training Type": "训练模式", "core.dataset.collection.metadata.Updatetime": "更新时间", "core.dataset.collection.metadata.Web page selector": "网站选择器", "core.dataset.collection.metadata.metadata": "元数据", @@ -581,7 +587,6 @@ "core.dataset.import.Sources list": "来源列表", "core.dataset.import.Start upload": "开始上传", "core.dataset.import.Total files": "共 {{total}} 个文件", - "core.dataset.import.Training mode": "训练模式", "core.dataset.import.Upload complete": "完成上传", "core.dataset.import.Upload data": "确认上传", "core.dataset.import.Upload file progress": "文件上传进度", @@ -831,6 +836,7 @@ "dataset.Create Folder": "创建文件夹", "dataset.Create manual collection": "创建手动数据集", "dataset.Delete Dataset Error": "删除知识库异常", + "dataset.Edit API Service": "编辑 API 文件接口", "dataset.Edit Folder": "编辑文件夹", "dataset.Edit Info": "编辑信息", "dataset.Export": "导出", @@ -900,6 +906,7 @@ "navbar.Chat": "聊天", "navbar.Datasets": "知识库", "navbar.Studio": "工作台", + "navbar.Toolkit": "工具箱", "navbar.Tools": "工具", "new_create": "新建", "no": "否", @@ -1173,12 +1180,8 @@ "user.password_message": "密码最少 4 位最多 60 位", "user.team.Balance": "团队余额", "user.team.Check Team": "切换", - "user.team.Confirm Invite": "确认邀请", - "user.team.Create Team": "创建新团队", + "user.team.Invite Member": "邀请成员", - "user.team.Invite Member Failed Tip": "邀请成员出现异常", - "user.team.Invite Member Result Tip": "邀请结果提示", - "user.team.Invite Member Success Tip": "邀请成员完成\n成功:{{success}} 人\n用户名无效:{{inValid}}\n已在团队中:{{inTeam}}", "user.team.Invite Member Tips": "对方可查阅或使用团队内的其他资源", "user.team.Leave Team": "离开团队", "user.team.Leave Team Failed": "离开团队异常", @@ -1190,13 +1193,13 @@ "user.team.Processing invitations Tips": "你有 {{amount}} 个需要处理的团队邀请", "user.team.Remove Member Confirm Tip": "确认将 {{username}} 移出团队?", "user.team.Select Team": "团队选择", - "user.team.Set Name": "给团队取个名字", + "user.team.Switch Team Failed": "切换团队异常", "user.team.Tags Async": "保存", - "user.team.Team Name": "团队名", + "user.team.Team Tags Async": "标签同步", "user.team.Team Tags Async Success": "链接报错成功,标签信息更新", - "user.team.Update Team": "更新团队信息", + "user.team.invite.Accept Confirm": "确认加入该团队?", "user.team.invite.Accepted": "已加入团队", "user.team.invite.Deal Width Footer Tip": "处理完会自动关闭噢~", @@ -1214,6 +1217,7 @@ "user.team.role.writer": "可写成员", "user.type": "类型", "verification": "验证", + "workflow.template.communication": "通信", "xx_search_result": "{{key}} 的搜索结果", "yes": "是", "yesterday": "昨天", diff --git a/packages/web/i18n/zh-CN/dataset.json b/packages/web/i18n/zh-CN/dataset.json index 6d1429452614..eba4614fe281 100644 --- a/packages/web/i18n/zh-CN/dataset.json +++ b/packages/web/i18n/zh-CN/dataset.json @@ -1,18 +1,28 @@ { "Enable": "启用", + "add_file": "添加文件", + "api_file": "API 文件库", + "api_url": "接口地址", "collection.Create update time": "创建/更新时间", "collection.Training type": "训练模式", + "collection_not_support_retraining": "该集合类型不支持重新调整参数", + "collection_not_support_sync": "该集合不支持同步", + "collection_sync": "立即同步", + "collection_sync_confirm_tip": "确认开始同步数据?系统将会拉取最新数据进行比较,如果内容不相同,则会创建一个新的集合并删除旧的集合,请确认!", "collection_tags": "集合标签", "common_dataset": "通用知识库", "common_dataset_desc": "可通过导入文件、网页链接或手动录入形式构建知识库", "confirm_to_rebuild_embedding_tip": "确认为知识库切换索引?\n切换索引是一个非常重量的操作,需要对您知识库内所有数据进行重新索引,时间可能较长,请确保账号内剩余积分充足。\n\n此外,你还需要注意修改选择该知识库的应用,避免它们与其他索引模型知识库混用。", + "core.dataset.import.Adjust parameters": "调整参数", "custom_data_process_params": "自定义", "custom_data_process_params_desc": "自定义设置数据处理规则", "data.ideal_chunk_length": "理想分块长度", "data_process_params": "处理参数", "data_process_setting": "数据处理配置", + "dataset.Unsupported operation": "操作不支持", "dataset.no_collections": "暂无数据集", "dataset.no_tags": "暂无标签", + "error.collectionNotFound": "集合找不到了~", "external_file": "外部文件库", "external_file_dataset_desc": "可以从外部文件库导入文件构建知识库,文件不会进行二次存储", "external_id": "文件阅读 ID", @@ -32,6 +42,11 @@ "permission.des.write": "可增加和变更知识库内容", "rebuild_embedding_start_tip": "切换索引模型任务已开始", "rebuilding_index_count": "重建中索引数量:{{count}}", + "request_headers": "请求头参数,会自动补充 Bearer", + "retain_collection": "调整训练参数", + "retrain_task_submitted": "重新训练任务已提交", + "same_api_collection": "存在相同的 API 集合", + "start_sync_website_tip": "确认开始同步数据?将会删除旧数据后重新获取,请确认!", "tag.Add New": "新建", "tag.Add_new_tag": "新建标签", "tag.Edit_tag": "编辑标签", @@ -46,4 +61,4 @@ "training_mode": "处理方式", "website_dataset": "Web 站点同步", "website_dataset_desc": "Web 站点同步允许你直接使用一个网页链接构建知识库" -} \ No newline at end of file +} diff --git a/packages/web/i18n/zh-CN/publish.json b/packages/web/i18n/zh-CN/publish.json index e24230697e7e..99ef17803928 100644 --- a/packages/web/i18n/zh-CN/publish.json +++ b/packages/web/i18n/zh-CN/publish.json @@ -24,10 +24,8 @@ "publish_name": "名称", "qpm_is_empty": "QPM 不能为空", "qpm_tips": "每个 IP 每分钟最多提问多少次", - "quote_content": "知识库引用", "request_address": "请求地址", "show_node": "实时运行状态", - "show_origin_content": "查看来源原文", "show_share_link_modal_title": "开始使用", "token_auth": "身份验证", "token_auth_tips": "身份校验服务器地址", @@ -38,4 +36,4 @@ "wecom.create_modal_title": "创建企微机器人", "wecom.edit_modal_title": "编辑企微机器人", "wecom.title": "发布到企业微信机器人" -} +} \ No newline at end of file diff --git a/packages/web/i18n/zh-CN/user.json b/packages/web/i18n/zh-CN/user.json index 3e74e5328826..2430feec5a17 100644 --- a/packages/web/i18n/zh-CN/user.json +++ b/packages/web/i18n/zh-CN/user.json @@ -17,9 +17,6 @@ "bill.valid_time": "生效时间", "bill.you_can_convert": "您可兑换", "bill.yuan": "元", - "bill_and_invoices": "账单 & 发票", - "bind_inform_account_error": "绑定通知账号异常", - "bind_inform_account_success": "绑定通知账号成功", "delete.admin_failed": "删除管理员失败", "delete.admin_success": "删除管理员成功", "delete.failed": "删除失败", @@ -29,11 +26,12 @@ "login.error": "登录异常", "login.password_condition": "密码最多 60 位", "login.success": "登录成功", + "manage_team": "管理团队", "name": "名称", - "notice": "通知", "notification.Bind Notification Pipe Hint": "请绑定通知接收账号,以确保您能正常接收套餐过期提醒等通知,保障您的服务正常运行。", "notification.remind_owner_bind": "请提醒创建者绑定通知账号", "operations": "操作", + "owner": "所有者", "password.code_required": "验证码不能为空", "password.code_send_error": "验证码发送异常", "password.code_sended": "验证码已发送", @@ -72,14 +70,23 @@ "register.success": "注册成功", "register.to_login": "已有账号,去登录", "search_user": "搜索用户名", - "sign_out": "登出", + "sso_auth_failed": "SSO 鉴权失败", "synchronization.button": "立即同步", "synchronization.placeholder": "请输入同步标签", "synchronization.title": "填写标签同步链接,点击同步按钮即可同步", "team.Add manager": "添加管理员", + "team.Confirm Invite": "确认邀请", + "team.Create Team": "创建新团队", + "team.Invite Member Failed Tip": "邀请成员出现异常", + "team.Invite Member Result Tip": "邀请结果提示", + "team.Invite Member Success Tip": "邀请成员完成\n成功:{{success}} 人\n用户名无效:{{inValid}}\n已在团队中:{{inTeam}}", + "team.Set Name": "给团队取个名字", + "team.Team Name": "团队名", + "team.Update Team": "更新团队信息", "team.add_collaborator": "添加协作者", "team.add_writer": "添加可写成员", "team.avatar_and_name": "头像 & 名称", + "team.belong_to_group": "所属成员组", "team.group.avatar": "群头像", "team.group.create": "创建群组", "team.group.create_failed": "创建群组失败", @@ -87,30 +94,27 @@ "team.group.delete_confirm": "确认删除群组?", "team.group.edit": "编辑群组", "team.group.edit_info": "编辑信息", - "team.group.manage_member": "管理成员", - "team.group.transfer_owner": "转让所有者", "team.group.group": "群组", + "team.group.keep_admin": "保留管理员权限", + "team.group.manage_member": "管理成员", + "team.group.manage_tip": "可以邀请成员、删除成员、创建群组、管理所有群组、为群组和成员分配权限", "team.group.members": "成员", "team.group.name": "群组名称", + "team.group.permission.manage": "管理员", + "team.group.permission.write": "工作台/知识库创建", + "team.group.permission_tip": "单独配置权限的成员,将遵循个人权限配置,不再受群组权限影响。\n若成员在多个权限组,则该成员的权限取并集。", "team.group.role.admin": "管理员", "team.group.role.member": "成员", "team.group.role.owner": "所有者", - "team.group.toast.can_not_delete_owner": "不能删除所有者, 请先转让", - "team.group.set_as_admin": "设为管理员", - "team.group.keep_admin": "保留管理员权限", - "team.group.permission_tip": "单独配置权限的成员,将遵循个人权限配置,不再受群组权限影响。\n若成员在多个权限组,则该成员的权限取并集。", - "team.group.permission.write": "工作台/知识库创建", - "team.group.permission.manage": "管理员", - "team.group.manage_tip": "可以邀请成员、删除成员、创建群组、管理所有群组、为群组和成员分配权限", "team.group.search_placeholder": "搜索成员/群组名称", + "team.group.set_as_admin": "设为管理员", + "team.group.toast.can_not_delete_owner": "不能删除所有者, 请先转让", + "team.group.transfer_owner": "转让所有者", "team.manage_collaborators": "管理协作者", "team.no_collaborators": "暂无协作者", - "team.belong_to_group": "所属成员组", "team.write_role_member": "可写权限", "usage.feishu": "飞书", "usage.official_account": "公众号", "usage.share": "分享链接", - "usage.wecom": "企业微信", - "usage_record": "使用记录", - "owner": "所有者" -} \ No newline at end of file + "usage.wecom": "企业微信" +} diff --git a/packages/web/i18n/zh-CN/workflow.json b/packages/web/i18n/zh-CN/workflow.json index 74310d6816d3..6f703f200970 100644 --- a/packages/web/i18n/zh-CN/workflow.json +++ b/packages/web/i18n/zh-CN/workflow.json @@ -39,7 +39,6 @@ "dataset_quote_role_tip": "设置为 System 时,将会把知识库引用内容放置到 system 消息中,可以确保历史记录的连贯性,但约束效果可能不佳,需要多调试。\n设置为 User 时,将会把知识库引用内容放置到 user 消息中,并且需要指定 {{question}} 变量位置。会对历史记录连贯性有一定影响,但通常约束效果更优。", "dataset_quote_role_user_option_desc": "强约束优先", "dynamic_input_description": "接收前方节点的输出值作为变量,这些变量可以被 Laf 请求参数使用。", - "dynamic_input_description_concat": "可以引用其他节点的输出,作为文本拼接的变量,输入 / 唤起变量列表", "edit_input": "编辑输入", "edit_output": "编辑输出", "end_with": "结束为", @@ -111,7 +110,6 @@ "loop_input_array": "数组", "loop_result": "数组执行结果", "loop_start": "循环体开始", - "loop_start_tip": "未输入数组", "max_dialog_rounds": "最多携带多少轮对话记录", "max_tokens": "最大 Tokens", "mouse_priority": "鼠标优先\n- 左键按下后可拖动画布\n- 按住 shift 后左键可批量选择", @@ -195,4 +193,4 @@ "workflow.Switch_success": "切换成功", "workflow.Team cloud": "团队云端", "workflow.exit_tips": "您的更改尚未保存,「直接退出」将不会保存您的编辑记录。" -} +} \ No newline at end of file diff --git a/packages/web/i18n/zh-Hant/account.json b/packages/web/i18n/zh-Hant/account.json new file mode 100644 index 000000000000..28ba36be2f8b --- /dev/null +++ b/packages/web/i18n/zh-Hant/account.json @@ -0,0 +1,12 @@ +{ + "api_key": "API 金鑰", + "bills_and_invoices": "帳單與發票", + "confirm_logout": "確認登出登入?", + "logout": "登出", + "notifications": "通知", + "personal_information": "個人資訊", + "personalization": "個人化", + "promotion_records": "促銷記錄", + "team": "團隊管理", + "usage_records": "使用記錄" +} diff --git a/packages/web/i18n/zh-Hant/account_apikey.json b/packages/web/i18n/zh-Hant/account_apikey.json new file mode 100644 index 000000000000..0253c202f16e --- /dev/null +++ b/packages/web/i18n/zh-Hant/account_apikey.json @@ -0,0 +1,3 @@ +{ + "key_tips": "你可以使用 API 金鑰存取一些特定的介面(無法存取應用,存取應用程式需使用應用程式內的 API key)" +} \ No newline at end of file diff --git a/packages/web/i18n/zh-Hant/account_bill.json b/packages/web/i18n/zh-Hant/account_bill.json new file mode 100644 index 000000000000..5fe01c369a83 --- /dev/null +++ b/packages/web/i18n/zh-Hant/account_bill.json @@ -0,0 +1,51 @@ +{ + "all": "全部", + "back": "返回", + "bank_account": "開戶帳號", + "bank_name": "開戶銀行", + "bill_detail": "帳單詳情", + "bill_record": "帳單記錄", + "company_address": "公司地址", + "company_phone": "公司電話", + "completed": "已完成", + "confirm": "確認", + "default_header": "預設抬頭", + "detail": "詳情", + "email_address": "郵件地址", + "extra_ai_points": "AI 積分運算標準", + "extra_dataset_size": "額外知識庫容量", + "generation_time": "生成時間", + "has_invoice": "是否已開票", + "invoice_amount": "開票金額", + "invoice_detail": "發票詳情", + "invoice_sending_info": "發票將在 3-7 個工作天內發送至郵箱,請耐心等待", + "mm": "毫米", + "need_special_invoice": "是否需要專票", + "no": "否", + "no_invoice_record": "無帳單記錄~", + "no_invoice_record_tip": "暫無開立發票紀錄", + "order_number": "訂單編號", + "order_type": "訂單類型", + "organization_name": "組織名稱", + "payment_method": "支付方式", + "save": "儲存", + "save_failed": "保存異常", + "save_success": "保存成功", + "status": "狀態", + "submit_failed": "提交失敗", + "submit_success": "提交成功", + "submitted": "已提交", + "subscription_mode_month": "按月", + "subscription_package": "訂閱套餐", + "subscription_period": "訂閱週期", + "support_wallet_amount": "金額", + "support_wallet_apply_invoice": "可開立帳單", + "support_wallet_bill_tag_invoice": "帳單發票", + "support_wallet_invoicing": "開票", + "time": "時間", + "type": "類型", + "unit_code": "統一信用代碼", + "update": "更新", + "yes": "是", + "yuan": "{{amount}}元" +} \ No newline at end of file diff --git a/packages/web/i18n/zh-Hant/account_individuation.json b/packages/web/i18n/zh-Hant/account_individuation.json new file mode 100644 index 000000000000..f2ad5f32a1e4 --- /dev/null +++ b/packages/web/i18n/zh-Hant/account_individuation.json @@ -0,0 +1,6 @@ +{ + "language": "語言", + "personalization": "個人化", + "timezone": "時區", + "update_data_success": "更新數據成功" +} \ No newline at end of file diff --git a/packages/web/i18n/zh-Hant/account_info.json b/packages/web/i18n/zh-Hant/account_info.json new file mode 100644 index 000000000000..84c71e6945b1 --- /dev/null +++ b/packages/web/i18n/zh-Hant/account_info.json @@ -0,0 +1,83 @@ +{ + "account_duplicate": "帳號", + "account_knowledge_base_cleanup_warning": "免費版團隊連續 30 天未登入系統時,系統會自動清理帳號知識庫。", + "active": "生效中", + "ai_points": "AI 積分", + "ai_points_calculation_standard": "AI 積分", + "ai_points_usage": "AI 積分使用量", + "ai_points_usage_tip": "每次呼叫 AI 模型時,都會消耗一定的 AI 積分。\n具體的計算標準可參考上方的「計費標準」。", + "avatar": "頭像", + "avatar_selection_exception": "頭像選擇異常", + "balance": "餘額", + "billing_standard": "計費標準", + "bind_notification_error": "綁定通知帳號異常", + "bind_notification_hint": "請綁定通知接收帳號,確保您能正常接收套餐過期提醒等通知,保障您的服務正常運作。", + "bind_notification_success": "綁定通知帳號成功", + "cancel": "取消", + "change": "變更", + "choose_avatar": "點選選擇頭像", + "click_modify_nickname": "點選修改暱稱", + "code_required": "驗證碼不能為空", + "confirm": "確認", + "confirm_password": "確認密碼", + "contact_customer_service": "聯絡客服", + "contact_us": "聯絡我們", + "current_package": "目前套餐", + "current_token_price": "目前積分價格", + "effective_time": "生效時間", + "email_label": "信箱", + "exchange": "兌換", + "exchange_failure": "兌換失敗", + "exchange_success": "兌換成功", + "expiration_time": "到期時間", + "expired": "已過期", + "group": "群組", + "help_chatbot": "機器人助手", + "help_document": "幫助文檔", + "knowledge_base_capacity": "知識庫容量", + "manage": "管理", + "member_name": "成員名", + "month": "月", + "new_password": "新密碼", + "notification_receiving": "通知接收", + "notification_receiving_hint": "通知接收", + "old_password": "舊密碼", + "open_api_notice": "可以填寫 OpenAI/OneAPI 的相關金鑰。\n如果你填寫了該內容,在線上平台使用【 AI 對話】、【問題分類】和【內容提取】將會走你填寫的 Key,不會計費用。\n請注意你的 Key 是否有存取對應模型的權限。 \nGPT 模型可以選擇 FastAI 。", + "openai_account_configuration": "OpenAI 帳號配置", + "openai_account_setting_exception": "設定 OpenAI 帳號異常", + "package_and_usage": "套餐與用量", + "package_details": "套餐詳情", + "package_expiry_time": "套餐到期時間", + "package_usage_rules": "套餐使用規則:系統優先使用更進階的套餐,原未用完的套餐將延遲生效", + "password": "密碼", + "password_length_error": "密碼最少 4 位最多 60 位", + "password_mismatch": "密碼不一致: 兩次密碼不一致", + "password_update_error": "修改密碼異常", + "password_update_success": "修改密碼成功", + "pending_usage": "待使用", + "personal_information": "個人資訊", + "phone_label": "手機號", + "please_bind_notification_receiving_path": "請先綁定通知接收途徑", + "purchase_extra_package": "購買額外套餐", + "reminder_create_bound_notification_account": "提醒創建者綁定通知帳號", + "request_address_notice": "請求地址,預設為 openai 官方。\n可填中轉位址,未自動補全 \"v1\"", + "resource_usage": "資源用量", + "select_avatar": "點選選擇頭像", + "standard_package_and_extra_resource_package": "包含標準套餐與額外資源包", + "storage_capacity": "儲存量", + "team_balance": "團隊餘額", + "token_validity_period": "積分有效期限一年", + "tokens": "積分", + "type": "類型", + "unlimited": "無限制", + "update_password": "修改密碼", + "update_success_tip": "更新數據成功", + "upgrade_package": "升級套餐", + "usage_balance": "使用餘額: 使用餘額", + "usage_balance_notice": "由於系統升級,原「自動續費從餘額扣款」模式取消,餘額儲值入口關閉。\n您的餘額可用於購買積分", + "user_account": "帳號", + "user_team_team_name": "團隊名", + "verification_code_required": "驗證碼不能為空", + "you_can_convert": "您可以兌換", + "yuan": "元" +} diff --git a/packages/web/i18n/zh-Hant/account_inform.json b/packages/web/i18n/zh-Hant/account_inform.json new file mode 100644 index 000000000000..8577fa334d00 --- /dev/null +++ b/packages/web/i18n/zh-Hant/account_inform.json @@ -0,0 +1,4 @@ +{ + "no_notifications": "暫無通知", + "read": "已讀" +} \ No newline at end of file diff --git a/packages/web/i18n/zh-Hant/account_promotion.json b/packages/web/i18n/zh-Hant/account_promotion.json new file mode 100644 index 000000000000..788c975af432 --- /dev/null +++ b/packages/web/i18n/zh-Hant/account_promotion.json @@ -0,0 +1,13 @@ +{ + "amount": "金額", + "cashback_ratio": "返現比例", + "cashback_ratio_description": "好友儲值時你將獲得一定比例的餘額獎勵", + "copy_invite_link": "複製邀請連結", + "earnings": "收益(¥)", + "invite_url": "邀請連結", + "invite_url_tip": "透過該連結註冊的好友將永久與你綁定,其儲值時你會獲得一定餘額獎勵。\n \n此外,好友使用手機號碼註冊時,你將立即獲得 5 元獎勵。\n \n獎勵會發送到您的預設團隊。", + "no_invite_records": "暫無邀請紀錄", + "time": "時間", + "total_invited": "累計邀請人數", + "type": "類型" +} \ No newline at end of file diff --git a/packages/web/i18n/zh-Hant/account_team.json b/packages/web/i18n/zh-Hant/account_team.json new file mode 100644 index 000000000000..c02d361e162e --- /dev/null +++ b/packages/web/i18n/zh-Hant/account_team.json @@ -0,0 +1,27 @@ +{ + "action": "操作", + "confirm_delete_group": "確認刪除群組?", + "confirm_leave_team": "確認離開該團隊? \n \n退出後,您在該團隊所有的資源( 應用程式、知識庫、資料夾、管理的群組等)均轉讓給團隊所有者。", + "create_group": "建立群組", + "delete": "刪除", + "edit_info": "編輯訊息", + "group": "群組", + "group_name": "群組名稱", + "label_sync": "標籤同步", + "leave_team_failed": "離開團隊異常", + "manage_member": "管理成員", + "member": "成員", + "member_group": "所屬成員組", + "owner": "擁有者", + "permission": "權限", + "remove_tip": "確認將 {{username}} 移出團隊?", + "retain_admin_permissions": "保留管理員權限", + "search_member_group_name": "搜尋成員/群組名稱", + "total_team_members": "共 {{amount}} 名成員", + "transfer_ownership": "轉讓所有者", + "user_name": "使用者名稱", + "user_team_invite_member": "邀請成員", + "user_team_leave_team": "離開團隊", + "user_team_leave_team_failed": "離開團隊失敗", + "waiting": "待接受" +} diff --git a/packages/web/i18n/zh-Hant/account_usage.json b/packages/web/i18n/zh-Hant/account_usage.json new file mode 100644 index 000000000000..e79499b72af9 --- /dev/null +++ b/packages/web/i18n/zh-Hant/account_usage.json @@ -0,0 +1,23 @@ +{ + "ai_model": "AI 模型", + "all": "所有", + "app_name": "應用程式名", + "billing_module": "扣費模組", + "details": "詳情", + "duration_seconds": "時長(秒)", + "generation_time": "生成時間", + "member": "成員", + "member_name": "成員名", + "module_name": "模組名", + "month": "月", + "no_usage_records": "暫無使用紀錄", + "order_number": "訂單編號", + "project_name": "專案名", + "source": "來源", + "text_length": "文字長度", + "token_length": "token 長度", + "total_points": "AI 積分消耗", + "total_points_consumed": "AI 積分消耗", + "usage_detail": "使用詳情", + "user_type": "類型" +} \ No newline at end of file diff --git a/packages/web/i18n/zh-Hant/app.json b/packages/web/i18n/zh-Hant/app.json index 1e811ed92620..73e5570495c4 100644 --- a/packages/web/i18n/zh-Hant/app.json +++ b/packages/web/i18n/zh-Hant/app.json @@ -14,6 +14,9 @@ "app.version_past": "已發布過", "app.version_publish_tips": "此版本將儲存至團隊雲端,同步給整個團隊,同時更新所有發布通道的應用程式版本", "app_detail": "應用程式詳細資訊", + "auto_execute": "自動執行", + "auto_execute_default_prompt_placeholder": "自動執行時,發送的預設問題", + "auto_execute_tip": "開啟後,使用者進入對話式介面將自動觸發工作流程。\n執行順序:1、對話開場白;2、全域變數;3、自動執行。", "chat_debug": "聊天預覽", "chat_logs": "對話紀錄", "chat_logs_tips": "紀錄會記錄此應用程式的線上、分享和 API(需填寫 chatId)對話紀錄", @@ -45,7 +48,6 @@ "file_recover": "檔案將會覆蓋目前內容", "file_upload": "檔案上傳", "file_upload_tip": "開啟後,可以上傳文件/圖片。文件保留 7 天,圖片保留 15 天。使用這個功能可能產生較多額外費用。為了確保使用體驗,使用這個功能時,請選擇上下文長度較大的 AI 模型。", - "global_variables_desc": "變數描述", "go_to_chat": "前往對話", "go_to_run": "前往執行", "image_upload": "圖片上傳", @@ -77,13 +79,16 @@ "move.hint": "移動後,所選應用程式/資料夾將會繼承新資料夾的權限設定,原先的權限設定將會失效。", "move_app": "移動應用程式", "not_json_file": "請選擇 JSON 檔案", + "open_auto_execute": "啟用自動執行", "open_vision_function_tip": "有圖示開關的模型即擁有圖片辨識功能。若開啟,模型會解析檔案連結中的圖片,並自動解析使用者問題中的圖片(使用者問題 ≤ 500 字時生效)。", "or_drag_JSON": "或拖曳 JSON 檔案", "paste_config": "貼上設定", "permission.des.manage": "在寫入權限基礎上,可以設定發布通道、檢視對話紀錄、分配這個應用程式的權限", "permission.des.read": "可以使用這個應用程式進行對話", "permission.des.write": "可以檢視和編輯應用程式", - "plugin_cost_per_times": "{{cost}}/次", + "plugin.Instructions": "使用說明", + "plugin_cost_by_token": "根據 token 消耗計費", + "plugin_cost_per_times": "{{cost}} 積分/次", "plugin_dispatch": "外掛呼叫", "plugin_dispatch_tip": "賦予模型取得外部資料的能力,具體呼叫哪些外掛,將由模型自主決定,所有外掛都將以非串流模式執行。\n若選擇了外掛,知識庫呼叫將自動作為一個特殊的外掛。", "publish_channel": "發布通道", @@ -102,7 +107,6 @@ "template.standard_template": "標準範本", "template.standard_template_des": "標準提示詞,用於結構不固定的知識庫。", "templateMarket.Search_template": "搜尋範本", - "templateMarket.Template_market": "範本市集", "templateMarket.Use": "使用", "templateMarket.no_intro": "還沒有介紹~", "templateMarket.templateTags.Image_generation": "影像生成", diff --git a/packages/web/i18n/zh-Hant/chat.json b/packages/web/i18n/zh-Hant/chat.json index f780d39c041e..20d6611402e6 100644 --- a/packages/web/i18n/zh-Hant/chat.json +++ b/packages/web/i18n/zh-Hant/chat.json @@ -4,6 +4,7 @@ "LLM_model_response_empty": "模型流程回應為空,請檢查模型流程輸出是否正常", "chat_history": "對話紀錄", "chat_input_guide_lexicon_is_empty": "尚未設定詞彙庫", + "chat_test_app": "調試-{{name}}", "citations": "{{num}} 筆引用", "click_contextual_preview": "點選檢視上下文預覽", "config_input_guide": "設定輸入導引", @@ -47,4 +48,4 @@ "upload": "上傳", "view_citations": "檢視引用", "web_site_sync": "網站同步" -} +} \ No newline at end of file diff --git a/packages/web/i18n/zh-Hant/common.json b/packages/web/i18n/zh-Hant/common.json index ae0bd4b726bb..2e17fdf5d8e3 100644 --- a/packages/web/i18n/zh-Hant/common.json +++ b/packages/web/i18n/zh-Hant/common.json @@ -18,6 +18,7 @@ "FAQ.switch_package_a": "方案使用規則為優先使用較進階的方案。因此,若購買的新方案比目前方案更進階,則新方案會立即生效;否則將繼續使用目前方案。", "FAQ.switch_package_q": "是否會切換訂閱方案?", "Folder": "資料夾", + "Instructions": "使用說明", "Login": "登入", "Move": "移動", "Name": "名稱", @@ -25,6 +26,7 @@ "Rename": "重新命名", "Resume": "繼續", "Running": "執行中", + "Select_all": "全選", "Submit": "送出", "UnKnow": "未知", "Warning": "警告", @@ -96,6 +98,7 @@ "common.Cancel": "取消", "common.Choose": "選擇", "common.Close": "關閉", + "common.Code": "源碼", "common.Config": "設定", "common.Confirm": "確認", "common.Confirm Create": "確認建立", @@ -128,6 +131,8 @@ "common.Expired Time": "到期時間", "common.File": "檔案", "common.Finish": "完成", + "common.FullScreen": "全屏", + "common.FullScreenLight": "全屏預覽", "common.Import": "匯入", "common.Import failed": "匯入失敗", "common.Import success": "匯入成功", @@ -156,6 +161,7 @@ "common.Permission": "權限", "common.Permission_tip": "個人權限大於群組權限", "common.Please Input Name": "請輸入名稱", + "common.Preview": "預覽", "common.Read document": "閱讀文件", "common.Read intro": "閱讀說明", "common.Remove": "移除", @@ -268,9 +274,11 @@ "core.app.Api request": "API 存取", "core.app.Api request desc": "透過 API 整合到現有系統中,或整合到企業微信、飛書等", "core.app.App intro": "應用程式介紹", + "core.app.Auto execute": "自動執行", "core.app.Chat Variable": "對話變數", "core.app.Config schedule plan": "設定排程執行", "core.app.Config whisper": "設定語音輸入", + "core.app.Config_auto_execute": "點選配置自動執行規則", "core.app.Interval timer config": "排程執行設定", "core.app.Interval timer run": "排程執行", "core.app.Interval timer tip": "可排程執行應用程式", @@ -338,7 +346,7 @@ "core.app.schedule.Default prompt placeholder": "執行應用程式時的預設問題", "core.app.schedule.Every day": "每天 {{hour}}:00", "core.app.schedule.Every month": "每月 {{day}} 號 {{hour}}:00", - "core.app.schedule.Every week": "每週{{day}} {{hour}}:00", + "core.app.schedule.Every week": "每週 {{day}} {{hour}}:00", "core.app.schedule.Interval": "每 {{interval}} 小時", "core.app.schedule.Open schedule": "排程執行", "core.app.setting": "應用程式資訊設定", @@ -365,7 +373,7 @@ "core.app.tts.Test Listen Text": "您好,這是語音測試。如果您能聽到這句話,表示語音播放功能正常", "core.app.tts.Web": "瀏覽器內建(免費)", "core.app.whisper.Auto send": "自動傳送", -"core.app.whisper.Auto send tip": "語音輸入完成後自動傳送,無需手動點選傳送按鈕", + "core.app.whisper.Auto send tip": "語音輸入完成後自動傳送,無需手動點選傳送按鈕", "core.app.whisper.Auto tts response": "自動語音回應", "core.app.whisper.Auto tts response tip": "透過語音輸入傳送的問題,將直接以語音形式回應。請確保已開啟語音播放功能。", "core.app.whisper.Close": "關閉", @@ -490,6 +498,7 @@ "core.dataset.Start export": "已開始匯出", "core.dataset.Table collection": "表格資料集", "core.dataset.Text collection": "文字資料集", + "core.dataset.apiFile": "API 檔案", "core.dataset.collection.Click top config website": "點選設定網站", "core.dataset.collection.Collection name": "資料集名稱", "core.dataset.collection.Collection raw text": "資料集內容", @@ -504,7 +513,6 @@ "core.dataset.collection.metadata.Chunk Size": "分割大小", "core.dataset.collection.metadata.Createtime": "建立時間", "core.dataset.collection.metadata.Raw text length": "原始文字長度", - "core.dataset.collection.metadata.Training Type": "訓練模式", "core.dataset.collection.metadata.Updatetime": "更新時間", "core.dataset.collection.metadata.Web page selector": "網頁選擇器", "core.dataset.collection.metadata.metadata": "中繼資料", @@ -580,7 +588,6 @@ "core.dataset.import.Sources list": "來源列表", "core.dataset.import.Start upload": "開始上傳", "core.dataset.import.Total files": "共 {{total}} 個檔案", - "core.dataset.import.Training mode": "訓練模式", "core.dataset.import.Upload complete": "上傳完成", "core.dataset.import.Upload data": "確認上傳", "core.dataset.import.Upload file progress": "檔案上傳進度", @@ -680,7 +687,7 @@ "core.module.Setting quote prompt": "設定引用提示詞", "core.module.Variable": "全域變數", "core.module.Variable Setting": "變數設定", -"core.module.edit.Field Name Cannot Be Empty": "欄位名稱不能為空", + "core.module.edit.Field Name Cannot Be Empty": "欄位名稱不能為空", "core.module.extract.Add field": "新增欄位", "core.module.extract.Enum Description": "列舉此欄位可能的值,每行一個", "core.module.extract.Enum Value": "列舉值", @@ -734,7 +741,7 @@ "core.module.template.AI support tool tip": "支援函式呼叫的模型可以更好地使用工具呼叫。", "core.module.template.Basic Node": "基本功能", "core.module.template.Query extension": "問題最佳化", - "core.module.template.System Plugin": "系統外掛程式", + "core.module.template.System Plugin": "系統插件", "core.module.template.System input module": "系統輸入模組", "core.module.template.Team app": "團隊應用程式", "core.module.template.Tool module": "工具", @@ -899,6 +906,7 @@ "navbar.Chat": "對話", "navbar.Datasets": "知識庫", "navbar.Studio": "工作區", + "navbar.Toolkit": "工具箱", "navbar.Tools": "工具", "new_create": "建立新項目", "no": "否", @@ -1017,7 +1025,7 @@ "support.wallet.Amount": "金額", "support.wallet.Buy": "購買", "support.wallet.Not sufficient": "您的 AI 點數不足,請先升級方案或購買額外 AI 點數後繼續使用。", -"support.wallet.Plan expired time": "方案到期時間", + "support.wallet.Plan expired time": "方案到期時間", "support.wallet.Standard Plan Detail": "方案詳細資訊", "support.wallet.To read plan": "檢視方案", "support.wallet.amount_0": "購買數量不能為 0", @@ -1213,6 +1221,7 @@ "user.team.role.writer": "可寫入成員", "user.type": "類型", "verification": "驗證", + "workflow.template.communication": "通訊", "xx_search_result": "{{key}} 的搜尋結果", "yes": "是", "yesterday": "昨天", diff --git a/packages/web/i18n/zh-Hant/dataset.json b/packages/web/i18n/zh-Hant/dataset.json index 45ae67406527..a1936acf556d 100644 --- a/packages/web/i18n/zh-Hant/dataset.json +++ b/packages/web/i18n/zh-Hant/dataset.json @@ -1,18 +1,28 @@ { "Enable": "啟用", + "add_file": "新增文件", + "api_file": "API 檔案庫", + "api_url": "介面位址", "collection.Create update time": "建立/更新時間", "collection.Training type": "分段模式", + "collection_not_support_retraining": "此集合類型不支援重新調整參數", + "collection_not_support_sync": "該集合不支援同步", + "collection_sync": "立即同步", + "collection_sync_confirm_tip": "確認開始同步資料?\n系統將會拉取最新資料進行比較,如果內容不相同,則會建立一個新的集合並刪除舊的集合,請確認!", "collection_tags": "集合標籤", "common_dataset": "通用資料集", "common_dataset_desc": "可透過匯入檔案、網頁連結或手動輸入的方式建立資料集", "confirm_to_rebuild_embedding_tip": "確定要為資料集切換索引嗎?\n切換索引是一個重要的操作,需要對您資料集內所有資料重新建立索引,可能需要較長時間,請確保帳號內剩餘點數充足。\n\n此外,您還需要注意修改使用此資料集的應用程式,避免與其他索引模型資料集混用。", + "core.dataset.import.Adjust parameters": "調整參數", "custom_data_process_params": "自訂", "custom_data_process_params_desc": "自訂資料處理規則", "data.ideal_chunk_length": "理想分塊長度", "data_process_params": "處理參數", "data_process_setting": "資料處理設定", + "dataset.Unsupported operation": "操作不支持", "dataset.no_collections": "尚無資料集", "dataset.no_tags": "尚無標籤", + "error.collectionNotFound": "找不到集合", "external_file": "外部檔案庫", "external_file_dataset_desc": "可以從外部檔案庫匯入檔案建立資料集,檔案不會進行二次儲存", "external_id": "檔案讀取識別碼", @@ -32,6 +42,11 @@ "permission.des.write": "可新增和變更資料集內容", "rebuild_embedding_start_tip": "切換索引模型任務已開始", "rebuilding_index_count": "重建中索引數量:{{count}}", + "request_headers": "請求頭", + "retain_collection": "調整訓練參數", + "retrain_task_submitted": "重新訓練任務已提交", + "same_api_collection": "存在相同的 API 集合", + "start_sync_website_tip": "確認開始同步資料?\n將會刪除舊資料後重新獲取,請確認!", "tag.Add New": "新增", "tag.Add_new_tag": "新增標籤", "tag.Edit_tag": "編輯標籤", diff --git a/packages/web/i18n/zh-Hant/file.json b/packages/web/i18n/zh-Hant/file.json index 1dd0469ed4ee..bb297ce9b9d4 100644 --- a/packages/web/i18n/zh-Hant/file.json +++ b/packages/web/i18n/zh-Hant/file.json @@ -14,5 +14,5 @@ "support_max_size": "單一檔案大小上限為 {{maxSize}}", "upload_failed": "上傳失敗", "reached_max_file_count": "已達檔案數量上限", - "upload_error_description": "單次僅支援上傳多個檔案或一個資料夾" -} + "upload_error_description": "單次僅支援上傳多個檔案或一個資料夾" +} \ No newline at end of file diff --git a/packages/web/i18n/zh-Hant/login.json b/packages/web/i18n/zh-Hant/login.json index 351344bc1ad4..db26897436ab 100644 --- a/packages/web/i18n/zh-Hant/login.json +++ b/packages/web/i18n/zh-Hant/login.json @@ -8,7 +8,7 @@ "password_condition": "密碼最多 60 個字元", "policy_tip": "使用即代表您同意我們的", "privacy": "隱私權政策", - "redirect": "跳轉", + "redirect": "跳轉", "register": "註冊帳號", "root_password_placeholder": "root 使用者密碼為環境變數 DEFAULT_ROOT_PSW 的值", "terms": "服務條款", @@ -16,4 +16,4 @@ "agree": "同意", "cookies_tip": "本網站使用 cookies 提供更好的服務體驗。繼續使用即表示您同意我們的 Cookie 政策。", "privacy_policy": "隱私權政策" -} +} \ No newline at end of file diff --git a/packages/web/i18n/zh-Hant/publish.json b/packages/web/i18n/zh-Hant/publish.json index 60b379adc9cc..0a702abad4d0 100644 --- a/packages/web/i18n/zh-Hant/publish.json +++ b/packages/web/i18n/zh-Hant/publish.json @@ -24,10 +24,8 @@ "publish_name": "名稱", "qpm_is_empty": "QPM 不可為空白", "qpm_tips": "每個 IP 每分鐘最高查詢次數", - "quote_content": "引用內容", "request_address": "請求網址", "show_node": "即時執行狀態", - "show_origin_content": "檢視原始來源", "show_share_link_modal_title": "開始使用", "token_auth": "身分驗證", "token_auth_tips": "身分驗證伺服器網址。若有提供,每次對話前將向指定伺服器傳送驗證請求。", @@ -38,4 +36,4 @@ "wecom.create_modal_title": "建立企業微信聊天機器人", "wecom.edit_modal_title": "編輯企業微信聊天機器人", "wecom.title": "發布至企業微信聊天機器人" -} +} \ No newline at end of file diff --git a/packages/web/i18n/zh-Hant/user.json b/packages/web/i18n/zh-Hant/user.json index 41f9a4209a1d..8b9d4418108d 100644 --- a/packages/web/i18n/zh-Hant/user.json +++ b/packages/web/i18n/zh-Hant/user.json @@ -5,7 +5,7 @@ "bill.conversion": "兌換", "bill.convert_error": "兌換失敗", "bill.convert_success": "兌換成功", - "bill.current_token_price": "目前點數價格", + "bill.current_token_price": "目前點數價格", "bill.not_need_invoice": "餘額支付無法開立發票", "bill.price": "價格", "bill.renew_plan": "續訂方案", @@ -17,9 +17,6 @@ "bill.valid_time": "生效時間", "bill.you_can_convert": "您可兌換", "bill.yuan": "元", - "bill_and_invoices": "帳單與發票", - "bind_inform_account_error": "綁定通知帳號失敗", - "bind_inform_account_success": "綁定通知帳號成功", "delete.admin_failed": "刪除管理員失敗", "delete.admin_success": "刪除管理員成功", "delete.failed": "刪除失敗", @@ -29,8 +26,8 @@ "login.error": "登入失敗", "login.password_condition": "密碼最多可輸入 60 個字元", "login.success": "登入成功", + "manage_team": "管理團隊", "name": "名稱", - "notice": "通知", "notification.Bind Notification Pipe Hint": "請綁定通知接收帳號,以確保您能正常接收方案到期提醒等通知,保障您的服務正常運作。", "notification.remind_owner_bind": "請提醒建立者綁定通知帳號", "operations": "操作", @@ -73,11 +70,19 @@ "register.success": "註冊成功", "register.to_login": "已有帳號?前往登入", "search_user": "搜尋使用者名稱", - "sign_out": "登出", + "sso_auth_failed": "SSO 鑑權失敗", "synchronization.button": "立即同步", "synchronization.placeholder": "請輸入同步標籤", "synchronization.title": "填寫標籤同步連結,點選同步按鈕即可同步", "team.Add manager": "新增管理員", + "team.Confirm Invite": "確認邀請", + "team.Create Team": "創建新團隊", + "team.Invite Member Failed Tip": "邀請成員出現異常", + "team.Invite Member Result Tip": "邀請結果提示", + "team.Invite Member Success Tip": "邀請成員完成\n\n成功:{{success}} 人\n\n使用者名稱無效:{{inValid}}\n\n已在團隊中:{{inTeam}}", + "team.Set Name": "給團隊取個名字", + "team.Team Name": "團隊名", + "team.Update Team": "更新團隊資訊", "team.add_collaborator": "新增協作者", "team.add_writer": "新增可寫入成員", "team.avatar_and_name": "頭像與名稱", @@ -111,6 +116,5 @@ "usage.feishu": "飛書", "usage.official_account": "公眾號", "usage.share": "分享連結", - "usage.wecom": "企業微信", - "usage_record": "使用紀錄" + "usage.wecom": "企業微信" } diff --git a/packages/web/i18n/zh-Hant/workflow.json b/packages/web/i18n/zh-Hant/workflow.json index 1223bdaeeb90..c21a1d691be3 100644 --- a/packages/web/i18n/zh-Hant/workflow.json +++ b/packages/web/i18n/zh-Hant/workflow.json @@ -39,7 +39,6 @@ "dataset_quote_role_tip": "設定為 System 時,會將知識庫引用內容放置到系統訊息中,可以確保歷史紀錄的連貫性,但約束效果可能不佳。\n設定為 User 時,會將知識庫引用內容放置到使用者訊息中,並且需要指定 {{question}} 變數位置。會對歷史紀錄連貫性有一定影響,但通常約束效果較佳。", "dataset_quote_role_user_option_desc": "強約束優先", "dynamic_input_description": "接收前一個節點的輸出值作為變數,這些變數可以被 Laf 請求參數使用。", - "dynamic_input_description_concat": "可以引用其他節點的輸出作為文字串接的變數,輸入 / 叫出變數清單", "edit_input": "編輯輸入", "edit_output": "編輯輸出", "end_with": "結尾為", @@ -111,7 +110,6 @@ "loop_input_array": "陣列", "loop_result": "陣列執行結果", "loop_start": "迴圈開始", - "loop_start_tip": "未輸入陣列", "max_dialog_rounds": "最多攜帶幾輪對話紀錄", "max_tokens": "最大 Token 數", "mouse_priority": "滑鼠優先\n- 按下左鍵拖曳畫布\n- 按住 Shift 鍵並點選左鍵可批次選取", @@ -195,4 +193,4 @@ "workflow.Switch_success": "切換成功", "workflow.Team cloud": "團隊雲端", "workflow.exit_tips": "您的變更尚未儲存,「直接結束」將不會儲存您的編輯紀錄。" -} +} \ No newline at end of file diff --git a/packages/web/styles/theme.ts b/packages/web/styles/theme.ts index c8bf06d7b091..0114a7db84a0 100644 --- a/packages/web/styles/theme.ts +++ b/packages/web/styles/theme.ts @@ -494,7 +494,10 @@ const Radio = radioStyle({ const Checkbox = checkBoxMultiStyle({ baseStyle: checkBoxPart({ label: { - fontFamily: 'mono' // change the font family of the label + fontFamily: 'mono', // change the font family of the label + _disabled: { + outline: 'none' + } }, control: { borderRadius: 'xs', @@ -507,6 +510,12 @@ const Checkbox = checkBoxMultiStyle({ boxShadow: `${shadowLight} !important`, _hover: { bg: 'primary.50' + }, + _disabled: { + bg: 'myGray.100', + borderColor: 'transparent', + color: 'myGray.400', + outline: 'none' } }, _hover: { diff --git a/packages/web/types/i18next.d.ts b/packages/web/types/i18next.d.ts index 145804a530f4..3f6347c0e041 100644 --- a/packages/web/types/i18next.d.ts +++ b/packages/web/types/i18next.d.ts @@ -1,4 +1,13 @@ import 'i18next'; +import account_team from '../i18n/zh-CN/account_team.json'; +import account from '../i18n/zh-CN/account.json'; +import account_promotion from '../i18n/zh-CN/account_promotion.json'; +import account_inform from '../i18n/zh-CN/account_inform.json'; +import account_individuation from '../i18n/zh-CN/account_individuation.json'; +import account_apikey from '../i18n/zh-CN/account_apikey.json'; +import account_bill from '../i18n/zh-CN/account_bill.json'; +import account_usage from '../i18n/zh-CN/account_usage.json'; +import account_info from '../i18n/zh-CN/account_info.json'; import common from '../i18n/zh-CN/common.json'; import dataset from '../i18n/zh-CN/dataset.json'; import app from '../i18n/zh-CN/app.json'; @@ -19,6 +28,15 @@ export interface I18nNamespaces { user: typeof user; chat: typeof chat; login: typeof login; + account_info: typeof account_info; + account_usage: typeof account_usage; + account_bill: typeof account_bill; + account_apikey: typeof account_apikey; + account_individuation: typeof account_individuation; + account_inform: typeof account_inform; + account_promotion: typeof account_promotion; + account: typeof account; + account_team: typeof account_team; } export type I18nNsType = (keyof I18nNamespaces)[]; @@ -34,7 +52,26 @@ export type I18nKeyFunction = { declare module 'i18next' { interface CustomTypeOptions { returnNull: false; - defaultNS: ['common', 'dataset', 'app', 'file', 'publish', 'workflow', 'user', 'chat', 'login']; + defaultNS: [ + 'common', + 'dataset', + 'app', + 'file', + 'publish', + 'workflow', + 'user', + 'chat', + 'login', + 'account_info', + 'account_usage', + 'account_bill', + 'account_apikey', + 'account_individuation', + 'account_inform', + 'account_promotion', + 'account', + 'account_team' + ]; resources: I18nNamespaces; } } diff --git a/projects/app/data/config.json b/projects/app/data/config.json index 9a61095c385f..34d1b2eae600 100644 --- a/projects/app/data/config.json +++ b/projects/app/data/config.json @@ -80,11 +80,7 @@ "customExtractPrompt": "", "defaultSystemChatPrompt": "", "defaultConfig": { - "temperature": 1, - "stream": false - }, - "fieldMap": { - "max_tokens": "max_completion_tokens" + "temperature": 1 } }, { @@ -109,11 +105,7 @@ "customExtractPrompt": "", "defaultSystemChatPrompt": "", "defaultConfig": { - "temperature": 1, - "stream": false - }, - "fieldMap": { - "max_tokens": "max_completion_tokens" + "temperature": 1 } } ], diff --git a/projects/app/src/components/Layout/index.tsx b/projects/app/src/components/Layout/index.tsx index ff91e838dcf9..bb477360493c 100644 --- a/projects/app/src/components/Layout/index.tsx +++ b/projects/app/src/components/Layout/index.tsx @@ -43,6 +43,8 @@ const phoneUnShowLayoutRoute: Record = { '/price': true }; +export const navbarWidth = '64px'; + const Layout = ({ children }: { children: JSX.Element }) => { const router = useRouter(); const { Loading } = useLoading(); @@ -79,10 +81,10 @@ const Layout = ({ children }: { children: JSX.Element }) => { {children} ) : ( <> - + - + {children} diff --git a/projects/app/src/components/Layout/navbar.tsx b/projects/app/src/components/Layout/navbar.tsx index 86c35589507e..1cbbbaf09d68 100644 --- a/projects/app/src/components/Layout/navbar.tsx +++ b/projects/app/src/components/Layout/navbar.tsx @@ -11,19 +11,37 @@ import MyIcon from '@fastgpt/web/components/common/Icon'; import { useTranslation } from 'next-i18next'; import { useSystemStore } from '@/web/common/system/useSystemStore'; import MyTooltip from '@fastgpt/web/components/common/MyTooltip'; -import { getDocPath } from '@/web/common/system/doc'; export enum NavbarTypeEnum { normal = 'normal', small = 'small' } +const itemStyles: BoxProps & LinkProps = { + my: 2, + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + justifyContent: 'center', + cursor: 'pointer', + w: '48px', + h: '58px', + borderRadius: 'md' +}; +const hoverStyle: LinkProps = { + _hover: { + bg: 'myGray.05', + color: 'primary.600' + } +}; + const Navbar = ({ unread }: { unread: number }) => { const { t } = useTranslation(); const router = useRouter(); const { userInfo } = useUserStore(); const { gitStar, feConfigs } = useSystemStore(); const { lastChatAppId } = useChatStore(); + const navbarList = useMemo( () => [ { @@ -47,34 +65,36 @@ const Navbar = ({ unread }: { unread: number }) => { link: `/dataset/list`, activeLink: ['/dataset/list', '/dataset/detail'] }, + { + label: t('common:navbar.Toolkit'), + icon: 'phoneTabbar/tool', + activeIcon: 'phoneTabbar/toolFill', + link: `/toolkit`, + activeLink: ['/toolkit'] + }, { label: t('common:navbar.Account'), icon: 'support/user/userLight', activeIcon: 'support/user/userFill', - link: '/account', - activeLink: ['/account'] + link: '/account/info', + activeLink: [ + '/account/bill', + '/account/info', + '/account/team', + '/account/usage', + '/account/apikey', + '/account/individuation', + '/account/inform', + '/account/promotion' + ] } ], [lastChatAppId, t] ); - const itemStyles: BoxProps & LinkProps = { - my: 3, - display: 'flex', - flexDirection: 'column', - alignItems: 'center', - justifyContent: 'center', - cursor: 'pointer', - w: '48px', - h: '58px', - borderRadius: 'md' - }; - const hoverStyle: LinkProps = { - _hover: { - bg: 'myGray.05', - color: 'primary.600' - } - }; + const isSecondNavbarPage = useMemo(() => { + return ['/toolkit'].includes(router.pathname); + }, [router.pathname]); return ( { w={'100%'} userSelect={'none'} pb={2} + bg={isSecondNavbarPage ? 'myGray.50' : 'transparent'} > {/* logo */} { cursor={'pointer'} onClick={() => router.push('/account')} > - + {/* 导航列表 */} @@ -121,7 +136,7 @@ const Navbar = ({ unread }: { unread: number }) => { color: 'myGray.500', bg: 'transparent', _hover: { - bg: 'rgba(255,255,255,0.9)' + bg: isSecondNavbarPage ? 'white' : 'rgba(255,255,255,0.9)' } })} {...(item.link !== router.asPath @@ -153,7 +168,7 @@ const Navbar = ({ unread }: { unread: number }) => { {...itemStyles} {...hoverStyle} prefetch - href={`/account?currentTab=inform`} + href={`/account/inform`} mb={0} color={'myGray.500'} height={'48px'} @@ -164,21 +179,26 @@ const Navbar = ({ unread }: { unread: number }) => { )} - {(feConfigs?.docUrl || feConfigs?.chatbotUrl) && ( - - - - - - )} + + {feConfigs?.navbarItems + ?.filter((item) => item.isActive) + .map((item) => ( + + + + + + ))} + {feConfigs?.show_git && ( { unread: 0 }, { - label: t('common:navbar.Tools'), + label: t('common:navbar.Datasets'), + icon: 'core/dataset/datasetLight', + activeIcon: 'core/dataset/datasetFill', + link: `/dataset/list`, + activeLink: ['/dataset/list', '/dataset/detail'], + unread: 0 + }, + { + label: t('common:navbar.Toolkit'), icon: 'phoneTabbar/tool', activeIcon: 'phoneTabbar/toolFill', - link: '/tools', - activeLink: ['/tools'], + link: `/toolkit`, + activeLink: ['/toolkit'], unread: 0 }, { @@ -41,7 +49,16 @@ const NavbarPhone = ({ unread }: { unread: number }) => { icon: 'support/user/userLight', activeIcon: 'support/user/userFill', link: '/account', - activeLink: ['/account'], + activeLink: [ + '/account/bill', + '/account/info', + '/account/team', + '/account/usage', + '/account/apikey', + '/account/individuation', + '/account/inform', + '/account/promotion' + ], unread } ], @@ -56,7 +73,7 @@ const NavbarPhone = ({ unread }: { unread: number }) => { justifyContent={'space-between'} backgroundColor={'white'} position={'relative'} - px={10} + px={4} > {navbarList.map((item) => ( void; + isActive?: boolean; + viewMode: 'source' | 'iframe'; + isMobile?: boolean; +}) => { + const isPreview = viewMode === 'iframe'; + + const textColor = isPreview + ? isActive + ? 'myGray.900' + : 'myGray.500' + : isActive + ? '#FFF' + : 'rgba(255, 255, 255, 0.8)'; + const bg = isPreview ? (isActive ? 'myGray.150' : '') : isActive ? '#333A47' : ''; + const hoverBg = isPreview ? 'myGray.150' : '#333A47'; -const MermaidBlock = ({ code }: { code: string }) => { - const { width, Ref } = useMarkdownWidth(); return ( - + + {isMobile ? ( + + + + + + ) : ( + + + + {label} + + + )} + + ); +}; + +const IframeHtmlCodeBlock = ({ + children, + className, + codeBlock, + match +}: { + children: React.ReactNode & React.ReactNode[]; + className?: string; + codeBlock?: boolean; + match: RegExpExecArray | null; +}) => { + const { t } = useTranslation(); + const { copyData } = useCopyData(); + const [viewMode, setViewMode] = useState<'source' | 'iframe'>('source'); + const isPreview = viewMode === 'iframe'; + + const { isOpen, onOpen, onClose } = useDisclosure(); + + const { width, Ref } = useMarkdownWidth(); + const isMobile = width <= 420; + + const codeBoxName = useMemo(() => { + const input = match?.['input'] || ''; + if (!input) return match?.[1]?.toUpperCase(); + + const splitInput = input.split('#'); + return splitInput[1] || match?.[1]?.toUpperCase(); + }, [match]); + + const Iframe = useMemo( + () => (