diff --git a/docSite/content/zh-cn/docs/development/upgrading/4810.md b/docSite/content/zh-cn/docs/development/upgrading/4810.md index e0b44a6f8386..667c6200d30d 100644 --- a/docSite/content/zh-cn/docs/development/upgrading/4810.md +++ b/docSite/content/zh-cn/docs/development/upgrading/4810.md @@ -55,30 +55,32 @@ curl --location --request POST 'https://{{host}}/api/admin/initv4810' \ 6. 新增 - 工作流版本支持重命名 7. 新增 - 应用调用迁移成单独节点,同时可以传递全局变量和用户的文件。 8. 新增 - 插件增加使用说明配置。 -9. 商业版新增 - 飞书机器人接入 -10. 商业版新增 - 公众号接入接入 -11. 商业版新增 - 自助开票申请 -12. 商业版新增 - SSO 定制 -13. 优化 - SSE 响应优化。 -14. 优化 - 无 SSL 证书情况下,优化复制。 -15. 优化 - 单选框打开后自动滚动到选中的位置。 -16. 优化 - 知识库集合禁用,目录禁用会递归修改其下所有 children 的禁用状态。 -17. 优化 - 节点选择,避免切换 tab 时候,path 加载报错。 -18. 优化 - 最新 React Markdown 组件,支持 Base64 图片。 -19. 优化 - 知识库列表 UI。 -20. 优化 - 知识库详情页 UI。 -21. 优化 - 支持无网络配置情况下运行。 -22. 优化 - 部分全局变量,增加数据类型约束。 -23. 修复 - 全局变量 key 可能重复。 -24. 修复 - Prompt 模式调用工具,stream=false 模式下,会携带 0: 开头标记。 -25. 修复 - 对话日志鉴权问题:仅为 APP 管理员的用户,无法查看对话日志详情。 -26. 修复 - 选择 Milvus 部署时,无法导出知识库。 -27. 修复 - 创建 APP 副本,无法复制系统配置。 -28. 修复 - 图片识别模式下,自动解析图片链接正则不够严谨问题。 -29. 修复 - 内容提取的数据类型与输出数据类型未一致。 -30. 修复 - 工作流运行时间统计错误。 -31. 修复 - stream 模式下,工具调用有可能出现 undefined -32. 修复 - 全局变量在 API 中无法持久化。 -33. 修复 - OpenAPI,detail=false模式下,不应该返回 tool 调用结果,仅返回文字。(可解决 cow 不适配问题) -34. 修复 - 知识库标签重复加载。 -35. 修复 - Debug 模式下,循环调用边问题。 +9. 新增 - 工作流导出导入,支持直接导出和导入 JSON 文件,便于交流。 +10. 商业版新增 - 飞书机器人接入 +11. 商业版新增 - 公众号接入接入 +12. 商业版新增 - 自助开票申请 +13. 商业版新增 - SSO 定制 +14. 优化 - 工作流循环校验,避免 skip 循环空转。同时支持分支完全并发执行。 +15. 优化 - SSE 响应优化。 +16. 优化 - 无 SSL 证书情况下,优化复制。 +17. 优化 - 单选框打开后自动滚动到选中的位置。 +18. 优化 - 知识库集合禁用,目录禁用会递归修改其下所有 children 的禁用状态。 +19. 优化 - 节点选择,避免切换 tab 时候,path 加载报错。 +20. 优化 - 最新 React Markdown 组件,支持 Base64 图片。 +21. 优化 - 知识库列表 UI。 +22. 优化 - 知识库详情页 UI。 +23. 优化 - 支持无网络配置情况下运行。 +24. 优化 - 部分全局变量,增加数据类型约束。 +25. 修复 - 全局变量 key 可能重复。 +26. 修复 - Prompt 模式调用工具,stream=false 模式下,会携带 0: 开头标记。 +27. 修复 - 对话日志鉴权问题:仅为 APP 管理员的用户,无法查看对话日志详情。 +28. 修复 - 选择 Milvus 部署时,无法导出知识库。 +29. 修复 - 创建 APP 副本,无法复制系统配置。 +30. 修复 - 图片识别模式下,自动解析图片链接正则不够严谨问题。 +31. 修复 - 内容提取的数据类型与输出数据类型未一致。 +32. 修复 - 工作流运行时间统计错误。 +33. 修复 - stream 模式下,工具调用有可能出现 undefined +34. 修复 - 全局变量在 API 中无法持久化。 +35. 修复 - OpenAPI,detail=false模式下,不应该返回 tool 调用结果,仅返回文字。(可解决 cow 不适配问题) +36. 修复 - 知识库标签重复加载。 +37. 修复 - Debug 模式下,循环调用边问题。 diff --git a/packages/global/core/workflow/runtime/type.d.ts b/packages/global/core/workflow/runtime/type.d.ts index a084445f42de..d164d3f5b7ff 100644 --- a/packages/global/core/workflow/runtime/type.d.ts +++ b/packages/global/core/workflow/runtime/type.d.ts @@ -26,8 +26,8 @@ export type ChatDispatchProps = { res?: NextApiResponse; requestOrigin?: string; mode: 'test' | 'chat' | 'debug'; - teamId: string; - tmbId: string; + teamId: string; // App teamId + tmbId: string; // App tmbId user: UserModelSchema; app: AppDetailType | AppSchema; chatId?: string; diff --git a/packages/service/core/workflow/dispatch/index.ts b/packages/service/core/workflow/dispatch/index.ts index a3e4a829137c..b4238d14200b 100644 --- a/packages/service/core/workflow/dispatch/index.ts +++ b/packages/service/core/workflow/dispatch/index.ts @@ -63,7 +63,7 @@ import { InteractiveNodeResponseItemType, UserSelectInteractive } from '@fastgpt/global/core/workflow/template/system/userSelect/type'; -import { dispatchRunAppNode } from './agent/runApp'; +import { dispatchRunAppNode } from './plugin/runApp'; const callbackMap: Record = { [FlowNodeTypeEnum.workflowStart]: dispatchWorkflowStart, @@ -186,7 +186,10 @@ export async function dispatchWorkFlow(data: Props): Promise = {} - ): RuntimeNodeItemType[] { + ): { + nextStepActiveNodes: RuntimeNodeItemType[]; + nextStepSkipNodes: RuntimeNodeItemType[]; + } { pushStore(node, result); // Assign the output value to the next node @@ -211,16 +214,32 @@ export async function dispatchWorkFlow(data: Props): Promise { - return targetEdges.some((item) => item.target === node.nodeId); + const nextStepActiveNodes: RuntimeNodeItemType[] = []; + const nextStepSkipNodes: RuntimeNodeItemType[] = []; + runtimeNodes.forEach((node) => { + if (targetEdges.some((item) => item.target === node.nodeId && item.status === 'active')) { + nextStepActiveNodes.push(node); + } + if (targetEdges.some((item) => item.target === node.nodeId && item.status === 'skipped')) { + nextStepSkipNodes.push(node); + } }); if (props.mode === 'debug') { - debugNextStepRunNodes = debugNextStepRunNodes.concat(nextStepNodes); - return []; + debugNextStepRunNodes = debugNextStepRunNodes.concat([ + ...nextStepActiveNodes, + ...nextStepSkipNodes + ]); + return { + nextStepActiveNodes: [], + nextStepSkipNodes: [] + }; } - return nextStepNodes; + return { + nextStepActiveNodes, + nextStepSkipNodes + }; } /* Have interactive result, computed edges and node outputs */ @@ -281,69 +300,82 @@ export async function dispatchWorkFlow(data: Props): Promise { - return Promise.all( - nodes.map(async (node) => { - const status = checkNodeRunStatus({ - node, - runtimeEdges - }); + async function checkNodeCanRun( + node: RuntimeNodeItemType, + skippedNodeIdList = new Set() + ): Promise { + if (res?.closed || props.maxRunTimes <= 0) return []; + // Thread avoidance + await surrenderProcess(); - if (res?.closed || props.maxRunTimes <= 0) return; + addLog.debug(`Run node`, { maxRunTimes: props.maxRunTimes, uid: user._id }); - addLog.debug(`Run node`, { maxRunTimes: props.maxRunTimes, uid: user._id }); + // Get node run status by edges + const status = checkNodeRunStatus({ + node, + runtimeEdges + }); + const nodeRunResult = await (() => { + if (status === 'run') { + props.maxRunTimes--; + addLog.debug(`[dispatchWorkFlow] nodeRunWithActive: ${node.name}`); + return nodeRunWithActive(node); + } + if (status === 'skip' && !skippedNodeIdList.has(node.nodeId)) { + props.maxRunTimes -= 0.1; + skippedNodeIdList.add(node.nodeId); + addLog.debug(`[dispatchWorkFlow] nodeRunWithSkip: ${node.name}`); + return nodeRunWithSkip(node); + } + })(); - // Thread avoidance - await surrenderProcess(); + if (!nodeRunResult) return []; - if (status === 'run') { - addLog.debug(`[dispatchWorkFlow] nodeRunWithActive: ${node.name}`); - return nodeRunWithActive(node); - } - if (status === 'skip') { - addLog.debug(`[dispatchWorkFlow] nodeRunWithSkip: ${node.name}`); - return nodeRunWithSkip(node); - } + // Update the node output at the end of the run and get the next nodes + let { nextStepActiveNodes, nextStepSkipNodes } = nodeOutput( + nodeRunResult.node, + nodeRunResult.result + ); + // Remove repeat nodes(Make sure that the node is only executed once) + nextStepActiveNodes = nextStepActiveNodes.filter( + (node, index, self) => self.findIndex((t) => t.nodeId === node.nodeId) === index + ); + nextStepSkipNodes = nextStepSkipNodes.filter( + (node, index, self) => self.findIndex((t) => t.nodeId === node.nodeId) === index + ); - return; - }) - ).then((result) => { - props.maxRunTimes--; - - const flat = result.flat().filter(Boolean) as unknown as { - node: RuntimeNodeItemType; - runStatus: 'run' | 'skip'; - result: Record; - }[]; - // If there are no running nodes, the workflow is complete - if (flat.length === 0) return; - - // Update the node output at the end of the run and get the next nodes - const nextNodes = flat.map((item) => nodeOutput(item.node, item.result)).flat(); - // Remove repeat nodes(Make sure that the node is only executed once) - const filterNextNodes = nextNodes.filter( - (node, index, self) => self.findIndex((t) => t.nodeId === node.nodeId) === index + // In the current version, only one interactive node is allowed at the same time + const interactiveResponse = nodeRunResult.result?.[DispatchNodeResponseKeyEnum.interactive]; + if (interactiveResponse) { + chatAssistantResponse.push( + handleInteractiveResult({ + entryNodeIds: [nodeRunResult.node.nodeId], + interactiveResponse + }) ); + return []; + } - // In the current version, only one interactive node is allowed at the same time - const haveInteractiveResponse = flat - .map((response) => { - const interactiveResponse = response.result?.[DispatchNodeResponseKeyEnum.interactive]; - if (interactiveResponse) { - chatAssistantResponse.push( - handleInteractiveResult({ - entryNodeIds: [response.node.nodeId], - interactiveResponse - }) - ); - return 1; - } - }) - .filter(Boolean); - if (haveInteractiveResponse.length > 0) return; + // Run next nodes(先运行 run 的,再运行 skip 的) + const nextStepActiveNodesResults = ( + await Promise.all(nextStepActiveNodes.map((node) => checkNodeCanRun(node))) + ).flat(); - return checkNodeCanRun(filterNextNodes); - }); + // 如果已经 active 运行过,不再执行 skip(active 中有闭环) + nextStepSkipNodes = nextStepSkipNodes.filter( + (node) => !nextStepActiveNodesResults.some((item) => item.nodeId === node.nodeId) + ); + + const nextStepSkipNodesResults = ( + await Promise.all(nextStepSkipNodes.map((node) => checkNodeCanRun(node, skippedNodeIdList))) + ).flat(); + + return [ + ...nextStepActiveNodes, + ...nextStepSkipNodes, + ...nextStepActiveNodesResults, + ...nextStepSkipNodesResults + ]; } /* Inject data into module input */ function getNodeRunParams(node: RuntimeNodeItemType) { @@ -396,7 +428,11 @@ export async function dispatchWorkFlow(data: Props): Promise; + }> { // push run status messages if (node.showStatus) { props.workflowStreamResponse?.({ @@ -465,8 +501,12 @@ export async function dispatchWorkFlow(data: Props): Promise; + }> { + // Set target edges status to skipped const targetEdges = runtimeEdges.filter((item) => item.source === node.nodeId); nodeRunAfterHook(node); @@ -486,7 +526,7 @@ export async function dispatchWorkFlow(data: Props): Promise { // item.isEntry = false; // }); - await checkNodeCanRun(entryNodes); + await Promise.all(entryNodes.map((node) => checkNodeCanRun(node))); // focus try to run pluginOutput const pluginOutputModule = runtimeNodes.find( diff --git a/packages/service/core/workflow/dispatch/plugin/run.ts b/packages/service/core/workflow/dispatch/plugin/run.ts index 56f8ea84fbf0..c9354d4961d3 100644 --- a/packages/service/core/workflow/dispatch/plugin/run.ts +++ b/packages/service/core/workflow/dispatch/plugin/run.ts @@ -64,7 +64,11 @@ export const dispatchRunPlugin = async (props: RunPluginProps): Promise { }; }; -export const initTeamStandardPlan2Free = async ({ +export const initTeamFreePlan = async ({ teamId, session }: { @@ -59,23 +59,28 @@ export const initTeamStandardPlan2Free = async ({ }) => { const freePoints = global?.subPlans?.standard?.[StandardSubLevelEnum.free]?.totalPoints || 100; - const teamStandardSub = await MongoTeamSub.findOne({ teamId, type: SubTypeEnum.standard }); - - if (teamStandardSub) { - teamStandardSub.currentMode = SubModeEnum.month; - teamStandardSub.nextMode = SubModeEnum.month; - teamStandardSub.startTime = new Date(); - teamStandardSub.expiredTime = addMonths(new Date(), 1); - - teamStandardSub.currentSubLevel = StandardSubLevelEnum.free; - teamStandardSub.nextSubLevel = StandardSubLevelEnum.free; - - teamStandardSub.totalPoints = freePoints; - teamStandardSub.surplusPoints = - teamStandardSub.surplusPoints && teamStandardSub.surplusPoints < 0 - ? teamStandardSub.surplusPoints + freePoints + const freePlan = await MongoTeamSub.findOne({ + teamId, + type: SubTypeEnum.standard, + currentSubLevel: StandardSubLevelEnum.free + }); + + // Reset one month free plan + if (freePlan) { + freePlan.currentMode = SubModeEnum.month; + freePlan.nextMode = SubModeEnum.month; + freePlan.startTime = new Date(); + freePlan.expiredTime = addMonths(new Date(), 1); + + freePlan.currentSubLevel = StandardSubLevelEnum.free; + freePlan.nextSubLevel = StandardSubLevelEnum.free; + + freePlan.totalPoints = freePoints; + freePlan.surplusPoints = + freePlan.surplusPoints && freePlan.surplusPoints < 0 + ? freePlan.surplusPoints + freePoints : freePoints; - return teamStandardSub.save({ session }); + return freePlan.save({ session }); } return MongoTeamSub.create( @@ -123,13 +128,14 @@ export const getTeamPlanStatus = async ({ // Free user, first login after expiration. The free subscription plan will be reset if ( - standardPlan && - standardPlan.expiredTime && - standardPlan.currentSubLevel === StandardSubLevelEnum.free && - dayjs(standardPlan.expiredTime).isBefore(new Date()) + (standardPlan && + standardPlan.expiredTime && + standardPlan.currentSubLevel === StandardSubLevelEnum.free && + dayjs(standardPlan.expiredTime).isBefore(new Date())) || + teamStandardPlans.length === 0 ) { console.log('Init free stand plan', { teamId }); - await initTeamStandardPlan2Free({ teamId }); + await initTeamFreePlan({ teamId }); return getTeamPlanStatus({ teamId }); } diff --git a/packages/web/components/common/MyMenu/index.tsx b/packages/web/components/common/MyMenu/index.tsx index 7ce9e207ac50..d7aa6309ef81 100644 --- a/packages/web/components/common/MyMenu/index.tsx +++ b/packages/web/components/common/MyMenu/index.tsx @@ -33,7 +33,7 @@ export type Props = { icon?: IconNameType | string; label: string | React.ReactNode; description?: string; - onClick: () => any; + onClick?: () => any; }[]; }[]; }; @@ -170,8 +170,10 @@ const MyMenu = ({ {...menuItemStyles} onClick={(e) => { e.stopPropagation(); - setIsOpen(false); - child.onClick && child.onClick(); + if (child.onClick) { + setIsOpen(false); + child.onClick(); + } }} color={child.isActive ? 'primary.700' : 'myGray.600'} whiteSpace={'pre-wrap'} diff --git a/packages/web/components/common/MySelect/MultipleRowSelect.tsx b/packages/web/components/common/MySelect/MultipleRowSelect.tsx index 474558e9d065..1af0e6a15218 100644 --- a/packages/web/components/common/MySelect/MultipleRowSelect.tsx +++ b/packages/web/components/common/MySelect/MultipleRowSelect.tsx @@ -105,7 +105,7 @@ const MultipleRowSelect = ({ justifyContent={'space-between'} width={'100%'} rightIcon={} - variant={'whiteFlow'} + variant={'whiteBase'} _active={{ transform: 'none' }} diff --git a/packages/web/styles/theme.ts b/packages/web/styles/theme.ts index 6929597cc4bb..ff5b58f61175 100644 --- a/packages/web/styles/theme.ts +++ b/packages/web/styles/theme.ts @@ -194,28 +194,6 @@ const Button = defineStyleConfig({ color: 'myGray.600 !important' } }, - whiteFlow: { - color: 'myGray.600', - border: '1px solid', - borderColor: 'myGray.200', - height: '40px', - bg: 'white', - px: '12px', - py: '0', - borderRadius: '6px', - transition: 'background 0.1s', - _hover: { - color: 'primary.600', - background: 'primary.1', - borderColor: 'primary.300' - }, - _active: { - color: 'primary.600' - }, - _disabled: { - color: 'myGray.600 !important' - } - }, whiteDanger: { color: 'myGray.600', border: '1px solid', diff --git a/projects/app/src/components/core/ai/SettingLLMModel/index.tsx b/projects/app/src/components/core/ai/SettingLLMModel/index.tsx index 00a2d7d27a60..88de9ec28115 100644 --- a/projects/app/src/components/core/ai/SettingLLMModel/index.tsx +++ b/projects/app/src/components/core/ai/SettingLLMModel/index.tsx @@ -68,7 +68,7 @@ const SettingLLMModel = ({ + + {isFreeTeam ? ( + <> + + {t('common:info.free_plan')} + + + ) : ( + + {t('common:support.wallet.Plan expired time')}: + {formatTime2YMD(standardPlan?.expiredTime)} + + )} + + { borderColor={'borderColor.low'} borderRadius={'md'} px={[5, 10]} - pt={[2, 4]} + pt={4} pb={[4, 7]} > diff --git a/projects/app/src/pages/account/components/UsageTable.tsx b/projects/app/src/pages/account/components/UsageTable.tsx index 87829454a1a4..1b5acd02bb13 100644 --- a/projects/app/src/pages/account/components/UsageTable.tsx +++ b/projects/app/src/pages/account/components/UsageTable.tsx @@ -180,12 +180,11 @@ const UsageTable = () => { ))} + {!isLoading && usages.length === 0 && ( + + )} - {!isLoading && usages.length === 0 && ( - - )} - {!!usageDetail && ( setUsageDetail(undefined)} /> diff --git a/projects/app/src/pages/app/detail/components/Plugin/Header.tsx b/projects/app/src/pages/app/detail/components/Plugin/Header.tsx index c791056a0e78..920167330000 100644 --- a/projects/app/src/pages/app/detail/components/Plugin/Header.tsx +++ b/projects/app/src/pages/app/detail/components/Plugin/Header.tsx @@ -125,7 +125,7 @@ const Header = () => { try { localStorage.removeItem(`${appDetail._id}-past`); localStorage.removeItem(`${appDetail._id}-future`); - router.push('/app/list'); + router.back(); } catch (error) {} }, [appDetail._id, router]); diff --git a/projects/app/src/pages/app/detail/components/RouteTab.tsx b/projects/app/src/pages/app/detail/components/RouteTab.tsx index 183492bb3b7d..bdafac4eb724 100644 --- a/projects/app/src/pages/app/detail/components/RouteTab.tsx +++ b/projects/app/src/pages/app/detail/components/RouteTab.tsx @@ -15,7 +15,7 @@ const RouteTab = () => { const setCurrentTab = useCallback( (tab: TabEnum) => { - router.push({ + router.replace({ query: { ...router.query, currentTab: tab @@ -41,7 +41,7 @@ const RouteTab = () => { ] : []) ], - [appDetail.permission.hasManagePer, appT] + [appDetail.permission.hasManagePer, appDetail.type, appT] ); return ( diff --git a/projects/app/src/pages/app/detail/components/Workflow/Header.tsx b/projects/app/src/pages/app/detail/components/Workflow/Header.tsx index c791056a0e78..920167330000 100644 --- a/projects/app/src/pages/app/detail/components/Workflow/Header.tsx +++ b/projects/app/src/pages/app/detail/components/Workflow/Header.tsx @@ -125,7 +125,7 @@ const Header = () => { try { localStorage.removeItem(`${appDetail._id}-past`); localStorage.removeItem(`${appDetail._id}-future`); - router.push('/app/list'); + router.back(); } catch (error) {} }, [appDetail._id, router]); diff --git a/projects/app/src/pages/app/detail/components/WorkflowComponents/AppCard.tsx b/projects/app/src/pages/app/detail/components/WorkflowComponents/AppCard.tsx index 912f0080f335..3a345db8d00f 100644 --- a/projects/app/src/pages/app/detail/components/WorkflowComponents/AppCard.tsx +++ b/projects/app/src/pages/app/detail/components/WorkflowComponents/AppCard.tsx @@ -74,8 +74,7 @@ const AppCard = ({ label: ExportPopover({ chatConfig: appDetail.chatConfig, appName: appDetail.name - }), - onClick: () => {} + }) } ] } @@ -192,7 +191,7 @@ function ExportPopover({ const { t } = useTranslation(); const { copyData } = useCopyData(); const { flowData2StoreDataAndCheck } = useContextSelector(WorkflowContext, (v) => v); - const data = flowData2StoreDataAndCheck(); + const onExportWorkflow = useCallback(async () => { const data = flowData2StoreDataAndCheck(); if (data) { @@ -251,7 +250,10 @@ function ExportPopover({ }} borderRadius={'xs'} onClick={() => { + const data = flowData2StoreDataAndCheck(); + if (!data) return; + fileDownload({ text: JSON.stringify( { diff --git a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/NodePluginIO/InputEditModal.tsx b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/NodePluginIO/InputEditModal.tsx index 8dcccab616df..020514150996 100644 --- a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/NodePluginIO/InputEditModal.tsx +++ b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/NodePluginIO/InputEditModal.tsx @@ -312,6 +312,7 @@ const FieldEditModal = ({ title={isEdit ? t('workflow:edit_input') : t('workflow:add_new_input')} maxW={['90vw', '1028px']} w={'100%'} + isCentered > diff --git a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/RenderInput/templates/SelectApp.tsx b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/RenderInput/templates/SelectApp.tsx index bc0882edafab..a7a9145998cd 100644 --- a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/RenderInput/templates/SelectApp.tsx +++ b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/RenderInput/templates/SelectApp.tsx @@ -50,7 +50,7 @@ const SelectAppRender = ({ item, nodeId }: RenderInputProps) => { <> {!value ? ( - ) : ( @@ -58,7 +58,7 @@ const SelectAppRender = ({ item, nodeId }: RenderInputProps) => { isLoading={loading} w={'100%'} justifyContent={loading ? 'center' : 'flex-start'} - variant={'whiteFlow'} + variant={'whiteBase'} leftIcon={} > {appDetail?.name} diff --git a/projects/app/src/pages/dataset/detail/components/DataCard.tsx b/projects/app/src/pages/dataset/detail/components/DataCard.tsx index 50844077b098..a0d0d0fd3c9b 100644 --- a/projects/app/src/pages/dataset/detail/components/DataCard.tsx +++ b/projects/app/src/pages/dataset/detail/components/DataCard.tsx @@ -179,7 +179,7 @@ const DataCard = () => { - {t('core.dataset.data.Total Amount', { total })} + {t('common:core.dataset.data.Total Amount', { total })} @@ -204,7 +204,7 @@ const DataCard = () => { /> {/* data */} - + {datasetDataList.map((item, index) => (