V4.6.9-first commit (#899)
* perf: insert mongo dataset data session * perf: dataset data index * remove delay * rename bill schema * rename bill record * perf: bill table * perf: prompt * perf: sub plan * change the usage count * feat: usage bill * publish usages * doc * 新增团队聊天功能 (#20) * perf: doc * feat 添加标签部分 feat 信息团队标签配置 feat 新增团队同步管理 feat team分享页面 feat 完成team分享页面 feat 实现模糊搜索 style 格式化 fix 修复迷糊匹配 style 样式修改 fix 团队标签功能修复 * fix 修复鉴权功能 * merge 合并代码 * fix 修复引用错误 * fix 修复pr问题 * fix 修复ts格式问题 --------- Co-authored-by: archer <545436317@qq.com> Co-authored-by: liuxingwan <liuxingwan.lxw@alibaba-inc.com> * update extra plan * fix: ts * format * perf: bill field * feat: standard plan * fix: ts * feat 个人账号页面修改 (#22) * feat 添加标签部分 feat 信息团队标签配置 feat 新增团队同步管理 feat team分享页面 feat 完成team分享页面 feat 实现模糊搜索 style 格式化 fix 修复迷糊匹配 style 样式修改 fix 团队标签功能修复 * fix 修复鉴权功能 * merge 合并代码 * fix 修复引用错误 * fix 修复pr问题 * fix 修复ts格式问题 * feat 修改个人账号页 --------- Co-authored-by: liuxingwan <liuxingwan.lxw@alibaba-inc.com> * sub plan page (#23) * fix chunk index; error page text * feat: dataset process Integral prediction * feat: stand plan field * feat: sub plan limit * perf: index * query extension * perf: share link push app name * perf: plan point unit * perf: get sub plan * perf: account page * feat 新增套餐详情弹窗代码 (#24) * merge 合并代码 * fix 新增套餐详情弹框 * fix 修复pr问题 * feat: change http node input to prompt editor (#21) * feat: change http node input to prompt editor * fix * split PromptEditor to HttpInput * Team plans (#25) * perf: pay check * perf: team plan test * plan limit check * replace sensitive text * perf: fix some null * collection null check * perf: plans modal * perf: http module * pacakge (#26) * individuation page and pay modal amount (#27) * feat: individuation page * team chat config * pay modal * plan count and replace invalid chars (#29) * fix: user oneapi * fix: training queue * fix: qa queue * perf: remove space chars * replace invalid chars * change httpinput dropdown menu (#28) * perf: http * reseet free plan * perf: plan code to packages * remove llm config to package * perf: code * perf: faq * fix: get team plan --------- Co-authored-by: yst <77910600+yu-and-liu@users.noreply.github.com> Co-authored-by: liuxingwan <liuxingwan.lxw@alibaba-inc.com> Co-authored-by: heheer <71265218+newfish-cmyk@users.noreply.github.com>
This commit is contained in:
@@ -13,8 +13,7 @@
|
||||
"maxResponse": 4000,
|
||||
"quoteMaxToken": 13000,
|
||||
"maxTemperature": 1.2,
|
||||
"inputPrice": 0,
|
||||
"outputPrice": 0,
|
||||
"charsPointsPrice": 0,
|
||||
"censor": false,
|
||||
"vision": false,
|
||||
"datasetProcess": false,
|
||||
@@ -32,8 +31,7 @@
|
||||
"maxResponse": 16000,
|
||||
"quoteMaxToken": 13000,
|
||||
"maxTemperature": 1.2,
|
||||
"inputPrice": 0,
|
||||
"outputPrice": 0,
|
||||
"charsPointsPrice": 0,
|
||||
"censor": false,
|
||||
"vision": false,
|
||||
"datasetProcess": true,
|
||||
@@ -51,8 +49,7 @@
|
||||
"maxResponse": 4000,
|
||||
"quoteMaxToken": 100000,
|
||||
"maxTemperature": 1.2,
|
||||
"inputPrice": 0,
|
||||
"outputPrice": 0,
|
||||
"charsPointsPrice": 0,
|
||||
"censor": false,
|
||||
"vision": false,
|
||||
"datasetProcess": false,
|
||||
@@ -70,10 +67,9 @@
|
||||
"maxResponse": 4000,
|
||||
"quoteMaxToken": 100000,
|
||||
"maxTemperature": 1.2,
|
||||
"inputPrice": 0,
|
||||
"outputPrice": 0,
|
||||
"charsPointsPrice": 0,
|
||||
"censor": false,
|
||||
"vision": false,
|
||||
"vision": true,
|
||||
"datasetProcess": false,
|
||||
"toolChoice": true,
|
||||
"functionCall": false,
|
||||
@@ -87,8 +83,7 @@
|
||||
{
|
||||
"model": "text-embedding-ada-002",
|
||||
"name": "Embedding-2",
|
||||
"inputPrice": 0,
|
||||
"outputPrice": 0,
|
||||
"charsPointsPrice": 0,
|
||||
"defaultToken": 700,
|
||||
"maxToken": 3000,
|
||||
"weight": 100
|
||||
@@ -99,22 +94,44 @@
|
||||
{
|
||||
"model": "tts-1",
|
||||
"name": "OpenAI TTS1",
|
||||
"inputPrice": 0,
|
||||
"outputPrice": 0,
|
||||
"charsPointsPrice": 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",
|
||||
"inputPrice": 0,
|
||||
"outputPrice": 0
|
||||
"charsPointsPrice": 0
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,13 +45,7 @@ const nextConfig = {
|
||||
},
|
||||
transpilePackages: ['@fastgpt/*'],
|
||||
experimental: {
|
||||
serverComponentsExternalPackages: [
|
||||
'mongoose',
|
||||
'pg',
|
||||
'react',
|
||||
'@chakra-ui/react',
|
||||
'@lexical/react'
|
||||
],
|
||||
serverComponentsExternalPackages: ['mongoose', 'pg'],
|
||||
outputFileTracingRoot: path.join(__dirname, '../../')
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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",
|
||||
@@ -35,6 +35,7 @@
|
||||
"hyperdown": "^2.4.29",
|
||||
"i18next": "^22.5.1",
|
||||
"immer": "^9.0.19",
|
||||
"js-yaml": "^4.1.0",
|
||||
"jschardet": "^3.0.0",
|
||||
"jsonwebtoken": "^9.0.2",
|
||||
"lodash": "^4.17.21",
|
||||
@@ -63,6 +64,7 @@
|
||||
"@svgr/webpack": "^6.5.1",
|
||||
"@types/formidable": "^2.0.5",
|
||||
"@types/js-cookie": "^3.0.3",
|
||||
"@types/js-yaml": "^4.0.9",
|
||||
"@types/jsonwebtoken": "^9.0.3",
|
||||
"@types/lodash": "^4.14.191",
|
||||
"@types/node": "^20.8.5",
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
1. 新增 - 知识库搜索合并模块。
|
||||
2. 新增 - 新的 Http 模块,支持更加灵活的参数传入。同时支持了输入输出自动数据类型转化,例如:接口输出的 JSON 类型会自动转成字符串类型,直接给其他模块使用。此外,还补充了一些例子,可在文档中查看。
|
||||
3. 优化 - 问题补全并入知识库搜索模块,无需单独配置。并且问题补全的同时,实现了问题扩展,丰富搜索的语义。(知识库模块会看到有2个参数配置,有一个是多余的,如果想让它消失,可以删除模块,重新增加一个)
|
||||
3. 优化 - 问题优化并入知识库搜索模块,无需单独配置。并且问题优化的同时,实现了问题扩展,丰富搜索的语义。(知识库模块会看到有2个参数配置,有一个是多余的,如果想让它消失,可以删除模块,重新增加一个)
|
||||
4. 修复 - 语音输入文件无法上传。
|
||||
5. 修复 - 对话框重新生成无法使用。
|
||||
6. [点击查看高级编排介绍文档](https://doc.fastgpt.in/docs/workflow/intro)
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
"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",
|
||||
@@ -83,6 +84,7 @@
|
||||
"Delete Success": "Delete Successful",
|
||||
"Delete Tip": "Delete Confirm",
|
||||
"Delete Warning": "Warning",
|
||||
"Detail": "Detail",
|
||||
"Done": "Done",
|
||||
"Edit": "Edit",
|
||||
"Exit": "Exit",
|
||||
@@ -91,6 +93,9 @@
|
||||
"Filed is repeat": "Filed is repeated",
|
||||
"Filed is repeated": "",
|
||||
"Finish": "Finish",
|
||||
"Import": "Import",
|
||||
"Import failed": "Import failed",
|
||||
"Import success": "Import success",
|
||||
"Input": "Input",
|
||||
"Intro": "Intro",
|
||||
"Invalid Json": "Invalid Json",
|
||||
@@ -98,8 +103,6 @@
|
||||
"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,6 +113,7 @@
|
||||
"Number of words": "{{amount}} words",
|
||||
"OK": "OK",
|
||||
"Opened": "Opened",
|
||||
"Other": "Other",
|
||||
"Output": "Output",
|
||||
"Params": "Params",
|
||||
"Password inconsistency": "Password inconsistency",
|
||||
@@ -132,17 +136,20 @@
|
||||
"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",
|
||||
@@ -500,6 +507,7 @@
|
||||
"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}}",
|
||||
@@ -609,7 +617,8 @@
|
||||
"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: {{inputPrice}}/1k tokens\nOutput: {{outputPrice}}/1k tokens",
|
||||
"Estimated Price Tips": "QA charges\nInput: 1k chars={{charsPointsPrice}} points",
|
||||
"Estimated points": "About {{points}} points",
|
||||
"Fetch Error": "Get link failed",
|
||||
"Fetch Url": "Url",
|
||||
"Fetch url placeholder": "Up to 10 links, one per line.",
|
||||
@@ -679,7 +688,7 @@
|
||||
"Source name": "Source",
|
||||
"Top K": "Top K",
|
||||
"Using cfr": "Open query extension",
|
||||
"Using query extension": "",
|
||||
"Using query extension": "Open query extension",
|
||||
"mode": {
|
||||
"embedding": "Vector recall",
|
||||
"embedding desc": "Use vectors for text correlation queries",
|
||||
@@ -775,6 +784,7 @@
|
||||
"Unlink tip": "[{{name}}] An unfilled or unconnected parameter exists",
|
||||
"Variable": "Variables",
|
||||
"Variable Setting": "Variable Setting",
|
||||
"Variable import": "Variable import",
|
||||
"edit": {
|
||||
"Field Already Exist": "Key already exist",
|
||||
"Field Edit": "Field Edit"
|
||||
@@ -793,6 +803,8 @@
|
||||
"Histories": "histories",
|
||||
"Key already exists": "Key already exists",
|
||||
"Key cannot be empty": "Name cannot be empty",
|
||||
"OpenAPI import": "OpenAPI import",
|
||||
"OpenAPI import placeholder": "Please enter the OpenAPI format content, and the request information for the first interface will be extracted.",
|
||||
"Props name": "Name",
|
||||
"Props tip": "You can set parameters related to Http requests\nGlobal changes or external parameter inputs can be invoked by {{key}}",
|
||||
"Props value": "Value",
|
||||
@@ -843,8 +855,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"
|
||||
@@ -856,6 +868,7 @@
|
||||
"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": {
|
||||
@@ -864,7 +877,7 @@
|
||||
"Quote": "Quote",
|
||||
"Search result empty": "Search result empty",
|
||||
"Search result not empty": "Search result not empty",
|
||||
"cfr result": "Response text",
|
||||
"query extension result": "Response text",
|
||||
"result false": "False",
|
||||
"result true": "True",
|
||||
"running done": "done",
|
||||
@@ -902,6 +915,7 @@
|
||||
"Tool module": "Tools",
|
||||
"UnKnow Module": "UnKnow Module",
|
||||
"User guide": "User guide",
|
||||
"http body placeholder": "Same syntax as APIFox",
|
||||
"textEditor": "Text Editor",
|
||||
"textEditor intro": "Output of fixed or incoming text after edit"
|
||||
},
|
||||
@@ -1103,6 +1117,7 @@
|
||||
"navbar": {
|
||||
"Account": "Account",
|
||||
"Apps": "App",
|
||||
"Apps Share": "",
|
||||
"Chat": "Chat",
|
||||
"Datasets": "DataSet",
|
||||
"Module": "Module",
|
||||
@@ -1157,6 +1172,9 @@
|
||||
"Update Your Plugin": "Update Plugin"
|
||||
},
|
||||
"support": {
|
||||
"account": {
|
||||
"Individuation": "Individuation"
|
||||
},
|
||||
"openapi": {
|
||||
"Api baseurl": "Baseurl",
|
||||
"Api manager": "API key management",
|
||||
@@ -1167,17 +1185,34 @@
|
||||
"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."
|
||||
}
|
||||
},
|
||||
"standard": {
|
||||
"AI Bonus Points": "AI Bonus Points",
|
||||
"Expired Time": "Expired Time",
|
||||
"Start Time": "Start Time",
|
||||
"storage": "storage",
|
||||
"type": "type"
|
||||
},
|
||||
"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"
|
||||
},
|
||||
@@ -1192,40 +1227,72 @@
|
||||
}
|
||||
},
|
||||
"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",
|
||||
"Pay success": "Pay success",
|
||||
"Standard Plan Detail": "Standard Plan Detail",
|
||||
"bill": {
|
||||
"AI Model": "AI Model",
|
||||
"AI Type": "AI Type",
|
||||
"Price": "Price(¥)"
|
||||
"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"
|
||||
}
|
||||
},
|
||||
"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 dataset size": "Update size",
|
||||
"Update extra ai points": "AI Points",
|
||||
"Update extra dataset size": "Dataset size",
|
||||
"Upgrade plan": "Upgrade plan",
|
||||
"function": {
|
||||
"History store": "",
|
||||
"Max app": "",
|
||||
@@ -1236,6 +1303,7 @@
|
||||
},
|
||||
"mode": {
|
||||
"Month": "Monthly",
|
||||
"Period": "Period",
|
||||
"Year": "Yearly",
|
||||
"Year sale": "2 months free"
|
||||
},
|
||||
@@ -1248,9 +1316,38 @@
|
||||
"team": ""
|
||||
},
|
||||
"type": {
|
||||
"balance": "Add Balance",
|
||||
"extraDatasetSize": "Extra dataset size",
|
||||
"standard": ""
|
||||
"extraPoints": "AI Points",
|
||||
"standard": "Sub plan"
|
||||
}
|
||||
},
|
||||
"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"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -1292,6 +1389,7 @@
|
||||
"Set OpenAI Account Failed": "Set OpenAI account failed",
|
||||
"Sign Out": "Sign Out",
|
||||
"Source": "Source",
|
||||
"Standard Detail": "",
|
||||
"Team": "Team",
|
||||
"Time": "Time",
|
||||
"Timezone": "Timezone",
|
||||
@@ -1339,7 +1437,11 @@
|
||||
"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?",
|
||||
@@ -1364,37 +1466,5 @@
|
||||
"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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
"AI Settings": "AI 配置",
|
||||
"Advance App TestTip": "当前应用可能为高级编排模式\n如需切换为【简易模式】请点击左侧保存按键",
|
||||
"App Detail": "应用详情",
|
||||
"Apps Share": "应用分享",
|
||||
"Basic Settings": "基本信息",
|
||||
"Chat Debug": "调试预览",
|
||||
"Chat Logs Tips": "日志会记录该应用的在线、分享和 API(需填写 chatId) 对话记录",
|
||||
@@ -83,6 +84,7 @@
|
||||
"Delete Success": "删除成功",
|
||||
"Delete Tip": "删除提示",
|
||||
"Delete Warning": "删除警告",
|
||||
"Detail": "详情",
|
||||
"Done": "完成",
|
||||
"Edit": "编辑",
|
||||
"Exit": "退出",
|
||||
@@ -91,6 +93,9 @@
|
||||
"Filed is repeat": "",
|
||||
"Filed is repeated": "字段重复了",
|
||||
"Finish": "完成",
|
||||
"Import": "导入",
|
||||
"Import failed": "导入失败",
|
||||
"Import success": "导入成功",
|
||||
"Input": "输入",
|
||||
"Intro": "介绍",
|
||||
"Invalid Json": "无效的JSON格式,请注意检查。",
|
||||
@@ -98,8 +103,6 @@
|
||||
"Last use time": "最后使用时间",
|
||||
"Load Failed": "加载失败",
|
||||
"Loading": "加载中...",
|
||||
"Max credit": "最大金额",
|
||||
"Max credit tips": "该链接最大可消耗多少金额,超出后链接将被禁止使用。-1 代表无限制。",
|
||||
"More settings": "更多设置",
|
||||
"Name": "名称",
|
||||
"Name Can": "名称不能为空",
|
||||
@@ -110,6 +113,7 @@
|
||||
"Number of words": "{{amount}}字",
|
||||
"OK": "好的",
|
||||
"Opened": "已开启",
|
||||
"Other": "其他",
|
||||
"Output": "输出",
|
||||
"Params": "参数",
|
||||
"Password inconsistency": "两次密码不一致",
|
||||
@@ -132,17 +136,20 @@
|
||||
"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": "更新成功",
|
||||
@@ -402,7 +409,7 @@
|
||||
"Stop Speak": "停止录音",
|
||||
"Type a message": "输入问题",
|
||||
"Unpin": "取消置顶",
|
||||
"You need to a chat app": "你需要创建一个应用",
|
||||
"You need to a chat app": "鉴权失败,暂无权限访问应用",
|
||||
"error": {
|
||||
"Chat error": "对话出现异常",
|
||||
"Messages empty": "接口内容为空,可能文本超长了~",
|
||||
@@ -434,7 +441,7 @@
|
||||
},
|
||||
"response": {
|
||||
"Complete Response": "完整响应",
|
||||
"Extension model": "问题补全模型",
|
||||
"Extension model": "问题优化模型",
|
||||
"Plugin Resonse Detail": "插件详情",
|
||||
"Read complete response": "查看详情",
|
||||
"Read complete response tips": "点击查看详细流程",
|
||||
@@ -500,6 +507,7 @@
|
||||
"Manual collection": "手动数据集",
|
||||
"My Dataset": "我的知识库",
|
||||
"Name": "知识库名称",
|
||||
"Query extension intro": "开启问题优化功能,可以提高提高连续对话时,知识库搜索的精度。开启该功能后,在进行知识库搜索时,会根据对话记录,利用 AI 补全问题缺失的信息。",
|
||||
"Quote Length": "引用内容长度",
|
||||
"Read Dataset": "查看知识库详情",
|
||||
"Search score tip": "{{scoreText}}下面是详细排名和得分情况:\n----\n{{detailScore}}",
|
||||
@@ -551,7 +559,8 @@
|
||||
"success": "开始同步"
|
||||
}
|
||||
},
|
||||
"training": {}
|
||||
"training": {
|
||||
}
|
||||
},
|
||||
"data": {
|
||||
"Auxiliary Data": "辅助数据",
|
||||
@@ -560,7 +569,7 @@
|
||||
"Data Content": "相关数据内容",
|
||||
"Data Content Placeholder": "该输入框是必填项,该内容通常是对于知识点的描述,也可以是用户的问题,最多 {{maxToken}} 字。",
|
||||
"Data Content Tip": "该输入框是必填项\n该内容通常是对于知识点的描述,也可以是用户的问题。",
|
||||
"Default Index Tip": "无法编辑,默认索引会使用【相关数据内容】与【辅助数据】的文本直接生成索引,如不需要默认索引,可删除。 每条数据必须保证有一个以上索引,所有索引被删除后,会自动生成默认索引。",
|
||||
"Default Index Tip": "无法编辑,默认索引会使用【相关数据内容】与【辅助数据】的文本直接生成索引。",
|
||||
"Edit": "编辑数据",
|
||||
"Empty Tip": "这个集合还没有数据~",
|
||||
"Main Content": "主要内容",
|
||||
@@ -608,9 +617,10 @@
|
||||
"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输入: {{inputPrice}}/1k tokens\n输出: {{outputPrice}}/1k tokens",
|
||||
"Estimated Price Tips": "QA计费为\n输入: 1k字符 = {{charsPointsPrice}}积分",
|
||||
"Estimated points": "预估消耗 {{points}} 积分",
|
||||
"Fetch Error": "获取链接失败",
|
||||
"Fetch Url": "网络链接",
|
||||
"Fetch url placeholder": "最多10个链接,每行一个。",
|
||||
@@ -630,7 +640,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": "重新生成预览",
|
||||
@@ -680,7 +690,7 @@
|
||||
"Source name": "引用来源名",
|
||||
"Top K": "单次搜索上限",
|
||||
"Using cfr": "",
|
||||
"Using query extension": "使用问题补全",
|
||||
"Using query extension": "使用问题优化",
|
||||
"mode": {
|
||||
"embedding": "语义检索",
|
||||
"embedding desc": "使用向量进行文本相关性查询",
|
||||
@@ -776,6 +786,7 @@
|
||||
"Unlink tip": "【{{name}}】存在未填或未连接参数",
|
||||
"Variable": "全局变量",
|
||||
"Variable Setting": "变量设置",
|
||||
"Variable import": "外部参数输入",
|
||||
"edit": {
|
||||
"Field Already Exist": "key 重复",
|
||||
"Field Edit": "字段编辑"
|
||||
@@ -794,6 +805,8 @@
|
||||
"Histories": "历史纪录,最多取10条",
|
||||
"Key already exists": "Key 已经存在",
|
||||
"Key cannot be empty": "参数名不能为空",
|
||||
"OpenAPI import": "OpenAPI 导入",
|
||||
"OpenAPI import placeholder": "请输入 OpenAPI 格式内容,将会提取第一个接口的请求信息。",
|
||||
"Props name": "参数名",
|
||||
"Props tip": "可以设置 Http 请求的相关参数\n可通过 {{key}} 来调用全局变了或外部参数输入,当前可使用变量:\n{{variable}}",
|
||||
"Props value": "参数值",
|
||||
@@ -844,8 +857,8 @@
|
||||
"dynamicTargetInput": "动态外部数据",
|
||||
"input": "输入框",
|
||||
"selectApp": "应用选择",
|
||||
"selectChatModel": "对话模型选择",
|
||||
"selectDataset": "知识库选择",
|
||||
"selectLLMModel": "对话模型选择",
|
||||
"switch": "开关",
|
||||
"target": "外部数据",
|
||||
"textarea": "段落输入"
|
||||
@@ -857,6 +870,7 @@
|
||||
"Ai response content": "将在 stream 回复完毕后触发",
|
||||
"New context": "将本次回复内容拼接上历史记录,作为新的上下文返回",
|
||||
"Quote": "始终返回数组,如果希望搜索结果为空时执行额外操作,需要用到上面的两个输入以及目标模块的触发器",
|
||||
"query extension result": "以字符串数组的形式输出,可将该结果直接连接到“知识库搜索”的“用户问题”中,建议不要连接到“AI对话”的“用户问题”中",
|
||||
"running done": "模块调用结束时触发"
|
||||
},
|
||||
"label": {
|
||||
@@ -865,7 +879,7 @@
|
||||
"Quote": "引用内容",
|
||||
"Search result empty": "搜索结果为空",
|
||||
"Search result not empty": "搜索结果不为空",
|
||||
"cfr result": "补全结果",
|
||||
"query extension result": "优化结果",
|
||||
"result false": "False",
|
||||
"result true": "True",
|
||||
"running done": "模块调用结束",
|
||||
@@ -892,8 +906,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": "可以选择一个其他应用进行调用",
|
||||
@@ -903,6 +917,7 @@
|
||||
"Tool module": "工具",
|
||||
"UnKnow Module": "未知模块",
|
||||
"User guide": "用户引导",
|
||||
"http body placeholder": "与APIFox相同的语法",
|
||||
"textEditor": "文本加工",
|
||||
"textEditor intro": "可对固定或传入的文本进行加工后输出,非字符串类型数据最终会转成字符串类型。"
|
||||
},
|
||||
@@ -1104,6 +1119,7 @@
|
||||
"navbar": {
|
||||
"Account": "账号",
|
||||
"Apps": "应用",
|
||||
"Apps Share": "应用分享",
|
||||
"Chat": "聊天",
|
||||
"Datasets": "知识库",
|
||||
"Module": "模块",
|
||||
@@ -1158,6 +1174,9 @@
|
||||
"Update Your Plugin": "更新插件"
|
||||
},
|
||||
"support": {
|
||||
"account": {
|
||||
"Individuation": "个性化"
|
||||
},
|
||||
"openapi": {
|
||||
"Api baseurl": "API根地址",
|
||||
"Api manager": "API 秘钥管理",
|
||||
@@ -1168,17 +1187,34 @@
|
||||
"Usage": "已用额度(¥)"
|
||||
},
|
||||
"outlink": {
|
||||
"Max usage points": "积分上限",
|
||||
"Max usage points tip": "该链接最多允许使用多少积分,超出后将无法使用。-1 代表无限制。",
|
||||
"Usage points": "积分消耗",
|
||||
"share": {
|
||||
"Response Quote": "返回引用",
|
||||
"Response Quote tips": "在分享链接中返回引用内容,但不会允许用户下载原文档"
|
||||
}
|
||||
},
|
||||
"standard": {
|
||||
"AI Bonus Points": "AI 积分",
|
||||
"Expired Time": "结束时间",
|
||||
"Start Time": "开始时间",
|
||||
"storage": "存储量",
|
||||
"type": "类型"
|
||||
},
|
||||
"subscription": {
|
||||
"Cancel subscription": "取消订阅"
|
||||
},
|
||||
"team": {
|
||||
"limit": {
|
||||
"No permission rerank": "无权使用结果重排,请升级您的套餐"
|
||||
}
|
||||
},
|
||||
"user": {
|
||||
"Avatar": "头像",
|
||||
"Need to login": "请先登录",
|
||||
"Price": "计费标准",
|
||||
"User self info": "个人信息",
|
||||
"auth": {
|
||||
"Sending Code": "正在发送"
|
||||
},
|
||||
@@ -1193,65 +1229,127 @@
|
||||
}
|
||||
},
|
||||
"wallet": {
|
||||
"Amount": "金额",
|
||||
"Balance not enough tip": "余额不足,请先到账号页充值",
|
||||
"Bills": "账单",
|
||||
"Buy": "购买",
|
||||
"Buy more": "扩容",
|
||||
"Confirm pay": "支付确认",
|
||||
"Pay error": "支付失败",
|
||||
"Pay success": "支付成功",
|
||||
"Standard Plan Detail": "套餐详情",
|
||||
"bill": {
|
||||
"AI Model": "AI 模型",
|
||||
"AI Type": "AI 类型",
|
||||
"Price": "价格(¥)"
|
||||
"Number": "订单号",
|
||||
"Price": "价格(¥)",
|
||||
"Status": "状态",
|
||||
"Type": "订单类型",
|
||||
"payWay": {
|
||||
"Way": "支付方式",
|
||||
"balance": "余额支付",
|
||||
"wx": "微信支付"
|
||||
},
|
||||
"status": {
|
||||
"closed": "已关闭",
|
||||
"notpay": "未支付",
|
||||
"refund": "已退款",
|
||||
"success": "支付成功"
|
||||
}
|
||||
},
|
||||
"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 plan": "当前计划",
|
||||
"Current extra ai points": "当前额外积分",
|
||||
"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": "常见问题",
|
||||
"Next plan": "未来计划",
|
||||
"Month amount": "月数",
|
||||
"Next extra ai points": "下次额外积分",
|
||||
"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 dataset size": "调整知识库额外容量",
|
||||
"Update extra ai points": "额外AI积分",
|
||||
"Update extra dataset size": "额外存储量",
|
||||
"Upgrade plan": "升级套餐",
|
||||
"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": "可使用 {{title}} 的完整功能",
|
||||
"experience desc": "",
|
||||
"free": "免费版",
|
||||
"free desc": "每月均可免费使用 {{title}} 的基础功能",
|
||||
"free desc": "每月均可免费使用基础功能,15天不活跃时,将会清除知识库",
|
||||
"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": "用户"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -1293,6 +1391,7 @@
|
||||
"Set OpenAI Account Failed": "设置 OpenAI 账号异常",
|
||||
"Sign Out": "登出",
|
||||
"Source": "来源",
|
||||
"Standard Detail": "",
|
||||
"Team": "团队",
|
||||
"Time": "时间",
|
||||
"Timezone": "时区",
|
||||
@@ -1340,7 +1439,11 @@
|
||||
"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": "确认加入该团队?",
|
||||
@@ -1365,37 +1468,5 @@
|
||||
"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 拆分"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useMemo, useState } from 'react';
|
||||
import React, { useState } from 'react';
|
||||
import { ModalBody, useTheme, ModalFooter, Button, Box, Card, Flex, Grid } from '@chakra-ui/react';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import Avatar from '../Avatar';
|
||||
@@ -8,7 +8,6 @@ import DatasetSelectModal, { useDatasetSelect } from '@/components/core/dataset/
|
||||
import dynamic from 'next/dynamic';
|
||||
import { AdminFbkType } from '@fastgpt/global/core/chat/type.d';
|
||||
import SelectCollections from '@/web/core/dataset/components/SelectCollections';
|
||||
import { getDefaultIndex } from '@fastgpt/global/core/dataset/utils';
|
||||
|
||||
const InputDataModal = dynamic(() => import('@/pages/dataset/detail/components/InputDataModal'));
|
||||
|
||||
|
||||
@@ -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?.price !== undefined && (
|
||||
{activeModule?.totalPoints !== undefined && (
|
||||
<Row
|
||||
label={t('core.chat.response.module price')}
|
||||
value={`¥${formatStorePrice2Read(activeModule?.price)}`}
|
||||
label={t('support.wallet.usage.Total points')}
|
||||
value={formatNumber(activeModule.totalPoints)}
|
||||
/>
|
||||
)}
|
||||
<Row
|
||||
@@ -142,11 +142,9 @@ 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('wallet.bill.Output Token Length')}
|
||||
value={`${activeModule?.outputTokens}`}
|
||||
label={t('support.wallet.usage.Chars length')}
|
||||
value={`${activeModule?.charsLength}`}
|
||||
/>
|
||||
<Row label={t('core.chat.response.module query')} value={activeModule?.query} />
|
||||
<Row
|
||||
@@ -208,14 +206,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('wallet.bill.Extension result')}
|
||||
label={t('support.wallet.usage.Extension result')}
|
||||
value={`${activeModule?.extensionResult}`}
|
||||
/>
|
||||
</>
|
||||
@@ -273,18 +271,18 @@ const ResponseBox = React.memo(function ResponseBox({
|
||||
|
||||
{/* plugin */}
|
||||
<>
|
||||
{activeModule?.pluginDetail && activeModule?.pluginDetail.length > 0 && (
|
||||
<Row
|
||||
label={t('core.chat.response.Plugin Resonse Detail')}
|
||||
rawDom={<ResponseBox response={activeModule.pluginDetail} isShare={isShare} />}
|
||||
/>
|
||||
)}
|
||||
{activeModule?.pluginOutput && (
|
||||
<Row
|
||||
label={t('core.chat.response.plugin output')}
|
||||
value={`~~~json\n${JSON.stringify(activeModule?.pluginOutput, null, 2)}`}
|
||||
/>
|
||||
)}
|
||||
{activeModule?.pluginDetail && activeModule?.pluginDetail.length > 0 && (
|
||||
<Row
|
||||
label={t('core.chat.response.Plugin Resonse Detail')}
|
||||
rawDom={<ResponseBox response={activeModule.pluginDetail} isShare={isShare} />}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
|
||||
{/* text output */}
|
||||
|
||||
@@ -121,6 +121,7 @@ type Props = {
|
||||
appId?: string;
|
||||
chatId?: string;
|
||||
shareId?: string;
|
||||
shareTeamId?: string;
|
||||
outLinkUid?: string;
|
||||
|
||||
onUpdateVariable?: (e: Record<string, any>) => void;
|
||||
@@ -146,6 +147,7 @@ const ChatBox = (
|
||||
appId,
|
||||
chatId,
|
||||
shareId,
|
||||
shareTeamId,
|
||||
outLinkUid,
|
||||
onUpdateVariable,
|
||||
onStartChat,
|
||||
@@ -396,21 +398,22 @@ const ChatBox = (
|
||||
};
|
||||
})
|
||||
);
|
||||
|
||||
setTimeout(() => {
|
||||
createQuestionGuide({
|
||||
history: newChatList.map((item, i) =>
|
||||
i === newChatList.length - 1
|
||||
? {
|
||||
...item,
|
||||
value: responseText
|
||||
}
|
||||
: item
|
||||
)
|
||||
});
|
||||
generatingScroll();
|
||||
isPc && TextareaDom.current?.focus();
|
||||
}, 100);
|
||||
if (!shareTeamId) {
|
||||
setTimeout(() => {
|
||||
createQuestionGuide({
|
||||
history: newChatList.map((item, i) =>
|
||||
i === newChatList.length - 1
|
||||
? {
|
||||
...item,
|
||||
value: responseText
|
||||
}
|
||||
: item
|
||||
)
|
||||
});
|
||||
generatingScroll();
|
||||
isPc && TextareaDom.current?.focus();
|
||||
}, 100);
|
||||
}
|
||||
} catch (err: any) {
|
||||
toast({
|
||||
title: t(getErrText(err, 'core.chat.error.Chat error')),
|
||||
|
||||
@@ -11,6 +11,7 @@ const unAuthPage: { [key: string]: boolean } = {
|
||||
'/login/fastlogin': true,
|
||||
'/appStore': true,
|
||||
'/chat/share': true,
|
||||
'/chat/team': true,
|
||||
'/tools/price': true,
|
||||
'/price': true
|
||||
};
|
||||
|
||||
@@ -23,6 +23,7 @@ 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,
|
||||
@@ -34,6 +35,7 @@ const phoneUnShowLayoutRoute: Record<string, boolean> = {
|
||||
'/login/provider': true,
|
||||
'/login/fastlogin': true,
|
||||
'/chat/share': true,
|
||||
'/chat/team': true,
|
||||
'/tools/price': true,
|
||||
'/price': true
|
||||
};
|
||||
@@ -114,9 +116,10 @@ const Layout = ({ children }: { children: JSX.Element }) => {
|
||||
</Box>
|
||||
</>
|
||||
)}
|
||||
|
||||
{!!userInfo && <UpdateInviteModal />}
|
||||
</Box>
|
||||
<Loading loading={loading} zIndex={999999} />
|
||||
{!!userInfo && <UpdateInviteModal />}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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, RemarkBreaks]}
|
||||
remarkPlugins={[RemarkMath, [RemarkGfm, { singleTilde: false }], RemarkBreaks]}
|
||||
rehypePlugins={[RehypeKatex]}
|
||||
components={components}
|
||||
linkTarget={'_blank'}
|
||||
|
||||
@@ -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';
|
||||
|
||||
const PriceBox = dynamic(() => import('@/components/support/wallet/Price'));
|
||||
import { useRouter } from 'next/router';
|
||||
import { AI_POINT_USAGE_CARD_ROUTE } from '@/web/support/wallet/sub/constants';
|
||||
|
||||
const SelectAiModel = ({ list, ...props }: SelectProps) => {
|
||||
const { t } = useTranslation();
|
||||
const { feConfigs } = useSystemStore();
|
||||
const router = useRouter();
|
||||
|
||||
const expandList = useMemo(() => {
|
||||
return feConfigs.show_pay
|
||||
? list.concat({
|
||||
@@ -20,12 +20,6 @@ const SelectAiModel = ({ list, ...props }: SelectProps) => {
|
||||
: list;
|
||||
}, [feConfigs.show_pay, list, t]);
|
||||
|
||||
const {
|
||||
isOpen: isOpenPriceBox,
|
||||
onOpen: onOpenPriceBox,
|
||||
onClose: onClosePriceBox
|
||||
} = useDisclosure();
|
||||
|
||||
return (
|
||||
<>
|
||||
<MySelect
|
||||
@@ -33,13 +27,12 @@ const SelectAiModel = ({ list, ...props }: SelectProps) => {
|
||||
{...props}
|
||||
onchange={(e) => {
|
||||
if (e === 'price') {
|
||||
onOpenPriceBox();
|
||||
router.push(AI_POINT_USAGE_CARD_ROUTE);
|
||||
return;
|
||||
}
|
||||
props.onchange?.(e);
|
||||
}}
|
||||
/>
|
||||
{isOpenPriceBox && <PriceBox onClose={onClosePriceBox} />}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
103
projects/app/src/components/TagEdit/index.tsx
Normal file
103
projects/app/src/components/TagEdit/index.tsx
Normal file
@@ -0,0 +1,103 @@
|
||||
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;
|
||||
@@ -27,6 +27,8 @@ 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}`;
|
||||
@@ -61,6 +63,8 @@ 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);
|
||||
@@ -71,7 +75,7 @@ const DatasetParamsModal = ({
|
||||
limit,
|
||||
similarity,
|
||||
searchMode,
|
||||
usingReRank,
|
||||
usingReRank: !!usingReRank && !!teamPlanStatus?.standardConstants?.permissionReRank,
|
||||
datasetSearchUsingExtensionQuery,
|
||||
datasetSearchExtensionModel: datasetSearchExtensionModel ?? llmModelList[0]?.model,
|
||||
datasetSearchExtensionBg
|
||||
@@ -105,6 +109,10 @@ const DatasetParamsModal = ({
|
||||
return true;
|
||||
}, [getValues, similarity]);
|
||||
|
||||
const showReRank = useMemo(() => {
|
||||
return usingReRank !== undefined && reRankModelList.length > 0;
|
||||
}, [reRankModelList.length, usingReRank]);
|
||||
|
||||
return (
|
||||
<MyModal
|
||||
isOpen={true}
|
||||
@@ -148,7 +156,7 @@ const DatasetParamsModal = ({
|
||||
setRefresh(!refresh);
|
||||
}}
|
||||
/>
|
||||
{usingReRank !== undefined && reRankModelList.length > 0 && (
|
||||
{showReRank && (
|
||||
<>
|
||||
<Divider my={4} />
|
||||
<Flex
|
||||
@@ -168,6 +176,15 @@ const DatasetParamsModal = ({
|
||||
}
|
||||
: {})}
|
||||
onClick={(e) => {
|
||||
if (
|
||||
teamPlanStatus?.standardConstants &&
|
||||
!teamPlanStatus?.standardConstants?.permissionReRank
|
||||
) {
|
||||
return toast({
|
||||
status: 'warning',
|
||||
title: t('support.team.limit.No permission rerank')
|
||||
});
|
||||
}
|
||||
setValue('usingReRank', !getValues('usingReRank'));
|
||||
setRefresh((state) => !state);
|
||||
}}
|
||||
@@ -273,7 +290,7 @@ const DatasetParamsModal = ({
|
||||
{currentTabType === SearchSettingTabEnum.queryExtension && (
|
||||
<Box>
|
||||
<Box fontSize={'xs'} color={'myGray.500'}>
|
||||
{t('core.module.template.Query extension intro')}
|
||||
{t('core.dataset.Query extension intro')}
|
||||
</Box>
|
||||
<Flex mt={3} alignItems={'center'}>
|
||||
<Box flex={'1 0 0'}>{t('core.dataset.search.Using query extension')}</Box>
|
||||
|
||||
@@ -0,0 +1,185 @@
|
||||
import React from 'react';
|
||||
import MyModal from '@/components/MyModal';
|
||||
import { ModalBody, Button, ModalFooter, useDisclosure, Textarea, Box } from '@chakra-ui/react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { onChangeNode } from '../../../FlowProvider';
|
||||
import { ModuleInputKeyEnum } from '@fastgpt/global/core/module/constants';
|
||||
import { FlowNodeInputItemType } from '@fastgpt/global/core/module/node/type';
|
||||
import { useToast } from '@fastgpt/web/hooks/useToast';
|
||||
import yaml from 'js-yaml';
|
||||
import { useForm } from 'react-hook-form';
|
||||
|
||||
type RequestMethod = 'get' | 'post' | 'put' | 'delete' | 'patch';
|
||||
const methodMap: { [K in RequestMethod]: string } = {
|
||||
get: 'GET',
|
||||
post: 'POST',
|
||||
put: 'PUT',
|
||||
delete: 'DELETE',
|
||||
patch: 'PATCH'
|
||||
};
|
||||
|
||||
const OpenApiImportModal = ({
|
||||
children,
|
||||
moduleId,
|
||||
inputs
|
||||
}: {
|
||||
children: React.ReactElement;
|
||||
moduleId: string;
|
||||
inputs: FlowNodeInputItemType[];
|
||||
}) => {
|
||||
const { isOpen, onOpen, onClose } = useDisclosure();
|
||||
const { t } = useTranslation();
|
||||
const { register, handleSubmit } = useForm({
|
||||
defaultValues: {
|
||||
openapiContent: ''
|
||||
}
|
||||
});
|
||||
|
||||
const { toast } = useToast();
|
||||
|
||||
const handleFileProcessing = async (content: string) => {
|
||||
try {
|
||||
let data;
|
||||
try {
|
||||
data = JSON.parse(content);
|
||||
} catch (jsonError) {
|
||||
try {
|
||||
data = yaml.load(content, { schema: yaml.FAILSAFE_SCHEMA });
|
||||
} catch (yamlError) {
|
||||
console.error(yamlError);
|
||||
throw new Error();
|
||||
}
|
||||
}
|
||||
|
||||
const firstPathName = Object.keys(data.paths)[0];
|
||||
const firstPathData = data.paths[firstPathName];
|
||||
const firstRequestMethod = Object.keys(firstPathData)[0];
|
||||
const firstRequestMethodData = firstPathData[firstRequestMethod];
|
||||
const firstRequestParameters = firstRequestMethodData.parameters || [];
|
||||
|
||||
const pathParams = [];
|
||||
const headerParams = [];
|
||||
for (const parameter of firstRequestParameters) {
|
||||
if (parameter.in === 'path') {
|
||||
pathParams.push({
|
||||
key: parameter.name,
|
||||
type: parameter.schema.type
|
||||
});
|
||||
} else {
|
||||
headerParams.push({
|
||||
key: parameter.name,
|
||||
type: parameter.schema.type
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const requestBodySchema =
|
||||
firstRequestMethodData.requestBody?.content?.['application/json']?.schema;
|
||||
let requestBodyValue = '';
|
||||
if (requestBodySchema) {
|
||||
requestBodyValue = JSON.stringify(requestBodySchema, null, 2);
|
||||
}
|
||||
|
||||
const requestUrl = inputs.find((item) => item.key === ModuleInputKeyEnum.httpReqUrl);
|
||||
const requestMethod = inputs.find((item) => item.key === ModuleInputKeyEnum.httpMethod);
|
||||
const params = inputs.find((item) => item.key === ModuleInputKeyEnum.httpParams);
|
||||
const headers = inputs.find((item) => item.key === ModuleInputKeyEnum.httpHeaders);
|
||||
const jsonBody = inputs.find((item) => item.key === ModuleInputKeyEnum.httpJsonBody);
|
||||
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'updateInput',
|
||||
key: ModuleInputKeyEnum.httpReqUrl,
|
||||
value: {
|
||||
...requestUrl,
|
||||
value: firstPathName
|
||||
}
|
||||
});
|
||||
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'updateInput',
|
||||
key: ModuleInputKeyEnum.httpMethod,
|
||||
value: {
|
||||
...requestMethod,
|
||||
value: methodMap[firstRequestMethod.toLowerCase() as RequestMethod] || 'GET'
|
||||
}
|
||||
});
|
||||
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'updateInput',
|
||||
key: ModuleInputKeyEnum.httpParams,
|
||||
value: {
|
||||
...params,
|
||||
value: pathParams
|
||||
}
|
||||
});
|
||||
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'updateInput',
|
||||
key: ModuleInputKeyEnum.httpHeaders,
|
||||
value: {
|
||||
...headers,
|
||||
value: headerParams
|
||||
}
|
||||
});
|
||||
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'updateInput',
|
||||
key: ModuleInputKeyEnum.httpJsonBody,
|
||||
value: {
|
||||
...jsonBody,
|
||||
value: requestBodyValue
|
||||
}
|
||||
});
|
||||
|
||||
onClose();
|
||||
|
||||
toast({
|
||||
title: t('common.Import success'),
|
||||
status: 'success'
|
||||
});
|
||||
} catch (error: any) {
|
||||
toast({
|
||||
title: t('common.Import failed'),
|
||||
description: error.message,
|
||||
status: 'error'
|
||||
});
|
||||
console.error(error);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{children && <Box onClick={onOpen}>{children}</Box>}
|
||||
<MyModal
|
||||
isOpen={isOpen}
|
||||
onClose={onClose}
|
||||
iconSrc="modal/edit"
|
||||
title={t('common.Import')}
|
||||
m={'auto'}
|
||||
w={500}
|
||||
>
|
||||
<ModalBody>
|
||||
<Textarea
|
||||
height={400}
|
||||
maxH={500}
|
||||
mt={2}
|
||||
{...register('openapiContent')}
|
||||
placeholder={t('core.module.http.OpenAPI import placeholder')}
|
||||
/>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<Button onClick={handleSubmit((data) => handleFileProcessing(data.openapiContent))}>
|
||||
{t('common.Confirm')}
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
</MyModal>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(OpenApiImportModal);
|
||||
@@ -1,11 +1,11 @@
|
||||
import React, { useCallback, useMemo, useState, useTransition } from 'react';
|
||||
import React, { useCallback, useEffect, useMemo, useState, useTransition } from 'react';
|
||||
import { NodeProps } from 'reactflow';
|
||||
import NodeCard from '../render/NodeCard';
|
||||
import NodeCard from '../../render/NodeCard';
|
||||
import { FlowModuleItemType } from '@fastgpt/global/core/module/type.d';
|
||||
import Divider from '../modules/Divider';
|
||||
import Container from '../modules/Container';
|
||||
import RenderInput from '../render/RenderInput';
|
||||
import RenderOutput from '../render/RenderOutput';
|
||||
import Divider from '../../modules/Divider';
|
||||
import Container from '../../modules/Container';
|
||||
import RenderInput from '../../render/RenderInput';
|
||||
import RenderOutput from '../../render/RenderOutput';
|
||||
import {
|
||||
Box,
|
||||
Flex,
|
||||
@@ -16,17 +16,17 @@ import {
|
||||
Tr,
|
||||
Th,
|
||||
Td,
|
||||
TableContainer
|
||||
TableContainer,
|
||||
Button
|
||||
} from '@chakra-ui/react';
|
||||
import MySelect from '@/components/Select';
|
||||
import { ModuleInputKeyEnum } from '@fastgpt/global/core/module/constants';
|
||||
import { onChangeNode, useFlowProviderStore } from '../../FlowProvider';
|
||||
import { onChangeNode, useFlowProviderStore } from '../../../FlowProvider';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import Tabs from '@/components/Tabs';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import { FlowNodeInputItemType } from '@fastgpt/global/core/module/node/type';
|
||||
import { useToast } from '@fastgpt/web/hooks/useToast';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import MyTooltip from '@fastgpt/web/components/common/MyTooltip';
|
||||
import { QuestionOutlineIcon } from '@chakra-ui/icons';
|
||||
import JSONEditor from '@fastgpt/web/components/common/Textarea/JsonEditor';
|
||||
@@ -36,6 +36,9 @@ import {
|
||||
splitGuideModule
|
||||
} from '@fastgpt/global/core/module/utils';
|
||||
import { EditorVariablePickerType } from '@fastgpt/web/components/common/Textarea/PromptEditor/type';
|
||||
import HttpInput from '@fastgpt/web/components/common/Input/HttpInput';
|
||||
import dynamic from 'next/dynamic';
|
||||
const OpenApiImportModal = dynamic(() => import('./OpenApiImportModal'));
|
||||
|
||||
enum TabEnum {
|
||||
params = 'params',
|
||||
@@ -133,11 +136,18 @@ const RenderHttpMethodAndUrl = React.memo(function RenderHttpMethodAndUrl({
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Box mb={2}>{t('core.module.Http request settings')}</Box>
|
||||
<Box mb={2} display={'flex'} justifyContent={'space-between'}>
|
||||
<span>{t('core.module.Http request settings')}</span>
|
||||
<span>
|
||||
<OpenApiImportModal moduleId={moduleId} inputs={inputs}>
|
||||
<Button variant={'link'}>{t('core.module.http.OpenAPI import')}</Button>
|
||||
</OpenApiImportModal>
|
||||
</span>
|
||||
</Box>
|
||||
<Flex alignItems={'center'} className="nodrag">
|
||||
<MySelect
|
||||
h={'34px'}
|
||||
w={'80px'}
|
||||
w={'88px'}
|
||||
bg={'myGray.50'}
|
||||
width={'100%'}
|
||||
value={requestMethods?.value}
|
||||
@@ -149,6 +159,18 @@ const RenderHttpMethodAndUrl = React.memo(function RenderHttpMethodAndUrl({
|
||||
{
|
||||
label: 'POST',
|
||||
value: 'POST'
|
||||
},
|
||||
{
|
||||
label: 'PUT',
|
||||
value: 'PUT'
|
||||
},
|
||||
{
|
||||
label: 'DELETE',
|
||||
value: 'DELETE'
|
||||
},
|
||||
{
|
||||
label: 'PATCH',
|
||||
value: 'PATCH'
|
||||
}
|
||||
]}
|
||||
onchange={(e) => {
|
||||
@@ -178,10 +200,6 @@ const RenderHttpMethodAndUrl = React.memo(function RenderHttpMethodAndUrl({
|
||||
);
|
||||
});
|
||||
|
||||
const defaultForm = {
|
||||
key: '',
|
||||
value: ''
|
||||
};
|
||||
function RenderHttpProps({
|
||||
moduleId,
|
||||
inputs
|
||||
@@ -261,7 +279,7 @@ function RenderHttpProps({
|
||||
<Tabs
|
||||
list={[
|
||||
{ label: <RenderPropsItem text="Params" num={paramsLength} />, id: TabEnum.params },
|
||||
...(requestMethods === 'POST'
|
||||
...(!['GET', 'DELETE'].includes(requestMethods)
|
||||
? [
|
||||
{
|
||||
label: (
|
||||
@@ -303,36 +321,75 @@ const RenderForm = ({
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const { toast } = useToast();
|
||||
const [_, startSts] = useTransition();
|
||||
const { register, reset, handleSubmit } = useForm({
|
||||
defaultValues: defaultForm
|
||||
});
|
||||
|
||||
const list = useMemo(() => (input.value || []) as PropsArrType[], [input.value]);
|
||||
const [list, setList] = useState<PropsArrType[]>(input.value || []);
|
||||
const [updateTrigger, setUpdateTrigger] = useState(false);
|
||||
const [shouldUpdateNode, setShouldUpdateNode] = useState(false);
|
||||
|
||||
const addNewProps = useCallback(
|
||||
({ key, value }: { key: string; value: string }) => {
|
||||
const checkExist = list.find((item) => item.key === key);
|
||||
if (checkExist) {
|
||||
return toast({
|
||||
status: 'warning',
|
||||
title: t('core.module.http.Key already exists')
|
||||
});
|
||||
}
|
||||
if (!key) return;
|
||||
const leftVariables = useMemo(() => {
|
||||
return variables.filter((variable) => {
|
||||
const existVariables = list.map((item) => item.key);
|
||||
return !existVariables.includes(variable.key);
|
||||
});
|
||||
}, [list, variables]);
|
||||
|
||||
useEffect(() => {
|
||||
setList(input.value || []);
|
||||
}, [input.value]);
|
||||
|
||||
useEffect(() => {
|
||||
if (shouldUpdateNode) {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'updateInput',
|
||||
key: input.key,
|
||||
value: {
|
||||
...input,
|
||||
value: [...list, { key, type: 'string', value }]
|
||||
value: list
|
||||
}
|
||||
});
|
||||
reset(defaultForm);
|
||||
},
|
||||
[input, list, moduleId, reset, t, toast]
|
||||
);
|
||||
setShouldUpdateNode(false);
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [list]);
|
||||
|
||||
const handleKeyChange = (index: number, newKey: string) => {
|
||||
setList((prevList) => {
|
||||
if (!newKey) {
|
||||
setUpdateTrigger((prev) => !prev);
|
||||
toast({
|
||||
status: 'warning',
|
||||
title: t('core.module.http.Key cannot be empty')
|
||||
});
|
||||
return prevList;
|
||||
}
|
||||
const checkExist = prevList.find((item, i) => i !== index && item.key == newKey);
|
||||
if (checkExist) {
|
||||
setUpdateTrigger((prev) => !prev);
|
||||
toast({
|
||||
status: 'warning',
|
||||
title: t('core.module.http.Key already exists')
|
||||
});
|
||||
return prevList;
|
||||
}
|
||||
return prevList.map((item, i) => (i === index ? { ...item, key: newKey } : item));
|
||||
});
|
||||
setShouldUpdateNode(true);
|
||||
};
|
||||
|
||||
const handleAddNewProps = (key: string, value: string = '') => {
|
||||
const checkExist = list.find((item) => item.key === key);
|
||||
if (checkExist) {
|
||||
return toast({
|
||||
status: 'warning',
|
||||
title: t('core.module.http.Key already exists')
|
||||
});
|
||||
}
|
||||
if (!key) return;
|
||||
|
||||
setList((prevList) => [...prevList, { key, type: 'string', value }]);
|
||||
setShouldUpdateNode(true);
|
||||
};
|
||||
|
||||
return (
|
||||
<TableContainer>
|
||||
@@ -347,103 +404,69 @@ const RenderForm = ({
|
||||
{list.map((item, index) => (
|
||||
<Tr key={`${input.key}${index}`}>
|
||||
<Td p={0} w={'150px'}>
|
||||
<Input
|
||||
w={'150px'}
|
||||
defaultValue={item.key}
|
||||
variant={'unstyled'}
|
||||
paddingLeft={2}
|
||||
placeholder={t('core.module.http.Props name')}
|
||||
onBlur={(e) => {
|
||||
const val = e.target.value;
|
||||
if (!val) {
|
||||
return toast({
|
||||
status: 'warning',
|
||||
title: t('core.module.http.Key cannot be empty')
|
||||
});
|
||||
}
|
||||
|
||||
const checkExist = list.find((item, i) => i !== index && item.key == val);
|
||||
if (checkExist) {
|
||||
return toast({
|
||||
status: 'warning',
|
||||
title: t('core.module.http.Key already exists')
|
||||
});
|
||||
}
|
||||
|
||||
startSts(() => {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'updateInput',
|
||||
key: input.key,
|
||||
value: {
|
||||
...input,
|
||||
value: list.map((item, i) => (i === index ? { ...item, key: val } : item))
|
||||
}
|
||||
});
|
||||
});
|
||||
<HttpInput
|
||||
hasVariablePlugin={false}
|
||||
hasDropDownPlugin={true}
|
||||
setDropdownValue={(value) => {
|
||||
handleKeyChange(index, value);
|
||||
setUpdateTrigger((prev) => !prev);
|
||||
}}
|
||||
placeholder={t('core.module.http.Props name')}
|
||||
value={item.key}
|
||||
variables={leftVariables}
|
||||
onBlur={(val) => {
|
||||
handleKeyChange(index, val);
|
||||
}}
|
||||
updateTrigger={updateTrigger}
|
||||
/>
|
||||
</Td>
|
||||
<Td p={0} display={'flex'} alignItems={'center'}>
|
||||
<Input
|
||||
flex={'1 0 0'}
|
||||
w={'150px'}
|
||||
defaultValue={item.value}
|
||||
variant={'unstyled'}
|
||||
paddingLeft={2}
|
||||
placeholder={t('core.module.http.Props value')}
|
||||
onBlur={(e) => {
|
||||
const val = e.target.value;
|
||||
startSts(() => {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'updateInput',
|
||||
key: input.key,
|
||||
value: {
|
||||
...input,
|
||||
value: list.map((item, i) =>
|
||||
i === index ? { ...item, value: val } : item
|
||||
)
|
||||
}
|
||||
});
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<MyIcon
|
||||
name={'delete'}
|
||||
cursor={'pointer'}
|
||||
_hover={{ color: 'red.600' }}
|
||||
w={'14px'}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'updateInput',
|
||||
key: input.key,
|
||||
value: {
|
||||
...input,
|
||||
value: list.filter((val) => val.key !== item.key)
|
||||
}
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<Td p={0}>
|
||||
<Box display={'flex'} alignItems={'center'}>
|
||||
<HttpInput
|
||||
placeholder={t('core.module.http.Props value')}
|
||||
value={item.value}
|
||||
variables={variables}
|
||||
onBlur={(val) => {
|
||||
setList((prevList) =>
|
||||
prevList.map((item, i) => (i === index ? { ...item, value: val } : item))
|
||||
);
|
||||
setShouldUpdateNode(true);
|
||||
}}
|
||||
/>
|
||||
<MyIcon
|
||||
name={'delete'}
|
||||
cursor={'pointer'}
|
||||
_hover={{ color: 'red.600' }}
|
||||
w={'14px'}
|
||||
onClick={() => {
|
||||
setList((prevlist) => prevlist.filter((val) => val.key !== item.key));
|
||||
setShouldUpdateNode(true);
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
</Td>
|
||||
</Tr>
|
||||
))}
|
||||
<Tr>
|
||||
<Td p={0} w={'150px'}>
|
||||
<Input
|
||||
w={'150px'}
|
||||
variant={'unstyled'}
|
||||
paddingLeft={2}
|
||||
<HttpInput
|
||||
hasDropDownPlugin={true}
|
||||
setDropdownValue={(val) => {
|
||||
handleAddNewProps(val);
|
||||
}}
|
||||
placeholder={t('core.module.http.Add props')}
|
||||
{...register('key', {
|
||||
onBlur: handleSubmit(addNewProps)
|
||||
})}
|
||||
value={''}
|
||||
h={40}
|
||||
variables={leftVariables}
|
||||
onBlur={(val) => {
|
||||
handleAddNewProps(val);
|
||||
}}
|
||||
/>
|
||||
</Td>
|
||||
<Td p={0}>
|
||||
<Input variant={'unstyled'} paddingLeft={2} {...register('value')} />
|
||||
<Box display={'flex'} alignItems={'center'}>
|
||||
<HttpInput />
|
||||
</Box>
|
||||
</Td>
|
||||
</Tr>
|
||||
</Tbody>
|
||||
@@ -460,7 +483,9 @@ const RenderJson = ({
|
||||
input: FlowNodeInputItemType;
|
||||
variables: EditorVariablePickerType[];
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const [_, startSts] = useTransition();
|
||||
|
||||
return (
|
||||
<Box mt={1}>
|
||||
<JSONEditor
|
||||
@@ -468,6 +493,7 @@ const RenderJson = ({
|
||||
height={200}
|
||||
resize
|
||||
value={input.value}
|
||||
placeholder={t('core.module.template.http body placeholder')}
|
||||
onChange={(e) => {
|
||||
startSts(() => {
|
||||
onChangeNode({
|
||||
@@ -488,7 +514,7 @@ const RenderJson = ({
|
||||
};
|
||||
const RenderPropsItem = ({ text, num }: { text: string; num: number }) => {
|
||||
return (
|
||||
<Flex alignItems={'center'} fontSize={'xs'} transform={'scale(0.8)'}>
|
||||
<Flex alignItems={'center'}>
|
||||
<Box>{text}</Box>
|
||||
{num > 0 && (
|
||||
<Box ml={1} borderRadius={'50%'} bg={'myGray.200'} px={2} py={'1px'}>
|
||||
@@ -500,6 +526,7 @@ const RenderPropsItem = ({ text, num }: { text: string; num: number }) => {
|
||||
};
|
||||
|
||||
const NodeHttp = ({ data, selected }: NodeProps<FlowModuleItemType>) => {
|
||||
const { t } = useTranslation();
|
||||
const { moduleId, inputs, outputs } = data;
|
||||
|
||||
const CustomComponents = useMemo(
|
||||
@@ -511,12 +538,12 @@ const NodeHttp = ({ data, selected }: NodeProps<FlowModuleItemType>) => {
|
||||
<>
|
||||
<RenderHttpProps moduleId={moduleId} inputs={inputs} />
|
||||
<Box mt={2} transform={'translateY(10px)'}>
|
||||
外部参数输入
|
||||
{t('core.module.Variable import')}
|
||||
</Box>
|
||||
</>
|
||||
)
|
||||
}),
|
||||
[inputs, moduleId]
|
||||
[inputs, moduleId, t]
|
||||
);
|
||||
|
||||
return (
|
||||
@@ -93,47 +93,38 @@ const FieldEditModal = ({
|
||||
const { register, getValues, setValue, handleSubmit, watch } = useForm<EditNodeFieldType>({
|
||||
defaultValues: defaultField
|
||||
});
|
||||
const inputType = watch('inputType');
|
||||
const outputType = watch('outputType');
|
||||
const valueType = watch('valueType');
|
||||
const [refresh, setRefresh] = useState(false);
|
||||
|
||||
const showDataTypeSelect = useMemo(() => {
|
||||
if (!editField.dataType) return false;
|
||||
const inputType = getValues('inputType');
|
||||
const outputType = getValues('outputType');
|
||||
|
||||
if (inputType === FlowNodeInputTypeEnum.target) return true;
|
||||
|
||||
if (outputType === FlowNodeOutputTypeEnum.source) return true;
|
||||
|
||||
return false;
|
||||
}, [editField.dataType, getValues, refresh]);
|
||||
}, [editField.dataType, inputType, outputType]);
|
||||
|
||||
const showRequired = useMemo(() => {
|
||||
const inputType = getValues('inputType');
|
||||
const valueType = getValues('valueType');
|
||||
if (inputType === FlowNodeInputTypeEnum.addInputParam) return false;
|
||||
|
||||
return editField.required;
|
||||
}, [editField.required, getValues, refresh]);
|
||||
}, [editField.required, inputType]);
|
||||
|
||||
const showNameInput = useMemo(() => {
|
||||
const inputType = getValues('inputType');
|
||||
|
||||
return editField.name;
|
||||
}, [editField.name, getValues, refresh]);
|
||||
}, [editField.name]);
|
||||
|
||||
const showKeyInput = useMemo(() => {
|
||||
const inputType = getValues('inputType');
|
||||
const valueType = getValues('valueType');
|
||||
if (inputType === FlowNodeInputTypeEnum.addInputParam) return false;
|
||||
|
||||
return editField.key;
|
||||
}, [editField.key, getValues, refresh]);
|
||||
}, [editField.key, inputType]);
|
||||
|
||||
const showDescriptionInput = useMemo(() => {
|
||||
const inputType = getValues('inputType');
|
||||
|
||||
return editField.description;
|
||||
}, [editField.description, getValues, refresh]);
|
||||
}, [editField.description]);
|
||||
|
||||
return (
|
||||
<MyModal
|
||||
@@ -209,7 +200,18 @@ const FieldEditModal = ({
|
||||
{showKeyInput && (
|
||||
<Flex mb={5} alignItems={'center'}>
|
||||
<Box flex={'0 0 70px'}>{t('core.module.Field key')}</Box>
|
||||
<Input placeholder="appointment/sql" {...register('key', { required: true })} />
|
||||
<Input
|
||||
placeholder="appointment/sql"
|
||||
{...register('key', {
|
||||
required: true,
|
||||
onChange: (e) => {
|
||||
const value = e.target.value;
|
||||
if (!showNameInput) {
|
||||
setValue('label', value);
|
||||
}
|
||||
}
|
||||
})}
|
||||
/>
|
||||
</Flex>
|
||||
)}
|
||||
{showDescriptionInput && (
|
||||
|
||||
@@ -46,11 +46,7 @@ const RenderList: {
|
||||
Component: dynamic(() => import('./templates/AiSetting'))
|
||||
},
|
||||
{
|
||||
types: [
|
||||
FlowNodeInputTypeEnum.selectChatModel,
|
||||
FlowNodeInputTypeEnum.selectCQModel,
|
||||
FlowNodeInputTypeEnum.selectExtractModel
|
||||
],
|
||||
types: [FlowNodeInputTypeEnum.selectLLMModel],
|
||||
Component: dynamic(() => import('./templates/SelectAiModel'))
|
||||
},
|
||||
{
|
||||
|
||||
@@ -55,7 +55,7 @@ const JsonEditor = ({ inputs = [], item, moduleId }: RenderInputProps) => {
|
||||
return (
|
||||
<JSONEditor
|
||||
bg={'myGray.50'}
|
||||
placeholder={t(item.placeholder || '')}
|
||||
placeholder={item.placeholder}
|
||||
resize
|
||||
value={value}
|
||||
onChange={(e) => {
|
||||
|
||||
@@ -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.cfr]: NodeSimple
|
||||
[FlowNodeTypeEnum.queryExtension]: NodeSimple
|
||||
};
|
||||
const edgeTypes = {
|
||||
[EDGE_TYPE]: ButtonEdge
|
||||
|
||||
@@ -46,7 +46,7 @@ type EditProps = EditApiKeyProps & { _id?: string };
|
||||
const defaultEditData: EditProps = {
|
||||
name: '',
|
||||
limit: {
|
||||
credit: -1
|
||||
maxUsagePoints: -1
|
||||
}
|
||||
};
|
||||
|
||||
@@ -139,10 +139,9 @@ const ApiKeyTable = ({ tips, appId }: { tips: string; appId?: string }) => {
|
||||
<Tr>
|
||||
<Th>{t('Name')}</Th>
|
||||
<Th>Api Key</Th>
|
||||
<Th>{t('support.openapi.Usage')}</Th>
|
||||
<Th>{t('support.outlink.Usage points')}</Th>
|
||||
{feConfigs?.isPlus && (
|
||||
<>
|
||||
<Th>{t('support.openapi.Max usage')}</Th>
|
||||
<Th>{t('common.Expired Time')}</Th>
|
||||
</>
|
||||
)}
|
||||
@@ -153,18 +152,18 @@ const ApiKeyTable = ({ tips, appId }: { tips: string; appId?: string }) => {
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody fontSize={'sm'}>
|
||||
{apiKeys.map(({ _id, name, usage, limit, apiKey, createTime, lastUsedTime }) => (
|
||||
{apiKeys.map(({ _id, name, usagePoints, limit, apiKey, createTime, lastUsedTime }) => (
|
||||
<Tr key={_id}>
|
||||
<Td>{name}</Td>
|
||||
<Td>{apiKey}</Td>
|
||||
<Td>{usage}</Td>
|
||||
<Td>
|
||||
{Math.round(usagePoints)}/
|
||||
{feConfigs?.isPlus && limit?.maxUsagePoints && limit?.maxUsagePoints > -1
|
||||
? `${limit?.maxUsagePoints}`
|
||||
: t('common.Unlimited')}
|
||||
</Td>
|
||||
{feConfigs?.isPlus && (
|
||||
<>
|
||||
<Td>
|
||||
{limit?.credit && limit?.credit > -1
|
||||
? `${limit?.credit}`
|
||||
: t('common.Unlimited')}
|
||||
</Td>
|
||||
<Td whiteSpace={'pre-wrap'}>
|
||||
{limit?.expiredTime
|
||||
? dayjs(limit?.expiredTime).format('YYYY/MM/DD\nHH:mm')
|
||||
@@ -334,15 +333,15 @@ function EditKeyModal({
|
||||
<>
|
||||
<Flex alignItems={'center'} mt={4}>
|
||||
<Flex flex={'0 0 90px'} alignItems={'center'}>
|
||||
{t('common.Max credit')}:
|
||||
<MyTooltip label={t('common.Max credit tips' || '')}>
|
||||
{t('support.outlink.Max usage points')}:
|
||||
<MyTooltip label={t('support.outlink.Max usage points tip')}>
|
||||
<QuestionOutlineIcon ml={1} />
|
||||
</MyTooltip>
|
||||
</Flex>
|
||||
<Input
|
||||
{...register('limit.credit', {
|
||||
{...register('limit.maxUsagePoints', {
|
||||
min: -1,
|
||||
max: 1000,
|
||||
max: 10000000,
|
||||
valueAsNumber: true,
|
||||
required: true
|
||||
})}
|
||||
|
||||
@@ -2,13 +2,16 @@ 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
|
||||
delLeaveTeam,
|
||||
getTeamsTags,
|
||||
insertTeamsTags
|
||||
} from '@/web/support/user/team/api';
|
||||
import {
|
||||
Box,
|
||||
@@ -22,7 +25,6 @@ import {
|
||||
Th,
|
||||
Td,
|
||||
TableContainer,
|
||||
useTheme,
|
||||
useDisclosure,
|
||||
MenuButton
|
||||
} from '@chakra-ui/react';
|
||||
@@ -43,15 +45,19 @@ import { FormDataType, defaultForm } from './EditModal';
|
||||
import MyMenu from '@/components/MyMenu';
|
||||
import { useConfirm } from '@/web/common/hooks/useConfirm';
|
||||
import { useToast } from '@fastgpt/web/hooks/useToast';
|
||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||
|
||||
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 { teamPlanStatus } = useUserStore();
|
||||
const { feConfigs } = useSystemStore();
|
||||
const [teamsTags, setTeamTags] = useState<any>();
|
||||
|
||||
const { ConfirmModal: ConfirmRemoveMemberModal, openConfirm: openRemoveMember } = useConfirm();
|
||||
const { ConfirmModal: ConfirmLeaveTeamModal, openConfirm: openLeaveConfirm } = useConfirm({
|
||||
@@ -61,6 +67,11 @@ 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 = [],
|
||||
@@ -76,6 +87,8 @@ 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')
|
||||
@@ -86,6 +99,11 @@ 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);
|
||||
}
|
||||
);
|
||||
@@ -108,7 +126,9 @@ 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() {
|
||||
@@ -184,6 +204,7 @@ const TeamManageModal = ({ onClose }: { onClose: () => void }) => {
|
||||
bg: 'myGray.100'
|
||||
}
|
||||
})}
|
||||
onClick={() => onSwitchTeam(team.teamId)}
|
||||
>
|
||||
<Avatar src={team.avatar} w={['18px', '22px']} />
|
||||
<Box
|
||||
@@ -196,6 +217,17 @@ 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'} />
|
||||
@@ -229,7 +261,7 @@ const TeamManageModal = ({ onClose }: { onClose: () => void }) => {
|
||||
borderBottomColor={'myGray.100'}
|
||||
mb={3}
|
||||
>
|
||||
<Box fontSize={['lg', 'xl']} fontWeight={'bold'}>
|
||||
<Box fontSize={['lg', 'xl']} fontWeight={'bold'} alignItems={'center'}>
|
||||
{userInfo.team.teamName}
|
||||
</Box>
|
||||
{userInfo.team.role === TeamMemberRoleEnum.owner && (
|
||||
@@ -258,25 +290,50 @@ const TeamManageModal = ({ onClose }: { onClose: () => void }) => {
|
||||
<Box ml={2} bg={'myGray.100'} borderRadius={'20px'} px={3} fontSize={'xs'}>
|
||||
{members.length}
|
||||
</Box>
|
||||
{userInfo.team.role === TeamMemberRoleEnum.owner && (
|
||||
{userInfo.team.role === TeamMemberRoleEnum.owner &&
|
||||
teamPlanStatus?.standardConstants &&
|
||||
teamPlanStatus.standardConstants.maxTeamMember > members.length && (
|
||||
<Button
|
||||
variant={'whitePrimary'}
|
||||
size="sm"
|
||||
borderRadius={'md'}
|
||||
ml={3}
|
||||
leftIcon={
|
||||
<MyIcon name={'common/inviteLight'} w={'14px'} color={'primary.500'} />
|
||||
}
|
||||
onClick={() => {
|
||||
if (userInfo.team.maxSize <= members.length) {
|
||||
toast({
|
||||
status: 'warning',
|
||||
title: t('user.team.Over Max Member Tip', { max: userInfo.team.maxSize })
|
||||
});
|
||||
} else {
|
||||
onOpenInvite();
|
||||
}
|
||||
}}
|
||||
>
|
||||
{t('user.team.Invite Member')}
|
||||
</Button>
|
||||
)}
|
||||
{userInfo.team.role === TeamMemberRoleEnum.owner && feConfigs?.show_team_chat && (
|
||||
<Button
|
||||
variant={'whitePrimary'}
|
||||
size="sm"
|
||||
borderRadius={'md'}
|
||||
ml={3}
|
||||
leftIcon={<MyIcon name={'common/inviteLight'} w={'14px'} color={'primary.500'} />}
|
||||
leftIcon={<DragHandleIcon w={'14px'} color={'primary.500'} />}
|
||||
onClick={() => {
|
||||
if (userInfo.team.maxSize <= members.length) {
|
||||
toast({
|
||||
status: 'warning',
|
||||
title: t('user.team.Over Max Member Tip', { max: userInfo.team.maxSize })
|
||||
title: t('user.team.Team Tags Async', { max: userInfo.team.maxSize })
|
||||
});
|
||||
} else {
|
||||
onOpenInvite();
|
||||
onOpenTeamTagsAsync();
|
||||
}
|
||||
}}
|
||||
>
|
||||
{t('user.team.Invite Member')}
|
||||
{t('user.team.Team Tags Async')}
|
||||
</Button>
|
||||
)}
|
||||
<Box flex={1} />
|
||||
@@ -435,6 +492,13 @@ const TeamManageModal = ({ onClose }: { onClose: () => void }) => {
|
||||
onSuccess={refetchMembers}
|
||||
/>
|
||||
)}
|
||||
{isOpenTeamTagsAsync && (
|
||||
<TeamTagsAsync
|
||||
teamInfo={teamsTags?.tagsUrl}
|
||||
teamsTags={teamsTags?.list || []}
|
||||
onClose={onCloseTeamTagsAsync}
|
||||
/>
|
||||
)}
|
||||
<ConfirmRemoveMemberModal />
|
||||
<ConfirmLeaveTeamModal />
|
||||
</>
|
||||
|
||||
@@ -0,0 +1,182 @@
|
||||
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?shareTeamId=${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);
|
||||
@@ -1,97 +0,0 @@
|
||||
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;
|
||||
@@ -0,0 +1,84 @@
|
||||
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);
|
||||
@@ -0,0 +1,116 @@
|
||||
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;
|
||||
@@ -1,240 +0,0 @@
|
||||
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;
|
||||
@@ -15,7 +15,8 @@ export const defaultApp: AppDetailType = {
|
||||
tmbId: '',
|
||||
permission: 'private',
|
||||
isOwner: false,
|
||||
canWrite: false
|
||||
canWrite: false,
|
||||
teamTags: ['']
|
||||
};
|
||||
|
||||
export const defaultOutLinkForm: OutLinkEditType = {
|
||||
@@ -23,7 +24,7 @@ export const defaultOutLinkForm: OutLinkEditType = {
|
||||
responseDetail: false,
|
||||
limit: {
|
||||
QPM: 100,
|
||||
credit: -1
|
||||
maxUsagePoints: -1
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
7
projects/app/src/global/core/chat/api.d.ts
vendored
7
projects/app/src/global/core/chat/api.d.ts
vendored
@@ -14,6 +14,12 @@ export type InitChatProps = {
|
||||
chatId?: string;
|
||||
loadCustomFeedbacks?: boolean;
|
||||
};
|
||||
/* ---------- chat ----------- */
|
||||
export type chatByTeamProps = {
|
||||
teamId?: string;
|
||||
appId?: string;
|
||||
outLinkUid?: string;
|
||||
};
|
||||
export type InitOutLinkChatProps = {
|
||||
chatId?: string;
|
||||
shareId?: string;
|
||||
@@ -39,6 +45,7 @@ export type InitChatResponse = {
|
||||
/* ---------- history ----------- */
|
||||
export type getHistoriesProps = {
|
||||
appId?: string;
|
||||
authToken?: string;
|
||||
// share chat
|
||||
shareId?: string;
|
||||
outLinkUid?: string; // authToken/uid
|
||||
|
||||
@@ -4,42 +4,34 @@ export const Prompt_QuoteTemplateList: PromptTemplateItem[] = [
|
||||
{
|
||||
title: '标准模板',
|
||||
desc: '标准提示词,用于结构不固定的知识库。',
|
||||
value: `<data>
|
||||
{{q}}
|
||||
{{a}}
|
||||
</data>`
|
||||
value: `{{q}}
|
||||
{{a}}`
|
||||
},
|
||||
{
|
||||
title: '问答模板',
|
||||
desc: '适合 QA 问答结构的知识库,可以让AI较为严格的按预设内容回答',
|
||||
value: `<QA>
|
||||
<问题>
|
||||
value: `<Question>
|
||||
{{q}}
|
||||
</问题>
|
||||
<答案>
|
||||
</Question>
|
||||
<Answer>
|
||||
{{a}}
|
||||
</答案>
|
||||
</QA>`
|
||||
</Answer>`
|
||||
},
|
||||
{
|
||||
title: '标准严格模板',
|
||||
desc: '在标准模板基础上,对模型的回答做更严格的要求。',
|
||||
value: `<data>
|
||||
{{q}}
|
||||
{{a}}
|
||||
</data>`
|
||||
value: `{{q}}
|
||||
{{a}}`
|
||||
},
|
||||
{
|
||||
title: '严格问答模板',
|
||||
desc: '在问答模板基础上,对模型的回答做更严格的要求。',
|
||||
value: `<QA>
|
||||
<问题>
|
||||
value: `<Question>
|
||||
{{q}}
|
||||
</问题>
|
||||
<答案>
|
||||
</Question>
|
||||
<Answer>
|
||||
{{a}}
|
||||
</答案>
|
||||
</QA>`
|
||||
</Answer>`
|
||||
}
|
||||
];
|
||||
|
||||
@@ -47,14 +39,16 @@ 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 语法优化回答格式。
|
||||
- 使用与问题相同的语言回答。
|
||||
|
||||
@@ -65,7 +59,9 @@ export const Prompt_QuotePromptList: PromptTemplateItem[] = [
|
||||
desc: '',
|
||||
value: `使用 <QA></QA> 标记中的问答对进行回答。
|
||||
|
||||
<QA>
|
||||
{{quote}}
|
||||
</QA>
|
||||
|
||||
回答要求:
|
||||
- 选择其中一个或多个问答对进行回答。
|
||||
@@ -78,18 +74,20 @@ 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 语法优化回答格式。
|
||||
- 使用与问题相同的语言回答。
|
||||
|
||||
@@ -100,7 +98,9 @@ export const Prompt_QuotePromptList: PromptTemplateItem[] = [
|
||||
desc: '',
|
||||
value: `忘记你已有的知识,仅使用 <QA></QA> 标记中的问答对进行回答。
|
||||
|
||||
<QA>
|
||||
{{quote}}
|
||||
</QA>}
|
||||
|
||||
思考流程:
|
||||
1. 判断问题是否与 <QA></QA> 标记中的内容有关。
|
||||
|
||||
@@ -2,6 +2,7 @@ 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();
|
||||
@@ -24,11 +25,12 @@ function Error() {
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<p>
|
||||
部分系统不兼容,导致页面崩溃。如果可以,请联系作者,反馈下具体操作和页面。 大部分是 苹果 的
|
||||
safari 浏览器导致,可以尝试更换 chrome
|
||||
浏览器。或者是因为开了中文翻译导致,请检查并关闭中文翻译。
|
||||
</p>
|
||||
<Box whiteSpace={'pre-wrap'}>
|
||||
{`出现未捕获的异常。
|
||||
1. 私有部署用户,90%由于配置文件不正确导致。
|
||||
2. 部分系统不兼容相关API。大部分是苹果的safari 浏览器导致,可以尝试更换 chrome。
|
||||
3. 请关闭浏览器翻译功能,部分翻译导致页面崩溃。`}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,160 +0,0 @@
|
||||
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;
|
||||
@@ -1,5 +1,6 @@
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
import React, { useState, useCallback, useMemo, useEffect } from 'react';
|
||||
import {
|
||||
Button,
|
||||
Table,
|
||||
Thead,
|
||||
Tbody,
|
||||
@@ -9,43 +10,38 @@ import {
|
||||
TableContainer,
|
||||
Flex,
|
||||
Box,
|
||||
Button
|
||||
ModalBody
|
||||
} from '@chakra-ui/react';
|
||||
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 { getBills, checkBalancePayResult } from '@/web/support/wallet/bill/api';
|
||||
import type { BillSchemaType } from '@fastgpt/global/support/wallet/bill/type.d';
|
||||
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 { 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'));
|
||||
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';
|
||||
|
||||
const BillTable = () => {
|
||||
const { t } = useTranslation();
|
||||
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 { toast } = useToast();
|
||||
const [billType, setBillType] = useState<`${BillTypeEnum}` | ''>('');
|
||||
const [billDetail, setBillDetail] = useState<BillSchemaType>();
|
||||
|
||||
const sourceList = useMemo(
|
||||
const billTypeList = useMemo(
|
||||
() => [
|
||||
{ label: t('common.All'), value: '' },
|
||||
...Object.entries(BillSourceMap).map(([key, value]) => ({
|
||||
...Object.entries(billTypeMap).map(([key, value]) => ({
|
||||
label: t(value.label),
|
||||
value: key
|
||||
}))
|
||||
@@ -53,134 +49,199 @@ 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
|
||||
} = usePagination<BillItemType>({
|
||||
api: getUserBills,
|
||||
pageSize: isPc ? 20 : 10,
|
||||
getData,
|
||||
total
|
||||
} = usePagination<BillSchemaType>({
|
||||
api: getBills,
|
||||
pageSize: 20,
|
||||
params: {
|
||||
dateStart: dateRange.from || new Date(),
|
||||
dateEnd: addDays(dateRange.to || new Date(), 1),
|
||||
source: billSource,
|
||||
teamMemberId: selectTmbId
|
||||
type: billType
|
||||
},
|
||||
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);
|
||||
}, [billSource, selectTmbId]);
|
||||
}, [billType]);
|
||||
|
||||
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'}>
|
||||
<MyBox
|
||||
isLoading={isLoading || isRefreshing}
|
||||
position={'relative'}
|
||||
h={'100%'}
|
||||
overflow={'overlay'}
|
||||
py={[0, 5]}
|
||||
px={[3, 8]}
|
||||
>
|
||||
<TableContainer>
|
||||
<Table>
|
||||
<Thead>
|
||||
<Tr>
|
||||
{/* <Th>{t('user.team.Member Name')}</Th> */}
|
||||
<Th>{t('user.Time')}</Th>
|
||||
<Th>#</Th>
|
||||
<Th>
|
||||
<MySelect
|
||||
list={sourceList}
|
||||
value={billSource}
|
||||
list={billTypeList}
|
||||
value={billType}
|
||||
size={'sm'}
|
||||
onchange={(e) => {
|
||||
setBillSource(e);
|
||||
setBillType(e);
|
||||
}}
|
||||
w={'130px'}
|
||||
></MySelect>
|
||||
</Th>
|
||||
<Th>{t('user.Application Name')}</Th>
|
||||
<Th>{t('user.Total Amount')}</Th>
|
||||
<Th>{t('user.Time')}</Th>
|
||||
<Th>{t('support.wallet.Amount')}</Th>
|
||||
<Th>{t('support.wallet.bill.Status')}</Th>
|
||||
<Th></Th>
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody fontSize={'sm'}>
|
||||
{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>
|
||||
{bills.map((item, i) => (
|
||||
<Tr key={item._id}>
|
||||
<Td>{i + 1}</Td>
|
||||
<Td>{t(billTypeMap[item.type]?.label)}</Td>
|
||||
<Td>
|
||||
<Button size={'sm'} variant={'whitePrimary'} onClick={() => setBillDetail(item)}>
|
||||
详情
|
||||
{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>
|
||||
</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>
|
||||
|
||||
{!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>
|
||||
{!!billDetail && (
|
||||
<BillDetailModal bill={billDetail} onClose={() => setBillDetail(undefined)} />
|
||||
)}
|
||||
|
||||
<Loading loading={isLoading} fixed={false} />
|
||||
{!!billDetail && <BillDetail bill={billDetail} onClose={() => setBillDetail(undefined)} />}
|
||||
</Flex>
|
||||
</MyBox>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(BillTable);
|
||||
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?.month !== undefined && (
|
||||
<Flex alignItems={'center'} pb={4}>
|
||||
<Box flex={'0 0 120px'}>{t('support.wallet.subscription.Month amount')}:</Box>
|
||||
<Box>{bill.metadata?.month}</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>
|
||||
);
|
||||
}
|
||||
|
||||
92
projects/app/src/pages/account/components/Individuation.tsx
Normal file
92
projects/app/src/pages/account/components/Individuation.tsx
Normal file
@@ -0,0 +1,92 @@
|
||||
import { Box, Card, Flex, Select } from '@chakra-ui/react';
|
||||
import React, { useCallback, useRef } from 'react';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { timezoneList } from '@fastgpt/global/common/time/timezone';
|
||||
import { useUserStore } from '@/web/support/user/useUserStore';
|
||||
import { UserType } from '@fastgpt/global/support/user/type';
|
||||
import { useToast } from '@fastgpt/web/hooks/useToast';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { UserUpdateParams } from '@/types/user';
|
||||
import { langMap, setLngStore } from '@/web/common/utils/i18n';
|
||||
import MySelect from '@/components/Select';
|
||||
import { useRouter } from 'next/router';
|
||||
|
||||
const Individuation = () => {
|
||||
const { t, i18n } = useTranslation();
|
||||
const timezones = useRef(timezoneList());
|
||||
const { userInfo, updateUserInfo, initUserInfo } = useUserStore();
|
||||
const { toast } = useToast();
|
||||
const router = useRouter();
|
||||
|
||||
const { reset } = useForm<UserUpdateParams>({
|
||||
defaultValues: userInfo as UserType
|
||||
});
|
||||
|
||||
const onclickSave = useCallback(
|
||||
async (data: UserType) => {
|
||||
await updateUserInfo({
|
||||
timezone: data.timezone
|
||||
});
|
||||
reset(data);
|
||||
toast({
|
||||
title: t('dataset.data.Update Success Tip'),
|
||||
status: 'success'
|
||||
});
|
||||
},
|
||||
[reset, t, toast, updateUserInfo]
|
||||
);
|
||||
|
||||
return (
|
||||
<Box py={[3, '28px']} px={['5vw', '64px']}>
|
||||
<Flex alignItems={'center'} fontSize={'xl'} h={'30px'}>
|
||||
<MyIcon mr={2} name={'support/user/individuation'} w={'20px'} />
|
||||
{t('support.account.Individuation')}
|
||||
</Flex>
|
||||
|
||||
<Card mt={6} px={[3, 10]} py={[3, 7]}>
|
||||
<Flex alignItems={'center'} w={['85%', '350px']}>
|
||||
<Box flex={'0 0 80px'}>{t('user.Language')}: </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(
|
||||
{
|
||||
query: router.query
|
||||
},
|
||||
router.asPath,
|
||||
{ locale: lang }
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
</Flex>
|
||||
<Flex mt={6} alignItems={'center'} w={['85%', '350px']}>
|
||||
<Box flex={'0 0 80px'}>{t('user.Timezone')}: </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>
|
||||
</Card>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default Individuation;
|
||||
@@ -1,15 +1,14 @@
|
||||
import React, { useCallback, useMemo, useRef } from 'react';
|
||||
import React, { useCallback, useMemo } from 'react';
|
||||
import {
|
||||
Box,
|
||||
Flex,
|
||||
Button,
|
||||
useDisclosure,
|
||||
useTheme,
|
||||
Divider,
|
||||
Select,
|
||||
Input,
|
||||
Link,
|
||||
Progress
|
||||
Progress,
|
||||
Grid
|
||||
} from '@chakra-ui/react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { UserUpdateParams } from '@/types/user';
|
||||
@@ -22,35 +21,72 @@ 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/bill/tools';
|
||||
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 { 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 { formatTime2YMD } 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 UserInfo = () => {
|
||||
const Account = () => {
|
||||
const { isPc } = useSystemStore();
|
||||
const { teamPlanStatus } = useUserStore();
|
||||
const standardPlan = teamPlanStatus?.standardConstants;
|
||||
|
||||
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>
|
||||
{!!standardPlan && (
|
||||
<Box ml={'45px'} flex={'1 0 0'} maxW={'600px'}>
|
||||
<PlanUsage />
|
||||
</Box>
|
||||
)}
|
||||
</Flex>
|
||||
) : (
|
||||
<>
|
||||
<MyInfo />
|
||||
{!!standardPlan && <PlanUsage />}
|
||||
<Other />
|
||||
</>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(Account);
|
||||
|
||||
const MyInfo = () => {
|
||||
const theme = useTheme();
|
||||
const router = useRouter();
|
||||
const { feConfigs, systemVersion } = useSystemStore();
|
||||
const { t, i18n } = useTranslation();
|
||||
const { userInfo, updateUserInfo, initUserInfo } = useUserStore();
|
||||
const timezones = useRef(timezoneList());
|
||||
const { feConfigs } = useSystemStore();
|
||||
const { t } = useTranslation();
|
||||
const { userInfo, updateUserInfo } = useUserStore();
|
||||
const { reset } = useForm<UserUpdateParams>({
|
||||
defaultValues: userInfo as UserType
|
||||
});
|
||||
const { isPc } = useSystemStore();
|
||||
|
||||
const { toast } = useToast();
|
||||
const {
|
||||
@@ -63,13 +99,6 @@ const UserInfo = () => {
|
||||
onClose: onCloseUpdatePsw,
|
||||
onOpen: onOpenUpdatePsw
|
||||
} = useDisclosure();
|
||||
const { isOpen: isOpenOpenai, onClose: onCloseOpenai, onOpen: onOpenOpenai } = useDisclosure();
|
||||
const {
|
||||
isOpen: isOpenSubDatasetModal,
|
||||
onClose: onCloseSubDatasetModal,
|
||||
onOpen: onOpenSubDatasetModal
|
||||
} = useDisclosure();
|
||||
|
||||
const { File, onOpen: onOpenSelectFile } = useSelectFile({
|
||||
fileType: '.jpg,.png',
|
||||
multiple: false
|
||||
@@ -117,81 +146,73 @@ const UserInfo = () => {
|
||||
[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
|
||||
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')}
|
||||
<Box>
|
||||
{/* user info */}
|
||||
{isPc && (
|
||||
<Flex alignItems={'center'} fontSize={'xl'} h={'30px'}>
|
||||
<MyIcon mr={2} name={'support/user/userLight'} w={'20px'} />
|
||||
{t('support.user.User self info')}
|
||||
</Flex>
|
||||
</Flex>
|
||||
<Box
|
||||
display={['flex', 'block']}
|
||||
flexDirection={'column'}
|
||||
alignItems={'center'}
|
||||
ml={[0, 10]}
|
||||
mt={[6, 0]}
|
||||
>
|
||||
)}
|
||||
|
||||
<Box mt={[0, 6]}>
|
||||
{isPc ? (
|
||||
<Flex alignItems={'center'} cursor={'pointer'}>
|
||||
<Box flex={'0 0 80px'}>{t('support.user.Avatar')}: </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}
|
||||
>
|
||||
<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>
|
||||
)}
|
||||
{feConfigs.isPlus && (
|
||||
<Flex mb={4} alignItems={'center'} w={['85%', '300px']}>
|
||||
<Flex mt={[0, 4]} alignItems={'center'}>
|
||||
<Box flex={'0 0 80px'}>{t('user.Member Name')}: </Box>
|
||||
<Input
|
||||
flex={1}
|
||||
flex={'1 0 0'}
|
||||
defaultValue={userInfo?.team?.memberName || 'Member'}
|
||||
title={t('user.Edit name')}
|
||||
borderColor={'transparent'}
|
||||
pl={'10px'}
|
||||
transform={'translateX(-11px)'}
|
||||
maxLength={20}
|
||||
onBlur={(e) => {
|
||||
@@ -204,109 +225,269 @@ const UserInfo = () => {
|
||||
/>
|
||||
</Flex>
|
||||
)}
|
||||
<Flex alignItems={'center'} w={['85%', '300px']}>
|
||||
<Flex alignItems={'center'} mt={6}>
|
||||
<Box flex={'0 0 80px'}>{t('user.Account')}: </Box>
|
||||
<Box flex={1}>{userInfo?.username}</Box>
|
||||
</Flex>
|
||||
<Flex mt={6} alignItems={'center'} w={['85%', '300px']}>
|
||||
{feConfigs.isPlus && (
|
||||
<Flex mt={6} alignItems={'center'}>
|
||||
<Box flex={'0 0 80px'}>{t('user.Password')}: </Box>
|
||||
<Box flex={1}>*****</Box>
|
||||
<Button size={'sm'} variant={'whitePrimary'} onClick={onOpenUpdatePsw}>
|
||||
{t('user.Change')}
|
||||
</Button>
|
||||
</Flex>
|
||||
)}
|
||||
<Flex mt={6} alignItems={'center'}>
|
||||
<Box flex={'0 0 80px'}>{t('user.Team')}: </Box>
|
||||
<Box flex={1}>
|
||||
<TeamMenu />
|
||||
</Box>
|
||||
</Flex>
|
||||
<Flex mt={6} alignItems={'center'} w={['85%', '300px']}>
|
||||
<Box flex={'0 0 80px'}>{t('user.Language')}: </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 });
|
||||
}}
|
||||
/>
|
||||
{feConfigs.isPlus && (
|
||||
<Box mt={6} whiteSpace={'nowrap'}>
|
||||
<Flex alignItems={'center'}>
|
||||
<Box flex={'0 0 80px'} fontSize={'md'}>
|
||||
{t('user.team.Balance')}:
|
||||
</Box>
|
||||
<Box flex={1}>
|
||||
<strong>{formatStorePrice2Read(userInfo?.team?.balance).toFixed(3)}</strong> 元
|
||||
</Box>
|
||||
{feConfigs?.show_pay && userInfo?.team?.canWrite && (
|
||||
<Button variant={'whitePrimary'} 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 {
|
||||
isOpen: isOpenStandardModal,
|
||||
onClose: onCloseStandardModal,
|
||||
onOpen: onOpenStandardModal
|
||||
} = useDisclosure();
|
||||
|
||||
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,
|
||||
max: teamPlanStatus.totalPoints ? teamPlanStatus.totalPoints : t('common.Unlimited'),
|
||||
used: teamPlanStatus.usedPoints ? Math.round(teamPlanStatus.usedPoints) : 0
|
||||
};
|
||||
}, [teamPlanStatus, t]);
|
||||
|
||||
return standardPlan ? (
|
||||
<Box mt={[6, 0]}>
|
||||
<Flex fontSize={'xl'} h={'30px'}>
|
||||
<Flex alignItems={'center'}>
|
||||
<MyIcon mr={2} name={'support/account/plans'} w={'20px'} />
|
||||
{t('support.wallet.subscription.Team plan and usage')}
|
||||
</Flex>
|
||||
<Flex mt={6} alignItems={'center'} w={['85%', '300px']}>
|
||||
<Box flex={'0 0 80px'}>{t('user.Timezone')}: </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')}: </Box>
|
||||
<Box flex={1}>*****</Box>
|
||||
<Button size={['sm', 'md']} variant={'whitePrimary'} ml={5} onClick={onOpenUpdatePsw}>
|
||||
{t('user.Change')}
|
||||
<Button ml={4} size={'sm'} onClick={() => router.push(AI_POINT_USAGE_CARD_ROUTE)}>
|
||||
{t('support.user.Price')}
|
||||
</Button>
|
||||
<Button ml={4} variant={'whitePrimary'} size={'sm'} onClick={onOpenStandardModal}>
|
||||
{t('support.wallet.Standard Plan Detail')}
|
||||
</Button>
|
||||
</Flex>
|
||||
<Box
|
||||
mt={[3, 6]}
|
||||
bg={'white'}
|
||||
borderWidth={'1px'}
|
||||
borderColor={'borderColor.low'}
|
||||
borderRadius={'md'}
|
||||
>
|
||||
<Flex px={[5, 10]} py={[3, 6]}>
|
||||
<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}>{formatTime2YMD(standardPlan?.expiredTime)}</Box>
|
||||
</Flex>
|
||||
</Box>
|
||||
<Button onClick={() => router.push('/price')}>
|
||||
{t('support.wallet.subscription.Upgrade plan')}
|
||||
</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')}:
|
||||
</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')}: {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 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}
|
||||
</Box>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</Flex>
|
||||
</Flex>
|
||||
<Box mt={3}>
|
||||
<Progress
|
||||
size={'sm'}
|
||||
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}>
|
||||
{aiPointsUsageMap.used}/{aiPointsUsageMap.max}
|
||||
</Box>
|
||||
</Flex>
|
||||
</Flex>
|
||||
<Box mt={3}>
|
||||
<Progress
|
||||
size={'sm'}
|
||||
value={aiPointsUsageMap.value}
|
||||
colorScheme={aiPointsUsageMap.colorScheme}
|
||||
borderRadius={'md'}
|
||||
isAnimated
|
||||
hasStripe
|
||||
borderWidth={'1px'}
|
||||
borderColor={'borderColor.low'}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
<Flex></Flex>
|
||||
</Box>
|
||||
{isOpenStandardModal && <StandDetailModal onClose={onCloseStandardModal} />}
|
||||
</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}
|
||||
@@ -326,64 +507,53 @@ const UserInfo = () => {
|
||||
</Box>
|
||||
</Link>
|
||||
)}
|
||||
{feConfigs?.chatbotUrl && (
|
||||
<Link
|
||||
href={feConfigs.chatbotUrl}
|
||||
target="_blank"
|
||||
display={'flex'}
|
||||
mt={4}
|
||||
w={['85%', '300px']}
|
||||
py={3}
|
||||
<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}
|
||||
px={6}
|
||||
border={theme.borders.sm}
|
||||
borderWidth={'1.5px'}
|
||||
borderRadius={'md'}
|
||||
alignItems={'center'}
|
||||
cursor={'pointer'}
|
||||
userSelect={'none'}
|
||||
textDecoration={'none !important'}
|
||||
onClick={onOpenOpenai}
|
||||
>
|
||||
<MyIcon name={'core/app/aiLight'} w={'18px'} />
|
||||
<MyIcon name={'common/openai'} w={'18px'} color={'myGray.600'} />
|
||||
<Box ml={2} flex={1}>
|
||||
{t('common.system.Help Chatbot')}
|
||||
OpenAI/OneAPI 账号
|
||||
</Box>
|
||||
</Link>
|
||||
<Box
|
||||
w={'9px'}
|
||||
h={'9px'}
|
||||
borderRadius={'50%'}
|
||||
bg={userInfo?.openaiAccount?.key ? '#67c13b' : 'myGray.500'}
|
||||
/>
|
||||
</Flex>
|
||||
)}
|
||||
{feConfigs?.show_openai_account && (
|
||||
<>
|
||||
<Divider my={3} />
|
||||
</Grid>
|
||||
|
||||
<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}
|
||||
@@ -396,10 +566,6 @@ const UserInfo = () => {
|
||||
onClose={onCloseOpenai}
|
||||
/>
|
||||
)}
|
||||
{isOpenSubDatasetModal && <SubDatasetModal onClose={onCloseSubDatasetModal} />}
|
||||
<File onSelect={onSelectFile} />
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(UserInfo);
|
||||
|
||||
@@ -37,8 +37,9 @@ const OpenAIAccountModal = ({
|
||||
>
|
||||
<ModalBody>
|
||||
<Box fontSize={'sm'} color={'myGray.500'}>
|
||||
可以填写 OpenAI/OneAPI 的相关秘钥。如果你填写了该内容,在线上平台使用 OpenAI Chat
|
||||
模型不会计费(不包含知识库训练、索引生成)。请注意你的 Key 是否有访问对应模型的权限。
|
||||
可以填写 OpenAI/OneAPI
|
||||
的相关秘钥。如果你填写了该内容,在线上平台使用【AI对话】、【问题分类】和【内容提取】将会走你填写的Key,不会计费。请注意你的
|
||||
Key 是否有访问对应模型的权限。GPT模型可以选择 FastAI。
|
||||
</Box>
|
||||
<Flex alignItems={'center'} mt={5}>
|
||||
<Box flex={'0 0 65px'}>API Key:</Box>
|
||||
|
||||
@@ -1,37 +1,46 @@
|
||||
import React, { useState, useCallback } from 'react';
|
||||
import React, { useState, useCallback, useMemo } from 'react';
|
||||
import { ModalFooter, ModalBody, Button, Input, Box, Grid } from '@chakra-ui/react';
|
||||
import { getPayCode, checkPayResult } from '@/web/support/wallet/pay/api';
|
||||
import { getWxPayQRCode } from '@/web/support/wallet/bill/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';
|
||||
|
||||
const PayModal = ({ onClose }: { onClose: () => void }) => {
|
||||
const router = useRouter();
|
||||
import QRCodePayModal, { type QRPayProps } from '@/components/support/wallet/QRCodePayModal';
|
||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||
|
||||
const PayModal = ({
|
||||
onClose,
|
||||
defaultValue,
|
||||
onSuccess
|
||||
}: {
|
||||
defaultValue?: number;
|
||||
onClose: () => void;
|
||||
onSuccess?: () => any;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const { toast } = useToast();
|
||||
const [inputVal, setInputVal] = useState<number | ''>('');
|
||||
const { subPlans } = useSystemStore();
|
||||
const [inputVal, setInputVal] = useState<number | undefined>(defaultValue);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [payId, setPayId] = useState('');
|
||||
const [qrPayData, setQRPayData] = useState<QRPayProps>();
|
||||
|
||||
const handleClickPay = useCallback(async () => {
|
||||
if (!inputVal || inputVal <= 0 || isNaN(+inputVal)) return;
|
||||
setLoading(true);
|
||||
try {
|
||||
// 获取支付二维码
|
||||
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
|
||||
const res = await getWxPayQRCode({
|
||||
type: BillTypeEnum.balance,
|
||||
balance: inputVal
|
||||
});
|
||||
setQRPayData({
|
||||
readPrice: res.readPrice,
|
||||
codeUrl: res.codeUrl,
|
||||
billId: res.billId
|
||||
});
|
||||
setPayId(res.payId);
|
||||
} catch (err) {
|
||||
toast({
|
||||
title: getErrText(err),
|
||||
@@ -41,84 +50,57 @@ const PayModal = ({ onClose }: { onClose: () => void }) => {
|
||||
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();
|
||||
}
|
||||
}
|
||||
);
|
||||
const payList = useMemo(() => {
|
||||
const list = Object.values(subPlans?.standard || {});
|
||||
const priceList = list.map((item) => item.price);
|
||||
return priceList.concat(priceList.map((item) => item * 10)).filter(Boolean);
|
||||
}, [subPlans?.standard]);
|
||||
|
||||
return (
|
||||
<MyModal
|
||||
isOpen={true}
|
||||
onClose={payId ? undefined : onClose}
|
||||
title={t('user.Pay')}
|
||||
iconSrc="/imgs/modal/pay.svg"
|
||||
>
|
||||
<MyModal isOpen={true} onClose={onClose} title={t('user.Pay')} iconSrc="/imgs/modal/pay.svg">
|
||||
<ModalBody px={0} display={'flex'} flexDirection={'column'}>
|
||||
{!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 px={6} fontSize={'sm'} color={'myGray.600'} mb={2} maxW={'400px'}>
|
||||
该余额仅用于自动续费标准套餐。如需购买额外套餐,可直接下单,无需充值余额。
|
||||
</Box>
|
||||
<Grid gridTemplateColumns={'repeat(3,1fr)'} gridGap={5} mb={4} px={6}>
|
||||
{payList.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>
|
||||
</ModalBody>
|
||||
|
||||
<ModalFooter>
|
||||
{!payId && (
|
||||
<>
|
||||
<Button variant={'whiteBase'} onClick={onClose}>
|
||||
{t('common.Close')}
|
||||
</Button>
|
||||
<Button
|
||||
ml={3}
|
||||
isLoading={loading}
|
||||
isDisabled={!inputVal || inputVal === 0}
|
||||
onClick={handleClickPay}
|
||||
>
|
||||
获取充值二维码
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
<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>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,108 +0,0 @@
|
||||
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;
|
||||
119
projects/app/src/pages/account/components/UsageDetail.tsx
Normal file
119
projects/app/src/pages/account/components/UsageDetail.tsx
Normal file
@@ -0,0 +1,119 @@
|
||||
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;
|
||||
189
projects/app/src/pages/account/components/UsageTable.tsx
Normal file
189
projects/app/src/pages/account/components/UsageTable.tsx
Normal file
@@ -0,0 +1,189 @@
|
||||
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);
|
||||
@@ -0,0 +1,103 @@
|
||||
import React from 'react';
|
||||
import {
|
||||
ModalBody,
|
||||
ModalFooter,
|
||||
Table,
|
||||
Thead,
|
||||
Tbody,
|
||||
Tr,
|
||||
Th,
|
||||
Td,
|
||||
TableContainer,
|
||||
ModalCloseButton
|
||||
} from '@chakra-ui/react';
|
||||
import MyModal from '@/components/MyModal';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { useLoading } from '@/web/common/hooks/useLoading';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import { getTeamPlans } from '@/web/support/user/team/api';
|
||||
import { subTypeMap, standardSubLevelMap } from '@fastgpt/global/support/wallet/sub/constants';
|
||||
import { TeamSubSchema } from '@fastgpt/global/support/wallet/sub/type';
|
||||
import { formatTime2YMDHM } from '@fastgpt/global/common/string/time';
|
||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||
const StandDetailModal = ({ onClose }: { onClose: () => void }) => {
|
||||
const { t } = useTranslation();
|
||||
const { Loading } = useLoading();
|
||||
const { subPlans } = useSystemStore();
|
||||
const { data: teamPlans = [], isLoading } = useQuery(['getTeamPlans'], getTeamPlans);
|
||||
|
||||
return (
|
||||
<MyModal
|
||||
isOpen
|
||||
maxW={['90vw', '1200px']}
|
||||
iconSrc="modal/teamPlans"
|
||||
title={t('support.wallet.Standard Plan Detail')}
|
||||
>
|
||||
<ModalCloseButton onClick={onClose} />
|
||||
<ModalBody>
|
||||
<TableContainer mt={2} position={'relative'} minH={'300px'}>
|
||||
<Table>
|
||||
<Thead>
|
||||
<Tr>
|
||||
<Th>{t('support.standard.type')}</Th>
|
||||
<Th>{t('support.standard.storage')}</Th>
|
||||
<Th>{t('support.standard.AI Bonus Points')}</Th>
|
||||
<Th>{t('support.standard.Start Time')}</Th>
|
||||
<Th>{t('support.standard.Expired Time')}</Th>
|
||||
<Th />
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody fontSize={'sm'}>
|
||||
{teamPlans.map(
|
||||
({
|
||||
_id,
|
||||
type,
|
||||
currentSubLevel,
|
||||
currentExtraDatasetSize,
|
||||
surplusPoints = 0,
|
||||
totalPoints = 0,
|
||||
startTime,
|
||||
expiredTime
|
||||
}: TeamSubSchema) => {
|
||||
const standardPlan = currentSubLevel
|
||||
? subPlans?.standard?.[currentSubLevel]
|
||||
: undefined;
|
||||
const datasetSize = standardPlan?.maxDatasetSize || currentExtraDatasetSize;
|
||||
|
||||
return (
|
||||
<Tr key={_id}>
|
||||
<Td>
|
||||
<MyIcon
|
||||
mr={2}
|
||||
name={subTypeMap[type]?.icon as any}
|
||||
w={'20px'}
|
||||
color={'myGray.800'}
|
||||
/>
|
||||
{t(subTypeMap[type]?.label)}
|
||||
{currentSubLevel && `(${t(standardSubLevelMap[currentSubLevel]?.label)})`}
|
||||
</Td>
|
||||
<Td>{datasetSize ? `${datasetSize}组` : '-'}</Td>
|
||||
<Td>
|
||||
{totalPoints
|
||||
? `${Math.round(totalPoints - surplusPoints)} / ${totalPoints} 积分`
|
||||
: '-'}
|
||||
</Td>
|
||||
<Td>{formatTime2YMDHM(startTime)}</Td>
|
||||
<Td>{formatTime2YMDHM(expiredTime)}</Td>
|
||||
</Tr>
|
||||
);
|
||||
}
|
||||
)}
|
||||
<Tr key={'_id'}></Tr>
|
||||
</Tbody>
|
||||
</Table>
|
||||
<Loading loading={isLoading} fixed={false} />
|
||||
</TableContainer>
|
||||
</ModalBody>
|
||||
<ModalFooter></ModalFooter>
|
||||
</MyModal>
|
||||
);
|
||||
};
|
||||
|
||||
export default StandDetailModal;
|
||||
@@ -14,19 +14,19 @@ 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'));
|
||||
const Individuation = dynamic(() => import('./components/Individuation'));
|
||||
|
||||
enum TabEnum {
|
||||
'info' = 'info',
|
||||
'promotion' = 'promotion',
|
||||
'usage' = 'usage',
|
||||
'bill' = 'bill',
|
||||
'price' = 'price',
|
||||
'pay' = 'pay',
|
||||
'inform' = 'inform',
|
||||
'individuation' = 'individuation',
|
||||
'apikey' = 'apikey',
|
||||
'loginout' = 'loginout'
|
||||
}
|
||||
@@ -45,27 +45,18 @@ const Account = ({ currentTab }: { currentTab: `${TabEnum}` }) => {
|
||||
...(feConfigs?.isPlus
|
||||
? [
|
||||
{
|
||||
icon: 'support/bill/billRecordLight',
|
||||
icon: 'support/usage/usageRecordLight',
|
||||
label: t('user.Usage Record'),
|
||||
id: TabEnum.bill
|
||||
id: TabEnum.usage
|
||||
}
|
||||
]
|
||||
: []),
|
||||
...(feConfigs?.show_pay && userInfo?.team.canWrite
|
||||
? [
|
||||
{
|
||||
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
|
||||
icon: 'support/bill/payRecordLight',
|
||||
label: t('support.wallet.Bills'),
|
||||
id: TabEnum.bill
|
||||
}
|
||||
]
|
||||
: []),
|
||||
@@ -88,6 +79,11 @@ const Account = ({ currentTab }: { currentTab: `${TabEnum}` }) => {
|
||||
}
|
||||
]
|
||||
: []),
|
||||
{
|
||||
icon: 'support/user/individuation',
|
||||
label: t('support.account.Individuation'),
|
||||
id: TabEnum.individuation
|
||||
},
|
||||
...(feConfigs.isPlus
|
||||
? [
|
||||
{
|
||||
@@ -108,11 +104,6 @@ const Account = ({ currentTab }: { currentTab: `${TabEnum}` }) => {
|
||||
const { openConfirm, ConfirmModal } = useConfirm({
|
||||
content: '确认退出登录?'
|
||||
});
|
||||
const {
|
||||
isOpen: isOpenPriceBox,
|
||||
onOpen: onOpenPriceBox,
|
||||
onClose: onClosePriceBox
|
||||
} = useDisclosure();
|
||||
|
||||
const router = useRouter();
|
||||
const theme = useTheme();
|
||||
@@ -124,8 +115,6 @@ const Account = ({ currentTab }: { currentTab: `${TabEnum}` }) => {
|
||||
setUserInfo(null);
|
||||
router.replace('/login');
|
||||
})();
|
||||
} else if (tab === TabEnum.price) {
|
||||
onOpenPriceBox();
|
||||
} else {
|
||||
router.replace({
|
||||
query: {
|
||||
@@ -134,7 +123,7 @@ const Account = ({ currentTab }: { currentTab: `${TabEnum}` }) => {
|
||||
});
|
||||
}
|
||||
},
|
||||
[onOpenPriceBox, openConfirm, router, setUserInfo]
|
||||
[openConfirm, router, setUserInfo]
|
||||
);
|
||||
|
||||
return (
|
||||
@@ -178,16 +167,15 @@ 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.individuation && <Individuation />}
|
||||
{currentTab === TabEnum.inform && <InformTable />}
|
||||
{currentTab === TabEnum.apikey && <ApiKeyTable />}
|
||||
</Box>
|
||||
</Flex>
|
||||
<ConfirmModal />
|
||||
</PageContainer>
|
||||
|
||||
{isOpenPriceBox && <PriceBox onClose={onClosePriceBox} />}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
99
projects/app/src/pages/api/admin/initv468.ts
Normal file
99
projects/app/src/pages/api/admin/initv468.ts
Normal file
@@ -0,0 +1,99 @@
|
||||
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();
|
||||
}
|
||||
};
|
||||
35
projects/app/src/pages/api/admin/initv469.ts
Normal file
35
projects/app/src/pages/api/admin/initv469.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
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
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -16,7 +16,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
|
||||
let filePaths: string[] = [];
|
||||
|
||||
try {
|
||||
const { userId, teamId, tmbId } = await authCert({ req, authToken: true });
|
||||
const { teamId, tmbId } = await authCert({ req, authToken: true });
|
||||
|
||||
const { file, bucketName, metadata } = await upload.doUpload(req, res);
|
||||
|
||||
|
||||
@@ -6,8 +6,6 @@ import type { InitDateResponse } from '@/global/common/api/systemRes';
|
||||
import type { FastGPTConfigFileType } from '@fastgpt/global/common/system/types/index.d';
|
||||
import { getTikTokenEnc } from '@fastgpt/global/common/string/tiktoken';
|
||||
import { initHttpAgent } from '@fastgpt/service/common/middle/httpAgent';
|
||||
import { SimpleModeTemplate_FastGPT_Universal } from '@/global/core/app/constants';
|
||||
import { getSimpleTemplatesFromPlus } from '@/service/core/app/utils';
|
||||
import { PluginSourceEnum } from '@fastgpt/global/core/plugin/constants';
|
||||
import { getFastGPTConfigFromDB } from '@fastgpt/service/common/system/config/controller';
|
||||
import { connectToDatabase } from '@/service/mongo';
|
||||
@@ -15,6 +13,7 @@ import { PluginTemplateType } from '@fastgpt/global/core/plugin/type';
|
||||
import { readConfigData } from '@/service/common/system';
|
||||
import { exit } from 'process';
|
||||
import { FastGPTProUrl } from '@fastgpt/service/common/system/constants';
|
||||
import { initFastGPTConfig } from '@fastgpt/service/common/system/tools';
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
await getInitConfig();
|
||||
@@ -125,15 +124,8 @@ export async function initSystemConfig() {
|
||||
};
|
||||
|
||||
// set config
|
||||
global.feConfigs = config.feConfigs;
|
||||
initFastGPTConfig(config);
|
||||
global.systemEnv = config.systemEnv;
|
||||
global.subPlans = config.subPlans;
|
||||
|
||||
global.llmModels = config.llmModels;
|
||||
global.vectorModels = config.vectorModels;
|
||||
global.reRankModels = config.reRankModels;
|
||||
global.audioSpeechModels = config.audioSpeechModels;
|
||||
global.whisperModel = config.whisperModel;
|
||||
|
||||
console.log({
|
||||
feConfigs: global.feConfigs,
|
||||
|
||||
@@ -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 { pushQuestionGuideBill } from '@/service/support/wallet/bill/push';
|
||||
import { pushQuestionGuideUsage } from '@/service/support/wallet/usage/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, inputTokens, outputTokens } = await createQuestionGuide({
|
||||
const { result, charsLength } = await createQuestionGuide({
|
||||
messages,
|
||||
model: qgModel.model
|
||||
});
|
||||
@@ -28,9 +28,8 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
|
||||
data: result
|
||||
});
|
||||
|
||||
pushQuestionGuideBill({
|
||||
inputTokens,
|
||||
outputTokens,
|
||||
pushQuestionGuideUsage({
|
||||
charsLength,
|
||||
teamId,
|
||||
tmbId
|
||||
});
|
||||
|
||||
@@ -6,6 +6,7 @@ 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 '@fastgpt/service/support/permission/teamLimit';
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
||||
try {
|
||||
@@ -25,12 +26,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
|
||||
const { teamId, tmbId } = await authUserNotVisitor({ req, authToken: true });
|
||||
|
||||
// 上限校验
|
||||
const authCount = await MongoApp.countDocuments({
|
||||
teamId
|
||||
});
|
||||
if (authCount >= 50) {
|
||||
throw new Error('每个团队上限 50 个应用');
|
||||
}
|
||||
await checkTeamAppLimit(teamId);
|
||||
|
||||
// 创建模型
|
||||
const response = await MongoApp.create({
|
||||
|
||||
@@ -1,51 +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 { 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
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,6 @@ import type { AppSimpleEditFormType } from '@fastgpt/global/core/app/type.d';
|
||||
import type { ModuleItemType } from '@fastgpt/global/core/module/type';
|
||||
import { FormatForm2ModulesProps } from '@fastgpt/global/core/app/api';
|
||||
import { DatasetSearchModeEnum } from '@fastgpt/global/core/dataset/constants';
|
||||
import { getLLMModel } from '@/service/core/ai/model';
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
||||
try {
|
||||
@@ -92,7 +91,7 @@ function simpleChatTemplate({ formData, maxToken }: Props): ModuleItemType[] {
|
||||
},
|
||||
{
|
||||
key: 'model',
|
||||
type: 'selectChatModel',
|
||||
type: 'selectLLMModel',
|
||||
label: 'core.module.input.label.aiModel',
|
||||
required: true,
|
||||
valueType: 'string',
|
||||
@@ -471,7 +470,7 @@ function datasetTemplate({ formData, maxToken }: Props): ModuleItemType[] {
|
||||
},
|
||||
{
|
||||
key: 'model',
|
||||
type: 'selectChatModel',
|
||||
type: 'selectLLMModel',
|
||||
label: 'core.module.input.label.aiModel',
|
||||
required: true,
|
||||
valueType: 'string',
|
||||
|
||||
@@ -7,7 +7,6 @@ import { jsonRes } from '@fastgpt/service/common/response';
|
||||
import type { AppSimpleEditFormType } from '@fastgpt/global/core/app/type.d';
|
||||
import type { ModuleItemType } from '@fastgpt/global/core/module/type';
|
||||
import { FormatForm2ModulesProps } from '@fastgpt/global/core/app/api';
|
||||
import { getLLMModel } from '@/service/core/ai/model';
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
||||
try {
|
||||
@@ -88,7 +87,7 @@ function simpleChatTemplate(formData: AppSimpleEditFormType): ModuleItemType[] {
|
||||
},
|
||||
{
|
||||
key: 'model',
|
||||
type: 'selectChatModel',
|
||||
type: 'selectLLMModel',
|
||||
label: 'core.module.input.label.aiModel',
|
||||
required: true,
|
||||
valueType: 'string',
|
||||
@@ -498,7 +497,7 @@ function datasetTemplate(formData: AppSimpleEditFormType): ModuleItemType[] {
|
||||
},
|
||||
{
|
||||
key: 'model',
|
||||
type: 'selectChatModel',
|
||||
type: 'selectLLMModel',
|
||||
label: 'core.module.input.label.aiModel',
|
||||
required: true,
|
||||
valueType: 'string',
|
||||
|
||||
@@ -6,13 +6,13 @@ 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';
|
||||
import { getLLMModel } from '@fastgpt/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 } =
|
||||
const { name, avatar, type, simpleTemplateId, intro, modules, permission, teamTags } =
|
||||
req.body as AppUpdateParams;
|
||||
const { appId } = req.query as { appId: string };
|
||||
|
||||
@@ -65,6 +65,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
|
||||
avatar,
|
||||
intro,
|
||||
permission,
|
||||
teamTags: teamTags,
|
||||
...(modules && {
|
||||
modules
|
||||
})
|
||||
|
||||
82
projects/app/src/pages/api/core/app/updateTeamTasg.ts
Normal file
82
projects/app/src/pages/api/core/app/updateTeamTasg.ts
Normal file
@@ -0,0 +1,82 @@
|
||||
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 '@fastgpt/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
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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 { pushChatBill } from '@/service/support/wallet/bill/push';
|
||||
import { BillSourceEnum } from '@fastgpt/global/support/wallet/bill/constants';
|
||||
import { pushChatUsage } from '@/service/support/wallet/usage/push';
|
||||
import { UsageSourceEnum } from '@fastgpt/global/support/wallet/usage/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 { getUserAndAuthBalance } from '@fastgpt/service/support/user/controller';
|
||||
import { getUserChatInfoAndAuthTeamPoints } from '@/service/support/permission/auth/team';
|
||||
|
||||
export type Props = {
|
||||
history: ChatItemType[];
|
||||
@@ -50,13 +50,10 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
]);
|
||||
|
||||
// auth balance
|
||||
const user = await getUserAndAuthBalance({
|
||||
tmbId,
|
||||
minBalance: 0
|
||||
});
|
||||
const { user } = await getUserChatInfoAndAuthTeamPoints(tmbId);
|
||||
|
||||
/* start process */
|
||||
const { responseData } = await dispatchModules({
|
||||
const { responseData, moduleDispatchBills } = await dispatchModules({
|
||||
res,
|
||||
mode: 'test',
|
||||
teamId,
|
||||
@@ -85,13 +82,13 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
});
|
||||
res.end();
|
||||
|
||||
pushChatBill({
|
||||
pushChatUsage({
|
||||
appName,
|
||||
appId,
|
||||
teamId,
|
||||
tmbId,
|
||||
source: BillSourceEnum.fastgpt,
|
||||
response: responseData
|
||||
source: UsageSourceEnum.fastgpt,
|
||||
moduleDispatchBills
|
||||
});
|
||||
} catch (err: any) {
|
||||
res.status(500);
|
||||
|
||||
@@ -14,10 +14,13 @@ 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 { uid } = await authOutLink({ shareId, outLinkUid });
|
||||
const { appId, uid } = await authOutLink({ shareId, outLinkUid });
|
||||
|
||||
chatAppId = appId;
|
||||
return {
|
||||
shareId,
|
||||
outLinkUid: uid
|
||||
@@ -41,11 +44,11 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
const idList = list.map((item) => item.chatId);
|
||||
|
||||
await MongoChatItem.deleteMany({
|
||||
appId,
|
||||
appId: chatAppId,
|
||||
chatId: { $in: idList }
|
||||
});
|
||||
await MongoChat.deleteMany({
|
||||
appId,
|
||||
appId: chatAppId,
|
||||
chatId: { $in: idList }
|
||||
});
|
||||
|
||||
|
||||
@@ -28,6 +28,13 @@ 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 {
|
||||
@@ -36,6 +43,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
source: ChatSourceEnum.online
|
||||
};
|
||||
}
|
||||
|
||||
return Promise.reject('Params are error');
|
||||
})();
|
||||
|
||||
|
||||
@@ -3,10 +3,10 @@ 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 { pushAudioSpeechBill } from '@/service/support/wallet/bill/push';
|
||||
import { pushAudioSpeechUsage } from '@/service/support/wallet/usage/push';
|
||||
import { authCertOrShareId } from '@fastgpt/service/support/permission/auth/common';
|
||||
import { authType2BillSource } from '@/service/support/wallet/bill/utils';
|
||||
import { getAudioSpeechModel } from '@/service/core/ai/model';
|
||||
import { authType2UsageSource } from '@/service/support/wallet/usage/utils';
|
||||
import { getAudioSpeechModel } from '@fastgpt/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 {
|
||||
pushAudioSpeechBill({
|
||||
pushAudioSpeechUsage({
|
||||
model: model,
|
||||
charsLength: input.length,
|
||||
tmbId,
|
||||
teamId,
|
||||
source: authType2BillSource({ authType })
|
||||
source: authType2UsageSource({ authType })
|
||||
});
|
||||
|
||||
await MongoTTSBuffer.create({
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
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'
|
||||
}
|
||||
};
|
||||
91
projects/app/src/pages/api/core/chat/team/init.ts
Normal file
91
projects/app/src/pages/api/core/chat/team/init.ts
Normal file
@@ -0,0 +1,91 @@
|
||||
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'
|
||||
}
|
||||
};
|
||||
81
projects/app/src/pages/api/core/chat/teamInit.ts
Normal file
81
projects/app/src/pages/api/core/chat/teamInit.ts
Normal file
@@ -0,0 +1,81 @@
|
||||
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'
|
||||
}
|
||||
};
|
||||
@@ -24,6 +24,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
await MongoChat.findOneAndUpdate(
|
||||
{ appId, chatId },
|
||||
{
|
||||
updateTime: new Date(),
|
||||
...(customTitle !== undefined && { customTitle }),
|
||||
...(top !== undefined && { top })
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { jsonRes } from '@fastgpt/service/common/response';
|
||||
import { connectToDatabase } from '@/service/mongo';
|
||||
import { MongoDataset } from '@fastgpt/service/core/dataset/schema';
|
||||
import { getVectorModel } from '@/service/core/ai/model';
|
||||
import { getVectorModel } from '@fastgpt/service/core/ai/model';
|
||||
import type { DatasetListItemType } from '@fastgpt/global/core/dataset/type.d';
|
||||
import { mongoRPermission } from '@fastgpt/global/support/permission/utils';
|
||||
import { authUserRole } from '@fastgpt/service/support/permission/auth/user';
|
||||
|
||||
@@ -11,13 +11,12 @@ import {
|
||||
TrainingModeEnum,
|
||||
DatasetCollectionTypeEnum
|
||||
} from '@fastgpt/global/core/dataset/constants';
|
||||
import { checkDatasetLimit } from '@fastgpt/service/support/permission/limit/dataset';
|
||||
import { checkDatasetLimit } from '@fastgpt/service/support/permission/teamLimit';
|
||||
import { predictDataLimitLength } from '@fastgpt/global/core/dataset/utils';
|
||||
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 { createTrainingUsage } from '@fastgpt/service/support/wallet/usage/controller';
|
||||
import { UsageSourceEnum } from '@fastgpt/global/support/wallet/usage/constants';
|
||||
import { getLLMModel, getVectorModel } from '@fastgpt/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>) {
|
||||
@@ -43,8 +42,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
|
||||
// 1. check dataset limit
|
||||
await checkDatasetLimit({
|
||||
teamId,
|
||||
insertLen: predictDataLimitLength(trainingType, new Array(10)),
|
||||
standardPlans: getStandardSubPlan()
|
||||
insertLen: predictDataLimitLength(trainingType, new Array(10))
|
||||
});
|
||||
|
||||
const { _id: collectionId } = await mongoSessionRun(async (session) => {
|
||||
@@ -66,11 +64,11 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
|
||||
});
|
||||
|
||||
// 3. create bill and start sync
|
||||
const { billId } = await createTrainingBill({
|
||||
const { billId } = await createTrainingUsage({
|
||||
teamId,
|
||||
tmbId,
|
||||
appName: 'core.dataset.collection.Sync Collection',
|
||||
billSource: BillSourceEnum.training,
|
||||
billSource: UsageSourceEnum.training,
|
||||
vectorModel: getVectorModel(dataset.vectorModel).name,
|
||||
agentModel: getLLMModel(dataset.agentModel).name,
|
||||
session
|
||||
|
||||
@@ -12,14 +12,13 @@ import {
|
||||
DatasetCollectionTypeEnum
|
||||
} from '@fastgpt/global/core/dataset/constants';
|
||||
import { splitText2Chunks } from '@fastgpt/global/common/string/textSplitter';
|
||||
import { checkDatasetLimit } from '@fastgpt/service/support/permission/limit/dataset';
|
||||
import { checkDatasetLimit } from '@fastgpt/service/support/permission/teamLimit';
|
||||
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 { 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';
|
||||
import { createTrainingUsage } from '@fastgpt/service/support/wallet/usage/controller';
|
||||
import { UsageSourceEnum } from '@fastgpt/global/support/wallet/usage/constants';
|
||||
import { getLLMModel, getVectorModel } from '@fastgpt/service/core/ai/model';
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
||||
try {
|
||||
@@ -53,8 +52,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
|
||||
// 2. check dataset limit
|
||||
await checkDatasetLimit({
|
||||
teamId,
|
||||
insertLen: predictDataLimitLength(trainingType, chunks),
|
||||
standardPlans: getStandardSubPlan()
|
||||
insertLen: predictDataLimitLength(trainingType, chunks)
|
||||
});
|
||||
|
||||
// 3. create collection and training bill
|
||||
@@ -74,11 +72,11 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
|
||||
hashRawText: hashStr(text),
|
||||
rawTextLength: text.length
|
||||
}),
|
||||
createTrainingBill({
|
||||
createTrainingUsage({
|
||||
teamId,
|
||||
tmbId,
|
||||
appName: name,
|
||||
billSource: BillSourceEnum.training,
|
||||
billSource: UsageSourceEnum.training,
|
||||
vectorModel: getVectorModel(dataset.vectorModel)?.name,
|
||||
agentModel: getLLMModel(dataset.agentModel)?.name
|
||||
})
|
||||
|
||||
@@ -12,9 +12,9 @@ import {
|
||||
DatasetCollectionTypeEnum
|
||||
} from '@fastgpt/global/core/dataset/constants';
|
||||
import { DatasetErrEnum } from '@fastgpt/global/common/error/code/dataset';
|
||||
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 { createTrainingUsage } from '@fastgpt/service/support/wallet/usage/controller';
|
||||
import { UsageSourceEnum } from '@fastgpt/global/support/wallet/usage/constants';
|
||||
import { getLLMModel, getVectorModel } from '@fastgpt/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 createTrainingBill({
|
||||
const { billId } = await createTrainingUsage({
|
||||
teamId: collection.teamId,
|
||||
tmbId,
|
||||
appName: 'core.dataset.collection.Sync Collection',
|
||||
billSource: BillSourceEnum.training,
|
||||
billSource: UsageSourceEnum.training,
|
||||
vectorModel: vectorModelData.name,
|
||||
agentModel: agentModelData.name,
|
||||
session
|
||||
|
||||
@@ -6,7 +6,8 @@ import type { CreateDatasetParams } from '@/global/core/dataset/api.d';
|
||||
import { createDefaultCollection } from '@fastgpt/service/core/dataset/collection/controller';
|
||||
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 { getLLMModel, getVectorModel, getDatasetModel } from '@fastgpt/service/core/ai/model';
|
||||
import { checkTeamDatasetLimit } from '@fastgpt/service/support/permission/teamLimit';
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
||||
try {
|
||||
@@ -31,13 +32,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
|
||||
}
|
||||
|
||||
// check limit
|
||||
const authCount = await MongoDataset.countDocuments({
|
||||
teamId,
|
||||
type: DatasetTypeEnum.dataset
|
||||
});
|
||||
if (authCount >= 50) {
|
||||
throw new Error('每个团队上限 50 个知识库');
|
||||
}
|
||||
await checkTeamDatasetLimit(teamId);
|
||||
|
||||
const { _id } = await MongoDataset.create({
|
||||
name,
|
||||
|
||||
@@ -3,8 +3,7 @@ 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 { MongoDatasetData } from '@fastgpt/service/core/dataset/data/schema';
|
||||
import { deleteDatasetDataVector } from '@fastgpt/service/common/vectorStore/controller';
|
||||
import { deleteDatasetData } from '@/service/core/dataset/data/controller';
|
||||
|
||||
export default withNextCors(async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
||||
try {
|
||||
@@ -26,19 +25,7 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
|
||||
per: 'w'
|
||||
});
|
||||
|
||||
// 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);
|
||||
await deleteDatasetData(datasetData);
|
||||
|
||||
jsonRes(res, {
|
||||
data: 'success'
|
||||
|
||||
@@ -7,17 +7,15 @@ import { jsonRes } from '@fastgpt/service/common/response';
|
||||
import { connectToDatabase } from '@/service/mongo';
|
||||
import { withNextCors } from '@fastgpt/service/common/middle/cors';
|
||||
import { countPromptTokens } from '@fastgpt/global/common/string/tiktoken';
|
||||
import { getVectorModel } from '@/service/core/ai/model';
|
||||
import { getVectorModel } from '@fastgpt/service/core/ai/model';
|
||||
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 { authTeamBalance } from '@/service/support/permission/auth/bill';
|
||||
import { pushGenerateVectorBill } from '@/service/support/wallet/bill/push';
|
||||
import { pushGenerateVectorUsage } from '@/service/support/wallet/usage/push';
|
||||
import { InsertOneDatasetDataProps } from '@/global/core/dataset/api';
|
||||
import { simpleText } from '@fastgpt/global/common/string/tools';
|
||||
import { checkDatasetLimit } from '@fastgpt/service/support/permission/limit/dataset';
|
||||
import { getStandardSubPlan } from '@/service/support/wallet/sub/utils';
|
||||
import { checkDatasetLimit } from '@fastgpt/service/support/permission/teamLimit';
|
||||
|
||||
export default withNextCors(async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
||||
try {
|
||||
@@ -43,8 +41,7 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
|
||||
|
||||
await checkDatasetLimit({
|
||||
teamId,
|
||||
insertLen: 1,
|
||||
standardPlans: getStandardSubPlan()
|
||||
insertLen: 1
|
||||
});
|
||||
|
||||
// auth collection and get dataset
|
||||
@@ -52,7 +49,7 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
|
||||
{
|
||||
datasetId: { _id: datasetId, vectorModel }
|
||||
}
|
||||
] = await Promise.all([getCollectionWithDataset(collectionId), authTeamBalance(teamId)]);
|
||||
] = await Promise.all([getCollectionWithDataset(collectionId)]);
|
||||
|
||||
// format data
|
||||
const formatQ = simpleText(q);
|
||||
@@ -90,7 +87,7 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
|
||||
indexes: formatIndexes
|
||||
});
|
||||
|
||||
pushGenerateVectorBill({
|
||||
pushGenerateVectorUsage({
|
||||
teamId,
|
||||
tmbId,
|
||||
charsLength,
|
||||
|
||||
@@ -8,10 +8,9 @@ import type {
|
||||
PushDatasetDataResponse
|
||||
} from '@fastgpt/global/core/dataset/api.d';
|
||||
import { authDatasetCollection } from '@fastgpt/service/support/permission/auth/dataset';
|
||||
import { checkDatasetLimit } from '@fastgpt/service/support/permission/limit/dataset';
|
||||
import { checkDatasetLimit } from '@fastgpt/service/support/permission/teamLimit';
|
||||
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 {
|
||||
@@ -38,8 +37,7 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
|
||||
// auth dataset limit
|
||||
await checkDatasetLimit({
|
||||
teamId,
|
||||
insertLen: predictDataLimitLength(collection.trainingType, data),
|
||||
standardPlans: getStandardSubPlan()
|
||||
insertLen: predictDataLimitLength(collection.trainingType, data)
|
||||
});
|
||||
|
||||
jsonRes<PushDatasetDataResponse>(res, {
|
||||
|
||||
@@ -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 { authTeamBalance } from '@/service/support/permission/auth/bill';
|
||||
import { pushGenerateVectorBill } from '@/service/support/wallet/bill/push';
|
||||
import { pushGenerateVectorUsage } from '@/service/support/wallet/usage/push';
|
||||
import { UpdateDatasetDataProps } from '@/global/core/dataset/api';
|
||||
import { checkDatasetLimit } from '@fastgpt/service/support/permission/teamLimit';
|
||||
|
||||
export default withNextCors(async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
||||
try {
|
||||
@@ -29,7 +29,10 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
|
||||
});
|
||||
|
||||
// auth team balance
|
||||
await authTeamBalance(teamId);
|
||||
await checkDatasetLimit({
|
||||
teamId,
|
||||
insertLen: 1
|
||||
});
|
||||
|
||||
const { charsLength } = await updateData2Dataset({
|
||||
dataId: id,
|
||||
@@ -39,7 +42,7 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
|
||||
model: vectorModel
|
||||
});
|
||||
|
||||
pushGenerateVectorBill({
|
||||
pushGenerateVectorUsage({
|
||||
teamId,
|
||||
tmbId,
|
||||
charsLength,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { jsonRes } from '@fastgpt/service/common/response';
|
||||
import { connectToDatabase } from '@/service/mongo';
|
||||
import { getLLMModel, getVectorModel } from '@/service/core/ai/model';
|
||||
import { getLLMModel, getVectorModel } from '@fastgpt/service/core/ai/model';
|
||||
import type { DatasetItemType } from '@fastgpt/global/core/dataset/type.d';
|
||||
import { authDataset } from '@fastgpt/service/support/permission/auth/dataset';
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ import { DatasetTypeEnum } from '@fastgpt/global/core/dataset/constants';
|
||||
import { MongoDataset } from '@fastgpt/service/core/dataset/schema';
|
||||
import { mongoRPermission } from '@fastgpt/global/support/permission/utils';
|
||||
import { authUserRole } from '@fastgpt/service/support/permission/auth/user';
|
||||
import { getVectorModel } from '@/service/core/ai/model';
|
||||
import { getVectorModel } from '@fastgpt/service/core/ai/model';
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
||||
try {
|
||||
|
||||
@@ -4,14 +4,16 @@ 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 { authTeamBalance } from '@/service/support/permission/auth/bill';
|
||||
import { pushGenerateVectorBill } from '@/service/support/wallet/bill/push';
|
||||
import { pushGenerateVectorUsage } from '@/service/support/wallet/usage/push';
|
||||
import { searchDatasetData } from '@/service/core/dataset/data/controller';
|
||||
import { updateApiKeyUsage } from '@fastgpt/service/support/openapi/tools';
|
||||
import { BillSourceEnum } from '@fastgpt/global/support/wallet/bill/constants';
|
||||
import { getLLMModel } from '@/service/core/ai/model';
|
||||
import { queryExtension } from '@fastgpt/service/core/ai/functions/queryExtension';
|
||||
import { UsageSourceEnum } from '@fastgpt/global/support/wallet/usage/constants';
|
||||
import { getLLMModel } from '@fastgpt/service/core/ai/model';
|
||||
import { datasetSearchQueryExtension } from '@fastgpt/service/core/dataset/search/utils';
|
||||
import {
|
||||
checkTeamAIPoints,
|
||||
checkTeamReRankPermission
|
||||
} from '@fastgpt/service/support/permission/teamLimit';
|
||||
|
||||
export default withNextCors(async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
||||
try {
|
||||
@@ -43,7 +45,7 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
|
||||
per: 'r'
|
||||
});
|
||||
// auth balance
|
||||
await authTeamBalance(teamId);
|
||||
await checkTeamAIPoints(teamId);
|
||||
|
||||
// query extension
|
||||
const extensionModel =
|
||||
@@ -65,28 +67,27 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
|
||||
similarity,
|
||||
datasetIds: [datasetId],
|
||||
searchMode,
|
||||
usingReRank
|
||||
usingReRank: usingReRank && (await checkTeamReRankPermission(teamId))
|
||||
});
|
||||
|
||||
// push bill
|
||||
const { total } = pushGenerateVectorBill({
|
||||
const { totalPoints } = pushGenerateVectorUsage({
|
||||
teamId,
|
||||
tmbId,
|
||||
charsLength,
|
||||
model: dataset.vectorModel,
|
||||
source: apikey ? BillSourceEnum.api : BillSourceEnum.fastgpt,
|
||||
source: apikey ? UsageSourceEnum.api : UsageSourceEnum.fastgpt,
|
||||
|
||||
...(aiExtensionResult &&
|
||||
extensionModel && {
|
||||
extensionModel: extensionModel.name,
|
||||
extensionInputTokens: aiExtensionResult.inputTokens,
|
||||
extensionOutputTokens: aiExtensionResult.outputTokens
|
||||
extensionCharsLength: aiExtensionResult.charsLength
|
||||
})
|
||||
});
|
||||
if (apikey) {
|
||||
updateApiKeyUsage({
|
||||
apikey,
|
||||
usage: total
|
||||
totalPoints: totalPoints
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ 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 '@fastgpt/service/support/permission/teamLimit';
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
||||
try {
|
||||
@@ -11,6 +12,8 @@ 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,
|
||||
|
||||
@@ -34,7 +34,6 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
|
||||
});
|
||||
|
||||
const textResult = replaceVariable(text, obj);
|
||||
|
||||
res.json({
|
||||
text: textResult
|
||||
});
|
||||
|
||||
@@ -5,6 +5,7 @@ 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>) {
|
||||
@@ -12,8 +13,12 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
|
||||
await connectToDatabase();
|
||||
const { avatar, timezone, openaiAccount } = req.body as UserUpdateParams;
|
||||
|
||||
const { userId } = await authCert({ req, authToken: true });
|
||||
|
||||
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;
|
||||
// auth key
|
||||
if (openaiAccount?.key) {
|
||||
console.log('auth user openai key', openaiAccount?.key);
|
||||
|
||||
@@ -3,6 +3,7 @@ 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 {
|
||||
@@ -13,8 +14,12 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
|
||||
throw new Error('Params is missing');
|
||||
}
|
||||
|
||||
const { userId } = await authCert({ req, authToken: true });
|
||||
|
||||
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;
|
||||
// auth old password
|
||||
const user = await MongoUser.findOne({
|
||||
_id: userId,
|
||||
|
||||
@@ -2,8 +2,7 @@ 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 '@fastgpt/service/support/permission/limit/dataset';
|
||||
import { getStandardSubPlan } from '@/service/support/wallet/sub/utils';
|
||||
import { checkDatasetLimit } from '@fastgpt/service/support/permission/teamLimit';
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
||||
try {
|
||||
@@ -23,8 +22,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
|
||||
|
||||
await checkDatasetLimit({
|
||||
teamId,
|
||||
insertLen: numberSize,
|
||||
standardPlans: getStandardSubPlan()
|
||||
insertLen: numberSize
|
||||
});
|
||||
|
||||
jsonRes(res);
|
||||
|
||||
@@ -2,24 +2,21 @@ 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 { getTeamSubPlanStatus } from '@fastgpt/service/support/wallet/sub/utils';
|
||||
import { getStandardSubPlan } from '@/service/support/wallet/sub/utils';
|
||||
import { FeTeamSubType } from '@fastgpt/global/support/wallet/sub/type';
|
||||
import { FeTeamPlanStatusType } from '@fastgpt/global/support/wallet/sub/type';
|
||||
import { getTeamPlanStatus } from '@fastgpt/service/support/wallet/sub/utils';
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
||||
try {
|
||||
await connectToDatabase();
|
||||
|
||||
// 凭证校验
|
||||
const { teamId } = await authCert({
|
||||
req,
|
||||
authToken: true
|
||||
});
|
||||
|
||||
jsonRes<FeTeamSubType>(res, {
|
||||
data: await getTeamSubPlanStatus({
|
||||
teamId,
|
||||
standardPlans: getStandardSubPlan()
|
||||
jsonRes<FeTeamPlanStatusType>(res, {
|
||||
data: await getTeamPlanStatus({
|
||||
teamId
|
||||
})
|
||||
});
|
||||
} catch (err) {
|
||||
@@ -1,16 +1,16 @@
|
||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { jsonRes } from '@fastgpt/service/common/response';
|
||||
import { connectToDatabase } from '@/service/mongo';
|
||||
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 { createTrainingBill } from '@fastgpt/service/support/wallet/bill/controller';
|
||||
import { UsageSourceEnum } from '@fastgpt/global/support/wallet/usage/constants';
|
||||
import { CreateTrainingUsageProps } from '@fastgpt/global/support/wallet/usage/api.d';
|
||||
import { getLLMModel, getVectorModel } from '@fastgpt/service/core/ai/model';
|
||||
import { createTrainingUsage } from '@fastgpt/service/support/wallet/usage/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 CreateTrainingBillProps;
|
||||
const { name, datasetId } = req.body as CreateTrainingUsageProps;
|
||||
|
||||
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 createTrainingBill({
|
||||
const { billId } = await createTrainingUsage({
|
||||
teamId,
|
||||
tmbId,
|
||||
appName: name,
|
||||
billSource: BillSourceEnum.training,
|
||||
billSource: UsageSourceEnum.training,
|
||||
vectorModel: getVectorModel(dataset.vectorModel).name,
|
||||
agentModel: getLLMModel(dataset.agentModel).name
|
||||
});
|
||||
@@ -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 { pushWhisperBill } from '@/service/support/wallet/bill/push';
|
||||
import { pushWhisperUsage } from '@/service/support/wallet/usage/push';
|
||||
|
||||
const upload = getUploadModel({
|
||||
maxSize: 2
|
||||
@@ -40,7 +40,7 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
|
||||
model: global.whisperModel.model
|
||||
});
|
||||
|
||||
pushWhisperBill({
|
||||
pushWhisperUsage({
|
||||
teamId,
|
||||
tmbId,
|
||||
duration
|
||||
|
||||
@@ -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 { pushChatBill } from '@/service/support/wallet/bill/push';
|
||||
import { pushChatUsage } from '@/service/support/wallet/usage/push';
|
||||
import { authOutLinkChatStart } from '@/service/support/permission/auth/outLink';
|
||||
import { pushResult2Remote, updateOutLinkUsage } from '@fastgpt/service/support/outLink/tools';
|
||||
import { pushResult2Remote, addOutLinkUsage } from '@fastgpt/service/support/outLink/tools';
|
||||
import requestIp from 'request-ip';
|
||||
import { getBillSourceByAuthType } from '@fastgpt/global/support/wallet/bill/tools';
|
||||
|
||||
import { getUsageSourceByAuthType } from '@fastgpt/global/support/wallet/usage/tools';
|
||||
import { authTeamShareChatStart } from '@/service/support/permission/auth/teamChat';
|
||||
import { selectShareResponse } from '@/utils/service/core/chat';
|
||||
import { updateApiKeyUsage } from '@fastgpt/service/support/openapi/tools';
|
||||
import { connectToDatabase } from '@/service/mongo';
|
||||
import { getUserAndAuthBalance } from '@fastgpt/service/support/user/controller';
|
||||
import { getUserChatInfoAndAuthTeamPoints } from '@/service/support/permission/auth/team';
|
||||
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,9 +35,14 @@ type FastGptShareChatProps = {
|
||||
shareId?: string;
|
||||
outLinkUid?: string;
|
||||
};
|
||||
type FastGptTeamShareChatProps = {
|
||||
shareTeamId?: string;
|
||||
outLinkUid?: string;
|
||||
};
|
||||
export type Props = ChatCompletionCreateParams &
|
||||
FastGptWebChatProps &
|
||||
FastGptShareChatProps & {
|
||||
FastGptShareChatProps &
|
||||
FastGptTeamShareChatProps & {
|
||||
messages: ChatMessageItemType[];
|
||||
stream?: boolean;
|
||||
detail?: boolean;
|
||||
@@ -60,6 +65,7 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
|
||||
const {
|
||||
chatId,
|
||||
appId,
|
||||
shareTeamId,
|
||||
shareId,
|
||||
outLinkUid,
|
||||
stream = false,
|
||||
@@ -67,7 +73,6 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
|
||||
messages = [],
|
||||
variables = {}
|
||||
} = req.body as Props;
|
||||
|
||||
try {
|
||||
const originIp = requestIp.getClientIp(req);
|
||||
|
||||
@@ -95,92 +100,122 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
|
||||
if (!question) {
|
||||
throw new Error('Question is empty');
|
||||
}
|
||||
|
||||
/* auth app permission */
|
||||
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 { 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');
|
||||
if (!app) {
|
||||
return Promise.reject('app is empty');
|
||||
}
|
||||
|
||||
return {
|
||||
teamId,
|
||||
tmbId,
|
||||
user,
|
||||
app,
|
||||
responseDetail,
|
||||
apikey: '',
|
||||
authType,
|
||||
canWrite: false,
|
||||
outLinkUserId: uid
|
||||
};
|
||||
}
|
||||
// team Apps share
|
||||
if (shareTeamId && appId && outLinkUid) {
|
||||
const { user, uid, tmbId } = await authTeamShareChatStart({
|
||||
teamId: shareTeamId,
|
||||
ip: originIp,
|
||||
outLinkUid,
|
||||
question: question.value
|
||||
});
|
||||
const app = await MongoApp.findById(appId);
|
||||
if (!app) {
|
||||
return Promise.reject('app is empty');
|
||||
}
|
||||
|
||||
return {
|
||||
teamId: shareTeamId,
|
||||
tmbId,
|
||||
user,
|
||||
app,
|
||||
responseDetail: detail,
|
||||
authType: AuthUserTypeEnum.token,
|
||||
apikey: '',
|
||||
canWrite: false,
|
||||
outLinkUserId: uid
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
user,
|
||||
app,
|
||||
responseDetail,
|
||||
apikey: '',
|
||||
const {
|
||||
appId: apiKeyAppId,
|
||||
teamId,
|
||||
tmbId,
|
||||
authType,
|
||||
canWrite: false,
|
||||
uid
|
||||
};
|
||||
}
|
||||
apikey
|
||||
} = await authCert({
|
||||
req,
|
||||
authToken: true,
|
||||
authApiKey: true
|
||||
});
|
||||
|
||||
const {
|
||||
appId: apiKeyAppId,
|
||||
tmbId,
|
||||
authType,
|
||||
apikey
|
||||
} = await authCert({
|
||||
req,
|
||||
authToken: true,
|
||||
authApiKey: true
|
||||
});
|
||||
const { user } = await getUserChatInfoAndAuthTeamPoints(tmbId);
|
||||
|
||||
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);
|
||||
|
||||
// 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.'
|
||||
);
|
||||
if (!app) {
|
||||
return Promise.reject('app is empty');
|
||||
}
|
||||
|
||||
return {
|
||||
teamId,
|
||||
tmbId,
|
||||
user,
|
||||
app,
|
||||
responseDetail: detail,
|
||||
apikey,
|
||||
authType,
|
||||
canWrite: true
|
||||
};
|
||||
}
|
||||
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: true
|
||||
canWrite: canWrite || false
|
||||
};
|
||||
}
|
||||
|
||||
// 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({
|
||||
@@ -190,6 +225,7 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
|
||||
appId: app._id,
|
||||
chatId,
|
||||
shareId,
|
||||
shareTeamId,
|
||||
outLinkUid,
|
||||
per: 'w'
|
||||
});
|
||||
@@ -201,16 +237,17 @@ 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, answerText } = await dispatchModules({
|
||||
const { responseData, moduleDispatchBills, answerText } = await dispatchModules({
|
||||
res,
|
||||
mode: 'chat',
|
||||
user,
|
||||
teamId: String(user.team.teamId),
|
||||
tmbId: String(user.team.tmbId),
|
||||
teamId: String(teamId),
|
||||
tmbId: String(tmbId),
|
||||
appId: String(app._id),
|
||||
chatId,
|
||||
responseChatItemId,
|
||||
@@ -229,12 +266,12 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
|
||||
await saveChat({
|
||||
chatId,
|
||||
appId: app._id,
|
||||
teamId: user.team.teamId,
|
||||
tmbId: user.team.tmbId,
|
||||
teamId,
|
||||
tmbId: tmbId,
|
||||
variables,
|
||||
updateUseTime: !shareId && String(user.team.tmbId) === String(app.tmbId), // owner update use time
|
||||
updateUseTime: !shareId && String(tmbId) === String(app.tmbId), // owner update use time
|
||||
shareId,
|
||||
outLinkUid: uid,
|
||||
outLinkUid: outLinkUserId,
|
||||
source: (() => {
|
||||
if (shareId) {
|
||||
return ChatSourceEnum.share;
|
||||
@@ -305,29 +342,29 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
|
||||
}
|
||||
|
||||
// add record
|
||||
const { total } = pushChatBill({
|
||||
const { totalPoints } = pushChatUsage({
|
||||
appName: app.name,
|
||||
appId: app._id,
|
||||
teamId: user.team.teamId,
|
||||
tmbId: user.team.tmbId,
|
||||
source: getBillSourceByAuthType({ shareId, authType }),
|
||||
response: responseData
|
||||
teamId,
|
||||
tmbId: tmbId,
|
||||
source: getUsageSourceByAuthType({ shareId, authType }),
|
||||
moduleDispatchBills
|
||||
});
|
||||
|
||||
if (shareId) {
|
||||
pushResult2Remote({ outLinkUid, shareId, responseData });
|
||||
updateOutLinkUsage({
|
||||
pushResult2Remote({ outLinkUid, shareId, appName: app.name, responseData });
|
||||
addOutLinkUsage({
|
||||
shareId,
|
||||
total
|
||||
totalPoints
|
||||
});
|
||||
}
|
||||
if (apikey) {
|
||||
updateApiKeyUsage({
|
||||
apikey,
|
||||
usage: total
|
||||
totalPoints
|
||||
});
|
||||
}
|
||||
} catch (err: any) {
|
||||
} catch (err) {
|
||||
if (stream) {
|
||||
sseErrRes(res, err);
|
||||
res.end();
|
||||
|
||||
@@ -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 { pushGenerateVectorBill } from '@/service/support/wallet/bill/push';
|
||||
import { pushGenerateVectorUsage } from '@/service/support/wallet/usage/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 { getBillSourceByAuthType } from '@fastgpt/global/support/wallet/bill/tools';
|
||||
import { getVectorModel } from '@/service/core/ai/model';
|
||||
import { getUsageSourceByAuthType } from '@fastgpt/global/support/wallet/usage/tools';
|
||||
import { getVectorModel } from '@fastgpt/service/core/ai/model';
|
||||
import { checkTeamAIPoints } from '@fastgpt/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 authTeamBalance(teamId);
|
||||
await checkTeamAIPoints(teamId);
|
||||
|
||||
const { charsLength, vectors } = await getVectorsByText({
|
||||
input: query,
|
||||
@@ -55,19 +55,19 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
|
||||
}
|
||||
});
|
||||
|
||||
const { total } = pushGenerateVectorBill({
|
||||
const { totalPoints } = pushGenerateVectorUsage({
|
||||
teamId,
|
||||
tmbId,
|
||||
charsLength,
|
||||
model,
|
||||
billId,
|
||||
source: getBillSourceByAuthType({ authType })
|
||||
source: getUsageSourceByAuthType({ authType })
|
||||
});
|
||||
|
||||
if (apikey) {
|
||||
updateApiKeyUsage({
|
||||
apikey,
|
||||
usage: total
|
||||
totalPoints: totalPoints
|
||||
});
|
||||
}
|
||||
} catch (err) {
|
||||
|
||||
@@ -1,50 +0,0 @@
|
||||
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
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -1,201 +0,0 @@
|
||||
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);
|
||||
@@ -36,7 +36,6 @@ 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';
|
||||
@@ -94,7 +93,7 @@ const Share = ({ appId }: { appId: string }) => {
|
||||
<Thead>
|
||||
<Tr>
|
||||
<Th>{t('common.Name')}</Th>
|
||||
<Th>{t('common.Price used')}</Th>
|
||||
<Th>{t('support.outlink.Usage points')}</Th>
|
||||
<Th>{t('core.app.share.Is response quote')}</Th>
|
||||
{feConfigs?.isPlus && (
|
||||
<>
|
||||
@@ -112,11 +111,11 @@ const Share = ({ appId }: { appId: string }) => {
|
||||
<Tr key={item._id}>
|
||||
<Td>{item.name}</Td>
|
||||
<Td>
|
||||
{formatStorePrice2Read(item.total)}
|
||||
{Math.round(item.usagePoints)}
|
||||
{feConfigs?.isPlus
|
||||
? `${
|
||||
item.limit && item.limit.credit > -1
|
||||
? ` / ¥${item.limit.credit}`
|
||||
item.limit?.maxUsagePoints && item.limit.maxUsagePoints > -1
|
||||
? ` / ${item.limit.maxUsagePoints}`
|
||||
: ` / ${t('common.Unlimited')}`
|
||||
}`
|
||||
: ''}
|
||||
@@ -315,15 +314,15 @@ function EditLinkModal({
|
||||
</Flex>
|
||||
<Flex alignItems={'center'} mt={4}>
|
||||
<Flex flex={'0 0 90px'} alignItems={'center'}>
|
||||
{t('common.Max credit')}
|
||||
<MyTooltip label={t('common.Max credit tips' || '')}>
|
||||
{t('support.outlink.Max usage points')}
|
||||
<MyTooltip label={t('support.outlink.Max usage points tip')}>
|
||||
<QuestionOutlineIcon ml={1} />
|
||||
</MyTooltip>
|
||||
</Flex>
|
||||
<Input
|
||||
{...register('limit.credit', {
|
||||
{...register('limit.maxUsagePoints', {
|
||||
min: -1,
|
||||
max: 1000,
|
||||
max: 10000000,
|
||||
valueAsNumber: true,
|
||||
required: true
|
||||
})}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
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';
|
||||
@@ -12,6 +13,8 @@ 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';
|
||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||
const InfoModal = dynamic(() => import('../InfoModal'));
|
||||
|
||||
const AppCard = ({ appId }: { appId: string }) => {
|
||||
@@ -19,7 +22,9 @@ const AppCard = ({ appId }: { appId: string }) => {
|
||||
const { t } = useTranslation();
|
||||
const { toast } = useToast();
|
||||
const { appDetail } = useAppStore();
|
||||
const { feConfigs } = useSystemStore();
|
||||
const [settingAppInfo, setSettingAppInfo] = useState<AppSchema>();
|
||||
const [TeamTagsSet, setTeamTagsSet] = useState<AppSchema>();
|
||||
|
||||
const { openConfirm: openConfirmDel, ConfirmModal: ConfirmDelModal } = useConfirm({
|
||||
content: t('app.Confirm Del App Tip')
|
||||
@@ -123,6 +128,17 @@ const AppCard = ({ appId }: { appId: string }) => {
|
||||
>
|
||||
{t('core.app.navbar.Publish')}
|
||||
</Button>
|
||||
{appDetail.canWrite && feConfigs?.show_team_chat && (
|
||||
<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']}
|
||||
@@ -136,11 +152,13 @@ const AppCard = ({ appId }: { appId: string }) => {
|
||||
</Flex>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
<ConfirmDelModal />
|
||||
{settingAppInfo && (
|
||||
<InfoModal defaultApp={settingAppInfo} onClose={() => setSettingAppInfo(undefined)} />
|
||||
)}
|
||||
{TeamTagsSet && (
|
||||
<TagsEditModal appDetail={appDetail} onClose={() => setTeamTagsSet(undefined)} />
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
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;
|
||||
@@ -8,8 +8,12 @@ import {
|
||||
Input,
|
||||
Grid,
|
||||
useTheme,
|
||||
Card
|
||||
Card,
|
||||
Text,
|
||||
HStack,
|
||||
Tag
|
||||
} 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';
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useCallback } from 'react';
|
||||
import React, { useCallback, useState, useEffect } 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,7 +8,6 @@ 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';
|
||||
@@ -24,6 +23,7 @@ const MyApps = () => {
|
||||
const router = useRouter();
|
||||
const { userInfo } = useUserStore();
|
||||
const { myApps, loadMyApps } = useAppStore();
|
||||
const [teamsTags, setTeamTags] = useState([]);
|
||||
const { openConfirm, ConfirmModal } = useConfirm({
|
||||
title: '删除提示',
|
||||
content: '确认删除该应用所有信息?'
|
||||
@@ -65,11 +65,9 @@ const MyApps = () => {
|
||||
<Box letterSpacing={1} fontSize={['20px', '24px']} color={'myGray.900'}>
|
||||
{t('app.My Apps')}
|
||||
</Box>
|
||||
{userInfo?.team?.canWrite && (
|
||||
<Button leftIcon={<AddIcon />} variant={'primaryOutline'} onClick={onOpenCreateModal}>
|
||||
{t('common.New Create')}
|
||||
</Button>
|
||||
)}
|
||||
<Button leftIcon={<AddIcon />} variant={'primaryOutline'} onClick={onOpenCreateModal}>
|
||||
{t('common.New Create')}
|
||||
</Button>
|
||||
</Flex>
|
||||
<Grid
|
||||
py={[4, 6]}
|
||||
@@ -171,6 +169,10 @@ 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'} />
|
||||
|
||||
547
projects/app/src/pages/chat/team.tsx
Normal file
547
projects/app/src/pages/chat/team.tsx
Normal file
@@ -0,0 +1,547 @@
|
||||
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 = ({
|
||||
shareTeamId,
|
||||
appId,
|
||||
chatId,
|
||||
authToken
|
||||
}: {
|
||||
shareTeamId: string;
|
||||
appId: string;
|
||||
chatId: string;
|
||||
authToken: string;
|
||||
}) => {
|
||||
type routerQueryType = {
|
||||
chatId?: string;
|
||||
appId?: string;
|
||||
shareTeamId: 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 (!shareTeamId) {
|
||||
toast({
|
||||
status: 'error',
|
||||
title: t('core.chat.You need to a chat app')
|
||||
});
|
||||
return;
|
||||
}
|
||||
// 根据获取历史记录列表
|
||||
const res = await getChatListById({ shareTeamId, authToken });
|
||||
const { apps, teamInfo } = res;
|
||||
setMyApps(apps);
|
||||
setTeamInfo(teamInfo);
|
||||
if (apps.length <= 0) {
|
||||
return toast({
|
||||
status: 'error',
|
||||
title: t('core.chat.You need to a chat app')
|
||||
});
|
||||
}
|
||||
if (!apps.find((obj) => obj._id === appId)) {
|
||||
toast({
|
||||
status: 'warning',
|
||||
title: 'you do not have this App'
|
||||
});
|
||||
router.replace({
|
||||
query: {
|
||||
appId: apps[0]?._id,
|
||||
shareTeamId,
|
||||
authToken: authToken
|
||||
} as routerQueryType
|
||||
});
|
||||
}
|
||||
} catch (error: any) {
|
||||
toast({
|
||||
status: 'warning',
|
||||
title: error?.message
|
||||
});
|
||||
}
|
||||
}, [appId, authToken, router, shareTeamId, t, toast]);
|
||||
|
||||
const startChat = useCallback(
|
||||
async ({ messages, controller, generatingMessage, variables }: StartChatFnProps) => {
|
||||
const prompts = messages.slice(-2);
|
||||
const completionChatId = chatId ? chatId : nanoid();
|
||||
|
||||
const { responseText, responseData } = await streamFetch({
|
||||
data: {
|
||||
messages: prompts,
|
||||
variables,
|
||||
appId,
|
||||
shareTeamId,
|
||||
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,
|
||||
shareTeamId,
|
||||
authToken: authToken
|
||||
} as routerQueryType
|
||||
});
|
||||
}
|
||||
} 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, shareTeamId], async () => {
|
||||
console.log('res', 3);
|
||||
if (!shareTeamId) {
|
||||
toast({
|
||||
status: 'error',
|
||||
title: t('core.chat.You need to a chat app')
|
||||
});
|
||||
return;
|
||||
}
|
||||
return shareTeamId && loadApps();
|
||||
});
|
||||
|
||||
useQuery(['loadHistories', appId], () => {
|
||||
if (shareTeamId && appId) {
|
||||
return loadHistories({ appId, outLinkUid });
|
||||
}
|
||||
return;
|
||||
});
|
||||
// 初始化聊天框
|
||||
useQuery(['init', { appId, chatId }], () => {
|
||||
if (!shareTeamId) {
|
||||
toast({
|
||||
status: 'error',
|
||||
title: t('core.chat.You need to a chat app')
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (myApps.length > 0 && myApps.findIndex((obj) => obj._id === appId) === -1) {
|
||||
toast({
|
||||
status: 'warning',
|
||||
title: 'you do not have this App'
|
||||
});
|
||||
return;
|
||||
}
|
||||
// pc: redirect to latest model chat
|
||||
if (!appId && lastChatAppId) {
|
||||
return router.replace({
|
||||
query: {
|
||||
appId: lastChatAppId,
|
||||
chatId: lastChatId,
|
||||
shareTeamId,
|
||||
authToken: authToken
|
||||
} as routerQueryType
|
||||
});
|
||||
}
|
||||
if (!appId && myApps[0]) {
|
||||
return router.replace({
|
||||
query: {
|
||||
appId: myApps[0]._id,
|
||||
chatId: lastChatId,
|
||||
shareTeamId,
|
||||
authToken: authToken
|
||||
} as routerQueryType
|
||||
});
|
||||
}
|
||||
if (!appId) {
|
||||
(async () => {
|
||||
const { apps = [] } = await getChatListById({ shareTeamId, 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,
|
||||
shareTeamId,
|
||||
authToken: authToken
|
||||
} as routerQueryType
|
||||
});
|
||||
}
|
||||
})();
|
||||
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 (!shareTeamId) {
|
||||
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: ''
|
||||
} as routerQueryType
|
||||
});
|
||||
}
|
||||
}
|
||||
setIsLoading(false);
|
||||
return null;
|
||||
},
|
||||
[setIsLoading, setChatData, router, setLastChatAppId, setLastChatId, toast]
|
||||
);
|
||||
// 监测路由改变
|
||||
useEffect(() => {
|
||||
const activeHistory = teamShareChatHistory.filter((item) => !item.delete);
|
||||
if (!localUId || !shareTeamId || 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, shareTeamId, 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 &&
|
||||
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,
|
||||
shareTeamId,
|
||||
authToken: authToken
|
||||
} as routerQueryType
|
||||
});
|
||||
}
|
||||
})}
|
||||
>
|
||||
<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,
|
||||
shareTeamId,
|
||||
authToken: authToken
|
||||
} as routerQueryType
|
||||
});
|
||||
if (!isPc) {
|
||||
onCloseSlider();
|
||||
}
|
||||
}}
|
||||
onDelHistory={(e) => delOneHistory({ ...e, appId })}
|
||||
onClearHistory={() => {
|
||||
clearHistories({ appId });
|
||||
router.replace({
|
||||
query: {
|
||||
appId,
|
||||
shareTeamId,
|
||||
authToken: authToken
|
||||
} as routerQueryType
|
||||
});
|
||||
}}
|
||||
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}
|
||||
shareTeamId={shareTeamId}
|
||||
chatId={chatId}
|
||||
outLinkUid={outLinkUid}
|
||||
/>
|
||||
</Box>
|
||||
</Flex>
|
||||
</Flex>
|
||||
</PageContainer>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
export async function getServerSideProps(context: any) {
|
||||
const shareTeamId = context?.query?.shareTeamId || '';
|
||||
const appId = context?.query?.appId || '';
|
||||
const chatId = context?.query?.chatId || '';
|
||||
const authToken: string = context?.query?.authToken || '';
|
||||
|
||||
return {
|
||||
props: {
|
||||
shareTeamId,
|
||||
appId,
|
||||
chatId,
|
||||
authToken,
|
||||
...(await serviceSideProps(context))
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export default OutLink;
|
||||
@@ -1,6 +1,5 @@
|
||||
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';
|
||||
@@ -34,7 +33,7 @@ type useImportStoreType = {
|
||||
totalChunkChars: number;
|
||||
totalChunks: number;
|
||||
chunkSize: number;
|
||||
predictPrice: number;
|
||||
predictPoints: number;
|
||||
priceTip: string;
|
||||
uploadRate: number;
|
||||
splitSources2Chunks: () => void;
|
||||
@@ -54,7 +53,7 @@ const StateContext = createContext<useImportStoreType>({
|
||||
totalChunkChars: 0,
|
||||
totalChunks: 0,
|
||||
chunkSize: 0,
|
||||
predictPrice: 0,
|
||||
predictPoints: 0,
|
||||
priceTip: '',
|
||||
uploadRate: 50,
|
||||
splitSources2Chunks: () => {}
|
||||
@@ -105,10 +104,9 @@ const Provider = ({
|
||||
chunkSize: embeddingChunkSize,
|
||||
showChunkInput: true,
|
||||
showPromptInput: false,
|
||||
inputPrice: vectorModel.inputPrice,
|
||||
outputPrice: 0,
|
||||
charsPointsPrice: vectorModel.charsPointsPrice,
|
||||
priceTip: t('core.dataset.import.Embedding Estimated Price Tips', {
|
||||
price: vectorModel.inputPrice
|
||||
price: vectorModel.charsPointsPrice
|
||||
}),
|
||||
uploadRate: 150
|
||||
},
|
||||
@@ -120,10 +118,9 @@ const Provider = ({
|
||||
chunkSize: agentModel.maxContext * 0.55 || 6000,
|
||||
showChunkInput: false,
|
||||
showPromptInput: true,
|
||||
inputPrice: agentModel.inputPrice,
|
||||
outputPrice: agentModel.outputPrice,
|
||||
charsPointsPrice: agentModel.charsPointsPrice,
|
||||
priceTip: t('core.dataset.import.QA Estimated Price Tips', {
|
||||
price: agentModel?.inputPrice
|
||||
price: agentModel?.charsPointsPrice
|
||||
}),
|
||||
uploadRate: 30
|
||||
}
|
||||
@@ -151,15 +148,12 @@ const Provider = ({
|
||||
() => sources.reduce((sum, file) => sum + file.chunkChars, 0),
|
||||
[sources]
|
||||
);
|
||||
const predictPrice = useMemo(() => {
|
||||
const predictPoints = useMemo(() => {
|
||||
if (mode === TrainingModeEnum.qa) {
|
||||
const inputTotal = totalChunkChars * selectModelStaticParam.inputPrice;
|
||||
const outputTotal = totalChunkChars * 0.5 * selectModelStaticParam.inputPrice;
|
||||
|
||||
return formatModelPrice2Read(inputTotal + outputTotal);
|
||||
return +(((totalChunkChars * 1.5) / 1000) * agentModel.charsPointsPrice).toFixed(2);
|
||||
}
|
||||
return formatModelPrice2Read(totalChunkChars * selectModelStaticParam.inputPrice);
|
||||
}, [mode, selectModelStaticParam.inputPrice, totalChunkChars]);
|
||||
return +((totalChunkChars / 1000) * vectorModel.charsPointsPrice).toFixed(2);
|
||||
}, [agentModel.charsPointsPrice, mode, totalChunkChars, vectorModel.charsPointsPrice]);
|
||||
const totalChunks = useMemo(
|
||||
() => sources.reduce((sum, file) => sum + file.chunks.length, 0),
|
||||
[sources]
|
||||
@@ -178,7 +172,8 @@ const Provider = ({
|
||||
return {
|
||||
...file,
|
||||
chunkChars: chars,
|
||||
chunks: chunks.map((chunk) => ({
|
||||
chunks: chunks.map((chunk, i) => ({
|
||||
chunkIndex: i,
|
||||
q: chunk,
|
||||
a: ''
|
||||
}))
|
||||
@@ -198,7 +193,7 @@ const Provider = ({
|
||||
totalChunkChars,
|
||||
totalChunks,
|
||||
chunkSize,
|
||||
predictPrice,
|
||||
predictPoints,
|
||||
splitSources2Chunks
|
||||
};
|
||||
return <StateContext.Provider value={value}>{children}</StateContext.Provider>;
|
||||
|
||||
@@ -46,7 +46,7 @@ function DataProcess({
|
||||
maxChunkSize,
|
||||
totalChunkChars,
|
||||
totalChunks,
|
||||
predictPrice,
|
||||
predictPoints,
|
||||
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 Price', { amount: predictPrice, unit: '元' })}
|
||||
{t('core.dataset.import.Estimated points', { points: predictPoints })}
|
||||
</Tag>
|
||||
</MyTooltip>
|
||||
)}
|
||||
|
||||
@@ -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 { postCreateTrainingBill } from '@/web/support/wallet/bill/api';
|
||||
import { postCreateTrainingUsage } from '@/web/support/wallet/usage/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,11 +54,6 @@ const Upload = ({ showPreviewChunks }: { showPreviewChunks: boolean }) => {
|
||||
|
||||
// Batch create collection and upload chunks
|
||||
for await (const item of uploadList) {
|
||||
const billId = await postCreateTrainingBill({
|
||||
name: item.sourceName,
|
||||
datasetId: datasetDetail._id
|
||||
});
|
||||
|
||||
// create collection
|
||||
const collectionId = await (async () => {
|
||||
const commonParams = {
|
||||
@@ -125,6 +120,12 @@ const Upload = ({ showPreviewChunks }: { showPreviewChunks: boolean }) => {
|
||||
})();
|
||||
|
||||
if (!collectionId) continue;
|
||||
if (item.link) continue;
|
||||
|
||||
const billId = await postCreateTrainingUsage({
|
||||
name: item.sourceName,
|
||||
datasetId: datasetDetail._id
|
||||
});
|
||||
|
||||
// upload chunks
|
||||
const chunks = item.chunks;
|
||||
|
||||
@@ -19,7 +19,6 @@ 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';
|
||||
@@ -118,8 +117,7 @@ const InputDataModal = ({
|
||||
} else if (defaultValue) {
|
||||
reset({
|
||||
q: defaultValue.q,
|
||||
a: defaultValue.a,
|
||||
indexes: [getDefaultIndex({ dataId: `${Date.now()}` })]
|
||||
a: defaultValue.a
|
||||
});
|
||||
}
|
||||
},
|
||||
@@ -149,10 +147,7 @@ const InputDataModal = ({
|
||||
return Promise.reject(t('dataset.data.input is empty'));
|
||||
}
|
||||
if (countPromptTokens(e.q) >= maxToken) {
|
||||
return toast({
|
||||
title: t('core.dataset.data.Too Long'),
|
||||
status: 'warning'
|
||||
});
|
||||
return Promise.reject(t('core.dataset.data.Too Long'));
|
||||
}
|
||||
|
||||
const data = { ...e };
|
||||
@@ -162,9 +157,11 @@ const InputDataModal = ({
|
||||
q: e.q,
|
||||
a: e.a,
|
||||
// remove dataId
|
||||
indexes: e.indexes.map((index) =>
|
||||
index.defaultIndex ? getDefaultIndex({ q: e.q, a: e.a }) : index
|
||||
)
|
||||
indexes:
|
||||
e.indexes?.map((index) => ({
|
||||
...index,
|
||||
dataId: undefined
|
||||
})) || []
|
||||
});
|
||||
|
||||
return {
|
||||
@@ -178,7 +175,7 @@ const InputDataModal = ({
|
||||
...e,
|
||||
q: '',
|
||||
a: '',
|
||||
indexes: [getDefaultIndex({ q: e.q, a: e.a, dataId: `${Date.now()}` })]
|
||||
indexes: []
|
||||
});
|
||||
|
||||
onSuccess(e);
|
||||
@@ -194,9 +191,10 @@ const InputDataModal = ({
|
||||
await putDatasetDataById({
|
||||
id: dataId,
|
||||
...e,
|
||||
indexes: e.indexes.map((index) =>
|
||||
index.defaultIndex ? getDefaultIndex({ q: e.q, a: e.a }) : index
|
||||
)
|
||||
indexes:
|
||||
e.indexes?.map((index) =>
|
||||
index.defaultIndex ? getDefaultIndex({ q: e.q, a: e.a, dataId: index.dataId }) : index
|
||||
) || []
|
||||
});
|
||||
|
||||
return {
|
||||
@@ -269,7 +267,7 @@ const InputDataModal = ({
|
||||
{currentTab === TabEnum.content && <InputTab maxToken={maxToken} register={register} />}
|
||||
{currentTab === TabEnum.index && (
|
||||
<Grid gridTemplateColumns={['1fr', '1fr 1fr']} gridGap={4}>
|
||||
{indexes.map((index, i) => (
|
||||
{indexes?.map((index, i) => (
|
||||
<Box
|
||||
key={index.dataId || i}
|
||||
p={3}
|
||||
@@ -278,7 +276,7 @@ const InputDataModal = ({
|
||||
bg={i % 2 !== 0 ? 'myWhite.400' : ''}
|
||||
_hover={{
|
||||
'& .delete': {
|
||||
display: index.defaultIndex && indexes.length === 1 ? 'none' : 'block'
|
||||
display: index.defaultIndex ? 'none' : 'block'
|
||||
}
|
||||
}}
|
||||
>
|
||||
@@ -331,7 +329,6 @@ const InputDataModal = ({
|
||||
onClick={() =>
|
||||
appendIndexes({
|
||||
defaultIndex: false,
|
||||
type: DatasetDataIndexTypeEnum.chunk,
|
||||
text: '',
|
||||
dataId: `${Date.now()}`
|
||||
})
|
||||
@@ -383,45 +380,47 @@ const InputTab = ({
|
||||
const [inputType, setInputType] = useState(InputTypeEnum.q);
|
||||
|
||||
return (
|
||||
<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)}
|
||||
/>
|
||||
<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 mt={3}>
|
||||
<Box mt={3} flex={'1 0 0'}>
|
||||
{inputType === InputTypeEnum.q && (
|
||||
<Textarea
|
||||
placeholder={t('core.dataset.data.Data Content Placeholder', { maxToken })}
|
||||
maxLength={maxToken}
|
||||
rows={isPc ? 24 : 12}
|
||||
h={'100%'}
|
||||
bg={'myWhite.400'}
|
||||
{...register(`q`, {
|
||||
required: true
|
||||
@@ -433,6 +432,7 @@ 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}
|
||||
@@ -440,6 +440,6 @@ const InputTab = ({
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -27,6 +27,8 @@ 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';
|
||||
import MyBox from '@/components/common/MyBox';
|
||||
|
||||
const DataCard = dynamic(() => import('./components/DataCard'));
|
||||
const Test = dynamic(() => import('./components/Test'));
|
||||
@@ -145,9 +147,17 @@ const Detail = ({ datasetId, currentTab }: { datasetId: string; currentTab: `${T
|
||||
|
||||
return (
|
||||
<>
|
||||
<Script src="/js/pdf.js" strategy="lazyOnload"></Script>
|
||||
<Head>
|
||||
<title>{datasetDetail?.name}</title>
|
||||
</Head>
|
||||
<PageContainer>
|
||||
<Flex flexDirection={['column', 'row']} h={'100%'} pt={[4, 0]}>
|
||||
<MyBox
|
||||
isLoading={isUpdating}
|
||||
display={'flex'}
|
||||
flexDirection={['column', 'row']}
|
||||
h={'100%'}
|
||||
pt={[4, 0]}
|
||||
>
|
||||
{isPc ? (
|
||||
<Flex
|
||||
flexDirection={'column'}
|
||||
@@ -274,9 +284,9 @@ const Detail = ({ datasetId, currentTab }: { datasetId: string; currentTab: `${T
|
||||
{currentTab === TabEnum.import && <Import />}
|
||||
</Box>
|
||||
)}
|
||||
</Flex>
|
||||
</MyBox>
|
||||
</PageContainer>
|
||||
<ConfirmSyncModal isLoading={isUpdating} />
|
||||
<ConfirmSyncModal />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -7,92 +7,110 @@ import {
|
||||
NumberIncrementStepper,
|
||||
NumberInputField,
|
||||
NumberInputStepper,
|
||||
Button,
|
||||
useDisclosure,
|
||||
ModalBody,
|
||||
ModalFooter
|
||||
Button
|
||||
} from '@chakra-ui/react';
|
||||
import { TeamSubSchema } from '@fastgpt/global/support/wallet/sub/type';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
import React, { useCallback, 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 { 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';
|
||||
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';
|
||||
|
||||
const ExtraPlan = ({ extraDatasetSize }: { extraDatasetSize?: TeamSubSchema }) => {
|
||||
const ExtraPlan = () => {
|
||||
const { t } = useTranslation();
|
||||
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 [datasetSize, setDatasetSize] = useState(0);
|
||||
const [isRenew, setIsRenew] = useState('false');
|
||||
const router = useRouter();
|
||||
const { userInfo } = useUserStore();
|
||||
|
||||
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
|
||||
});
|
||||
},
|
||||
successToast: t('common.Update success'),
|
||||
errorToast: t('common.error.Update error')
|
||||
});
|
||||
|
||||
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 { register: registerDatasetSize, handleSubmit: handleSubmitDatasetSize } = useForm({
|
||||
defaultValues: {
|
||||
datasetSize: 0,
|
||||
month: 1
|
||||
}
|
||||
);
|
||||
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')
|
||||
});
|
||||
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 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);
|
||||
},
|
||||
[extraDatasetPrice, toast]
|
||||
);
|
||||
|
||||
// extra ai points
|
||||
const extraPointsPrice = subPlans?.extraPoints?.price || 0;
|
||||
const { register: registerExtraPoints, handleSubmit: handleSubmitExtraPoints } = useForm({
|
||||
defaultValues: {
|
||||
points: 0,
|
||||
month: 1
|
||||
}
|
||||
});
|
||||
const onclickBuyExtraPoints = useCallback(
|
||||
async ({ points }: { points: number }) => {
|
||||
try {
|
||||
const month = 1;
|
||||
const payAmount = points * month * extraPointsPrice;
|
||||
|
||||
if (payAmount === 0) {
|
||||
return toast({
|
||||
status: 'warning',
|
||||
title: '购买数量不能为0'
|
||||
});
|
||||
}
|
||||
setLoading(true);
|
||||
|
||||
const res = await getWxPayQRCode({
|
||||
type: BillTypeEnum.extraPoints,
|
||||
extraPoints: points
|
||||
});
|
||||
|
||||
setQRPayData({
|
||||
readPrice: res.readPrice,
|
||||
codeUrl: res.codeUrl,
|
||||
billId: res.billId
|
||||
});
|
||||
} catch (err) {
|
||||
toast({
|
||||
title: getErrText(err),
|
||||
status: 'error'
|
||||
});
|
||||
}
|
||||
setLoading(false);
|
||||
},
|
||||
[extraPointsPrice, toast]
|
||||
);
|
||||
|
||||
return (
|
||||
<Flex
|
||||
@@ -101,13 +119,13 @@ const ExtraPlan = ({ extraDatasetSize }: { extraDatasetSize?: TeamSubSchema }) =
|
||||
alignItems={'center'}
|
||||
position={'relative'}
|
||||
>
|
||||
<Box fontWeight={'bold'} fontSize={['24px', '36px']}>
|
||||
<Box id={'extra-plan'} 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']}>
|
||||
<Grid mt={8} gridTemplateColumns={['1fr', '1fr 1fr']} gap={5} w={['100%', 'auto']}>
|
||||
<Box
|
||||
bg={'rgba(255, 255, 255, 0.90)'}
|
||||
px={'32px'}
|
||||
@@ -116,78 +134,60 @@ const ExtraPlan = ({ extraDatasetSize }: { extraDatasetSize?: TeamSubSchema }) =
|
||||
borderWidth={'1px'}
|
||||
borderColor={'myGray.150'}
|
||||
boxShadow={'1.5'}
|
||||
w={['100%', '500px']}
|
||||
>
|
||||
<Flex w={['100%', '500px']} borderBottomWidth={'1px'} borderBottomColor={'myGray.200'}>
|
||||
<Flex 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={['32px', '38px']} fontWeight={'bold'}>
|
||||
¥{extraDatasetPrice}/1k组{' '}
|
||||
<Box mt={3} fontSize={['28px', '32px']} fontWeight={'bold'}>
|
||||
¥{extraDatasetPrice}/1000组{' '}
|
||||
<Box ml={1} as={'span'} fontSize={'lg'} color={'myGray.600'} fontWeight={'normal'}>
|
||||
/{t('common.month')}
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
<MyIcon
|
||||
transform={'translate(20px,-20px)'}
|
||||
name={'support/pay/extraDatasetsize'}
|
||||
display={['none', 'block']}
|
||||
mt={'-30px'}
|
||||
transform={'translateX(20px)'}
|
||||
name={'support/bill/extraDatasetsize'}
|
||||
fill={'none'}
|
||||
/>
|
||||
</Flex>
|
||||
<Box>
|
||||
<Box h={'120px'} w={'100%'}>
|
||||
<Flex mt={4}>
|
||||
<Box flex={'0 0 200px'}>
|
||||
{t('support.wallet.subscription.Current dataset store')}:{' '}
|
||||
</Box>
|
||||
<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);
|
||||
}}
|
||||
/>
|
||||
<MyIcon mr={2} name={'support/bill/shoppingCart'} w={'16px'} color={'primary.600'} />
|
||||
购买资源包
|
||||
</Flex>
|
||||
<Flex mt={4} alignItems={'center'}>
|
||||
<Box flex={'0 0 200px'}>
|
||||
<Box flex={['0 0 100px', '1 0 0']}>
|
||||
{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'}
|
||||
{...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>
|
||||
</Flex>
|
||||
</Flex>
|
||||
<Flex mt={4} alignItems={'center'}>
|
||||
<Box flex={['0 0 100px', '1 0 0']}>
|
||||
{t('support.wallet.subscription.Update extra dataset size')}
|
||||
</Box>
|
||||
<Flex alignItems={'center'} mt={1} w={'180px'} position={'relative'}>
|
||||
@@ -197,13 +197,18 @@ const ExtraPlan = ({ extraDatasetSize }: { extraDatasetSize?: TeamSubSchema }) =
|
||||
min={0}
|
||||
max={10000}
|
||||
step={1}
|
||||
value={datasetSize}
|
||||
position={'relative'}
|
||||
onChange={(e) => {
|
||||
setDatasetSize(Number(e));
|
||||
}}
|
||||
>
|
||||
<NumberInputField pr={'30px'} value={datasetSize} step={1} min={0} max={10000} />
|
||||
<NumberInputField
|
||||
pr={'30px'}
|
||||
{...registerDatasetSize('datasetSize', {
|
||||
required: true,
|
||||
min: 0,
|
||||
max: 10000,
|
||||
valueAsNumber: true
|
||||
})}
|
||||
step={1}
|
||||
/>
|
||||
<NumberInputStepper>
|
||||
<NumberIncrementStepper />
|
||||
<NumberDecrementStepper />
|
||||
@@ -214,89 +219,125 @@ const ExtraPlan = ({ extraDatasetSize }: { extraDatasetSize?: TeamSubSchema }) =
|
||||
</Box>
|
||||
</Flex>
|
||||
</Flex>
|
||||
<Button
|
||||
isDisabled={datasetSize * 1000 === extraDatasetSize?.nextExtraDatasetSize}
|
||||
mt={6}
|
||||
w={'100%'}
|
||||
variant={'primaryGhost'}
|
||||
isLoading={isPayingExtraDatasetSize || isFetchingPreviewCheck}
|
||||
onClick={onClickPreviewCheck}
|
||||
>
|
||||
{t('common.change')}
|
||||
</Button>
|
||||
</Box>
|
||||
<Button
|
||||
mt={6}
|
||||
w={'100%'}
|
||||
variant={'primaryGhost'}
|
||||
isLoading={loading}
|
||||
onClick={handleSubmitDatasetSize(onclickBuyDatasetSize)}
|
||||
>
|
||||
{t('support.wallet.Buy')}
|
||||
</Button>
|
||||
</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', 'block']}
|
||||
mt={'-30px'}
|
||||
transform={'translateX(20px)'}
|
||||
name={'support/bill/extraPoints'}
|
||||
fill={'none'}
|
||||
/>
|
||||
</Flex>
|
||||
<Box h={'120px'} w={'100%'}>
|
||||
<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', '1 0 0']}>
|
||||
{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', '1 0 0']}>
|
||||
{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>
|
||||
</Box>
|
||||
<Button
|
||||
mt={6}
|
||||
w={'100%'}
|
||||
variant={'primaryGhost'}
|
||||
isLoading={loading}
|
||||
onClick={handleSubmitExtraPoints(onclickBuyExtraPoints)}
|
||||
>
|
||||
{t('support.wallet.Buy')}
|
||||
</Button>
|
||||
</Box>
|
||||
</Grid>
|
||||
|
||||
{/* 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>
|
||||
)}
|
||||
{!!qrPayData && <QRCodePayModal {...qrPayData} />}
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -4,7 +4,40 @@ import { useTranslation } from 'next-i18next';
|
||||
|
||||
const FAQ = () => {
|
||||
const { t } = useTranslation();
|
||||
const faqs = [{ title: '怎么付费', describe: '2222' }];
|
||||
const faqs = [
|
||||
{
|
||||
title: '订阅套餐会自动续费么?',
|
||||
desc: '当前套餐过期后,系统会自动根据“未来套餐”进行续费,系统会尝试从账户余额进行扣费,如果您需要自动续费,请在账户余额中预留额度。'
|
||||
},
|
||||
{
|
||||
title: '能否切换订阅套餐?',
|
||||
desc: '当前套餐价格大于新套餐时,无法立即切换,将会在当前套餐过期后以“续费”形式进行切换。\n当前套餐价格小于新套餐时,系统会自动计算当前套餐剩余余额,您可支付差价进行套餐切换。'
|
||||
},
|
||||
{
|
||||
title: '什么是AI积分?',
|
||||
desc: '每次调用AI模型时,都会消耗一定的AI积分。具体的计算标准可参考上方的“AI 积分计算标准”。\n1 字符=1中英文字符和标点符号,会去掉换行和空格符号,计算字符时包含对话上下文与知识库引用。'
|
||||
},
|
||||
{
|
||||
title: 'AI积分会过期么?',
|
||||
desc: '会过期。当前套餐过期后,AI积分将会清空,并更新为新套餐的AI积分。年度套餐的AI积分时长为1年,而不是每个月。'
|
||||
},
|
||||
{
|
||||
title: '知识库存储怎么计算?',
|
||||
desc: '1条知识库存储等于1条知识库索引。一条知识库数据可以包含1条或多条知识库索引。'
|
||||
},
|
||||
{
|
||||
title: '知识库索引超出会删除么?',
|
||||
desc: '不会。但知识库索引超出时,无法插入和更新知识库内容。'
|
||||
},
|
||||
{
|
||||
title: '额外资源包可以叠加么?',
|
||||
desc: '可以的。每次购买的资源包都是独立的,在其有效期内将会叠加使用。AI积分会优先扣除最先过期的资源包。'
|
||||
},
|
||||
{
|
||||
title: '免费版数据会清除么?',
|
||||
desc: '免费版用户15天无使用记录后,会自动清除所有知识库内容。'
|
||||
}
|
||||
];
|
||||
|
||||
return (
|
||||
<Flex
|
||||
@@ -18,22 +51,25 @@ const FAQ = () => {
|
||||
{t('support.wallet.subscription.FAQ')}
|
||||
</Box>
|
||||
<Grid mt={4} gridTemplateColumns={['1fr', '1fr 1fr']} gap={4} w={'100%'}>
|
||||
<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'}>
|
||||
怎么付费
|
||||
{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>
|
||||
<Box color={'myGray.500'}>2222</Box>
|
||||
</Box>
|
||||
))}
|
||||
</Grid>
|
||||
</Flex>
|
||||
);
|
||||
|
||||
@@ -15,7 +15,7 @@ const Points = () => {
|
||||
alignItems={'center'}
|
||||
position={'relative'}
|
||||
>
|
||||
<Box fontWeight={'bold'} fontSize={['24px', '36px']}>
|
||||
<Box id="point-card" 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'}>5积分 / 1000字符</Box>
|
||||
<Box flex={'1 0 0'}>{item.charsPointsPrice}积分 / 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'}>5积分 / 1000字符</Box>
|
||||
<Box flex={'1 0 0'}>{item.charsPointsPrice}积分 / 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'}>5积分 / 1000字符</Box>
|
||||
<Box flex={'1 0 0'}>{item.charsPointsPrice}积分 / 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?.inputPrice}积分 / 分钟</Box>
|
||||
<Box flex={'1 0 0'}>{whisperModel?.charsPointsPrice}积分 / 分钟</Box>
|
||||
</Flex>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user