Revert "sub plan page (#885)" (#886)

This reverts commit 443ad37b6a.
This commit is contained in:
Archer
2024-02-23 17:48:15 +08:00
committed by GitHub
parent 443ad37b6a
commit fd9b6291af
246 changed files with 4281 additions and 6286 deletions

View File

@@ -13,7 +13,8 @@
"maxResponse": 4000,
"quoteMaxToken": 13000,
"maxTemperature": 1.2,
"charsPointsPrice": 0,
"inputPrice": 0,
"outputPrice": 0,
"censor": false,
"vision": false,
"datasetProcess": false,
@@ -31,7 +32,8 @@
"maxResponse": 16000,
"quoteMaxToken": 13000,
"maxTemperature": 1.2,
"charsPointsPrice": 0,
"inputPrice": 0,
"outputPrice": 0,
"censor": false,
"vision": false,
"datasetProcess": true,
@@ -49,7 +51,8 @@
"maxResponse": 4000,
"quoteMaxToken": 100000,
"maxTemperature": 1.2,
"charsPointsPrice": 0,
"inputPrice": 0,
"outputPrice": 0,
"censor": false,
"vision": false,
"datasetProcess": false,
@@ -67,9 +70,10 @@
"maxResponse": 4000,
"quoteMaxToken": 100000,
"maxTemperature": 1.2,
"charsPointsPrice": 0,
"inputPrice": 0,
"outputPrice": 0,
"censor": false,
"vision": true,
"vision": false,
"datasetProcess": false,
"toolChoice": true,
"functionCall": false,
@@ -83,7 +87,8 @@
{
"model": "text-embedding-ada-002",
"name": "Embedding-2",
"charsPointsPrice": 0,
"inputPrice": 0,
"outputPrice": 0,
"defaultToken": 700,
"maxToken": 3000,
"weight": 100
@@ -94,44 +99,22 @@
{
"model": "tts-1",
"name": "OpenAI TTS1",
"charsPointsPrice": 0,
"inputPrice": 0,
"outputPrice": 0,
"voices": [
{
"label": "Alloy",
"value": "alloy",
"bufferId": "openai-Alloy"
},
{
"label": "Echo",
"value": "echo",
"bufferId": "openai-Echo"
},
{
"label": "Fable",
"value": "fable",
"bufferId": "openai-Fable"
},
{
"label": "Onyx",
"value": "onyx",
"bufferId": "openai-Onyx"
},
{
"label": "Nova",
"value": "nova",
"bufferId": "openai-Nova"
},
{
"label": "Shimmer",
"value": "shimmer",
"bufferId": "openai-Shimmer"
}
{ "label": "Alloy", "value": "alloy", "bufferId": "openai-Alloy" },
{ "label": "Echo", "value": "echo", "bufferId": "openai-Echo" },
{ "label": "Fable", "value": "fable", "bufferId": "openai-Fable" },
{ "label": "Onyx", "value": "onyx", "bufferId": "openai-Onyx" },
{ "label": "Nova", "value": "nova", "bufferId": "openai-Nova" },
{ "label": "Shimmer", "value": "shimmer", "bufferId": "openai-Shimmer" }
]
}
],
"whisperModel": {
"model": "whisper-1",
"name": "Whisper1",
"charsPointsPrice": 0
"inputPrice": 0,
"outputPrice": 0
}
}

View File

@@ -45,7 +45,13 @@ const nextConfig = {
},
transpilePackages: ['@fastgpt/*'],
experimental: {
serverComponentsExternalPackages: ['mongoose', 'pg'],
serverComponentsExternalPackages: [
'mongoose',
'pg',
'react',
'@chakra-ui/react',
'@lexical/react'
],
outputFileTracingRoot: path.join(__dirname, '../../')
}
};

View File

@@ -26,7 +26,7 @@
"@tanstack/react-query": "^4.24.10",
"@types/nprogress": "^0.2.0",
"axios": "^1.5.1",
"date-fns": "2.30.0",
"date-fns": "^2.30.0",
"dayjs": "^1.11.7",
"echarts": "^5.4.1",
"echarts-gl": "^2.0.9",
@@ -74,4 +74,4 @@
"eslint-config-next": "13.1.6",
"typescript": "4.9.5"
}
}
}

View File

@@ -2,7 +2,7 @@
1. 新增 - 知识库搜索合并模块。
2. 新增 - 新的 Http 模块,支持更加灵活的参数传入。同时支持了输入输出自动数据类型转化,例如:接口输出的 JSON 类型会自动转成字符串类型,直接给其他模块使用。此外,还补充了一些例子,可在文档中查看。
3. 优化 - 问题优化并入知识库搜索模块,无需单独配置。并且问题优化的同时实现了问题扩展丰富搜索的语义。知识库模块会看到有2个参数配置有一个是多余的如果想让它消失可以删除模块重新增加一个
3. 优化 - 问题补全并入知识库搜索模块,无需单独配置。并且问题补全的同时实现了问题扩展丰富搜索的语义。知识库模块会看到有2个参数配置有一个是多余的如果想让它消失可以删除模块重新增加一个
4. 修复 - 语音输入文件无法上传。
5. 修复 - 对话框重新生成无法使用。
6. [点击查看高级编排介绍文档](https://doc.fastgpt.in/docs/workflow/intro)

View File

@@ -15,7 +15,6 @@
"AI Settings": "AI Settings",
"Advance App TestTip": "The current application is advanced editing mode \n. If you need to switch to [simple mode], please click the save button on the left",
"App Detail": "App Detail",
"Apps Share": "Apps Share",
"Basic Settings": "Basic Settings",
"Chat Debug": "Chat Debug",
"Chat Logs Tips": "Logs record the app's online, shared, and API(chatId is existing) conversations",
@@ -84,7 +83,6 @@
"Delete Success": "Delete Successful",
"Delete Tip": "Delete Confirm",
"Delete Warning": "Warning",
"Detail": "Detail",
"Done": "Done",
"Edit": "Edit",
"Exit": "Exit",
@@ -100,6 +98,8 @@
"Last use time": "Last use time",
"Load Failed": "Load Failed",
"Loading": "Loading",
"Max credit": "Credit",
"Max credit tips": "What is the maximum amount of money that can be consumed by the link? If the link is exceeded, it will be banned. -1 indicates no limit.",
"More settings": "More settings",
"Name": "Name",
"Name Can": "Name Can't Be Empty",
@@ -110,7 +110,6 @@
"Number of words": "{{amount}} words",
"OK": "OK",
"Opened": "Opened",
"Other": "Other",
"Output": "Output",
"Params": "Params",
"Password inconsistency": "Password inconsistency",
@@ -133,20 +132,17 @@
"Select template": "Select template",
"Set Avatar": "Set Avatar",
"Set Name": "Make a nice name",
"Set Team Tags": "Set Team Tags",
"Setting": "Setting",
"Status": "Status",
"Submit failed": "Submit failed",
"Submit success": "Update Success",
"Team": "Team",
"Team Tags Set": "Team Tags",
"Test": "Test",
"Time": "Time",
"Un used": "Unused",
"UnKnow": "UnKnow",
"UnKnow Source": "UnKnow Source",
"Unlimited": "Unlimited",
"Update": "Update",
"Update Failed": "Update Failed",
"Update Success": "Update Success",
"Update Successful": "Update Successful",
@@ -504,7 +500,6 @@
"Manual collection": "Manual collection",
"My Dataset": "My Dataset",
"Name": "Name",
"Query extension intro": "If the problem completion function is enabled, the accuracy of knowledge base search can be improved in continuous conversations. After this function is enabled, when searching the knowledge base, AI will be used to complete the missing information of the problem according to the conversation records.",
"Quote Length": "Quote Length",
"Read Dataset": "Read Dataset",
"Search score tip": "{{scoreText}}Here are the rankings and scores:\n----\n{{detailScore}}",
@@ -614,8 +609,7 @@
"Down load csv template": "Down load csv template",
"Embedding Estimated Price Tips": "Index billing: {{price}}/1k chars",
"Estimated Price": "Estimated Price: : {{amount}}{{unit}}",
"Estimated Price Tips": "QA charges\nInput: 1k chars={{charsPointsPrice}} points",
"Estimated points": "About {{points}} points",
"Estimated Price Tips": "QA charges\nInput: {{inputPrice}}/1k tokens\nOutput: {{outputPrice}}/1k tokens",
"Fetch Error": "Get link failed",
"Fetch Url": "Url",
"Fetch url placeholder": "Up to 10 links, one per line.",
@@ -685,7 +679,7 @@
"Source name": "Source",
"Top K": "Top K",
"Using cfr": "Open query extension",
"Using query extension": "Open query extension",
"Using query extension": "",
"mode": {
"embedding": "Vector recall",
"embedding desc": "Use vectors for text correlation queries",
@@ -849,8 +843,8 @@
"dynamicTargetInput": "dynamic Target Input",
"input": "Input",
"selectApp": "App Selector",
"selectChatModel": "Select Chat Model",
"selectDataset": "Dataset Selector",
"selectLLMModel": "Select Chat Model",
"switch": "Switch",
"target": "Target Data",
"textarea": "Textarea"
@@ -862,7 +856,6 @@
"Ai response content": "Will be triggered after the stream reply is complete",
"New context": "Concatenate the reply content with history and return it as a new context",
"Quote": "Always return an array, if you want the search results to be empty to perform additional operations, you need to use the above two inputs and the trigger of the target module",
"query extension result": "Output as an array of strings to connect the result directly to the 'Question' of the knowledge base search",
"running done": "Triggered when the module call finish"
},
"label": {
@@ -871,7 +864,7 @@
"Quote": "Quote",
"Search result empty": "Search result empty",
"Search result not empty": "Search result not empty",
"query extension result": "Response text",
"cfr result": "Response text",
"result false": "False",
"result true": "True",
"running done": "done",
@@ -1110,7 +1103,6 @@
"navbar": {
"Account": "Account",
"Apps": "App",
"Apps Share": "",
"Chat": "Chat",
"Datasets": "DataSet",
"Module": "Module",
@@ -1175,9 +1167,6 @@
"Usage": "Usage"
},
"outlink": {
"Max usage points": "Max usage",
"Max usage points tip": "The maximum number of credits allowed for this link will not be used. -1 indicates no limit.",
"Usage points": "Usage points",
"share": {
"Response Quote": "Show Quote",
"Response Quote tips": "The referenced content is returned in the share link, but the user is not allowed to download the original document."
@@ -1186,16 +1175,9 @@
"subscription": {
"Cancel subscription": "Cancel"
},
"team": {
"limit": {
"No permission rerank": "Not permission to rerank, please upgrade your plan"
}
},
"user": {
"Avatar": "Avatar",
"Need to login": "Please log in first",
"Price": "Price",
"User self info": "My info",
"auth": {
"Sending Code": "Sending"
},
@@ -1210,10 +1192,7 @@
}
},
"wallet": {
"Amount": "Amount",
"Balance not enough tip": "The balance is insufficient, please go to the account page first",
"Bills": "Bill",
"Buy": "Buy",
"Buy more": "Buy more",
"Confirm pay": "Confirm pay",
"Pay error": "Pay error",
@@ -1221,60 +1200,32 @@
"bill": {
"AI Model": "AI Model",
"AI Type": "AI Type",
"Number": "Number",
"Price": "Price",
"Status": "Status",
"Type": "Bill type",
"payWay": {
"Way": "Pay way",
"balance": "Balance",
"wx": "Wechat"
},
"status": {
"closed": "CLOSED",
"notpay": "NOT_PAY",
"refund": "REFUND",
"success": "SUCCESS"
}
"Price": "Price"
},
"moduleName": {
"index": "Index Generation",
"qa": "QA Generation"
},
"noBill": "Not Bills",
"subscription": {
"AI points": "AI points",
"Ai points": "AI Points Standard",
"Buy now": "Buy now",
"Change will take effect after the current subscription expires": "Change will take effect after the current subscription expires",
"Current dataset store": "Current dataset store subscription",
"Current extra ai points": "Current extra points",
"Current plan": "Current plan",
"Dataset store": "Dataset store size",
"Dataset store price tip": "Deduct it from the account balance on the 1st of each month",
"Expand size": "Expand size",
"Extra ai points": "Extra ai points",
"Extra dataset size": "Extra dataset size",
"Extra plan": "Extra Plan",
"Extra plan tip": "When the standard plan is not enough, you can purchase an additional plan to continue using",
"FAQ": "Pricing FAQs",
"Month amount": "Month",
"Next extra ai points": "Next extra points",
"Next plan": "Future plan",
"Next sub dataset size": "",
"Nonsupport": "Nonsupport",
"Refund plan and pay confirm": "There is no extra cost for you to switch this package and {{amount}} will be refunded to the balance.",
"Stand plan level": "Sub plan",
"Standard plan pay confirm": "To switch this package, you need to pay {{payPrice}} Yuan.",
"Standard update fail": "Update plan failed.",
"Standard update success": "Change subscription plan successful!",
"Sub plan": "Pricing Plans",
"Sub plan tip": "Use FastGPT for free or upgrade to a higher plan",
"Team plan and usage": "Plan and usage",
"Training weight": "Training weight: {{weight}}",
"Update extra ai points": "AI Points",
"Update extra dataset size": "Dataset size",
"Upgrade plan": "Upgrade plan",
"Update extra dataset size": "Update size",
"function": {
"History store": "",
"Max app": "",
@@ -1285,7 +1236,6 @@
},
"mode": {
"Month": "Monthly",
"Period": "Period",
"Year": "Yearly",
"Year sale": "2 months free"
},
@@ -1298,38 +1248,9 @@
"team": ""
},
"type": {
"balance": "Add Balance",
"extraDatasetSize": "Extra dataset size",
"extraPoints": "AI Points",
"standard": "Sub plan"
"standard": ""
}
},
"usage": {
"Ai model": "Ai Model",
"App name": "App name",
"Audio Speech": "Audio Speech",
"Bill Module": "Bill Detail",
"Chars length": "Chars length",
"Data Length": "Data length",
"Dataset store": "Dataset store",
"Duration": "Duration(s)",
"Extension Input Token Length": "Extension input tokens",
"Extension Output Token Length": "Extension output tokens",
"Extension result": "Extension result",
"Input Token Length": "Input tokens",
"Module name": "Module name",
"Number": "Bill ID",
"Output Token Length": "Output tokens",
"ReRank": "ReRank",
"Source": "Source",
"Text Length": "Text length",
"Time": "Time",
"Token Length": "Tokens",
"Total": "Total",
"Total points": "AI points usage",
"Usage Detail": "Usage Detail",
"Whisper": "Whisper",
"bill username": "User"
}
}
},
@@ -1371,7 +1292,6 @@
"Set OpenAI Account Failed": "Set OpenAI account failed",
"Sign Out": "Sign Out",
"Source": "Source",
"Standard Detail": "",
"Team": "Team",
"Time": "Time",
"Timezone": "Timezone",
@@ -1419,11 +1339,7 @@
"Select Team": "Select Team",
"Set Name": "Team Name",
"Switch Team Failed": "Switch Team Failed",
"Tags Async": "Tag synchronization",
"Team Name": "Team Name",
"Team Tags Async": "Team Tags Async",
"Team Tags Async Success": "Team Tags Async Success",
"Team Tags Async Tip": "Fill in the tag sync connection to get the latest",
"Update Team": "Update Team",
"invite": {
"Accept Confirm": "Want to join the team?",
@@ -1448,5 +1364,37 @@
"Visitor": "Visitor"
}
}
},
"wallet": {
"bill": {
"Ai model": "Ai Model",
"App name": "App name",
"Audio Speech": "Audio Speech",
"Bill Module": "Bill Detail",
"Chars length": "Chars length",
"Data Length": "Data length",
"Dataset store": "",
"Duration": "Duration(s)",
"Extension Input Token Length": "Extension input tokens",
"Extension Output Token Length": "Extension output tokens",
"Extension result": "Extension result",
"Input Token Length": "Input tokens",
"Module name": "Module name",
"Next Step Guide": "",
"Number": "Bill ID",
"Output Token Length": "Output tokens",
"ReRank": "ReRank",
"Source": "Source",
"Text Length": "Text length",
"Time": "Time",
"Token Length": "Tokens",
"Total": "Total",
"Whisper": "Whisper",
"bill username": "User"
},
"moduleName": {
"index": "Index Generation",
"qa": "QA Generation"
}
}
}

View File

@@ -15,7 +15,6 @@
"AI Settings": "AI 配置",
"Advance App TestTip": "当前应用可能为高级编排模式\n如需切换为【简易模式】请点击左侧保存按键",
"App Detail": "应用详情",
"Apps Share": "应用分享",
"Basic Settings": "基本信息",
"Chat Debug": "调试预览",
"Chat Logs Tips": "日志会记录该应用的在线、分享和 API(需填写 chatId) 对话记录",
@@ -84,7 +83,6 @@
"Delete Success": "删除成功",
"Delete Tip": "删除提示",
"Delete Warning": "删除警告",
"Detail": "详情",
"Done": "完成",
"Edit": "编辑",
"Exit": "退出",
@@ -100,6 +98,8 @@
"Last use time": "最后使用时间",
"Load Failed": "加载失败",
"Loading": "加载中...",
"Max credit": "最大金额",
"Max credit tips": "该链接最大可消耗多少金额,超出后链接将被禁止使用。-1 代表无限制。",
"More settings": "更多设置",
"Name": "名称",
"Name Can": "名称不能为空",
@@ -110,7 +110,6 @@
"Number of words": "{{amount}}字",
"OK": "好的",
"Opened": "已开启",
"Other": "其他",
"Output": "输出",
"Params": "参数",
"Password inconsistency": "两次密码不一致",
@@ -133,20 +132,17 @@
"Select template": "选择模板",
"Set Avatar": "点击设置头像",
"Set Name": "取个名字",
"Set Team Tags": "团队标签",
"Setting": "设置",
"Status": "状态",
"Submit failed": "提交失败",
"Submit success": "提交成功",
"Team": "团队",
"Team Tags Set": "标签",
"Test": "测试",
"Time": "时间",
"Un used": "未使用",
"UnKnow": "未知",
"UnKnow Source": "未知来源",
"Unlimited": "无限制",
"Update": "更新",
"Update Failed": "更新异常",
"Update Success": "更新成功",
"Update Successful": "更新成功",
@@ -406,7 +402,7 @@
"Stop Speak": "停止录音",
"Type a message": "输入问题",
"Unpin": "取消置顶",
"You need to a chat app": "鉴权失败,暂无权限访问应用",
"You need to a chat app": "你需要创建一个应用",
"error": {
"Chat error": "对话出现异常",
"Messages empty": "接口内容为空,可能文本超长了~",
@@ -438,7 +434,7 @@
},
"response": {
"Complete Response": "完整响应",
"Extension model": "问题优化模型",
"Extension model": "问题补全模型",
"Plugin Resonse Detail": "插件详情",
"Read complete response": "查看详情",
"Read complete response tips": "点击查看详细流程",
@@ -504,7 +500,6 @@
"Manual collection": "手动数据集",
"My Dataset": "我的知识库",
"Name": "知识库名称",
"Query extension intro": "开启问题优化功能,可以提高提高连续对话时,知识库搜索的精度。开启该功能后,在进行知识库搜索时,会根据对话记录,利用 AI 补全问题缺失的信息。",
"Quote Length": "引用内容长度",
"Read Dataset": "查看知识库详情",
"Search score tip": "{{scoreText}}下面是详细排名和得分情况:\n----\n{{detailScore}}",
@@ -556,8 +551,7 @@
"success": "开始同步"
}
},
"training": {
}
"training": {}
},
"data": {
"Auxiliary Data": "辅助数据",
@@ -566,7 +560,7 @@
"Data Content": "相关数据内容",
"Data Content Placeholder": "该输入框是必填项,该内容通常是对于知识点的描述,也可以是用户的问题,最多 {{maxToken}} 字。",
"Data Content Tip": "该输入框是必填项\n该内容通常是对于知识点的描述也可以是用户的问题。",
"Default Index Tip": "无法编辑,默认索引会使用【相关数据内容】与【辅助数据】的文本直接生成索引。",
"Default Index Tip": "无法编辑,默认索引会使用【相关数据内容】与【辅助数据】的文本直接生成索引,如不需要默认索引,可删除。 每条数据必须保证有一个以上索引,所有索引被删除后,会自动生成默认索引。",
"Edit": "编辑数据",
"Empty Tip": "这个集合还没有数据~",
"Main Content": "主要内容",
@@ -614,10 +608,9 @@
"Data file progress": "数据上传进度",
"Data process params": "数据处理参数",
"Down load csv template": "点击下载 CSV 模板",
"Embedding Estimated Price Tips": "索引计费: {{price}}积分/1k字符",
"Embedding Estimated Price Tips": "索引计费: {{price}}/1k字符",
"Estimated Price": "预估价格: {{amount}}{{unit}}",
"Estimated Price Tips": "QA计费为\n输入: 1k字符 = {{charsPointsPrice}}积分",
"Estimated points": "预估消耗 {{points}} 积分",
"Estimated Price Tips": "QA计费为\n输入: {{inputPrice}}/1k tokens\n输出: {{outputPrice}}/1k tokens",
"Fetch Error": "获取链接失败",
"Fetch Url": "网络链接",
"Fetch url placeholder": "最多10个链接每行一个。",
@@ -637,7 +630,7 @@
"Preview chunks": "分段预览",
"Preview raw text": "预览源文本最多展示10000字",
"Process way": "处理方式",
"QA Estimated Price Tips": "QA计费为: {{price}}积分/1k 字符(包含输入和输出)",
"QA Estimated Price Tips": "QA计费为: {{price}}/1k 字符(包含输入和输出)",
"QA Import": "QA拆分",
"QA Import Tip": "根据一定规则,将文本拆成一段较大的段落,调用 AI 为该段落生成问答对。",
"Re Preview": "重新生成预览",
@@ -687,7 +680,7 @@
"Source name": "引用来源名",
"Top K": "单次搜索上限",
"Using cfr": "",
"Using query extension": "使用问题优化",
"Using query extension": "使用问题补全",
"mode": {
"embedding": "语义检索",
"embedding desc": "使用向量进行文本相关性查询",
@@ -851,8 +844,8 @@
"dynamicTargetInput": "动态外部数据",
"input": "输入框",
"selectApp": "应用选择",
"selectChatModel": "对话模型选择",
"selectDataset": "知识库选择",
"selectLLMModel": "对话模型选择",
"switch": "开关",
"target": "外部数据",
"textarea": "段落输入"
@@ -864,7 +857,6 @@
"Ai response content": "将在 stream 回复完毕后触发",
"New context": "将本次回复内容拼接上历史记录,作为新的上下文返回",
"Quote": "始终返回数组,如果希望搜索结果为空时执行额外操作,需要用到上面的两个输入以及目标模块的触发器",
"query extension result": "以字符串数组的形式输出可将该结果直接连接到“知识库搜索”的“用户问题”中建议不要连接到“AI对话”的“用户问题”中",
"running done": "模块调用结束时触发"
},
"label": {
@@ -873,7 +865,7 @@
"Quote": "引用内容",
"Search result empty": "搜索结果为空",
"Search result not empty": "搜索结果不为空",
"query extension result": "优化结果",
"cfr result": "补全结果",
"result false": "False",
"result true": "True",
"running done": "模块调用结束",
@@ -900,8 +892,8 @@
"Http request": "HTTP 请求",
"Http request intro": "可以发出一个 HTTP 请求,实现更为复杂的操作(联网搜索、数据库查询等)",
"My plugin module": "个人插件",
"Query extension": "问题优化",
"Query extension intro": "使用问题优化功能,可以提高知识库连续对话时搜索的精度。使用该功能后,会先利用 AI 根据上下文构建一个或多个新的检索词,这些检索词更利于进行知识库搜索。该模块已内置在知识库搜索模块中,如果您仅进行一次知识库搜索,可直接使用知识库内置的补全功能。",
"Query extension": "问题补全",
"Query extension intro": "开启问题补全功能,可以提高提高连续对话时,知识库搜索的精度。开启该功能后,在进行知识库搜索时,会根据对话记录,利用 AI 补全问题缺失的信息。",
"Response module": "文本输出",
"Running app": "应用调用",
"Running app intro": "可以选择一个其他应用进行调用",
@@ -1112,7 +1104,6 @@
"navbar": {
"Account": "账号",
"Apps": "应用",
"Apps Share": "应用分享",
"Chat": "聊天",
"Datasets": "知识库",
"Module": "模块",
@@ -1177,9 +1168,6 @@
"Usage": "已用额度(¥)"
},
"outlink": {
"Max usage points": "积分上限",
"Max usage points tip": "该链接最多允许使用多少积分,超出后将无法使用。-1 代表无限制。",
"Usage points": "积分消耗",
"share": {
"Response Quote": "返回引用",
"Response Quote tips": "在分享链接中返回引用内容,但不会允许用户下载原文档"
@@ -1188,16 +1176,9 @@
"subscription": {
"Cancel subscription": "取消订阅"
},
"team": {
"limit": {
"No permission rerank": "无权使用结果重排,请升级您的套餐"
}
},
"user": {
"Avatar": "头像",
"Need to login": "请先登录",
"Price": "计费标准",
"User self info": "个人信息",
"auth": {
"Sending Code": "正在发送"
},
@@ -1212,10 +1193,7 @@
}
},
"wallet": {
"Amount": "金额",
"Balance not enough tip": "余额不足,请先到账号页充值",
"Bills": "账单",
"Buy": "购买",
"Buy more": "扩容",
"Confirm pay": "支付确认",
"Pay error": "支付失败",
@@ -1223,115 +1201,57 @@
"bill": {
"AI Model": "AI 模型",
"AI Type": "AI 类型",
"Number": "订单号",
"Price": "价格(¥)",
"Status": "状态",
"Type": "订单类型",
"payWay": {
"Way": "支付方式",
"balance": "余额支付",
"wx": "微信支付"
},
"status": {
"closed": "已关闭",
"notpay": "未支付",
"refund": "已退款",
"success": "支付成功"
}
"Price": "价格(¥)"
},
"moduleName": {
"index": "索引生成",
"qa": "QA 拆分"
},
"noBill": "无账单记录~",
"subscription": {
"AI points": "AI积分",
"Ai points": "AI 积分计算标准",
"Buy now": "切换套餐",
"Buy now": "开始使用",
"Change will take effect after the current subscription expires": "更新成功。将会再下个订阅周期生效。",
"Current dataset store": "当前额外容量",
"Current extra ai points": "当前额外积分",
"Current plan": "当前套餐",
"Current plan": "当前计划",
"Dataset store": "知识库容量",
"Dataset store price tip": "每月1号从账号余额里扣除",
"Expand size": "扩大容量",
"Extra ai points": "额外AI积分",
"Extra dataset size": "额外知识库容量",
"Extra plan": "额外资源包",
"Extra plan tip": "标准套餐不够时,您可以购买额外资源包继续使用",
"Extra plan": "额外套餐",
"Extra plan tip": "标准套餐不够时,您可以购买额外套餐继续使用",
"FAQ": "常见问题",
"Month amount": "月数",
"Next extra ai points": "下次额外积分",
"Next plan": "未来套餐",
"Next plan": "未来计划",
"Next sub dataset size": "下次订阅额外容量",
"Nonsupport": "无法切换",
"Refund plan and pay confirm": "切换该套餐您无需支付额外费用,并将退换 {{amount}} 元至余额中。",
"Stand plan level": "订阅套餐",
"Standard plan pay confirm": "切换该套餐,您本次需要补充支付 {{payPrice}} 元。",
"Standard update fail": "修改订阅套餐异常",
"Standard update success": "变更订阅套餐成功!",
"Sub plan": "订阅套餐",
"Sub plan tip": "免费使用 FastGPT 或升级更高的套餐",
"Team plan and usage": "套餐与用量",
"Training weight": "训练优先级: {{weight}}",
"Update extra ai points": "额外AI积分",
"Update extra dataset size": "额外存储量",
"Upgrade plan": "升级套餐",
"Update extra dataset size": "调整知识库额外容量",
"function": {
"History store": "{{amount}} 天对话记录保留",
"Max app": "{{amount}} 个应用与插件",
"Max dataset": "{{amount}} 个知识库",
"Max dataset size": "{{amount}} 组知识库索引",
"Max dataset size": "{{amount}} 组知识库数据",
"Max members": "{{amount}} 个团队成员",
"Points": "{{amount}} AI积分"
"Points": "{{amount}}AI积分"
},
"mode": {
"Month": "按月",
"Period": "订阅周期",
"Year": "按年",
"Year sale": "赠送两个月"
},
"standardSubLevel": {
"enterprise": "企业版",
"experience": "体验版",
"experience desc": "",
"experience desc": "可使用 {{title}} 的完整功能",
"free": "免费版",
"free desc": "每月均可免费使用基础功能15天不活跃时将会清除知识库",
"free desc": "每月均可免费使用 {{title}} 的基础功能",
"team": "团队版"
},
"type": {
"balance": "余额充值",
"extraDatasetSize": "知识库扩容",
"extraPoints": "AI积分套餐",
"standard": "套餐订阅"
}
},
"usage": {
"Ai model": "AI模型",
"App name": "应用名",
"Audio Speech": "语音播报",
"Bill Module": "扣费模块",
"Chars length": "文本长度",
"Data Length": "数据长度",
"Dataset store": "知识库存储",
"Duration": "时长(秒)",
"Extension Input Token Length": "问题优化输入Tokens",
"Extension Output Token Length": "问题优化输出Tokens",
"Extension result": "问题优化结果",
"Input Token Length": "输入 Tokens",
"Module name": "模块名",
"Number": "",
"Output Token Length": "输出 Tokens",
"ReRank": "结果重排",
"Source": "来源",
"Text Length": "文本长度",
"Time": "生成时间",
"Token Length": "Token长度",
"Total": "总金额",
"Total points": "AI积分消耗",
"Usage Detail": "使用详情",
"Whisper": "语音输入",
"bill username": "用户"
}
}
},
@@ -1373,7 +1293,6 @@
"Set OpenAI Account Failed": "设置 OpenAI 账号异常",
"Sign Out": "登出",
"Source": "来源",
"Standard Detail": "套餐详情",
"Team": "团队",
"Time": "时间",
"Timezone": "时区",
@@ -1421,11 +1340,7 @@
"Select Team": "团队选择",
"Set Name": "给团队取个名字",
"Switch Team Failed": "切换团队异常",
"Tags Async": "保存",
"Team Name": "团队名",
"Team Tags Async": "标签同步",
"Team Tags Async Success": "链接报错成功,标签信息更新",
"Team Tags Async Tip": "填写标签同步连接,获取最新",
"Update Team": "更新团队信息",
"invite": {
"Accept Confirm": "确认加入该团队?",
@@ -1450,5 +1365,37 @@
"Visitor": "访客"
}
}
},
"wallet": {
"bill": {
"Ai model": "AI模型",
"App name": "应用名",
"Audio Speech": "语音播报",
"Bill Module": "扣费模块",
"Chars length": "文本长度",
"Data Length": "数据长度",
"Dataset store": "知识库存储",
"Duration": "时长(秒)",
"Extension Input Token Length": "问题补全输入Tokens",
"Extension Output Token Length": "问题补全输出Tokens",
"Extension result": "问题补全结果",
"Input Token Length": "输入 Tokens",
"Module name": "模块名",
"Next Step Guide": "下一步指引",
"Number": "订单号",
"Output Token Length": "输出 Tokens",
"ReRank": "结果重排",
"Source": "来源",
"Text Length": "文本长度",
"Time": "生成时间",
"Token Length": "Token长度",
"Total": "总金额",
"Whisper": "语音输入",
"bill username": "用户"
},
"moduleName": {
"index": "索引生成",
"qa": "QA 拆分"
}
}
}

View File

