4.8.10 test (#2568)

* perf: i18n perf

* fix: detail=fasle response

* fix: dataset tag load repeat

* feat :doc

* perf: rename fun

* code comment
This commit is contained in:
Archer
2024-08-29 14:51:34 +08:00
committed by GitHub
parent a177a302d4
commit 322ca757af
18 changed files with 230 additions and 224 deletions

View File

@@ -65,15 +65,18 @@ curl --location --request POST 'https://{{host}}/api/admin/initv4810' \
16. 优化 - 节点选择,避免切换 tab 时候path 加载报错。 16. 优化 - 节点选择,避免切换 tab 时候path 加载报错。
17. 优化 - 最新 React Markdown 组件,支持 Base64 图片。 17. 优化 - 最新 React Markdown 组件,支持 Base64 图片。
18. 优化 - 知识库列表 UI。 18. 优化 - 知识库列表 UI。
19. 优化 - 支持无网络配置情况下运行 19. 优化 - 知识库详情页 UI
20. 优化 - 部分全局变量,增加数据类型约束 20. 优化 - 支持无网络配置情况下运行
21. 修复 - 全局变量 key 可能重复 21. 优化 - 部分全局变量,增加数据类型约束
22. 修复 - Prompt 模式调用工具stream=false 模式下,会携带 0: 开头标记 22. 修复 - 全局变量 key 可能重复
23. 修复 - 对话日志鉴权问题:仅为 APP 管理员的用户,无法查看对话日志详情 23. 修复 - Prompt 模式调用工具stream=false 模式下,会携带 0: 开头标记
24. 修复 - 选择 Milvus 部署时,无法导出知识库。 24. 修复 - 对话日志鉴权问题:仅为 APP 管理员的用户,无法查看对话日志详情。
25. 修复 - 创建 APP 副本,无法复制系统配置。 25. 修复 - 选择 Milvus 部署时,无法导出知识库。
26. 修复 - 图片识别模式下,自动解析图片链接正则不够严谨问题 26. 修复 - 创建 APP 副本,无法复制系统配置
27. 修复 - 内容提取的数据类型与输出数据类型未一致 27. 修复 - 图片识别模式下,自动解析图片链接正则不够严谨问题
28. 修复 - 工作流运行时间统计错误 28. 修复 - 内容提取的数据类型与输出数据类型未一致
29. 修复 - stream 模式下,工具调用有可能出现 undefined 29. 修复 - 工作流运行时间统计错误。
30. 修复 - 全局变量在 API 中无法持久化。 30. 修复 - stream 模式下,工具调用有可能出现 undefined
31. 修复 - 全局变量在 API 中无法持久化。
32. 修复 - OpenAPIdetail=false模式下不应该返回 tool 调用结果,仅返回文字。(可解决 cow 不适配问题)
33. 修复 - 知识库标签重复加载。

View File

@@ -333,7 +333,8 @@ export const removePluginInputVariables = (
); );
}; };
export function replaceVariableLabel({ // replace {{$xx.xx$}} variables for text
export function replaceEditorVariable({
text, text,
nodes, nodes,
variables, variables,
@@ -341,7 +342,7 @@ export function replaceVariableLabel({
}: { }: {
text: any; text: any;
nodes: RuntimeNodeItemType[]; nodes: RuntimeNodeItemType[];
variables: Record<string, string | number>; variables: Record<string, any>; // global variables
runningNode: RuntimeNodeItemType; runningNode: RuntimeNodeItemType;
}) { }) {
if (typeof text !== 'string') return text; if (typeof text !== 'string') return text;

View File

@@ -79,7 +79,7 @@ export const readRawContentByFileBuffer = async ({
) )
return; return;
addLog.info('Use custom read file service'); const start = Date.now();
const data = new FormData(); const data = new FormData();
data.append('file', buffer, { data.append('file', buffer, {
@@ -101,6 +101,8 @@ export const readRawContentByFileBuffer = async ({
} }
}); });
addLog.info(`Use custom read file service, time: ${Date.now() - start}ms`);
const rawText = response.data.markdown; const rawText = response.data.markdown;
return { return {

View File

@@ -21,7 +21,7 @@ import {
} from '@fastgpt/global/core/workflow/node/constant'; } from '@fastgpt/global/core/workflow/node/constant';
import { replaceVariable } from '@fastgpt/global/common/string/tools'; import { replaceVariable } from '@fastgpt/global/common/string/tools';
import { getSystemTime } from '@fastgpt/global/common/time/timezone'; import { getSystemTime } from '@fastgpt/global/common/time/timezone';
import { replaceVariableLabel } from '@fastgpt/global/core/workflow/utils'; import { replaceEditorVariable } from '@fastgpt/global/core/workflow/utils';
import { dispatchWorkflowStart } from './init/workflowStart'; import { dispatchWorkflowStart } from './init/workflowStart';
import { dispatchChatCompletion } from './chat/oneapi'; import { dispatchChatCompletion } from './chat/oneapi';
@@ -368,7 +368,7 @@ export async function dispatchWorkFlow(data: Props): Promise<DispatchFlowRespons
let value = replaceVariable(input.value, variables); let value = replaceVariable(input.value, variables);
// replace {{$xx.xx$}} variables // replace {{$xx.xx$}} variables
value = replaceVariableLabel({ value = replaceEditorVariable({
text: value, text: value,
nodes: runtimeNodes, nodes: runtimeNodes,
variables, variables,

View File

@@ -73,25 +73,35 @@ export const dispatchReadFiles = async (props: Props): Promise<Response> => {
// Concat fileUrlList and filesFromHistories; remove not supported files // Concat fileUrlList and filesFromHistories; remove not supported files
const parseUrlList = [...fileUrlList, ...filesFromHistories] const parseUrlList = [...fileUrlList, ...filesFromHistories]
.map((url) => { .map((url) => {
// System file try {
if (url.startsWith('/') || (requestOrigin && url.startsWith(requestOrigin))) { // Avoid "/api/xxx" file error.
// Parse url, get filename query. Keep only documents that can be parsed const origin = requestOrigin ?? 'http://localhost:3000';
const parseUrl = new URL(url);
const filenameQuery = parseUrl.searchParams.get('filename'); // Check is system upload file
if (filenameQuery) { if (url.startsWith('/') || (requestOrigin && url.startsWith(requestOrigin))) {
const extensionQuery = filenameQuery.split('.').pop()?.toLowerCase() || ''; // Parse url, get filename query. Keep only documents that can be parsed
if (!documentFileType.includes(extensionQuery)) { const parseUrl = new URL(url, origin);
return ''; const filenameQuery = parseUrl.searchParams.get('filename');
// Not document
if (filenameQuery) {
const extensionQuery = filenameQuery.split('.').pop()?.toLowerCase() || '';
if (!documentFileType.includes(extensionQuery)) {
return '';
}
}
// Remove the origin(Make intranet requests directly)
if (requestOrigin && url.startsWith(requestOrigin)) {
url = url.replace(requestOrigin, '');
} }
} }
// Remove the origin(Make intranet requests directly) return url;
if (requestOrigin && url.startsWith(requestOrigin)) { } catch (error) {
url = url.replace(requestOrigin, ''); console.log(error);
} return '';
} }
return url;
}) })
.filter(Boolean) .filter(Boolean)
.slice(0, maxFiles); .slice(0, maxFiles);

View File

@@ -8,7 +8,7 @@ import { getReferenceVariableValue } from '@fastgpt/global/core/workflow/runtime
import { TUpdateListItem } from '@fastgpt/global/core/workflow/template/system/variableUpdate/type'; import { TUpdateListItem } from '@fastgpt/global/core/workflow/template/system/variableUpdate/type';
import { ModuleDispatchProps } from '@fastgpt/global/core/workflow/runtime/type'; import { ModuleDispatchProps } from '@fastgpt/global/core/workflow/runtime/type';
import { removeSystemVariable, valueTypeFormat } from '../utils'; import { removeSystemVariable, valueTypeFormat } from '../utils';
import { replaceVariableLabel } from '@fastgpt/global/core/workflow/utils'; import { replaceEditorVariable } from '@fastgpt/global/core/workflow/utils';
type Props = ModuleDispatchProps<{ type Props = ModuleDispatchProps<{
[NodeInputKeyEnum.updateList]: TUpdateListItem[]; [NodeInputKeyEnum.updateList]: TUpdateListItem[];
@@ -32,7 +32,7 @@ export const dispatchUpdateVariable = async (props: Props): Promise<Response> =>
const formatValue = valueTypeFormat(item.value?.[1], item.valueType); const formatValue = valueTypeFormat(item.value?.[1], item.valueType);
return typeof formatValue === 'string' return typeof formatValue === 'string'
? replaceVariableLabel({ ? replaceEditorVariable({
text: formatValue, text: formatValue,
nodes: runtimeNodes, nodes: runtimeNodes,
variables, variables,

View File

@@ -20,6 +20,10 @@
"you_can_convert": "You can redeem", "you_can_convert": "You can redeem",
"yuan": "Yuan" "yuan": "Yuan"
}, },
"promotion": {
"register": "Register",
"pay": "Pay"
},
"bind_inform_account_error": "Abnormal binding notification account", "bind_inform_account_error": "Abnormal binding notification account",
"bind_inform_account_success": "Binding notification account successful", "bind_inform_account_success": "Binding notification account successful",
"code_error": { "code_error": {

View File

@@ -20,6 +20,10 @@
"current_token_price": "当前积分价格", "current_token_price": "当前积分价格",
"yuan": "元" "yuan": "元"
}, },
"promotion": {
"register": "好友注册",
"pay": "好友充值"
},
"bind_inform_account_error": "绑定通知账号异常", "bind_inform_account_error": "绑定通知账号异常",
"bind_inform_account_success": "绑定通知账号成功", "bind_inform_account_success": "绑定通知账号成功",
"delete": { "delete": {

View File

@@ -19,10 +19,8 @@ import { useQuery } from '@tanstack/react-query';
import { getPromotionInitData, getPromotionRecords } from '@/web/support/activity/promotion/api'; import { getPromotionInitData, getPromotionRecords } from '@/web/support/activity/promotion/api';
import { useUserStore } from '@/web/support/user/useUserStore'; import { useUserStore } from '@/web/support/user/useUserStore';
import MyTooltip from '@fastgpt/web/components/common/MyTooltip';
import { useCopyData } from '@/web/common/hooks/useCopyData'; import { useCopyData } from '@/web/common/hooks/useCopyData';
import type { PromotionRecordType } from '@/global/support/api/userRes.d'; import type { PromotionRecordType } from '@/global/support/api/userRes.d';
import MyIcon from '@fastgpt/web/components/common/Icon';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import { usePagination } from '@fastgpt/web/hooks/usePagination'; import { usePagination } from '@fastgpt/web/hooks/usePagination';
import { useLoading } from '@fastgpt/web/hooks/useLoading'; import { useLoading } from '@fastgpt/web/hooks/useLoading';
@@ -116,7 +114,7 @@ const Promotion = () => {
<Td> <Td>
{item.createTime ? dayjs(item.createTime).format('YYYY/MM/DD HH:mm:ss') : '-'} {item.createTime ? dayjs(item.createTime).format('YYYY/MM/DD HH:mm:ss') : '-'}
</Td> </Td>
<Td>{t(`user.promotion.${item.type}` as any)}</Td> <Td>{t(`user:promotion.${item.type}` as any)}</Td>
<Td>{item.amount}</Td> <Td>{item.amount}</Td>
</Tr> </Tr>
))} ))}

View File

@@ -2,13 +2,11 @@ import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@fastgpt/service/common/response'; import { jsonRes } from '@fastgpt/service/common/response';
import { connectToDatabase } from '@/service/mongo'; import { connectToDatabase } from '@/service/mongo';
import { authCert } from '@fastgpt/service/support/permission/auth/common'; import { authCert } from '@fastgpt/service/support/permission/auth/common';
import { MongoDataset } from '@fastgpt/service/core/dataset/schema';
import { DatasetDefaultPermissionVal } from '@fastgpt/global/support/permission/dataset/constant';
import { MongoAppVersion } from '@fastgpt/service/core/app/version/schema'; import { MongoAppVersion } from '@fastgpt/service/core/app/version/schema';
import { FastGPTProUrl } from '@fastgpt/service/common/system/constants'; import { FastGPTProUrl } from '@fastgpt/service/common/system/constants';
import { POST } from '@fastgpt/service/common/api/plusRequest'; import { POST } from '@fastgpt/service/common/api/plusRequest';
/* pg 中的数据搬到 mongo dataset.datas 中,并做映射 */ /* 初始化发布的版本 */
export default async function handler(req: NextApiRequest, res: NextApiResponse) { export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try { try {
await connectToDatabase(); await connectToDatabase();

View File

@@ -30,7 +30,11 @@ async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
}); });
const { file, bucketName, metadata } = await upload.doUpload(req, res); const { file, bucketName, metadata } = await upload.doUpload(req, res);
filePaths.push(file.path); filePaths.push(file.path);
const { teamId, tmbId, outLinkUid } = await authChatCert({ req, authToken: true }); const { teamId, tmbId, outLinkUid } = await authChatCert({
req,
authToken: true,
authApiKey: true
});
await authUploadLimit(outLinkUid || tmbId); await authUploadLimit(outLinkUid || tmbId);

View File

@@ -368,6 +368,14 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
if (assistantResponses.length === 0) return ''; if (assistantResponses.length === 0) return '';
if (assistantResponses.length === 1 && assistantResponses[0].text?.content) if (assistantResponses.length === 1 && assistantResponses[0].text?.content)
return assistantResponses[0].text?.content; return assistantResponses[0].text?.content;
if (!detail) {
return assistantResponses
.map((item) => item?.text?.content)
.filter(Boolean)
.join('\n');
}
return assistantResponses; return assistantResponses;
})(); })();

View File

@@ -125,7 +125,7 @@ const Header = ({}: {}) => {
return ( return (
<Box display={['block', 'flex']} alignItems={'center'} gap={2}> <Box display={['block', 'flex']} alignItems={'center'} gap={2}>
<HStack flex={1}> <HStack flex={1}>
<Box flex={1} fontWeight={'500'} color={'myGray.900'}> <Box flex={1} fontWeight={'500'} color={'myGray.900'} whiteSpace={'nowrap'}>
<ParentPath <ParentPath
paths={paths.map((path, i) => ({ paths={paths.map((path, i) => ({
parentId: path.parentId, parentId: path.parentId,
@@ -173,7 +173,8 @@ const Header = ({}: {}) => {
{/* search input */} {/* search input */}
{isPc && ( {isPc && (
<MyInput <MyInput
w={['100%', '250px']} maxW={'250px'}
flex={1}
size={'sm'} size={'sm'}
h={'36px'} h={'36px'}
placeholder={t('common:common.Search') || ''} placeholder={t('common:common.Search') || ''}

View File

@@ -2,12 +2,10 @@ import { Box, Button, Checkbox, Flex, Input, useDisclosure } from '@chakra-ui/re
import MyPopover from '@fastgpt/web/components/common/MyPopover'; import MyPopover from '@fastgpt/web/components/common/MyPopover';
import MyIcon from '@fastgpt/web/components/common/Icon'; import MyIcon from '@fastgpt/web/components/common/Icon';
import MyBox from '@fastgpt/web/components/common/MyBox'; import MyBox from '@fastgpt/web/components/common/MyBox';
import { postCreateDatasetCollectionTag } from '@/web/core/dataset/api';
import { useContextSelector } from 'use-context-selector'; import { useContextSelector } from 'use-context-selector';
import { DatasetPageContext } from '@/web/core/dataset/context/datasetPageContext'; import { DatasetPageContext } from '@/web/core/dataset/context/datasetPageContext';
import { useTranslation } from 'next-i18next'; import { useTranslation } from 'next-i18next';
import { useCallback, useEffect, useState } from 'react'; import { useCallback, useState } from 'react';
import { useRequest } from '@fastgpt/web/hooks/useRequest';
import { CollectionPageContext } from './Context'; import { CollectionPageContext } from './Context';
import { debounce, isEqual } from 'lodash'; import { debounce, isEqual } from 'lodash';
import TagManageModal from './TagManageModal'; import TagManageModal from './TagManageModal';
@@ -15,27 +13,17 @@ import { DatasetTagType } from '@fastgpt/global/core/dataset/type';
const HeaderTagPopOver = () => { const HeaderTagPopOver = () => {
const { t } = useTranslation(); const { t } = useTranslation();
const [searchTag, setSearchTag] = useState('');
const [checkedTags, setCheckedTags] = useState<string[]>([]); const [checkedTags, setCheckedTags] = useState<string[]>([]);
const { datasetDetail, datasetTags, loadDatasetTags, checkedDatasetTag, setCheckedDatasetTag } = const {
useContextSelector(DatasetPageContext, (v) => v); searchDatasetTagsResult,
searchTagKey,
const { mutate: onCreateCollectionTag, isLoading: isCreateCollectionTagLoading } = useRequest({ setSearchTagKey,
mutationFn: async (tag: string) => { checkedDatasetTag,
const id = await postCreateDatasetCollectionTag({ setCheckedDatasetTag,
datasetId: datasetDetail._id, onCreateCollectionTag,
tag isCreateCollectionTagLoading
}); } = useContextSelector(DatasetPageContext, (v) => v);
return id;
},
onSuccess() {
setSearchTag('');
},
successToast: t('common:common.Create Success'),
errorToast: t('common:common.Create Failed')
});
const { filterTags, setFilterTags, getData } = useContextSelector( const { filterTags, setFilterTags, getData } = useContextSelector(
CollectionPageContext, CollectionPageContext,
@@ -48,10 +36,6 @@ const HeaderTagPopOver = () => {
[] []
); );
useEffect(() => {
loadDatasetTags({ id: datasetDetail._id, searchKey: searchTag });
}, [searchTag]);
const { const {
isOpen: isTagManageModalOpen, isOpen: isTagManageModalOpen,
onOpen: onOpenTagManageModal, onOpen: onOpenTagManageModal,
@@ -122,35 +106,34 @@ const HeaderTagPopOver = () => {
pl={2} pl={2}
h={8} h={8}
borderRadius={'xs'} borderRadius={'xs'}
value={searchTag} value={searchTagKey}
placeholder={t('dataset:tag.searchOrAddTag')} placeholder={t('dataset:tag.searchOrAddTag')}
onChange={(e) => setSearchTag(e.target.value)} onChange={(e) => setSearchTagKey(e.target.value)}
/> />
</Box> </Box>
<Box my={1} px={1.5} maxH={'240px'} overflow={'auto'}> <Box my={1} px={1.5} maxH={'240px'} overflow={'auto'}>
{searchTag && !datasetTags.map((item) => item.tag).includes(searchTag) && ( {searchTagKey &&
<Flex !searchDatasetTagsResult.map((item) => item.tag).includes(searchTagKey) && (
alignItems={'center'} <Flex
fontSize={'sm'} alignItems={'center'}
px={1} fontSize={'sm'}
cursor={'pointer'} px={1}
_hover={{ bg: '#1118240D', color: 'primary.700' }} cursor={'pointer'}
borderRadius={'xs'} _hover={{ bg: '#1118240D', color: 'primary.700' }}
onClick={() => { borderRadius={'xs'}
onCreateCollectionTag(searchTag); onClick={() => onCreateCollectionTag(searchTagKey)}
}} >
> <MyIcon name={'common/addLight'} w={'16px'} />
<MyIcon name={'common/addLight'} w={'16px'} /> <Box ml={2} py={2}>
<Box ml={2} py={2}> {t('dataset:tag.add') + ` "${searchTagKey}"`}
{t('dataset:tag.add') + ` "${searchTag}"`} </Box>
</Box> </Flex>
</Flex> )}
)}
{[ {[
...new Map( ...new Map(
[...checkedDatasetTag, ...datasetTags].map((item) => [item._id, item]) [...checkedDatasetTag, ...searchDatasetTagsResult].map((item) => [item._id, item])
).values() ).values()
].map((item) => { ].map((item) => {
const checked = checkedTags.includes(item._id); const checked = checkedTags.includes(item._id);
@@ -197,6 +180,7 @@ const HeaderTagPopOver = () => {
borderBottomLeftRadius={'md'} borderBottomLeftRadius={'md'}
variant={'unstyled'} variant={'unstyled'}
onClick={() => { onClick={() => {
setSearchTagKey('');
setCheckedTags([]); setCheckedTags([]);
setFilterTags([]); setFilterTags([]);
debounceRefetch(); debounceRefetch();

View File

@@ -1,5 +1,5 @@
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import React, { useEffect, useMemo, useRef, useState } from 'react';
import { Input, Button, Flex, Box, Checkbox, BoxProps } from '@chakra-ui/react'; import { Input, Button, Flex, Box, Checkbox } from '@chakra-ui/react';
import MyModal from '@fastgpt/web/components/common/MyModal'; import MyModal from '@fastgpt/web/components/common/MyModal';
import { useTranslation } from 'next-i18next'; import { useTranslation } from 'next-i18next';
import MyIcon from '@fastgpt/web/components/common/Icon'; import MyIcon from '@fastgpt/web/components/common/Icon';
@@ -13,10 +13,9 @@ import {
getScrollCollectionList, getScrollCollectionList,
getTagUsage, getTagUsage,
postAddTagsToCollections, postAddTagsToCollections,
postCreateDatasetCollectionTag,
updateDatasetCollectionTag updateDatasetCollectionTag
} from '@/web/core/dataset/api'; } from '@/web/core/dataset/api';
import { useRequest, useRequest2 } from '@fastgpt/web/hooks/useRequest'; import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
import MyInput from '@/components/MyInput'; import MyInput from '@/components/MyInput';
import { DatasetTagType } from '@fastgpt/global/core/dataset/type'; import { DatasetTagType } from '@fastgpt/global/core/dataset/type';
import { ScrollListType, useScrollPagination } from '@fastgpt/web/hooks/useScrollPagination'; import { ScrollListType, useScrollPagination } from '@fastgpt/web/hooks/useScrollPagination';
@@ -26,9 +25,13 @@ import { DatasetCollectionsListItemType } from '@/global/core/dataset/type';
const TagManageModal = ({ onClose }: { onClose: () => void }) => { const TagManageModal = ({ onClose }: { onClose: () => void }) => {
const { t } = useTranslation(); const { t } = useTranslation();
const datasetDetail = useContextSelector(DatasetPageContext, (v) => v.datasetDetail); const {
const loadDatasetTags = useContextSelector(DatasetPageContext, (v) => v.loadDatasetTags); datasetDetail,
const loadAllDatasetTags = useContextSelector(DatasetPageContext, (v) => v.loadAllDatasetTags); onCreateCollectionTag,
isCreateCollectionTagLoading,
loadAllDatasetTags,
setSearchTagKey
} = useContextSelector(DatasetPageContext, (v) => v);
const { getData, pageNum, collections } = useContextSelector(CollectionPageContext, (v) => v); const { getData, pageNum, collections } = useContextSelector(CollectionPageContext, (v) => v);
const tagInputRef = useRef<HTMLInputElement>(null); const tagInputRef = useRef<HTMLInputElement>(null);
@@ -56,36 +59,17 @@ const TagManageModal = ({ onClose }: { onClose: () => void }) => {
} }
}, [currentEditTag]); }, [currentEditTag]);
const { mutate: onCreateCollectionTag, isLoading: isCreateCollectionTagLoading } = useRequest({
mutationFn: async (tag: string) => {
const id = await postCreateDatasetCollectionTag({
datasetId: datasetDetail._id,
tag
});
return id;
},
onSuccess() {
fetchData(1);
loadDatasetTags({ id: datasetDetail._id, searchKey: '' });
loadAllDatasetTags({ id: datasetDetail._id });
},
successToast: t('common:common.Create Success'),
errorToast: t('common:common.Create Failed')
});
const { runAsync: onDeleteCollectionTag, loading: isDeleteCollectionTagLoading } = useRequest2( const { runAsync: onDeleteCollectionTag, loading: isDeleteCollectionTagLoading } = useRequest2(
(tag: string) => { (tag: string) =>
return delDatasetCollectionTag({ delDatasetCollectionTag({
datasetId: datasetDetail._id, datasetId: datasetDetail._id,
id: tag id: tag
}); }),
},
{ {
onSuccess() { onSuccess() {
fetchData(1); fetchData(1);
loadDatasetTags({ id: datasetDetail._id, searchKey: '' }); setSearchTagKey('');
loadAllDatasetTags({ id: datasetDetail._id }); loadAllDatasetTags();
}, },
successToast: t('common:common.Delete Success'), successToast: t('common:common.Delete Success'),
errorToast: t('common:common.Delete Failed') errorToast: t('common:common.Delete Failed')
@@ -103,8 +87,8 @@ const TagManageModal = ({ onClose }: { onClose: () => void }) => {
{ {
onSuccess() { onSuccess() {
fetchData(1); fetchData(1);
loadDatasetTags({ id: datasetDetail._id, searchKey: '' }); setSearchTagKey('');
loadAllDatasetTags({ id: datasetDetail._id }); loadAllDatasetTags();
} }
} }
); );

View File

@@ -2,12 +2,11 @@ import { Box, Checkbox, Flex, Input } from '@chakra-ui/react';
import MyPopover from '@fastgpt/web/components/common/MyPopover'; import MyPopover from '@fastgpt/web/components/common/MyPopover';
import MyIcon from '@fastgpt/web/components/common/Icon'; import MyIcon from '@fastgpt/web/components/common/Icon';
import MyBox from '@fastgpt/web/components/common/MyBox'; import MyBox from '@fastgpt/web/components/common/MyBox';
import { postCreateDatasetCollectionTag, putDatasetCollectionById } from '@/web/core/dataset/api'; import { putDatasetCollectionById } from '@/web/core/dataset/api';
import { useContextSelector } from 'use-context-selector'; import { useContextSelector } from 'use-context-selector';
import { DatasetPageContext } from '@/web/core/dataset/context/datasetPageContext'; import { DatasetPageContext } from '@/web/core/dataset/context/datasetPageContext';
import { useTranslation } from 'next-i18next'; import { useTranslation } from 'next-i18next';
import { useEffect, useMemo, useRef, useState } from 'react'; import { useMemo, useRef, useState } from 'react';
import { useRequest } from '@fastgpt/web/hooks/useRequest';
import { useDeepCompareEffect } from 'ahooks'; import { useDeepCompareEffect } from 'ahooks';
import { DatasetCollectionItemType, DatasetTagType } from '@fastgpt/global/core/dataset/type'; import { DatasetCollectionItemType, DatasetTagType } from '@fastgpt/global/core/dataset/type';
import { isEqual } from 'lodash'; import { isEqual } from 'lodash';
@@ -19,25 +18,20 @@ const TagsPopOver = ({
currentCollection: DatasetCollectionItemType | DatasetCollectionsListItemType; currentCollection: DatasetCollectionItemType | DatasetCollectionsListItemType;
}) => { }) => {
const { t } = useTranslation(); const { t } = useTranslation();
const datasetDetail = useContextSelector(DatasetPageContext, (v) => v.datasetDetail); const {
const datasetTags = useContextSelector(DatasetPageContext, (v) => v.datasetTags); searchTagKey,
const loadDatasetTags = useContextSelector(DatasetPageContext, (v) => v.loadDatasetTags); setSearchTagKey,
const allDatasetTags = useContextSelector(DatasetPageContext, (v) => v.allDatasetTags); searchDatasetTagsResult,
const loadAllDatasetTags = useContextSelector(DatasetPageContext, (v) => v.loadAllDatasetTags); allDatasetTags,
onCreateCollectionTag,
isCreateCollectionTagLoading
} = useContextSelector(DatasetPageContext, (v) => v);
const [collectionTags, setCollectionTags] = useState<string[]>([]); const [collectionTags, setCollectionTags] = useState<string[]>(currentCollection.tags ?? []);
const [searchTag, setSearchTag] = useState('');
const [checkedTags, setCheckedTags] = useState<DatasetTagType[]>([]); const [checkedTags, setCheckedTags] = useState<DatasetTagType[]>([]);
const [showTagManage, setShowTagManage] = useState(false); const [showTagManage, setShowTagManage] = useState(false);
const [isFocusInput, setIsFocusInput] = useState(false);
const [isUpdateLoading, setIsUpdateLoading] = useState(false); const [isUpdateLoading, setIsUpdateLoading] = useState(false);
useEffect(() => {
if (!currentCollection.tags) return;
setCollectionTags(currentCollection.tags);
}, [currentCollection]);
const tagList = useMemo( const tagList = useMemo(
() => () =>
(collectionTags (collectionTags
@@ -52,12 +46,6 @@ const TagsPopOver = ({
[collectionTags, allDatasetTags] [collectionTags, allDatasetTags]
); );
useEffect(() => {
if (!isFocusInput) return;
loadDatasetTags({ id: datasetDetail._id, searchKey: searchTag });
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [datasetDetail._id, isFocusInput, searchTag]);
const [visibleTags, setVisibleTags] = useState<DatasetTagType[]>(tagList); const [visibleTags, setVisibleTags] = useState<DatasetTagType[]>(tagList);
const [overflowTags, setOverflowTags] = useState<DatasetTagType[]>([]); const [overflowTags, setOverflowTags] = useState<DatasetTagType[]>([]);
const containerRef = useRef<HTMLDivElement>(null); const containerRef = useRef<HTMLDivElement>(null);
@@ -96,24 +84,6 @@ const TagsPopOver = ({
}; };
}, [tagList]); }, [tagList]);
const { mutate: onCreateCollectionTag, isLoading: isCreateCollectionTagLoading } = useRequest({
mutationFn: async (tag: string) => {
const id = await postCreateDatasetCollectionTag({
datasetId: datasetDetail._id,
tag
});
return id;
},
onSuccess() {
setSearchTag('');
loadDatasetTags({ id: datasetDetail._id, searchKey: '' });
loadAllDatasetTags({ id: datasetDetail._id });
},
successToast: t('common:common.Create Success'),
errorToast: t('common:common.Create Failed')
});
return ( return (
<MyPopover <MyPopover
placement={showTagManage ? 'bottom' : 'bottom-end'} placement={showTagManage ? 'bottom' : 'bottom-end'}
@@ -176,6 +146,8 @@ const TagsPopOver = ({
</MyBox> </MyBox>
} }
onCloseFunc={async () => { onCloseFunc={async () => {
setSearchTagKey('');
setShowTagManage(false); setShowTagManage(false);
if (isEqual(checkedTags, tagList) || !showTagManage) return; if (isEqual(checkedTags, tagList) || !showTagManage) return;
setIsUpdateLoading(true); setIsUpdateLoading(true);
@@ -194,36 +166,33 @@ const TagsPopOver = ({
<MyBox isLoading={isCreateCollectionTagLoading} onClick={(e) => e.stopPropagation()}> <MyBox isLoading={isCreateCollectionTagLoading} onClick={(e) => e.stopPropagation()}>
<Box px={1.5} pt={1.5}> <Box px={1.5} pt={1.5}>
<Input <Input
onFocus={() => setIsFocusInput(true)}
onBlur={() => setIsFocusInput(false)}
pl={2} pl={2}
h={7} h={7}
borderRadius={'xs'} borderRadius={'xs'}
value={searchTag} value={searchTagKey}
placeholder={t('dataset:tag.searchOrAddTag')} placeholder={t('dataset:tag.searchOrAddTag')}
onChange={(e) => setSearchTag(e.target.value)} onChange={(e) => setSearchTagKey(e.target.value)}
/> />
</Box> </Box>
<Box my={1} px={1.5} maxH={'200px'} overflow={'auto'}> <Box my={1} px={1.5} maxH={'200px'} overflow={'auto'}>
{searchTag && !datasetTags.map((item) => item.tag).includes(searchTag) && ( {searchTagKey &&
<Flex !searchDatasetTagsResult.map((item) => item.tag).includes(searchTagKey) && (
alignItems={'center'} <Flex
fontSize={'xs'} alignItems={'center'}
px={1} fontSize={'xs'}
cursor={'pointer'} px={1}
_hover={{ bg: '#1118240D', color: '#2B5FD9' }} cursor={'pointer'}
borderRadius={'xs'} _hover={{ bg: '#1118240D', color: '#2B5FD9' }}
onClick={() => { borderRadius={'xs'}
onCreateCollectionTag(searchTag); onClick={() => onCreateCollectionTag(searchTagKey)}
}} >
> <MyIcon name={'common/addLight'} w={'1rem'} />
<MyIcon name={'common/addLight'} w={'16px'} /> <Box ml={1} py={1}>
<Box ml={1} py={1}> {t('dataset:tag.add') + ` "${searchTagKey}"`}
{t('dataset:tag.add') + ` "${searchTag}"`} </Box>
</Box> </Flex>
</Flex> )}
)} {searchDatasetTagsResult?.map((item) => {
{datasetTags?.map((item) => {
const tagsList = checkedTags.map((tag) => tag.tag); const tagsList = checkedTags.map((tag) => tag.tag);
return ( return (
<Flex <Flex

View File

@@ -49,12 +49,8 @@ const Detail = ({ datasetId, currentTab }: Props) => {
const { isPc } = useSystem(); const { isPc } = useSystem();
const datasetDetail = useContextSelector(DatasetPageContext, (v) => v.datasetDetail); const datasetDetail = useContextSelector(DatasetPageContext, (v) => v.datasetDetail);
const loadDatasetDetail = useContextSelector(DatasetPageContext, (v) => v.loadDatasetDetail); const loadDatasetDetail = useContextSelector(DatasetPageContext, (v) => v.loadDatasetDetail);
const loadAllDatasetTags = useContextSelector(DatasetPageContext, (v) => v.loadAllDatasetTags);
useRequest2(() => loadDatasetDetail(datasetId), { useRequest2(() => loadDatasetDetail(datasetId), {
onSuccess: () => {
loadAllDatasetTags({ id: datasetId });
},
onError(err: any) { onError(err: any) {
router.replace(`/dataset/list`); router.replace(`/dataset/list`);
toast({ toast({

View File

@@ -1,5 +1,5 @@
import { useQuery } from '@tanstack/react-query'; import { useQuery } from '@tanstack/react-query';
import { ReactNode, SetStateAction, useMemo, useState } from 'react'; import { Dispatch, ReactNode, SetStateAction, useEffect, useMemo, useState } from 'react';
import { useTranslation } from 'next-i18next'; import { useTranslation } from 'next-i18next';
import { createContext } from 'use-context-selector'; import { createContext } from 'use-context-selector';
import { import {
@@ -8,24 +8,30 @@ import {
getDatasetCollectionTags, getDatasetCollectionTags,
getDatasetTrainingQueue, getDatasetTrainingQueue,
getTrainingQueueLen, getTrainingQueueLen,
postCreateDatasetCollectionTag,
putDatasetById putDatasetById
} from '../api'; } from '../api';
import { defaultDatasetDetail } from '../constants'; import { defaultDatasetDetail } from '../constants';
import { DatasetUpdateBody } from '@fastgpt/global/core/dataset/api'; import { DatasetUpdateBody } from '@fastgpt/global/core/dataset/api';
import { DatasetItemType, DatasetTagType } from '@fastgpt/global/core/dataset/type'; import { DatasetItemType, DatasetTagType } from '@fastgpt/global/core/dataset/type';
import { useSystemStore } from '@/web/common/system/useSystemStore'; import { useSystemStore } from '@/web/common/system/useSystemStore';
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
type DatasetPageContextType = { type DatasetPageContextType = {
datasetId: string; datasetId: string;
datasetDetail: DatasetItemType; datasetDetail: DatasetItemType;
loadDatasetDetail: (id: string) => Promise<DatasetItemType>; loadDatasetDetail: (id: string) => Promise<DatasetItemType>;
updateDataset: (data: DatasetUpdateBody) => Promise<void>; updateDataset: (data: DatasetUpdateBody) => Promise<void>;
datasetTags: DatasetTagType[];
loadDatasetTags: (data: { id: string; searchKey: string }) => Promise<void>; searchDatasetTagsResult: DatasetTagType[];
allDatasetTags: DatasetTagType[]; allDatasetTags: DatasetTagType[];
loadAllDatasetTags: (data: { id: string }) => Promise<void>; loadAllDatasetTags: () => Promise<DatasetTagType[]>;
checkedDatasetTag: DatasetTagType[]; checkedDatasetTag: DatasetTagType[];
setCheckedDatasetTag: React.Dispatch<SetStateAction<DatasetTagType[]>>; setCheckedDatasetTag: React.Dispatch<SetStateAction<DatasetTagType[]>>;
onCreateCollectionTag: (tag: string) => Promise<void>;
isCreateCollectionTagLoading: boolean;
searchTagKey: string;
setSearchTagKey: Dispatch<SetStateAction<string>>;
vectorTrainingMap: { vectorTrainingMap: {
colorSchema: string; colorSchema: string;
@@ -62,17 +68,22 @@ export const DatasetPageContext = createContext<DatasetPageContextType>({
updateDataset: function (data: DatasetUpdateBody): Promise<void> { updateDataset: function (data: DatasetUpdateBody): Promise<void> {
throw new Error('Function not implemented.'); throw new Error('Function not implemented.');
}, },
datasetTags: [], searchDatasetTagsResult: [],
loadDatasetTags: function (data: { id: string; searchKey: string }): Promise<void> {
throw new Error('Function not implemented.');
},
allDatasetTags: [], allDatasetTags: [],
loadAllDatasetTags: function (data: { id: string }): Promise<void> {
throw new Error('Function not implemented.');
},
checkedDatasetTag: [], checkedDatasetTag: [],
setCheckedDatasetTag: function (): void { setCheckedDatasetTag: function (): void {
throw new Error('Function not implemented.'); throw new Error('Function not implemented.');
},
loadAllDatasetTags: function (): Promise<DatasetTagType[]> {
throw new Error('Function not implemented.');
},
onCreateCollectionTag: function (tag: string): Promise<void> {
throw new Error('Function not implemented.');
},
isCreateCollectionTagLoading: false,
searchTagKey: '',
setSearchTagKey: function (value: SetStateAction<string>): void {
throw new Error('Function not implemented.');
} }
}); });
@@ -108,28 +119,53 @@ export const DatasetPageContextProvider = ({
}; };
// dataset tags // dataset tags
const [datasetTags, setDatasetTags] = useState<DatasetTagType[]>([]);
const loadDatasetTags = async ({ id, searchKey }: { id: string; searchKey: string }) => {
const { list } = await getDatasetCollectionTags({
datasetId: id,
searchText: searchKey,
current: 1,
pageSize: 15
});
setDatasetTags(list);
};
const [checkedDatasetTag, setCheckedDatasetTag] = useState<DatasetTagType[]>([]); const [checkedDatasetTag, setCheckedDatasetTag] = useState<DatasetTagType[]>([]);
const [searchTagKey, setSearchTagKey] = useState('');
const [allDatasetTags, setAllDatasetTags] = useState<DatasetTagType[]>([]); const { runAsync: loadAllDatasetTags, data: allDatasetTags = [] } = useRequest2(
async () => {
if (!feConfigs?.isPlus || !datasetDetail._id) return [];
const loadAllDatasetTags = async ({ id }: { id: string }) => { const { list } = await getAllTags(datasetDetail._id);
if (!feConfigs?.isPlus) return; return list;
},
const { list } = await getAllTags(id); {
setAllDatasetTags(list); manual: false,
}; refreshDeps: [datasetDetail._id]
}
);
const { data: searchDatasetTagsResult = [] } = useRequest2(
async () => {
if (!searchTagKey) return allDatasetTags;
const { list } = await getDatasetCollectionTags({
datasetId: datasetDetail._id,
searchText: searchTagKey,
current: 1,
pageSize: 15
});
return list;
},
{
manual: false,
throttleWait: 300,
refreshDeps: [datasetDetail._id, searchTagKey, allDatasetTags]
}
);
const { runAsync: onCreateCollectionTag, loading: isCreateCollectionTagLoading } = useRequest2(
(tag: string) =>
postCreateDatasetCollectionTag({
datasetId: datasetDetail._id,
tag
}),
{
refreshDeps: [datasetDetail._id],
onSuccess() {
loadAllDatasetTags();
},
successToast: t('common:common.Create Success'),
errorToast: t('common:common.Create Failed')
}
);
// global queue // global queue
const { data: { vectorTrainingCount = 0, agentTrainingCount = 0 } = {} } = useQuery( const { data: { vectorTrainingCount = 0, agentTrainingCount = 0 } = {} } = useQuery(
@@ -199,12 +235,16 @@ export const DatasetPageContextProvider = ({
rebuildingCount, rebuildingCount,
trainingCount, trainingCount,
refetchDatasetTraining, refetchDatasetTraining,
datasetTags,
loadDatasetTags, searchDatasetTagsResult,
checkedDatasetTag, checkedDatasetTag,
setCheckedDatasetTag, setCheckedDatasetTag,
allDatasetTags, allDatasetTags,
loadAllDatasetTags loadAllDatasetTags,
onCreateCollectionTag,
isCreateCollectionTagLoading,
searchTagKey,
setSearchTagKey
}; };
return <DatasetPageContext.Provider value={contextValue}>{children}</DatasetPageContext.Provider>; return <DatasetPageContext.Provider value={contextValue}>{children}</DatasetPageContext.Provider>;