diff --git a/docSite/content/zh-cn/docs/course/official_account.md b/docSite/content/zh-cn/docs/course/official_account.md index 2aa62abd992c..f4977b2d19a2 100644 --- a/docSite/content/zh-cn/docs/course/official_account.md +++ b/docSite/content/zh-cn/docs/course/official_account.md @@ -15,9 +15,9 @@ weight: 113 ![图片](/imgs/offiaccount-1.png) -## 2. 登陆微信公众平台,获取 AppID 、 Secret和Token +## 2. 登录微信公众平台,获取 AppID 、 Secret和Token -### 1. https://mp.weixin.qq.com 登陆微信公众平台,选择您的公众号。 +### 1. https://mp.weixin.qq.com 登录微信公众平台,选择您的公众号。 **只支持通过验证的公众号,未通过验证的公众号暂不支持。** diff --git a/docSite/content/zh-cn/docs/course/openapi.md b/docSite/content/zh-cn/docs/course/openapi.md index dc3fd2a10fae..898e20138bf6 100644 --- a/docSite/content/zh-cn/docs/course/openapi.md +++ b/docSite/content/zh-cn/docs/course/openapi.md @@ -9,7 +9,7 @@ weight: 112 在 FastGPT 中,你可以为每一个应用创建多个 API 密钥,用于访问应用的 API 接口。每个密钥仅能访问一个应用。完整的接口可以[查看应用对话接口](/docs/development/openapi/chat)。 -## 获取 API 秘钥 +## 获取 API 密钥 依次选择应用 -> 「API访问」,然后点击「API 密钥」来创建密钥。 @@ -28,7 +28,7 @@ Tips: 安全起见,你可以设置一个额度或者过期时间,放置 key ```bash OPENAI_API_BASE_URL: https://api.fastgpt.in/api (改成自己部署的域名) -OPENAI_API_KEY = 上一步获取到的秘钥 +OPENAI_API_KEY = 上一步获取到的密钥 ``` **[ChatGPT Next Web](https://github.com/Yidadaa/ChatGPT-Next-Web) 示例:** diff --git a/docSite/content/zh-cn/docs/development/migration/ docker_mongo.md b/docSite/content/zh-cn/docs/development/migration/ docker_mongo.md index 50cc6d24d899..216275ea28e8 100644 --- a/docSite/content/zh-cn/docs/development/migration/ docker_mongo.md +++ b/docSite/content/zh-cn/docs/development/migration/ docker_mongo.md @@ -172,7 +172,7 @@ docker exec -it mongo mongorestore -u "username" -p "password" --authenticationD 5.重启容器 【C环境】 ``` docker compose restart -docker logs -f mongo **强烈建议先检查mongo运行情况,在去做登陆动作,如果mongo报错,访问web也会报错” +docker logs -f mongo **强烈建议先检查mongo运行情况,在去做登录动作,如果mongo报错,访问web也会报错” ``` 如果mongo启动正常,显示的是类似这样的,而不是 “mongo is restarting”,后者就是错误 @@ -182,5 +182,5 @@ docker logs -f mongo **强烈建议先检查mongo运行情况,在去做登陆 iShot_2024-05-09_19 23 13 -6. 启动fastgpt容器服务后,登陆新fastgpt web,能看到原来的数据库内容完整显示,说明已经导入系统了。 +6. 启动fastgpt容器服务后,登录新fastgpt web,能看到原来的数据库内容完整显示,说明已经导入系统了。 iShot_2024-05-09_19 23 51 diff --git a/docSite/content/zh-cn/docs/development/upgrading/4810.md b/docSite/content/zh-cn/docs/development/upgrading/4810.md index e32975893fb0..886a512c7154 100644 --- a/docSite/content/zh-cn/docs/development/upgrading/4810.md +++ b/docSite/content/zh-cn/docs/development/upgrading/4810.md @@ -4,7 +4,7 @@ description: 'FastGPT V4.8.10 更新说明' icon: 'upgrade' draft: false toc: true -weight: 816 +weight: 814 --- ## 更新指南 @@ -13,7 +13,13 @@ weight: 816 ### 2. 更新商业版环境变量 -商业版用户,需要给`fastgpt-pro`镜像,增加沙盒的环境变量:`SANDBOX_URL=http://fastgpt-sandbox.ns-hti44k5d.svc.cluster.local:3000` +1. 需要给`fastgpt-pro`镜像,增加沙盒的环境变量:`SANDBOX_URL=http://xxxxx:3000` +2. 给两个镜像增加环境变量,以便更好的存储系统日志: + +``` +LOG_LEVEL=debug +STORE_LOG_LEVEL=warn +``` ------- @@ -24,9 +30,12 @@ weight: 816 3. 新增 - 用户选择节点(Debug 模式暂未支持) 4. 商业版新增 - 飞书机器人接入 5. 商业版新增 - 公众号接入接入 -6. 优化 - 知识库集合禁用,目录禁用会递归修改其下所有 children 的禁用状态。 -7. 修复 - Prompt 模式调用工具,stream=false 模式下,会携带 0: 开头标记。 -8. 修复 - 对话日志鉴权问题:仅为 APP 管理员的用户,无法查看对话日志详情。 -9. 修复 - 选择 Milvus 部署时,无法导出知识库。 -10. 修复 - 创建 APP 副本,无法复制系统配置。 -11. 修复 - 图片识别模式下,自动解析图片链接正则不够严谨问题。 +6. 商业版新增 - 自助开票申请 +7. 商业版新增 - SSO 定制 +8. 优化 - 知识库集合禁用,目录禁用会递归修改其下所有 children 的禁用状态。 +9. 优化 - 节点选择,避免切换 tab 时候,path 加载报错。 +10. 修复 - Prompt 模式调用工具,stream=false 模式下,会携带 0: 开头标记。 +11. 修复 - 对话日志鉴权问题:仅为 APP 管理员的用户,无法查看对话日志详情。 +12. 修复 - 选择 Milvus 部署时,无法导出知识库。 +13. 修复 - 创建 APP 副本,无法复制系统配置。 +14. 修复 - 图片识别模式下,自动解析图片链接正则不够严谨问题。 diff --git a/docSite/content/zh-cn/docs/use-cases/onwechat.md b/docSite/content/zh-cn/docs/use-cases/onwechat.md index e52ff68b2e19..bc8959bdd4b8 100644 --- a/docSite/content/zh-cn/docs/use-cases/onwechat.md +++ b/docSite/content/zh-cn/docs/use-cases/onwechat.md @@ -13,7 +13,7 @@ weight: 504 由于 FastGPT 的 API 接口和 OpenAI 的规范一致,可以无需变更原来的应用即可使用 FastGPT 上编排好的应用。API 使用可参考 [这篇文章](/docs/course/openapi/)。编排示例,可参考 [高级编排介绍](/docs/workflow/intro) -## 1. 获取 OpenAPI 秘钥 +## 1. 获取 OpenAPI 密钥 依次选择应用 -> 「API访问」,然后点击「API 密钥」来创建密钥。 @@ -26,7 +26,7 @@ weight: 504 ## 3. 创建 docker-compose.yml 文件 -只需要修改 `OPEN_AI_API_KEY` 和 `OPEN_AI_API_BASE` 两个环境变量即可。其中 `OPEN_AI_API_KEY` 为第一步获取的秘钥,`OPEN_AI_API_BASE` 为 FastGPT 的 OpenAPI 地址,例如:`https://api.fastgpt.in/api/v1`。 +只需要修改 `OPEN_AI_API_KEY` 和 `OPEN_AI_API_BASE` 两个环境变量即可。其中 `OPEN_AI_API_KEY` 为第一步获取的密钥,`OPEN_AI_API_BASE` 为 FastGPT 的 OpenAPI 地址,例如:`https://api.fastgpt.in/api/v1`。 随便找一个目录,创建一个 docker-compose.yml 文件,将下面的代码复制进去。 diff --git a/docSite/content/zh-cn/docs/use-cases/wechat.md b/docSite/content/zh-cn/docs/use-cases/wechat.md index ff7cb468b71e..58d74ea4a072 100644 --- a/docSite/content/zh-cn/docs/use-cases/wechat.md +++ b/docSite/content/zh-cn/docs/use-cases/wechat.md @@ -17,7 +17,7 @@ weight: 506 ## 配置微秘书 -打开[微秘书](https://wechat.aibotk.com?r=zWLnZK) 注册登陆后找到菜单栏「基础配置」->「智能配置」,按照下图配置。 +打开[微秘书](https://wechat.aibotk.com?r=zWLnZK) 注册登录后找到菜单栏「基础配置」->「智能配置」,按照下图配置。 ![](/imgs/wechat2.png) @@ -27,7 +27,7 @@ weight: 506 ## sealos部署服务 -[访问sealos](https://cloud.sealos.run/) 登陆进来之后打开「应用管理」-> 「新建应用」。 +[访问sealos](https://cloud.sealos.run/) 登录进来之后打开「应用管理」-> 「新建应用」。 - 应用名:称随便填写 - 镜像名:私人微信填写 aibotk/wechat-assistant 企业微信填写 aibotk/worker-assistant - cpu和内存建议 1c1g @@ -61,12 +61,12 @@ WORK_PRO_TOKEN=你申请的企微 token (企业微信需要填写,私人 ![](/imgs/wechat8.png) -返回[微秘书](https://wechat.aibotk.com?r=zWLnZK) 找到「首页」,扫码登陆需要接入的微信号。 +返回[微秘书](https://wechat.aibotk.com?r=zWLnZK) 找到「首页」,扫码登录需要接入的微信号。 ![](/imgs/wechat9.png) ## 测试 -只需要发送信息,或者拉入群聊@登陆的微信就会回复信息啦。 +只需要发送信息,或者拉入群聊@登录的微信就会回复信息啦。 ![](/imgs/wechat10.png) diff --git a/packages/global/core/workflow/template/system/userSelect/index.ts b/packages/global/core/workflow/template/system/userSelect/index.ts index 0cfa1e6b3861..94f66145afac 100644 --- a/packages/global/core/workflow/template/system/userSelect/index.ts +++ b/packages/global/core/workflow/template/system/userSelect/index.ts @@ -30,7 +30,9 @@ export const UserSelectNode: FlowNodeTemplateType = { key: NodeInputKeyEnum.description, renderTypeList: [FlowNodeInputTypeEnum.textarea], valueType: WorkflowIOValueTypeEnum.string, - label: i18nT('app:workflow.select_description') + label: i18nT('app:workflow.select_description'), + description: i18nT('app:workflow.select_description_tip'), + placeholder: i18nT('app:workflow.select_description_placeholder') }, { key: NodeInputKeyEnum.userSelectOptions, diff --git a/packages/global/core/workflow/type/node.d.ts b/packages/global/core/workflow/type/node.d.ts index fdf00dfeeb1c..26fdb8681945 100644 --- a/packages/global/core/workflow/type/node.d.ts +++ b/packages/global/core/workflow/type/node.d.ts @@ -80,6 +80,7 @@ export type NodeTemplateListItemType = { name: string; intro?: string; // template list intro isTool?: boolean; + authorAvatar?: string; author?: string; unique?: boolean; // 唯一的 currentCost?: number; // 当前积分消耗 diff --git a/packages/service/common/vectorStore/controller.ts b/packages/service/common/vectorStore/controller.ts index b2bd216e380b..156ce70d114a 100644 --- a/packages/service/common/vectorStore/controller.ts +++ b/packages/service/common/vectorStore/controller.ts @@ -20,6 +20,7 @@ export const deleteDatasetDataVector = Vector.delete; export const recallFromVectorStore = Vector.embRecall; export const getVectorDataByTime = Vector.getVectorDataByTime; export const getVectorCountByTeamId = Vector.getVectorCountByTeamId; +export const getVectorCountByDatasetId = Vector.getVectorCountByDatasetId; export const insertDatasetDataVector = async ({ model, diff --git a/packages/service/common/vectorStore/milvus/class.ts b/packages/service/common/vectorStore/milvus/class.ts index b0f640891622..e9634b0b15b7 100644 --- a/packages/service/common/vectorStore/milvus/class.ts +++ b/packages/service/common/vectorStore/milvus/class.ts @@ -297,6 +297,20 @@ export class MilvusCtrl { return total; }; + getVectorCountByDatasetId = async (teamId: string, datasetId: string) => { + const client = await this.getClient(); + + const result = await client.query({ + collection_name: DatasetVectorTableName, + output_fields: ['count(*)'], + filter: `(teamId == "${String(teamId)}") and (dataset == "${String(datasetId)}")` + }); + + const total = result.data?.[0]?.['count(*)'] as number; + + return total; + }; + getVectorDataByTime = async (start: Date, end: Date) => { const client = await this.getClient(); const startTimestamp = new Date(start).getTime(); diff --git a/packages/service/common/vectorStore/pg/class.ts b/packages/service/common/vectorStore/pg/class.ts index ebd80f17d702..cfd4ef386284 100644 --- a/packages/service/common/vectorStore/pg/class.ts +++ b/packages/service/common/vectorStore/pg/class.ts @@ -205,13 +205,6 @@ export class PgVectorCtrl { }); } }; - getVectorCountByTeamId = async (teamId: string) => { - const total = await PgClient.count(DatasetVectorTableName, { - where: [['team_id', String(teamId)]] - }); - - return total; - }; getVectorDataByTime = async (start: Date, end: Date) => { const { rows } = await PgClient.query<{ id: string; @@ -230,4 +223,18 @@ export class PgVectorCtrl { datasetId: item.dataset_id })); }; + getVectorCountByTeamId = async (teamId: string) => { + const total = await PgClient.count(DatasetVectorTableName, { + where: [['team_id', String(teamId)]] + }); + + return total; + }; + getVectorCountByDatasetId = async (teamId: string, datasetId: string) => { + const total = await PgClient.count(DatasetVectorTableName, { + where: [['team_id', String(teamId)], 'and', ['dataset_id', String(datasetId)]] + }); + + return total; + }; } diff --git a/packages/service/core/workflow/dispatch/chat/oneapi.ts b/packages/service/core/workflow/dispatch/chat/oneapi.ts index a6d5392e907c..18cc39335012 100644 --- a/packages/service/core/workflow/dispatch/chat/oneapi.ts +++ b/packages/service/core/workflow/dispatch/chat/oneapi.ts @@ -112,7 +112,7 @@ export const dispatchChatCompletion = async (props: ChatProps): Promise { + (() => { // censor model and system key if (modelConstantsData.censor && !user.openaiAccount?.key) { return postTextCensor({ diff --git a/packages/service/core/workflow/dispatch/index.ts b/packages/service/core/workflow/dispatch/index.ts index 8f62094143ba..ad6f2988b395 100644 --- a/packages/service/core/workflow/dispatch/index.ts +++ b/packages/service/core/workflow/dispatch/index.ts @@ -469,7 +469,7 @@ export async function dispatchWorkFlow(data: Props): Promise item.isEntry); - console.log(runtimeEdges); + // reset entry // runtimeNodes.forEach((item) => { // item.isEntry = false; diff --git a/packages/web/components/common/Tabs/FillRowTabs.tsx b/packages/web/components/common/Tabs/FillRowTabs.tsx index f55b2f76c788..b788e4271294 100644 --- a/packages/web/components/common/Tabs/FillRowTabs.tsx +++ b/packages/web/components/common/Tabs/FillRowTabs.tsx @@ -31,6 +31,7 @@ const FillRowTabs = ({ list, value, onChange, py = '7px', px = '12px', ...props key={item.value} flex={'1 0 0'} alignItems={'center'} + justifyContent={'center'} cursor={'pointer'} borderRadius={'md'} px={px} diff --git a/packages/web/i18n/en/app.json b/packages/web/i18n/en/app.json index f4bea3119987..e476f1f16242 100644 --- a/packages/web/i18n/en/app.json +++ b/packages/web/i18n/en/app.json @@ -176,6 +176,8 @@ "read_files_result_desc": "The original text of the document consists of the file name and the document content. Multiple files are separated by horizontal lines.", "read_files_tip": "Parse all uploaded documents in the conversation and return the corresponding document content", "select_description": "Select description", + "select_description_placeholder": "For example: \n\nAre there any tomatoes in the refrigerator?", + "select_description_tip": "You can add a descriptive text to explain to users what each option represents.", "select_result": "Select result", "template": { "communication": "Communication" diff --git a/packages/web/i18n/en/common.json b/packages/web/i18n/en/common.json index 4aa5c41cb8bd..6337140dddd5 100644 --- a/packages/web/i18n/en/common.json +++ b/packages/web/i18n/en/common.json @@ -881,11 +881,11 @@ "AI function": "AI function", "AI response switch tip": "If you wish the current node not to output content, you can turn off this switch. AI output content will not be displayed to the user, you can manually use 'AI response content' for special processing.", "AI support tool tip": "Supports function calls model, can better use tool invocation.", - "Basic Node": "Basic function", + "Basic Node": "Basic", "Query extension": "Question optimization", - "System Plugin": "System plugin", + "System Plugin": "System", "System input module": "System input", - "Team Plugin": "Team plugin", + "Team Plugin": "Team", "Tool module": "Tool", "UnKnow Module": "Unknown module", "http body placeholder": "Same syntax as APIFox" @@ -1091,6 +1091,7 @@ "confirm_pay": "confirm payment", "get_pay_QR": "Get the recharge QR code", "need_pay": "Need to pay", + "need_to_pay": "Actually paid", "new_package_price": "New package price", "notice": "Do not close the page", "old_package_price": "Old package price", diff --git a/packages/web/i18n/zh/app.json b/packages/web/i18n/zh/app.json index e5a5009c3979..f9dafc2fcf1a 100644 --- a/packages/web/i18n/zh/app.json +++ b/packages/web/i18n/zh/app.json @@ -154,6 +154,8 @@ "read_files_result_desc": "文档原文,由文件名和文档内容组成,多个文件之间通过横线隔开。", "read_files_tip": "解析对话中所有上传的文档,并返回对应文档内容", "select_description": "说明文字", + "select_description_placeholder": "例如: \n冰箱里是否有西红柿?", + "select_description_tip": "你可以添加一段说明文字,用以向用户说明每个选项代表的含义。", "select_result": "选择的结果", "template": { "communication": "通信" diff --git a/packages/web/i18n/zh/common.json b/packages/web/i18n/zh/common.json index 0155c8e0d848..739e9e5fb3a4 100644 --- a/packages/web/i18n/zh/common.json +++ b/packages/web/i18n/zh/common.json @@ -19,9 +19,6 @@ "package_overlay_a": "可以的。每次购买的资源包都是独立的,在其有效期内将会叠加使用。AI积分会优先扣除最先过期的资源包。", "package_overlay_q": "额外资源包可以叠加么?" }, - "yes": "是", - "no": "否", - "back": "返回", "Folder": "文件夹", "Login": "登录", "Move": "移动", @@ -32,6 +29,7 @@ "UnKnow": "未知", "Warning": "提示", "add_new": "新增", + "back": "返回", "chose_condition": "选择条件", "chosen": "已选", "classification": "分类", @@ -138,7 +136,6 @@ "Detail": "详情", "Documents": "文档", "Done": "完成", - "have_done": "已完成", "Edit": "编辑", "Exit": "退出", "Expired Time": "过期时间", @@ -194,11 +191,7 @@ "Set Name": "取个名字", "Setting": "设置", "Status": "状态", - "submitting": "提交中", - "submit_success": "提交成功", "Submit failed": "提交失败", - "submitted": "已提交", - "Success": "成功", "Sync success": "同步成功", "Team": "团队", @@ -247,6 +240,7 @@ "empty": "这个目录已经没东西可选了~", "open_dataset": "打开知识库" }, + "have_done": "已完成", "input": { "Repeat Value": "有重复的值" }, @@ -268,6 +262,9 @@ "error tip": "语音转文字失败", "not support": "您的浏览器不支持语音输入" }, + "submit_success": "提交成功", + "submitted": "已提交", + "submitting": "提交中", "support": "支持", "system": { "Commercial version function": "请升级商业版后使用该功能:https://fastgpt.in", @@ -1065,7 +1062,7 @@ "include": "包含标准套餐与额外资源包", "node_info": "调整该模块会对工具调用时机有影响。\n你可以通过精确的描述该模块功能,引导模型进行工具调用。", "old_version_attention": "检测到您的高级编排为旧版,系统将为您自动格式化成新版工作流。\n\n由于版本差异较大,会导致一些工作流无法正常排布,请重新手动连接工作流。如仍异常,可尝试删除对应节点后重新添加。\n\n你可以直接点击调试进行工作流测试,调试完毕后点击发布。直到你点击发布,新工作流才会真正保存生效。\n\n在你发布新工作流前,自动保存不会生效。", - "open_api_notice": "可以填写 OpenAI/OneAPI的相关秘钥。如果你填写了该内容,在线上平台使用【AI对话】、【问题分类】和【内容提取】将会走你填写的Key,不会计费。请注意你的Key 是否有访问对应模型的权限。GPT模型可以选择 FastAI。", + "open_api_notice": "可以填写 OpenAI/OneAPI的相关密钥。如果你填写了该内容,在线上平台使用【AI对话】、【问题分类】和【内容提取】将会走你填写的Key,不会计费。请注意你的Key 是否有访问对应模型的权限。GPT模型可以选择 FastAI。", "open_api_placeholder": "请求地址,默认为 openai 官方。可填中转地址,未自动补全 \"v1\"", "resource": "资源用量" }, @@ -1082,6 +1079,7 @@ "Tools": "工具" }, "new_create": "新建", + "no": "否", "no_data": "暂无数据", "no_laf_env": "系统未配置Laf环境", "not_yet_introduced": "暂无介绍", @@ -1093,9 +1091,10 @@ "confirm_pay": "确认支付", "get_pay_QR": "获取充值二维码", "need_pay": "需支付", + "need_to_pay": "实付", "new_package_price": "新套餐价格", "notice": "请勿关闭页面", - "old_package_price": "旧套餐价格", + "old_package_price": "旧套餐余额", "other": "其他金额,请取整数", "to_recharge": "余额不足,去充值", "wechat": "请微信扫码支付: {{price}}元,请勿关闭页面", @@ -1162,10 +1161,10 @@ }, "openapi": { "Api baseurl": "API 根地址", - "Api manager": "API 秘钥管理", + "Api manager": "API 密钥管理", "Copy success": "已复制 API 地址", - "New api key": "新的 API 秘钥", - "New api key tip": "请保管好你的秘钥,秘钥不会再次展示~" + "New api key": "新的 API 密钥", + "New api key tip": "请保管好你的密钥,密钥不会再次展示~" }, "outlink": { "Delete link tip": "确认删除该免登录链接?删除后,该链接将会立即失效,对话日志仍会保留,请确认!", @@ -1243,16 +1242,7 @@ "Ai point every thousand tokens": "{{points}} 积分/1K tokens", "Amount": "金额", "Bills": "账单与开票", - "invoicing": "开票", - "invoice_amount": "开票金额", - "bill_detail": "账单详情", - "billable_invoice": "可开票账单", - "apply_invoice": "申请开票", "Buy": "购买", - "invoice_detail": "发票详情", - "invoice_info": "发票将在 3-7 个工作日内发送至邮箱,请耐心等待", - "no_invoice": "暂无开票记录", - "has_invoice": "是否已开票", "Confirm pay": "支付确认", "Not sufficient": "您的 AI 积分不足,请先升级套餐或购买额外 AI 积分后继续使用。", "Plan expired time": "套餐到期时间", @@ -1260,23 +1250,7 @@ "Standard Plan Detail": "套餐详情", "To read plan": "查看套餐", "amount_0": "购买数量不能为0", - "use_default": "使用默认抬头", - "bill_tag": { - "bill": "账单记录", - "invoice": "开票记录", - "default_header": "默认抬头" - }, - "invoice_data": { - "organization_name": "组织名称", - "unit_code": "统一信用代码", - "company_address": "公司地址", - "company_phone": "公司电话", - "bank": "开户银行", - "bank_account": "开户账号", - "need_special_invoice": "是否需要专票", - "email": "邮箱地址", - "in_valid": "存在空字段或邮箱格式错误" - }, + "apply_invoice": "申请开票", "bill": { "Number": "订单号", "Status": "状态", @@ -1293,12 +1267,36 @@ "success": "支付成功" } }, + "bill_detail": "账单详情", + "bill_tag": { + "bill": "账单记录", + "default_header": "默认抬头", + "invoice": "开票记录" + }, + "billable_invoice": "可开票账单", "buy_resource": "购买资源包", + "has_invoice": "是否已开票", + "invoice_amount": "开票金额", + "invoice_data": { + "bank": "开户银行", + "bank_account": "开户账号", + "company_address": "公司地址", + "company_phone": "公司电话", + "email": "邮箱地址", + "in_valid": "存在空字段或邮箱格式错误", + "need_special_invoice": "是否需要专票", + "organization_name": "组织名称", + "unit_code": "统一信用代码" + }, + "invoice_detail": "发票详情", + "invoice_info": "发票将在 3-7 个工作日内发送至邮箱,请耐心等待", + "invoicing": "开票", "moduleName": { "index": "索引生成", "qa": "QA 拆分" }, "noBill": "无账单记录~", + "no_invoice": "暂无开票记录", "subscription": { "AI points": "AI 积分", "AI points click to read tip": "每次调用 AI 模型时,都会消耗一定的 AI 积分(类似于 token)。点击可查看详细计算规则。", @@ -1322,8 +1320,6 @@ "Sub plan tip": "免费使用 {{title}} 或升级更高的套餐", "Team plan and usage": "套餐与用量", "Training weight": "训练优先级:{{weight}}", - "web_site_sync": "Web 站点同步", - "rerank": "检索结果重排", "Update extra ai points": "额外 AI 积分", "Update extra dataset size": "额外存储量", "Upgrade plan": "升级套餐", @@ -1343,6 +1339,7 @@ "Year sale": "赠送两个月" }, "point": "积分", + "rerank": "检索结果重排", "standardSubLevel": { "enterprise": "企业版", "experience": "体验版", @@ -1356,7 +1353,8 @@ "extraDatasetSize": "知识库扩容", "extraPoints": "AI 积分套餐", "standard": "套餐订阅" - } + }, + "web_site_sync": "Web 站点同步" }, "usage": { "Ai model": "AI 模型", @@ -1374,7 +1372,8 @@ "Total points": "AI 积分消耗", "Usage Detail": "使用详情", "Whisper": "语音输入" - } + }, + "use_default": "使用默认抬头" } }, "sync_link": "同步链接", @@ -1434,7 +1433,7 @@ "Update password successful": "修改密码成功", "Usage Record": "使用记录", "apikey": { - "key": "API 秘钥" + "key": "API 密钥" }, "confirm_password": "确认密码", "new_password": "新密码", @@ -1494,5 +1493,6 @@ "type": "类型" }, "verification": "验证", - "xx_search_result": "{{key}} 的搜索结果" + "xx_search_result": "{{key}} 的搜索结果", + "yes": "是" } diff --git a/packages/web/i18n/zh/publish.json b/packages/web/i18n/zh/publish.json index 4277fd3fa7f2..484bece90de0 100644 --- a/packages/web/i18n/zh/publish.json +++ b/packages/web/i18n/zh/publish.json @@ -13,7 +13,7 @@ "feishu_bot_desc": "通过 API 直接接入飞书机器人", "feishu_name": "飞书", "key_alias": "key 的别名,仅用于展示", - "key_tips": "你可以使用 API 秘钥访问一些特定的接口(无法访问应用,访问应用需使用应用内的 API key)", + "key_tips": "你可以使用 API 密钥访问一些特定的接口(无法访问应用,访问应用需使用应用内的 API key)", "link_name": "分享链接的名字", "new_feishu_bot": "新增飞书机器人", "official_account": { diff --git a/projects/app/.env.template b/projects/app/.env.template index b403f02c87f9..cb4fef2cafd6 100644 --- a/projects/app/.env.template +++ b/projects/app/.env.template @@ -5,7 +5,7 @@ DEFAULT_ROOT_PSW=123456 DB_MAX_LINK=5 # token TOKEN_KEY=dfdasfdas -# 文件阅读时的秘钥 +# 文件阅读时的密钥 FILE_TOKEN_KEY=filetokenkey # root key, 最高权限 ROOT_KEY=fdafasd diff --git a/projects/app/src/pages/account/components/UsageTable.tsx b/projects/app/src/pages/account/components/UsageTable.tsx index 148283e8dfcb..1489f3ea904b 100644 --- a/projects/app/src/pages/account/components/UsageTable.tsx +++ b/projects/app/src/pages/account/components/UsageTable.tsx @@ -110,7 +110,7 @@ const UsageTable = () => { px={[3, 8]} alignItems={['flex-end', 'center']} > - {tmbList.length > 1 && userInfo?.team?.permission.hasWritePer && ( + {tmbList.length > 1 && userInfo?.team?.permission.hasManagePer && ( {t('common:support.user.team.member')} diff --git a/projects/app/src/pages/app/detail/components/SimpleApp/components/ToolSelectModal.tsx b/projects/app/src/pages/app/detail/components/SimpleApp/components/ToolSelectModal.tsx index 51005021f3c3..10ca2e226cbb 100644 --- a/projects/app/src/pages/app/detail/components/SimpleApp/components/ToolSelectModal.tsx +++ b/projects/app/src/pages/app/detail/components/SimpleApp/components/ToolSelectModal.tsx @@ -66,21 +66,35 @@ const ToolSelectModal = ({ onClose, ...props }: Props & { onClose: () => void }) const [parentId, setParentId] = useState(''); const [searchKey, setSearchKey] = useState(''); - const { data: templates = [], loading: isLoading } = useRequest2( - async () => { - if (templateType === TemplateTypeEnum.systemPlugin) { - return getSystemPlugTemplates({ parentId, searchKey }); - } else if (templateType === TemplateTypeEnum.teamPlugin) { + const { + data: templates = [], + runAsync: loadTemplates, + loading: isLoading + } = useRequest2( + async ({ + type = templateType, + parentId = '', + searchVal = searchKey + }: { + type?: TemplateTypeEnum; + parentId?: ParentIdType; + searchVal?: string; + }) => { + if (type === TemplateTypeEnum.systemPlugin) { + return getSystemPlugTemplates({ parentId, searchKey: searchVal }); + } else if (type === TemplateTypeEnum.teamPlugin) { return getTeamPlugTemplates({ parentId, - searchKey, + searchKey: searchVal, type: [AppTypeEnum.folder, AppTypeEnum.httpPlugin, AppTypeEnum.plugin] }); } }, { - manual: false, - throttleWait: 300, + onSuccess(_, [{ type = templateType, parentId = '' }]) { + setTemplateType(type); + setParentId(parentId); + }, refreshDeps: [templateType, searchKey, parentId], errorToast: t('common:core.module.templates.Load plugin error') } @@ -97,9 +111,20 @@ const ToolSelectModal = ({ onClose, ...props }: Props & { onClose: () => void }) } ); - useEffect(() => { - setParentId(''); - }, [templateType, searchKey]); + const onUpdateParentId = useCallback( + (parentId: ParentIdType) => { + loadTemplates({ + parentId + }); + }, + [loadTemplates] + ); + + useRequest2(() => loadTemplates({ searchVal: searchKey }), { + manual: false, + throttleWait: 300, + refreshDeps: [searchKey] + }); return ( void }) py={'5px'} px={'15px'} value={templateType} - onChange={(e) => setTemplateType(e as TemplateTypeEnum)} + onChange={(e) => + loadTemplates({ + type: e as TemplateTypeEnum, + parentId: null + }) + } /> @@ -149,7 +179,7 @@ const ToolSelectModal = ({ onClose, ...props }: Props & { onClose: () => void }) paths={paths} FirstPathDom={null} onClick={() => { - setParentId(null); + onUpdateParentId(null); }} /> @@ -158,7 +188,7 @@ const ToolSelectModal = ({ onClose, ...props }: Props & { onClose: () => void }) @@ -180,7 +210,7 @@ const RenderList = React.memo(function RenderList({ }: Props & { templates: NodeTemplateListItemType[]; isLoadingData: boolean; - setParentId: React.Dispatch>; + setParentId: (parentId: ParentIdType) => any; showCost?: boolean; }) { const { t } = useTranslation(); diff --git a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/NodeTemplatesModal.tsx b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/NodeTemplatesModal.tsx index e2865a5e5a5f..c23407925bb3 100644 --- a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/NodeTemplatesModal.tsx +++ b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/NodeTemplatesModal.tsx @@ -1,8 +1,9 @@ import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { Box, - Divider, Flex, + Grid, + HStack, IconButton, Input, InputGroup, @@ -47,6 +48,7 @@ import { moduleTemplatesFlat } from '@fastgpt/global/core/workflow/template/cons import { cloneDeep } from 'lodash'; import { useSystem } from '@fastgpt/web/hooks/useSystem'; import CostTooltip from '@/components/core/app/plugin/CostTooltip'; +import { useUserStore } from '@/web/support/user/useUserStore'; type ModuleTemplateListProps = { isOpen: boolean; @@ -57,7 +59,7 @@ type RenderListProps = { type: TemplateTypeEnum; onClose: () => void; parentId: ParentIdType; - setParentId: React.Dispatch>; + setParentId: (parenId: ParentIdType) => any; }; enum TemplateTypeEnum { @@ -66,11 +68,13 @@ enum TemplateTypeEnum { 'teamPlugin' = 'teamPlugin' } -const sliderWidth = 420; +const sliderWidth = 460; const NodeTemplatesModal = ({ isOpen, onClose }: ModuleTemplateListProps) => { const { t } = useTranslation(); const router = useRouter(); + const { loadAndGetTeamMembers } = useUserStore(); + const [parentId, setParentId] = useState(''); const [searchKey, setSearchKey] = useState(''); const { feConfigs } = useSystemStore(); @@ -79,11 +83,16 @@ const NodeTemplatesModal = ({ isOpen, onClose }: ModuleTemplateListProps) => { (v) => v ); + const { data: members = [] } = useRequest2(loadAndGetTeamMembers, { + manual: !feConfigs.isPlus + }); + const [templateType, setTemplateType] = useState(TemplateTypeEnum.basic); const { data: basicNodes } = useRequest2( async () => { if (templateType === TemplateTypeEnum.basic) { + console.log(1111); return basicNodeTemplates .filter((item) => { // unique node filter @@ -115,43 +124,61 @@ const NodeTemplatesModal = ({ isOpen, onClose }: ModuleTemplateListProps) => { }, { manual: false, - throttleWait: 300, - refreshDeps: [basicNodeTemplates, nodeList, hasToolNode, templateType, searchKey, parentId] + refreshDeps: [members, basicNodeTemplates, nodeList, hasToolNode, templateType] } ); - const { data: teamAndSystemApps, loading: isLoadingTeamApp } = useRequest2( - async () => { - if (templateType === TemplateTypeEnum.teamPlugin) { - return getTeamPlugTemplates({ + const { + data: teamAndSystemApps, + loading: isLoading, + runAsync: loadNodeTemplates + } = useRequest2( + async ({ + parentId = '', + type = templateType, + searchVal = searchKey + }: { + parentId?: ParentIdType; + type?: TemplateTypeEnum; + searchVal?: string; + }) => { + if (type === TemplateTypeEnum.teamPlugin) { + const plugins = await getTeamPlugTemplates({ parentId, - searchKey, + searchKey: searchVal, type: [AppTypeEnum.folder, AppTypeEnum.httpPlugin, AppTypeEnum.plugin] }).then((res) => res.filter((app) => app.id !== appId)); + + return plugins.map((plugin) => { + const member = members.find((member) => member.tmbId === plugin.tmbId); + return { + ...plugin, + author: member?.memberName, + authorAvatar: member?.avatar + }; + }); } - if (templateType === TemplateTypeEnum.systemPlugin) { + if (type === TemplateTypeEnum.systemPlugin) { return getSystemPlugTemplates({ - searchKey, + searchKey: searchVal, parentId }); } }, { - manual: false, - throttleWait: 300, - refreshDeps: [templateType, searchKey, parentId] + onSuccess(res, [{ parentId = '', type = templateType }]) { + setParentId(parentId); + setTemplateType(type); + }, + refreshDeps: [searchKey, templateType] } ); - const isLoading = isLoadingTeamApp; const templates = useMemo( () => basicNodes || teamAndSystemApps || [], [basicNodes, teamAndSystemApps] ); - useEffect(() => { - setParentId(''); - }, [templateType, searchKey]); - + // Get paths const { data: paths = [] } = useRequest2( () => { if (templateType === TemplateTypeEnum.teamPlugin) return getAppFolderPath(parentId); @@ -163,6 +190,29 @@ const NodeTemplatesModal = ({ isOpen, onClose }: ModuleTemplateListProps) => { } ); + const onUpdateParentId = useCallback( + (parentId: ParentIdType) => { + loadNodeTemplates({ + parentId + }); + }, + [loadNodeTemplates] + ); + + // Init load refresh templates + useRequest2( + () => + loadNodeTemplates({ + parentId: '', + searchVal: searchKey + }), + { + manual: false, + throttleWait: 300, + refreshDeps: [searchKey] + } + ); + const Render = useMemo(() => { return ( <> @@ -198,7 +248,7 @@ const NodeTemplatesModal = ({ isOpen, onClose }: ModuleTemplateListProps) => { overflow={isOpen ? 'none' : 'hidden'} > {/* Header */} - + {/* Tabs */} @@ -206,24 +256,29 @@ const NodeTemplatesModal = ({ isOpen, onClose }: ModuleTemplateListProps) => { list={[ { icon: 'core/modules/basicNode', - label: t('core.module.template.Basic Node'), + label: t('common:core.module.template.Basic Node'), value: TemplateTypeEnum.basic }, { icon: 'core/modules/systemPlugin', - label: t('core.module.template.System Plugin'), + label: t('common:core.module.template.System Plugin'), value: TemplateTypeEnum.systemPlugin }, { icon: 'core/modules/teamPlugin', - label: t('core.module.template.Team Plugin'), + label: t('common:core.module.template.Team Plugin'), value: TemplateTypeEnum.teamPlugin } ]} width={'100%'} py={'5px'} value={templateType} - onChange={(e) => setTemplateType(e as TemplateTypeEnum)} + onChange={(e) => { + loadNodeTemplates({ + type: e as TemplateTypeEnum, + parentId: '' + }); + }} /> {/* close icon */} @@ -291,7 +346,7 @@ const NodeTemplatesModal = ({ isOpen, onClose }: ModuleTemplateListProps) => { !searchKey && parentId && ( - + )} @@ -300,12 +355,26 @@ const NodeTemplatesModal = ({ isOpen, onClose }: ModuleTemplateListProps) => { type={templateType} onClose={onClose} parentId={parentId} - setParentId={setParentId} + setParentId={onUpdateParentId} /> ); - }, [isOpen, onClose, isLoading, t, templateType, searchKey, parentId, paths, templates, router]); + }, [ + isOpen, + onClose, + isLoading, + t, + templateType, + feConfigs.systemPluginCourseUrl, + searchKey, + parentId, + paths, + onUpdateParentId, + templates, + loadNodeTemplates, + router + ]); return Render; }; @@ -320,7 +389,6 @@ const RenderList = React.memo(function RenderList({ setParentId }: RenderListProps) { const { t } = useTranslation(); - const { appT } = useI18n(); const { feConfigs } = useSystemStore(); const { isPc } = useSystem(); @@ -412,11 +480,31 @@ const RenderList = React.memo(function RenderList({ [computedNewNodeName, reactFlowWrapper, setLoading, setNodes, t, toast, x, y, zoom] ); + const gridStyle = useMemo(() => { + if (type === TemplateTypeEnum.teamPlugin) { + return { + gridTemplateColumns: ['1fr', '1fr'], + py: 2, + avatarSize: '2rem', + authorInName: false, + authorInRight: true + }; + } + + return { + gridTemplateColumns: ['1fr', '1fr 1fr'], + py: 3, + avatarSize: '1.75rem', + authorInName: true, + authorInRight: false + }; + }, [type]); + const Render = useMemo(() => { return templates.length === 0 ? ( - + ) : ( - + {formatTemplates.map((item, i) => ( {item.label && formatTemplates.length > 1 && ( - + {t(item.label as any)} )} - <> + {item.list.map((template) => ( - - {t(template.name as any)} - - {template.author !== undefined && ( - - {`by ${template.author || feConfigs.systemTitle}`} + + + {t(template.name as any)} + {gridStyle.authorInName && template.author !== undefined && ( + + {`by ${template.author || feConfigs.systemTitle}`} + + )} + + + {gridStyle.authorInRight && template.authorAvatar && template.author && ( + + + + {template.author} + + )} ))} - + ))} ); }, [ - appT, + feConfigs.systemTitle, formatTemplates, + gridStyle, isPc, isSystemPlugin, onAddNode, diff --git a/projects/app/src/pages/app/list/components/TemplateMarketModal.tsx b/projects/app/src/pages/app/list/components/TemplateMarketModal.tsx index e7070d282c4c..1a6eae4b806d 100644 --- a/projects/app/src/pages/app/list/components/TemplateMarketModal.tsx +++ b/projects/app/src/pages/app/list/components/TemplateMarketModal.tsx @@ -179,7 +179,7 @@ const TemplateMarketModal = ({ postUpdateStandardSub(data), - onSuccess() { - refetchTeamSubPlan(); - router.reload(); - }, - successToast: t('common:support.wallet.subscription.Standard update success'), - errorToast: t('common:support.wallet.subscription.Standard update fail') - }); + const { runAsync: onclickUpdateStandardPlan, loading: isUpdatingStandardPlan } = useRequest2( + postUpdateStandardSub, + { + onSuccess() { + refetchTeamSubPlan(); + router.reload(); + }, + successToast: t('common:support.wallet.subscription.Standard update success'), + errorToast: t('common:support.wallet.subscription.Standard update fail') + } + ); const { mutate: onclickPreCheckStandPlan, isLoading: isCheckingStandardPlan } = useRequest({ mutationFn: (data: StandardSubPlanParams) => postCheckStandardSub(data), @@ -332,6 +333,8 @@ const ConfirmPayModal = ({ } }); + const { runAsync: onPay, loading: onPaying } = useRequest2(async () => onConfirmPay()); + return ( - {t('common:pay.balance_notice')} + {t('common:pay.need_to_pay')} {t('common:pay.yuan', { amount: formatPayPrice @@ -375,7 +378,7 @@ const ConfirmPayModal = ({ })} {teamBalance >= payPrice ? ( - ) : ( diff --git a/projects/app/src/web/core/app/api/plugin.ts b/projects/app/src/web/core/app/api/plugin.ts index 388e24537628..00cc1d88f592 100644 --- a/projects/app/src/web/core/app/api/plugin.ts +++ b/projects/app/src/web/core/app/api/plugin.ts @@ -17,7 +17,8 @@ import { GetSystemPluginTemplatesBody } from '@/pages/api/core/app/plugin/getSys /* ============ team plugin ============== */ export const getTeamPlugTemplates = (data?: ListAppBody) => getMyApps(data).then((res) => - res.map((app) => ({ + res.map((app) => ({ + tmbId: app.tmbId, id: app._id, pluginId: app._id, isFolder: app.type === AppTypeEnum.folder || app.type === AppTypeEnum.httpPlugin, diff --git a/projects/app/src/web/support/user/useUserStore.ts b/projects/app/src/web/support/user/useUserStore.ts index 21d7630e98e3..c1835f075aaf 100644 --- a/projects/app/src/web/support/user/useUserStore.ts +++ b/projects/app/src/web/support/user/useUserStore.ts @@ -6,6 +6,8 @@ import type { UserType } from '@fastgpt/global/support/user/type.d'; import { getTokenLogin, putUserInfo } from '@/web/support/user/api'; import { FeTeamPlanStatusType } from '@fastgpt/global/support/wallet/sub/type'; import { getTeamPlanStatus } from './team/api'; +import { getTeamMembers } from '@/web/support/user/team/api'; +import { TeamMemberItemType } from '@fastgpt/global/support/user/team/type'; type State = { systemMsgReadId: string; @@ -18,6 +20,9 @@ type State = { teamPlanStatus: FeTeamPlanStatusType | null; initTeamPlanStatus: () => Promise; + + teamMembers: TeamMemberItemType[]; + loadAndGetTeamMembers: () => Promise; }; export const useUserStore = create()( @@ -78,6 +83,17 @@ export const useUserStore = create()( }); return res; }); + }, + teamMembers: [], + loadAndGetTeamMembers: async () => { + if (get().teamMembers.length) return Promise.resolve(get().teamMembers); + + const res = await getTeamMembers(); + set((state) => { + state.teamMembers = res; + }); + + return res; } })), {