@@ -8,10 +8,10 @@ import Tabs from '../Tabs';
import MyModal from '../MyModal';
import MyTooltip from '../MyTooltip';
import { QuestionOutlineIcon } from '@chakra-ui/icons';
import { formatStorePrice2Read } from '@fastgpt/global/support/wallet/bill/tools';
import Markdown from '../Markdown';
import { QuoteList } from './QuoteModal';
import { DatasetSearchModeMap } from '@fastgpt/global/core/dataset/constants';
import { formatNumber } from '@fastgpt/global/common/math/tools';
function Row({
label,
@@ -131,10 +131,10 @@ const ResponseBox = React.memo(function ResponseBox({
<Box py={2} px={4} flex={'1 0 0'} overflow={'auto'}>
<>
<Row label={t('core.chat.response.module name')} value={t(activeModule.moduleName)} />
{activeModule?.totalPoints !== undefined && (
{activeModule?.price !== undefined && (
<Row
label={t('support.wallet.usage.Total points')}
value={formatNumber(activeModule.totalPoints)}
label={t('core.chat.response.module price')}
value={`${formatStorePrice2Read(activeModule?.price)}`}
/>
)}
<Row
@@ -142,9 +142,11 @@ const ResponseBox = React.memo(function ResponseBox({
value={`${activeModule?.runningTime || 0}s`}
/>
<Row label={t('core.chat.response.module model')} value={activeModule?.model} />
<Row label={t('wallet.bill.Chars length')} value={`${activeModule?.charsLength}`} />
<Row label={t('wallet.bill.Input Token Length')} value={`${activeModule?.inputTokens}`} />
<Row
label={t('support.wallet.usage.Chars length')}
value={`${activeModule?.charsLength}`}
label={t('wallet.bill.Output Token Length')}
value={`${activeModule?.outputTokens}`}
/>
<Row label={t('core.chat.response.module query')} value={activeModule?.query} />
<Row
@@ -206,14 +208,14 @@ const ResponseBox = React.memo(function ResponseBox({
<Row label={t('core.chat.response.module limit')} value={activeModule?.limit} />
<Row
label={t('core.chat.response.search using reRank')}
value={`${activeModule?.searchUsingReRank}`}
value={activeModule?.searchUsingReRank}
/>
<Row
label={t('core.chat.response.Extension model')}
value={activeModule?.extensionModel}
/>
<Row
label={t('support.wallet.usage.Extension result')}
label={t('wallet.bill.Extension result')}
value={`${activeModule?.extensionResult}`}
/>
</>

View File

@@ -11,7 +11,6 @@ const unAuthPage: { [key: string]: boolean } = {
'/login/fastlogin': true,
'/appStore': true,
'/chat/share': true,
'/chat/team': true,
'/tools/price': true,
'/price': true
};

View File

@@ -23,7 +23,6 @@ const pcUnShowLayoutRoute: Record<string, boolean> = {
'/login/provider': true,
'/login/fastlogin': true,
'/chat/share': true,
'/chat/team': true,
'/app/edit': true,
'/chat': true,
'/tools/price': true,
@@ -35,7 +34,6 @@ const phoneUnShowLayoutRoute: Record<string, boolean> = {
'/login/provider': true,
'/login/fastlogin': true,
'/chat/share': true,
'/chat/team': true,
'/tools/price': true,
'/price': true
};
@@ -116,10 +114,9 @@ const Layout = ({ children }: { children: JSX.Element }) => {
</Box>
</>
)}
{!!userInfo && <UpdateInviteModal />}
</Box>
<Loading loading={loading} zIndex={999999} />
{!!userInfo && <UpdateInviteModal />}
</>
);
};

View File

@@ -57,7 +57,7 @@ const Markdown = ({ source, isChatting = false }: { source: string; isChatting?:
className={`markdown ${styles.markdown}
${isChatting ? `${formatSource ? styles.waitingAnimation : styles.animation}` : ''}
`}
remarkPlugins={[RemarkMath, [RemarkGfm, { singleTilde: false }], RemarkBreaks]}
remarkPlugins={[RemarkMath, RemarkGfm, RemarkBreaks]}
rehypePlugins={[RehypeKatex]}
components={components}
linkTarget={'_blank'}

View File

@@ -2,15 +2,15 @@ import React, { useMemo } from 'react';
import MySelect, { type SelectProps } from './index';
import { useTranslation } from 'next-i18next';
import dynamic from 'next/dynamic';
import { useDisclosure } from '@chakra-ui/react';
import { useSystemStore } from '@/web/common/system/useSystemStore';
import { useRouter } from 'next/router';
import { AI_POINT_USAGE_CARD_ROUTE } from '@/web/support/wallet/sub/constants';
const PriceBox = dynamic(() => import('@/components/support/wallet/Price'));
const SelectAiModel = ({ list, ...props }: SelectProps) => {
const { t } = useTranslation();
const { feConfigs } = useSystemStore();
const router = useRouter();
const expandList = useMemo(() => {
return feConfigs.show_pay
? list.concat({
@@ -20,6 +20,12 @@ const SelectAiModel = ({ list, ...props }: SelectProps) => {
: list;
}, [feConfigs.show_pay, list, t]);
const {
isOpen: isOpenPriceBox,
onOpen: onOpenPriceBox,
onClose: onClosePriceBox
} = useDisclosure();
return (
<>
<MySelect
@@ -27,12 +33,13 @@ const SelectAiModel = ({ list, ...props }: SelectProps) => {
{...props}
onchange={(e) => {
if (e === 'price') {
router.push(AI_POINT_USAGE_CARD_ROUTE);
onOpenPriceBox();
return;
}
props.onchange?.(e);
}}
/>
{isOpenPriceBox && <PriceBox onClose={onClosePriceBox} />}
</>
);
};

View File

@@ -1,103 +0,0 @@
import React, { useEffect, useMemo, useState } from 'react';
import {
Menu,
MenuButton,
MenuList,
MenuItemOption,
MenuOptionGroup,
Flex,
TagLabel,
TagCloseButton,
HStack,
Tag,
Input
} from '@chakra-ui/react';
import type { TeamTagsSchema } from '@fastgpt/global/support/user/team/type';
const TagEdit = ({
defaultValues,
teamsTags,
setSelectedTags
}: {
defaultValues: [];
teamsTags: Array<TeamTagsSchema>;
setSelectedTags: (item: Array<string>) => void;
}) => {
const [teamTagsOptions, setTeamTagsOptions] = useState(teamsTags);
const setSelectTeamsTags = (item: any) => {
setSelectedTags(item);
};
useMemo(() => {
setTeamTagsOptions(teamsTags);
}, [teamsTags]);
return (
<>
<Menu closeOnSelect={false}>
<MenuButton className="menu-btn" maxHeight={'250'} minWidth={'80%'}>
<HStack
style={{
border: 'solid 2px #f3f3f3',
borderRadius: '5px',
padding: '3px',
flexWrap: 'wrap',
minHeight: '40px'
}}
>
{teamsTags.map((item: TeamTagsSchema, index: number) => {
const key: string = item?.key;
if (defaultValues.indexOf(key as never) > -1) {
return (
<Tag
key={index}
size={'md'}
colorScheme="red"
// maxWidth={"100px"}
borderRadius="full"
>
<TagLabel> {item.label}</TagLabel>
<TagCloseButton />
</Tag>
);
}
})}
</HStack>
</MenuButton>
<MenuList style={{ height: '300px', overflow: 'scroll' }}>
<Input
style={{ border: 'none', borderBottom: 'solid 1px #f6f6f6' }}
placeholder="pleace "
onChange={(e: any) => {
// 对用户输入的搜索文本进行小写转换,以实现不区分大小写的搜索
const searchLower: string = e?.nativeEvent?.data || '';
// 使用filter方法来过滤列表只返回包含搜索文本的项
const resultList = teamsTags.filter((item) => {
const searchValue = item.label || '';
// 对列表中的每一项也进行小写转换
return searchValue.includes(searchLower);
});
!searchLower ? setTeamTagsOptions(teamsTags) : setTeamTagsOptions(resultList);
}}
/>
<MenuOptionGroup
defaultValue={defaultValues}
type="checkbox"
style={{ height: '300px', overflow: 'scroll' }}
onChange={(e) => {
setSelectTeamsTags(e);
}}
>
{teamTagsOptions.map((item, index) => {
return (
<MenuItemOption key={index} value={item.key}>
{item?.label}
</MenuItemOption>
);
})}
</MenuOptionGroup>
</MenuList>
</Menu>
</>
);
};
export default TagEdit;

View File

@@ -27,8 +27,6 @@ import MyIcon from '@fastgpt/web/components/common/Icon';
import Tabs from '@/components/Tabs';
import PromptEditor from '@fastgpt/web/components/common/Textarea/PromptEditor';
import SelectAiModel from '@/components/Select/SelectAiModel';
import { useUserStore } from '@/web/support/user/useUserStore';
import { useToast } from '@fastgpt/web/hooks/useToast';
export type DatasetParamsProps = {
searchMode: `${DatasetSearchModeEnum}`;
@@ -63,8 +61,6 @@ const DatasetParamsModal = ({
}: DatasetParamsProps & { onClose: () => void; onSuccess: (e: DatasetParamsProps) => void }) => {
const { t } = useTranslation();
const theme = useTheme();
const { toast } = useToast();
const { teamPlanStatus } = useUserStore();
const { reRankModelList, llmModelList } = useSystemStore();
const [refresh, setRefresh] = useState(false);
const [currentTabType, setCurrentTabType] = useState(SearchSettingTabEnum.searchMode);
@@ -75,7 +71,7 @@ const DatasetParamsModal = ({
limit,
similarity,
searchMode,
usingReRank: !!usingReRank && !!teamPlanStatus?.standardConstants?.permissionReRank,
usingReRank,
datasetSearchUsingExtensionQuery,
datasetSearchExtensionModel: datasetSearchExtensionModel ?? llmModelList[0]?.model,
datasetSearchExtensionBg
@@ -109,10 +105,6 @@ const DatasetParamsModal = ({
return true;
}, [getValues, similarity]);
const showReRank = useMemo(() => {
return usingReRank !== undefined && reRankModelList.length > 0;
}, [reRankModelList.length, usingReRank]);
return (
<MyModal
isOpen={true}
@@ -156,7 +148,7 @@ const DatasetParamsModal = ({
setRefresh(!refresh);
}}
/>
{showReRank && (
{usingReRank !== undefined && reRankModelList.length > 0 && (
<>
<Divider my={4} />
<Flex
@@ -176,12 +168,6 @@ const DatasetParamsModal = ({
}
: {})}
onClick={(e) => {
if (!teamPlanStatus?.standardConstants?.permissionReRank) {
return toast({
status: 'warning',
title: t('support.team.limit.No permission rerank')
});
}
setValue('usingReRank', !getValues('usingReRank'));
setRefresh((state) => !state);
}}
@@ -287,7 +273,7 @@ const DatasetParamsModal = ({
{currentTabType === SearchSettingTabEnum.queryExtension && (
<Box>
<Box fontSize={'xs'} color={'myGray.500'}>
{t('core.dataset.Query extension intro')}
{t('core.module.template.Query extension intro')}
</Box>
<Flex mt={3} alignItems={'center'}>
<Box flex={'1 0 0'}>{t('core.dataset.search.Using query extension')}</Box>

View File

@@ -46,7 +46,11 @@ const RenderList: {
Component: dynamic(() => import('./templates/AiSetting'))
},
{
types: [FlowNodeInputTypeEnum.selectLLMModel],
types: [
FlowNodeInputTypeEnum.selectChatModel,
FlowNodeInputTypeEnum.selectCQModel,
FlowNodeInputTypeEnum.selectExtractModel
],
Component: dynamic(() => import('./templates/SelectAiModel'))
},
{

View File

@@ -33,7 +33,7 @@ const nodeTypes: Record<`${FlowNodeTypeEnum}`, any> = {
[FlowNodeTypeEnum.pluginInput]: dynamic(() => import('./components/nodes/NodePluginInput')),
[FlowNodeTypeEnum.pluginOutput]: dynamic(() => import('./components/nodes/NodePluginOutput')),
[FlowNodeTypeEnum.pluginModule]: NodeSimple,
[FlowNodeTypeEnum.queryExtension]: NodeSimple
[FlowNodeTypeEnum.cfr]: NodeSimple
};
const edgeTypes = {
[EDGE_TYPE]: ButtonEdge

View File

@@ -46,7 +46,7 @@ type EditProps = EditApiKeyProps & { _id?: string };
const defaultEditData: EditProps = {
name: '',
limit: {
maxUsagePoints: -1
credit: -1
}
};
@@ -153,16 +153,16 @@ const ApiKeyTable = ({ tips, appId }: { tips: string; appId?: string }) => {
</Tr>
</Thead>
<Tbody fontSize={'sm'}>
{apiKeys.map(({ _id, name, usagePoints, limit, apiKey, createTime, lastUsedTime }) => (
{apiKeys.map(({ _id, name, usage, limit, apiKey, createTime, lastUsedTime }) => (
<Tr key={_id}>
<Td>{name}</Td>
<Td>{apiKey}</Td>
<Td>{Math.round(usagePoints)}</Td>
<Td>{usage}</Td>
{feConfigs?.isPlus && (
<>
<Td>
{limit?.maxUsagePoints && limit?.maxUsagePoints > -1
? `${limit?.maxUsagePoints}`
{limit?.credit && limit?.credit > -1
? `${limit?.credit}`
: t('common.Unlimited')}
</Td>
<Td whiteSpace={'pre-wrap'}>
@@ -334,15 +334,15 @@ function EditKeyModal({
<>
<Flex alignItems={'center'} mt={4}>
<Flex flex={'0 0 90px'} alignItems={'center'}>
{t('support.outlink.Max usage points')}:
<MyTooltip label={t('support.outlink.Max usage points tip')}>
{t('common.Max credit')}:
<MyTooltip label={t('common.Max credit tips' || '')}>
<QuestionOutlineIcon ml={1} />
</MyTooltip>
</Flex>
<Input
{...register('limit.maxUsagePoints', {
{...register('limit.credit', {
min: -1,
max: 10000000,
max: 1000,
valueAsNumber: true,
required: true
})}

View File

@@ -2,16 +2,13 @@ import React, { useMemo, useState } from 'react';
import MyModal from '@/components/MyModal';
import { useTranslation } from 'next-i18next';
import { useQuery } from '@tanstack/react-query';
import { DragHandleIcon } from '@chakra-ui/icons';
import {
getTeamList,
getTeamMembers,
putSwitchTeam,
putUpdateMember,
delRemoveMember,
delLeaveTeam,
getTeamsTags,
insertTeamsTags
delLeaveTeam
} from '@/web/support/user/team/api';
import {
Box,
@@ -23,15 +20,12 @@ import {
Tbody,
Tr,
Th,
Tag,
Td,
TableContainer,
useTheme,
useDisclosure,
MenuButton,
HStack
MenuButton
} from '@chakra-ui/react';
import { SpinnerIcon } from '@chakra-ui/icons';
import MyIcon from '@fastgpt/web/components/common/Icon';
import Avatar from '@/components/Avatar';
import { useUserStore } from '@/web/support/user/useUserStore';
@@ -52,14 +46,12 @@ import { useToast } from '@fastgpt/web/hooks/useToast';
const EditModal = dynamic(() => import('./EditModal'));
const InviteModal = dynamic(() => import('./InviteModal'));
const TeamTagsAsync = dynamic(() => import('../TeamTagsAsync'));
const TeamManageModal = ({ onClose }: { onClose: () => void }) => {
const theme = useTheme();
const { t } = useTranslation();
const { Loading } = useLoading();
const { toast } = useToast();
const [teamsTags, setTeamTags] = useState<any>();
const { ConfirmModal: ConfirmRemoveMemberModal, openConfirm: openRemoveMember } = useConfirm();
const { ConfirmModal: ConfirmLeaveTeamModal, openConfirm: openLeaveConfirm } = useConfirm({
@@ -69,11 +61,6 @@ const TeamManageModal = ({ onClose }: { onClose: () => void }) => {
const { userInfo, initUserInfo } = useUserStore();
const [editTeamData, setEditTeamData] = useState<FormDataType>();
const { isOpen: isOpenInvite, onOpen: onOpenInvite, onClose: onCloseInvite } = useDisclosure();
const {
isOpen: isOpenTeamTagsAsync,
onOpen: onOpenTeamTagsAsync,
onClose: onCloseTeamTagsAsync
} = useDisclosure();
const {
data: myTeams = [],
@@ -89,8 +76,6 @@ const TeamManageModal = ({ onClose }: { onClose: () => void }) => {
mutationFn: async (teamId: string) => {
const token = await putSwitchTeam(teamId);
token && setToken(token);
// get team tags
await getTeamsTags(teamId);
return initUserInfo();
},
errorToast: t('user.team.Switch Team Failed')
@@ -101,11 +86,6 @@ const TeamManageModal = ({ onClose }: { onClose: () => void }) => {
['getMembers', userInfo?.team?.teamId],
() => {
if (!userInfo?.team?.teamId) return [];
// get team tags
getTeamsTags(userInfo.team.teamId).then((res: any) => {
setTeamTags(res);
});
return getTeamMembers(userInfo.team.teamId);
}
);
@@ -128,9 +108,7 @@ const TeamManageModal = ({ onClose }: { onClose: () => void }) => {
mutationFn: async (teamId?: string) => {
if (!teamId) return;
// change to personal team
// get members
await onSwitchTeam(defaultTeam.teamId);
return delLeaveTeam(teamId);
},
onSuccess() {
@@ -206,7 +184,6 @@ const TeamManageModal = ({ onClose }: { onClose: () => void }) => {
bg: 'myGray.100'
}
})}
onClick={() => onSwitchTeam(team.teamId)}
>
<Avatar src={team.avatar} w={['18px', '22px']} />
<Box
@@ -219,17 +196,6 @@ const TeamManageModal = ({ onClose }: { onClose: () => void }) => {
: {})}
>
{team.teamName}
{/* {userInfo?.team?.teamId === team.teamId && (
<HStack spacing={1}>
{teamsTags.slice(0, 3).map((item: any, index) => {
return (
<Tag key={index} size={'sm'} variant="outline" colorScheme="blue">
{item.label}
</Tag>
);
})}
</HStack>
)} */}
</Box>
{userInfo?.team?.teamId === team.teamId ? (
<MyIcon name={'common/tickFill'} w={'16px'} color={'primary.500'} />
@@ -263,7 +229,7 @@ const TeamManageModal = ({ onClose }: { onClose: () => void }) => {
borderBottomColor={'myGray.100'}
mb={3}
>
<Box fontSize={['lg', 'xl']} fontWeight={'bold'} alignItems={'center'}>
<Box fontSize={['lg', 'xl']} fontWeight={'bold'}>
{userInfo.team.teamName}
</Box>
{userInfo.team.role === TeamMemberRoleEnum.owner && (
@@ -313,27 +279,6 @@ const TeamManageModal = ({ onClose }: { onClose: () => void }) => {
{t('user.team.Invite Member')}
</Button>
)}
{userInfo.team.role === TeamMemberRoleEnum.owner && (
<Button
variant={'whitePrimary'}
size="sm"
borderRadius={'md'}
ml={3}
leftIcon={<DragHandleIcon w={'14px'} color={'primary.500'} />}
onClick={() => {
if (userInfo.team.maxSize <= members.length) {
toast({
status: 'warning',
title: t('user.team.Team Tags Async', { max: userInfo.team.maxSize })
});
} else {
onOpenTeamTagsAsync();
}
}}
>
{t('user.team.Team Tags Async')}
</Button>
)}
<Box flex={1} />
{userInfo.team.role !== TeamMemberRoleEnum.owner && (
<Button
@@ -490,13 +435,6 @@ const TeamManageModal = ({ onClose }: { onClose: () => void }) => {
onSuccess={refetchMembers}
/>
)}
{isOpenTeamTagsAsync && (
<TeamTagsAsync
teamInfo={teamsTags?.tagsUrl}
teamsTags={teamsTags?.list || []}
onClose={onCloseTeamTagsAsync}
/>
)}
<ConfirmRemoveMemberModal />
<ConfirmLeaveTeamModal />
</>

View File

@@ -1,182 +0,0 @@
import React, { useEffect, useMemo, useState } from 'react';
import MyModal from '@/components/MyModal';
import {
Box,
Button,
Flex,
ModalBody,
Tag,
ModalFooter,
Input,
HStack,
Avatar
} from '@chakra-ui/react';
import { AttachmentIcon, CopyIcon, DragHandleIcon } from '@chakra-ui/icons';
import { putUpdateTeamTags, updateTags } from '@/web/support/user/team/api';
import { useForm } from 'react-hook-form';
import { useTranslation } from 'next-i18next';
import type { TeamTagsSchema } from '@fastgpt/global/support/user/team/type';
import { useRequest } from '@/web/common/hooks/useRequest';
import { RepeatIcon } from '@chakra-ui/icons';
import MyIcon from '@fastgpt/web/components/common/Icon';
import { useToast } from '@fastgpt/web/hooks/useToast';
import { useCopyData } from '@/web/common/hooks/useCopyData';
const TeamTagsAsync = ({
teamsTags,
teamInfo,
onClose
}: {
teamsTags: Array<TeamTagsSchema>;
teamInfo: any;
onClose: () => void;
}) => {
const { t } = useTranslation();
const { toast } = useToast();
const [_teamsTags, setTeamTags] = useState<Array<TeamTagsSchema>>(teamsTags);
const { register, setValue, getValues, handleSubmit } = useForm<any>({
defaultValues: { ...teamInfo }
});
const { copyData } = useCopyData();
const baseUrl = global.feConfigs?.customSharePageDomain || location?.origin;
const linkUrl = `${baseUrl}/chat/team?teamId=${teamInfo?._id}${
getValues('showHistory') ? '' : '&showHistory=0'
}`;
// tags Async
const { mutate: onclickAsync, isLoading: creating } = useRequest({
mutationFn: async (data: any) => {
return putUpdateTeamTags({ tagsUrl: data.tagsUrl, teamId: teamInfo?._id });
},
onSuccess(id: string) {
onClose();
},
successToast: t('user.team.Team Tags Async Success'),
errorToast: t('common.Create Failed')
});
const asyncTags = async () => {
console.log('getValues', getValues());
const res: Array<TeamTagsSchema> = await updateTags(teamInfo?._id, getValues().tagsUrl);
setTeamTags(res);
toast({ status: 'success', title: '团队标签同步成功' });
};
useEffect(() => {
console.log('teamInfo', teamInfo);
}, []);
// 获取
return (
<>
<MyModal
isOpen
onClose={onClose}
maxW={['70vw', '1000px']}
w={'100%'}
h={'550px'}
iconSrc="/imgs/modal/team.svg"
isCentered
bg={'white'}
overflow={'hidden'}
title={
<Box>
<Box>{teamInfo?.name}</Box>
<Box color={'myGray.500'} fontSize={'xs'} fontWeight={'normal'}>
{'填写标签同步链接,点击同步按钮即可同步'}
</Box>
</Box>
}
>
<ModalBody style={{ padding: '10rpx' }}>
<Flex mt={3} alignItems={'center'}>
<Box mb={2} fontWeight="semibold">
{t('同步链接')}
</Box>
<Input
flex={1}
ml={4}
autoFocus
bg={'myWhite.600'}
placeholder="请输入同步标签"
{...register('tagsUrl', {
required: t('core.app.error.App name can not be empty')
})}
/>
</Flex>
<Flex mt={3} alignItems={'center'}>
<Box mb={2} fontWeight="semibold">
{t('分享链接')}
</Box>
{/* code */}
<Box ml={4} borderRadius={'md'} overflow={'hidden'}>
<Flex>
<Box whiteSpace={'pre'} p={3} overflowX={'auto'} bg={'myWhite.600'} color="blue">
{linkUrl}
</Box>
<MyIcon
name={'copy'}
w={'16px'}
p={3}
bg={'primary.500'}
color={'myWhite.600'}
cursor={'pointer'}
_hover={{ bg: 'primary.400' }}
onClick={() => {
copyData(linkUrl);
}}
/>
</Flex>
</Box>
</Flex>
<Flex mt={3} alignItems={'center'}>
<Box mb={2} fontWeight="semibold">
{t('标签列表')}
</Box>
<HStack
ml={4}
maxHeight={'250'}
bg={'myWhite.600'}
style={{
border: 'solid 2px #f3f3f377',
borderRadius: '5px',
padding: '10px',
maxWidth: '70%',
flexWrap: 'wrap',
overflow: 'scroll'
}}
spacing={1}
>
{_teamsTags.map((item, index) => {
return (
<Tag key={index} mt={2} size={'md'} colorScheme="red" borderRadius="full">
<Avatar
src="https://bit.ly/sage-adeb"
size="xs"
name={item.label}
ml={-2}
mr={2}
/>
{item.label}
</Tag>
);
})}
</HStack>
<Button ml={4} size="md" leftIcon={<RepeatIcon />} onClick={asyncTags}>
</Button>
</Flex>
</ModalBody>
<ModalFooter mb={2}>
<Button variant={'whiteBase'} mr={3} onClick={onClose}>
{t('common.Close')}
</Button>
<Button isLoading={creating} onClick={handleSubmit((data) => onclickAsync(data))}>
{t('user.team.Tags Async')}
</Button>
</ModalFooter>
</MyModal>
</>
);
};
export default React.memo(TeamTagsAsync);

View File

@@ -0,0 +1,97 @@
import React from 'react';
import { Box, CloseButton } from '@chakra-ui/react';
import { useSystemStore } from '@/web/common/system/useSystemStore';
import ReactDOM from 'react-dom';
import Markdown from '@/components/Markdown';
const Price = ({ onClose }: { onClose: () => void }) => {
const { llmModelList, vectorModelList, audioSpeechModelList, whisperModel } = useSystemStore();
const list = [
{
title: 'AI语言模型',
describe: '',
md: `
| 模型 | 输入价格(¥) | 输出价格(¥) |
| --- | --- | --- |
${llmModelList
?.map((item) => `| ${item.name} | ${item.inputPrice}/1k tokens | ${item.outputPrice}/1k tokens |`)
.join('\n')}`
},
{
title: '索引模型(文档训练 & 文档检索)',
describe: '',
md: `
| 模型 | 价格(¥) |
| --- | --- |
${vectorModelList?.map((item) => `| ${item.name} | ${item.inputPrice}/1k 字符 |`).join('\n')}
`
},
{
title: '语音播放',
describe: '',
md: `
| 模型 | 价格(¥) |
| --- | --- |
${audioSpeechModelList
?.map((item) => `| ${item.name} | ${item.inputPrice}/1k 字符 | - |`)
.join('\n')}`
},
...(whisperModel
? [
{
title: '语音输入',
describe: '',
md: `
| 模型 | 价格(¥) |
| --- | --- |
| ${whisperModel.name} | ${whisperModel.inputPrice}/分钟 | - |`
}
]
: [])
];
return ReactDOM.createPortal(
<Box position={'fixed'} top={0} right={0} bottom={0} left={0} zIndex={99999} bg={'white'}>
<CloseButton
position={'absolute'}
top={'10px'}
right={'20px'}
bg={'myGray.200'}
w={'30px'}
h={'30px'}
borderRadius={'50%'}
onClick={onClose}
/>
<Box overflow={'overlay'} h={'100%'}>
<Box py={[0, 10]} px={5} mx={'auto'} maxW={'1200px'}>
{list.map((item) => (
<Box
display={['block', 'flex']}
key={item.title}
w={'100%'}
mb={4}
pb={6}
_notLast={{
borderBottom: '1px',
borderBottomColor: 'borderColor.high'
}}
>
<Box fontSize={'xl'} fontWeight={'bold'} mb={1} flex={'1 0 0'}>
{item.title}
</Box>
<Box w={['100%', '410px']}>
<Markdown source={item.md}></Markdown>
</Box>
</Box>
))}
</Box>
</Box>
</Box>,
// @ts-ignore
document.querySelector('body')
);
};
export default Price;

View File

@@ -1,84 +0,0 @@
import MyModal from '@/components/MyModal';
import React, { useEffect } from 'react';
import { useTranslation } from 'next-i18next';
import { Box, ModalBody, ModalFooter } from '@chakra-ui/react';
import { useQuery } from '@tanstack/react-query';
import { checkBalancePayResult } from '@/web/support/wallet/bill/api';
import { useToast } from '@fastgpt/web/hooks/useToast';
import { useRouter } from 'next/router';
import { getErrText } from '@fastgpt/global/common/error/utils';
export type QRPayProps = {
readPrice: number;
codeUrl: string;
billId: string;
};
const QRCodePayModal = ({
readPrice,
codeUrl,
billId,
onSuccess
}: QRPayProps & { onSuccess?: () => any }) => {
const router = useRouter();
const { t } = useTranslation();
const { toast } = useToast();
const dom = document.getElementById('payQRCode');
useEffect(() => {
if (dom && window.QRCode) {
new window.QRCode(dom, {
text: codeUrl,
width: 128,
height: 128,
colorDark: '#000000',
colorLight: '#ffffff',
correctLevel: window.QRCode.CorrectLevel.H
});
}
}, [dom]);
useQuery(
[billId],
() => {
if (!billId) return null;
return checkBalancePayResult(billId);
},
{
enabled: !!billId,
refetchInterval: 3000,
onSuccess: async (res) => {
if (!res) return;
try {
await onSuccess?.();
toast({
title: res,
status: 'success'
});
} catch (error) {
toast({
title: getErrText(error),
status: 'error'
});
}
setTimeout(() => {
router.reload();
}, 1000);
}
}
);
return (
<MyModal isOpen title={t('user.Pay')} iconSrc="/imgs/modal/pay.svg">
<ModalBody textAlign={'center'}>
<Box mb={3}>: {readPrice}</Box>
<Box id={'payQRCode'} display={'inline-block'} h={'128px'}></Box>
</ModalBody>
<ModalFooter />
</MyModal>
);
};
export default React.memo(QRCodePayModal);

View File

@@ -1,116 +0,0 @@
import { useSystemStore } from '@/web/common/system/useSystemStore';
import { StandardSubLevelEnum, SubModeEnum } from '@fastgpt/global/support/wallet/sub/constants';
import React, { useMemo } from 'react';
import { standardSubLevelMap } from '@fastgpt/global/support/wallet/sub/constants';
import { Box, Flex, Grid } from '@chakra-ui/react';
import MyIcon from '@fastgpt/web/components/common/Icon';
import { useTranslation } from 'next-i18next';
const StandardPlanContentList = ({
level,
mode
}: {
level: `${StandardSubLevelEnum}`;
mode: `${SubModeEnum}`;
}) => {
const { t } = useTranslation();
const { subPlans } = useSystemStore();
const planContent = useMemo(() => {
const plan = subPlans?.standard?.[level];
if (!plan) return;
return {
price: plan.price * (mode === SubModeEnum.month ? 1 : 10),
level: level as `${StandardSubLevelEnum}`,
...standardSubLevelMap[level as `${StandardSubLevelEnum}`],
maxTeamMember: plan.maxTeamMember,
maxAppAmount: plan.maxAppAmount,
maxDatasetAmount: plan.maxDatasetAmount,
chatHistoryStoreDuration: plan.chatHistoryStoreDuration,
maxDatasetSize: plan.maxDatasetSize,
permissionCustomApiKey: plan.permissionCustomApiKey,
permissionCustomCopyright: plan.permissionCustomCopyright,
trainingWeight: plan.trainingWeight,
permissionReRank: plan.permissionReRank,
totalPoints: plan.totalPoints * (mode === SubModeEnum.month ? 1 : 12),
permissionWebsiteSync: plan.permissionWebsiteSync
};
}, [subPlans?.standard, level, mode]);
return planContent ? (
<Grid gap={4}>
<Flex alignItems={'center'}>
<MyIcon name={'price/right'} w={'16px'} mr={3} />
<Box color={'myGray.600'}>
{t('support.wallet.subscription.function.Max members', {
amount: planContent.maxTeamMember
})}
</Box>
</Flex>
<Flex alignItems={'center'}>
<MyIcon name={'price/right'} w={'16px'} mr={3} />
<Box color={'myGray.600'}>
{t('support.wallet.subscription.function.Max app', {
amount: planContent.maxAppAmount
})}
</Box>
</Flex>
<Flex alignItems={'center'}>
<MyIcon name={'price/right'} w={'16px'} mr={3} />
<Box color={'myGray.600'}>
{t('support.wallet.subscription.function.Max dataset', {
amount: planContent.maxDatasetAmount
})}
</Box>
</Flex>
<Flex alignItems={'center'}>
<MyIcon name={'price/right'} w={'16px'} mr={3} />
<Box color={'myGray.600'}>
{t('support.wallet.subscription.function.History store', {
amount: planContent.chatHistoryStoreDuration
})}
</Box>
</Flex>
<Flex alignItems={'center'}>
<MyIcon name={'price/right'} w={'16px'} mr={3} />
<Box fontWeight={'bold'}>
{t('support.wallet.subscription.function.Max dataset size', {
amount: planContent.maxDatasetSize
})}
</Box>
</Flex>
<Flex alignItems={'center'}>
<MyIcon name={'price/right'} w={'16px'} mr={3} />
<Flex alignItems={'center'}>
<Box fontWeight={'bold'}>
{t('support.wallet.subscription.function.Points', {
amount: planContent.totalPoints
})}
</Box>
</Flex>
</Flex>
<Flex alignItems={'center'}>
<MyIcon name={'price/right'} w={'16px'} mr={3} />
<Box color={'myGray.600'}>
{t('support.wallet.subscription.Training weight', {
weight: planContent.trainingWeight
})}
</Box>
</Flex>
{!!planContent.permissionReRank && (
<Flex alignItems={'center'}>
<MyIcon name={'price/right'} w={'16px'} mr={3} />
<Box color={'myGray.600'}></Box>
</Flex>
)}
{!!planContent.permissionWebsiteSync && (
<Flex alignItems={'center'}>
<MyIcon name={'price/right'} w={'16px'} mr={3} />
<Box color={'myGray.600'}>Web站点同步</Box>
</Flex>
)}
</Grid>
) : null;
};
export default StandardPlanContentList;

View File

@@ -0,0 +1,240 @@
import React, { useState } from 'react';
import MyModal from '@/components/MyModal';
import { useTranslation } from 'next-i18next';
import {
Box,
Flex,
ModalBody,
NumberInput,
NumberInputField,
NumberInputStepper,
NumberIncrementStepper,
NumberDecrementStepper,
ModalFooter,
Button
} from '@chakra-ui/react';
import { useQuery } from '@tanstack/react-query';
import {
getTeamDatasetValidSub,
posCheckTeamDatasetSizeSub,
postUpdateTeamDatasetSizeSub,
putTeamDatasetSubStatus
} from '@/web/support/wallet/sub/api';
import Markdown from '@/components/Markdown';
import MyTooltip from '@/components/MyTooltip';
import { QuestionOutlineIcon } from '@chakra-ui/icons';
import { useConfirm } from '@/web/common/hooks/useConfirm';
import { useRequest } from '@/web/common/hooks/useRequest';
import { useRouter } from 'next/router';
import { useSystemStore } from '@/web/common/system/useSystemStore';
import { formatTime2YMDHM } from '@fastgpt/global/common/string/time';
import MySelect from '@/components/Select';
import {
SubStatusEnum,
SubTypeEnum,
subSelectMap
} from '@fastgpt/global/support/wallet/sub/constants';
import { SubDatasetSizePreviewCheckResponse } from '@fastgpt/global/support/wallet/sub/api.d';
import { formatStorePrice2Read } from '@fastgpt/global/support/wallet/bill/tools';
import { useUserStore } from '@/web/support/user/useUserStore';
const SubDatasetModal = ({ onClose }: { onClose: () => void }) => {
const { subPlans } = useSystemStore();
const datasetStorePrice = subPlans?.extraDatasetSize?.price || 0;
const { t } = useTranslation();
const router = useRouter();
const { ConfirmModal, openConfirm } = useConfirm({});
const { userInfo } = useUserStore();
const [datasetSize, setDatasetSize] = useState(0);
const [isRenew, setIsRenew] = useState('false');
const { data: teamSubPlan } = useQuery(['getTeamDatasetValidSub'], getTeamDatasetValidSub, {
onSuccess(res) {
setIsRenew(res?.extraDatasetSize?.status === SubStatusEnum.active ? 'true' : 'false');
setDatasetSize((res?.extraDatasetSize?.nextExtraDatasetSize || 0) / 1000);
}
});
const { mutate: onClickUpdateSub, isLoading: isPaying } = useRequest({
mutationFn: () => postUpdateTeamDatasetSizeSub({ size: datasetSize }),
onSuccess() {
setTimeout(() => {
router.reload();
}, 100);
},
successToast: t('common.Update success'),
errorToast: t('common.error.Update error')
});
const { mutate: onClickPreviewCheck, isLoading: isFetchingPreviewCheck } = useRequest({
mutationFn: () =>
posCheckTeamDatasetSizeSub({
size: datasetSize
}),
onSuccess(res: SubDatasetSizePreviewCheckResponse) {
if (!res.payForNewSub) {
onClickUpdateSub('');
return;
} else {
openConfirm(
() => {
if (!res.balanceEnough) return;
onClickUpdateSub('');
},
undefined,
<Box>
<Flex>
<Box flex={'0 0 100px'}>:</Box>
<Box>{teamSubPlan?.extraDatasetSize?.currentExtraDatasetSize || 0}</Box>
</Flex>
<Flex>
<Box flex={'0 0 100px'}>:</Box>
<Box>{res.newSubSize}</Box>
</Flex>
<Flex>
<Box flex={'0 0 100px'}>:</Box>
<Box>{formatStorePrice2Read(res.newPlanPrice)}</Box>
</Flex>
<Flex>
<Box flex={'0 0 100px'}>:</Box>
<Box>{formatStorePrice2Read(res.payPrice)}</Box>
</Flex>
<Flex>
<Box flex={'0 0 100px'}>:</Box>
<Box>30</Box>
</Flex>
<Flex>
<Box flex={'0 0 100px'}>:</Box>
<Box>{formatStorePrice2Read(userInfo?.team?.balance).toFixed(3)}</Box>
</Flex>
{!res.balanceEnough && (
<Box mt={1} color={'red.600'}>
</Box>
)}
</Box>
)();
}
},
errorToast: t('common.error.Update error')
});
const { mutate: onUpdateStatus } = useRequest({
mutationFn: (e: 'true' | 'false') => {
setIsRenew(e);
return putTeamDatasetSubStatus({
status: subSelectMap[e],
type: SubTypeEnum.extraDatasetSize
});
},
successToast: t('common.Update success'),
errorToast: t('common.error.Update error')
});
const isLoading = isPaying || isFetchingPreviewCheck;
return (
<MyModal
isOpen
iconSrc="/imgs/module/db.png"
title={t('support.wallet.subscription.Dataset store')}
>
<ModalBody>
<>
<Flex alignItems={'center'}>
{t('support.user.Price')}
<MyTooltip label={t('support.wallet.subscription.Dataset store price tip')}>
<QuestionOutlineIcon ml={1} />
</MyTooltip>
</Flex>
<Markdown
source={`
| 套餐知识库容量 | ${teamSubPlan?.standardMaxDatasetSize || Infinity}条 |
| --- | --- |
| 额外知识库 | ${datasetStorePrice}元/1000条/月 |
`}
/>
</>
<Flex mt={4}>
<Box flex={'0 0 120px'}>{t('support.wallet.subscription.Current dataset store')}: </Box>
<Box ml={2} fontWeight={'bold'} flex={1}>
{teamSubPlan?.extraDatasetSize?.currentExtraDatasetSize || 0}
{t('core.dataset.data.unit')}
</Box>
</Flex>
{teamSubPlan?.extraDatasetSize?.nextExtraDatasetSize !== undefined && (
<Flex mt={4}>
<Box flex={'0 0 120px'}>{t('support.wallet.subscription.Next sub dataset size')}: </Box>
<Box ml={2} fontWeight={'bold'} flex={1}>
{teamSubPlan?.extraDatasetSize?.nextExtraDatasetSize || 0}
{t('core.dataset.data.unit')}
</Box>
</Flex>
)}
{!!teamSubPlan?.extraDatasetSize?.startTime && (
<Flex mt={3}>
<Box flex={'0 0 120px'}>: </Box>
<Box ml={2}>{formatTime2YMDHM(teamSubPlan?.extraDatasetSize?.startTime)}</Box>
</Flex>
)}
{!!teamSubPlan?.extraDatasetSize?.expiredTime && (
<Flex mt={3}>
<Box flex={'0 0 120px'}>: </Box>
<Box ml={2}>{formatTime2YMDHM(teamSubPlan?.extraDatasetSize?.expiredTime)}</Box>
</Flex>
)}
<Flex mt={3} alignItems={'center'}>
<Box flex={'0 0 120px'}>: </Box>
<MySelect
ml={2}
value={isRenew}
size={'sm'}
w={'150px'}
list={[
{ label: '自动续费', value: 'true' },
{ label: '不自动续费', value: 'false' }
]}
onchange={onUpdateStatus}
/>
</Flex>
<Box mt={4}>
<Box>{t('support.wallet.subscription.Update extra dataset size')}</Box>
<Flex alignItems={'center'} mt={1}>
<NumberInput
flex={1}
min={0}
max={10000}
step={1}
value={datasetSize}
position={'relative'}
onChange={(e) => {
setDatasetSize(Number(e));
}}
>
<NumberInputField value={datasetSize} step={1} min={0} max={10000} />
<NumberInputStepper>
<NumberIncrementStepper />
<NumberDecrementStepper />
</NumberInputStepper>
</NumberInput>
<Box ml={2}>000{t('core.dataset.data.unit')}</Box>
</Flex>
</Box>
</ModalBody>
<ModalFooter>
<Button variant={'whiteBase'} onClick={onClose}>
{t('common.Close')}
</Button>
{datasetSize * 1000 !== teamSubPlan?.extraDatasetSize?.nextExtraDatasetSize && (
<Button ml={3} isLoading={isLoading} onClick={onClickPreviewCheck}>
{t('common.Confirm')}
</Button>
)}
</ModalFooter>
<ConfirmModal />
</MyModal>
);
};
export default SubDatasetModal;

View File

@@ -15,8 +15,7 @@ export const defaultApp: AppDetailType = {
tmbId: '',
permission: 'private',
isOwner: false,
canWrite: false,
teamTags: ['']
canWrite: false
};
export const defaultOutLinkForm: OutLinkEditType = {
@@ -24,7 +23,7 @@ export const defaultOutLinkForm: OutLinkEditType = {
responseDetail: false,
limit: {
QPM: 100,
maxUsagePoints: -1
credit: -1
}
};

View File

@@ -14,12 +14,6 @@ export type InitChatProps = {
chatId?: string;
loadCustomFeedbacks?: boolean;
};
/* ---------- chat ----------- */
export type chatByTeamProps = {
teamId?: string;
appId?: string;
outLinkUid?: string;
};
export type InitOutLinkChatProps = {
chatId?: string;
shareId?: string;
@@ -45,7 +39,6 @@ export type InitChatResponse = {
/* ---------- history ----------- */
export type getHistoriesProps = {
appId?: string;
authToken?: string;
// share chat
shareId?: string;
outLinkUid?: string; // authToken/uid

View File

@@ -4,34 +4,42 @@ export const Prompt_QuoteTemplateList: PromptTemplateItem[] = [
{
title: '标准模板',
desc: '标准提示词,用于结构不固定的知识库。',
value: `{{q}}
{{a}}`
value: `<data>
{{q}}
{{a}}
</data>`
},
{
title: '问答模板',
desc: '适合 QA 问答结构的知识库可以让AI较为严格的按预设内容回答',
value: `<Question>
value: `<QA>
<问题>
{{q}}
</Question>
<Answer>
</问题>
<答案>
{{a}}
</Answer>`
</答案>
</QA>`
},
{
title: '标准严格模板',
desc: '在标准模板基础上,对模型的回答做更严格的要求。',
value: `{{q}}
{{a}}`
value: `<data>
{{q}}
{{a}}
</data>`
},
{
title: '严格问答模板',
desc: '在问答模板基础上,对模型的回答做更严格的要求。',
value: `<Question>
value: `<QA>
<问题>
{{q}}
</Question>
<Answer>
</问题>
<答案>
{{a}}
</Answer>`
</答案>
</QA>`
}
];
@@ -39,16 +47,14 @@ export const Prompt_QuotePromptList: PromptTemplateItem[] = [
{
title: '标准模板',
desc: '',
value: `使用 <Data></Data> 标记中的内容作为你的知识:
value: `使用 <data></data> 标记中的内容作为你的知识:
<Data>
{{quote}}
</Data>
回答要求:
- 如果你不清楚答案,你需要澄清。
- 避免提及你是从 <Data></Data> 获取的知识。
- 保持答案与 <Data></Data> 中描述的一致。
- 避免提及你是从 <data></data> 获取的知识。
- 保持答案与 <data></data> 中描述的一致。
- 使用 Markdown 语法优化回答格式。
- 使用与问题相同的语言回答。
@@ -59,9 +65,7 @@ export const Prompt_QuotePromptList: PromptTemplateItem[] = [
desc: '',
value: `使用 <QA></QA> 标记中的问答对进行回答。
<QA>
{{quote}}
</QA>
回答要求:
- 选择其中一个或多个问答对进行回答。
@@ -74,20 +78,18 @@ export const Prompt_QuotePromptList: PromptTemplateItem[] = [
{
title: '标准严格模板',
desc: '',
value: `忘记你已有的知识,仅使用 <Data></Data> 标记中的内容作为你的知识:
value: `忘记你已有的知识,仅使用 <data></data> 标记中的内容作为你的知识:
<Data>
{{quote}}
</Data>
思考流程:
1. 判断问题是否与 <Data></Data> 标记中的内容有关。
1. 判断问题是否与 <data></data> 标记中的内容有关。
2. 如果有关,你按下面的要求回答。
3. 如果无关,你直接拒绝回答本次问题。
回答要求:
- 避免提及你是从 <Data></Data> 获取的知识。
- 保持答案与 <Data></Data> 中描述的一致。
- 避免提及你是从 <data></data> 获取的知识。
- 保持答案与 <data></data> 中描述的一致。
- 使用 Markdown 语法优化回答格式。
- 使用与问题相同的语言回答。
@@ -98,9 +100,7 @@ export const Prompt_QuotePromptList: PromptTemplateItem[] = [
desc: '',
value: `忘记你已有的知识,仅使用 <QA></QA> 标记中的问答对进行回答。
<QA>
{{quote}}
</QA>}
思考流程:
1. 判断问题是否与 <QA></QA> 标记中的内容有关。

View File

@@ -2,7 +2,6 @@ import { useEffect } from 'react';
import { useRouter } from 'next/router';
import { serviceSideProps } from '@/web/common/utils/i18n';
import { useSystemStore } from '@/web/common/system/useSystemStore';
import { Box } from '@chakra-ui/react';
function Error() {
const router = useRouter();
@@ -25,12 +24,11 @@ function Error() {
}, []);
return (
<Box whiteSpace={'pre-wrap'}>
{`出现未捕获的异常。
1. 私有部署用户90%由于配置文件不正确导致。
2. 部分系统不兼容相关API。大部分是苹果的safari 浏览器导致,可以尝试更换 chrome。
3. 请关闭浏览器翻译功能,部分翻译导致页面崩溃。`}
</Box>
<p>
safari chrome
</p>
);
}

View File

@@ -0,0 +1,160 @@
import React, { useMemo } from 'react';
import {
ModalBody,
Flex,
Box,
Table,
Thead,
Tbody,
Tr,
Th,
Td,
TableContainer
} from '@chakra-ui/react';
import { BillItemType } from '@fastgpt/global/support/wallet/bill/type.d';
import dayjs from 'dayjs';
import { BillSourceMap } from '@fastgpt/global/support/wallet/bill/constants';
import { formatStorePrice2Read } from '@fastgpt/global/support/wallet/bill/tools';
import MyModal from '@/components/MyModal';
import { useTranslation } from 'next-i18next';
const BillDetail = ({ bill, onClose }: { bill: BillItemType; onClose: () => void }) => {
const { t } = useTranslation();
const filterBillList = useMemo(
() => bill.list.filter((item) => item && item.moduleName),
[bill.list]
);
const {
hasModel,
hasTokens,
hasInputTokens,
hasOutputTokens,
hasCharsLen,
hasDuration,
hasDataLen,
hasDatasetSize
} = useMemo(() => {
let hasModel = false;
let hasTokens = false;
let hasInputTokens = false;
let hasOutputTokens = false;
let hasCharsLen = false;
let hasDuration = false;
let hasDataLen = false;
let hasDatasetSize = false;
bill.list.forEach((item) => {
if (item.model !== undefined) {
hasModel = true;
}
if (typeof item.tokenLen === 'number') {
hasTokens = true;
}
if (typeof item.inputTokens === 'number') {
hasInputTokens = true;
}
if (typeof item.outputTokens === 'number') {
hasOutputTokens = true;
}
if (typeof item.charsLength === 'number') {
hasCharsLen = true;
}
if (typeof item.duration === 'number') {
hasDuration = true;
}
if (typeof item.datasetSize === 'number') {
hasDatasetSize = true;
}
});
return {
hasModel,
hasTokens,
hasInputTokens,
hasOutputTokens,
hasCharsLen,
hasDuration,
hasDataLen,
hasDatasetSize
};
}, [bill.list]);
return (
<MyModal
isOpen={true}
onClose={onClose}
iconSrc="/imgs/modal/bill.svg"
title={t('user.Bill Detail')}
maxW={['90vw', '700px']}
>
<ModalBody>
{/* <Flex alignItems={'center'} pb={4}>
<Box flex={'0 0 80px'}>{t('wallet.bill.bill username')}:</Box>
<Box>{t(bill.memberName)}</Box>
</Flex> */}
<Flex alignItems={'center'} pb={4}>
<Box flex={'0 0 80px'}>{t('wallet.bill.Number')}:</Box>
<Box>{bill.id}</Box>
</Flex>
<Flex alignItems={'center'} pb={4}>
<Box flex={'0 0 80px'}>{t('wallet.bill.Time')}:</Box>
<Box>{dayjs(bill.time).format('YYYY/MM/DD HH:mm:ss')}</Box>
</Flex>
<Flex alignItems={'center'} pb={4}>
<Box flex={'0 0 80px'}>{t('wallet.bill.App name')}:</Box>
<Box>{t(bill.appName) || '-'}</Box>
</Flex>
<Flex alignItems={'center'} pb={4}>
<Box flex={'0 0 80px'}>{t('wallet.bill.Source')}:</Box>
<Box>{t(BillSourceMap[bill.source]?.label)}</Box>
</Flex>
<Flex alignItems={'center'} pb={4}>
<Box flex={'0 0 80px'}>{t('wallet.bill.Total')}:</Box>
<Box fontWeight={'bold'}>{bill.total}</Box>
</Flex>
<Box pb={4}>
<Box flex={'0 0 80px'} mb={1}>
{t('wallet.bill.Bill Module')}
</Box>
<TableContainer>
<Table>
<Thead>
<Tr>
<Th>{t('wallet.bill.Module name')}</Th>
{hasModel && <Th>{t('wallet.bill.Ai model')}</Th>}
{hasTokens && <Th>{t('wallet.bill.Token Length')}</Th>}
{hasInputTokens && <Th>{t('wallet.bill.Input Token Length')}</Th>}
{hasOutputTokens && <Th>{t('wallet.bill.Output Token Length')}</Th>}
{hasCharsLen && <Th>{t('wallet.bill.Text Length')}</Th>}
{hasDuration && <Th>{t('wallet.bill.Duration')}</Th>}
{hasDatasetSize && (
<Th>{t('support.wallet.subscription.type.extraDatasetSize')}</Th>
)}
<Th>()</Th>
</Tr>
</Thead>
<Tbody>
{filterBillList.map((item, i) => (
<Tr key={i}>
<Td>{t(item.moduleName)}</Td>
{hasModel && <Td>{item.model ?? '-'}</Td>}
{hasTokens && <Td>{item.tokenLen ?? '-'}</Td>}
{hasInputTokens && <Td>{item.inputTokens ?? '-'}</Td>}
{hasOutputTokens && <Td>{item.outputTokens ?? '-'}</Td>}
{hasCharsLen && <Td>{item.charsLength ?? '-'}</Td>}
{hasDuration && <Td>{item.duration ?? '-'}</Td>}
{hasDatasetSize && <Td>{item.datasetSize ?? '-'}</Td>}
<Td>{formatStorePrice2Read(item.amount)}</Td>
</Tr>
))}
</Tbody>
</Table>
</TableContainer>
</Box>
</ModalBody>
</MyModal>
);
};
export default BillDetail;

View File

@@ -1,6 +1,5 @@
import React, { useState, useCallback, useMemo, useEffect } from 'react';
import React, { useEffect, useMemo, useState } from 'react';
import {
Button,
Table,
Thead,
Tbody,
@@ -10,38 +9,43 @@ import {
TableContainer,
Flex,
Box,
ModalBody
Button
} from '@chakra-ui/react';
import { getBills, checkBalancePayResult } from '@/web/support/wallet/bill/api';
import type { BillSchemaType } from '@fastgpt/global/support/wallet/bill/type.d';
import { BillSourceEnum, BillSourceMap } from '@fastgpt/global/support/wallet/bill/constants';
import { getUserBills } from '@/web/support/wallet/bill/api';
import type { BillItemType } from '@fastgpt/global/support/wallet/bill/type';
import { usePagination } from '@/web/common/hooks/usePagination';
import { useLoading } from '@/web/common/hooks/useLoading';
import dayjs from 'dayjs';
import { formatStorePrice2Read } from '@fastgpt/global/support/wallet/usage/tools';
import { useToast } from '@fastgpt/web/hooks/useToast';
import MyIcon from '@fastgpt/web/components/common/Icon';
import DateRangePicker, { type DateRangeType } from '@/components/DateRangePicker';
import { addDays } from 'date-fns';
import dynamic from 'next/dynamic';
import { useSystemStore } from '@/web/common/system/useSystemStore';
import { useTranslation } from 'next-i18next';
import MySelect from '@/components/Select';
import {
BillTypeEnum,
billPayWayMap,
billStatusMap,
billTypeMap
} from '@fastgpt/global/support/wallet/bill/constants';
import { usePagination } from '@/web/common/hooks/usePagination';
import MyBox from '@/components/common/MyBox';
import { useRequest } from '@/web/common/hooks/useRequest';
import MyModal from '@/components/MyModal';
import { standardSubLevelMap, subModeMap } from '@fastgpt/global/support/wallet/sub/constants';
import { useQuery } from '@tanstack/react-query';
import { useUserStore } from '@/web/support/user/useUserStore';
import { getTeamMembers } from '@/web/support/user/team/api';
import Avatar from '@/components/Avatar';
const BillDetail = dynamic(() => import('./BillDetail'));
const BillTable = () => {
const { t } = useTranslation();
const { toast } = useToast();
const [billType, setBillType] = useState<`${BillTypeEnum}` | ''>('');
const [billDetail, setBillDetail] = useState<BillSchemaType>();
const { Loading } = useLoading();
const [dateRange, setDateRange] = useState<DateRangeType>({
from: addDays(new Date(), -7),
to: new Date()
});
const [billSource, setBillSource] = useState<`${BillSourceEnum}` | ''>('');
const { isPc } = useSystemStore();
const { userInfo } = useUserStore();
const [billDetail, setBillDetail] = useState<BillItemType>();
const billTypeList = useMemo(
const sourceList = useMemo(
() => [
{ label: t('common.All'), value: '' },
...Object.entries(billTypeMap).map(([key, value]) => ({
...Object.entries(BillSourceMap).map(([key, value]) => ({
label: t(value.label),
value: key
}))
@@ -49,193 +53,134 @@ const BillTable = () => {
[t]
);
const [selectTmbId, setSelectTmbId] = useState(userInfo?.team?.tmbId);
const { data: members = [] } = useQuery(['getMembers', userInfo?.team?.teamId], () => {
if (!userInfo?.team?.teamId) return [];
return getTeamMembers(userInfo.team.teamId);
});
const tmbList = useMemo(
() =>
members.map((item) => ({
label: (
<Flex alignItems={'center'}>
<Avatar src={item.avatar} w={'16px'} mr={1} />
{item.memberName}
</Flex>
),
value: item.tmbId
})),
[members]
);
const {
data: bills,
isLoading,
Pagination,
getData,
total
} = usePagination<BillSchemaType>({
api: getBills,
pageSize: 20,
getData
} = usePagination<BillItemType>({
api: getUserBills,
pageSize: isPc ? 20 : 10,
params: {
type: billType
dateStart: dateRange.from || new Date(),
dateEnd: addDays(dateRange.to || new Date(), 1),
source: billSource,
teamMemberId: selectTmbId
},
defaultRequest: false
});
const { mutate: handleRefreshPayOrder, isLoading: isRefreshing } = useRequest({
mutationFn: async (payId: string) => {
try {
const data = await checkBalancePayResult(payId);
toast({
title: data,
status: 'success'
});
} catch (error: any) {
toast({
title: error?.message,
status: 'warning'
});
console.log(error);
}
try {
getData(1);
} catch (error) {}
}
});
useEffect(() => {
getData(1);
}, [billType]);
}, [billSource, selectTmbId]);
return (
<MyBox
isLoading={isLoading || isRefreshing}
position={'relative'}
h={'100%'}
overflow={'overlay'}
py={[0, 5]}
px={[3, 8]}
>
<TableContainer>
<Flex flexDirection={'column'} py={[0, 5]} h={'100%'} position={'relative'}>
<Flex
flexDir={['column', 'row']}
gap={2}
w={'100%'}
px={[3, 8]}
alignItems={['flex-end', 'center']}
>
{tmbList.length > 1 && userInfo?.team?.canWrite && (
<Flex alignItems={'center'}>
<Box mr={2} flexShrink={0}>
{t('support.user.team.member')}
</Box>
<MySelect
size={'sm'}
minW={'100px'}
list={tmbList}
value={selectTmbId}
onchange={setSelectTmbId}
/>
</Flex>
)}
<Box flex={'1'} />
<Flex alignItems={'center'} gap={3}>
<DateRangePicker
defaultDate={dateRange}
position="bottom"
onChange={setDateRange}
onSuccess={() => getData(1)}
/>
<Pagination />
</Flex>
</Flex>
<TableContainer px={[3, 8]} position={'relative'} flex={'1 0 0'} h={0} overflowY={'auto'}>
<Table>
<Thead>
<Tr>
<Th>#</Th>
{/* <Th>{t('user.team.Member Name')}</Th> */}
<Th>{t('user.Time')}</Th>
<Th>
<MySelect
list={billTypeList}
value={billType}
list={sourceList}
value={billSource}
size={'sm'}
onchange={(e) => {
setBillType(e);
setBillSource(e);
}}
w={'130px'}
></MySelect>
</Th>
<Th>{t('user.Time')}</Th>
<Th>{t('support.wallet.Amount')}</Th>
<Th>{t('support.wallet.bill.Status')}</Th>
<Th>{t('user.Application Name')}</Th>
<Th>{t('user.Total Amount')}</Th>
<Th></Th>
</Tr>
</Thead>
<Tbody fontSize={'sm'}>
{bills.map((item, i) => (
<Tr key={item._id}>
<Td>{i + 1}</Td>
<Td>{t(billTypeMap[item.type]?.label)}</Td>
{bills.map((item) => (
<Tr key={item.id}>
{/* <Td>{item.memberName}</Td> */}
<Td>{dayjs(item.time).format('YYYY/MM/DD HH:mm:ss')}</Td>
<Td>{t(BillSourceMap[item.source]?.label)}</Td>
<Td>{t(item.appName) || '-'}</Td>
<Td>{item.total}</Td>
<Td>
{item.createTime ? dayjs(item.createTime).format('YYYY/MM/DD HH:mm:ss') : '-'}
</Td>
<Td>{formatStorePrice2Read(item.price)}</Td>
<Td>{t(billStatusMap[item.status]?.label)}</Td>
<Td>
{item.status === 'NOTPAY' && (
<Button mr={4} onClick={() => handleRefreshPayOrder(item._id)} size={'sm'}>
{t('common.Update')}
</Button>
)}
<Button variant={'whiteBase'} size={'sm'} onClick={() => setBillDetail(item)}>
{t('common.Detail')}
<Button size={'sm'} variant={'whitePrimary'} onClick={() => setBillDetail(item)}>
</Button>
</Td>
</Tr>
))}
</Tbody>
</Table>
{total >= 20 && (
<Flex mt={3} justifyContent={'flex-end'}>
<Pagination />
</Flex>
)}
{!isLoading && bills.length === 0 && (
<Flex
mt={'20vh'}
flexDirection={'column'}
alignItems={'center'}
justifyContent={'center'}
>
<MyIcon name="empty" w={'48px'} h={'48px'} color={'transparent'} />
<Box mt={2} color={'myGray.500'}>
{t('support.wallet.noBill')}
</Box>
</Flex>
)}
</TableContainer>
{!!billDetail && (
<BillDetailModal bill={billDetail} onClose={() => setBillDetail(undefined)} />
{!isLoading && bills.length === 0 && (
<Flex flex={'1 0 0'} flexDirection={'column'} alignItems={'center'}>
<MyIcon name="empty" w={'48px'} h={'48px'} color={'transparent'} />
<Box mt={2} color={'myGray.500'}>
使~
</Box>
</Flex>
)}
</MyBox>
<Loading loading={isLoading} fixed={false} />
{!!billDetail && <BillDetail bill={billDetail} onClose={() => setBillDetail(undefined)} />}
</Flex>
);
};
export default BillTable;
function BillDetailModal({ bill, onClose }: { bill: BillSchemaType; onClose: () => void }) {
const { t } = useTranslation();
return (
<MyModal
isOpen={true}
onClose={onClose}
iconSrc="/imgs/modal/bill.svg"
title={t('support.wallet.usage.Usage Detail')}
maxW={['90vw', '700px']}
>
<ModalBody>
<Flex alignItems={'center'} pb={4}>
<Box flex={'0 0 120px'}>{t('support.wallet.bill.Number')}:</Box>
<Box>{bill.orderId}</Box>
</Flex>
<Flex alignItems={'center'} pb={4}>
<Box flex={'0 0 120px'}>{t('support.wallet.usage.Time')}:</Box>
<Box>{dayjs(bill.createTime).format('YYYY/MM/DD HH:mm:ss')}</Box>
</Flex>
<Flex alignItems={'center'} pb={4}>
<Box flex={'0 0 120px'}>{t('support.wallet.bill.Status')}:</Box>
<Box>{t(billStatusMap[bill.status]?.label)}</Box>
</Flex>
{!!bill.metadata?.payWay && (
<Flex alignItems={'center'} pb={4}>
<Box flex={'0 0 120px'}>{t('support.wallet.bill.payWay.Way')}:</Box>
<Box>{t(billPayWayMap[bill.metadata.payWay]?.label)}</Box>
</Flex>
)}
<Flex alignItems={'center'} pb={4}>
<Box flex={'0 0 120px'}>{t('support.wallet.Amount')}:</Box>
<Box>{formatStorePrice2Read(bill.price)}</Box>
</Flex>
<Flex alignItems={'center'} pb={4}>
<Box flex={'0 0 120px'}>{t('support.wallet.bill.Type')}:</Box>
<Box>{t(billTypeMap[bill.type]?.label)}</Box>
</Flex>
{!!bill.metadata?.subMode && (
<Flex alignItems={'center'} pb={4}>
<Box flex={'0 0 120px'}>{t('support.wallet.subscription.mode.Period')}:</Box>
<Box>{t(subModeMap[bill.metadata.subMode]?.label)}</Box>
</Flex>
)}
{!!bill.metadata?.standSubLevel && (
<Flex alignItems={'center'} pb={4}>
<Box flex={'0 0 120px'}>{t('support.wallet.subscription.Stand plan level')}:</Box>
<Box>{t(standardSubLevelMap[bill.metadata.standSubLevel]?.label)}</Box>
</Flex>
)}
{bill.metadata?.datasetSize !== undefined && (
<Flex alignItems={'center'} pb={4}>
<Box flex={'0 0 120px'}>{t('support.wallet.subscription.Extra dataset size')}:</Box>
<Box>{bill.metadata?.datasetSize}</Box>
</Flex>
)}
{bill.metadata?.extraPoints !== undefined && (
<Flex alignItems={'center'} pb={4}>
<Box flex={'0 0 120px'}>{t('support.wallet.subscription.Extra ai points')}:</Box>
<Box>{bill.metadata.extraPoints}</Box>
</Flex>
)}
</ModalBody>
</MyModal>
);
}
export default React.memo(BillTable);

View File

@@ -1,14 +1,15 @@
import React, { useCallback, useMemo } from 'react';
import React, { useCallback, useMemo, useRef } from 'react';
import {
Box,
Flex,
Button,
useDisclosure,
useTheme,
Divider,
Select,
Input,
Link,
Progress,
Grid
Progress
} from '@chakra-ui/react';
import { useForm } from 'react-hook-form';
import { UserUpdateParams } from '@/types/user';
@@ -21,68 +22,35 @@ import { useSelectFile } from '@/web/common/file/hooks/useSelectFile';
import { compressImgFileAndUpload } from '@/web/common/file/controller';
import { useSystemStore } from '@/web/common/system/useSystemStore';
import { useTranslation } from 'next-i18next';
import { timezoneList } from '@fastgpt/global/common/time/timezone';
import Avatar from '@/components/Avatar';
import MyIcon from '@fastgpt/web/components/common/Icon';
import MyTooltip from '@/components/MyTooltip';
import { langMap, setLngStore } from '@/web/common/utils/i18n';
import { useRouter } from 'next/router';
import { formatStorePrice2Read } from '@fastgpt/global/support/wallet/usage/tools';
import MySelect from '@/components/Select';
import { formatStorePrice2Read } from '@fastgpt/global/support/wallet/bill/tools';
import { putUpdateMemberName } from '@/web/support/user/team/api';
import { getDocPath } from '@/web/common/system/doc';
import { getTeamDatasetValidSub } from '@/web/support/wallet/sub/api';
import { MongoImageTypeEnum } from '@fastgpt/global/common/file/image/constants';
import { standardSubLevelMap } from '@fastgpt/global/support/wallet/sub/constants';
import { formatTime2YMDHM } from '@fastgpt/global/common/string/time';
import { AI_POINT_USAGE_CARD_ROUTE } from '@/web/support/wallet/sub/constants';
import StandardPlanContentList from '@/components/support/wallet/StandardPlanContentList';
const StandDetailModal = dynamic(() => import('./standardDetailModal'));
const TeamMenu = dynamic(() => import('@/components/support/user/team/TeamMenu'));
const PayModal = dynamic(() => import('./PayModal'));
const UpdatePswModal = dynamic(() => import('./UpdatePswModal'));
const OpenAIAccountModal = dynamic(() => import('./OpenAIAccountModal'));
const SubDatasetModal = dynamic(() => import('@/components/support/wallet/SubDatasetModal'));
const Account = () => {
const { isPc } = useSystemStore();
const { initUserInfo } = useUserStore();
useQuery(['init'], initUserInfo);
return (
<Box py={[3, '28px']} px={['5vw', '64px']}>
{isPc ? (
<Flex justifyContent={'center'}>
<Box flex={'0 0 330px'}>
<MyInfo />
<Box mt={9}>
<Other />
</Box>
</Box>
<Box ml={'45px'} flex={'1 0 0'} maxW={'600px'}>
<PlanUsage />
</Box>
</Flex>
) : (
<>
<MyInfo />
<PlanUsage />
<Other />
</>
)}
</Box>
);
};
export default React.memo(Account);
const MyInfo = () => {
const UserInfo = () => {
const theme = useTheme();
const { feConfigs } = useSystemStore();
const { t } = useTranslation();
const { userInfo, updateUserInfo } = useUserStore();
const router = useRouter();
const { feConfigs, systemVersion } = useSystemStore();
const { t, i18n } = useTranslation();
const { userInfo, updateUserInfo, initUserInfo } = useUserStore();
const timezones = useRef(timezoneList());
const { reset } = useForm<UserUpdateParams>({
defaultValues: userInfo as UserType
});
const { isPc } = useSystemStore();
const { toast } = useToast();
const {
@@ -95,11 +63,13 @@ const MyInfo = () => {
onClose: onCloseUpdatePsw,
onOpen: onOpenUpdatePsw
} = useDisclosure();
const { isOpen: isOpenOpenai, onClose: onCloseOpenai, onOpen: onOpenOpenai } = useDisclosure();
const {
isOpen: isOpenStandardModal,
onClose: onCloseStandardModal,
onOpen: onOpenStandardModal
isOpen: isOpenSubDatasetModal,
onClose: onCloseSubDatasetModal,
onOpen: onOpenSubDatasetModal
} = useDisclosure();
const { File, onOpen: onOpenSelectFile } = useSelectFile({
fileType: '.jpg,.png',
multiple: false
@@ -147,73 +117,81 @@ const MyInfo = () => {
[onclickSave, t, toast, userInfo]
);
useQuery(['init'], initUserInfo, {
onSuccess(res) {
reset(res);
}
});
const {
data: teamSubPlan = { totalPoints: 0, usedPoints: 0, datasetMaxSize: 800, usedDatasetSize: 0 }
} = useQuery(['getTeamDatasetValidSub'], getTeamDatasetValidSub);
const datasetUsageMap = useMemo(() => {
const rate = teamSubPlan.usedDatasetSize / teamSubPlan.datasetMaxSize;
const colorScheme = (() => {
if (rate < 0.5) return 'green';
if (rate < 0.8) return 'yellow';
return 'red';
})();
return {
colorScheme,
value: rate * 100,
maxSize: teamSubPlan.datasetMaxSize || t('common.Unlimited'),
usedSize: teamSubPlan.usedDatasetSize
};
}, [teamSubPlan.usedDatasetSize, teamSubPlan.datasetMaxSize, t]);
return (
<Box>
{/* user info */}
{isPc && (
<Flex alignItems={'center'} fontSize={'xl'} h={'30px'}>
<MyIcon mr={2} name={'acount/user'} w={'20px'} />
{t('support.user.User self info')}
</Flex>
)}
<Box mt={[0, 6]}>
{isPc ? (
<Flex alignItems={'center'} cursor={'pointer'}>
<Box flex={'0 0 80px'}>{t('support.user.Avatar')}:&nbsp;</Box>
<MyTooltip label={t('common.avatar.Select Avatar')}>
<Box
w={['44px', '56px']}
h={['44px', '56px']}
borderRadius={'50%'}
border={theme.borders.base}
overflow={'hidden'}
p={'2px'}
boxShadow={'0 0 5px rgba(0,0,0,0.1)'}
mb={2}
onClick={onOpenSelectFile}
>
<Avatar src={userInfo?.avatar} borderRadius={'50%'} w={'100%'} h={'100%'} />
</Box>
</MyTooltip>
</Flex>
) : (
<Flex
flexDirection={'column'}
alignItems={'center'}
cursor={'pointer'}
onClick={onOpenSelectFile}
<Box
display={['block', 'flex']}
py={[2, 10]}
justifyContent={'center'}
alignItems={'flex-start'}
>
<Flex
flexDirection={'column'}
alignItems={'center'}
cursor={'pointer'}
onClick={onOpenSelectFile}
>
<MyTooltip label={'更换头像'}>
<Box
w={['44px', '54px']}
h={['44px', '54px']}
borderRadius={'50%'}
border={theme.borders.base}
overflow={'hidden'}
p={'2px'}
boxShadow={'0 0 5px rgba(0,0,0,0.1)'}
mb={2}
>
<MyTooltip label={'更换头像'}>
<Box
w={['44px', '54px']}
h={['44px', '54px']}
borderRadius={'50%'}
border={theme.borders.base}
overflow={'hidden'}
p={'2px'}
boxShadow={'0 0 5px rgba(0,0,0,0.1)'}
mb={2}
>
<Avatar src={userInfo?.avatar} borderRadius={'50%'} w={'100%'} h={'100%'} />
</Box>
</MyTooltip>
<Avatar src={userInfo?.avatar} borderRadius={'50%'} w={'100%'} h={'100%'} />
</Box>
</MyTooltip>
<Flex alignItems={'center'} fontSize={'sm'} color={'myGray.600'}>
<MyIcon mr={1} name={'edit'} w={'14px'} />
{t('user.Replace')}
</Flex>
</Flex>
)}
<Flex alignItems={'center'} fontSize={'sm'} color={'myGray.600'}>
<MyIcon mr={1} name={'edit'} w={'14px'} />
{t('user.Replace')}
</Flex>
</Flex>
<Box
display={['flex', 'block']}
flexDirection={'column'}
alignItems={'center'}
ml={[0, 10]}
mt={[6, 0]}
>
{feConfigs.isPlus && (
<Flex mt={[0, 4]} alignItems={'center'}>
<Flex mb={4} alignItems={'center'} w={['85%', '300px']}>
<Box flex={'0 0 80px'}>{t('user.Member Name')}:&nbsp;</Box>
<Input
flex={'1 0 0'}
flex={1}
defaultValue={userInfo?.team?.memberName || 'Member'}
title={t('user.Edit name')}
borderColor={'transparent'}
pl={'10px'}
transform={'translateX(-11px)'}
maxLength={20}
onBlur={(e) => {
@@ -226,265 +204,109 @@ const MyInfo = () => {
/>
</Flex>
)}
<Flex alignItems={'center'} mt={6}>
<Flex alignItems={'center'} w={['85%', '300px']}>
<Box flex={'0 0 80px'}>{t('user.Account')}:&nbsp;</Box>
<Box flex={1}>{userInfo?.username}</Box>
</Flex>
{feConfigs.isPlus && (
<Flex mt={6} alignItems={'center'}>
<Box flex={'0 0 80px'}>{t('user.Password')}:&nbsp;</Box>
<Box flex={1}>*****</Box>
<Button size={'sm'} variant={'whitePrimary'} onClick={onOpenUpdatePsw}>
{t('user.Change')}
</Button>
</Flex>
)}
<Flex mt={6} alignItems={'center'}>
<Flex mt={6} alignItems={'center'} w={['85%', '300px']}>
<Box flex={'0 0 80px'}>{t('user.Team')}:&nbsp;</Box>
<Box flex={1}>
<TeamMenu />
</Box>
</Flex>
{feConfigs.isPlus && (
<Box mt={6} whiteSpace={'nowrap'}>
<Flex alignItems={'center'}>
<Box flex={'0 0 80px'} fontSize={'md'}>
{t('user.team.Balance')}:&nbsp;
</Box>
<Box flex={1}>
<strong>{formatStorePrice2Read(userInfo?.team?.balance).toFixed(3)}</strong>
</Box>
{feConfigs?.show_pay && userInfo?.team?.canWrite && (
<Button size={'sm'} ml={5} onClick={onOpenPayModal}>
{t('user.Pay')}
</Button>
)}
</Flex>
</Box>
)}
</Box>
{isOpenPayModal && <PayModal onClose={onClosePayModal} />}
{isOpenUpdatePsw && <UpdatePswModal onClose={onCloseUpdatePsw} />}
<File onSelect={onSelectFile} />
</Box>
);
};
const PlanUsage = () => {
const { isPc } = useSystemStore();
const router = useRouter();
const { t } = useTranslation();
const { userInfo, initUserInfo, teamPlanStatus } = useUserStore();
const { reset } = useForm<UserUpdateParams>({
defaultValues: userInfo as UserType
});
const planName = useMemo(() => {
if (!teamPlanStatus?.standard?.currentSubLevel) return '';
return standardSubLevelMap[teamPlanStatus.standard.currentSubLevel].label;
}, [teamPlanStatus?.standard?.currentSubLevel]);
const standardPlan = teamPlanStatus?.standard;
useQuery(['init'], initUserInfo, {
onSuccess(res) {
reset(res);
}
});
const datasetUsageMap = useMemo(() => {
if (!teamPlanStatus) {
return {
colorScheme: 'green',
value: 0,
maxSize: t('common.Unlimited'),
usedSize: 0
};
}
const rate = teamPlanStatus.usedDatasetSize / teamPlanStatus.datasetMaxSize;
const colorScheme = (() => {
if (rate < 0.5) return 'green';
if (rate < 0.8) return 'yellow';
return 'red';
})();
return {
colorScheme,
value: rate * 100,
maxSize: teamPlanStatus.datasetMaxSize || t('common.Unlimited'),
usedSize: teamPlanStatus.usedDatasetSize
};
}, [teamPlanStatus, t]);
const aiPointsUsageMap = useMemo(() => {
if (!teamPlanStatus) {
return {
colorScheme: 'green',
value: 0,
maxSize: t('common.Unlimited'),
usedSize: 0
};
}
const rate = teamPlanStatus.usedPoints / teamPlanStatus.totalPoints;
const colorScheme = (() => {
if (rate < 0.5) return 'green';
if (rate < 0.8) return 'yellow';
return 'red';
})();
return {
colorScheme,
value: rate * 100,
maxSize: teamPlanStatus.totalPoints || t('common.Unlimited'),
usedSize: teamPlanStatus.usedPoints
};
}, [teamPlanStatus, t]);
return standardPlan ? (
<Box mt={[6, 0]}>
<Flex fontSize={'xl'} h={'30px'}>
<Flex alignItems={'center'}>
<MyIcon mr={2} name={'acount/plans'} w={'20px'} />
{t('support.wallet.subscription.Team plan and usage')}
</Flex>
<Button
ml={4}
variant={'whitePrimary'}
size={'sm'}
onClick={() => router.push(AI_POINT_USAGE_CARD_ROUTE)}
>
{t('support.user.Price')}
</Button>
{/* <Button ml={4} variant={'whitePrimary'} size={'sm'}>
套餐详情
</Button> */}
</Flex>
<Box
mt={[3, 6]}
bg={'white'}
borderWidth={'1px'}
borderColor={'borderColor.low'}
borderRadius={'md'}
>
<Flex px={[5, 10]} py={[3, 6]}>
<Flex mt={6} alignItems={'center'} w={['85%', '300px']}>
<Box flex={'0 0 80px'}>{t('user.Language')}:&nbsp;</Box>
<Box flex={'1 0 0'}>
<Box color={'myGray.600'} fontSize="sm">
{t('support.wallet.subscription.Current plan')}
</Box>
<Box fontWeight={'bold'} fontSize="xl">
{t(planName)}
</Box>
<Flex mt="3" color={'#485264'} fontSize="sm">
<Box>{t('common.Expired Time')}:</Box>
<Box ml={2}>{formatTime2YMDHM(standardPlan?.expiredTime)}</Box>
</Flex>
<MySelect
value={i18n.language}
list={Object.entries(langMap).map(([key, lang]) => ({
label: lang.label,
value: key
}))}
onchange={(val: any) => {
const lang = val;
setLngStore(lang);
router.replace(router.basePath, router.asPath, { locale: lang });
}}
/>
</Box>
<Button onClick={() => router.push('/price')}>
{t('support.wallet.subscription.Upgrade plan')}
</Flex>
<Flex mt={6} alignItems={'center'} w={['85%', '300px']}>
<Box flex={'0 0 80px'}>{t('user.Timezone')}:&nbsp;</Box>
<Select
value={userInfo?.timezone}
onChange={(e) => {
if (!userInfo) return;
onclickSave({ ...userInfo, timezone: e.target.value });
}}
>
{timezones.current.map((item) => (
<option key={item.value} value={item.value}>
{item.name}
</option>
))}
</Select>
</Flex>
<Flex mt={6} alignItems={'center'} w={['85%', '300px']}>
<Box flex={'0 0 80px'}>{t('user.Password')}:&nbsp;</Box>
<Box flex={1}>*****</Box>
<Button size={['sm', 'md']} variant={'whitePrimary'} ml={5} onClick={onOpenUpdatePsw}>
{t('user.Change')}
</Button>
</Flex>
<Box py={3} borderTopWidth={'1px'} borderTopColor={'borderColor.base'}>
<Box py={[0, 3]} px={[5, 10]} overflow={'auto'}>
<StandardPlanContentList
level={standardPlan?.currentSubLevel}
mode={standardPlan.currentMode}
/>
</Box>
</Box>
</Box>
<Box
mt={6}
bg={'white'}
borderWidth={'1px'}
borderColor={'borderColor.low'}
borderRadius={'md'}
px={[5, 10]}
py={[4, 7]}
>
<Box width={'100%'}>
<Flex alignItems={'center'}>
<Flex alignItems={'center'}>
<Box fontWeight={'bold'}>{t('support.user.team.Dataset usage')}</Box>
<Box color={'myGray.600'} ml={2}>
{datasetUsageMap.usedSize}/{datasetUsageMap.maxSize}
{feConfigs.isPlus && (
<>
<Box mt={6} whiteSpace={'nowrap'} w={['85%', '300px']}>
<Flex alignItems={'center'}>
<Box flex={'0 0 80px'} fontSize={'md'}>
{t('user.team.Balance')}:&nbsp;
</Box>
<Box flex={1}>
<strong>{formatStorePrice2Read(userInfo?.team?.balance).toFixed(3)}</strong>
</Box>
{feConfigs?.show_pay && userInfo?.team?.canWrite && (
<Button size={['sm', 'md']} ml={5} onClick={onOpenPayModal}>
{t('user.Pay')}
</Button>
)}
</Flex>
</Box>
{feConfigs?.show_pay && (
<Box mt={6} whiteSpace={'nowrap'} w={['85%', '300px']}>
<Flex alignItems={'center'}>
<Box flex={'1 0 0'} fontSize={'md'}>
{t('support.user.team.Dataset usage')}:&nbsp;{datasetUsageMap.usedSize}/
{datasetUsageMap.maxSize}
</Box>
{userInfo?.team?.canWrite && (
<Button size={'sm'} onClick={onOpenSubDatasetModal}>
{t('support.wallet.Buy more')}
</Button>
)}
</Flex>
<Box mt={1}>
<Progress
value={datasetUsageMap.value}
colorScheme={datasetUsageMap.colorScheme}
borderRadius={'md'}
isAnimated
hasStripe
borderWidth={'1px'}
borderColor={'borderColor.base'}
/>
</Box>
</Box>
</Flex>
</Flex>
<Box mt={3}>
<Progress
value={datasetUsageMap.value}
colorScheme={datasetUsageMap.colorScheme}
borderRadius={'md'}
isAnimated
hasStripe
borderWidth={'1px'}
borderColor={'borderColor.low'}
/>
</Box>
</Box>
<Box mt="9" width={'100%'}>
<Flex alignItems={'center'}>
<Flex alignItems={'center'}>
<Box fontWeight={'bold'}>{t('support.wallet.subscription.AI points')}</Box>
<Box color={'myGray.600'} ml={2}>
{Math.round(teamPlanStatus.usedPoints)}/{teamPlanStatus.totalPoints}
</Box>
</Flex>
</Flex>
<Box mt={3}>
<Progress
value={aiPointsUsageMap.value}
colorScheme={aiPointsUsageMap.colorScheme}
borderRadius={'md'}
isAnimated
hasStripe
borderWidth={'1px'}
borderColor={'borderColor.low'}
/>
</Box>
</Box>
<Flex></Flex>
</Box>
</Box>
) : null;
};
const Other = () => {
const theme = useTheme();
const { toast } = useToast();
const { feConfigs, systemVersion } = useSystemStore();
const { t } = useTranslation();
const { userInfo, updateUserInfo, initUserInfo, teamPlanStatus } = useUserStore();
const { reset } = useForm<UserUpdateParams>({
defaultValues: userInfo as UserType
});
)}
</>
)}
const { isOpen: isOpenOpenai, onClose: onCloseOpenai, onOpen: onOpenOpenai } = useDisclosure();
const onclickSave = useCallback(
async (data: UserType) => {
await updateUserInfo({
avatar: data.avatar,
timezone: data.timezone,
openaiAccount: data.openaiAccount
});
reset(data);
toast({
title: '更新数据成功',
status: 'success'
});
},
[reset, toast, updateUserInfo]
);
return (
<Box>
<Grid gridGap={4} mt={3}>
{feConfigs?.docUrl && (
<Link
bg={'white'}
href={getDocPath('/docs/intro')}
target="_blank"
display={'flex'}
mt={4}
w={['85%', '300px']}
py={3}
px={6}
border={theme.borders.sm}
@@ -504,53 +326,64 @@ const Other = () => {
</Box>
</Link>
)}
<Link
href={feConfigs.chatbotUrl}
target="_blank"
display={'flex'}
py={3}
px={6}
bg={'white'}
border={theme.borders.sm}
borderWidth={'1.5px'}
borderRadius={'md'}
alignItems={'center'}
userSelect={'none'}
textDecoration={'none !important'}
>
<MyIcon name={'core/app/aiLight'} w={'18px'} />
<Box ml={2} flex={1}>
{t('common.system.Help Chatbot')}
</Box>
</Link>
{feConfigs?.show_openai_account && (
<Flex
bg={'white'}
py={4}
{feConfigs?.chatbotUrl && (
<Link
href={feConfigs.chatbotUrl}
target="_blank"
display={'flex'}
mt={4}
w={['85%', '300px']}
py={3}
px={6}
border={theme.borders.sm}
borderWidth={'1.5px'}
borderRadius={'md'}
alignItems={'center'}
cursor={'pointer'}
userSelect={'none'}
onClick={onOpenOpenai}
textDecoration={'none !important'}
>
<MyIcon name={'common/openai'} w={'18px'} color={'myGray.600'} />
<MyIcon name={'core/app/aiLight'} w={'18px'} />
<Box ml={2} flex={1}>
OpenAI/OneAPI
{t('common.system.Help Chatbot')}
</Box>
<Box
w={'9px'}
h={'9px'}
borderRadius={'50%'}
bg={userInfo?.openaiAccount?.key ? '#67c13b' : 'myGray.500'}
/>
</Flex>
</Link>
)}
</Grid>
{feConfigs?.show_openai_account && (
<>
<Divider my={3} />
<MyTooltip label={'点击配置账号'}>
<Flex
w={['85%', '300px']}
py={4}
px={6}
border={theme.borders.sm}
borderWidth={'1.5px'}
borderRadius={'md'}
bg={'myWhite.300'}
alignItems={'center'}
cursor={'pointer'}
userSelect={'none'}
onClick={onOpenOpenai}
>
<MyIcon name={'common/openai'} w={'18px'} color={'myGray.600'} />
<Box ml={2} flex={1}>
OpenAI/OneAPI
</Box>
<Box
w={'9px'}
h={'9px'}
borderRadius={'50%'}
bg={userInfo?.openaiAccount?.key ? '#67c13b' : 'myGray.500'}
/>
</Flex>
</MyTooltip>
</>
)}
</Box>
{isOpenPayModal && <PayModal onClose={onClosePayModal} />}
{isOpenUpdatePsw && <UpdatePswModal onClose={onCloseUpdatePsw} />}
{isOpenOpenai && userInfo && (
<OpenAIAccountModal
defaultData={userInfo?.openaiAccount}
@@ -563,6 +396,10 @@ const Other = () => {
onClose={onCloseOpenai}
/>
)}
{isOpenSubDatasetModal && <SubDatasetModal onClose={onCloseSubDatasetModal} />}
<File onSelect={onSelectFile} />
</Box>
);
};
export default React.memo(UserInfo);

View File

@@ -1,439 +0,0 @@
import React, { useCallback, useMemo, useRef } from 'react';
import {
Box,
Flex,
Button,
useDisclosure,
useTheme,
Divider,
Select,
Input,
Link,
Progress
} from '@chakra-ui/react';
import { useForm } from 'react-hook-form';
import { UserUpdateParams } from '@/types/user';
import { useToast } from '@fastgpt/web/hooks/useToast';
import { useUserStore } from '@/web/support/user/useUserStore';
import type { UserType } from '@fastgpt/global/support/user/type.d';
import { useQuery } from '@tanstack/react-query';
import dynamic from 'next/dynamic';
import { useSelectFile } from '@/web/common/file/hooks/useSelectFile';
import { compressImgFileAndUpload } from '@/web/common/file/controller';
import { useSystemStore } from '@/web/common/system/useSystemStore';
import { useTranslation } from 'next-i18next';
import { timezoneList } from '@fastgpt/global/common/time/timezone';
import Avatar from '@/components/Avatar';
import MyIcon from '@fastgpt/web/components/common/Icon';
import MyTooltip from '@/components/MyTooltip';
import { langMap, setLngStore } from '@/web/common/utils/i18n';
import { useRouter } from 'next/router';
import MySelect from '@/components/Select';
import { formatStorePrice2Read } from '@fastgpt/global/support/wallet/usage/tools';
import { putUpdateMemberName } from '@/web/support/user/team/api';
import { getDocPath } from '@/web/common/system/doc';
import { getTeamPlanStatus } from '@/web/support/wallet/sub/api';
import { MongoImageTypeEnum } from '@fastgpt/global/common/file/image/constants';
const TeamMenu = dynamic(() => import('@/components/support/user/team/TeamMenu'));
const PayModal = dynamic(() => import('./PayModal'));
const UpdatePswModal = dynamic(() => import('./UpdatePswModal'));
const OpenAIAccountModal = dynamic(() => import('./OpenAIAccountModal'));
const UserInfo = () => {
const theme = useTheme();
const router = useRouter();
const { feConfigs, systemVersion } = useSystemStore();
const { t, i18n } = useTranslation();
const { userInfo, updateUserInfo, initUserInfo } = useUserStore();
const timezones = useRef(timezoneList());
const { reset } = useForm<UserUpdateParams>({
defaultValues: userInfo as UserType
});
const { toast } = useToast();
const {
isOpen: isOpenPayModal,
onClose: onClosePayModal,
onOpen: onOpenPayModal
} = useDisclosure();
const {
isOpen: isOpenUpdatePsw,
onClose: onCloseUpdatePsw,
onOpen: onOpenUpdatePsw
} = useDisclosure();
const { isOpen: isOpenOpenai, onClose: onCloseOpenai, onOpen: onOpenOpenai } = useDisclosure();
const { File, onOpen: onOpenSelectFile } = useSelectFile({
fileType: '.jpg,.png',
multiple: false
});
const onclickSave = useCallback(
async (data: UserType) => {
await updateUserInfo({
avatar: data.avatar,
timezone: data.timezone,
openaiAccount: data.openaiAccount
});
reset(data);
toast({
title: '更新数据成功',
status: 'success'
});
},
[reset, toast, updateUserInfo]
);
const onSelectFile = useCallback(
async (e: File[]) => {
const file = e[0];
if (!file || !userInfo) return;
try {
const src = await compressImgFileAndUpload({
type: MongoImageTypeEnum.userAvatar,
file,
maxW: 300,
maxH: 300
});
onclickSave({
...userInfo,
avatar: src
});
} catch (err: any) {
toast({
title: typeof err === 'string' ? err : t('common.error.Select avatar failed'),
status: 'warning'
});
}
},
[onclickSave, t, toast, userInfo]
);
useQuery(['init'], initUserInfo, {
onSuccess(res) {
reset(res);
}
});
const {
data: teamSubPlan = {
totalPoints: 0,
usedPoints: 0,
datasetMaxSize: 800,
usedDatasetSize: 0
}
} = useQuery(['getTeamPlanStatus'], getTeamPlanStatus);
const datasetUsageMap = useMemo(() => {
const rate = teamSubPlan.usedDatasetSize / teamSubPlan.datasetMaxSize;
const colorScheme = (() => {
if (rate < 0.5) return 'green';
if (rate < 0.8) return 'yellow';
return 'red';
})();
return {
colorScheme,
value: rate * 100,
maxSize: teamSubPlan.datasetMaxSize || t('common.Unlimited'),
usedSize: teamSubPlan.usedDatasetSize
};
}, [teamSubPlan.usedDatasetSize, teamSubPlan.datasetMaxSize, t]);
const aiPointsUsageMap = useMemo(() => {
const rate = teamSubPlan.usedPoints / teamSubPlan.totalPoints;
const colorScheme = (() => {
if (rate < 0.5) return 'green';
if (rate < 0.8) return 'yellow';
return 'red';
})();
return {
colorScheme,
value: rate * 100,
maxSize: teamSubPlan.totalPoints || t('common.Unlimited'),
usedSize: teamSubPlan.usedPoints
};
}, [teamSubPlan.usedPoints, teamSubPlan.totalPoints, t]);
return (
<Box
display={['block', 'flex']}
py={[2, 10]}
justifyContent={'center'}
alignItems={'flex-start'}
>
<Flex
flexDirection={'column'}
alignItems={'center'}
cursor={'pointer'}
onClick={onOpenSelectFile}
>
<MyTooltip label={'更换头像'}>
<Box
w={['44px', '54px']}
h={['44px', '54px']}
borderRadius={'50%'}
border={theme.borders.base}
overflow={'hidden'}
p={'2px'}
boxShadow={'0 0 5px rgba(0,0,0,0.1)'}
mb={2}
>
<Avatar src={userInfo?.avatar} borderRadius={'50%'} w={'100%'} h={'100%'} />
</Box>
</MyTooltip>
<Flex alignItems={'center'} fontSize={'sm'} color={'myGray.600'}>
<MyIcon mr={1} name={'edit'} w={'14px'} />
{t('user.Replace')}
</Flex>
</Flex>
<Box
display={['flex', 'block']}
flexDirection={'column'}
alignItems={'center'}
ml={[0, 10]}
mt={[6, 0]}
>
{feConfigs.isPlus && (
<Flex mb={4} alignItems={'center'} w={['85%', '300px']}>
<Box flex={'0 0 80px'}>{t('user.Member Name')}:&nbsp;</Box>
<Input
flex={1}
defaultValue={userInfo?.team?.memberName || 'Member'}
title={t('user.Edit name')}
borderColor={'transparent'}
pl={'10px'}
transform={'translateX(-11px)'}
maxLength={20}
onBlur={(e) => {
const val = e.target.value;
if (val === userInfo?.team?.memberName) return;
try {
putUpdateMemberName(val);
} catch (error) {}
}}
/>
</Flex>
)}
<Flex alignItems={'center'} w={['85%', '300px']}>
<Box flex={'0 0 80px'}>{t('user.Account')}:&nbsp;</Box>
<Box flex={1}>{userInfo?.username}</Box>
</Flex>
<Flex mt={6} alignItems={'center'} w={['85%', '300px']}>
<Box flex={'0 0 80px'}>{t('user.Team')}:&nbsp;</Box>
<Box flex={1}>
<TeamMenu />
</Box>
</Flex>
<Flex mt={6} alignItems={'center'} w={['85%', '300px']}>
<Box flex={'0 0 80px'}>{t('user.Language')}:&nbsp;</Box>
<Box flex={'1 0 0'}>
<MySelect
value={i18n.language}
list={Object.entries(langMap).map(([key, lang]) => ({
label: lang.label,
value: key
}))}
onchange={(val: any) => {
const lang = val;
setLngStore(lang);
router.replace(router.basePath, router.asPath, { locale: lang });
}}
/>
</Box>
</Flex>
<Flex mt={6} alignItems={'center'} w={['85%', '300px']}>
<Box flex={'0 0 80px'}>{t('user.Timezone')}:&nbsp;</Box>
<Select
value={userInfo?.timezone}
onChange={(e) => {
if (!userInfo) return;
onclickSave({ ...userInfo, timezone: e.target.value });
}}
>
{timezones.current.map((item) => (
<option key={item.value} value={item.value}>
{item.name}
</option>
))}
</Select>
</Flex>
<Flex mt={6} alignItems={'center'} w={['85%', '300px']}>
<Box flex={'0 0 80px'}>{t('user.Password')}:&nbsp;</Box>
<Box flex={1}>*****</Box>
<Button size={['sm', 'md']} variant={'whitePrimary'} ml={5} onClick={onOpenUpdatePsw}>
{t('user.Change')}
</Button>
</Flex>
{feConfigs.isPlus && (
<>
<Box mt={6} whiteSpace={'nowrap'} w={['85%', '300px']}>
<Flex alignItems={'center'}>
<Box flex={'0 0 80px'} fontSize={'md'}>
{t('user.team.Balance')}:&nbsp;
</Box>
<Box flex={1}>
<strong>{formatStorePrice2Read(userInfo?.team?.balance).toFixed(3)}</strong>
</Box>
{feConfigs?.show_pay && userInfo?.team?.canWrite && (
<Button size={['sm', 'md']} ml={5} onClick={onOpenPayModal}>
{t('user.Pay')}
</Button>
)}
</Flex>
</Box>
{feConfigs?.show_pay && (
<>
<Box mt={6} whiteSpace={'nowrap'} w={['85%', '300px']}>
<Flex alignItems={'center'}>
<Box flex={'1 0 0'} fontSize={'md'}>
{t('support.user.team.Dataset usage')}:&nbsp;{datasetUsageMap.usedSize}/
{datasetUsageMap.maxSize}
</Box>
{userInfo?.team?.canWrite && (
<Button size={'sm'} onClick={() => router.push('/price')}>
{t('support.wallet.Buy more')}
</Button>
)}
</Flex>
<Box mt={1}>
<Progress
value={datasetUsageMap.value}
colorScheme={datasetUsageMap.colorScheme}
borderRadius={'md'}
isAnimated
hasStripe
borderWidth={'1px'}
borderColor={'borderColor.base'}
/>
</Box>
</Box>
<Box mt={6} whiteSpace={'nowrap'} w={['85%', '300px']}>
<Flex alignItems={'center'}>
<Box flex={'1 0 0'} fontSize={'md'}>
AI积分: {Math.round(teamSubPlan.usedPoints)}/{teamSubPlan.totalPoints}
</Box>
</Flex>
<Box mt={1}>
<Progress
value={aiPointsUsageMap.value}
colorScheme={aiPointsUsageMap.colorScheme}
borderRadius={'md'}
isAnimated
hasStripe
borderWidth={'1px'}
borderColor={'borderColor.base'}
/>
</Box>
</Box>
</>
)}
</>
)}
{feConfigs?.docUrl && (
<Link
href={getDocPath('/docs/intro')}
target="_blank"
display={'flex'}
mt={4}
w={['85%', '300px']}
py={3}
px={6}
border={theme.borders.sm}
borderWidth={'1.5px'}
borderRadius={'md'}
alignItems={'center'}
userSelect={'none'}
textDecoration={'none !important'}
>
<MyIcon name={'common/courseLight'} w={'18px'} color={'myGray.600'} />
<Box ml={2} flex={1}>
{t('system.Help Document')}
</Box>
<Box w={'8px'} h={'8px'} borderRadius={'50%'} bg={'#67c13b'} />
<Box fontSize={'md'} ml={2}>
V{systemVersion}
</Box>
</Link>
)}
{feConfigs?.chatbotUrl && (
<Link
href={feConfigs.chatbotUrl}
target="_blank"
display={'flex'}
mt={4}
w={['85%', '300px']}
py={3}
px={6}
border={theme.borders.sm}
borderWidth={'1.5px'}
borderRadius={'md'}
alignItems={'center'}
userSelect={'none'}
textDecoration={'none !important'}
>
<MyIcon name={'core/app/aiLight'} w={'18px'} />
<Box ml={2} flex={1}>
{t('common.system.Help Chatbot')}
</Box>
</Link>
)}
{feConfigs?.show_openai_account && (
<>
<Divider my={3} />
<MyTooltip label={'点击配置账号'}>
<Flex
w={['85%', '300px']}
py={4}
px={6}
border={theme.borders.sm}
borderWidth={'1.5px'}
borderRadius={'md'}
bg={'myWhite.300'}
alignItems={'center'}
cursor={'pointer'}
userSelect={'none'}
onClick={onOpenOpenai}
>
<MyIcon name={'common/openai'} w={'18px'} color={'myGray.600'} />
<Box ml={2} flex={1}>
OpenAI/OneAPI
</Box>
<Box
w={'9px'}
h={'9px'}
borderRadius={'50%'}
bg={userInfo?.openaiAccount?.key ? '#67c13b' : 'myGray.500'}
/>
</Flex>
</MyTooltip>
</>
)}
</Box>
{isOpenPayModal && <PayModal onClose={onClosePayModal} />}
{isOpenUpdatePsw && <UpdatePswModal onClose={onCloseUpdatePsw} />}
{isOpenOpenai && userInfo && (
<OpenAIAccountModal
defaultData={userInfo?.openaiAccount}
onSuccess={(data) =>
onclickSave({
...userInfo,
openaiAccount: data
})
}
onClose={onCloseOpenai}
/>
)}
<File onSelect={onSelectFile} />
</Box>
);
};
export default React.memo(UserInfo);

View File

@@ -1,45 +1,37 @@
import React, { useState, useCallback } from 'react';
import { ModalFooter, ModalBody, Button, Input, Box, Grid } from '@chakra-ui/react';
import { getWxPayQRCode } from '@/web/support/wallet/bill/api';
import { getPayCode, checkPayResult } from '@/web/support/wallet/pay/api';
import { useToast } from '@fastgpt/web/hooks/useToast';
import { useQuery } from '@tanstack/react-query';
import { useRouter } from 'next/router';
import { getErrText } from '@fastgpt/global/common/error/utils';
import { useTranslation } from 'next-i18next';
import Markdown from '@/components/Markdown';
import MyModal from '@/components/MyModal';
import { BillTypeEnum } from '@fastgpt/global/support/wallet/bill/constants';
import QRCodePayModal, { type QRPayProps } from '@/components/support/wallet/QRCodePayModal';
const PayModal = ({
onClose,
defaultValue,
onSuccess
}: {
defaultValue?: number;
onClose: () => void;
onSuccess?: () => any;
}) => {
const PayModal = ({ onClose }: { onClose: () => void }) => {
const router = useRouter();
const { t } = useTranslation();
const { toast } = useToast();
const [inputVal, setInputVal] = useState<number | undefined>(defaultValue);
const [inputVal, setInputVal] = useState<number | ''>('');
const [loading, setLoading] = useState(false);
const [qrPayData, setQRPayData] = useState<QRPayProps>();
const [payId, setPayId] = useState('');
const handleClickPay = useCallback(async () => {
if (!inputVal || inputVal <= 0 || isNaN(+inputVal)) return;
setLoading(true);
try {
// 获取支付二维码
const res = await getWxPayQRCode({
type: BillTypeEnum.balance,
balance: inputVal
});
setQRPayData({
readPrice: res.readPrice,
codeUrl: res.codeUrl,
billId: res.billId
const res = await getPayCode(inputVal);
new window.QRCode(document.getElementById('payQRCode'), {
text: res.codeUrl,
width: 128,
height: 128,
colorDark: '#000000',
colorLight: '#ffffff',
correctLevel: window.QRCode.CorrectLevel.H
});
setPayId(res.payId);
} catch (err) {
toast({
title: getErrText(err),
@@ -49,48 +41,84 @@ const PayModal = ({
setLoading(false);
}, [inputVal, toast]);
useQuery(
[payId],
() => {
if (!payId) return null;
return checkPayResult(payId);
},
{
enabled: !!payId,
refetchInterval: 3000,
onSuccess(res) {
if (!res) return;
toast({
title: res,
status: 'success'
});
router.reload();
}
}
);
return (
<MyModal isOpen={true} onClose={onClose} title={t('user.Pay')} iconSrc="/imgs/modal/pay.svg">
<MyModal
isOpen={true}
onClose={payId ? undefined : onClose}
title={t('user.Pay')}
iconSrc="/imgs/modal/pay.svg"
>
<ModalBody px={0} display={'flex'} flexDirection={'column'}>
<Grid gridTemplateColumns={'repeat(3,1fr)'} gridGap={5} mb={4} px={6}>
{[10, 20, 50, 100, 200, 500].map((item) => (
<Button
key={item}
variant={item === inputVal ? 'solid' : 'outline'}
onClick={() => setInputVal(item)}
>
{item}
</Button>
))}
</Grid>
<Box px={6}>
<Input
value={inputVal}
type={'number'}
step={1}
placeholder={'其他金额,请取整数'}
onChange={(e) => {
setInputVal(Math.floor(+e.target.value));
}}
></Input>
{!payId && (
<>
<Grid gridTemplateColumns={'repeat(3,1fr)'} gridGap={5} mb={4} px={6}>
{[10, 20, 50, 100, 200, 500].map((item) => (
<Button
key={item}
variant={item === inputVal ? 'solid' : 'outline'}
onClick={() => setInputVal(item)}
>
{item}
</Button>
))}
</Grid>
<Box px={6}>
<Input
value={inputVal}
type={'number'}
step={1}
placeholder={'其他金额,请取整数'}
onChange={(e) => {
setInputVal(Math.floor(+e.target.value));
}}
></Input>
</Box>
</>
)}
{/* 付费二维码 */}
<Box textAlign={'center'}>
{payId && <Box mb={3}>: {inputVal}</Box>}
<Box id={'payQRCode'} display={'inline-block'}></Box>
</Box>
</ModalBody>
<ModalFooter>
<Button variant={'whiteBase'} onClick={onClose}>
{t('common.Close')}
</Button>
<Button
ml={3}
isLoading={loading}
isDisabled={!inputVal || inputVal === 0}
onClick={handleClickPay}
>
</Button>
{!payId && (
<>
<Button variant={'whiteBase'} onClick={onClose}>
{t('common.Close')}
</Button>
<Button
ml={3}
isLoading={loading}
isDisabled={!inputVal || inputVal === 0}
onClick={handleClickPay}
>
</Button>
</>
)}
</ModalFooter>
{!!qrPayData && <QRCodePayModal {...qrPayData} onSuccess={onSuccess} />}
</MyModal>
);
};

View File

@@ -0,0 +1,108 @@
import React, { useState, useCallback } from 'react';
import {
Button,
Table,
Thead,
Tbody,
Tr,
Th,
Td,
TableContainer,
Flex,
Box
} from '@chakra-ui/react';
import { getPayOrders, checkPayResult } from '@/web/support/wallet/pay/api';
import type { PaySchema } from '@fastgpt/global/support/wallet/pay/type.d';
import dayjs from 'dayjs';
import { useQuery } from '@tanstack/react-query';
import { formatStorePrice2Read } from '@fastgpt/global/support/wallet/bill/tools';
import { useToast } from '@fastgpt/web/hooks/useToast';
import { useLoading } from '@/web/common/hooks/useLoading';
import MyIcon from '@fastgpt/web/components/common/Icon';
const PayRecordTable = () => {
const { Loading, setIsLoading } = useLoading();
const [payOrders, setPayOrders] = useState<PaySchema[]>([]);
const { toast } = useToast();
const { isInitialLoading, refetch } = useQuery(['initPayOrder'], getPayOrders, {
onSuccess(res) {
setPayOrders(res);
}
});
const handleRefreshPayOrder = useCallback(
async (payId: string) => {
setIsLoading(true);
try {
const data = await checkPayResult(payId);
toast({
title: data,
status: 'success'
});
} catch (error: any) {
toast({
title: error?.message,
status: 'warning'
});
console.log(error);
}
try {
refetch();
} catch (error) {}
setIsLoading(false);
},
[refetch, setIsLoading, toast]
);
return (
<Box position={'relative'} h={'100%'} overflow={'overlay'}>
{!isInitialLoading && payOrders.length === 0 ? (
<Flex h={'100%'} flexDirection={'column'} alignItems={'center'} justifyContent={'center'}>
<MyIcon name="empty" w={'48px'} h={'48px'} color={'transparent'} />
<Box mt={2} color={'myGray.500'}>
~
</Box>
</Flex>
) : (
<TableContainer py={[0, 5]} px={[3, 8]}>
<Table>
<Thead>
<Tr>
<Th></Th>
<Th></Th>
<Th></Th>
<Th></Th>
<Th></Th>
</Tr>
</Thead>
<Tbody fontSize={'sm'}>
{payOrders.map((item) => (
<Tr key={item._id}>
<Td>{item.orderId}</Td>
<Td>
{item.createTime ? dayjs(item.createTime).format('YYYY/MM/DD HH:mm:ss') : '-'}
</Td>
<Td>{formatStorePrice2Read(item.price)}</Td>
<Td>{item.status}</Td>
<Td>
{item.status === 'NOTPAY' && (
<Button onClick={() => handleRefreshPayOrder(item._id)} size={'sm'}>
</Button>
)}
</Td>
</Tr>
))}
</Tbody>
</Table>
</TableContainer>
)}
<Loading loading={isInitialLoading} fixed={false} />
</Box>
);
};
export default PayRecordTable;

View File

@@ -1,119 +0,0 @@
import React, { useMemo } from 'react';
import {
ModalBody,
Flex,
Box,
Table,
Thead,
Tbody,
Tr,
Th,
Td,
TableContainer
} from '@chakra-ui/react';
import { UsageItemType } from '@fastgpt/global/support/wallet/usage/type.d';
import dayjs from 'dayjs';
import { UsageSourceMap } from '@fastgpt/global/support/wallet/usage/constants';
import MyModal from '@/components/MyModal';
import { useTranslation } from 'next-i18next';
import { formatNumber } from '@fastgpt/global/common/math/tools';
const UsageDetail = ({ usage, onClose }: { usage: UsageItemType; onClose: () => void }) => {
const { t } = useTranslation();
const filterBillList = useMemo(
() => usage.list.filter((item) => item && item.moduleName),
[usage.list]
);
const { hasModel, hasCharsLen, hasDuration } = useMemo(() => {
let hasModel = false;
let hasCharsLen = false;
let hasDuration = false;
let hasDataLen = false;
usage.list.forEach((item) => {
if (item.model !== undefined) {
hasModel = true;
}
if (typeof item.charsLength === 'number') {
hasCharsLen = true;
}
if (typeof item.duration === 'number') {
hasDuration = true;
}
});
return {
hasModel,
hasCharsLen,
hasDuration,
hasDataLen
};
}, [usage.list]);
return (
<MyModal
isOpen={true}
onClose={onClose}
iconSrc="/imgs/modal/bill.svg"
title={t('support.wallet.usage.Usage Detail')}
maxW={['90vw', '700px']}
>
<ModalBody>
<Flex alignItems={'center'} pb={4}>
<Box flex={'0 0 80px'}>{t('support.wallet.bill.Number')}:</Box>
<Box>{usage.id}</Box>
</Flex>
<Flex alignItems={'center'} pb={4}>
<Box flex={'0 0 80px'}>{t('support.wallet.usage.Time')}:</Box>
<Box>{dayjs(usage.time).format('YYYY/MM/DD HH:mm:ss')}</Box>
</Flex>
<Flex alignItems={'center'} pb={4}>
<Box flex={'0 0 80px'}>{t('support.wallet.usage.App name')}:</Box>
<Box>{t(usage.appName) || '-'}</Box>
</Flex>
<Flex alignItems={'center'} pb={4}>
<Box flex={'0 0 80px'}>{t('support.wallet.usage.Source')}:</Box>
<Box>{t(UsageSourceMap[usage.source]?.label)}</Box>
</Flex>
<Flex alignItems={'center'} pb={4}>
<Box flex={'0 0 80px'}>{t('support.wallet.usage.Total points')}:</Box>
<Box fontWeight={'bold'}>{formatNumber(usage.totalPoints)}</Box>
</Flex>
<Box pb={4}>
<Box flex={'0 0 80px'} mb={1}>
{t('support.wallet.usage.Bill Module')}
</Box>
<TableContainer>
<Table>
<Thead>
<Tr>
<Th>{t('support.wallet.usage.Module name')}</Th>
{hasModel && <Th>{t('support.wallet.usage.Ai model')}</Th>}
{hasCharsLen && <Th>{t('support.wallet.usage.Text Length')}</Th>}
{hasDuration && <Th>{t('support.wallet.usage.Duration')}</Th>}
<Th>{t('support.wallet.usage.Total points')}</Th>
</Tr>
</Thead>
<Tbody>
{filterBillList.map((item, i) => (
<Tr key={i}>
<Td>{t(item.moduleName)}</Td>
{hasModel && <Td>{item.model ?? '-'}</Td>}
{hasCharsLen && <Td>{item.charsLength ?? '-'}</Td>}
{hasDuration && <Td>{item.duration ?? '-'}</Td>}
<Td>{formatNumber(item.amount)}</Td>
</Tr>
))}
</Tbody>
</Table>
</TableContainer>
</Box>
</ModalBody>
</MyModal>
);
};
export default UsageDetail;

View File

@@ -1,189 +0,0 @@
import React, { useEffect, useMemo, useState } from 'react';
import {
Table,
Thead,
Tbody,
Tr,
Th,
Td,
TableContainer,
Flex,
Box,
Button
} from '@chakra-ui/react';
import { UsageSourceEnum, UsageSourceMap } from '@fastgpt/global/support/wallet/usage/constants';
import { getUserUsages } from '@/web/support/wallet/usage/api';
import type { UsageItemType } from '@fastgpt/global/support/wallet/usage/type';
import { usePagination } from '@/web/common/hooks/usePagination';
import { useLoading } from '@/web/common/hooks/useLoading';
import dayjs from 'dayjs';
import MyIcon from '@fastgpt/web/components/common/Icon';
import DateRangePicker, { type DateRangeType } from '@/components/DateRangePicker';
import { addDays } from 'date-fns';
import dynamic from 'next/dynamic';
import { useSystemStore } from '@/web/common/system/useSystemStore';
import { useTranslation } from 'next-i18next';
import MySelect from '@/components/Select';
import { useQuery } from '@tanstack/react-query';
import { useUserStore } from '@/web/support/user/useUserStore';
import { getTeamMembers } from '@/web/support/user/team/api';
import Avatar from '@/components/Avatar';
import { formatNumber } from '../../../../../../packages/global/common/math/tools';
const UsageDetail = dynamic(() => import('./UsageDetail'));
const UsageTable = () => {
const { t } = useTranslation();
const { Loading } = useLoading();
const [dateRange, setDateRange] = useState<DateRangeType>({
from: addDays(new Date(), -7),
to: new Date()
});
const [usageSource, setUsageSource] = useState<`${UsageSourceEnum}` | ''>('');
const { isPc } = useSystemStore();
const { userInfo } = useUserStore();
const [usageDetail, setUsageDetail] = useState<UsageItemType>();
const sourceList = useMemo(
() => [
{ label: t('common.All'), value: '' },
...Object.entries(UsageSourceMap).map(([key, value]) => ({
label: t(value.label),
value: key
}))
],
[t]
);
const [selectTmbId, setSelectTmbId] = useState(userInfo?.team?.tmbId);
const { data: members = [] } = useQuery(['getMembers', userInfo?.team?.teamId], () => {
if (!userInfo?.team?.teamId) return [];
return getTeamMembers(userInfo.team.teamId);
});
const tmbList = useMemo(
() =>
members.map((item) => ({
label: (
<Flex alignItems={'center'}>
<Avatar src={item.avatar} w={'16px'} mr={1} />
{item.memberName}
</Flex>
),
value: item.tmbId
})),
[members]
);
const {
data: usages,
isLoading,
Pagination,
getData
} = usePagination<UsageItemType>({
api: getUserUsages,
pageSize: isPc ? 20 : 10,
params: {
dateStart: dateRange.from || new Date(),
dateEnd: addDays(dateRange.to || new Date(), 1),
source: usageSource,
teamMemberId: selectTmbId
},
defaultRequest: false
});
useEffect(() => {
getData(1);
}, [usageSource, selectTmbId]);
return (
<Flex flexDirection={'column'} py={[0, 5]} h={'100%'} position={'relative'}>
<Flex
flexDir={['column', 'row']}
gap={2}
w={'100%'}
px={[3, 8]}
alignItems={['flex-end', 'center']}
>
{tmbList.length > 1 && userInfo?.team?.canWrite && (
<Flex alignItems={'center'}>
<Box mr={2} flexShrink={0}>
{t('support.user.team.member')}
</Box>
<MySelect
size={'sm'}
minW={'100px'}
list={tmbList}
value={selectTmbId}
onchange={setSelectTmbId}
/>
</Flex>
)}
<Box flex={'1'} />
<Flex alignItems={'center'} gap={3}>
<DateRangePicker
defaultDate={dateRange}
position="bottom"
onChange={setDateRange}
onSuccess={() => getData(1)}
/>
<Pagination />
</Flex>
</Flex>
<TableContainer px={[3, 8]} position={'relative'} flex={'1 0 0'} h={0} overflowY={'auto'}>
<Table>
<Thead>
<Tr>
{/* <Th>{t('user.team.Member Name')}</Th> */}
<Th>{t('user.Time')}</Th>
<Th>
<MySelect
list={sourceList}
value={usageSource}
size={'sm'}
onchange={(e) => {
setUsageSource(e);
}}
w={'130px'}
></MySelect>
</Th>
<Th>{t('user.Application Name')}</Th>
<Th>{t('support.wallet.usage.Total points')}</Th>
<Th></Th>
</Tr>
</Thead>
<Tbody fontSize={'sm'}>
{usages.map((item) => (
<Tr key={item.id}>
{/* <Td>{item.memberName}</Td> */}
<Td>{dayjs(item.time).format('YYYY/MM/DD HH:mm:ss')}</Td>
<Td>{t(UsageSourceMap[item.source]?.label) || '-'}</Td>
<Td>{t(item.appName) || '-'}</Td>
<Td>{formatNumber(item.totalPoints) || 0}</Td>
<Td>
<Button size={'sm'} variant={'whitePrimary'} onClick={() => setUsageDetail(item)}>
</Button>
</Td>
</Tr>
))}
</Tbody>
</Table>
</TableContainer>
{!isLoading && usages.length === 0 && (
<Flex flex={'1 0 0'} flexDirection={'column'} alignItems={'center'}>
<MyIcon name="empty" w={'48px'} h={'48px'} color={'transparent'} />
<Box mt={2} color={'myGray.500'}>
使~
</Box>
</Flex>
)}
<Loading loading={isLoading} fixed={false} />
{!!usageDetail && (
<UsageDetail usage={usageDetail} onClose={() => setUsageDetail(undefined)} />
)}
</Flex>
);
};
export default React.memo(UsageTable);

View File

@@ -1,25 +0,0 @@
import React from 'react';
import { ModalBody, Box, Flex, Input, ModalFooter, Button } from '@chakra-ui/react';
import MyModal from '@/components/MyModal';
import { useTranslation } from 'next-i18next';
import { useForm } from 'react-hook-form';
import { useRequest } from '@/web/common/hooks/useRequest';
import type { UserType } from '@fastgpt/global/support/user/type.d';
const StandDetailModal = ({ onClose }: { onClose: () => void }) => {
const { t } = useTranslation();
return (
<MyModal
isOpen
onClose={onClose}
iconSrc="acount/plansBlue"
title={t('user.OpenAI Account Setting')}
>
<ModalBody></ModalBody>
<ModalFooter></ModalFooter>
</MyModal>
);
};
export default StandDetailModal;

View File

@@ -14,16 +14,18 @@ import { useTranslation } from 'next-i18next';
import Script from 'next/script';
const Promotion = dynamic(() => import('./components/Promotion'));
const UsageTable = dynamic(() => import('./components/UsageTable'));
const BillTable = dynamic(() => import('./components/BillTable'));
const PayRecordTable = dynamic(() => import('./components/PayRecordTable'));
const InformTable = dynamic(() => import('./components/InformTable'));
const ApiKeyTable = dynamic(() => import('./components/ApiKeyTable'));
const PriceBox = dynamic(() => import('@/components/support/wallet/Price'));
enum TabEnum {
'info' = 'info',
'promotion' = 'promotion',
'usage' = 'usage',
'bill' = 'bill',
'price' = 'price',
'pay' = 'pay',
'inform' = 'inform',
'apikey' = 'apikey',
'loginout' = 'loginout'
@@ -43,18 +45,27 @@ const Account = ({ currentTab }: { currentTab: `${TabEnum}` }) => {
...(feConfigs?.isPlus
? [
{
icon: 'support/usage/usageRecordLight',
icon: 'support/bill/billRecordLight',
label: t('user.Usage Record'),
id: TabEnum.usage
id: TabEnum.bill
}
]
: []),
...(feConfigs?.show_pay && userInfo?.team.canWrite
? [
{
icon: 'support/bill/payRecordLight',
label: t('support.wallet.Bills'),
id: TabEnum.bill
icon: 'support/pay/payRecordLight',
label: t('user.Recharge Record'),
id: TabEnum.pay
}
]
: []),
...(feConfigs?.show_pay
? [
{
icon: 'support/pay/priceLight',
label: t('support.user.Price'),
id: TabEnum.price
}
]
: []),
@@ -97,6 +108,11 @@ const Account = ({ currentTab }: { currentTab: `${TabEnum}` }) => {
const { openConfirm, ConfirmModal } = useConfirm({
content: '确认退出登录?'
});
const {
isOpen: isOpenPriceBox,
onOpen: onOpenPriceBox,
onClose: onClosePriceBox
} = useDisclosure();
const router = useRouter();
const theme = useTheme();
@@ -108,6 +124,8 @@ const Account = ({ currentTab }: { currentTab: `${TabEnum}` }) => {
setUserInfo(null);
router.replace('/login');
})();
} else if (tab === TabEnum.price) {
onOpenPriceBox();
} else {
router.replace({
query: {
@@ -116,7 +134,7 @@ const Account = ({ currentTab }: { currentTab: `${TabEnum}` }) => {
});
}
},
[openConfirm, router, setUserInfo]
[onOpenPriceBox, openConfirm, router, setUserInfo]
);
return (
@@ -160,14 +178,16 @@ const Account = ({ currentTab }: { currentTab: `${TabEnum}` }) => {
<Box flex={'1 0 0'} h={'100%'} pb={[4, 0]}>
{currentTab === TabEnum.info && <UserInfo />}
{currentTab === TabEnum.promotion && <Promotion />}
{currentTab === TabEnum.usage && <UsageTable />}
{currentTab === TabEnum.bill && <BillTable />}
{currentTab === TabEnum.pay && <PayRecordTable />}
{currentTab === TabEnum.inform && <InformTable />}
{currentTab === TabEnum.apikey && <ApiKeyTable />}
</Box>
</Flex>
<ConfirmModal />
</PageContainer>
{isOpenPriceBox && <PriceBox onClose={onClosePriceBox} />}
</>
);
};

View File

@@ -1,99 +0,0 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@fastgpt/service/common/response';
import { connectToDatabase } from '@/service/mongo';
import { authCert } from '@fastgpt/service/support/permission/auth/common';
import { PgClient } from '@fastgpt/service/common/vectorStore/pg';
import { PgDatasetTableName } from '@fastgpt/global/common/vectorStore/constants';
import { MongoImage } from '@fastgpt/service/common/file/image/schema';
import { MongoImageSchemaType } from '@fastgpt/global/common/file/image/type';
import { delay } from '@fastgpt/global/common/system/utils';
import { MongoDatasetCollection } from '@fastgpt/service/core/dataset/collection/schema';
import { getNanoid } from '@fastgpt/global/common/string/tools';
import { MongoApp } from '@fastgpt/service/core/app/schema';
import { ModuleItemType } from '@fastgpt/global/core/module/type';
import { DYNAMIC_INPUT_KEY, ModuleInputKeyEnum } from '@fastgpt/global/core/module/constants';
let success = 0;
let deleteImg = 0;
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
// 设置所有app为 inited = false
const result = await MongoApp.updateMany({}, { $set: { inited: false } });
console.log(result);
await initApp();
jsonRes(res, {
message: 'success'
});
} catch (error) {
console.log(error);
jsonRes(res, {
code: 500,
error
});
}
}
const systemKeys: string[] = [
ModuleInputKeyEnum.switch,
ModuleInputKeyEnum.httpMethod,
ModuleInputKeyEnum.httpReqUrl,
ModuleInputKeyEnum.httpHeaders,
DYNAMIC_INPUT_KEY,
ModuleInputKeyEnum.addInputParam
];
const initApp = async (): Promise<any> => {
const app = await MongoApp.findOne({ inited: false }).sort({ updateTime: -1 });
if (!app) {
return;
}
try {
const modules = JSON.parse(JSON.stringify(app.modules)) as ModuleItemType[];
let update = false;
// 找到http模块
modules.forEach((module) => {
if (module.flowType === 'httpRequest') {
const method = module.inputs.find((input) => input.key === ModuleInputKeyEnum.httpMethod);
if (method?.value === 'POST') {
module.inputs.forEach((input) => {
// 更新非系统字段的key
if (!systemKeys.includes(input.key)) {
// 更新output的target
modules.forEach((item) => {
item.outputs.forEach((output) => {
output.targets.forEach((target) => {
if (target.moduleId === module.moduleId && target.key === input.key) {
target.key = `data.${input.key}`;
}
});
});
});
// 更新key
input.key = `data.${input.key}`;
update = true;
}
});
}
}
});
if (update) {
console.log('update http app');
app.modules = modules;
}
app.inited = true;
await app.save();
console.log(++success);
return initApp();
} catch (error) {
console.log(error);
await delay(1000);
return initApp();
}
};

View File

@@ -1,35 +0,0 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@fastgpt/service/common/response';
import { connectToDatabase } from '@/service/mongo';
import { authCert } from '@fastgpt/service/support/permission/auth/common';
import { MongoUsage } from '@fastgpt/service/support/wallet/usage/schema';
import { connectionMongo } from '@fastgpt/service/common/mongo';
/* pg 中的数据搬到 mongo dataset.datas 中,并做映射 */
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
await connectToDatabase();
await authCert({ req, authRoot: true });
// 检查 usage 是否有记录
const totalUsages = await MongoUsage.countDocuments();
if (totalUsages === 0) {
// 重命名 bills 集合成 usages
await connectionMongo.connection.db.renameCollection('bills', 'usages', {
// 强制
dropTarget: true
});
}
jsonRes(res, {
message: 'success'
});
} catch (error) {
console.log(error);
jsonRes(res, {
code: 500,
error
});
}
}

View File

@@ -16,7 +16,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
let filePaths: string[] = [];
try {
const { teamId, tmbId } = await authCert({ req, authToken: true });
const { userId, teamId, tmbId } = await authCert({ req, authToken: true });
const { file, bucketName, metadata } = await upload.doUpload(req, res);

View File

@@ -2,7 +2,7 @@ import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@fastgpt/service/common/response';
import { connectToDatabase } from '@/service/mongo';
import type { CreateQuestionGuideParams } from '@/global/core/ai/api.d';
import { pushQuestionGuideUsage } from '@/service/support/wallet/usage/push';
import { pushQuestionGuideBill } from '@/service/support/wallet/bill/push';
import { createQuestionGuide } from '@fastgpt/service/core/ai/functions/createQuestionGuide';
import { authCertOrShareId } from '@fastgpt/service/support/permission/auth/common';
@@ -19,7 +19,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
const qgModel = global.llmModels[0];
const { result, charsLength } = await createQuestionGuide({
const { result, inputTokens, outputTokens } = await createQuestionGuide({
messages,
model: qgModel.model
});
@@ -28,8 +28,9 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
data: result
});
pushQuestionGuideUsage({
charsLength,
pushQuestionGuideBill({
inputTokens,
outputTokens,
teamId,
tmbId
});

View File

@@ -6,7 +6,6 @@ import { AppTypeEnum } from '@fastgpt/global/core/app/constants';
import { MongoApp } from '@fastgpt/service/core/app/schema';
import { authUserNotVisitor } from '@fastgpt/service/support/permission/auth/user';
import { SimpleModeTemplate_FastGPT_Universal } from '@/global/core/app/constants';
import { checkTeamAppLimit } from '@/service/support/permission/teamLimit';
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
try {
@@ -26,7 +25,12 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
const { teamId, tmbId } = await authUserNotVisitor({ req, authToken: true });
// 上限校验
await checkTeamAppLimit(teamId);
const authCount = await MongoApp.countDocuments({
teamId
});
if (authCount >= 50) {
throw new Error('每个团队上限 50 个应用');
}
// 创建模型
const response = await MongoApp.create({

View File

@@ -0,0 +1,51 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@fastgpt/service/common/response';
import { connectToDatabase } from '@/service/mongo';
import { authCert } from '@fastgpt/service/support/permission/auth/common';
import { Types } from '@fastgpt/service/common/mongo';
import { MongoBill } from '@fastgpt/service/support/wallet/bill/schema';
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
await connectToDatabase();
const { appId, start, end } = req.body as { appId: string; start: number; end: number };
const { userId } = await authCert({ req, authToken: true });
const result = await MongoBill.aggregate([
{
$match: {
appId: new Types.ObjectId(appId),
userId: new Types.ObjectId(userId),
time: { $gte: new Date(start) }
}
},
{
$group: {
_id: {
year: { $year: '$time' },
month: { $month: '$time' },
day: { $dayOfMonth: '$time' }
},
total: { $sum: '$total' }
}
},
{
$project: {
_id: 0,
date: { $dateFromParts: { year: '$_id.year', month: '$_id.month', day: '$_id.day' } },
total: 1
}
},
{ $sort: { date: 1 } }
]);
jsonRes(res, {
data: result
});
} catch (err) {
jsonRes(res, {
code: 500,
error: err
});
}
}

View File

@@ -92,7 +92,7 @@ function simpleChatTemplate({ formData, maxToken }: Props): ModuleItemType[] {
},
{
key: 'model',
type: 'selectLLMModel',
type: 'selectChatModel',
label: 'core.module.input.label.aiModel',
required: true,
valueType: 'string',
@@ -471,7 +471,7 @@ function datasetTemplate({ formData, maxToken }: Props): ModuleItemType[] {
},
{
key: 'model',
type: 'selectLLMModel',
type: 'selectChatModel',
label: 'core.module.input.label.aiModel',
required: true,
valueType: 'string',

View File

@@ -88,7 +88,7 @@ function simpleChatTemplate(formData: AppSimpleEditFormType): ModuleItemType[] {
},
{
key: 'model',
type: 'selectLLMModel',
type: 'selectChatModel',
label: 'core.module.input.label.aiModel',
required: true,
valueType: 'string',
@@ -498,7 +498,7 @@ function datasetTemplate(formData: AppSimpleEditFormType): ModuleItemType[] {
},
{
key: 'model',
type: 'selectLLMModel',
type: 'selectChatModel',
label: 'core.module.input.label.aiModel',
required: true,
valueType: 'string',

View File

@@ -12,7 +12,7 @@ import { getLLMModel } from '@/service/core/ai/model';
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
try {
await connectToDatabase();
const { name, avatar, type, simpleTemplateId, intro, modules, permission, teamTags } =
const { name, avatar, type, simpleTemplateId, intro, modules, permission } =
req.body as AppUpdateParams;
const { appId } = req.query as { appId: string };
@@ -65,7 +65,6 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
avatar,
intro,
permission,
teamTags: teamTags,
...(modules && {
modules
})

View File

@@ -1,82 +0,0 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@fastgpt/service/common/response';
import { connectToDatabase } from '@/service/mongo';
import { MongoApp } from '@fastgpt/service/core/app/schema';
import type { AppUpdateParams } from '@fastgpt/global/core/app/api';
import { authApp } from '@fastgpt/service/support/permission/auth/app';
import { FlowNodeTypeEnum } from '@fastgpt/global/core/module/node/constant';
import { ModuleInputKeyEnum } from '@fastgpt/global/core/module/constants';
import { getLLMModel } from '@/service/core/ai/model';
/* 获取我的模型 */
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
try {
await connectToDatabase();
const { name, avatar, type, simpleTemplateId, intro, modules, permission, teamTags } =
req.body as AppUpdateParams;
const { appId } = req.query as { appId: string };
if (!appId) {
throw new Error('appId is empty');
}
// 凭证校验
await authApp({ req, authToken: true, appId, per: permission ? 'owner' : 'w' });
// check modules
// 1. dataset search limit, less than model quoteMaxToken
if (modules) {
let maxTokens = 3000;
modules.forEach((item) => {
if (item.flowType === FlowNodeTypeEnum.chatNode) {
const model =
item.inputs.find((item) => item.key === ModuleInputKeyEnum.aiModel)?.value || '';
const chatModel = getLLMModel(model);
const quoteMaxToken = chatModel.quoteMaxToken || 3000;
maxTokens = Math.max(maxTokens, quoteMaxToken);
}
});
modules.forEach((item) => {
if (item.flowType === FlowNodeTypeEnum.datasetSearchNode) {
item.inputs.forEach((input) => {
if (input.key === ModuleInputKeyEnum.datasetMaxTokens) {
const val = input.value as number;
if (val > maxTokens) {
input.value = maxTokens;
}
}
});
}
});
}
// 更新模型
await MongoApp.findOneAndUpdate(
{
_id: appId
},
{
name,
type,
simpleTemplateId,
avatar,
intro,
permission,
teamTags: teamTags,
...(modules && {
modules
})
}
);
jsonRes(res);
} catch (err) {
jsonRes(res, {
code: 500,
error: err
});
}
}

View File

@@ -4,13 +4,13 @@ import { sseErrRes } from '@fastgpt/service/common/response';
import { sseResponseEventEnum } from '@fastgpt/service/common/response/constant';
import { responseWrite } from '@fastgpt/service/common/response';
import type { ModuleItemType } from '@fastgpt/global/core/module/type.d';
import { pushChatUsage } from '@/service/support/wallet/usage/push';
import { UsageSourceEnum } from '@fastgpt/global/support/wallet/usage/constants';
import { pushChatBill } from '@/service/support/wallet/bill/push';
import { BillSourceEnum } from '@fastgpt/global/support/wallet/bill/constants';
import type { ChatItemType } from '@fastgpt/global/core/chat/type';
import { authApp } from '@fastgpt/service/support/permission/auth/app';
import { dispatchModules } from '@/service/moduleDispatch';
import { authCert } from '@fastgpt/service/support/permission/auth/common';
import { getUserChatInfoAndAuthTeamPoints } from '@/service/support/permission/auth/team';
import { getUserAndAuthBalance } from '@fastgpt/service/support/user/controller';
export type Props = {
history: ChatItemType[];
@@ -50,10 +50,13 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
]);
// auth balance
const { user } = await getUserChatInfoAndAuthTeamPoints(tmbId);
const user = await getUserAndAuthBalance({
tmbId,
minBalance: 0
});
/* start process */
const { responseData, moduleDispatchBills } = await dispatchModules({
const { responseData } = await dispatchModules({
res,
mode: 'test',
teamId,
@@ -82,13 +85,13 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
});
res.end();
pushChatUsage({
pushChatBill({
appName,
appId,
teamId,
tmbId,
source: UsageSourceEnum.fastgpt,
moduleDispatchBills
source: BillSourceEnum.fastgpt,
response: responseData
});
} catch (err: any) {
res.status(500);

View File

@@ -14,13 +14,10 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
await connectToDatabase();
const { appId, shareId, outLinkUid } = req.query as ClearHistoriesProps;
let chatAppId = appId;
const match = await (async () => {
if (shareId && outLinkUid) {
const { appId, uid } = await authOutLink({ shareId, outLinkUid });
const { uid } = await authOutLink({ shareId, outLinkUid });
chatAppId = appId;
return {
shareId,
outLinkUid: uid
@@ -44,11 +41,11 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
const idList = list.map((item) => item.chatId);
await MongoChatItem.deleteMany({
appId: chatAppId,
appId,
chatId: { $in: idList }
});
await MongoChat.deleteMany({
appId: chatAppId,
appId,
chatId: { $in: idList }
});

View File

@@ -28,13 +28,6 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
}
};
}
if (appId && outLinkUid) {
return {
shareId,
outLinkUid: outLinkUid,
source: ChatSourceEnum.team
};
}
if (appId) {
const { tmbId } = await authCert({ req, authToken: true });
return {
@@ -43,7 +36,6 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
source: ChatSourceEnum.online
};
}
return Promise.reject('Params are error');
})();

View File

@@ -3,9 +3,9 @@ import { jsonRes } from '@fastgpt/service/common/response';
import { connectToDatabase } from '@/service/mongo';
import { GetChatSpeechProps } from '@/global/core/chat/api.d';
import { text2Speech } from '@fastgpt/service/core/ai/audio/speech';
import { pushAudioSpeechUsage } from '@/service/support/wallet/usage/push';
import { pushAudioSpeechBill } from '@/service/support/wallet/bill/push';
import { authCertOrShareId } from '@fastgpt/service/support/permission/auth/common';
import { authType2UsageSource } from '@/service/support/wallet/usage/utils';
import { authType2BillSource } from '@/service/support/wallet/bill/utils';
import { getAudioSpeechModel } from '@/service/core/ai/model';
import { MongoTTSBuffer } from '@fastgpt/service/common/buffer/tts/schema';
@@ -54,12 +54,12 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
speed: ttsConfig.speed,
onSuccess: async ({ model, buffer }) => {
try {
pushAudioSpeechUsage({
pushAudioSpeechBill({
model: model,
charsLength: input.length,
tmbId,
teamId,
source: authType2UsageSource({ authType })
source: authType2BillSource({ authType })
});
await MongoTTSBuffer.create({

View File

@@ -1,37 +0,0 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@fastgpt/service/common/response';
import { connectToDatabase } from '@/service/mongo';
import type { chatByTeamProps } from '@/global/core/chat/api.d';
import axios from 'axios';
import { MongoChatItem } from '@fastgpt/service/core/chat/chatItemSchema';
import { getChatItems } from '@fastgpt/service/core/chat/controller';
import { selectShareResponse } from '@/utils/service/core/chat';
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
await connectToDatabase();
let { teamId, appId, outLinkUid } = req.query as chatByTeamProps;
const history = await MongoChatItem.find({
appId: appId,
outLinkUid: outLinkUid,
teamId: teamId
});
jsonRes(res, {
data: history
});
} catch (err) {
jsonRes(res, {
code: 500,
data: req.query,
error: err
});
}
}
export const config = {
api: {
responseLimit: '10mb'
}
};

View File

@@ -1,91 +0,0 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@fastgpt/service/common/response';
import { connectToDatabase } from '@/service/mongo';
import { getGuideModule } from '@fastgpt/global/core/module/utils';
import { getChatModelNameListByModules } from '@/service/core/app/module';
import { ModuleOutputKeyEnum } from '@fastgpt/global/core/module/constants';
import type { InitChatProps, InitChatResponse } from '@/global/core/chat/api.d';
import { MongoChat } from '@fastgpt/service/core/chat/chatSchema';
import { MongoApp } from '@fastgpt/service/core/app/schema';
import { getChatItems } from '@fastgpt/service/core/chat/controller';
import { AppErrEnum } from '@fastgpt/global/common/error/code/app';
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
await connectToDatabase();
let { appId, chatId, outLinkUid } = req.query as {
chatId?: string;
appId?: string;
outLinkUid?: string;
};
if (!appId) {
return jsonRes(res, {
code: 501,
message: "You don't have an app yet"
});
}
// auth app permission
const [chat, app] = await Promise.all([
// authApp({
// req,
// authToken: false,
// appId,
// per: 'r'
// }),
chatId ? MongoChat.findOne({ appId, chatId }) : undefined,
MongoApp.findById(appId).lean()
]);
if (!app) {
throw new Error(AppErrEnum.unExist);
}
// auth chat permission
// if (chat && chat.outLinkUid !== outLinkUid) {
// throw new Error(ChatErrEnum.unAuthChat);
// }
// // auth chat permission
// if (chat && !app.canWrite && String(tmbId) !== String(chat?.tmbId)) {
// throw new Error(ChatErrEnum.unAuthChat);
// }
// get app and history
const { history } = await getChatItems({
appId,
chatId,
limit: 30,
field: `dataId obj value adminFeedback userBadFeedback userGoodFeedback ${ModuleOutputKeyEnum.responseData}`
});
jsonRes<InitChatResponse>(res, {
data: {
chatId,
appId,
title: chat?.title || '新对话',
userAvatar: undefined,
variables: chat?.variables || {},
history,
app: {
userGuideModule: getGuideModule(app.modules),
chatModels: getChatModelNameListByModules(app.modules),
name: app.name,
avatar: app.avatar,
intro: app.intro
}
}
});
} catch (err) {
jsonRes(res, {
code: 500,
error: err
});
}
}
export const config = {
api: {
responseLimit: '10mb'
}
};

View File

@@ -1,81 +0,0 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@fastgpt/service/common/response';
import { connectToDatabase } from '@/service/mongo';
import { authApp } from '@fastgpt/service/support/permission/auth/app';
import { getGuideModule } from '@fastgpt/global/core/module/utils';
import { getChatModelNameListByModules } from '@/service/core/app/module';
import { ModuleOutputKeyEnum } from '@fastgpt/global/core/module/constants';
import type { InitChatProps, InitChatResponse } from '@/global/core/chat/api.d';
import { MongoChat } from '@fastgpt/service/core/chat/chatSchema';
import { getChatItems } from '@fastgpt/service/core/chat/controller';
import { ChatErrEnum } from '@fastgpt/global/common/error/code/chat';
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
await connectToDatabase();
let { appId, chatId, loadCustomFeedbacks } = req.query as InitChatProps;
if (!appId) {
return jsonRes(res, {
code: 501,
message: "You don't have an app yet"
});
}
// auth app permission
const [{ app, tmbId }, chat] = await Promise.all([
authApp({
req,
authToken: true,
appId,
per: 'r'
}),
chatId ? MongoChat.findOne({ appId, chatId }) : undefined
]);
// // auth chat permission
// if (chat && !app.canWrite && String(tmbId) !== String(chat?.tmbId)) {
// throw new Error(ChatErrEnum.unAuthChat);
// }
// get app and history
const { history } = await getChatItems({
appId,
chatId,
limit: 30,
field: `dataId obj value adminFeedback userBadFeedback userGoodFeedback ${
ModuleOutputKeyEnum.responseData
} ${loadCustomFeedbacks ? 'customFeedbacks' : ''}`
});
jsonRes<InitChatResponse>(res, {
data: {
chatId,
appId,
title: chat?.title || '新对话',
userAvatar: undefined,
variables: chat?.variables || {},
history,
app: {
userGuideModule: getGuideModule(app.modules),
chatModels: getChatModelNameListByModules(app.modules),
name: app.name,
avatar: app.avatar,
intro: app.intro
}
}
});
} catch (err) {
jsonRes(res, {
code: 500,
error: err
});
}
}
export const config = {
api: {
responseLimit: '10mb'
}
};

View File

@@ -24,7 +24,6 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
await MongoChat.findOneAndUpdate(
{ appId, chatId },
{
updateTime: new Date(),
...(customTitle !== undefined && { customTitle }),
...(top !== undefined && { top })
}

View File

@@ -11,12 +11,13 @@ import {
TrainingModeEnum,
DatasetCollectionTypeEnum
} from '@fastgpt/global/core/dataset/constants';
import { checkDatasetLimit } from '@/service/support/permission/teamLimit';
import { checkDatasetLimit } from '@fastgpt/service/support/permission/limit/dataset';
import { predictDataLimitLength } from '@fastgpt/global/core/dataset/utils';
import { createTrainingUsage } from '@fastgpt/service/support/wallet/usage/controller';
import { UsageSourceEnum } from '@fastgpt/global/support/wallet/usage/constants';
import { createTrainingBill } from '@fastgpt/service/support/wallet/bill/controller';
import { BillSourceEnum } from '@fastgpt/global/support/wallet/bill/constants';
import { getLLMModel, getVectorModel } from '@/service/core/ai/model';
import { reloadCollectionChunks } from '@fastgpt/service/core/dataset/collection/utils';
import { getStandardSubPlan } from '@/service/support/wallet/sub/utils';
import { mongoSessionRun } from '@fastgpt/service/common/mongo/sessionRun';
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
@@ -42,7 +43,8 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
// 1. check dataset limit
await checkDatasetLimit({
teamId,
insertLen: predictDataLimitLength(trainingType, new Array(10))
insertLen: predictDataLimitLength(trainingType, new Array(10)),
standardPlans: getStandardSubPlan()
});
const { _id: collectionId } = await mongoSessionRun(async (session) => {
@@ -64,11 +66,11 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
});
// 3. create bill and start sync
const { billId } = await createTrainingUsage({
const { billId } = await createTrainingBill({
teamId,
tmbId,
appName: 'core.dataset.collection.Sync Collection',
billSource: UsageSourceEnum.training,
billSource: BillSourceEnum.training,
vectorModel: getVectorModel(dataset.vectorModel).name,
agentModel: getLLMModel(dataset.agentModel).name,
session

View File

@@ -12,13 +12,14 @@ import {
DatasetCollectionTypeEnum
} from '@fastgpt/global/core/dataset/constants';
import { splitText2Chunks } from '@fastgpt/global/common/string/textSplitter';
import { checkDatasetLimit } from '@/service/support/permission/teamLimit';
import { checkDatasetLimit } from '@fastgpt/service/support/permission/limit/dataset';
import { predictDataLimitLength } from '@fastgpt/global/core/dataset/utils';
import { pushDataToTrainingQueue } from '@/service/core/dataset/data/controller';
import { hashStr } from '@fastgpt/global/common/string/tools';
import { createTrainingUsage } from '@fastgpt/service/support/wallet/usage/controller';
import { UsageSourceEnum } from '@fastgpt/global/support/wallet/usage/constants';
import { createTrainingBill } from '@fastgpt/service/support/wallet/bill/controller';
import { BillSourceEnum } from '@fastgpt/global/support/wallet/bill/constants';
import { getLLMModel, getVectorModel } from '@/service/core/ai/model';
import { getStandardSubPlan } from '@/service/support/wallet/sub/utils';
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
try {
@@ -52,7 +53,8 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
// 2. check dataset limit
await checkDatasetLimit({
teamId,
insertLen: predictDataLimitLength(trainingType, chunks)
insertLen: predictDataLimitLength(trainingType, chunks),
standardPlans: getStandardSubPlan()
});
// 3. create collection and training bill
@@ -72,11 +74,11 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
hashRawText: hashStr(text),
rawTextLength: text.length
}),
createTrainingUsage({
createTrainingBill({
teamId,
tmbId,
appName: name,
billSource: UsageSourceEnum.training,
billSource: BillSourceEnum.training,
vectorModel: getVectorModel(dataset.vectorModel)?.name,
agentModel: getLLMModel(dataset.agentModel)?.name
})

View File

@@ -12,8 +12,8 @@ import {
DatasetCollectionTypeEnum
} from '@fastgpt/global/core/dataset/constants';
import { DatasetErrEnum } from '@fastgpt/global/common/error/code/dataset';
import { createTrainingUsage } from '@fastgpt/service/support/wallet/usage/controller';
import { UsageSourceEnum } from '@fastgpt/global/support/wallet/usage/constants';
import { createTrainingBill } from '@fastgpt/service/support/wallet/bill/controller';
import { BillSourceEnum } from '@fastgpt/global/support/wallet/bill/constants';
import { getLLMModel, getVectorModel } from '@/service/core/ai/model';
import { createOneCollection } from '@fastgpt/service/core/dataset/collection/controller';
import { mongoSessionRun } from '@fastgpt/service/common/mongo/sessionRun';
@@ -56,11 +56,11 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
await mongoSessionRun(async (session) => {
// create training bill
const { billId } = await createTrainingUsage({
const { billId } = await createTrainingBill({
teamId: collection.teamId,
tmbId,
appName: 'core.dataset.collection.Sync Collection',
billSource: UsageSourceEnum.training,
billSource: BillSourceEnum.training,
vectorModel: vectorModelData.name,
agentModel: agentModelData.name,
session

View File

@@ -7,7 +7,6 @@ import { createDefaultCollection } from '@fastgpt/service/core/dataset/collectio
import { authUserNotVisitor } from '@fastgpt/service/support/permission/auth/user';
import { DatasetTypeEnum } from '@fastgpt/global/core/dataset/constants';
import { getLLMModel, getVectorModel, getDatasetModel } from '@/service/core/ai/model';
import { checkTeamDatasetLimit } from '@/service/support/permission/teamLimit';
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
try {
@@ -32,7 +31,13 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
}
// check limit
await checkTeamDatasetLimit(teamId);
const authCount = await MongoDataset.countDocuments({
teamId,
type: DatasetTypeEnum.dataset
});
if (authCount >= 50) {
throw new Error('每个团队上限 50 个知识库');
}
const { _id } = await MongoDataset.create({
name,

View File

@@ -3,7 +3,8 @@ import { jsonRes } from '@fastgpt/service/common/response';
import { withNextCors } from '@fastgpt/service/common/middle/cors';
import { connectToDatabase } from '@/service/mongo';
import { authDatasetData } from '@/service/support/permission/auth/dataset';
import { deleteDatasetData } from '@/service/core/dataset/data/controller';
import { MongoDatasetData } from '@fastgpt/service/core/dataset/data/schema';
import { deleteDatasetDataVector } from '@fastgpt/service/common/vectorStore/controller';
export default withNextCors(async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
try {
@@ -25,7 +26,19 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
per: 'w'
});
await deleteDatasetData(datasetData);
// update mongo data update time
await MongoDatasetData.findByIdAndUpdate(dataId, {
updateTime: new Date()
});
// delete vector data
await deleteDatasetDataVector({
teamId,
idList: datasetData.indexes.map((item) => item.dataId)
});
// delete mongo data
await MongoDatasetData.findByIdAndDelete(dataId);
jsonRes(res, {
data: 'success'

View File

@@ -12,10 +12,12 @@ import { hasSameValue } from '@/service/core/dataset/data/utils';
import { insertData2Dataset } from '@/service/core/dataset/data/controller';
import { authDatasetCollection } from '@fastgpt/service/support/permission/auth/dataset';
import { getCollectionWithDataset } from '@fastgpt/service/core/dataset/controller';
import { pushGenerateVectorUsage } from '@/service/support/wallet/usage/push';
import { authTeamBalance } from '@/service/support/permission/auth/bill';
import { pushGenerateVectorBill } from '@/service/support/wallet/bill/push';
import { InsertOneDatasetDataProps } from '@/global/core/dataset/api';
import { simpleText } from '@fastgpt/global/common/string/tools';
import { checkDatasetLimit } from '@/service/support/permission/teamLimit';
import { checkDatasetLimit } from '@fastgpt/service/support/permission/limit/dataset';
import { getStandardSubPlan } from '@/service/support/wallet/sub/utils';
export default withNextCors(async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
try {
@@ -41,7 +43,8 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
await checkDatasetLimit({
teamId,
insertLen: 1
insertLen: 1,
standardPlans: getStandardSubPlan()
});
// auth collection and get dataset
@@ -49,7 +52,7 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
{
datasetId: { _id: datasetId, vectorModel }
}
] = await Promise.all([getCollectionWithDataset(collectionId)]);
] = await Promise.all([getCollectionWithDataset(collectionId), authTeamBalance(teamId)]);
// format data
const formatQ = simpleText(q);
@@ -87,7 +90,7 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
indexes: formatIndexes
});
pushGenerateVectorUsage({
pushGenerateVectorBill({
teamId,
tmbId,
charsLength,

View File

@@ -8,9 +8,10 @@ import type {
PushDatasetDataResponse
} from '@fastgpt/global/core/dataset/api.d';
import { authDatasetCollection } from '@fastgpt/service/support/permission/auth/dataset';
import { checkDatasetLimit } from '@/service/support/permission/teamLimit';
import { checkDatasetLimit } from '@fastgpt/service/support/permission/limit/dataset';
import { predictDataLimitLength } from '@fastgpt/global/core/dataset/utils';
import { pushDataToTrainingQueue } from '@/service/core/dataset/data/controller';
import { getStandardSubPlan } from '@/service/support/wallet/sub/utils';
export default withNextCors(async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
try {
@@ -37,7 +38,8 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
// auth dataset limit
await checkDatasetLimit({
teamId,
insertLen: predictDataLimitLength(collection.trainingType, data)
insertLen: predictDataLimitLength(collection.trainingType, data),
standardPlans: getStandardSubPlan()
});
jsonRes<PushDatasetDataResponse>(res, {

View File

@@ -4,9 +4,9 @@ import { withNextCors } from '@fastgpt/service/common/middle/cors';
import { connectToDatabase } from '@/service/mongo';
import { updateData2Dataset } from '@/service/core/dataset/data/controller';
import { authDatasetData } from '@/service/support/permission/auth/dataset';
import { pushGenerateVectorUsage } from '@/service/support/wallet/usage/push';
import { authTeamBalance } from '@/service/support/permission/auth/bill';
import { pushGenerateVectorBill } from '@/service/support/wallet/bill/push';
import { UpdateDatasetDataProps } from '@/global/core/dataset/api';
import { checkDatasetLimit } from '@/service/support/permission/teamLimit';
export default withNextCors(async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
try {
@@ -29,10 +29,7 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
});
// auth team balance
await checkDatasetLimit({
teamId,
insertLen: 1
});
await authTeamBalance(teamId);
const { charsLength } = await updateData2Dataset({
dataId: id,
@@ -42,7 +39,7 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
model: vectorModel
});
pushGenerateVectorUsage({
pushGenerateVectorBill({
teamId,
tmbId,
charsLength,

View File

@@ -4,16 +4,14 @@ import { withNextCors } from '@fastgpt/service/common/middle/cors';
import type { SearchTestProps, SearchTestResponse } from '@/global/core/dataset/api.d';
import { connectToDatabase } from '@/service/mongo';
import { authDataset } from '@fastgpt/service/support/permission/auth/dataset';
import { pushGenerateVectorUsage } from '@/service/support/wallet/usage/push';
import { authTeamBalance } from '@/service/support/permission/auth/bill';
import { pushGenerateVectorBill } from '@/service/support/wallet/bill/push';
import { searchDatasetData } from '@/service/core/dataset/data/controller';
import { updateApiKeyUsage } from '@fastgpt/service/support/openapi/tools';
import { UsageSourceEnum } from '@fastgpt/global/support/wallet/usage/constants';
import { BillSourceEnum } from '@fastgpt/global/support/wallet/bill/constants';
import { getLLMModel } from '@/service/core/ai/model';
import { queryExtension } from '@fastgpt/service/core/ai/functions/queryExtension';
import { datasetSearchQueryExtension } from '@fastgpt/service/core/dataset/search/utils';
import {
checkTeamAIPoints,
checkTeamReRankPermission
} from '@/service/support/permission/teamLimit';
export default withNextCors(async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
try {
@@ -45,7 +43,7 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
per: 'r'
});
// auth balance
await checkTeamAIPoints(teamId);
await authTeamBalance(teamId);
// query extension
const extensionModel =
@@ -67,27 +65,28 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
similarity,
datasetIds: [datasetId],
searchMode,
usingReRank: usingReRank && (await checkTeamReRankPermission(teamId))
usingReRank
});
// push bill
const { totalPoints } = pushGenerateVectorUsage({
const { total } = pushGenerateVectorBill({
teamId,
tmbId,
charsLength,
model: dataset.vectorModel,
source: apikey ? UsageSourceEnum.api : UsageSourceEnum.fastgpt,
source: apikey ? BillSourceEnum.api : BillSourceEnum.fastgpt,
...(aiExtensionResult &&
extensionModel && {
extensionModel: extensionModel.name,
extensionCharsLength: aiExtensionResult.charsLength
extensionInputTokens: aiExtensionResult.inputTokens,
extensionOutputTokens: aiExtensionResult.outputTokens
})
});
if (apikey) {
updateApiKeyUsage({
apikey,
totalPoints: totalPoints
usage: total
});
}

View File

@@ -4,7 +4,6 @@ import { connectToDatabase } from '@/service/mongo';
import type { CreateOnePluginParams } from '@fastgpt/global/core/plugin/controller';
import { authUserNotVisitor } from '@fastgpt/service/support/permission/auth/user';
import { MongoPlugin } from '@fastgpt/service/core/plugin/schema';
import { checkTeamPluginLimit } from '@/service/support/permission/teamLimit';
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
try {
@@ -12,8 +11,6 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
const { teamId, tmbId } = await authUserNotVisitor({ req, authToken: true });
const body = req.body as CreateOnePluginParams;
await checkTeamPluginLimit(teamId);
const { _id } = await MongoPlugin.create({
...body,
teamId,

View File

@@ -34,6 +34,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
});
const textResult = replaceVariable(text, obj);
res.json({
text: textResult
});

View File

@@ -5,7 +5,6 @@ import { authCert } from '@fastgpt/service/support/permission/auth/common';
import { UserUpdateParams } from '@/types/user';
import { getAIApi, openaiBaseUrl } from '@fastgpt/service/core/ai/config';
import { connectToDatabase } from '@/service/mongo';
import { MongoTeamMember } from '@fastgpt/service/support/user/team/teamMemberSchema';
/* update user info */
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
@@ -13,12 +12,8 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
await connectToDatabase();
const { avatar, timezone, openaiAccount } = req.body as UserUpdateParams;
const { tmbId } = await authCert({ req, authToken: true });
const tmb = await MongoTeamMember.findById(tmbId);
if (!tmb) {
throw new Error('can not find it');
}
const userId = tmb.userId;
const { userId } = await authCert({ req, authToken: true });
// auth key
if (openaiAccount?.key) {
console.log('auth user openai key', openaiAccount?.key);

View File

@@ -3,7 +3,6 @@ import { jsonRes } from '@fastgpt/service/common/response';
import { authCert } from '@fastgpt/service/support/permission/auth/common';
import { MongoUser } from '@fastgpt/service/support/user/schema';
import { connectToDatabase } from '@/service/mongo';
import { MongoTeamMember } from '@fastgpt/service/support/user/team/teamMemberSchema';
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
try {
@@ -14,12 +13,8 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
throw new Error('Params is missing');
}
const { tmbId } = await authCert({ req, authToken: true });
const tmb = await MongoTeamMember.findById(tmbId);
if (!tmb) {
throw new Error('can not find it');
}
const userId = tmb.userId;
const { userId } = await authCert({ req, authToken: true });
// auth old password
const user = await MongoUser.findOne({
_id: userId,

View File

@@ -2,7 +2,8 @@ import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@fastgpt/service/common/response';
import { connectToDatabase } from '@/service/mongo';
import { authCert } from '@fastgpt/service/support/permission/auth/common';
import { checkDatasetLimit } from '@/service/support/permission/teamLimit';
import { checkDatasetLimit } from '@fastgpt/service/support/permission/limit/dataset';
import { getStandardSubPlan } from '@/service/support/wallet/sub/utils';
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
try {
@@ -22,7 +23,8 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
await checkDatasetLimit({
teamId,
insertLen: numberSize
insertLen: numberSize,
standardPlans: getStandardSubPlan()
});
jsonRes(res);

View File

@@ -1,16 +1,16 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@fastgpt/service/common/response';
import { connectToDatabase } from '@/service/mongo';
import { UsageSourceEnum } from '@fastgpt/global/support/wallet/usage/constants';
import { CreateTrainingUsageProps } from '@fastgpt/global/support/wallet/usage/api.d';
import { BillSourceEnum } from '@fastgpt/global/support/wallet/bill/constants';
import { CreateTrainingBillProps } from '@fastgpt/global/support/wallet/bill/api.d';
import { getLLMModel, getVectorModel } from '@/service/core/ai/model';
import { createTrainingUsage } from '@fastgpt/service/support/wallet/usage/controller';
import { createTrainingBill } from '@fastgpt/service/support/wallet/bill/controller';
import { authDataset } from '@fastgpt/service/support/permission/auth/dataset';
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
await connectToDatabase();
const { name, datasetId } = req.body as CreateTrainingUsageProps;
const { name, datasetId } = req.body as CreateTrainingBillProps;
const { teamId, tmbId, dataset } = await authDataset({
req,
@@ -20,11 +20,11 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
per: 'w'
});
const { billId } = await createTrainingUsage({
const { billId } = await createTrainingBill({
teamId,
tmbId,
appName: name,
billSource: UsageSourceEnum.training,
billSource: BillSourceEnum.training,
vectorModel: getVectorModel(dataset.vectorModel).name,
agentModel: getLLMModel(dataset.agentModel).name
});

View File

@@ -2,21 +2,22 @@ import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@fastgpt/service/common/response';
import { connectToDatabase } from '@/service/mongo';
import { authCert } from '@fastgpt/service/support/permission/auth/common';
import { getTeamSubPlans } from '@fastgpt/service/support/wallet/sub/utils';
import { getTeamSubPlanStatus } from '@fastgpt/service/support/wallet/sub/utils';
import { getStandardSubPlan } from '@/service/support/wallet/sub/utils';
import { FeTeamPlanStatusType } from '@fastgpt/global/support/wallet/sub/type';
import { FeTeamSubType } from '@fastgpt/global/support/wallet/sub/type';
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
try {
await connectToDatabase();
// 凭证校验
const { teamId } = await authCert({
req,
authToken: true
});
jsonRes<FeTeamPlanStatusType>(res, {
data: await getTeamSubPlans({
jsonRes<FeTeamSubType>(res, {
data: await getTeamSubPlanStatus({
teamId,
standardPlans: getStandardSubPlan()
})

View File

@@ -6,7 +6,7 @@ import { getUploadModel } from '@fastgpt/service/common/file/multer';
import { removeFilesByPaths } from '@fastgpt/service/common/file/utils';
import fs from 'fs';
import { getAIApi } from '@fastgpt/service/core/ai/config';
import { pushWhisperUsage } from '@/service/support/wallet/usage/push';
import { pushWhisperBill } from '@/service/support/wallet/bill/push';
const upload = getUploadModel({
maxSize: 2
@@ -40,7 +40,7 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
model: global.whisperModel.model
});
pushWhisperUsage({
pushWhisperBill({
teamId,
tmbId,
duration

View File

@@ -13,16 +13,16 @@ import { gptMessage2ChatType, textAdaptGptResponse } from '@/utils/adapt';
import { getChatItems } from '@fastgpt/service/core/chat/controller';
import { saveChat } from '@/service/utils/chat/saveChat';
import { responseWrite } from '@fastgpt/service/common/response';
import { pushChatUsage } from '@/service/support/wallet/usage/push';
import { pushChatBill } from '@/service/support/wallet/bill/push';
import { authOutLinkChatStart } from '@/service/support/permission/auth/outLink';
import { pushResult2Remote, addOutLinkUsage } from '@fastgpt/service/support/outLink/tools';
import { pushResult2Remote, updateOutLinkUsage } from '@fastgpt/service/support/outLink/tools';
import requestIp from 'request-ip';
import { getUsageSourceByAuthType } from '@fastgpt/global/support/wallet/usage/tools';
import { getBillSourceByAuthType } from '@fastgpt/global/support/wallet/bill/tools';
import { selectShareResponse } from '@/utils/service/core/chat';
import { updateApiKeyUsage } from '@fastgpt/service/support/openapi/tools';
import { connectToDatabase } from '@/service/mongo';
import { getUserChatInfoAndAuthTeamPoints } from '@/service/support/permission/auth/team';
import { getUserAndAuthBalance } from '@fastgpt/service/support/user/controller';
import { AuthUserTypeEnum } from '@fastgpt/global/support/permission/constant';
import { MongoApp } from '@fastgpt/service/core/app/schema';
import { autChatCrud } from '@/service/support/permission/auth/chat';
@@ -35,14 +35,9 @@ type FastGptShareChatProps = {
shareId?: string;
outLinkUid?: string;
};
type FastGptTeamShareChatProps = {
teamId?: string;
outLinkUid?: string;
};
export type Props = ChatCompletionCreateParams &
FastGptWebChatProps &
FastGptShareChatProps &
FastGptTeamShareChatProps & {
FastGptShareChatProps & {
messages: ChatMessageItemType[];
stream?: boolean;
detail?: boolean;
@@ -65,7 +60,6 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
const {
chatId,
appId,
teamId,
shareId,
outLinkUid,
stream = false,
@@ -103,96 +97,90 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
}
/* auth app permission */
const { teamId, tmbId, user, app, responseDetail, authType, apikey, canWrite, outLinkUserId } =
await (async () => {
if (shareId && outLinkUid) {
const { teamId, tmbId, user, appId, authType, responseDetail, uid } =
await authOutLinkChatStart({
shareId,
ip: originIp,
outLinkUid,
question: question.value
});
const app = await MongoApp.findById(appId);
if (!app) {
return Promise.reject('app is empty');
}
return {
teamId,
tmbId,
user,
app,
responseDetail,
apikey: '',
authType,
canWrite: false,
outLinkUserId: uid
};
}
const {
appId: apiKeyAppId,
teamId,
tmbId,
authType,
apikey
} = await authCert({
req,
authToken: true,
authApiKey: true
const { user, app, responseDetail, authType, apikey, canWrite, uid } = await (async () => {
if (shareId && outLinkUid) {
const { user, appId, authType, responseDetail, uid } = await authOutLinkChatStart({
shareId,
ip: originIp,
outLinkUid,
question: question.value
});
const app = await MongoApp.findById(appId);
const user = await getUserChatInfoAndAuthTeamPoints(tmbId);
// openapi key
if (authType === AuthUserTypeEnum.apikey) {
if (!apiKeyAppId) {
return Promise.reject(
'Key is error. You need to use the app key rather than the account key.'
);
}
const app = await MongoApp.findById(apiKeyAppId);
if (!app) {
return Promise.reject('app is empty');
}
return {
teamId,
tmbId,
user,
app,
responseDetail: detail,
apikey,
authType,
canWrite: true
};
if (!app) {
return Promise.reject('app is empty');
}
return {
user,
app,
responseDetail,
apikey: '',
authType,
canWrite: false,
uid
};
}
const {
appId: apiKeyAppId,
tmbId,
authType,
apikey
} = await authCert({
req,
authToken: true,
authApiKey: true
});
const user = await getUserAndAuthBalance({
tmbId,
minBalance: 0
});
// openapi key
if (authType === AuthUserTypeEnum.apikey) {
if (!apiKeyAppId) {
return Promise.reject(
'Key is error. You need to use the app key rather than the account key.'
);
}
const app = await MongoApp.findById(apiKeyAppId);
if (!app) {
return Promise.reject('app is empty');
}
// token auth
if (!appId) {
return Promise.reject('appId is empty');
}
const { app, canWrite } = await authApp({
req,
authToken: true,
appId,
per: 'r'
});
return {
teamId,
tmbId,
user,
app,
responseDetail: detail,
apikey,
authType,
canWrite: canWrite || false
canWrite: true
};
})();
}
// token auth
if (!appId) {
return Promise.reject('appId is empty');
}
const { app, canWrite } = await authApp({
req,
authToken: true,
appId,
per: 'r'
});
return {
user,
app,
responseDetail: detail,
apikey,
authType,
canWrite: canWrite || false
};
})();
// auth chat permission
await autChatCrud({
@@ -213,17 +201,16 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
limit: 30,
field: `dataId obj value`
});
const concatHistories = history.concat(chatMessages);
const responseChatItemId: string | undefined = messages[messages.length - 1].dataId;
/* start flow controller */
const { responseData, moduleDispatchBills, answerText } = await dispatchModules({
const { responseData, answerText } = await dispatchModules({
res,
mode: 'chat',
user,
teamId: String(teamId),
tmbId: String(tmbId),
teamId: String(user.team.teamId),
tmbId: String(user.team.tmbId),
appId: String(app._id),
chatId,
responseChatItemId,
@@ -236,19 +223,18 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
stream,
detail
});
console.log('af');
// save chat
if (chatId) {
await saveChat({
chatId,
appId: app._id,
teamId: teamId,
tmbId: tmbId,
teamId: user.team.teamId,
tmbId: user.team.tmbId,
variables,
updateUseTime: !shareId && String(tmbId) === String(app.tmbId), // owner update use time
updateUseTime: !shareId && String(user.team.tmbId) === String(app.tmbId), // owner update use time
shareId,
outLinkUid: outLinkUserId,
outLinkUid: uid,
source: (() => {
if (shareId) {
return ChatSourceEnum.share;
@@ -319,29 +305,29 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
}
// add record
const { totalPoints } = pushChatUsage({
const { total } = pushChatBill({
appName: app.name,
appId: app._id,
teamId: teamId,
tmbId: tmbId,
source: getUsageSourceByAuthType({ shareId, authType }),
moduleDispatchBills
teamId: user.team.teamId,
tmbId: user.team.tmbId,
source: getBillSourceByAuthType({ shareId, authType }),
response: responseData
});
if (shareId) {
pushResult2Remote({ outLinkUid, shareId, appName: app.name, responseData });
addOutLinkUsage({
pushResult2Remote({ outLinkUid, shareId, responseData });
updateOutLinkUsage({
shareId,
totalPoints
total
});
}
if (apikey) {
updateApiKeyUsage({
apikey,
totalPoints
usage: total
});
}
} catch (err) {
} catch (err: any) {
if (stream) {
sseErrRes(res, err);
res.end();

View File

@@ -2,13 +2,13 @@ import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@fastgpt/service/common/response';
import { authCert } from '@fastgpt/service/support/permission/auth/common';
import { withNextCors } from '@fastgpt/service/common/middle/cors';
import { pushGenerateVectorUsage } from '@/service/support/wallet/usage/push';
import { pushGenerateVectorBill } from '@/service/support/wallet/bill/push';
import { connectToDatabase } from '@/service/mongo';
import { authTeamBalance } from '@/service/support/permission/auth/bill';
import { getVectorsByText } from '@fastgpt/service/core/ai/embedding';
import { updateApiKeyUsage } from '@fastgpt/service/support/openapi/tools';
import { getUsageSourceByAuthType } from '@fastgpt/global/support/wallet/usage/tools';
import { getBillSourceByAuthType } from '@fastgpt/global/support/wallet/bill/tools';
import { getVectorModel } from '@/service/core/ai/model';
import { checkTeamAIPoints } from '@/service/support/permission/teamLimit';
type Props = {
input: string | string[];
@@ -34,7 +34,7 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
authApiKey: true
});
await checkTeamAIPoints(teamId);
await authTeamBalance(teamId);
const { charsLength, vectors } = await getVectorsByText({
input: query,
@@ -55,19 +55,19 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
}
});
const { totalPoints } = pushGenerateVectorUsage({
const { total } = pushGenerateVectorBill({
teamId,
tmbId,
charsLength,
model,
billId,
source: getUsageSourceByAuthType({ authType })
source: getBillSourceByAuthType({ authType })
});
if (apikey) {
updateApiKeyUsage({
apikey,
totalPoints: totalPoints
usage: total
});
}
} catch (err) {

View File

@@ -0,0 +1,50 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@fastgpt/service/common/response';
import { authCert } from '@fastgpt/service/support/permission/auth/common';
import { withNextCors } from '@fastgpt/service/common/middle/cors';
import { pushReRankBill } from '@/service/support/wallet/bill/push';
import { connectToDatabase } from '@/service/mongo';
import { authTeamBalance } from '@/service/support/permission/auth/bill';
import { PostReRankProps, PostReRankResponse } from '@fastgpt/global/core/ai/api';
import { reRankRecall } from '@/service/core/ai/rerank';
import { updateApiKeyUsage } from '@fastgpt/service/support/openapi/tools';
export default withNextCors(async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
let { query, inputs } = req.body as PostReRankProps;
try {
await connectToDatabase();
const { teamId, tmbId, apikey } = await authCert({
req,
authApiKey: true
});
await authTeamBalance(teamId);
// max 150 length
inputs = inputs.slice(0, 150);
const result = await reRankRecall({ query, inputs });
const { total } = pushReRankBill({
teamId,
tmbId,
source: 'api',
inputs
});
if (apikey) {
updateApiKeyUsage({
apikey,
usage: total
});
}
jsonRes<PostReRankResponse>(res, {
data: result
});
} catch (err) {
jsonRes(res, {
code: 500,
error: err
});
}
});

View File

@@ -0,0 +1,201 @@
import React, { useEffect, useMemo, useRef } from 'react';
import * as echarts from 'echarts';
import { useSystemStore } from '@/web/common/system/useSystemStore';
import { getAppTotalUsage } from '@/web/core/app/api';
import { useQuery } from '@tanstack/react-query';
import dayjs from 'dayjs';
import { formatStorePrice2Read } from '@fastgpt/global/support/wallet/bill/tools';
import Loading from '@/components/Loading';
import { Box } from '@chakra-ui/react';
const map = {
blue: {
backgroundColor: {
type: 'linear',
x: 0,
y: 0,
x2: 0,
y2: 1,
colorStops: [
{
offset: 0,
color: 'rgba(3, 190, 232, 0.42)' // 0% 处的颜色
},
{
offset: 1,
color: 'rgba(0, 182, 240, 0)'
}
],
global: false // 缺省为 false
},
lineColor: '#36ADEF'
},
deepBlue: {
backgroundColor: {
type: 'linear',
x: 0,
y: 0,
x2: 0,
y2: 1,
colorStops: [
{
offset: 0,
color: 'rgba(47, 112, 237, 0.42)' // 0% 处的颜色
},
{
offset: 1,
color: 'rgba(94, 159, 235, 0)'
}
],
global: false
},
lineColor: '#3293EC'
},
purple: {
backgroundColor: {
type: 'linear',
x: 0,
y: 0,
x2: 0,
y2: 1,
colorStops: [
{
offset: 0,
color: 'rgba(211, 190, 255, 0.42)' // 0% 处的颜色
},
{
offset: 1,
color: 'rgba(52, 60, 255, 0)'
}
],
global: false // 缺省为 false
},
lineColor: '#8172D8'
},
green: {
backgroundColor: {
type: 'linear',
x: 0,
y: 0,
x2: 0,
y2: 1,
colorStops: [
{
offset: 0,
color: 'rgba(4, 209, 148, 0.42)' // 0% 处的颜色
},
{
offset: 1,
color: 'rgba(19, 217, 181, 0)'
}
],
global: false // 缺省为 false
},
lineColor: '#00A9A6',
max: 100
}
};
const TokenUsage = ({ appId }: { appId: string }) => {
const { screenWidth } = useSystemStore();
const Dom = useRef<HTMLDivElement>(null);
const myChart = useRef<echarts.ECharts>();
const { data = [] } = useQuery(['init'], () => getAppTotalUsage({ appId }));
const option = useMemo(
() => ({
xAxis: {
type: 'category',
show: false,
boundaryGap: false,
data: data.map((item) => item.date)
},
yAxis: {
type: 'value',
splitNumber: 3,
min: 0
},
grid: {
show: false,
left: 5,
right: 5,
top: 0,
bottom: 5
},
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'line'
},
formatter: (e: any[]) => {
const data = e[0];
if (!data) return '';
return `
<div>
<div>${dayjs(data.axisValue).format('YYYY/MM/DD')}</div>
<div>${formatStorePrice2Read(e[0]?.value || 0)}元</div>
</div>
`;
}
},
series: [
{
data: data.map((item) => item.total),
type: 'line',
showSymbol: true,
animationDuration: 1000,
animationEasingUpdate: 'linear',
areaStyle: {
color: map['blue'].backgroundColor
},
lineStyle: {
width: '1',
color: map['blue'].lineColor
},
itemStyle: {
width: 1.5,
color: map['blue'].lineColor
},
emphasis: {
// highlight
disabled: true
}
}
]
}),
[data]
);
// init chart
useEffect(() => {
if (!Dom.current || myChart?.current?.getOption()) return;
myChart.current = echarts.init(Dom.current);
myChart.current && myChart.current.setOption(option);
setTimeout(() => {
myChart.current?.resize();
}, 500);
}, []);
// data changed, update
useEffect(() => {
if (!myChart.current || !myChart?.current?.getOption()) return;
myChart.current.setOption(option);
}, [data, option]);
// resize chart
useEffect(() => {
if (!myChart.current || !myChart.current.getOption()) return;
myChart.current.resize();
}, [screenWidth]);
return (
<Box ref={Dom} w={'100%'} flex={'1 0 0'} h={'100%'} position={'relative'}>
<Loading fixed={false} />
</Box>
);
};
export default React.memo(TokenUsage);

View File

@@ -36,6 +36,7 @@ import { useForm } from 'react-hook-form';
import { defaultOutLinkForm } from '@/constants/app';
import type { OutLinkEditType, OutLinkSchema } from '@fastgpt/global/support/outLink/type.d';
import { useRequest } from '@/web/common/hooks/useRequest';
import { formatStorePrice2Read } from '@fastgpt/global/support/wallet/bill/tools';
import { OutLinkTypeEnum } from '@fastgpt/global/support/outLink/constant';
import { useTranslation } from 'next-i18next';
import { useToast } from '@fastgpt/web/hooks/useToast';
@@ -93,7 +94,7 @@ const Share = ({ appId }: { appId: string }) => {
<Thead>
<Tr>
<Th>{t('common.Name')}</Th>
<Th>{t('support.outlink.Usage points')}</Th>
<Th>{t('common.Price used')}</Th>
<Th>{t('core.app.share.Is response quote')}</Th>
{feConfigs?.isPlus && (
<>
@@ -111,11 +112,11 @@ const Share = ({ appId }: { appId: string }) => {
<Tr key={item._id}>
<Td>{item.name}</Td>
<Td>
{Math.round(item.usagePoints)}
{formatStorePrice2Read(item.total)}
{feConfigs?.isPlus
? `${
item.limit?.maxUsagePoints && item.limit.maxUsagePoints > -1
? ` / ${item.limit.maxUsagePoints}`
item.limit && item.limit.credit > -1
? ` / ${item.limit.credit}`
: ` / ${t('common.Unlimited')}`
}`
: ''}
@@ -314,15 +315,15 @@ function EditLinkModal({
</Flex>
<Flex alignItems={'center'} mt={4}>
<Flex flex={'0 0 90px'} alignItems={'center'}>
{t('support.outlink.Max usage points')}
<MyTooltip label={t('support.outlink.Max usage points tip')}>
{t('common.Max credit')}
<MyTooltip label={t('common.Max credit tips' || '')}>
<QuestionOutlineIcon ml={1} />
</MyTooltip>
</Flex>
<Input
{...register('limit.maxUsagePoints', {
{...register('limit.credit', {
min: -1,
max: 10000000,
max: 1000,
valueAsNumber: true,
required: true
})}

View File

@@ -1,6 +1,5 @@
import React, { useState } from 'react';
import { Box, Flex, Button, IconButton } from '@chakra-ui/react';
import { DragHandleIcon } from '@chakra-ui/icons';
import { useRequest } from '@/web/common/hooks/useRequest';
import { useConfirm } from '@/web/common/hooks/useConfirm';
import { useRouter } from 'next/router';
@@ -13,7 +12,6 @@ import PermissionIconText from '@/components/support/permission/IconText';
import dynamic from 'next/dynamic';
import Avatar from '@/components/Avatar';
import MyIcon from '@fastgpt/web/components/common/Icon';
import TagsEditModal from './tagsEditModal';
const InfoModal = dynamic(() => import('../InfoModal'));
const AppCard = ({ appId }: { appId: string }) => {
@@ -22,7 +20,6 @@ const AppCard = ({ appId }: { appId: string }) => {
const { toast } = useToast();
const { appDetail } = useAppStore();
const [settingAppInfo, setSettingAppInfo] = useState<AppSchema>();
const [TeamTagsSet, setTeamTagsSet] = useState<AppSchema>();
const { openConfirm: openConfirmDel, ConfirmModal: ConfirmDelModal } = useConfirm({
content: t('app.Confirm Del App Tip')
@@ -126,17 +123,6 @@ const AppCard = ({ appId }: { appId: string }) => {
>
{t('core.app.navbar.Publish')}
</Button>
{appDetail.isOwner && (
<Button
mr={3}
size={['sm', 'md']}
variant={'whitePrimary'}
leftIcon={<DragHandleIcon w={'16px'} />}
onClick={() => setTeamTagsSet(appDetail)}
>
{t('common.Team Tags Set')}
</Button>
)}
{appDetail.isOwner && (
<Button
size={['sm', 'md']}
@@ -150,13 +136,11 @@ const AppCard = ({ appId }: { appId: string }) => {
</Flex>
</Box>
</Box>
<ConfirmDelModal />
{settingAppInfo && (
<InfoModal defaultApp={settingAppInfo} onClose={() => setSettingAppInfo(undefined)} />
)}
{TeamTagsSet && (
<TagsEditModal appDetail={appDetail} onClose={() => setTeamTagsSet(undefined)} />
)}
</>
);
};

View File

@@ -1,103 +0,0 @@
import React, { useCallback, useState, useEffect } from 'react';
import MyModal from '@/components/MyModal';
import { useTranslation } from 'next-i18next';
import { Button, Flex, Box, ModalFooter, ModalBody } from '@chakra-ui/react';
import TagsEdit from '@/components/TagEdit';
import { useToast } from '@fastgpt/web/hooks/useToast';
import { AppSchema } from '@fastgpt/global/core/app/type.d';
import { TeamTagsSchema } from '@fastgpt/global/support/user/team/type';
import { useAppStore } from '@/web/core/app/store/useAppStore';
import { useRequest } from '@/web/common/hooks/useRequest';
import { getTeamsTags } from '@/web/support/user/team/api';
const TagsEditModal = ({ appDetail, onClose }: { appDetail?: any; onClose: () => void }) => {
const { t } = useTranslation();
const [teamsTags, setTeamTags] = useState<Array<TeamTagsSchema>>([]);
const [selectedTags, setSelectedTags] = useState(appDetail?.teamTags);
const { toast } = useToast();
const { replaceAppDetail } = useAppStore();
// submit config
const { mutate: saveSubmitSuccess, isLoading: btnLoading } = useRequest({
mutationFn: async () => {
await replaceAppDetail(appDetail._id, {
teamTags: selectedTags
});
},
onSuccess() {
onClose();
toast({
title: t('common.Update Success'),
status: 'success'
});
},
errorToast: t('common.Update Failed')
});
//
// // 点击选择标签
// const clickTag = (tagId :Number) => {
// const index = selectedTags.indexOf(tagId);
// if (index === -1) {
// // 如果 num 不在数组 arr 中,添加它
// setSelectedTags([tagId,...selectedTags])
// } else {
// const _selectedTags = [...selectedTags];
// _selectedTags.splice(index, 1);
// console.log('_selectedTags',_selectedTags);
// // 如果 num 已经在数组 arr 中,移除它
// setSelectedTags(_selectedTags);
// }
// }
useEffect(() => {
// get team tags
getTeamsTags(appDetail?.teamId).then((res: any) => {
setTeamTags(res?.list);
});
}, []);
return (
<MyModal
style={{ width: '900px' }}
isOpen
onClose={onClose}
iconSrc="/imgs/module/ai.svg"
title={'标签管理'}
>
<ModalBody>
{/* <HStack spacing={2}>
{teamsTags.map((item,index) => {
return <Tag
key={index}
size={'md'}
variant='outline'
colorScheme={selectedTags.indexOf(item._id) > -1 ? 'green':'blue' }
onClick={() => clickTag(item._id)}
>
{item.label}
</Tag>
})}
</HStack> */}
<Flex width={'100%'} alignItems={'center'}>
<Box mb={3} mr={3} fontWeight="semibold">
{t('团队标签')}
</Box>
<TagsEdit
defaultValues={selectedTags}
teamsTags={teamsTags}
setSelectedTags={(item: Array<string>) => setSelectedTags(item)}
/>
</Flex>
<ModalFooter>
<Button variant={'whiteBase'} mr={3} onClick={onClose}>
{t('common.Close')}
</Button>
<Button isLoading={btnLoading} onClick={(e) => saveSubmitSuccess(e)}>
{t('common.Save')}
</Button>
</ModalFooter>
</ModalBody>
</MyModal>
);
};
export default TagsEditModal;

View File

@@ -8,12 +8,8 @@ import {
Input,
Grid,
useTheme,
Card,
Text,
HStack,
Tag
Card
} from '@chakra-ui/react';
import { AddIcon } from '@chakra-ui/icons';
import { useSelectFile } from '@/web/common/file/hooks/useSelectFile';
import { useForm } from 'react-hook-form';
import { compressImgFileAndUpload } from '@/web/common/file/controller';

View File

@@ -1,4 +1,4 @@
import React, { useCallback, useState, useEffect } from 'react';
import React, { useCallback } from 'react';
import { Box, Grid, Flex, IconButton, Button, useDisclosure } from '@chakra-ui/react';
import { useRouter } from 'next/router';
import { useQuery } from '@tanstack/react-query';
@@ -8,6 +8,7 @@ import { useToast } from '@fastgpt/web/hooks/useToast';
import { useConfirm } from '@/web/common/hooks/useConfirm';
import { serviceSideProps } from '@/web/common/utils/i18n';
import { useTranslation } from 'next-i18next';
import MyIcon from '@fastgpt/web/components/common/Icon';
import PageContainer from '@/components/PageContainer';
import Avatar from '@/components/Avatar';
@@ -23,7 +24,6 @@ const MyApps = () => {
const router = useRouter();
const { userInfo } = useUserStore();
const { myApps, loadMyApps } = useAppStore();
const [teamsTags, setTeamTags] = useState([]);
const { openConfirm, ConfirmModal } = useConfirm({
title: '删除提示',
content: '确认删除该应用所有信息?'
@@ -65,9 +65,11 @@ const MyApps = () => {
<Box letterSpacing={1} fontSize={['20px', '24px']} color={'myGray.900'}>
{t('app.My Apps')}
</Box>
<Button leftIcon={<AddIcon />} variant={'primaryOutline'} onClick={onOpenCreateModal}>
{t('common.New Create')}
</Button>
{userInfo?.team?.canWrite && (
<Button leftIcon={<AddIcon />} variant={'primaryOutline'} onClick={onOpenCreateModal}>
{t('common.New Create')}
</Button>
)}
</Flex>
<Grid
py={[4, 6]}
@@ -169,10 +171,6 @@ const MyApps = () => {
</MyTooltip>
))}
</Grid>
{/* (
<ShareBox></ShareBox>
) */}
{myApps.length === 0 && (
<Flex mt={'35vh'} flexDirection={'column'} alignItems={'center'}>
<MyIcon name="empty" w={'48px'} h={'48px'} color={'transparent'} />

View File

@@ -1,521 +0,0 @@
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import Head from 'next/head';
import { getTeamChatInfo } from '@/web/core/chat/api';
import { useRouter } from 'next/router';
import {
Box,
Flex,
useDisclosure,
Drawer,
DrawerOverlay,
DrawerContent,
useTheme
} from '@chakra-ui/react';
import Avatar from '@/components/Avatar';
import { useToast } from '@fastgpt/web/hooks/useToast';
import { useQuery } from '@tanstack/react-query';
import { useSystemStore } from '@/web/common/system/useSystemStore';
import SideBar from '@/components/SideBar';
import PageContainer from '@/components/PageContainer';
import { getChatListById } from '@/web/core/chat/api';
import ChatHistorySlider from './components/ChatHistorySlider';
import ChatHeader from './components/ChatHeader';
import { serviceSideProps } from '@/web/common/utils/i18n';
import { useTranslation } from 'next-i18next';
import { checkChatSupportSelectFileByChatModels } from '@/web/core/chat/utils';
import { useChatStore } from '@/web/core/chat/storeChat';
import { customAlphabet } from 'nanoid';
import { useLoading } from '@/web/common/hooks/useLoading';
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 12);
import ChatBox, { type ComponentRef, type StartChatFnProps } from '@/components/ChatBox';
import { streamFetch } from '@/web/common/api/fetch';
import { useTeamShareChatStore } from '@/web/core/chat/storeTeamChat';
import type {
ChatHistoryItemType,
chatAppListSchema,
teamInfoType
} from '@fastgpt/global/core/chat/type.d';
import { chatContentReplaceBlock } from '@fastgpt/global/core/chat/utils';
import { ChatStatusEnum } from '@fastgpt/global/core/chat/constants';
import { POST } from '@/web/common/api/request';
const OutLink = ({
teamId,
appId,
chatId,
authToken
}: {
teamId: string;
appId: string;
chatId: string;
authToken: string;
}) => {
const { t } = useTranslation();
const router = useRouter();
const { toast } = useToast();
const theme = useTheme();
const [myApps, setMyApps] = useState<Array<any>>([]);
const { isPc } = useSystemStore();
const ChatBoxRef = useRef<ComponentRef>(null);
const [teamInfo, setTeamInfo] = useState<teamInfoType>();
const { Loading, setIsLoading } = useLoading();
const forbidRefresh = useRef(false);
const { isOpen: isOpenSlider, onClose: onCloseSlider, onOpen: onOpenSlider } = useDisclosure();
const {
histories,
loadHistories,
lastChatAppId,
setLastChatAppId,
lastChatId,
setLastChatId,
pushHistory,
updateHistory,
delOneHistory,
chatData,
setChatData,
delOneHistoryItem,
clearHistories
} = useChatStore();
const {
localUId,
teamShareChatHistory, // abandon
clearLocalHistory // abandon
} = useTeamShareChatStore();
const outLinkUid: string = authToken || localUId;
// 纯网络获取流程
const loadApps = useCallback(async () => {
try {
if (!teamId) {
toast({
status: 'error',
title: t('core.chat.You need to a chat app')
});
return;
}
// 根据teamId 获取教研token以及用户tags然后通过是否为
// 根据获取历史记录列表
const res = await getChatListById({ teamId, authToken });
const { apps = [], teamInfo } = res;
setMyApps(apps);
setTeamInfo(teamInfo);
if (apps.length <= 0) {
toast({
status: 'error',
title: t('core.chat.You need to a chat app')
});
}
//
return null;
} catch (error: any) {
toast({
status: 'warning',
title: error?.message
});
}
}, [outLinkUid, router, t, toast]);
const startChat = useCallback(
async ({ messages, controller, generatingMessage, variables }: StartChatFnProps) => {
console.log('res', 13);
const prompts = messages.slice(-2);
const completionChatId = chatId ? chatId : nanoid();
const { responseText, responseData } = await streamFetch({
data: {
messages: prompts,
variables,
appId,
teamId: teamId,
outLinkUid: outLinkUid,
chatId: completionChatId
},
onMessage: generatingMessage,
abortCtrl: controller
});
const newTitle =
chatContentReplaceBlock(prompts[0].content).slice(0, 20) ||
prompts[1]?.value?.slice(0, 20) ||
t('core.chat.New Chat');
// new chat
if (completionChatId !== chatId) {
const newHistory: ChatHistoryItemType = {
chatId: completionChatId,
updateTime: new Date(),
title: newTitle,
appId,
top: false
};
pushHistory(newHistory);
if (controller.signal.reason !== 'leave') {
forbidRefresh.current = true;
router.replace({
query: {
chatId: completionChatId,
appId,
teamId: teamId,
authToken: authToken
}
});
}
} else {
// update chat
const currentChat = histories.find((item) => item.chatId === chatId);
currentChat &&
updateHistory({
...currentChat,
updateTime: new Date(),
title: newTitle
});
}
// update chat window
setChatData((state) => ({
...state,
title: newTitle,
history: ChatBoxRef.current?.getChatHistories() || state.history
}));
return { responseText, responseData, isNewChat: forbidRefresh.current };
},
[appId, chatId, histories, pushHistory, router, setChatData, updateHistory]
);
const { isFetching } = useQuery(['init', appId, teamId], async () => {
console.log('res', 3);
if (!teamId) {
toast({
status: 'error',
title: t('core.chat.You need to a chat app')
});
return;
}
return teamId && loadApps();
});
useQuery(['loadHistories', appId], () => {
console.log('res', 1);
teamId && appId ? loadHistories({ appId, outLinkUid }) : null;
});
// 初始化聊天框
useQuery(['init', { appId, chatId }], () => {
if (!teamId) {
toast({
status: 'error',
title: t('core.chat.You need to a chat app')
});
return;
}
// pc: redirect to latest model chat
if (!appId && lastChatAppId) {
return router.replace({
query: {
appId: lastChatAppId,
chatId: lastChatId,
teamId: teamId,
authToken: authToken
}
});
}
if (!appId && myApps[0]) {
return router.replace({
query: {
appId: myApps[0]._id,
chatId: lastChatId,
teamId: teamId,
authToken: authToken
}
});
}
if (!appId) {
(async () => {
const { apps = [] } = await getChatListById({ teamId, authToken });
setMyApps(apps);
if (apps.length === 0) {
toast({
status: 'error',
title: t('core.chat.You need to a chat app')
});
} else {
router.replace({
query: {
appId: apps[0]._id,
chatId: lastChatId,
teamId: teamId,
authToken: authToken
}
});
}
})();
return;
}
// store id
appId && setLastChatAppId(appId);
setLastChatId(chatId);
return loadChatInfo({
appId,
chatId,
loading: appId !== chatData.appId
});
});
// get chat app info
const loadChatInfo = useCallback(
async ({
appId,
chatId,
loading = false
}: {
appId: string;
chatId: string;
loading?: boolean;
}) => {
try {
if (!teamId) {
toast({
status: 'error',
title: t('core.chat.You need to a chat app')
});
return;
}
loading && setIsLoading(true);
const res = await getTeamChatInfo({ appId, chatId, outLinkUid });
console.log('res', res);
const history = res.history.map((item) => ({
...item,
status: ChatStatusEnum.finish
}));
setChatData({
...res,
history
});
// have records.
ChatBoxRef.current?.resetHistory(history);
ChatBoxRef.current?.resetVariables(res.variables);
if (res.history.length > 0) {
setTimeout(() => {
ChatBoxRef.current?.scrollToBottom('auto');
}, 500);
}
} catch (e: any) {
// reset all chat tore
setLastChatAppId('');
setLastChatId('');
toast({
title: t('core.chat.Failed to initialize chat'),
status: 'error'
});
if (e?.code === 501) {
//router.replace('/app/list');
} else if (chatId) {
router.replace({
query: {
...router.query,
chatId: ''
}
});
}
}
setIsLoading(false);
return null;
},
[setIsLoading, setChatData, router, setLastChatAppId, setLastChatId, toast]
);
// 监测路由改变
useEffect(() => {
const activeHistory = teamShareChatHistory.filter((item) => !item.delete);
if (!localUId || !teamId || activeHistory.length === 0) return;
(async () => {
try {
await POST('/core/chat/initLocalShareHistoryV464', {
outLinkUid: localUId,
chatIds: teamShareChatHistory.map((item) => item.chatId)
});
clearLocalHistory();
// router.reload();
} catch (error) {
toast({
status: 'warning',
title: t('core.shareChat.Init Error')
});
}
})();
}, [clearLocalHistory, localUId, router, teamShareChatHistory, teamId, t, toast]);
return (
<Flex h={'100%'}>
{/* pc show myself apps */}
<Box borderRight={theme.borders.base} w={'220px'} flexShrink={0}>
<Flex flexDirection={'column'} h={'100%'}>
<Box flex={'1 0 0'} h={0} px={5} py={4} overflow={'overlay'}>
{myApps.map((item) => (
<Flex
key={item._id}
py={2}
px={3}
mb={3}
cursor={'pointer'}
borderRadius={'md'}
alignItems={'center'}
{...(item._id === appId
? {
bg: 'white',
boxShadow: 'md'
}
: {
_hover: {
bg: 'myGray.200'
},
onClick: () => {
router.replace({
query: {
appId: item._id,
teamId: teamId,
authToken: authToken
}
});
}
})}
>
<Avatar src={item.avatar} w={'24px'} />
<Box ml={2} className={'textEllipsis'}>
{item.name}
</Box>
</Flex>
))}
</Box>
</Flex>
</Box>
<PageContainer flex={'1 0 0'} w={0} p={[0, '16px']} position={'relative'}>
<Flex h={'100%'} flexDirection={['column', 'row']} bg={'white'}>
{((children: React.ReactNode) => {
return isPc || !appId ? (
<SideBar>{children}</SideBar>
) : (
<Drawer
isOpen={isOpenSlider}
placement="left"
autoFocus={false}
size={'xs'}
onClose={onCloseSlider}
>
<DrawerOverlay backgroundColor={'rgba(255,255,255,0.5)'} />
<DrawerContent maxWidth={'250px'}>{children}</DrawerContent>
</Drawer>
);
})(
<ChatHistorySlider
appId={appId}
appName={chatData.app.name}
appAvatar={chatData.app.avatar}
activeChatId={chatId}
onClose={onCloseSlider}
history={histories.map((item, i) => ({
id: item.chatId,
title: item.title,
customTitle: item.customTitle,
top: item.top
}))}
onChangeChat={(chatId) => {
router.replace({
query: {
chatId: chatId || '',
appId,
teamId: teamId,
authToken: authToken
}
});
if (!isPc) {
onCloseSlider();
}
}}
onDelHistory={(e) => delOneHistory({ ...e, appId })}
onClearHistory={() => {
clearHistories({ appId });
router.replace({
query: {
appId,
teamId: teamId,
authToken: authToken
}
});
}}
onSetHistoryTop={(e) => {
updateHistory({ ...e, appId });
}}
onSetCustomTitle={async (e) => {
updateHistory({
appId,
chatId: e.chatId,
title: e.title,
customTitle: e.title
});
}}
/>
)}
{/* chat container */}
<Flex
position={'relative'}
h={[0, '100%']}
w={['100%', 0]}
flex={'1 0 0'}
flexDirection={'column'}
>
{/* header */}
<ChatHeader
appAvatar={chatData.app.avatar}
appName={chatData.app.name}
history={chatData.history}
showHistory={true}
onOpenSlider={onOpenSlider}
/>
{/* chat box */}
<Box flex={1}>
<ChatBox
active={!!chatData.app.name}
ref={ChatBoxRef}
appAvatar={chatData.app.avatar}
userAvatar={chatData.userAvatar}
userGuideModule={chatData.app?.userGuideModule}
showFileSelector={checkChatSupportSelectFileByChatModels(chatData.app.chatModels)}
feedbackType={'user'}
onUpdateVariable={(e) => {}}
onStartChat={startChat}
onDelMessage={(e) =>
delOneHistoryItem({ ...e, appId: chatData.appId, chatId, outLinkUid })
}
appId={chatData.appId}
chatId={chatId}
outLinkUid={outLinkUid}
/>
</Box>
</Flex>
</Flex>
</PageContainer>
</Flex>
);
};
export async function getServerSideProps(context: any) {
const teamId = context?.query?.teamId || '';
const appId = context?.query?.appId || '';
const chatId = context?.query?.chatId || '';
const authToken: string = context?.query?.authToken || '';
return {
props: {
teamId,
appId,
chatId,
authToken,
...(await serviceSideProps(context))
}
};
}
export default OutLink;

View File

@@ -1,5 +1,6 @@
import React, { useContext, useCallback, createContext, useState, useMemo, useEffect } from 'react';
import { formatModelPrice2Read } from '@fastgpt/global/support/wallet/bill/tools';
import { splitText2Chunks } from '@fastgpt/global/common/string/textSplitter';
import { TrainingModeEnum } from '@fastgpt/global/core/dataset/constants';
import { useTranslation } from 'next-i18next';
@@ -33,7 +34,7 @@ type useImportStoreType = {
totalChunkChars: number;
totalChunks: number;
chunkSize: number;
predictPoints: number;
predictPrice: number;
priceTip: string;
uploadRate: number;
splitSources2Chunks: () => void;
@@ -53,7 +54,7 @@ const StateContext = createContext<useImportStoreType>({
totalChunkChars: 0,
totalChunks: 0,
chunkSize: 0,
predictPoints: 0,
predictPrice: 0,
priceTip: '',
uploadRate: 50,
splitSources2Chunks: () => {}
@@ -104,9 +105,10 @@ const Provider = ({
chunkSize: embeddingChunkSize,
showChunkInput: true,
showPromptInput: false,
charsPointsPrice: vectorModel.charsPointsPrice,
inputPrice: vectorModel.inputPrice,
outputPrice: 0,
priceTip: t('core.dataset.import.Embedding Estimated Price Tips', {
price: vectorModel.charsPointsPrice
price: vectorModel.inputPrice
}),
uploadRate: 150
},
@@ -118,9 +120,10 @@ const Provider = ({
chunkSize: agentModel.maxContext * 0.55 || 6000,
showChunkInput: false,
showPromptInput: true,
charsPointsPrice: agentModel.charsPointsPrice,
inputPrice: agentModel.inputPrice,
outputPrice: agentModel.outputPrice,
priceTip: t('core.dataset.import.QA Estimated Price Tips', {
price: agentModel?.charsPointsPrice
price: agentModel?.inputPrice
}),
uploadRate: 30
}
@@ -148,12 +151,15 @@ const Provider = ({
() => sources.reduce((sum, file) => sum + file.chunkChars, 0),
[sources]
);
const predictPoints = useMemo(() => {
const predictPrice = useMemo(() => {
if (mode === TrainingModeEnum.qa) {
return +(((totalChunkChars * 1.5) / 1000) * agentModel.charsPointsPrice).toFixed(2);
const inputTotal = totalChunkChars * selectModelStaticParam.inputPrice;
const outputTotal = totalChunkChars * 0.5 * selectModelStaticParam.inputPrice;
return formatModelPrice2Read(inputTotal + outputTotal);
}
return +((totalChunkChars / 1000) * vectorModel.charsPointsPrice).toFixed(2);
}, [agentModel.charsPointsPrice, mode, totalChunkChars, vectorModel.charsPointsPrice]);
return formatModelPrice2Read(totalChunkChars * selectModelStaticParam.inputPrice);
}, [mode, selectModelStaticParam.inputPrice, totalChunkChars]);
const totalChunks = useMemo(
() => sources.reduce((sum, file) => sum + file.chunks.length, 0),
[sources]
@@ -172,8 +178,7 @@ const Provider = ({
return {
...file,
chunkChars: chars,
chunks: chunks.map((chunk, i) => ({
chunkIndex: i,
chunks: chunks.map((chunk) => ({
q: chunk,
a: ''
}))
@@ -193,7 +198,7 @@ const Provider = ({
totalChunkChars,
totalChunks,
chunkSize,
predictPoints,
predictPrice,
splitSources2Chunks
};
return <StateContext.Provider value={value}>{children}</StateContext.Provider>;

View File

@@ -46,7 +46,7 @@ function DataProcess({
maxChunkSize,
totalChunkChars,
totalChunks,
predictPoints,
predictPrice,
showRePreview,
splitSources2Chunks,
priceTip
@@ -275,7 +275,7 @@ function DataProcess({
{feConfigs?.show_pay && (
<MyTooltip label={priceTip}>
<Tag colorSchema={'gray'} py={'6px'} borderRadius={'md'} px={3}>
{t('core.dataset.import.Estimated points', { points: predictPoints })}
{t('core.dataset.import.Estimated Price', { amount: predictPrice, unit: '元' })}
</Tag>
</MyTooltip>
)}

View File

@@ -16,7 +16,7 @@ import { useImportStore, type FormType } from '../Provider';
import { useTranslation } from 'next-i18next';
import MyIcon from '@fastgpt/web/components/common/Icon';
import { useRequest } from '@/web/common/hooks/useRequest';
import { postCreateTrainingUsage } from '@/web/support/wallet/usage/api';
import { postCreateTrainingBill } from '@/web/support/wallet/bill/api';
import { useDatasetStore } from '@/web/core/dataset/store/dataset';
import { chunksUpload, fileCollectionCreate } from '@/web/core/dataset/utils';
import { ImportSourceItemType } from '@/web/core/dataset/type';
@@ -54,7 +54,7 @@ const Upload = ({ showPreviewChunks }: { showPreviewChunks: boolean }) => {
// Batch create collection and upload chunks
for await (const item of uploadList) {
const billId = await postCreateTrainingUsage({
const billId = await postCreateTrainingBill({
name: item.sourceName,
datasetId: datasetDetail._id
});

View File

@@ -19,6 +19,7 @@ import { useRequest } from '@/web/common/hooks/useRequest';
import { countPromptTokens } from '@fastgpt/global/common/string/tiktoken';
import { useConfirm } from '@/web/common/hooks/useConfirm';
import { getDefaultIndex } from '@fastgpt/global/core/dataset/utils';
import { DatasetDataIndexTypeEnum } from '@fastgpt/global/core/dataset/constants';
import { DatasetDataIndexItemType } from '@fastgpt/global/core/dataset/type';
import SideTabs from '@/components/SideTabs';
import DeleteIcon from '@fastgpt/web/components/common/Icon/delete';
@@ -161,10 +162,9 @@ const InputDataModal = ({
q: e.q,
a: e.a,
// remove dataId
indexes: e.indexes.map((index) => ({
...index,
dataId: undefined
}))
indexes: e.indexes.map((index) =>
index.defaultIndex ? getDefaultIndex({ q: e.q, a: e.a }) : index
)
});
return {
@@ -195,7 +195,7 @@ const InputDataModal = ({
id: dataId,
...e,
indexes: e.indexes.map((index) =>
index.defaultIndex ? getDefaultIndex({ q: e.q, a: e.a, dataId: index.dataId }) : index
index.defaultIndex ? getDefaultIndex({ q: e.q, a: e.a }) : index
)
});
@@ -278,7 +278,7 @@ const InputDataModal = ({
bg={i % 2 !== 0 ? 'myWhite.400' : ''}
_hover={{
'& .delete': {
display: index.defaultIndex ? 'none' : 'block'
display: index.defaultIndex && indexes.length === 1 ? 'none' : 'block'
}
}}
>
@@ -331,6 +331,7 @@ const InputDataModal = ({
onClick={() =>
appendIndexes({
defaultIndex: false,
type: DatasetDataIndexTypeEnum.chunk,
text: '',
dataId: `${Date.now()}`
})
@@ -382,47 +383,45 @@ const InputTab = ({
const [inputType, setInputType] = useState(InputTypeEnum.q);
return (
<Flex flexDirection={'column'} h={'100%'}>
<Box>
<RowTabs
list={[
{
label: (
<Flex alignItems={'center'}>
<Box as="span" color={'red.600'}>
*
</Box>
{t('core.dataset.data.Main Content')}
<MyTooltip label={t('core.dataset.data.Data Content Tip')}>
<QuestionOutlineIcon ml={1} />
</MyTooltip>
</Flex>
),
value: InputTypeEnum.q
},
{
label: (
<Flex alignItems={'center'}>
{t('core.dataset.data.Auxiliary Data')}
<MyTooltip label={t('core.dataset.data.Auxiliary Data Tip')}>
<QuestionOutlineIcon ml={1} />
</MyTooltip>
</Flex>
),
value: InputTypeEnum.a
}
]}
value={inputType}
onChange={(e) => setInputType(e as InputTypeEnum)}
/>
</Box>
<Box>
<RowTabs
list={[
{
label: (
<Flex alignItems={'center'}>
<Box as="span" color={'red.600'}>
*
</Box>
{t('core.dataset.data.Main Content')}
<MyTooltip label={t('core.dataset.data.Data Content Tip')}>
<QuestionOutlineIcon ml={1} />
</MyTooltip>
</Flex>
),
value: InputTypeEnum.q
},
{
label: (
<Flex alignItems={'center'}>
{t('core.dataset.data.Auxiliary Data')}
<MyTooltip label={t('core.dataset.data.Auxiliary Data Tip')}>
<QuestionOutlineIcon ml={1} />
</MyTooltip>
</Flex>
),
value: InputTypeEnum.a
}
]}
value={inputType}
onChange={(e) => setInputType(e as InputTypeEnum)}
/>
<Box mt={3} flex={'1 0 0'}>
<Box mt={3}>
{inputType === InputTypeEnum.q && (
<Textarea
placeholder={t('core.dataset.data.Data Content Placeholder', { maxToken })}
maxLength={maxToken}
h={'100%'}
rows={isPc ? 24 : 12}
bg={'myWhite.400'}
{...register(`q`, {
required: true
@@ -434,7 +433,6 @@ const InputTab = ({
placeholder={t('core.dataset.data.Auxiliary Data Placeholder', {
maxToken: maxToken * 1.5
})}
h={'100%'}
bg={'myWhite.400'}
rows={isPc ? 24 : 12}
maxLength={maxToken * 1.5}
@@ -442,6 +440,6 @@ const InputTab = ({
/>
)}
</Box>
</Flex>
</Box>
);
};

View File

@@ -27,7 +27,6 @@ import {
import { useConfirm } from '@/web/common/hooks/useConfirm';
import { useRequest } from '@/web/common/hooks/useRequest';
import DatasetTypeTag from '@/components/core/dataset/DatasetTypeTag';
import Head from 'next/head';
const DataCard = dynamic(() => import('./components/DataCard'));
const Test = dynamic(() => import('./components/Test'));
@@ -146,9 +145,7 @@ const Detail = ({ datasetId, currentTab }: { datasetId: string; currentTab: `${T
return (
<>
<Head>
<title>{datasetDetail?.name}</title>
</Head>
<Script src="/js/pdf.js" strategy="lazyOnload"></Script>
<PageContainer>
<Flex flexDirection={['column', 'row']} h={'100%'} pt={[4, 0]}>
{isPc ? (

View File

@@ -7,111 +7,92 @@ import {
NumberIncrementStepper,
NumberInputField,
NumberInputStepper,
Button
Button,
useDisclosure,
ModalBody,
ModalFooter
} from '@chakra-ui/react';
import { TeamSubSchema } from '@fastgpt/global/support/wallet/sub/type';
import { useTranslation } from 'next-i18next';
import React, { useCallback, useState } from 'react';
import React, { useEffect, useMemo, useState } from 'react';
import { useSystemStore } from '@/web/common/system/useSystemStore';
import MyIcon from '@fastgpt/web/components/common/Icon';
import { formatTime2YMDHM } from '@fastgpt/global/common/string/time';
import MySelect from '@/components/Select';
import {
SubStatusEnum,
SubTypeEnum,
subSelectMap
} from '@fastgpt/global/support/wallet/sub/constants';
import { useRequest } from '@/web/common/hooks/useRequest';
import {
posCheckTeamDatasetSizeSub,
postUpdateTeamDatasetSizeSub,
putTeamDatasetSubStatus
} from '@/web/support/wallet/sub/api';
import { SubDatasetSizePreviewCheckResponse } from '@fastgpt/global/support/wallet/sub/api.d';
import { useRouter } from 'next/router';
import { useForm } from 'react-hook-form';
import { useToast } from '@fastgpt/web/hooks/useToast';
import { getErrText } from '@fastgpt/global/common/error/utils';
import { getWxPayQRCode } from '@/web/support/wallet/bill/api';
import { BillTypeEnum } from '@fastgpt/global/support/wallet/bill/constants';
import QRCodePayModal, { type QRPayProps } from '@/components/support/wallet/QRCodePayModal';
import { formatStorePrice2Read } from '@fastgpt/global/support/wallet/bill/tools';
import { useConfirm } from '@/web/common/hooks/useConfirm';
import { useUserStore } from '@/web/support/user/useUserStore';
import MyModal from '@/components/MyModal';
const ExtraPlan = () => {
const ExtraPlan = ({ extraDatasetSize }: { extraDatasetSize?: TeamSubSchema }) => {
const { t } = useTranslation();
const router = useRouter();
const { toast } = useToast();
const { subPlans } = useSystemStore();
const [loading, setLoading] = useState(false);
const [qrPayData, setQRPayData] = useState<QRPayProps>();
// extra dataset
const extraDatasetPrice = subPlans?.extraDatasetSize?.price || 0;
const { register: registerDatasetSize, handleSubmit: handleSubmitDatasetSize } = useForm({
defaultValues: {
datasetSize: 0,
month: 1
}
});
const onclickBuyDatasetSize = useCallback(
async ({ datasetSize, month }: { datasetSize: number; month: number }) => {
try {
const datasetSizePayAmount = datasetSize * month * extraDatasetPrice;
if (datasetSizePayAmount === 0) {
return toast({
status: 'warning',
title: '购买数量不能为0'
});
}
setLoading(true);
const [datasetSize, setDatasetSize] = useState(0);
const [isRenew, setIsRenew] = useState('false');
const router = useRouter();
const { userInfo } = useUserStore();
const res = await getWxPayQRCode({
type: BillTypeEnum.extraDatasetSub,
month,
extraDatasetSize: datasetSize
});
setQRPayData({
readPrice: res.readPrice,
codeUrl: res.codeUrl,
billId: res.billId
});
} catch (err) {
toast({
title: getErrText(err),
status: 'error'
});
}
setLoading(false);
const [confirmPayExtraDatasetSizeData, setConfirmPayExtraDatasetSizeData] =
useState<SubDatasetSizePreviewCheckResponse>();
useEffect(() => {
setDatasetSize((extraDatasetSize?.nextExtraDatasetSize || 0) / 1000);
setIsRenew(extraDatasetSize?.status === SubStatusEnum.active ? 'true' : 'false');
}, [extraDatasetSize]);
const { mutate: onUpdateExtraDatasetSizeStatus } = useRequest({
mutationFn: (e: 'true' | 'false') => {
setIsRenew(e);
return putTeamDatasetSubStatus({
status: subSelectMap[e],
type: SubTypeEnum.extraDatasetSize
});
},
[extraDatasetPrice, toast]
);
// extra ai points
const extraPointsPrice = subPlans?.extraPoints?.price || 0;
const { register: registerExtraPoints, handleSubmit: handleSubmitExtraPoints } = useForm({
defaultValues: {
points: 0,
month: 1
}
successToast: t('common.Update success'),
errorToast: t('common.error.Update error')
});
const onclickBuyExtraPoints = useCallback(
async ({ points, month }: { points: number; month: number }) => {
try {
const payAmount = points * month * extraPointsPrice;
if (payAmount === 0) {
return toast({
status: 'warning',
title: '购买数量不能为0'
});
}
setLoading(true);
const res = await getWxPayQRCode({
type: BillTypeEnum.extraPoints,
month,
extraPoints: points
});
setQRPayData({
readPrice: res.readPrice,
codeUrl: res.codeUrl,
billId: res.billId
});
} catch (err) {
toast({
title: getErrText(err),
status: 'error'
});
}
setLoading(false);
},
[extraPointsPrice, toast]
const { mutate: onClickUpdateExtraDatasetPlan, isLoading: isPayingExtraDatasetSize } = useRequest(
{
mutationFn: () => postUpdateTeamDatasetSizeSub({ size: datasetSize }),
onSuccess() {
setTimeout(() => {
router.reload();
}, 100);
},
successToast: t('common.Update success'),
errorToast: t('common.error.Update error')
}
);
const { mutate: onClickPreviewCheck, isLoading: isFetchingPreviewCheck } = useRequest({
mutationFn: () =>
posCheckTeamDatasetSizeSub({
size: datasetSize
}),
onSuccess(res: SubDatasetSizePreviewCheckResponse) {
if (!res.payForNewSub) {
onClickUpdateExtraDatasetPlan('');
return;
} else {
setConfirmPayExtraDatasetSizeData(res);
}
},
errorToast: t('common.error.Update error')
});
return (
<Flex
@@ -120,13 +101,13 @@ const ExtraPlan = () => {
alignItems={'center'}
position={'relative'}
>
<Box id={'extra-plan'} fontWeight={'bold'} fontSize={['24px', '36px']}>
<Box fontWeight={'bold'} fontSize={['24px', '36px']}>
{t('support.wallet.subscription.Extra plan')}
</Box>
<Box mt={8} mb={10} color={'myGray.500'} fontSize={'lg'}>
{t('support.wallet.subscription.Extra plan tip')}
</Box>
<Grid mt={8} gridTemplateColumns={['1fr', '1fr 1fr']} gap={5} w={['100%', 'auto']}>
<Grid mt={8} gridTemplateColumns={['1fr', '1fr']}>
<Box
bg={'rgba(255, 255, 255, 0.90)'}
px={'32px'}
@@ -135,60 +116,78 @@ const ExtraPlan = () => {
borderWidth={'1px'}
borderColor={'myGray.150'}
boxShadow={'1.5'}
w={['100%', '500px']}
>
<Flex borderBottomWidth={'1px'} borderBottomColor={'myGray.200'}>
<Flex w={['100%', '500px']} borderBottomWidth={'1px'} borderBottomColor={'myGray.200'}>
<Box flex={'1 0 0'}>
<Box fontSize={'xl'} color={'primary.600'}>
{t('support.wallet.subscription.Extra dataset size')}
</Box>
<Box mt={3} fontSize={['28px', '32px']} fontWeight={'bold'}>
{extraDatasetPrice}/1000{' '}
<Box mt={3} fontSize={['32px', '38px']} fontWeight={'bold'}>
{extraDatasetPrice}/1k{' '}
<Box ml={1} as={'span'} fontSize={'lg'} color={'myGray.600'} fontWeight={'normal'}>
/{t('common.month')}
</Box>
</Box>
</Box>
<MyIcon
display={['none', 'display']}
mt={'-30px'}
transform={'translateX(20px)'}
name={'support/bill/extraDatasetsize'}
transform={'translate(20px,-20px)'}
name={'support/pay/extraDatasetsize'}
fill={'none'}
/>
</Flex>
<Box>
<Flex mt={4}>
<MyIcon mr={2} name={'support/bill/shoppingCart'} w={'16px'} color={'primary.600'} />
</Flex>
<Flex mt={4} alignItems={'center'}>
<Box flex={['0 0 100px', '0 0 200px']}>
{t('support.wallet.subscription.Month amount')}
<Box flex={'0 0 200px'}>
{t('support.wallet.subscription.Current dataset store')}:{' '}
</Box>
<Flex alignItems={'center'} mt={1} w={'180px'} position={'relative'}>
<NumberInput size={'sm'} flex={1} step={1} min={1} max={12} position={'relative'}>
<NumberInputField
pr={'30px'}
{...registerDatasetSize('month', {
required: true,
min: 1,
max: 12,
valueAsNumber: true
})}
/>
<NumberInputStepper>
<NumberIncrementStepper />
<NumberDecrementStepper />
</NumberInputStepper>
</NumberInput>
<Box position={'absolute'} right={'20px'} color={'myGray.500'} fontSize={'xs'}>
{t('common.month')}
<Box fontWeight={'bold'} flex={1}>
{extraDatasetSize?.currentExtraDatasetSize || 0}
{t('core.dataset.data.unit')}
</Box>
</Flex>
{extraDatasetSize?.nextExtraDatasetSize !== undefined && (
<Flex mt={4}>
<Box flex={'0 0 200px'}>
{t('support.wallet.subscription.Next sub dataset size')}:
</Box>
<Box fontWeight={'bold'} flex={1}>
{extraDatasetSize?.nextExtraDatasetSize || 0}
{t('core.dataset.data.unit')}
</Box>
</Flex>
)}
{!!extraDatasetSize?.startTime && (
<Flex mt={3}>
<Box flex={'0 0 200px'}>: </Box>
<Box>{formatTime2YMDHM(extraDatasetSize?.startTime)}</Box>
</Flex>
)}
{!!extraDatasetSize?.expiredTime && (
<Flex mt={3}>
<Box flex={'0 0 200px'}>: </Box>
<Box>{formatTime2YMDHM(extraDatasetSize?.expiredTime)}</Box>
</Flex>
)}
<Flex mt={3} alignItems={'center'}>
<Box flex={'0 0 200px'}>: </Box>
<MySelect
value={isRenew}
size={'sm'}
w={'180px'}
bg={'myGray.50'}
boxShadow={'none'}
list={[
{ label: '自动续费', value: 'true' },
{ label: '不自动续费', value: 'false' }
]}
onchange={(e) => {
if (!extraDatasetSize) return;
onUpdateExtraDatasetSizeStatus(e);
}}
/>
</Flex>
<Flex mt={4} alignItems={'center'}>
<Box flex={['0 0 100px', '0 0 200px']}>
<Box flex={'0 0 200px'}>
{t('support.wallet.subscription.Update extra dataset size')}
</Box>
<Flex alignItems={'center'} mt={1} w={'180px'} position={'relative'}>
@@ -198,18 +197,13 @@ const ExtraPlan = () => {
min={0}
max={10000}
step={1}
value={datasetSize}
position={'relative'}
onChange={(e) => {
setDatasetSize(Number(e));
}}
>
<NumberInputField
pr={'30px'}
{...registerDatasetSize('datasetSize', {
required: true,
min: 0,
max: 10000,
valueAsNumber: true
})}
step={1}
/>
<NumberInputField pr={'30px'} value={datasetSize} step={1} min={0} max={10000} />
<NumberInputStepper>
<NumberIncrementStepper />
<NumberDecrementStepper />
@@ -221,124 +215,88 @@ const ExtraPlan = () => {
</Flex>
</Flex>
<Button
isDisabled={datasetSize * 1000 === extraDatasetSize?.nextExtraDatasetSize}
mt={6}
w={'100%'}
variant={'primaryGhost'}
isLoading={loading}
onClick={handleSubmitDatasetSize(onclickBuyDatasetSize)}
isLoading={isPayingExtraDatasetSize || isFetchingPreviewCheck}
onClick={onClickPreviewCheck}
>
{t('support.wallet.Buy')}
</Button>
</Box>
</Box>
{/* points */}
<Box
bg={'rgba(255, 255, 255, 0.90)'}
w={['100%', '500px']}
px={'32px'}
py={'24px'}
borderRadius={'2xl'}
borderWidth={'1px'}
borderColor={'myGray.150'}
boxShadow={'1.5'}
>
<Flex borderBottomWidth={'1px'} borderBottomColor={'myGray.200'}>
<Box flex={'1 0 0'}>
<Box fontSize={'xl'} color={'primary.600'}>
{t('support.wallet.subscription.Extra ai points')}
</Box>
<Box mt={3} fontSize={['28px', '32px']} fontWeight={'bold'}>
{extraPointsPrice}/1000{' '}
<Box ml={1} as={'span'} fontSize={'lg'} color={'myGray.600'} fontWeight={'normal'}>
/{t('common.month')}
</Box>
</Box>
</Box>
<MyIcon
display={['none', 'display']}
mt={'-30px'}
transform={'translateX(20px)'}
name={'support/bill/extraPoints'}
fill={'none'}
/>
</Flex>
<Box>
<Flex mt={4}>
<MyIcon mr={2} name={'support/bill/shoppingCart'} w={'16px'} color={'primary.600'} />
</Flex>
<Flex mt={4} alignItems={'center'}>
<Box flex={['0 0 100px', '0 0 200px']}>
{t('support.wallet.subscription.Month amount')}
</Box>
<Flex alignItems={'center'} mt={1} w={'180px'} position={'relative'}>
<NumberInput size={'sm'} flex={1} step={1} min={1} max={12} position={'relative'}>
<NumberInputField
pr={'30px'}
{...registerExtraPoints('month', {
required: true,
min: 1,
max: 12,
valueAsNumber: true
})}
/>
<NumberInputStepper>
<NumberIncrementStepper />
<NumberDecrementStepper />
</NumberInputStepper>
</NumberInput>
<Box position={'absolute'} right={'20px'} color={'myGray.500'} fontSize={'xs'}>
{t('common.month')}
</Box>
</Flex>
</Flex>
<Flex mt={4} alignItems={'center'}>
<Box flex={['0 0 100px', '0 0 200px']}>
{t('support.wallet.subscription.Update extra ai points')}
</Box>
<Flex alignItems={'center'} mt={1} w={'180px'} position={'relative'}>
<NumberInput
size={'sm'}
flex={1}
min={0}
max={10000}
step={1}
position={'relative'}
>
<NumberInputField
pr={'30px'}
step={1}
{...registerExtraPoints('points', {
required: true,
min: 0,
max: 10000,
valueAsNumber: true
})}
/>
<NumberInputStepper>
<NumberIncrementStepper />
<NumberDecrementStepper />
</NumberInputStepper>
</NumberInput>
<Box position={'absolute'} right={'20px'} color={'myGray.500'} fontSize={'xs'}>
000
</Box>
</Flex>
</Flex>
<Button
mt={6}
w={'100%'}
variant={'primaryGhost'}
isLoading={loading}
onClick={handleSubmitExtraPoints(onclickBuyExtraPoints)}
>
{t('support.wallet.Buy')}
{t('common.change')}
</Button>
</Box>
</Box>
</Grid>
{!!qrPayData && <QRCodePayModal {...qrPayData} />}
{/* extra dataset size modal */}
{!!confirmPayExtraDatasetSizeData && (
<MyModal
isOpen
onClose={() => setConfirmPayExtraDatasetSizeData(undefined)}
title={t('support.wallet.Confirm pay')}
iconSrc="common/confirm/rightTip"
>
<ModalBody px={8} py={5}>
<Flex>
<Box flex={'0 0 120px'} color={'myGray.600'}>
</Box>
<Box>{extraDatasetSize?.currentExtraDatasetSize || 0}</Box>
</Flex>
<Flex mt={4}>
<Box flex={'0 0 120px'} color={'myGray.600'}>
</Box>
<Box>{confirmPayExtraDatasetSizeData.newSubSize}</Box>
</Flex>
<Flex mt={4}>
<Box flex={'0 0 120px'} color={'myGray.600'}>
</Box>
<Box>{formatStorePrice2Read(confirmPayExtraDatasetSizeData.newPlanPrice)}</Box>
</Flex>
<Flex mt={4}>
<Box flex={'0 0 120px'} color={'myGray.600'}>
</Box>
<Box>30</Box>
</Flex>
{/* <Flex>
<Box flex={'0 0 120px'}>账号余额:</Box>
<Box>{formatStorePrice2Read(userInfo?.team?.balance).toFixed(3)}元</Box>
</Flex> */}
</ModalBody>
<ModalFooter mx={8} px={0} borderTopWidth={'1px'} borderTopColor={'myGray.200'}>
<Box color={'myGray.600'}></Box>
{confirmPayExtraDatasetSizeData.balanceEnough ? (
<>
<Box flex={'1 0 0'}>
{formatStorePrice2Read(userInfo?.team?.balance).toFixed(2)}
</Box>
<Button
isLoading={isPayingExtraDatasetSize}
onClick={() => onClickUpdateExtraDatasetPlan('')}
>
{formatStorePrice2Read(confirmPayExtraDatasetSizeData.payPrice).toFixed(2)}
</Button>
</>
) : (
<>
<Box color={'red.600'} flex={'1 0 0'}>
</Box>
<Button
isLoading={isPayingExtraDatasetSize}
onClick={() => router.push('/account')}
>
</Button>
</>
)}
</ModalFooter>
</MyModal>
)}
</Flex>
);
};

View File

@@ -4,40 +4,7 @@ import { useTranslation } from 'next-i18next';
const FAQ = () => {
const { t } = useTranslation();
const faqs = [
{
title: '订阅套餐会自动续费么?',
desc: '当前套餐过期后,系统会自动根据“未来套餐”进行续费,系统会尝试从账户余额进行扣费,如果您需要自动续费,请在账户余额中预留额度。'
},
{
title: '能否切换订阅套餐?',
desc: '当前套餐价格大于新套餐时,无法立即切换,将会在当前套餐过期后以“续费”形式进行切换。\n当前套餐价格小于新套餐时系统会自动计算当前套餐剩余余额您可支付差价进行套餐切换。'
},
{
title: '什么是AI积分',
desc: '每次调用AI模型时都会消耗一定的AI积分。具体的计算标准可参考上方的“AI 积分计算标准”。'
},
{
title: 'AI积分会过期么',
desc: '会过期。当前套餐过期后AI积分将会清空并更新为新套餐的AI积分。年度套餐的AI积分时长为1年而不是每个月。'
},
{
title: '知识库索引怎么计算?',
desc: '知识库索引是系统存储的最小单位。通常每条知识库数据对应一条索引,但也会有多条索引的情况。你可以在知识库数据的编辑面板,查看该数据的索引数量和具体内容。'
},
{
title: '额外资源包可以叠加么?',
desc: '可以的。每次购买的资源包都是独立的在其有效期内将会叠加使用。AI积分会优先扣除最先过期的资源包。'
},
{
title: '知识库索引超出会删除么?',
desc: '不会,知识库索引超出时,仅无法插入新的知识库索引。'
},
{
title: '免费版数据会清除么?',
desc: '免费版用户15天无使用记录后会自动清除所有知识库内容。'
}
];
const faqs = [{ title: '怎么付费', describe: '2222' }];
return (
<Flex
@@ -51,25 +18,22 @@ const FAQ = () => {
{t('support.wallet.subscription.FAQ')}
</Box>
<Grid mt={4} gridTemplateColumns={['1fr', '1fr 1fr']} gap={4} w={'100%'}>
{faqs.map((item, i) => (
<Box
key={i}
py={4}
px={5}
borderRadius={'lg'}
borderWidth={'1px'}
borderColor={'myGray.150'}
bg={'rgba(255,255,255,0.9)'}
_hover={{
borderColor: 'primary.300'
}}
>
<Box fontWeight={'bold'}>{item.title}</Box>
<Box fontSize={'sm'} color={'myGray.600'} whiteSpace={'pre-wrap'}>
{item.desc}
</Box>
<Box
py={2}
px={4}
borderRadius={'lg'}
borderWidth={'1px'}
borderColor={'myGray.150'}
bg={'rgba(255,255,255,0.9)'}
_hover={{
borderColor: 'primary.300'
}}
>
<Box fontSize={'lg'} fontWeight={'500'}>
</Box>
))}
<Box color={'myGray.500'}>2222</Box>
</Box>
</Grid>
</Flex>
);

View File

@@ -15,7 +15,7 @@ const Points = () => {
alignItems={'center'}
position={'relative'}
>
<Box id="point-card" fontWeight={'bold'} fontSize={['24px', '36px']}>
<Box fontWeight={'bold'} fontSize={['24px', '36px']}>
{t('support.wallet.subscription.Ai points')}
</Box>
<Grid gap={6} mt={['30px', '48px']} w={'100%'}>
@@ -42,7 +42,7 @@ const Points = () => {
{llmModelList?.map((item, i) => (
<Flex key={item.model} py={4} bg={i % 2 !== 0 ? 'myGray.50' : ''}>
<Box flex={'1 0 0'}>{item.name}</Box>
<Box flex={'1 0 0'}>{item.charsPointsPrice} / 1000</Box>
<Box flex={'1 0 0'}>5 / 1000</Box>
</Flex>
))}
</Box>
@@ -67,7 +67,7 @@ const Points = () => {
{vectorModelList?.map((item, i) => (
<Flex key={item.model} py={4} bg={i % 2 !== 0 ? 'myGray.50' : ''}>
<Box flex={'1 0 0'}>{item.name}</Box>
<Box flex={'1 0 0'}>{item.charsPointsPrice} / 1000</Box>
<Box flex={'1 0 0'}>5 / 1000</Box>
</Flex>
))}
</Box>
@@ -89,7 +89,7 @@ const Points = () => {
{audioSpeechModelList?.map((item, i) => (
<Flex key={item.model} py={4} bg={i % 2 !== 0 ? 'myGray.50' : ''}>
<Box flex={'1 0 0'}>{item.name}</Box>
<Box flex={'1 0 0'}>{item.charsPointsPrice} / 1000</Box>
<Box flex={'1 0 0'}>5 / 1000</Box>
</Flex>
))}
</Box>
@@ -110,7 +110,7 @@ const Points = () => {
<Box flex={4} textAlign={'center'} h={'100%'}>
<Flex py={4}>
<Box flex={'1 0 0'}>{whisperModel?.name}</Box>
<Box flex={'1 0 0'}>{whisperModel?.charsPointsPrice} / </Box>
<Box flex={'1 0 0'}>{whisperModel?.inputPrice} / </Box>
</Flex>
</Box>
</Box>

View File

@@ -1,8 +1,9 @@
import React, { useMemo, useState } from 'react';
import MyIcon from '@fastgpt/web/components/common/Icon';
import { Box, Button, Flex, Grid, ModalBody, ModalFooter } from '@chakra-ui/react';
import { Box, Button, Flex, Grid } from '@chakra-ui/react';
import { useTranslation } from 'next-i18next';
import { StandardSubLevelEnum, SubModeEnum } from '@fastgpt/global/support/wallet/sub/constants';
import { useUserStore } from '@/web/support/user/useUserStore';
import { postCheckStandardSub, postUpdateStandardSub } from '@/web/support/wallet/sub/api';
import { useSystemStore } from '@/web/common/system/useSystemStore';
import { standardSubLevelMap } from '@fastgpt/global/support/wallet/sub/constants';
@@ -10,21 +11,9 @@ import { StandardSubPlanParams } from '@fastgpt/global/support/wallet/sub/api';
import { useRequest } from '@/web/common/hooks/useRequest';
import { StandardSubPlanUpdateResponse } from '@fastgpt/global/support/wallet/sub/api.d';
import { useToast } from '@fastgpt/web/hooks/useToast';
import { formatStorePrice2Read } from '@fastgpt/global/support/wallet/usage/tools';
import { useConfirm } from '@/web/common/hooks/useConfirm';
import { formatStorePrice2Read } from '@fastgpt/global/support/wallet/bill/tools';
import { TeamSubSchema } from '@fastgpt/global/support/wallet/sub/type';
import MyModal from '@/components/MyModal';
import QRCodePayModal, { type QRPayProps } from '@/components/support/wallet/QRCodePayModal';
import { getWxPayQRCode } from '@/web/support/wallet/bill/api';
import { BillTypeEnum } from '@fastgpt/global/support/wallet/bill/constants';
import StandardPlanContentList from '@/components/support/wallet/StandardPlanContentList';
type ConfirmPayModalProps = {
teamBalance: number;
totalPrice: number;
payPrice: number;
planProps: StandardSubPlanParams;
};
const Standard = ({
standardPlan,
@@ -36,7 +25,7 @@ const Standard = ({
const { t } = useTranslation();
const { subPlans, feConfigs } = useSystemStore();
const { toast } = useToast();
const [confirmPayData, setConfirmPayData] = useState<ConfirmPayModalProps>();
const { ConfirmModal, openConfirm } = useConfirm({});
const [selectSubMode, setSelectSubMode] = useState<`${SubModeEnum}`>(SubModeEnum.month);
@@ -52,12 +41,12 @@ const Standard = ({
maxDatasetAmount: value.maxDatasetAmount,
chatHistoryStoreDuration: value.chatHistoryStoreDuration,
maxDatasetSize: value.maxDatasetSize,
permissionCustomApiKey: value.permissionCustomApiKey,
permissionCustomCopyright: value.permissionCustomCopyright,
customApiKey: value.customApiKey,
customCopyright: value.customCopyright,
trainingWeight: value.trainingWeight,
permissionReRank: value.permissionReRank,
reRankWeight: value.reRankWeight,
totalPoints: value.totalPoints * (selectSubMode === SubModeEnum.month ? 1 : 12),
permissionWebsiteSync: value.permissionWebsiteSync
websiteSyncInterval: value.websiteSyncInterval
};
})
: [];
@@ -75,21 +64,41 @@ const Standard = ({
const { mutate: onclickPreCheckStandPlan, isLoading: isCheckingStandardPlan } = useRequest({
mutationFn: (data: StandardSubPlanParams) => postCheckStandardSub(data),
onSuccess(res: StandardSubPlanUpdateResponse) {
if (!res.balanceEnough) {
return toast({
status: 'warning',
title: t('support.wallet.Balance not enough tip')
});
}
if (res.payPrice === undefined) {
onclickUpdateStandardPlan({
level: res.nextSubLevel,
mode: res.nextMode
});
} else if (res.payPrice > 0) {
openConfirm(
() =>
onclickUpdateStandardPlan({
level: res.nextSubLevel,
mode: res.nextMode
}),
undefined,
t('support.wallet.subscription.Standard plan pay confirm', {
payPrice: formatStorePrice2Read(res.payPrice).toFixed(2)
})
)();
} else {
setConfirmPayData({
teamBalance: res.teamBalance,
totalPrice: res.planPrice,
payPrice: res.payPrice,
planProps: {
level: res.nextSubLevel,
mode: res.nextMode
}
});
openConfirm(
() =>
onclickUpdateStandardPlan({
level: res.nextSubLevel,
mode: res.nextMode
}),
undefined,
t('support.wallet.subscription.Refund plan and pay confirm', {
amount: formatStorePrice2Read(Math.abs(res.payPrice)).toFixed(2)
})
)();
}
}
});
@@ -129,114 +138,148 @@ const Standard = ({
gap={[4, 6, 8]}
w={'100%'}
>
{standardSubList.map((item) => {
const isCurrentPlan =
item.level === standardPlan?.currentSubLevel &&
selectSubMode === standardPlan?.currentMode;
return (
<Box
key={item.level}
flex={'1 0 0'}
bg={'rgba(255, 255, 255, 0.90)'}
p={'28px'}
borderRadius={'2xl'}
borderWidth={'1.5px'}
boxShadow={'1.5'}
{...(isCurrentPlan
? {
borderColor: 'primary.600'
}
: {
borderColor: 'myGray.150'
})}
>
<Box fontSize={'lg'} fontWeight={'500'}>
{t(item.label)}
</Box>
<Box fontSize={['32px', '42px']} fontWeight={'bold'}>
{item.price}
</Box>
<Box color={'myGray.500'} h={'40px'} fontSize={'xs'}>
{t(item.desc, { title: feConfigs?.systemTitle })}
</Box>
{(() => {
if (
item.level === StandardSubLevelEnum.free &&
selectSubMode === SubModeEnum.year
) {
return (
<Button isDisabled mt={4} mb={6} w={'100%'} variant={'solid'}>
{t('support.wallet.subscription.Nonsupport')}
</Button>
);
}
if (
item.level === standardPlan?.nextSubLevel &&
selectSubMode === standardPlan?.nextMode
) {
return (
<Button mt={4} mb={6} w={'100%'} variant={'whiteBase'} isDisabled>
{t('support.wallet.subscription.Next plan')}
</Button>
);
}
if (isCurrentPlan) {
return (
<Button
mt={4}
mb={6}
w={'100%'}
variant={'whiteBase'}
isDisabled={
item.level === standardPlan?.nextSubLevel &&
selectSubMode === standardPlan?.nextMode
}
onClick={() =>
onclickPreCheckStandPlan({
level: item.level,
mode: selectSubMode
})
}
>
{t('support.wallet.subscription.Current plan')}
</Button>
);
}
{standardSubList.map((item) => (
<Box
key={item.level}
bg={'rgba(255, 255, 255, 0.90)'}
p={'28px'}
borderRadius={'2xl'}
borderWidth={'1px'}
borderColor={'myGray.150'}
boxShadow={'1.5'}
>
<Box fontSize={'lg'} fontWeight={'500'}>
{t(item.label)}
</Box>
<Box fontSize={['32px', '42px']} fontWeight={'bold'}>
{item.price}
</Box>
<Box color={'myGray.500'} h={'40px'}>
{t(item.desc, { title: feConfigs?.systemTitle })}
</Box>
{(() => {
if (item.level === StandardSubLevelEnum.free && selectSubMode === SubModeEnum.year) {
return (
<Button
mt={4}
mb={6}
w={'100%'}
variant={'primaryGhost'}
isLoading={isUpdatingStandardPlan || isCheckingStandardPlan}
onClick={() =>
onclickPreCheckStandPlan({
level: item.level,
mode: selectSubMode
})
}
>
{t('support.wallet.subscription.Buy now')}
<Button isDisabled mt={4} mb={6} w={'100%'} variant={'solid'}>
{t('support.wallet.subscription.Nonsupport')}
</Button>
);
})()}
}
if (
item.level === standardPlan?.currentSubLevel &&
selectSubMode === standardPlan?.currentMode
) {
return (
<Button mt={4} mb={6} w={'100%'} variant={'whiteBase'} isDisabled>
{t('support.wallet.subscription.Current plan')}
</Button>
);
}
if (
item.level === standardPlan?.nextSubLevel &&
selectSubMode === standardPlan?.nextMode
) {
return (
<Button mt={4} mb={6} w={'100%'} variant={'whiteBase'} isDisabled>
{t('support.wallet.subscription.Next plan')}
</Button>
);
}
return (
<Button
mt={4}
mb={6}
w={'100%'}
variant={'primaryGhost'}
isLoading={isUpdatingStandardPlan || isCheckingStandardPlan}
onClick={() =>
onclickPreCheckStandPlan({
level: item.level,
mode: selectSubMode
})
}
>
{t('support.wallet.subscription.Buy now')}
</Button>
);
})()}
{/* function list */}
<StandardPlanContentList level={item.level} mode={selectSubMode} />
</Box>
);
})}
{/* function list */}
<Grid gap={4}>
<Flex alignItems={'center'}>
<MyIcon name={'price/right'} w={'16px'} mr={3} />
<Box color={'myGray.600'}>
{t('support.wallet.subscription.function.Max members', {
amount: item.maxTeamMember
})}
</Box>
</Flex>
<Flex alignItems={'center'}>
<MyIcon name={'price/right'} w={'16px'} mr={3} />
<Box color={'myGray.600'}>
{t('support.wallet.subscription.function.Max app', {
amount: item.maxAppAmount
})}
</Box>
</Flex>
<Flex alignItems={'center'}>
<MyIcon name={'price/right'} w={'16px'} mr={3} />
<Box color={'myGray.600'}>
{t('support.wallet.subscription.function.Max dataset', {
amount: item.maxDatasetAmount
})}
</Box>
</Flex>
<Flex alignItems={'center'}>
<MyIcon name={'price/right'} w={'16px'} mr={3} />
<Box color={'myGray.600'}>
{t('support.wallet.subscription.function.History store', {
amount: item.chatHistoryStoreDuration
})}
</Box>
</Flex>
<Flex alignItems={'center'}>
<MyIcon name={'price/right'} w={'16px'} mr={3} />
<Box color={'myGray.600'}>
{t('support.wallet.subscription.function.Max dataset size', {
amount: item.maxDatasetSize
})}
</Box>
</Flex>
<Flex alignItems={'center'}>
<MyIcon name={'price/right'} w={'16px'} mr={3} />
<Box color={'myGray.600'}>
{t('support.wallet.subscription.function.Points', {
amount: item.totalPoints
})}
</Box>
</Flex>
<Flex alignItems={'center'}>
<MyIcon name={'price/right'} w={'16px'} mr={3} />
<Box color={'myGray.600'}>
{t('support.wallet.subscription.Training weight', {
weight: item.trainingWeight
})}
</Box>
</Flex>
{!!item.customApiKey && (
<Flex alignItems={'center'}>
<MyIcon name={'price/right'} w={'16px'} mr={3} />
<Box color={'myGray.600'}>API Key</Box>
</Flex>
)}
{!!item.websiteSyncInterval && (
<Flex alignItems={'center'}>
<MyIcon name={'price/right'} w={'16px'} mr={3} />
<Box color={'myGray.600'}>{item.websiteSyncInterval} h/ web站点同步</Box>
</Flex>
)}
</Grid>
</Box>
))}
</Grid>
{!!confirmPayData && (
<ConfirmPayModal
{...confirmPayData}
onClose={() => setConfirmPayData(undefined)}
onConfirmPay={() => onclickUpdateStandardPlan(confirmPayData.planProps)}
/>
)}
<ConfirmModal />
</Flex>
);
};
@@ -295,87 +338,3 @@ const RowTabs = ({
</Box>
);
};
const ConfirmPayModal = ({
teamBalance,
totalPrice,
payPrice,
onClose,
onConfirmPay
}: ConfirmPayModalProps & { onClose: () => void; onConfirmPay: () => void }) => {
const { t } = useTranslation();
const [qrPayData, setQRPayData] = useState<QRPayProps>();
const formatPayPrice = Math.ceil(formatStorePrice2Read(payPrice));
const formatTeamBalance = Math.floor(formatStorePrice2Read(teamBalance));
const { mutate: handleClickPay, isLoading } = useRequest({
mutationFn: async (amount: number) => {
// 获取支付二维码
return getWxPayQRCode({
type: BillTypeEnum.balance,
balance: amount
});
},
onSuccess(res) {
setQRPayData({
readPrice: res.readPrice,
codeUrl: res.codeUrl,
billId: res.billId
});
}
});
return (
<MyModal
isOpen
iconSrc="modal/confirmPay"
title={t('support.wallet.Confirm pay')}
onClose={onClose}
>
<ModalBody py={5} px={9}>
<Flex>
<Box flex={'0 0 100px'}></Box>
<Box>{formatStorePrice2Read(totalPrice)}</Box>
</Flex>
<Flex mt={6}>
<Box flex={'0 0 100px'}></Box>
<Box>{Math.floor(formatStorePrice2Read(totalPrice - payPrice))}</Box>
</Flex>
<Flex mt={6}>
<Box flex={'0 0 100px'}></Box>
<Box>{formatPayPrice}</Box>
</Flex>
</ModalBody>
<ModalFooter
borderTopWidth={'1px'}
borderTopColor={'borderColor.base'}
mx={9}
justifyContent={'flex-start'}
px={0}
>
<Box>: </Box>
<Box ml={2} flex={1}>
{formatTeamBalance}
</Box>
{teamBalance >= payPrice ? (
<Button size={'sm'} onClick={onConfirmPay}>
</Button>
) : (
<Button
size={'sm'}
isLoading={isLoading}
onClick={() => {
handleClickPay(Math.ceil(formatStorePrice2Read(payPrice - teamBalance)));
}}
>
</Button>
)}
</ModalFooter>
{!!qrPayData && <QRCodePayModal {...qrPayData} onSuccess={onConfirmPay} />}
</MyModal>
);
};

View File

@@ -3,56 +3,48 @@ import { serviceSideProps } from '@/web/common/utils/i18n';
import { Box, Image } from '@chakra-ui/react';
import { useTranslation } from 'next-i18next';
import { useUserStore } from '@/web/support/user/useUserStore';
import { getTeamPlanStatus } from '@/web/support/wallet/sub/api';
import { getTeamDatasetValidSub } from '@/web/support/wallet/sub/api';
import { useQuery } from '@tanstack/react-query';
import StandardPlan from './components/Standard';
import ExtraPlan from './components/ExtraPlan';
import PointsCard from './components/Points';
import FAQ from './components/FAQ';
import { getToken } from '@/web/support/user/auth';
import Script from 'next/script';
const PriceBox = () => {
const { t } = useTranslation();
const { userInfo } = useUserStore();
const { data: teamSubPlan, refetch: refetchTeamSubPlan } = useQuery(
['getTeamPlanStatus'],
getTeamPlanStatus,
['getTeamDatasetValidSub'],
getTeamDatasetValidSub,
{
enabled: !!getToken() || !!userInfo
enabled: !!userInfo
}
);
return (
<>
<Script src="/js/qrcode.min.js" strategy="lazyOnload"></Script>
<Box
h={'100%'}
overflow={'overlay'}
w={'100%'}
px={['20px', '5vw']}
py={['30px', '80px']}
backgroundImage={'url(/imgs/priceBg.svg)'}
backgroundSize={'cover'}
backgroundRepeat={'no-repeat'}
>
{/* standard sub */}
<StandardPlan
standardPlan={teamSubPlan?.standard}
refetchTeamSubPlan={refetchTeamSubPlan}
/>
<Box
h={'100%'}
overflow={'overlay'}
w={'100%'}
px={['20px', '5vw']}
py={['30px', '80px']}
backgroundImage={'url(/imgs/priceBg.svg)'}
backgroundSize={'cover'}
backgroundRepeat={'no-repeat'}
>
{/* standard sub */}
<StandardPlan standardPlan={teamSubPlan?.standard} refetchTeamSubPlan={refetchTeamSubPlan} />
<ExtraPlan />
<ExtraPlan extraDatasetSize={teamSubPlan?.extraDatasetSize} />
{/* points */}
<PointsCard />
{/* points */}
<PointsCard />
{/* question */}
<FAQ />
</Box>
</>
{/* question */}
<FAQ />
</Box>
);
};

View File

@@ -40,15 +40,6 @@ const Tools = () => {
link: getDocPath('/docs/intro')
}
]
: []),
...(feConfigs?.show_pay
? [
{
icon: 'support/bill/priceLight',
label: '计费说明',
link: '/price'
}
]
: [])
];

View File

@@ -0,0 +1,17 @@
import React from 'react';
import Price from '@/components/support/wallet/Price';
import { useRouter } from 'next/router';
import { serviceSideProps } from '@/web/common/utils/i18n';
const PriceBox = () => {
const router = useRouter();
return <Price onClose={router.back} />;
};
export default PriceBox;
export async function getServerSideProps(context: any) {
return {
props: { ...(await serviceSideProps(context)) }
};
}

View File

@@ -6,9 +6,11 @@ import {
} from '@fastgpt/global/core/dataset/controller';
import {
insertDatasetDataVector,
recallFromVectorStore
recallFromVectorStore,
updateDatasetDataVector
} from '@fastgpt/service/common/vectorStore/controller';
import {
DatasetDataIndexTypeEnum,
DatasetSearchModeEnum,
DatasetSearchModeMap,
SearchScoreTypeEnum
@@ -20,7 +22,6 @@ import { deleteDatasetDataVector } from '@fastgpt/service/common/vectorStore/con
import { getVectorsByText } from '@fastgpt/service/core/ai/embedding';
import { MongoDatasetCollection } from '@fastgpt/service/core/dataset/collection/schema';
import {
DatasetDataItemType,
DatasetDataSchemaType,
DatasetDataWithCollectionType,
SearchDataResponseItemType
@@ -34,7 +35,7 @@ import type {
} from '@fastgpt/global/core/dataset/api.d';
import { pushDataListToTrainingQueue } from '@fastgpt/service/core/dataset/training/controller';
import { getVectorModel } from '../../ai/model';
import { mongoSessionRun } from '@fastgpt/service/common/mongo/sessionRun';
import { ModuleInputKeyEnum } from '@fastgpt/global/core/module/constants';
export async function pushDataToTrainingQueue(
props: {
@@ -77,7 +78,7 @@ export async function insertData2Dataset({
return Promise.reject("teamId and tmbId can't be the same");
}
const qaStr = getDefaultIndex({ q, a }).text;
const qaStr = `${q}\n${a}`.trim();
// empty indexes check, if empty, create default index
indexes =
@@ -85,16 +86,10 @@ export async function insertData2Dataset({
? indexes.map((index) => ({
...index,
dataId: undefined,
defaultIndex: index.text.trim() === qaStr
defaultIndex: indexes?.length === 1 && index.text === qaStr ? true : index.defaultIndex
}))
: [getDefaultIndex({ q, a })];
if (!indexes.find((index) => index.defaultIndex)) {
indexes.unshift(getDefaultIndex({ q, a }));
}
indexes = indexes.slice(0, 6);
// insert to vector store
const result = await Promise.all(
indexes.map((item) =>
@@ -133,10 +128,8 @@ export async function insertData2Dataset({
/**
* update data
* 1. compare indexes
* 2. insert new pg data
* session run:
* 3. update mongo data(session run)
* 4. delete old pg data
* 2. update pg data
* 3. update mongo data
*/
export async function updateData2Dataset({
dataId,
@@ -148,30 +141,31 @@ export async function updateData2Dataset({
if (!Array.isArray(indexes)) {
return Promise.reject('indexes is required');
}
const qaStr = getDefaultIndex({ q, a }).text;
const qaStr = `${q}\n${a}`.trim();
// patch index and update pg
const mongoData = await MongoDatasetData.findById(dataId);
if (!mongoData) return Promise.reject('core.dataset.error.Data not found');
// remove defaultIndex
let formatIndexes = indexes.map((index) => ({
...index,
text: index.text.trim(),
defaultIndex: index.text.trim() === qaStr
}));
if (!formatIndexes.find((index) => index.defaultIndex)) {
const defaultIndex = mongoData.indexes.find((index) => index.defaultIndex);
formatIndexes.unshift(defaultIndex ? defaultIndex : getDefaultIndex({ q, a }));
// make sure have one index
if (indexes.length === 0) {
const databaseDefaultIndex = mongoData.indexes.find((index) => index.defaultIndex);
indexes = [
getDefaultIndex({
q,
a,
dataId: databaseDefaultIndex ? String(databaseDefaultIndex.dataId) : undefined
})
];
}
formatIndexes = formatIndexes.slice(0, 6);
// patch indexes, create, update, delete
const patchResult: PatchIndexesProps[] = [];
// find database indexes in new Indexes, if have not, delete it
for (const item of mongoData.indexes) {
const index = formatIndexes.find((index) => index.dataId === item.dataId);
const index = indexes.find((index) => index.dataId === item.dataId);
if (!index) {
patchResult.push({
type: 'delete',
@@ -179,34 +173,35 @@ export async function updateData2Dataset({
});
}
}
for (const item of formatIndexes) {
for (const item of indexes) {
const index = mongoData.indexes.find((index) => index.dataId === item.dataId);
// in database, update
if (index) {
// default index update
if (index.defaultIndex && index.text !== qaStr) {
patchResult.push({
type: 'update',
index: {
//@ts-ignore
...index.toObject(),
text: qaStr
}
});
continue;
}
// custom index update
// manual update index
if (index.text !== item.text) {
patchResult.push({
type: 'update',
index: item
});
continue;
} else if (index.defaultIndex && index.text !== qaStr) {
// update default index
patchResult.push({
type: 'update',
index: {
...item,
type:
item.type === DatasetDataIndexTypeEnum.qa && !a
? DatasetDataIndexTypeEnum.chunk
: item.type,
text: qaStr
}
});
} else {
patchResult.push({
type: 'unChange',
index: item
});
}
patchResult.push({
type: 'unChange',
index: item
});
} else {
// not in database, create
patchResult.push({
@@ -220,12 +215,10 @@ export async function updateData2Dataset({
mongoData.updateTime = new Date();
await mongoData.save();
// insert vector
const clonePatchResult2Insert: PatchIndexesProps[] = JSON.parse(JSON.stringify(patchResult));
const insertResult = await Promise.all(
clonePatchResult2Insert.map(async (item) => {
// insert new vector and update dateId
if (item.type === 'create' || item.type === 'update') {
// update vector
const result = await Promise.all(
patchResult.map(async (item) => {
if (item.type === 'create') {
const result = await insertDatasetDataVector({
query: item.index.text,
model: getVectorModel(model),
@@ -236,54 +229,50 @@ export async function updateData2Dataset({
item.index.dataId = result.insertId;
return result;
}
if (item.type === 'update' && item.index.dataId) {
const result = await updateDatasetDataVector({
teamId: mongoData.teamId,
datasetId: mongoData.datasetId,
collectionId: mongoData.collectionId,
id: item.index.dataId,
query: item.index.text,
model: getVectorModel(model)
});
item.index.dataId = result.insertId;
return result;
}
if (item.type === 'delete' && item.index.dataId) {
await deleteDatasetDataVector({
teamId: mongoData.teamId,
id: item.index.dataId
});
return {
charsLength: 0
};
}
return {
charsLength: 0
};
})
);
const charsLength = insertResult.reduce((acc, cur) => acc + cur.charsLength, 0);
// console.log(clonePatchResult2Insert);
await mongoSessionRun(async (session) => {
// update mongo
const newIndexes = clonePatchResult2Insert
.filter((item) => item.type !== 'delete')
.map((item) => item.index);
// update mongo other data
mongoData.q = q || mongoData.q;
mongoData.a = a ?? mongoData.a;
mongoData.fullTextToken = jiebaSplit({ text: mongoData.q + mongoData.a });
// @ts-ignore
mongoData.indexes = newIndexes;
await mongoData.save({ session });
// delete vector
const deleteIdList = patchResult
.filter((item) => item.type === 'delete' || item.type === 'update')
.map((item) => item.index.dataId)
.filter(Boolean);
if (deleteIdList.length > 0) {
await deleteDatasetDataVector({
teamId: mongoData.teamId,
idList: deleteIdList as string[]
});
}
});
const charsLength = result.reduce((acc, cur) => acc + cur.charsLength, 0);
const newIndexes = patchResult.filter((item) => item.type !== 'delete').map((item) => item.index);
// update mongo other data
mongoData.q = q || mongoData.q;
mongoData.a = a ?? mongoData.a;
mongoData.fullTextToken = jiebaSplit({ text: mongoData.q + mongoData.a });
// @ts-ignore
mongoData.indexes = newIndexes;
await mongoData.save();
return {
charsLength
};
}
export const deleteDatasetData = async (data: DatasetDataItemType) => {
await mongoSessionRun(async (session) => {
await MongoDatasetData.findByIdAndDelete(data.id, { session });
await deleteDatasetDataVector({
teamId: data.teamId,
idList: data.indexes.map((item) => item.dataId)
});
});
};
type SearchDatasetDataProps = {
teamId: string;
model: string;
@@ -388,7 +377,7 @@ export async function searchDatasetData(props: SearchDatasetDataProps) {
a: data.a,
chunkIndex: data.chunkIndex,
datasetId: String(data.datasetId),
collectionId: String(data.collectionId?._id),
collectionId: String(data.collectionId._id),
sourceName: data.collectionId.name || '',
sourceId: data.collectionId?.fileId || data.collectionId?.rawLink,
score: [{ type: SearchScoreTypeEnum.embedding, value: data.score, index }]
@@ -492,7 +481,7 @@ export async function searchDatasetData(props: SearchDatasetDataProps) {
}))
});
if (results.length === 0) {
if (!Array.isArray(results)) {
usingReRank = false;
return [];
}

View File

@@ -1,6 +1,6 @@
import { MongoDatasetTraining } from '@fastgpt/service/core/dataset/training/schema';
import { pushQAUsage } from '@/service/support/wallet/usage/push';
import { TrainingModeEnum } from '@fastgpt/global/core/dataset/constants';
import { pushQABill } from '@/service/support/wallet/bill/push';
import { DatasetDataIndexTypeEnum, TrainingModeEnum } from '@fastgpt/global/core/dataset/constants';
import { sendOneInform } from '../support/user/inform/api';
import { getAIApi } from '@fastgpt/service/core/ai/config';
import type { ChatMessageItemType } from '@fastgpt/global/core/ai/type.d';
@@ -9,12 +9,12 @@ import { splitText2Chunks } from '@fastgpt/global/common/string/textSplitter';
import { replaceVariable } from '@fastgpt/global/common/string/tools';
import { Prompt_AgentQA } from '@/global/core/prompt/agent';
import { getErrText } from '@fastgpt/global/common/error/utils';
import { authTeamBalance } from '../support/permission/auth/bill';
import type { PushDatasetDataChunkProps } from '@fastgpt/global/core/dataset/api.d';
import { UserErrEnum } from '@fastgpt/global/common/error/code/user';
import { lockTrainingDataByTeamId } from '@fastgpt/service/core/dataset/training/controller';
import { pushDataToTrainingQueue } from '@/service/core/dataset/data/controller';
import { getLLMModel } from '../core/ai/model';
import { checkTeamAIPoints } from '../support/permission/teamLimit';
import { TeamErrEnum } from '@fastgpt/global/common/error/code/team';
const reduceQueue = () => {
global.qaQueueLen = global.qaQueueLen > 0 ? global.qaQueueLen - 1 : 0;
@@ -89,16 +89,16 @@ export async function generateQA(): Promise<any> {
// auth balance
try {
await checkTeamAIPoints(data.teamId);
await authTeamBalance(data.teamId);
} catch (error: any) {
if (error?.statusText === TeamErrEnum.aiPointsNotEnough) {
if (error?.statusText === UserErrEnum.balanceNotEnough) {
// send inform and lock data
try {
sendOneInform({
type: 'system',
title: '文本训练任务中止',
content:
'该团队账号的AI积分不足,文本训练任务中止,重新充值后将会继续。暂停的任务将在 7 天后被删除。',
'该团队账号余额不足,文本训练任务中止,重新充值后将会继续。暂停的任务将在 7 天后被删除。',
tmbId: data.tmbId
});
console.log('余额不足暂停【QA】生成任务');
@@ -161,7 +161,7 @@ ${replaceVariable(Prompt_AgentQA.fixedText, { text })}`;
// add bill
if (insertLen > 0) {
pushQAUsage({
pushQABill({
teamId: data.teamId,
tmbId: data.tmbId,
charsLength: `${prompt}${answer}`.length,
@@ -230,6 +230,7 @@ function formatSplitText(text: string, rawText: string) {
indexes: [
{
defaultIndex: true,
type: DatasetDataIndexTypeEnum.qa,
text: `${q}\n${a.trim().replace(/\n\s*/g, '\n')}`
}
]
@@ -247,6 +248,7 @@ function formatSplitText(text: string, rawText: string) {
indexes: [
{
defaultIndex: true,
type: DatasetDataIndexTypeEnum.chunk,
text: chunk
}
]

View File

@@ -4,10 +4,10 @@ import { TrainingModeEnum } from '@fastgpt/global/core/dataset/constants';
import { sendOneInform } from '../support/user/inform/api';
import { addLog } from '@fastgpt/service/common/system/log';
import { getErrText } from '@fastgpt/global/common/error/utils';
import { checkTeamAIPoints } from '../support/permission/teamLimit';
import { pushGenerateVectorUsage } from '@/service/support/wallet/usage/push';
import { authTeamBalance } from '@/service/support/permission/auth/bill';
import { pushGenerateVectorBill } from '@/service/support/wallet/bill/push';
import { UserErrEnum } from '@fastgpt/global/common/error/code/user';
import { lockTrainingDataByTeamId } from '@fastgpt/service/core/dataset/training/controller';
import { TeamErrEnum } from '@fastgpt/global/common/error/code/team';
const reduceQueue = () => {
global.vectorQueueLen = global.vectorQueueLen > 0 ? global.vectorQueueLen - 1 : 0;
@@ -93,16 +93,16 @@ export async function generateVector(): Promise<any> {
// auth balance
try {
await checkTeamAIPoints(data.teamId);
await authTeamBalance(data.teamId);
} catch (error: any) {
if (error?.statusText === TeamErrEnum.aiPointsNotEnough) {
if (error?.statusText === UserErrEnum.balanceNotEnough) {
// send inform and lock data
try {
sendOneInform({
type: 'system',
title: '文本训练任务中止',
content:
'该团队账号AI积分不足,文本训练任务中止,重新充值后将会继续。暂停的任务将在 7 天后被删除。',
'该团队账号余额不足,文本训练任务中止,重新充值后将会继续。暂停的任务将在 7 天后被删除。',
tmbId: data.tmbId
});
console.log('余额不足,暂停【向量】生成任务');
@@ -138,7 +138,7 @@ export async function generateVector(): Promise<any> {
});
// push bill
pushGenerateVectorUsage({
pushGenerateVectorBill({
teamId: data.teamId,
tmbId: data.tmbId,
charsLength,

View File

@@ -1,12 +1,9 @@
import { adaptChat2GptMessages } from '@fastgpt/global/core/chat/adapt';
import { ChatContextFilter, countMessagesChars } from '@fastgpt/service/core/chat/utils';
import { ChatContextFilter } from '@fastgpt/service/core/chat/utils';
import type { moduleDispatchResType, ChatItemType } from '@fastgpt/global/core/chat/type.d';
import { ChatRoleEnum } from '@fastgpt/global/core/chat/constants';
import { getAIApi } from '@fastgpt/service/core/ai/config';
import type {
ClassifyQuestionAgentItemType,
ModuleDispatchResponse
} from '@fastgpt/global/core/module/type.d';
import type { ClassifyQuestionAgentItemType } from '@fastgpt/global/core/module/type.d';
import { ModuleInputKeyEnum, ModuleOutputKeyEnum } from '@fastgpt/global/core/module/constants';
import type { ModuleDispatchProps } from '@fastgpt/global/core/module/type.d';
import { replaceVariable } from '@fastgpt/global/common/string/tools';
@@ -14,7 +11,7 @@ import { Prompt_CQJson } from '@/global/core/prompt/agent';
import { LLMModelItemType } from '@fastgpt/global/core/ai/model.d';
import { ModelTypeEnum, getLLMModel } from '@/service/core/ai/model';
import { getHistories } from '../utils';
import { formatModelChars2Points } from '@/service/support/wallet/usage/utils';
import { formatModelPrice2Store } from '@/service/support/wallet/bill/utils';
type Props = ModuleDispatchProps<{
[ModuleInputKeyEnum.aiModel]: string;
@@ -23,9 +20,10 @@ type Props = ModuleDispatchProps<{
[ModuleInputKeyEnum.userChatInput]: string;
[ModuleInputKeyEnum.agents]: ClassifyQuestionAgentItemType[];
}>;
type CQResponse = ModuleDispatchResponse<{
type CQResponse = {
[ModuleOutputKeyEnum.responseData]: moduleDispatchResType;
[key: string]: any;
}>;
};
const agentFunName = 'classify_question';
@@ -33,7 +31,6 @@ const agentFunName = 'classify_question';
export const dispatchClassifyQuestion = async (props: Props): Promise<CQResponse> => {
const {
user,
module: { name },
histories,
params: { model, history = 6, agents, userChatInput }
} = props as Props;
@@ -46,7 +43,7 @@ export const dispatchClassifyQuestion = async (props: Props): Promise<CQResponse
const chatHistories = getHistories(history, histories);
const { arg, charsLength } = await (async () => {
const { arg, inputTokens, outputTokens } = await (async () => {
if (cqModel.toolChoice) {
return toolChoice({
...props,
@@ -63,31 +60,25 @@ export const dispatchClassifyQuestion = async (props: Props): Promise<CQResponse
const result = agents.find((item) => item.key === arg?.type) || agents[agents.length - 1];
const { totalPoints, modelName } = formatModelChars2Points({
const { total, modelName } = formatModelPrice2Store({
model: cqModel.model,
charsLength,
modelType: ModelTypeEnum.llm
inputLen: inputTokens,
outputLen: outputTokens,
type: ModelTypeEnum.llm
});
return {
[result.key]: true,
[ModuleOutputKeyEnum.responseData]: {
totalPoints: user.openaiAccount?.key ? 0 : totalPoints,
price: user.openaiAccount?.key ? 0 : total,
model: modelName,
query: userChatInput,
charsLength,
inputTokens,
outputTokens,
cqList: agents,
cqResult: result.value,
contextTotalLen: chatHistories.length + 2
},
[ModuleOutputKeyEnum.moduleDispatchBills]: [
{
moduleName: name,
totalPoints,
model: modelName,
charsLength
}
]
}
};
};
@@ -158,13 +149,11 @@ ${systemPrompt}
const arg = JSON.parse(
response?.choices?.[0]?.message?.tool_calls?.[0]?.function?.arguments || ''
);
const functionChars =
agentFunction.description.length +
agentFunction.parameters.properties.type.description.length;
return {
arg,
charsLength: countMessagesChars(messages) + functionChars
inputTokens: response.usage?.prompt_tokens || 0,
outputTokens: response.usage?.completion_tokens || 0
};
} catch (error) {
console.log(agentFunction.parameters);
@@ -174,7 +163,8 @@ ${systemPrompt}
return {
arg: {},
charsLength: 0
inputTokens: 0,
outputTokens: 0
};
}
}
@@ -216,7 +206,8 @@ async function completions({
agents.find((item) => answer.includes(item.key) || answer.includes(item.value))?.key || '';
return {
charsLength: countMessagesChars(messages),
inputTokens: data.usage?.prompt_tokens || 0,
outputTokens: data.usage?.completion_tokens || 0,
arg: { type: id }
};
}

View File

@@ -1,12 +1,9 @@
import { adaptChat2GptMessages } from '@fastgpt/global/core/chat/adapt';
import { ChatContextFilter, countMessagesChars } from '@fastgpt/service/core/chat/utils';
import { ChatContextFilter } from '@fastgpt/service/core/chat/utils';
import type { moduleDispatchResType, ChatItemType } from '@fastgpt/global/core/chat/type.d';
import { ChatRoleEnum } from '@fastgpt/global/core/chat/constants';
import { getAIApi } from '@fastgpt/service/core/ai/config';
import type {
ContextExtractAgentItemType,
ModuleDispatchResponse
} from '@fastgpt/global/core/module/type';
import type { ContextExtractAgentItemType } from '@fastgpt/global/core/module/type';
import { ModuleInputKeyEnum, ModuleOutputKeyEnum } from '@fastgpt/global/core/module/constants';
import type { ModuleDispatchProps } from '@fastgpt/global/core/module/type.d';
import { Prompt_ExtractJson } from '@/global/core/prompt/agent';
@@ -14,7 +11,7 @@ import { replaceVariable } from '@fastgpt/global/common/string/tools';
import { LLMModelItemType } from '@fastgpt/global/core/ai/model.d';
import { getHistories } from '../utils';
import { ModelTypeEnum, getLLMModel } from '@/service/core/ai/model';
import { formatModelChars2Points } from '@/service/support/wallet/usage/utils';
import { formatModelPrice2Store } from '@/service/support/wallet/bill/utils';
type Props = ModuleDispatchProps<{
[ModuleInputKeyEnum.history]?: ChatItemType[];
@@ -23,18 +20,18 @@ type Props = ModuleDispatchProps<{
[ModuleInputKeyEnum.description]: string;
[ModuleInputKeyEnum.aiModel]: string;
}>;
type Response = ModuleDispatchResponse<{
type Response = {
[ModuleOutputKeyEnum.success]?: boolean;
[ModuleOutputKeyEnum.failed]?: boolean;
[ModuleOutputKeyEnum.contextExtractFields]: string;
}>;
[ModuleOutputKeyEnum.responseData]: moduleDispatchResType;
};
const agentFunName = 'extract_json_data';
export async function dispatchContentExtract(props: Props): Promise<Response> {
const {
user,
module: { name },
histories,
params: { content, history = 6, model, description, extractKeys }
} = props;
@@ -46,7 +43,7 @@ export async function dispatchContentExtract(props: Props): Promise<Response> {
const extractModel = getLLMModel(model);
const chatHistories = getHistories(history, histories);
const { arg, charsLength } = await (async () => {
const { arg, inputTokens, outputTokens } = await (async () => {
if (extractModel.toolChoice) {
return toolChoice({
...props,
@@ -83,10 +80,11 @@ export async function dispatchContentExtract(props: Props): Promise<Response> {
}
}
const { totalPoints, modelName } = formatModelChars2Points({
const { total, modelName } = formatModelPrice2Store({
model: extractModel.model,
charsLength,
modelType: ModelTypeEnum.llm
inputLen: inputTokens,
outputLen: outputTokens,
type: ModelTypeEnum.llm
});
return {
@@ -95,22 +93,15 @@ export async function dispatchContentExtract(props: Props): Promise<Response> {
[ModuleOutputKeyEnum.contextExtractFields]: JSON.stringify(arg),
...arg,
[ModuleOutputKeyEnum.responseData]: {
totalPoints: user.openaiAccount?.key ? 0 : totalPoints,
price: user.openaiAccount?.key ? 0 : total,
model: modelName,
query: content,
charsLength,
inputTokens,
outputTokens,
extractDescription: description,
extractResult: arg,
contextTotalLen: chatHistories.length + 2
},
[ModuleOutputKeyEnum.moduleDispatchBills]: [
{
moduleName: name,
totalPoints,
model: modelName,
charsLength
}
]
}
};
}
@@ -202,12 +193,10 @@ ${description || '根据用户要求获取适当的 JSON 字符串。'}
}
})();
const functionChars =
description.length + extractKeys.reduce((sum, item) => sum + item.desc.length, 0);
return {
rawResponse: response?.choices?.[0]?.message?.tool_calls?.[0]?.function?.arguments || '',
charsLength: countMessagesChars(messages) + functionChars,
inputTokens: response.usage?.prompt_tokens || 0,
outputTokens: response.usage?.completion_tokens || 0,
arg
};
}
@@ -249,6 +238,8 @@ Human: ${content}`
stream: false
});
const answer = data.choices?.[0].message?.content || '';
const inputTokens = data.usage?.prompt_tokens || 0;
const outputTokens = data.usage?.completion_tokens || 0;
// parse response
const start = answer.indexOf('{');
@@ -257,7 +248,8 @@ Human: ${content}`
if (start === -1 || end === -1)
return {
rawResponse: answer,
charsLength: countMessagesChars(messages),
inputTokens,
outputTokens,
arg: {}
};
@@ -269,14 +261,15 @@ Human: ${content}`
try {
return {
rawResponse: answer,
charsLength: countMessagesChars(messages),
inputTokens,
outputTokens,
arg: JSON.parse(jsonStr) as Record<string, any>
};
} catch (error) {
return {
rawResponse: answer,
charsLength: countMessagesChars(messages),
inputTokens,
outputTokens,
arg: {}
};
}

View File

@@ -1,16 +1,16 @@
import type { NextApiResponse } from 'next';
import { ChatContextFilter, countMessagesChars } from '@fastgpt/service/core/chat/utils';
import { ChatContextFilter } from '@fastgpt/service/core/chat/utils';
import type { moduleDispatchResType, ChatItemType } from '@fastgpt/global/core/chat/type.d';
import { ChatRoleEnum } from '@fastgpt/global/core/chat/constants';
import { sseResponseEventEnum } from '@fastgpt/service/common/response/constant';
import { textAdaptGptResponse } from '@/utils/adapt';
import { getAIApi } from '@fastgpt/service/core/ai/config';
import type { ChatCompletion, StreamChatType } from '@fastgpt/global/core/ai/type.d';
import { formatModelChars2Points } from '@/service/support/wallet/usage/utils';
import { formatModelPrice2Store } from '@/service/support/wallet/bill/utils';
import type { LLMModelItemType } from '@fastgpt/global/core/ai/model.d';
import { postTextCensor } from '@/service/common/censor';
import { ChatCompletionRequestMessageRoleEnum } from '@fastgpt/global/core/ai/constant';
import type { ModuleDispatchResponse, ModuleItemType } from '@fastgpt/global/core/module/type.d';
import type { ModuleItemType } from '@fastgpt/global/core/module/type.d';
import { countMessagesTokens, sliceMessagesTB } from '@fastgpt/global/common/string/tiktoken';
import { adaptChat2GptMessages } from '@fastgpt/global/core/chat/adapt';
import { Prompt_QuotePromptList, Prompt_QuoteTemplateList } from '@/global/core/prompt/AIChat';
@@ -32,10 +32,11 @@ export type ChatProps = ModuleDispatchProps<
[ModuleInputKeyEnum.aiChatDatasetQuote]?: SearchDataResponseItemType[];
}
>;
export type ChatResponse = ModuleDispatchResponse<{
export type ChatResponse = {
[ModuleOutputKeyEnum.answerText]: string;
[ModuleOutputKeyEnum.responseData]: moduleDispatchResType;
[ModuleOutputKeyEnum.history]: ChatItemType[];
}>;
};
/* request openai chat */
export const dispatchChatCompletion = async (props: ChatProps): Promise<ChatResponse> => {
@@ -45,7 +46,7 @@ export const dispatchChatCompletion = async (props: ChatProps): Promise<ChatResp
detail = false,
user,
histories,
module: { name, outputs },
outputs,
params: {
model,
temperature = 0,
@@ -153,7 +154,7 @@ export const dispatchChatCompletion = async (props: ChatProps): Promise<ChatResp
}
);
const { answerText, completeMessages } = await (async () => {
const { answerText, inputTokens, outputTokens, completeMessages } = await (async () => {
if (stream) {
// sse response
const { answer } = await streamResponse({
@@ -171,6 +172,17 @@ export const dispatchChatCompletion = async (props: ChatProps): Promise<ChatResp
return {
answerText: answer,
inputTokens: countMessagesTokens({
messages: filterMessages
}),
outputTokens: countMessagesTokens({
messages: [
{
obj: ChatRoleEnum.AI,
value: answer
}
]
}),
completeMessages
};
} else {
@@ -184,38 +196,33 @@ export const dispatchChatCompletion = async (props: ChatProps): Promise<ChatResp
return {
answerText: answer,
inputTokens: unStreamResponse.usage?.prompt_tokens || 0,
outputTokens: unStreamResponse.usage?.completion_tokens || 0,
completeMessages
};
}
})();
const charsLength = countMessagesChars(completeMessages);
const { totalPoints, modelName } = formatModelChars2Points({
const { total, modelName } = formatModelPrice2Store({
model,
charsLength,
modelType: ModelTypeEnum.llm
inputLen: inputTokens,
outputLen: outputTokens,
type: ModelTypeEnum.llm
});
return {
answerText,
[ModuleOutputKeyEnum.responseData]: {
totalPoints: user.openaiAccount?.key ? 0 : totalPoints,
responseData: {
price: user.openaiAccount?.key ? 0 : total,
model: modelName,
charsLength,
inputTokens,
outputTokens,
query: `${userChatInput}`,
maxToken: max_tokens,
quoteList: filterQuoteQA,
historyPreview: getHistoryPreview(completeMessages),
contextTotalLen: completeMessages.length
},
[ModuleOutputKeyEnum.moduleDispatchBills]: [
{
moduleName: name,
totalPoints,
model: modelName,
charsLength
}
],
history: completeMessages
};
};
@@ -242,13 +249,30 @@ function filterQuote({
// slice filterSearch
const filterQuoteQA = filterSearchResultsByMaxChars(quoteQA, model.quoteMaxToken);
// filterQuoteQA按collectionId聚合在一起后再按chunkIndex从小到大排序
const sortQuoteQAMap: Record<string, SearchDataResponseItemType[]> = {};
filterQuoteQA.forEach((item) => {
if (sortQuoteQAMap[item.collectionId]) {
sortQuoteQAMap[item.collectionId].push(item);
} else {
sortQuoteQAMap[item.collectionId] = [item];
}
});
const sortQuoteQAList = Object.values(sortQuoteQAMap);
sortQuoteQAList.forEach((qaList) => {
qaList.sort((a, b) => a.chunkIndex - b.chunkIndex);
});
const flatQuoteList = sortQuoteQAList.flat();
const quoteText =
filterQuoteQA.length > 0
? `${filterQuoteQA.map((item, index) => getValue(item, index).trim()).join('\n------\n')}`
flatQuoteList.length > 0
? `${flatQuoteList.map((item, index) => getValue(item, index)).join('\n')}`
: '';
return {
filterQuoteQA: filterQuoteQA,
filterQuoteQA: flatQuoteList,
quoteText
};
}

View File

@@ -1,19 +1,15 @@
import type { moduleDispatchResType } from '@fastgpt/global/core/chat/type.d';
import { formatModelChars2Points } from '@/service/support/wallet/usage/utils';
import { formatModelPrice2Store } from '@/service/support/wallet/bill/utils';
import type { SelectedDatasetType } from '@fastgpt/global/core/module/api.d';
import type { SearchDataResponseItemType } from '@fastgpt/global/core/dataset/type';
import type {
ModuleDispatchProps,
ModuleDispatchResponse
} from '@fastgpt/global/core/module/type.d';
import type { ModuleDispatchProps } from '@fastgpt/global/core/module/type.d';
import { ModelTypeEnum, getLLMModel, getVectorModel } from '@/service/core/ai/model';
import { searchDatasetData } from '@/service/core/dataset/data/controller';
import { ModuleInputKeyEnum, ModuleOutputKeyEnum } from '@fastgpt/global/core/module/constants';
import { DatasetSearchModeEnum } from '@fastgpt/global/core/dataset/constants';
import { queryExtension } from '@fastgpt/service/core/ai/functions/queryExtension';
import { getHistories } from '../utils';
import { datasetSearchQueryExtension } from '@fastgpt/service/core/dataset/search/utils';
import { ChatModuleBillType } from '@fastgpt/global/support/wallet/bill/type';
import { checkTeamReRankPermission } from '@/service/support/permission/teamLimit';
type DatasetSearchProps = ModuleDispatchProps<{
[ModuleInputKeyEnum.datasetSelectList]: SelectedDatasetType;
@@ -26,11 +22,12 @@ type DatasetSearchProps = ModuleDispatchProps<{
[ModuleInputKeyEnum.datasetSearchExtensionModel]: string;
[ModuleInputKeyEnum.datasetSearchExtensionBg]: string;
}>;
export type DatasetSearchResponse = ModuleDispatchResponse<{
export type DatasetSearchResponse = {
[ModuleOutputKeyEnum.responseData]: moduleDispatchResType;
[ModuleOutputKeyEnum.datasetIsEmpty]?: boolean;
[ModuleOutputKeyEnum.datasetUnEmpty]?: boolean;
[ModuleOutputKeyEnum.datasetQuoteQA]: SearchDataResponseItemType[];
}>;
};
export async function dispatchDatasetSearch(
props: DatasetSearchProps
@@ -38,7 +35,6 @@ export async function dispatchDatasetSearch(
const {
teamId,
histories,
module,
params: {
datasets = [],
similarity,
@@ -77,8 +73,6 @@ export async function dispatchDatasetSearch(
histories: getHistories(6, histories)
});
// console.log(concatQueries, rewriteQuery, aiExtensionResult);
// get vector
const vectorModel = getVectorModel(datasets[0]?.vectorModel?.model);
@@ -97,18 +91,18 @@ export async function dispatchDatasetSearch(
limit,
datasetIds: datasets.map((item) => item.datasetId),
searchMode,
usingReRank: usingReRank && (await checkTeamReRankPermission(teamId))
usingReRank
});
// count bill results
// vector
const { totalPoints, modelName } = formatModelChars2Points({
const { total, modelName } = formatModelPrice2Store({
model: vectorModel.model,
charsLength,
modelType: ModelTypeEnum.vector
inputLen: charsLength,
type: ModelTypeEnum.vector
});
const responseData: moduleDispatchResType & { totalPoints: number } = {
totalPoints,
const responseData: moduleDispatchResType & { price: number } = {
price: total,
query: concatQueries.join('\n'),
model: modelName,
charsLength,
@@ -117,42 +111,28 @@ export async function dispatchDatasetSearch(
searchMode,
searchUsingReRank: searchUsingReRank
};
const moduleDispatchBills: ChatModuleBillType[] = [
{
totalPoints,
moduleName: module.name,
model: modelName,
charsLength
}
];
if (aiExtensionResult) {
const { totalPoints, modelName } = formatModelChars2Points({
const { total, modelName } = formatModelPrice2Store({
model: aiExtensionResult.model,
charsLength: aiExtensionResult.charsLength,
modelType: ModelTypeEnum.llm
inputLen: aiExtensionResult.inputTokens,
outputLen: aiExtensionResult.outputTokens,
type: ModelTypeEnum.llm
});
responseData.totalPoints += totalPoints;
responseData.charsLength = aiExtensionResult.charsLength;
responseData.price += total;
responseData.inputTokens = aiExtensionResult.inputTokens;
responseData.outputTokens = aiExtensionResult.outputTokens;
responseData.extensionModel = modelName;
responseData.extensionResult =
aiExtensionResult.extensionQueries?.join('\n') ||
JSON.stringify(aiExtensionResult.extensionQueries);
moduleDispatchBills.push({
totalPoints,
moduleName: 'core.module.template.Query extension',
model: modelName,
charsLength: aiExtensionResult.charsLength
});
}
return {
isEmpty: searchRes.length === 0 ? true : undefined,
unEmpty: searchRes.length > 0 ? true : undefined,
quoteQA: searchRes,
responseData,
moduleDispatchBills
responseData
};
}

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