4.6.3-website dataset (#532)

This commit is contained in:
Archer
2023-12-03 20:45:57 +08:00
committed by GitHub
parent b916183848
commit a9ae270335
122 changed files with 3793 additions and 1360 deletions

View File

@@ -74,8 +74,8 @@ const MessageInput = ({
try {
const src = await compressImgFileAndUpload({
file: file.rawFile,
maxW: 1000,
maxH: 1000,
maxW: 4329,
maxH: 4329,
maxSize: 1024 * 1024 * 5,
// 30 day expired.
expiredTime: addDays(new Date(), 30)

View File

@@ -71,9 +71,9 @@ const QuoteModal = ({
iconSrc="/imgs/modal/quote.svg"
title={
<Box>
({rawSearch.length})
<Box fontSize={'10px'} color={'myGray.500'} fontWeight={'normal'}>
注意: 修改知识库内容成功后
{t('core.chat.Quote Amount', { amount: rawSearch.length })}
<Box fontSize={'sm'} color={'myGray.500'} fontWeight={'normal'}>
{t('core.chat.quote.Quote Tip')}
</Box>
</Box>
}

View File

@@ -1,7 +1,7 @@
import React, { useMemo, useState } from 'react';
import type { ChatHistoryItemResType } from '@fastgpt/global/core/chat/api.d';
import type { ChatItemType } from '@fastgpt/global/core/chat/type';
import { Flex, BoxProps, useDisclosure, Image, useTheme } from '@chakra-ui/react';
import { Flex, BoxProps, useDisclosure, Image, useTheme, Box } from '@chakra-ui/react';
import { useTranslation } from 'next-i18next';
import { useSystemStore } from '@/web/common/system/useSystemStore';
import type { SearchDataResponseItemType } from '@fastgpt/global/core/dataset/type';
@@ -11,6 +11,8 @@ import MyTooltip from '../MyTooltip';
import { FlowNodeTypeEnum } from '@fastgpt/global/core/module/node/constant';
import { getSourceNameIcon } from '@fastgpt/global/core/dataset/utils';
import ChatBoxDivider from '@/components/core/chat/Divider';
import MyIcon from '../Icon';
import { getFileAndOpen } from '@/web/core/dataset/utils';
const QuoteModal = dynamic(() => import('./QuoteModal'), { ssr: false });
const ContextModal = dynamic(() => import('./ContextModal'), { ssr: false });
@@ -59,6 +61,7 @@ const ResponseTags = ({ responseData = [] }: { responseData?: ChatHistoryItemRes
.flat()
.map((item) => ({
sourceName: item.sourceName,
sourceId: item.sourceId,
icon: getSourceNameIcon({ sourceId: item.sourceId, sourceName: item.sourceName })
})),
historyPreview: chatData?.historyPreview,
@@ -83,18 +86,67 @@ const ResponseTags = ({ responseData = [] }: { responseData?: ChatHistoryItemRes
alignItems={'center'}
flexWrap={'wrap'}
fontSize={'sm'}
cursor={'pointer'}
border={theme.borders.sm}
py={1}
px={2}
borderRadius={'md'}
_hover={{
bg: 'myBlue.100'
'.controller': {
display: 'flex'
}
}}
overflow={'hidden'}
position={'relative'}
onClick={() => setQuoteModalData(quoteList)}
>
<Image src={item.icon} alt={''} mr={1} w={'12px'} />
{item.sourceName}
<Box className="textEllipsis" flex={'1 0 0'}>
{item.sourceName}
</Box>
<Box
className="controller"
display={['flex', 'none']}
pr={2}
position={'absolute'}
right={0}
left={0}
justifyContent={'flex-end'}
alignItems={'center'}
h={'100%'}
lineHeight={0}
bg={`linear-gradient(to left, white,white ${
item.sourceId ? '60px' : '30px'
}, rgba(255,255,255,0) 80%)`}
>
<MyTooltip label={t('core.chat.quote.Read Quote')}>
<MyIcon
name="common/viewLight"
w={'14px'}
cursor={'pointer'}
_hover={{
color: 'green.600'
}}
/>
</MyTooltip>
{item.sourceId && (
<MyTooltip label={t('core.chat.quote.Read Source')}>
<MyIcon
ml={4}
name="common/routePushLight"
w={'14px'}
cursor={'pointer'}
_hover={{ color: 'myBlue.600' }}
onClick={async (e) => {
e.stopPropagation();
if (!item.sourceId) return;
await getFileAndOpen(item.sourceId);
}}
/>
</MyTooltip>
)}
</Box>
</Flex>
))}
</Flex>

View File

@@ -1,14 +1,6 @@
.stopIcon {
animation: zoomStopIcon 0.4s infinite alternate;
}
@keyframes zoomStopIcon {
0% {
transform: scale(0.8);
}
100% {
transform: scale(1.2);
}
}
.statusAnimation {
animation: statusBox 0.8s linear infinite alternate;

View File

@@ -1,17 +1,19 @@
import React from 'react';
import { Flex, Box, FlexProps } from '@chakra-ui/react';
import MyIcon from '../Icon';
import { useTranslation } from 'next-i18next';
type Props = FlexProps & {
text?: string | null;
text?: string | React.ReactNode;
};
const EmptyTip = ({ text, ...props }: Props) => {
const { t } = useTranslation();
return (
<Flex mt={5} flexDirection={'column'} alignItems={'center'} pt={'10vh'} {...props}>
<MyIcon name="empty" w={'48px'} h={'48px'} color={'transparent'} />
<Box mt={2} color={'myGray.500'}>
{text || '没有什么数据噢~'}
{text || t('common.empty.Common Tip')}
</Box>
</Flex>
);

View File

@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1701410305116" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="8647" xmlns:xlink="http://www.w3.org/1999/xlink" width="128" height="128"><path d="M138.9056 180.5312m94.7712 0l563.3536 0q94.7712 0 94.7712 94.7712l0 576q0 94.7712-94.7712 94.7712l-563.3536 0q-94.7712 0-94.7712-94.7712l0-576q0-94.7712 94.7712-94.7712Z" fill="#CCDAFF" p-id="8648"></path><path d="M292.3008 81.92m64.768 0l305.3056 0q64.768 0 64.768 64.768l0 70.4q0 64.768-64.768 64.768l-305.3056 0q-64.768 0-64.768-64.768l0-70.4q0-64.768 64.768-64.768Z" fill="#7A7AF9" p-id="8649"></path><path d="M480.768 736.8704a49.408 49.408 0 0 1-34.7648-14.2848L332.8 611.3792a49.6128 49.6128 0 1 1 69.7344-70.7072l77.7216 76.4928L634.5216 460.8a49.6128 49.6128 0 1 1 70.6048 69.6832L516.096 721.92a49.6128 49.6128 0 0 1-35.328 14.9504z" fill="#7A7AF9" p-id="8650"></path></svg>

After

Width:  |  Height:  |  Size: 1017 B

View File

@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1701403554068" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5098" xmlns:xlink="http://www.w3.org/1999/xlink" width="128" height="128"><path d="M572.074667 337.134933a57.275733 57.275733 0 1 0-114.176-6.007466h-0.170667l12.936533 272.896v0.443733a44.544 44.544 0 1 0 89.019734-1.194667l12.424533-266.1376z m-196.949334-191.214933c76.151467-130.048 199.816533-129.774933 275.797334 0l340.3776 581.085867c76.117333 130.048 15.7696 235.4176-135.236267 235.4176H169.984c-150.8352 0-211.217067-105.6768-135.202133-235.4176L375.125333 145.92z m140.049067 687.581867a57.275733 57.275733 0 1 0 0-114.517334 57.275733 57.275733 0 0 0 0 114.517334z" fill="#FB6547" p-id="5099"></path></svg>

After

Width:  |  Height:  |  Size: 869 B

View File

@@ -0,0 +1,11 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1701418384907"
class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="11367"
xmlns:xlink="http://www.w3.org/1999/xlink" width="128" height="128">
<path
d="M892 928.1H134c-19.9 0-36-16.1-36-36v-758c0-19.9 16.1-36 36-36h314.1c19.9 0 36 16.1 36 36s-16.1 36-36 36H170v686h686V579.6c0-19.9 16.1-36 36-36s36 16.1 36 36v312.5c0 19.9-16.1 36-36 36z"
p-id="11368"></path>
<path
d="M927.9 131.6v-0.5c-0.1-1.7-0.4-3.3-0.7-4.9 0-0.1 0-0.2-0.1-0.3-0.4-1.7-0.9-3.3-1.5-4.9v-0.1c-0.6-1.6-1.4-3.1-2.2-4.6 0-0.1-0.1-0.1-0.1-0.2-0.8-1.4-1.7-2.8-2.7-4.1-0.1-0.1-0.2-0.3-0.3-0.4-0.5-0.6-0.9-1.1-1.4-1.7 0-0.1-0.1-0.1-0.1-0.2-0.5-0.6-1-1.1-1.6-1.6l-0.4-0.4c-0.5-0.5-1.1-1-1.6-1.5l-0.1-0.1c-0.6-0.5-1.2-1-1.9-1.4-0.1-0.1-0.3-0.2-0.4-0.3-1.4-1-2.8-1.8-4.3-2.6l-0.1-0.1c-1.6-0.8-3.2-1.5-4.9-2-1.6-0.5-3.3-1-5-1.2-0.1 0-0.2 0-0.3-0.1l-2.4-0.3h-0.3c-0.7-0.1-1.3-0.1-2-0.1H640.1c-19.9 0-36 16.1-36 36s16.1 36 36 36h165L487.6 487.6c-14.1 14.1-14.1 36.9 0 50.9 7 7 16.2 10.5 25.5 10.5 9.2 0 18.4-3.5 25.5-10.5L856 221v162.8c0 19.9 16.1 36 36 36s36-16.1 36-36V134.1c0-0.8 0-1.7-0.1-2.5z"
p-id="11369"></path>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1701420318127" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="13238" xmlns:xlink="http://www.w3.org/1999/xlink" width="128" height="128"><path d="M987.52 367.36c-108.16-146.56-266.88-239.36-444.16-239.36-177.28 0-335.36 92.8-444.16 239.36-48 64.64-48 160.64 0 225.28C208 739.2 366.72 832 543.36 832c177.28 0 335.36-92.8 444.16-239.36C1035.52 528 1035.52 432 987.52 367.36zM939.52 539.52C839.04 684.8 694.4 768 542.72 768c-151.68 0-296.32-83.2-396.8-228.48C121.6 505.6 121.6 455.04 145.92 420.48 246.4 275.2 391.04 192 542.72 192c151.68 0 296.32 83.2 396.8 228.48C963.2 455.04 963.2 505.6 939.52 539.52zM544 256C420.48 256 320 356.48 320 480S420.48 704 544 704 768 603.52 768 480 667.52 256 544 256zM544 640C455.68 640 384 568.32 384 480S455.68 320 544 320C632.32 320 704 391.68 704 480S632.32 640 544 640z" p-id="13239"></path></svg>

After

Width:  |  Height:  |  Size: 1021 B

View File

@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1697622173220" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2398" xmlns:xlink="http://www.w3.org/1999/xlink" width="128" height="128"><path d="M722.36246914 1024.8960632h-415.54678519c-120.26260543 0-218.12969876-97.86709333-218.12969877-218.12969875v-586.42583704c0-120.26260543 97.86709333-218.12969876 218.12969877-218.12969877h415.54678519c120.26260543 0 218.12969876 97.86709333 218.12969876 218.12969877v586.42583704c0 120.26260543-97.86709333 218.12969876-218.12969876 218.12969875z m-415.54678519-898.28010667c-51.65207703 0-93.72457086 42.07249383-93.72457086 93.72457088v586.42583704c0 51.65207703 42.07249383 93.72457086 93.72457086 93.72457086h415.54678519c51.65207703 0 93.72457086-42.07249383 93.72457086-93.72457086v-586.42583704c0-51.65207703-42.07249383-93.72457086-93.72457086-93.72457088h-415.54678519z" fill="#BDD2EF" p-id="2399"></path><path d="M684.0441363 599.76969482H338.27296395c-42.07249383 0-76.11885037-34.04635653-76.11885037-76.11885037s34.04635653-76.11885037 76.11885037-76.11885037h345.77117235c42.07249383 0 76.11885037 34.04635653 76.11885037 76.11885037s-34.17581037 76.11885037-76.11885037 76.11885037zM465.00826075 360.66847605h-126.7352968c-42.07249383 0-76.11885037-34.04635653-76.11885037-76.11885036s34.04635653-76.11885037 76.11885037-76.11885038h126.7352968c42.07249383 0 76.11885037 34.04635653 76.11885036 76.11885038s-34.04635653 76.11885037-76.11885036 76.11885036zM684.0441363 360.66847605h-25.37295013c-42.07249383 0-76.11885037-34.04635653-76.11885036-76.11885036s34.04635653-76.11885037 76.11885036-76.11885038h25.37295013c42.07249383 0 76.11885037 34.04635653 76.11885037 76.11885038s-34.17581037 76.11885037-76.11885037 76.11885036zM684.0441363 838.87091358H338.27296395c-42.07249383 0-76.11885037-34.04635653-76.11885037-76.11885038s34.04635653-76.11885037 76.11885037-76.11885037h345.77117235c42.07249383 0 76.11885037 34.04635653 76.11885037 76.11885037s-34.17581037 76.11885037-76.11885037 76.11885038z" fill="#2867CE" p-id="2400"></path></svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1694141197423" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4891" xmlns:xlink="http://www.w3.org/1999/xlink" width="64" height="64"><path d="M855.04 385.024q19.456 2.048 38.912 10.24t33.792 23.04 21.504 37.376 2.048 54.272q-2.048 8.192-8.192 40.448t-14.336 74.24-18.432 86.528-19.456 76.288q-5.12 18.432-14.848 37.888t-25.088 35.328-36.864 26.112-51.2 10.24l-567.296 0q-21.504 0-44.544-9.216t-42.496-26.112-31.744-40.96-12.288-53.76l0-439.296q0-62.464 33.792-97.792t95.232-35.328l503.808 0q22.528 0 46.592 8.704t43.52 24.064 31.744 35.84 12.288 44.032l0 11.264-53.248 0q-40.96 0-95.744-0.512t-116.736-0.512-115.712-0.512-92.672-0.512l-47.104 0q-26.624 0-41.472 16.896t-23.04 44.544q-8.192 29.696-18.432 62.976t-18.432 61.952q-10.24 33.792-20.48 65.536-2.048 8.192-2.048 13.312 0 17.408 11.776 29.184t29.184 11.776q31.744 0 43.008-39.936l54.272-198.656q133.12 1.024 243.712 1.024l286.72 0z" fill="#FFCC66" p-id="4892"></path></svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -0,0 +1,14 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1701324062325"
class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4651"
xmlns:xlink="http://www.w3.org/1999/xlink" width="128" height="128">
<path d="M512 512m-512 0a512 512 0 1 0 1024 0 512 512 0 1 0-1024 0Z" fill="#c6dafe" p-id="4652"></path>
<path d="M486.4 665.6h51.2v153.6h-51.2z" fill="#515151" p-id="4653"
data-spm-anchor-id="a313x.search_index.0.i3.5ac83a811R1Mg6" class="selected"></path>
<path
d="M435.2 768h153.6v51.2h-153.6zM230.4 281.6v384h563.2V281.6H230.4z m0-51.2h563.2a51.2 51.2 0 0 1 51.2 51.2v384a51.2 51.2 0 0 1-51.2 51.2H230.4a51.2 51.2 0 0 1-51.2-51.2V281.6a51.2 51.2 0 0 1 51.2-51.2z"
fill="#515151" p-id="4654" data-spm-anchor-id="a313x.search_index.0.i1.5ac83a811R1Mg6" class="selected"></path>
<path
d="M662.272 388.864c6.144-21.5296 8.448-63.0272-29.952-68.3776-30.72-3.84-67.584 16.128-86.8352 29.2096-6.144-0.768-13.056-1.536-19.968-1.536-49.9712-0.7936-82.2016 16.128-109.8496 52.224-9.984 13.056-19.2 35.3536-22.2976 60.672 13.824-23.808 56.8576-67.584 102.1952-85.248 0 0-68.352 49.152-102.1952 118.3232v0.768c-1.5616 3.072-3.072 6.144-4.6336 9.984-33.024 82.2016-6.144 117.5552 19.2256 123.6992 23.04 6.144 55.3216-5.376 81.408-33.024 43.776 9.984 87.6288-1.536 103.7312-9.984 30.72-16.896 52.2496-46.848 56.8832-77.6192h-84.5312s-3.84 26.88-49.1776 26.88c-41.472 0-43.776-48.384-43.776-48.384h178.9952s3.072-52.2496-22.272-86.8352c-14.6176-19.2-33.8176-36.1216-60.6976-45.312 8.448-6.144 22.272-15.4112 34.56-18.4832 23.04-6.144 38.4256-2.304 48.4096 14.592 13.056 23.04-7.68 76.0832-7.68 76.0832s12.288-15.36 18.432-37.632z m-185.1648 203.6224c-35.328 29.2096-64.512 25.3696-76.0576 8.4736-9.984-15.36-11.52-42.2656 0-79.8976 5.376 14.592 13.824 28.416 26.112 40.704 15.36 15.36 32.256 25.344 49.9456 30.72z m-1.536-149.8368s1.536-34.56 39.168-37.632c32.3072-3.072 49.2032 11.52 54.5536 39.1936l-93.7472-1.536z"
fill="#2394FB" p-id="4655"></path>
</svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

@@ -114,7 +114,14 @@ const iconPaths = {
'core/dataset/modeEmbFTRerank': () => import('./icons/core/dataset/modeEmbFTRerank.svg'),
'core/app/variable/input': () => import('./icons/core/app/variable/input.svg'),
'core/app/variable/textarea': () => import('./icons/core/app/variable/textarea.svg'),
'core/app/variable/select': () => import('./icons/core/app/variable/select.svg')
'core/app/variable/select': () => import('./icons/core/app/variable/select.svg'),
'core/dataset/websiteDataset': () => import('./icons/core/dataset/websiteDataset.svg'),
'core/dataset/commonDataset': () => import('./icons/core/dataset/commonDataset.svg'),
'core/dataset/folderDataset': () => import('./icons/core/dataset/folderDataset.svg'),
'common/confirm/deleteTip': () => import('./icons/common/confirm/deleteTip.svg'),
'common/confirm/commonTip': () => import('./icons/common/confirm/commonTip.svg'),
'common/routePushLight': () => import('./icons/common/routePushLight.svg'),
'common/viewLight': () => import('./icons/common/viewLight.svg')
};
export type IconName = keyof typeof iconPaths;

View File

@@ -111,7 +111,7 @@ const Layout = ({ children }: { children: JSX.Element }) => {
</>
)}
</Box>
<Loading loading={loading} zIndex={9999} />
<Loading loading={loading} zIndex={999999} />
{!!userInfo && <UpdateInviteModal />}
</>
);

View File

@@ -1,30 +1,33 @@
import { Box, Flex } from '@chakra-ui/react';
import { Box, Flex, Grid } from '@chakra-ui/react';
import MdImage from '../img/Image';
import { useMemo } from 'react';
const ImageBlock = ({ images }: { images: string }) => {
const formatData = useMemo(
() =>
images.split('\n').map((item) => {
try {
return JSON.parse(item) as { src: string };
} catch (error) {
return { src: '' };
}
}),
images
.split('\n')
.filter((item) => item)
.map((item) => {
try {
return JSON.parse(item) as { src: string };
} catch (error) {
return { src: '' };
}
}),
[images]
);
return (
<Flex alignItems={'center'} wrap={'wrap'} gap={4}>
<Grid gridTemplateColumns={['1fr', '1fr 1fr']} gap={4}>
{formatData.map(({ src }) => {
return (
<Box key={src} rounded={'md'} flex={'0 0 auto'} w={'120px'}>
<Box key={src} rounded={'md'} flex={'1 0 0'} minW={'120px'}>
<MdImage src={src} />
</Box>
);
})}
</Flex>
</Grid>
);
};

View File

@@ -29,7 +29,6 @@ const MdImage = ({ src }: { src?: string }) => {
borderRadius={'md'}
src={src}
alt={''}
maxH={'150px'}
fallbackSrc={'/imgs/errImg.png'}
fallbackStrategy={'onError'}
cursor={succeed ? 'pointer' : 'default'}

View File

@@ -9,6 +9,7 @@ import {
Box,
Image
} from '@chakra-ui/react';
import MyIcon from '../Icon';
export interface MyModalProps extends ModalContentProps {
iconSrc?: string;
@@ -56,7 +57,15 @@ const MyModal = ({
roundedTop={'lg'}
py={'10px'}
>
{iconSrc && <Image mr={3} objectFit={'contain'} alt="" src={iconSrc} w={'20px'} />}
{iconSrc && (
<>
{iconSrc.startsWith('/') ? (
<Image mr={3} objectFit={'contain'} alt="" src={iconSrc} w={'20px'} />
) : (
<MyIcon mr={3} name={iconSrc as any} w={'20px'} />
)}
</>
)}
{title}
<Box flex={1} />
{onClose && <ModalCloseButton position={'relative'} top={0} right={0} />}

View File

@@ -1,5 +1,5 @@
import React from 'react';
import { Box, Flex, useTheme, Grid, type GridProps, theme } from '@chakra-ui/react';
import { Box, Flex, useTheme, Grid, type GridProps, theme, Image } from '@chakra-ui/react';
import MyIcon from '@/components/Icon';
import { useTranslation } from 'next-i18next';
@@ -45,8 +45,7 @@ const MyRadio = ({
: {
bg: 'myWhite.300',
_hover: {
bg: '#f5f8ff',
borderColor: '#b2ccff'
borderColor: 'myBlue.500'
}
})}
_after={{
@@ -71,7 +70,15 @@ const MyRadio = ({
}}
onClick={() => onChange(item.value)}
>
{!!item.icon && <MyIcon mr={'14px'} name={item.icon as any} w={iconSize} />}
{!!item.icon && (
<>
{item.icon.startsWith('/') ? (
<Image src={item.icon} mr={'14px'} w={iconSize} alt={''} />
) : (
<MyIcon mr={'14px'} name={item.icon as any} w={iconSize} />
)}
</>
)}
<Box pr={2}>
<Box>{t(item.title)}</Box>
{!!item.desc && (

View File

@@ -7,29 +7,20 @@ import {
ModalBody,
ModalFooter,
useTheme,
Textarea,
Grid,
Divider,
Switch,
Image
Divider
} from '@chakra-ui/react';
import Avatar from '@/components/Avatar';
import { useForm } from 'react-hook-form';
import { QuestionOutlineIcon } from '@chakra-ui/icons';
import type { SelectedDatasetType } from '@fastgpt/global/core/module/api.d';
import { useToast } from '@/web/common/hooks/useToast';
import MySlider from '@/components/Slider';
import MyTooltip from '@/components/MyTooltip';
import MyModal from '@/components/MyModal';
import MyIcon from '@/components/Icon';
import { DatasetSearchModeEnum, DatasetTypeEnum } from '@fastgpt/global/core/dataset/constant';
import { DatasetTypeEnum } from '@fastgpt/global/core/dataset/constant';
import { useTranslation } from 'next-i18next';
import { useDatasetStore } from '@/web/core/dataset/store/dataset';
import { feConfigs } from '@/web/common/system/staticData';
import DatasetSelectContainer, { useDatasetSelect } from '@/components/core/dataset/SelectModal';
import { useLoading } from '@/web/common/hooks/useLoading';
import EmptyTip from '@/components/EmptyTip';
import { AppSimpleEditFormType } from '@fastgpt/global/core/app/type';
export const DatasetSelectModal = ({
isOpen,
@@ -132,9 +123,9 @@ export const DatasetSelectModal = ({
<MyTooltip
key={item._id}
label={
item.type === DatasetTypeEnum.dataset
? t('dataset.Select Dataset')
: t('dataset.Select Folder')
item.type === DatasetTypeEnum.folder
? t('dataset.Select Folder')
: t('dataset.Select Dataset')
}
>
<Card
@@ -149,7 +140,7 @@ export const DatasetSelectModal = ({
onClick={() => {
if (item.type === DatasetTypeEnum.folder) {
setParentId(item._id);
} else if (item.type === DatasetTypeEnum.dataset) {
} else {
const vectorModel = selectedDatasets[0]?.vectorModel?.model;
if (vectorModel && vectorModel !== item.vectorModel.model) {

View File

@@ -51,8 +51,8 @@ function EditModal({
try {
const src = await compressImgFileAndUpload({
file,
maxW: 100,
maxH: 100
maxW: 300,
maxH: 300
});
setValue('avatar', src);
setRefresh((state) => !state);

View File

@@ -14,7 +14,8 @@ export const defaultDatasetDetail: DatasetItemType = {
type: 'dataset',
avatar: '/icon/logo.svg',
name: '',
tags: [],
intro: '',
status: 'active',
permission: 'private',
isOwner: false,
canWrite: false,
@@ -36,7 +37,8 @@ export const defaultCollectionDetail: DatasetCollectionItemType = {
type: 'dataset',
avatar: '/icon/logo.svg',
name: '',
tags: [],
intro: '',
status: 'active',
permission: 'private',
vectorModel: defaultVectorModels[0].model,
agentModel: defaultQAModels[0].model

View File

@@ -12,15 +12,6 @@ import { PermissionTypeEnum } from '@fastgpt/global/support/permission/constant'
import type { LLMModelItemType } from '@fastgpt/global/core/ai/model.d';
/* ===== dataset ===== */
export type DatasetUpdateParams = {
id: string;
parentId?: string;
tags?: string[];
name?: string;
avatar?: string;
permission?: `${PermissionTypeEnum}`;
agentModel?: LLMModelItemType;
};
/* ======= collections =========== */
export type GetDatasetCollectionsProps = RequestPaging & {
@@ -30,16 +21,7 @@ export type GetDatasetCollectionsProps = RequestPaging & {
simple?: boolean;
selectFolder?: boolean;
};
export type CreateDatasetCollectionParams = {
datasetId: string;
parentId?: string;
name: string;
type: `${DatasetCollectionTypeEnum}`;
trainingType?: `${DatasetCollectionTrainingModeEnum}`;
chunkSize?: number;
fileId?: string;
rawLink?: string;
};
export type UpdateDatasetCollectionParams = {
id: string;
parentId?: string;

View File

@@ -1,5 +1,9 @@
import { PushDatasetDataChunkProps } from '@fastgpt/global/core/dataset/api';
import { DatasetSearchModeEnum, TrainingModeEnum } from '@fastgpt/global/core/dataset/constant';
import {
DatasetSearchModeEnum,
DatasetTypeEnum,
TrainingModeEnum
} from '@fastgpt/global/core/dataset/constant';
import {
DatasetDataIndexItemType,
SearchDataResponseItemType
@@ -8,8 +12,9 @@ import {
/* ================= dataset ===================== */
export type CreateDatasetParams = {
parentId?: string;
type: `${DatasetTypeEnum}`;
name: string;
tags: string;
intro: string;
avatar: string;
vectorModel?: string;
agentModel?: string;

View File

@@ -27,57 +27,56 @@ export const Prompt_QuotePromptList: PromptTemplateItem[] = [
{
title: '标准模板',
desc: '',
value: `你的背景知识:
value: `你的知识:
"""
{{quote}}
"""
对话要求:
1. 背景知识是最新的实时的信息,使用背景知识回答问题。
2. 优先使用背景知识的内容回答我的问题,答案应与背景知识严格一致
3. 背景知识无法回答我的问题时,可以忽略背景知识,根据你的知识来自由回答
4. 使用对话的风格,自然的回答问题。包含markdown内容,需按markdown格式返回。
回答要求:
1. 优先使用知识库内容回答问题。
2. 你可以回答我不知道
3. 不要提及你是从知识库获取的知识
4. 知识库包含 markdown 内容时,按 markdown 格式返回。
我的问题是:"{{question}}"`
},
{
title: '问答模板',
desc: '',
value: `你的背景知识:
value: `你的知识:
"""
{{quote}}
"""
对话要求:
1. 背景知识是最新的实时的信息,使用背景知识回答问题,其中 instruction 是相关介绍output 是预期回答或补充。
2. 优先使用背景知识的内容回答我的问题,答案应与背景知识严格一致
3. 背景知识无法回答我的问题时,可以忽略背景知识,根据你的知识来自由回答
4. 使用对话的风格,自然的回答问题。包含markdown内容,需按markdown格式返回。
回答要求:
1. 优先使用知识库内容回答问题,其中 instruction 是相关介绍output 是预期回答或补充。
2. 你可以回答我不知道
3. 不要提及你是从知识库获取的知识
4. 知识库包含 markdown 内容时,按 markdown 格式返回。
我的问题是:"{{question}}"`
},
{
title: '标准严格模板',
desc: '',
value: `你的背景知识:
value: `你的知识:
"""
{{quote}}
"""
对话要求:
1. 背景知识是最新的实时的信息,是你的唯一信息来源,使用背景知识回答问题。
2. 优先使用背景知识回答我的问题,答案与背景知识完全一致,无需做其他回答
3. 背景知识与问题无关或背景知识无法回答本次问题时则拒绝回答本次问题“我不太清除xxx”
4. 使用对话的风格,自然的回答问题。包含markdown内容,需按markdown格式返回。
回答要求:
1. 仅使用知识库内容回答问题。
2. 与知识库无关的问题,你直接回答我不知道
3. 不要提及你是从知识库获取的知识
4. 知识库包含 markdown 内容时,按 markdown 格式返回。
我的问题是:"{{question}}"`
},
{
title: '严格问答模板',
desc: '',
value: `你的背景知识:
value: `你的知识:
"""
{{quote}}
"""
对话要求:
1. 背景知识是最新的实时的信息,是你的唯一信息来源,使用背景知识回答问题
2. 在背景知识的 JSON 中question 是相关问题answer 是已知答案
3. 选择 answer 中的内容作为答案,要求答案与 answer 完全一致,无需做其他回答
4. answer 中的答案无法满足问题直接回复“我不太清除xxx”。
回答要求:
1. 从知识库中选择一个合适的答案进行回答,其中 instruction 是相关问题answer 是已知答案
2. 与知识库无关的问题,你直接回答我不知道
3. 不要提及你是从知识库获取的知识
我的问题是:"{{question}}"`
}
];

View File

@@ -35,7 +35,7 @@ const BillDetail = ({ bill, onClose }: { bill: BillItemType; onClose: () => void
<ModalBody>
<Flex alignItems={'center'} pb={4}>
<Box flex={'0 0 80px'}>:</Box>
<Box>{bill.memberName}</Box>
<Box>{t(bill.memberName)}</Box>
</Flex>
<Flex alignItems={'center'} pb={4}>
<Box flex={'0 0 80px'}>:</Box>

View File

@@ -96,8 +96,8 @@ const UserInfo = () => {
try {
const src = await compressImgFileAndUpload({
file,
maxW: 100,
maxH: 100
maxW: 300,
maxH: 300
});
onclickSave({

View File

@@ -1,7 +1,7 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@fastgpt/service/common/response';
import { connectToDatabase } from '@/service/mongo';
import { delay } from '@/utils/tools';
import { delay } from '@fastgpt/global/common/system/utils';
import { PgClient } from '@fastgpt/service/common/pg';
import {
DatasetDataIndexTypeEnum,

View File

@@ -1,12 +1,9 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@fastgpt/service/common/response';
import { connectToDatabase } from '@/service/mongo';
import { delay } from '@/utils/tools';
import { delay } from '@fastgpt/global/common/system/utils';
import { PgClient } from '@fastgpt/service/common/pg';
import {
DatasetDataIndexTypeEnum,
PgDatasetTableName
} from '@fastgpt/global/core/dataset/constant';
import { PgDatasetTableName } from '@fastgpt/global/core/dataset/constant';
import { authCert } from '@fastgpt/service/support/permission/auth/common';
import { MongoDatasetData } from '@fastgpt/service/core/dataset/data/schema';

View File

@@ -8,7 +8,7 @@ import {
} from '@fastgpt/service/support/user/team/controller';
import { MongoUser } from '@fastgpt/service/support/user/schema';
import { UserModelSchema } from '@fastgpt/global/support/user/type';
import { delay } from '@/utils/tools';
import { delay } from '@fastgpt/global/common/system/utils';
import { MongoDataset } from '@fastgpt/service/core/dataset/schema';
import { PermissionTypeEnum } from '@fastgpt/global/support/permission/constant';
import { MongoDatasetCollection } from '@fastgpt/service/core/dataset/collection/schema';

View File

@@ -1,7 +1,7 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@fastgpt/service/common/response';
import { connectToDatabase } from '@/service/mongo';
import { delay } from '@/utils/tools';
import { delay } from '@fastgpt/global/common/system/utils';
import { authCert } from '@fastgpt/service/support/permission/auth/common';
import { MongoApp } from '@fastgpt/service/core/app/schema';
import { FlowNodeInputTypeEnum, FlowNodeTypeEnum } from '@fastgpt/global/core/module/node/constant';

View File

@@ -1,7 +1,7 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@fastgpt/service/common/response';
import { connectToDatabase } from '@/service/mongo';
import { delay } from '@/utils/tools';
import { delay } from '@fastgpt/global/common/system/utils';
import { authCert } from '@fastgpt/service/support/permission/auth/common';
import { MongoDatasetData } from '@fastgpt/service/core/dataset/data/schema';
import { jiebaSplit } from '@/service/core/dataset/utils';
@@ -17,10 +17,13 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
console.log(
'total',
await MongoDatasetData.countDocuments({ fullTextToken: { $exists: false } })
await MongoDatasetData.countDocuments({
fullTextToken: { $exists: false },
updateTime: { $lt: new Date() }
})
);
await initFullTextToken(limit);
await initFullTextToken(limit, new Date());
jsonRes(res, {
message: 'success'
@@ -34,9 +37,12 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
});
}
}
export async function initFullTextToken(limit = 50): Promise<any> {
export async function initFullTextToken(limit = 50, endDate: Date): Promise<any> {
try {
const dataList = await MongoDatasetData.find({ fullTextToken: { $exists: false } }, '_id q a')
const dataList = await MongoDatasetData.find(
{ fullTextToken: { $exists: false }, updateTime: { $lt: endDate } },
'_id q a'
)
.limit(limit)
.lean();
if (dataList.length === 0) return;
@@ -56,9 +62,9 @@ export async function initFullTextToken(limit = 50): Promise<any> {
success += result.filter((item) => item.status === 'fulfilled').length;
console.log(`success: ${success}`);
return initFullTextToken(limit);
return initFullTextToken(limit, endDate);
} catch (error) {
await delay(1000);
return initFullTextToken(limit);
return initFullTextToken(limit, endDate);
}
}

View File

@@ -0,0 +1,62 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@fastgpt/service/common/response';
import { connectToDatabase } from '@/service/mongo';
import { delay } from '@fastgpt/global/common/system/utils';
import { authCert } from '@fastgpt/service/support/permission/auth/common';
import { MongoDatasetData } from '@fastgpt/service/core/dataset/data/schema';
import { jiebaSplit } from '@/service/core/dataset/utils';
let success = 0;
/* pg 中的数据搬到 mongo dataset.datas 中,并做映射 */
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
const { limit = 50 } = req.body as { limit: number };
await authCert({ req, authRoot: true });
await connectToDatabase();
success = 0;
console.log('total', await MongoDatasetData.countDocuments({ inited: { $exists: false } }));
await initFullTextToken(limit);
jsonRes(res, {
message: 'success'
});
} catch (error) {
console.log(error);
jsonRes(res, {
code: 500,
error
});
}
}
export async function initFullTextToken(limit = 50): Promise<any> {
try {
const dataList = await MongoDatasetData.find({ inited: { $exists: false } }, '_id q a')
.limit(limit)
.lean();
if (dataList.length === 0) return;
const result = await Promise.allSettled(
dataList.map((item) => {
const text = item.q + (item.a || '');
const tokens = jiebaSplit({ text });
return MongoDatasetData.findByIdAndUpdate(item._id, {
$set: {
inited: true,
fullTextToken: tokens
}
});
})
);
success += result.filter((item) => item.status === 'fulfilled').length;
console.log(`success: ${success}`);
return initFullTextToken(limit);
} catch (error) {
await delay(1000);
return initFullTextToken(limit);
}
}

View File

@@ -4,7 +4,8 @@ import { connectToDatabase } from '@/service/mongo';
import { authCert } from '@fastgpt/service/support/permission/auth/common';
import { MongoDatasetData } from '@fastgpt/service/core/dataset/data/schema';
import { MongoDatasetCollection } from '@fastgpt/service/core/dataset/collection/schema';
import { TrainingModeEnum } from '@fastgpt/global/core/dataset/constant';
import { DatasetStatusEnum, TrainingModeEnum } from '@fastgpt/global/core/dataset/constant';
import { MongoDataset } from '@fastgpt/service/core/dataset/schema';
let success = 0;
/* pg 中的数据搬到 mongo dataset.datas 中,并做映射 */
@@ -15,32 +16,85 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
await connectToDatabase();
success = 0;
await MongoDatasetCollection.updateMany({}, [
await MongoDatasetCollection.updateMany({ createTime: { $exists: false } }, [
{
$set: {
createTime: '$updateTime'
}
}
]);
await MongoDatasetCollection.updateMany({ trainingType: { $exists: false } }, [
{
$set: {
createTime: '$updateTime',
trainingType: {
$cond: {
if: { $ifNull: ['$a', false] },
then: TrainingModeEnum.qa,
else: TrainingModeEnum.chunk
}
},
chunkSize: 0,
fileId: '$metadata.fileId',
}
}
}
]);
await MongoDatasetCollection.updateMany({ chunkSize: { $exists: false } }, [
{
$set: {
chunkSize: 0
}
}
]);
await MongoDatasetCollection.updateMany({ fileId: { $exists: false } }, [
{
$set: {
fileId: '$metadata.fileId'
}
}
]);
await MongoDatasetCollection.updateMany({ rawLink: { $exists: false } }, [
{
$set: {
rawLink: '$metadata.rawLink'
}
}
]);
await MongoDatasetData.updateMany(
{},
{ chunkIndex: { $exists: false } },
{
chunkIndex: 0
}
);
await MongoDatasetData.updateMany(
{ updateTime: { $exists: false } },
{
chunkIndex: 0,
updateTime: new Date()
}
);
await MongoDataset.updateMany(
{ status: { $exists: false } },
{
$set: {
status: DatasetStatusEnum.active
}
}
);
// dataset tags to intro
await MongoDataset.updateMany({ tags: { $exists: true } }, [
{
$set: {
intro: {
$reduce: {
input: '$tags',
initialValue: '',
in: { $concat: ['$$value', ' ', '$$this'] }
}
}
}
}
]);
jsonRes(res, {
message: 'success'
});

View File

@@ -0,0 +1,92 @@
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 { delFileById, getGFSCollection } from '@fastgpt/service/common/file/gridfs/controller';
import { addLog } from '@fastgpt/service/common/mongo/controller';
import { MongoDatasetCollection } from '@fastgpt/service/core/dataset/collection/schema';
import { delay } from '@fastgpt/global/common/system/utils';
/*
check dataset.files data. If there is no match in dataset.collections, delete it
*/
let deleteFileAmount = 0;
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
const {
startDay = 10,
endDay = 3,
limit = 30
} = req.body as { startDay?: number; endDay?: number; limit?: number };
await authCert({ req, authRoot: true });
await connectToDatabase();
// start: now - maxDay, end: now - 3 day
const start = new Date(Date.now() - startDay * 24 * 60 * 60 * 1000);
const end = new Date(Date.now() - endDay * 24 * 60 * 60 * 1000);
deleteFileAmount = 0;
checkFiles(start, end, limit);
jsonRes(res, {
message: 'success'
});
} catch (error) {
addLog.error(`check valid dataset files error`, error);
jsonRes(res, {
code: 500,
error
});
}
}
export async function checkFiles(start: Date, end: Date, limit: number) {
const collection = getGFSCollection('dataset');
const where = {
uploadDate: { $gte: start, $lte: end }
};
// 1. get all _id
const ids = await collection
.find(where, {
projection: {
_id: 1
}
})
.toArray();
console.log('total files', ids.length);
for (let i = 0; i < limit; i++) {
check(i);
}
async function check(index: number): Promise<any> {
const id = ids[index];
if (!id) {
console.log(`检测完成,共删除 ${deleteFileAmount} 个无效文件`);
return;
}
try {
const { _id } = id;
// 2. find fileId in dataset.collections
const hasCollection = await MongoDatasetCollection.countDocuments({ fileId: _id });
// 3. if not found, delete file
if (hasCollection === 0) {
await delFileById({ bucketName: 'dataset', fileId: String(_id) });
console.log('delete file', _id);
deleteFileAmount++;
}
index % 100 === 0 && console.log(index);
return check(index + limit);
} catch (error) {
console.log(error);
await delay(2000);
return check(index);
}
}
}

View File

@@ -2,8 +2,8 @@ import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@fastgpt/service/common/response';
import { connectToDatabase } from '@/service/mongo';
import { authFileToken } from '@fastgpt/service/support/permission/controller';
import jschardet from 'jschardet';
import { getDownloadBuf, getFileById } from '@fastgpt/service/common/file/gridfs/controller';
import { detect } from 'jschardet';
import { getDownloadStream, getFileById } from '@fastgpt/service/common/file/gridfs/controller';
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
try {
@@ -11,24 +11,43 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
const { token } = req.query as { token: string };
const { fileId, teamId, tmbId, bucketName } = await authFileToken(token);
const { fileId, bucketName } = await authFileToken(token);
if (!fileId) {
throw new Error('fileId is empty');
}
const [file, buffer] = await Promise.all([
const [file, encodeStream] = await Promise.all([
getFileById({ bucketName, fileId }),
getDownloadBuf({ bucketName, fileId })
getDownloadStream({ bucketName, fileId })
]);
const encoding = jschardet.detect(buffer)?.encoding;
// get encoding
let buffers: Buffer = Buffer.from([]);
for await (const chunk of encodeStream) {
buffers = Buffer.concat([buffers, chunk]);
if (buffers.length > 10) {
encodeStream.abort();
break;
}
}
const encoding = detect(buffers)?.encoding || 'utf-8';
res.setHeader('Content-Type', `${file.contentType}; charset=${encoding}`);
res.setHeader('Cache-Control', 'public, max-age=3600');
res.setHeader('Content-Disposition', `inline; filename="${encodeURIComponent(file.filename)}"`);
res.end(buffer);
const fileStream = await getDownloadStream({ bucketName, fileId });
fileStream.pipe(res);
fileStream.on('error', () => {
res.status(500).end();
});
fileStream.on('end', () => {
res.end();
});
} catch (error) {
jsonRes(res, {
code: 500,

View File

@@ -52,12 +52,12 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
/* start process */
const { responseData } = await dispatchModules({
res,
appId,
modules,
variables,
teamId,
tmbId,
user,
appId,
modules,
variables,
params: {
history,
userChatInput: prompt

View File

@@ -2,10 +2,11 @@ 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 { getQAModel, getVectorModel } from '@/service/core/ai/model';
import type { DatasetItemType } from '@fastgpt/global/core/dataset/type.d';
import { getVectorModel } from '@/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';
import { DatasetTypeEnum } from '@fastgpt/global/core/dataset/constant';
/* get all dataset by teamId or tmbId */
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
@@ -16,18 +17,23 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
const datasets = await MongoDataset.find({
...mongoRPermission({ teamId, tmbId, role }),
type: 'dataset'
type: { $ne: DatasetTypeEnum.folder }
}).lean();
const data = datasets.map((item) => ({
...item,
_id: item._id,
parentId: item.parentId,
avatar: item.avatar,
name: item.name,
intro: item.intro,
type: item.type,
permission: item.permission,
vectorModel: getVectorModel(item.vectorModel),
agentModel: getQAModel(item.agentModel),
canWrite: String(item.tmbId) === tmbId,
isOwner: teamOwner || String(item.tmbId) === tmbId
}));
jsonRes<DatasetItemType[]>(res, {
jsonRes<DatasetListItemType[]>(res, {
data
});
} catch (err) {

View File

@@ -4,15 +4,10 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@fastgpt/service/common/response';
import { connectToDatabase } from '@/service/mongo';
import type { CreateDatasetCollectionParams } from '@/global/core/api/datasetReq.d';
import { MongoDatasetCollection } from '@fastgpt/service/core/dataset/collection/schema';
import {
TrainingModeEnum,
DatasetCollectionTypeEnum,
DatasetCollectionTrainingModeEnum
} from '@fastgpt/global/core/dataset/constant';
import type { CreateDatasetCollectionParams } from '@fastgpt/global/core/dataset/api.d';
import { authUserNotVisitor } from '@fastgpt/service/support/permission/auth/user';
import { authDataset } from '@fastgpt/service/support/permission/auth/dataset';
import { createOneCollection } from '@fastgpt/service/core/dataset/collection/controller';
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
try {
@@ -42,68 +37,3 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
});
}
}
export async function createOneCollection({
name,
parentId,
datasetId,
type,
trainingType = DatasetCollectionTrainingModeEnum.manual,
chunkSize = 0,
fileId,
rawLink,
teamId,
tmbId
}: CreateDatasetCollectionParams & { teamId: string; tmbId: string }) {
const { _id } = await MongoDatasetCollection.create({
name,
teamId,
tmbId,
datasetId,
parentId: parentId || null,
type,
trainingType,
chunkSize,
fileId,
rawLink
});
// create default collection
if (type === DatasetCollectionTypeEnum.folder) {
await createDefaultCollection({
datasetId,
parentId: _id,
teamId,
tmbId
});
}
return _id;
}
// create default collection
export function createDefaultCollection({
name = '手动录入',
datasetId,
parentId,
teamId,
tmbId
}: {
name?: '手动录入' | '手动标注';
datasetId: string;
parentId?: string;
teamId: string;
tmbId: string;
}) {
return MongoDatasetCollection.create({
name,
teamId,
tmbId,
datasetId,
parentId,
type: DatasetCollectionTypeEnum.virtual,
trainingType: DatasetCollectionTrainingModeEnum.manual,
chunkSize: 0,
updateTime: new Date('2099')
});
}

View File

@@ -1,13 +1,10 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@fastgpt/service/common/response';
import { connectToDatabase } from '@/service/mongo';
import { MongoDatasetTraining } from '@fastgpt/service/core/dataset/training/schema';
import { findCollectionAndChild } from '@fastgpt/service/core/dataset/collection/utils';
import { delDataByCollectionId } from '@/service/core/dataset/data/controller';
import { MongoDatasetCollection } from '@fastgpt/service/core/dataset/collection/schema';
import { delCollectionRelevantData } from '@fastgpt/service/core/dataset/data/controller';
import { authDatasetCollection } from '@fastgpt/service/support/permission/auth/dataset';
import { delFileById } from '@fastgpt/service/common/file/gridfs/controller';
import { BucketNameEnum } from '@fastgpt/global/common/file/constants';
import { MongoDatasetCollection } from '@fastgpt/service/core/dataset/collection/schema';
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
try {
@@ -19,7 +16,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
throw new Error('CollectionIdId is required');
}
const { teamId } = await authDatasetCollection({
await authDatasetCollection({
req,
authToken: true,
collectionId,
@@ -30,26 +27,12 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
const collections = await findCollectionAndChild(collectionId, '_id metadata');
const delIdList = collections.map((item) => item._id);
// delete training data
await MongoDatasetTraining.deleteMany({
collectionId: { $in: delIdList },
teamId
// delete
await delCollectionRelevantData({
collectionIds: delIdList,
fileIds: collections.map((item) => String(item.metadata?.fileId)).filter(Boolean)
});
// delete pg data
await delDataByCollectionId({ collectionIds: delIdList });
// delete file
await Promise.all(
collections.map((collection) => {
if (!collection?.fileId) return;
return delFileById({
bucketName: BucketNameEnum.dataset,
fileId: collection.fileId
});
})
);
// delete collection
await MongoDatasetCollection.deleteMany({
_id: { $in: delIdList }

View File

@@ -6,7 +6,10 @@ import { Types } from '@fastgpt/service/common/mongo';
import type { DatasetCollectionsListItemType } from '@/global/core/dataset/type.d';
import type { GetDatasetCollectionsProps } from '@/global/core/api/datasetReq';
import { PagingData } from '@/types';
import { MongoDatasetCollection } from '@fastgpt/service/core/dataset/collection/schema';
import {
DatasetColCollectionName,
MongoDatasetCollection
} from '@fastgpt/service/core/dataset/collection/schema';
import { DatasetCollectionTypeEnum } from '@fastgpt/global/core/dataset/constant';
import { startQueue } from '@/service/utils/tools';
import { authDataset } from '@fastgpt/service/support/permission/auth/dataset';
@@ -45,7 +48,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
// not count data amount
if (simple) {
const collections = await MongoDatasetCollection.find(match, '_id name type parentId')
const collections = await MongoDatasetCollection.find(match, '_id parentId type name')
.sort({
updateTime: -1
})
@@ -72,6 +75,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
{
$match: match
},
// count training data
{
$lookup: {
from: DatasetTrainingCollectionName,
@@ -89,6 +93,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
as: 'trainings'
}
},
// count collection total data
{
$lookup: {
from: DatasetDataCollectionName,
@@ -106,7 +111,6 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
as: 'datas'
}
},
// 统计子集合的数量和子训练的数量
{
$project: {
_id: 1,
@@ -114,6 +118,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
tmbId: 1,
name: 1,
type: 1,
status: 1,
updateTime: 1,
dataAmount: { $size: '$datas' },
trainingAmount: { $size: '$trainings' },

View File

@@ -3,20 +3,20 @@ import { jsonRes } from '@fastgpt/service/common/response';
import { connectToDatabase } from '@/service/mongo';
import { MongoDataset } from '@fastgpt/service/core/dataset/schema';
import type { CreateDatasetParams } from '@/global/core/dataset/api.d';
import { createDefaultCollection } from './collection/create';
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/constant';
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
try {
await connectToDatabase();
const {
parentId,
name,
tags,
type,
avatar,
vectorModel = global.vectorModels[0].model,
agentModel,
parentId,
type
agentModel
} = req.body as CreateDatasetParams;
// 凭证校验
@@ -26,7 +26,6 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
name,
teamId,
tmbId,
tags,
vectorModel,
agentModel,
avatar,
@@ -34,11 +33,13 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
type
});
await createDefaultCollection({
datasetId: _id,
teamId,
tmbId
});
if (type === DatasetTypeEnum.dataset) {
await createDefaultCollection({
datasetId: _id,
teamId,
tmbId
});
}
jsonRes(res, { data: _id });
} catch (err) {

View File

@@ -3,7 +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 { deleteDataByDataId } from '@/service/core/dataset/data/controller';
import { delDatasetDataByDataId } from '@fastgpt/service/core/dataset/data/controller';
export default withNextCors(async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
try {
@@ -19,7 +19,7 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
// 凭证校验
await authDatasetData({ req, authToken: true, dataId, per: 'w' });
await deleteDataByDataId(dataId);
await delDatasetDataByDataId(dataId);
jsonRes(res, {
data: 'success'

View File

@@ -1,13 +1,10 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@fastgpt/service/common/response';
import { connectToDatabase } from '@/service/mongo';
import { MongoDatasetTraining } from '@fastgpt/service/core/dataset/training/schema';
import { MongoDataset } from '@fastgpt/service/core/dataset/schema';
import { delDatasetFiles } from '@fastgpt/service/core/dataset/file/controller';
import { MongoDatasetCollection } from '@fastgpt/service/core/dataset/collection/schema';
import { authDataset } from '@fastgpt/service/support/permission/auth/dataset';
import { delDataByDatasetId } from '@/service/core/dataset/data/controller';
import { delDatasetRelevantData } from '@fastgpt/service/core/dataset/data/controller';
import { findDatasetIdTreeByTopDatasetId } from '@fastgpt/service/core/dataset/controller';
import { MongoDataset } from '@fastgpt/service/core/dataset/schema';
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
try {
@@ -25,21 +22,8 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
const deletedIds = await findDatasetIdTreeByTopDatasetId(id);
// delete training data(There could be a training mission)
await MongoDatasetTraining.deleteMany({
datasetId: { $in: deletedIds }
});
// delete all dataset.data and pg data
await delDataByDatasetId({ datasetIds: deletedIds });
// delete related files
await delDatasetFiles({ datasetId: id });
// delete collections
await MongoDatasetCollection.deleteMany({
datasetId: { $in: deletedIds }
});
await delDatasetRelevantData({ datasetIds: deletedIds });
// delete dataset data
await MongoDataset.deleteMany({

View File

@@ -1,12 +1,12 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@fastgpt/service/common/response';
import { connectToDatabase } from '@/service/mongo';
import { getQAModel, getVectorModel } from '@/service/core/ai/model';
import type { DatasetItemType } from '@fastgpt/global/core/dataset/type.d';
import type { DatasetListItemType } from '@fastgpt/global/core/dataset/type.d';
import { DatasetTypeEnum } from '@fastgpt/global/core/dataset/constant';
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';
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
try {
@@ -27,16 +27,21 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
.lean();
const data = await Promise.all(
datasets.map(async (item) => ({
...item,
vectorModel: getVectorModel(item.vectorModel),
agentModel: getQAModel(item.agentModel),
datasets.map<DatasetListItemType>((item) => ({
_id: item._id,
parentId: item.parentId,
avatar: item.avatar,
name: item.name,
intro: item.intro,
type: item.type,
permission: item.permission,
canWrite,
isOwner: teamOwner || String(item.tmbId) === tmbId
isOwner: teamOwner || String(item.tmbId) === tmbId,
vectorModel: getVectorModel(item.vectorModel)
}))
);
jsonRes<DatasetItemType[]>(res, {
jsonRes<DatasetListItemType[]>(res, {
data
});
} catch (err) {

View File

@@ -44,7 +44,7 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
});
// push bill
pushGenerateVectorBill({
const { total } = pushGenerateVectorBill({
teamId,
tmbId,
tokenLen: tokenLen,
@@ -54,11 +54,7 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
if (apikey) {
updateApiKeyUsage({
apikey,
usage: countModelPrice({
model: dataset.vectorModel,
tokens: tokenLen,
type: ModelTypeEnum.vector
})
usage: total
});
}

View File

@@ -2,14 +2,14 @@ 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 type { DatasetUpdateParams } from '@/global/core/api/datasetReq.d';
import type { DatasetUpdateBody } from '@fastgpt/global/core/dataset/api.d';
import { authDataset } from '@fastgpt/service/support/permission/auth/dataset';
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
try {
await connectToDatabase();
const { id, parentId, name, avatar, tags, permission, agentModel } =
req.body as DatasetUpdateParams;
const { id, parentId, name, avatar, tags, permission, agentModel, websiteConfig, status } =
req.body as DatasetUpdateBody;
if (!id) {
throw new Error('缺少参数');
@@ -28,7 +28,9 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
...(avatar && { avatar }),
...(tags && { tags }),
...(permission && { permission }),
...(agentModel && { agentModel: agentModel.model })
...(agentModel && { agentModel: agentModel.model }),
...(websiteConfig && { websiteConfig }),
...(status && { status })
}
);

View File

@@ -1,73 +0,0 @@
// pages/api/fetchContent.ts
import { NextApiRequest, NextApiResponse } from 'next';
import axios from 'axios';
import { JSDOM } from 'jsdom';
import { Readability } from '@mozilla/readability';
import { jsonRes } from '@fastgpt/service/common/response';
import { authCert } from '@fastgpt/service/support/permission/auth/common';
import type { FetchResultItem } from '@fastgpt/global/common/plugin/types/pluginRes.d';
import { simpleText } from '@fastgpt/global/common/string/tools';
import { connectToDatabase } from '@/service/mongo';
export type UrlFetchResponse = FetchResultItem[];
const fetchContent = async (req: NextApiRequest, res: NextApiResponse) => {
try {
await connectToDatabase();
let { urlList = [] } = req.body as { urlList: string[] };
if (!urlList || urlList.length === 0) {
throw new Error('urlList is empty');
}
await authCert({ req, authToken: true });
urlList = urlList.filter((url) => /^(http|https):\/\/[^ "]+$/.test(url));
const response = (
await Promise.allSettled(
urlList.map(async (url) => {
try {
const fetchRes = await axios.get(url, {
timeout: 30000
});
const dom = new JSDOM(fetchRes.data, {
url,
contentType: 'text/html'
});
const reader = new Readability(dom.window.document);
const article = reader.parse();
const content = article?.textContent || '';
return {
url,
content: simpleText(`${article?.title}\n${content}`)
};
} catch (error) {
return {
url,
content: ''
};
}
})
)
)
.filter((item) => item.status === 'fulfilled')
.map((item: any) => item.value)
.filter((item) => item.content);
jsonRes<UrlFetchResponse>(res, {
data: response
});
} catch (error: any) {
jsonRes(res, {
code: 500,
error: error
});
}
};
export default fetchContent;

View File

@@ -24,13 +24,13 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
source: BillSourceEnum.training,
list: [
{
moduleName: '索引生成',
moduleName: 'wallet.moduleName.index',
model: vectorModelData.name,
amount: 0,
tokenLen: 0
},
{
moduleName: 'QA 拆分',
moduleName: 'wallet.moduleName.qa',
model: agentModelData.name,
amount: 0,
tokenLen: 0

View File

@@ -0,0 +1,34 @@
// pages/api/fetchContent.ts
import { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@fastgpt/service/common/response';
import { authCert } from '@fastgpt/service/support/permission/auth/common';
import { connectToDatabase } from '@/service/mongo';
import { UrlFetchParams, UrlFetchResponse } from '@fastgpt/global/common/file/api.d';
import { urlsFetch } from '@fastgpt/global/common/file/tools';
const fetchContent = async (req: NextApiRequest, res: NextApiResponse) => {
try {
await connectToDatabase();
let { urlList = [], selector } = req.body as UrlFetchParams;
if (!urlList || urlList.length === 0) {
throw new Error('urlList is empty');
}
await authCert({ req, authToken: true });
jsonRes<UrlFetchResponse>(res, {
data: await urlsFetch({
urlList,
selector
})
});
} catch (error: any) {
jsonRes(res, {
code: 500,
error: error
});
}
};
export default fetchContent;

View File

@@ -14,10 +14,10 @@ import { getChatHistory } from './getHistory';
import { saveChat } from '@/service/utils/chat/saveChat';
import { responseWrite } from '@fastgpt/service/common/response';
import { pushChatBill } from '@/service/support/wallet/bill/push';
import { BillSourceEnum } from '@fastgpt/global/support/wallet/bill/constants';
import { authOutLinkChat } from '@/service/support/permission/auth/outLink';
import { pushResult2Remote, updateOutLinkUsage } from '@fastgpt/service/support/outLink/tools';
import requestIp from 'request-ip';
import { getBillSourceByAuthType } from '@fastgpt/global/support/wallet/bill/tools';
import { selectShareResponse } from '@/utils/service/core/chat';
import { updateApiKeyUsage } from '@fastgpt/service/support/openapi/tools';
@@ -276,11 +276,7 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
appId: app._id,
teamId: user.team.teamId,
tmbId: user.team.tmbId,
source: (() => {
if (authType === 'apikey') return BillSourceEnum.api;
if (shareId) return BillSourceEnum.shareLink;
return BillSourceEnum.fastgpt;
})(),
source: getBillSourceByAuthType({ shareId, authType }),
response: responseData
});

View File

@@ -6,6 +6,8 @@ import { pushGenerateVectorBill } from '@/service/support/wallet/bill/push';
import { connectToDatabase } from '@/service/mongo';
import { authTeamBalance } from '@/service/support/permission/auth/bill';
import { getVectorsByText, GetVectorProps } from '@/service/core/ai/vector';
import { updateApiKeyUsage } from '@fastgpt/service/support/openapi/tools';
import { getBillSourceByAuthType } from '@fastgpt/global/support/wallet/bill/tools';
type Props = GetVectorProps & {
billId?: string;
@@ -15,24 +17,21 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
try {
let { input, model, billId } = req.body as Props;
await connectToDatabase();
const { teamId, tmbId } = await authCert({ req, authToken: true, authApiKey: true });
if (!Array.isArray(input) || typeof input !== 'string') {
if (!Array.isArray(input) && typeof input !== 'string') {
throw new Error('input is nor array or string');
}
const { teamId, tmbId, apikey, authType } = await authCert({
req,
authToken: true,
authApiKey: true
});
await authTeamBalance(teamId);
const { tokenLen, vectors } = await getVectorsByText({ input, model });
pushGenerateVectorBill({
teamId,
tmbId,
tokenLen: tokenLen,
model,
billId
});
jsonRes(res, {
data: {
object: 'list',
@@ -48,6 +47,22 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
}
}
});
const { total } = pushGenerateVectorBill({
teamId,
tmbId,
tokenLen,
model,
billId,
source: getBillSourceByAuthType({ authType })
});
if (apikey) {
updateApiKeyUsage({
apikey,
usage: total
});
}
} catch (err) {
console.log(err);
jsonRes(res, {

View File

@@ -7,12 +7,13 @@ 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 } = await authCert({
const { teamId, tmbId, apikey } = await authCert({
req,
authApiKey: true
});
@@ -23,12 +24,19 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
const result = await reRankRecall({ query, inputs });
pushReRankBill({
const { total } = pushReRankBill({
teamId,
tmbId,
source: 'api'
});
if (apikey) {
updateApiKeyUsage({
apikey,
usage: total
});
}
jsonRes<PostReRankResponse>(res, {
data: result
});

View File

@@ -103,8 +103,8 @@ const InfoModal = ({
try {
const src = await compressImgFileAndUpload({
file,
maxW: 100,
maxH: 100
maxW: 300,
maxH: 300
});
setValue('avatar', src);
setRefresh((state) => !state);

View File

@@ -60,8 +60,8 @@ const CreateModal = ({ onClose, onSuccess }: { onClose: () => void; onSuccess: (
try {
const src = await compressImgFileAndUpload({
file,
maxW: 100,
maxH: 100
maxW: 300,
maxH: 300
});
setValue('avatar', src);
setRefresh((state) => !state);

View File

@@ -11,14 +11,18 @@ import {
Tbody,
Image,
MenuButton,
useDisclosure
useDisclosure,
Button,
Link,
useTheme
} from '@chakra-ui/react';
import {
getDatasetCollections,
delDatasetCollectionById,
putDatasetCollectionById,
postDatasetCollection,
getDatasetCollectionPathById
getDatasetCollectionPathById,
postWebsiteSync
} from '@/web/core/dataset/api';
import { useQuery } from '@tanstack/react-query';
import { debounce } from 'lodash';
@@ -39,7 +43,10 @@ import EmptyTip from '@/components/EmptyTip';
import {
FolderAvatarSrc,
DatasetCollectionTypeEnum,
TrainingModeEnum
DatasetCollectionTrainingModeEnum,
DatasetTypeEnum,
DatasetTypeMap,
DatasetStatusEnum
} from '@fastgpt/global/core/dataset/constant';
import { getCollectionIcon } from '@fastgpt/global/core/dataset/utils';
import EditFolderModal, { useEditFolder } from '../../component/EditFolderModal';
@@ -52,13 +59,18 @@ import { useToast } from '@/web/common/hooks/useToast';
import MyTooltip from '@/components/MyTooltip';
import { useUserStore } from '@/web/support/user/useUserStore';
import { TeamMemberRoleEnum } from '@fastgpt/global/support/user/team/constant';
import { useDatasetStore } from '@/web/core/dataset/store/dataset';
import { DatasetSchemaType } from '@fastgpt/global/core/dataset/type';
import { postCreateTrainingBill } from '@/web/support/wallet/bill/api';
const FileImportModal = dynamic(() => import('./Import/ImportModal'), {});
const WebSiteConfigModal = dynamic(() => import('./Import/WebsiteConfig'), {});
const CollectionCard = () => {
const BoxRef = useRef<HTMLDivElement>(null);
const lastSearch = useRef('');
const router = useRouter();
const theme = useTheme();
const { toast } = useToast();
const { parentId = '', datasetId } = router.query as { parentId: string; datasetId: string };
const { t } = useTranslation();
@@ -66,7 +78,7 @@ const CollectionCard = () => {
const { isPc } = useSystemStore();
const { userInfo } = useUserStore();
const [searchText, setSearchText] = useState('');
const { setLoading } = useSystemStore();
const { datasetDetail, updateDataset, loadDatasetDetail } = useDatasetStore();
const { openConfirm, ConfirmModal } = useConfirm({
content: t('dataset.Confirm to delete the file')
@@ -76,11 +88,18 @@ const CollectionCard = () => {
onOpen: onOpenFileImportModal,
onClose: onCloseFileImportModal
} = useDisclosure();
const {
isOpen: isOpenWebsiteModal,
onOpen: onOpenWebsiteModal,
onClose: onCloseWebsiteModal
} = useDisclosure();
const { onOpenModal: onOpenCreateVirtualFileModal, EditModal: EditCreateVirtualFileModal } =
useEditTitle({
title: t('dataset.Create Virtual File'),
tip: t('dataset.Virtual File Tip')
tip: t('dataset.Virtual File Tip'),
canEmpty: false
});
const { onOpenModal: onOpenEditTitleModal, EditModal: EditTitleModal } = useEditTitle({
title: t('Rename')
});
@@ -128,48 +147,60 @@ const CollectionCard = () => {
() =>
collections.map((collection) => {
const icon = getCollectionIcon(collection.type, collection.name);
const status = (() => {
if (collection.trainingAmount > 0) {
return {
statusText: t('dataset.collections.Collection Embedding', {
total: collection.trainingAmount
}),
color: 'myGray.500'
};
}
return {
statusText: t('core.dataset.collection.status.active'),
color: 'green.500'
};
})();
return {
...collection,
icon,
...(collection.trainingAmount > 0
? {
statusText: t('dataset.collections.Collection Embedding', {
total: collection.trainingAmount
}),
color: 'myGray.500'
}
: {
statusText: t('dataset.collections.Ready'),
color: 'green.500'
})
...status
};
}),
[collections, t]
);
const hasTrainingData = useMemo(
() => !!formatCollections.find((item) => item.trainingAmount > 0),
[formatCollections]
);
const { mutate: onCreateVirtualFile } = useRequest({
mutationFn: ({ name }: { name: string }) => {
setLoading(true);
return postDatasetCollection({
const { mutate: onCreateCollection, isLoading: isCreating } = useRequest({
mutationFn: async ({
name,
type,
callback,
...props
}: {
name: string;
type: `${DatasetCollectionTypeEnum}`;
callback?: (id: string) => void;
trainingType?: `${DatasetCollectionTrainingModeEnum}`;
rawLink?: string;
chunkSize?: number;
}) => {
const id = await postDatasetCollection({
parentId,
datasetId,
name,
type: DatasetCollectionTypeEnum.virtual
type,
...props
});
callback?.(id);
return id;
},
onSuccess() {
getData(pageNum);
},
onSettled() {
setLoading(false);
},
successToast: t('dataset.collections.Create Virtual File Success'),
errorToast: t('common.Create Virtual File Failed')
successToast: t('common.Create Success'),
errorToast: t('common.Create Failed')
});
const { mutate: onUpdateCollectionName } = useRequest({
mutationFn: ({ collectionId, name }: { collectionId: string; name: string }) => {
@@ -185,9 +216,8 @@ const CollectionCard = () => {
successToast: t('common.Rename Success'),
errorToast: t('common.Rename Failed')
});
const { mutate: onDelCollection } = useRequest({
const { mutate: onDelCollection, isLoading: isDeleting } = useRequest({
mutationFn: (collectionId: string) => {
setLoading(true);
return delDatasetCollectionById({
collectionId
});
@@ -195,26 +225,54 @@ const CollectionCard = () => {
onSuccess() {
getData(pageNum);
},
onSettled() {
setLoading(false);
},
successToast: t('common.Delete Success'),
errorToast: t('common.Delete Failed')
});
const { mutate: onUpdateDatasetWebsiteConfig, isLoading: isUpdating } = useRequest({
mutationFn: async (websiteConfig: DatasetSchemaType['websiteConfig']) => {
onCloseWebsiteModal();
const [_, billId] = await Promise.all([
updateDataset({
id: datasetDetail._id,
websiteConfig,
status: DatasetStatusEnum.syncing
}),
postCreateTrainingBill({
name: 'core.dataset.training.Website Sync',
vectorModel: datasetDetail.vectorModel.model,
agentModel: datasetDetail.agentModel.model
})
]);
return billId;
},
onSuccess(billId: string) {
try {
postWebsiteSync({ datasetId: datasetDetail._id, billId });
} catch (error) {}
},
errorToast: t('common.Update Failed')
});
const { data: paths = [] } = useQuery(['getDatasetCollectionPathById', parentId], () =>
getDatasetCollectionPathById(parentId)
);
const hasTrainingData = useMemo(
() => !!formatCollections.find((item) => item.trainingAmount > 0),
[formatCollections]
);
useQuery(
['refreshCollection'],
() => {
getData(1);
if (datasetDetail.status === DatasetStatusEnum.syncing) {
loadDatasetDetail(datasetId, true);
}
return null;
},
{
refetchInterval: 6000,
enabled: hasTrainingData
enabled: hasTrainingData || datasetDetail.status === DatasetStatusEnum.syncing
}
);
@@ -224,17 +282,33 @@ const CollectionCard = () => {
return (
<Flex flexDirection={'column'} ref={BoxRef} py={[1, 3]} h={'100%'}>
<Flex px={[2, 5]} alignItems={['flex-start', 'center']}>
<Flex px={[2, 5]} alignItems={['flex-start', 'center']} h={'35px'}>
<Box flex={1}>
<ParentPath
paths={paths.map((path, i) => ({
parentId: path.parentId,
parentName: i === paths.length - 1 ? `${path.parentName}(${total})` : path.parentName
parentName: i === paths.length - 1 ? `${path.parentName}` : path.parentName
}))}
FirstPathDom={
<Box fontWeight={'bold'} fontSize={['sm', 'lg']}>
{t('common.File')}({total})
</Box>
<>
<Box fontWeight={'bold'} fontSize={['sm', 'lg']}>
{t(DatasetTypeMap[datasetDetail?.type]?.collectionLabel)}({total})
</Box>
{datasetDetail?.websiteConfig?.url && (
<Flex fontSize={'sm'}>
{t('core.dataset.website.Base Url')}:
<Link
href={datasetDetail.websiteConfig.url}
target="_blank"
mr={2}
textDecoration={'underline'}
color={'myBlue.700'}
>
{datasetDetail.websiteConfig.url}
</Link>
</Flex>
)}
</>
}
onClick={(e) => {
router.replace({
@@ -279,68 +353,109 @@ const CollectionCard = () => {
/>
</Flex>
)}
{userInfo?.team?.role !== TeamMemberRoleEnum.visitor && (
<MyMenu
offset={[-40, 10]}
width={120}
Button={
<MenuButton
_hover={{
color: 'myBlue.600'
}}
fontSize={['sm', 'md']}
>
<Flex
alignItems={'center'}
px={5}
py={2}
borderRadius={'md'}
cursor={'pointer'}
bg={'myBlue.600'}
overflow={'hidden'}
color={'white'}
h={['28px', '35px']}
>
<MyIcon name={'importLight'} mr={2} w={'14px'} />
<Box>{t('dataset.collections.Create And Import')}</Box>
</Flex>
</MenuButton>
}
menuList={[
{
child: (
<Flex>
<Image src={FolderAvatarSrc} alt={''} w={'20px'} mr={2} />
{t('Folder')}
</Flex>
),
onClick: () => setEditFolderData({})
},
{
child: (
<Flex>
<Image src={'/imgs/files/collection.svg'} alt={''} w={'20px'} mr={2} />
{t('dataset.Create Virtual File')}
</Flex>
),
onClick: () => {
onOpenCreateVirtualFileModal({
defaultVal: '',
onSuccess: (name) => onCreateVirtualFile({ name })
});
{datasetDetail?.type === DatasetTypeEnum.dataset && (
<>
{userInfo?.team?.role !== TeamMemberRoleEnum.visitor && (
<MyMenu
offset={[-40, 10]}
width={120}
Button={
<MenuButton
_hover={{
color: 'myBlue.600'
}}
fontSize={['sm', 'md']}
>
<Flex
alignItems={'center'}
px={5}
py={2}
borderRadius={'md'}
cursor={'pointer'}
bg={'myBlue.600'}
overflow={'hidden'}
color={'white'}
h={['28px', '35px']}
>
<MyIcon name={'importLight'} mr={2} w={'14px'} />
<Box>{t('dataset.collections.Create And Import')}</Box>
</Flex>
</MenuButton>
}
},
{
child: (
<Flex>
<Image src={'/imgs/files/file.svg'} alt={''} w={'20px'} mr={2} />
{t('dataset.File Input')}
menuList={[
{
child: (
<Flex>
<Image src={FolderAvatarSrc} alt={''} w={'20px'} mr={2} />
{t('Folder')}
</Flex>
),
onClick: () => setEditFolderData({})
},
{
child: (
<Flex>
<Image src={'/imgs/files/collection.svg'} alt={''} w={'20px'} mr={2} />
{t('dataset.Create Virtual File')}
</Flex>
),
onClick: () => {
onOpenCreateVirtualFileModal({
defaultVal: '',
onSuccess: (name) => {
onCreateCollection({ name, type: DatasetCollectionTypeEnum.virtual });
}
});
}
},
{
child: (
<Flex>
<Image src={'/imgs/files/file.svg'} alt={''} w={'20px'} mr={2} />
{t('dataset.File Input')}
</Flex>
),
onClick: onOpenFileImportModal
}
]}
/>
)}
</>
)}
{datasetDetail?.type === DatasetTypeEnum.websiteDataset && (
<>
{datasetDetail?.websiteConfig?.url ? (
<Flex alignItems={'center'}>
{datasetDetail.status === DatasetStatusEnum.active && (
<Button onClick={onOpenWebsiteModal}>{t('common.Config')}</Button>
)}
{datasetDetail.status === DatasetStatusEnum.syncing && (
<Flex
ml={3}
alignItems={'center'}
px={3}
py={1}
borderRadius="md"
border={theme.borders.base}
>
<Box
animation={'zoomStopIcon 0.5s infinite alternate'}
bg={'myGray.700'}
w="8px"
h="8px"
borderRadius={'50%'}
mt={'1px'}
></Box>
<Box ml={2} color={'myGray.600'}>
{t('core.dataset.status.syncing')}
</Box>
</Flex>
),
onClick: onOpenFileImportModal
}
]}
/>
)}
</Flex>
) : (
<Button onClick={onOpenWebsiteModal}>{t('core.dataset.Set Website Config')}</Button>
)}
</>
)}
</Flex>
@@ -428,11 +543,7 @@ const CollectionCard = () => {
</MyTooltip>
</Flex>
</Td>
<Td fontSize={'md'}>
{collection.type === DatasetCollectionTypeEnum.folder
? '-'
: collection.dataAmount}
</Td>
<Td fontSize={'md'}>{collection.dataAmount || '-'}</Td>
<Td>{dayjs(collection.updateTime).format('YYYY/MM/DD HH:mm')}</Td>
<Td>
<Flex
@@ -443,10 +554,10 @@ const CollectionCard = () => {
h: '10px',
mr: 2,
borderRadius: 'lg',
bg: collection?.color
bg: collection.color
}}
>
{collection?.statusText}
{t(collection.statusText)}
</Flex>
</Td>
<Td onClick={(e) => e.stopPropagation()}>
@@ -536,14 +647,31 @@ const CollectionCard = () => {
))}
</Tbody>
</Table>
<Loading loading={isLoading && collections.length === 0} fixed={false} />
{total > pageSize && (
<Flex mt={2} justifyContent={'center'}>
<Pagination />
</Flex>
)}
{total === 0 && <EmptyTip text="数据集空空如也" />}
{total === 0 && (
<EmptyTip
text={
datasetDetail.type === DatasetTypeEnum.dataset ? (
t('core.dataset.collection.Empty Tip')
) : (
<Flex>
{t('core.dataset.collection.Website Empty Tip')}
<Box textDecoration={'underline'} cursor={'pointer'} onClick={onOpenWebsiteModal}>
{t('core.dataset.collection.Click top config website')}
</Box>
</Flex>
)
}
/>
)}
</TableContainer>
<Loading
loading={isCreating || isDeleting || isUpdating || (isLoading && collections.length === 0)}
/>
<ConfirmModal />
<EditTitleModal />
@@ -559,7 +687,6 @@ const CollectionCard = () => {
onClose={onCloseFileImportModal}
/>
)}
{!!editFolderData && (
<EditFolderModal
onClose={() => setEditFolderData(undefined)}
@@ -570,15 +697,13 @@ const CollectionCard = () => {
id: editFolderData.id,
name
});
getData(pageNum);
} else {
await postDatasetCollection({
parentId,
datasetId,
onCreateCollection({
name,
type: DatasetCollectionTypeEnum.folder
});
}
getData(pageNum);
} catch (error) {
return Promise.reject(error);
}
@@ -587,7 +712,6 @@ const CollectionCard = () => {
name={editFolderData.name}
/>
)}
{!!moveCollectionData && (
<SelectCollections
datasetId={datasetId}
@@ -608,6 +732,16 @@ const CollectionCard = () => {
}}
/>
)}
{isOpenWebsiteModal && (
<WebSiteConfigModal
onClose={onCloseWebsiteModal}
onSuccess={onUpdateDatasetWebsiteConfig}
defaultValue={{
url: datasetDetail?.websiteConfig?.url,
selector: datasetDetail?.websiteConfig?.selector
}}
/>
)}
</Flex>
);
};

View File

@@ -55,7 +55,10 @@ const DataCard = () => {
const router = useRouter();
const { userInfo } = useUserStore();
const { isPc } = useSystemStore();
const { collectionId = '' } = router.query as { collectionId: string };
const { collectionId = '', datasetId } = router.query as {
collectionId: string;
datasetId: string;
};
const { Loading, setIsLoading } = useLoading({ defaultLoading: true });
const { t } = useTranslation();
const [searchText, setSearchText] = useState('');
@@ -99,8 +102,18 @@ const DataCard = () => {
);
// get file info
const { data: collection } = useQuery(['getDatasetCollectionById', collectionId], () =>
getDatasetCollectionById(collectionId)
const { data: collection } = useQuery(
['getDatasetCollectionById', collectionId],
() => getDatasetCollectionById(collectionId),
{
onError: () => {
router.replace({
query: {
datasetId
}
});
}
}
);
const canWrite = useMemo(
@@ -290,6 +303,7 @@ const DataCard = () => {
</Flex>
<Box
maxH={'135px'}
minH={'90px'}
overflow={'hidden'}
wordBreak={'break-all'}
pt={1}

View File

@@ -18,13 +18,13 @@ import { useTranslation } from 'next-i18next';
import { customAlphabet } from 'nanoid';
import dynamic from 'next/dynamic';
import MyTooltip from '@/components/MyTooltip';
import type { FetchResultItem } from '@fastgpt/global/common/plugin/types/pluginRes.d';
import { getErrText } from '@fastgpt/global/common/error/utils';
import { useDatasetStore } from '@/web/core/dataset/store/dataset';
import { getFileIcon } from '@fastgpt/global/common/file/icon';
import { countPromptTokens } from '@fastgpt/global/common/string/tiktoken';
import { DatasetCollectionTypeEnum } from '@fastgpt/global/core/dataset/constant';
import type { PushDatasetDataChunkProps } from '@fastgpt/global/core/dataset/api.d';
import { UrlFetchResponse } from '@fastgpt/global/common/file/api.d';
const UrlFetchModal = dynamic(() => import('./UrlFetchModal'));
const CreateFileModal = dynamic(() => import('./CreateFileModal'));
@@ -215,7 +215,7 @@ const FileSelect = ({
);
// link fetch
const onUrlFetch = useCallback(
(e: FetchResultItem[]) => {
(e: UrlFetchResponse) => {
const result: FileItemType[] = e.map<FileItemType>(({ url, content }) => {
const splitRes = splitText2Chunks({
text: content,

View File

@@ -188,6 +188,8 @@ const Provider = ({
const onReSplitChunks = useCallback(async () => {
try {
setPreviewFile(undefined);
setFiles((state) =>
state.map((file) => {
const splitRes = splitText2Chunks({
@@ -490,6 +492,7 @@ export const SelectorContainer = ({
display={['block', 'none']}
onClick={(e) => {
e.stopPropagation();
setPreviewFile(undefined);
setFiles((state) => state.filter((file) => file.id !== item.id));
}}
/>

View File

@@ -1,31 +1,39 @@
import React, { useRef } from 'react';
import React from 'react';
import { useTranslation } from 'next-i18next';
import MyModal from '@/components/MyModal';
import { Box, Button, ModalBody, ModalFooter, Textarea } from '@chakra-ui/react';
import type { FetchResultItem } from '@fastgpt/global/common/plugin/types/pluginRes.d';
import { Box, Button, Input, ModalBody, ModalFooter, Textarea } from '@chakra-ui/react';
import { useRequest } from '@/web/common/hooks/useRequest';
import { postFetchUrls } from '@/web/common/plugin/api';
import { postFetchUrls } from '@/web/common/tools/api';
import { useForm } from 'react-hook-form';
import { UrlFetchResponse } from '@fastgpt/global/common/file/api.d';
const UrlFetchModal = ({
onClose,
onSuccess
}: {
onClose: () => void;
onSuccess: (e: FetchResultItem[]) => void;
onSuccess: (e: UrlFetchResponse) => void;
}) => {
const { t } = useTranslation();
const Dom = useRef<HTMLTextAreaElement>(null);
const { register, handleSubmit } = useForm({
defaultValues: {
urls: '',
selector: ''
}
});
const { mutate, isLoading } = useRequest({
mutationFn: async () => {
const val = Dom.current?.value || '';
const urls = val.split('\n').filter((e) => e);
const res = await postFetchUrls(urls);
mutationFn: async ({ urls, selector }: { urls: string; selector: string }) => {
const urlList = urls.split('\n').filter((e) => e);
const res = await postFetchUrls({
urlList,
selector
});
onSuccess(res);
onClose();
},
errorToast: '获取链接失败'
errorToast: t('core.dataset.import.Fetch Error')
});
return (
@@ -34,8 +42,8 @@ const UrlFetchModal = ({
title={
<Box>
<Box>{t('file.Fetch Url')}</Box>
<Box fontWeight={'normal'} fontSize={'sm'} color={'myGray.500'} mt={1}>
<Box fontWeight={'normal'} fontSize={'sm'} color={'myGray.500'}>
{t('core.dataset.import.Fetch url tip')}
</Box>
</Box>
}
@@ -45,20 +53,31 @@ const UrlFetchModal = ({
w={'600px'}
>
<ModalBody>
<Textarea
ref={Dom}
rows={12}
whiteSpace={'nowrap'}
resize={'both'}
placeholder={'最多10个链接每行一个。'}
/>
<Box>
<Box fontWeight={'bold'}>{t('core.dataset.import.Fetch Url')}</Box>
<Textarea
{...register('urls', {
required: true
})}
rows={11}
whiteSpace={'nowrap'}
resize={'both'}
placeholder={t('core.dataset.import.Fetch url placeholder')}
/>
</Box>
<Box mt={4}>
<Box fontWeight={'bold'}>
{t('core.dataset.website.Selector')}({t('common.choosable')})
</Box>{' '}
<Input {...register('selector')} placeholder="body .content #document" />
</Box>
</ModalBody>
<ModalFooter>
<Button variant={'base'} mr={4} onClick={onClose}>
{t('common.Close')}
</Button>
<Button isLoading={isLoading} onClick={mutate}>
<Button isLoading={isLoading} onClick={handleSubmit((data) => mutate(data))}>
{t('common.Confirm')}
</Button>
</ModalFooter>
</MyModal>

View File

@@ -0,0 +1,101 @@
import React from 'react';
import MyModal from '@/components/MyModal';
import { useTranslation } from 'next-i18next';
import { Box, Button, Input, ModalBody, ModalFooter } from '@chakra-ui/react';
import { strIsLink } from '@fastgpt/global/common/string/tools';
import { useToast } from '@/web/common/hooks/useToast';
import { useForm } from 'react-hook-form';
import { useConfirm } from '@/web/common/hooks/useConfirm';
type FormType = {
url?: string | undefined;
selector?: string | undefined;
};
const WebsiteConfigModal = ({
onClose,
onSuccess,
defaultValue = {
url: '',
selector: ''
}
}: {
onClose: () => void;
onSuccess: (data: FormType) => void;
defaultValue?: FormType;
}) => {
const { t } = useTranslation();
const { toast } = useToast();
const { register, handleSubmit } = useForm({
defaultValues: defaultValue
});
const isEdit = !!defaultValue.url;
const confirmTip = isEdit
? t('core.dataset.website.Confirm Update Tips')
: t('core.dataset.website.Confirm Create Tips');
const { ConfirmModal, openConfirm } = useConfirm({
type: 'common'
});
return (
<MyModal
isOpen
iconSrc="core/dataset/websiteDataset"
title={t('core.dataset.website.Config')}
onClose={onClose}
maxW={'500px'}
>
<ModalBody>
<Box fontSize={'sm'} color={'myGray.600'}>
{t('core.dataset.website.Config Description')}
</Box>
<Box mt={2}>
<Box>{t('core.dataset.website.Base Url')}</Box>
<Input
placeholder={t('core.dataset.collection.Website Link')}
{...register('url', {
required: true
})}
/>
</Box>
<Box mt={3}>
<Box>
{t('core.dataset.website.Selector')}({t('common.choosable')})
</Box>
<Input {...register('selector')} placeholder="body .content #document" />
</Box>
</ModalBody>
<ModalFooter>
<Button variant={'base'} onClick={onClose}>
{t('common.Close')}
</Button>
<Button
ml={2}
onClick={handleSubmit((data) => {
if (!data.url) return;
// check is link
if (!strIsLink(data.url)) {
return toast({
status: 'warning',
title: t('common.link.UnValid')
});
}
openConfirm(
() => {
onSuccess(data);
},
undefined,
confirmTip
)();
})}
>
{t('core.dataset.website.Start Sync')}
</Button>
</ModalFooter>
<ConfirmModal />
</MyModal>
);
};
export default WebsiteConfigModal;

View File

@@ -7,7 +7,7 @@ import React, {
ForwardedRef
} from 'react';
import { useRouter } from 'next/router';
import { Box, Flex, Button, FormControl, IconButton, Input } from '@chakra-ui/react';
import { Box, Flex, Button, FormControl, IconButton, Input, Textarea } from '@chakra-ui/react';
import { QuestionOutlineIcon, DeleteIcon } from '@chakra-ui/icons';
import { delDatasetById } from '@/web/core/dataset/api';
import { useSelectFile } from '@/web/common/file/hooks/useSelectFile';
@@ -18,21 +18,19 @@ import { UseFormReturn } from 'react-hook-form';
import { compressImgFileAndUpload } from '@/web/common/file/controller';
import type { DatasetItemType } from '@fastgpt/global/core/dataset/type.d';
import Avatar from '@/components/Avatar';
import Tag from '@/components/Tag';
import MyTooltip from '@/components/MyTooltip';
import { useTranslation } from 'next-i18next';
import PermissionRadio from '@/components/support/permission/Radio';
import MySelect from '@/components/Select';
import { qaModelList } from '@/web/common/system/staticData';
export interface ComponentRef {
initInput: (tags: string) => void;
}
const Info = (
{ datasetId, form }: { datasetId: string; form: UseFormReturn<DatasetItemType, any> },
ref: ForwardedRef<ComponentRef>
) => {
const Info = ({
datasetId,
form
}: {
datasetId: string;
form: UseFormReturn<DatasetItemType, any>;
}) => {
const { t } = useTranslation();
const { getValues, formState, setValue, register, handleSubmit } = form;
const InputRef = useRef<HTMLInputElement>(null);
@@ -52,7 +50,7 @@ const Info = (
multiple: false
});
const { datasetDetail, loadDatasetDetail, loadDatasets, updateDataset } = useDatasetStore();
const { datasetDetail, loadDatasets, updateDataset } = useDatasetStore();
/* 点击删除 */
const onclickDelKb = useCallback(async () => {
@@ -121,8 +119,8 @@ const Info = (
try {
const src = await compressImgFileAndUpload({
file,
maxW: 100,
maxH: 100
maxW: 300,
maxH: 300
});
setValue('avatar', src);
@@ -138,14 +136,6 @@ const Info = (
[setRefresh, setValue, toast]
);
useImperativeHandle(ref, () => ({
initInput: (tags: string) => {
if (InputRef.current) {
InputRef.current.value = tags;
}
}
}));
return (
<Box py={5} px={[5, 10]}>
<Flex mt={5} w={'100%'} alignItems={'center'}>
@@ -154,18 +144,7 @@ const Info = (
</Box>
<Box flex={1}>{datasetDetail._id}</Box>
</Flex>
<Flex mt={8} w={'100%'} alignItems={'center'}>
<Box flex={['0 0 90px', '0 0 160px']} w={0}>
</Box>
<Box flex={[1, '0 0 300px']}>{getValues('vectorModel').name}</Box>
</Flex>
<Flex mt={8} w={'100%'} alignItems={'center'}>
<Box flex={['0 0 90px', '0 0 160px']} w={0}>
MaxTokens
</Box>
<Box flex={[1, '0 0 300px']}>{getValues('vectorModel').maxToken}</Box>
</Flex>
<Flex mt={5} w={'100%'} alignItems={'center'}>
<Box flex={['0 0 90px', '0 0 160px']} w={0}>
@@ -183,7 +162,7 @@ const Info = (
</MyTooltip>
</Box>
</Flex>
<FormControl mt={8} w={'100%'} display={'flex'} alignItems={'center'}>
<Flex mt={8} w={'100%'} alignItems={'center'}>
<Box flex={['0 0 90px', '0 0 160px']} w={0}>
</Box>
@@ -194,7 +173,19 @@ const Info = (
required: '知识库名称不能为空'
})}
/>
</FormControl>
</Flex>
<Flex mt={8} w={'100%'} alignItems={'center'}>
<Box flex={['0 0 90px', '0 0 160px']} w={0}>
</Box>
<Box flex={[1, '0 0 300px']}>{getValues('vectorModel').name}</Box>
</Flex>
<Flex mt={8} w={'100%'} alignItems={'center'}>
<Box flex={['0 0 90px', '0 0 160px']} w={0}>
{t('core.Max Token')}
</Box>
<Box flex={[1, '0 0 300px']}>{getValues('vectorModel').maxToken}</Box>
</Flex>
<Flex mt={6} alignItems={'center'}>
<Box flex={['0 0 90px', '0 0 160px']} w={0}>
{t('dataset.Agent Model')}
@@ -216,33 +207,9 @@ const Info = (
/>
</Box>
</Flex>
<Flex mt={8} alignItems={'center'} w={'100%'} flexWrap={'wrap'}>
<Box flex={['0 0 90px', '0 0 160px']} w={0}>
<MyTooltip label={'用空格隔开多个标签,便于搜索'} forceShow>
<QuestionOutlineIcon ml={1} />
</MyTooltip>
</Box>
<Input
flex={[1, '0 0 300px']}
ref={InputRef}
defaultValue={getValues('tags')}
placeholder={'标签,使用空格分割。'}
maxLength={30}
onChange={(e) => {
setValue('tags', e.target.value.split(' ').filter(Boolean));
setRefresh(!refresh);
}}
/>
<Flex w={'100%'} pl={['90px', '160px']} mt={2}>
{getValues('tags')
.filter(Boolean)
.map((item, i) => (
<Tag mr={2} mb={2} key={i} whiteSpace={'nowrap'}>
{item}
</Tag>
))}
</Flex>
<Flex mt={8} alignItems={'center'} w={'100%'}>
<Box flex={['0 0 90px', '0 0 160px']}>{t('common.Intro')}</Box>
<Textarea flex={[1, '0 0 300px']} {...register('intro')} placeholder={t('common.Intro')} />
</Flex>
{datasetDetail.isOwner && (
<Flex mt={5} alignItems={'center'} w={'100%'} flexWrap={'wrap'}>
@@ -292,4 +259,4 @@ const Info = (
);
};
export default forwardRef(Info);
export default React.memo(Info);

View File

@@ -199,12 +199,12 @@ const InputDataModal = ({
<Box p={5} borderRight={theme.borders.base}>
<RawSourceText
w={'200px'}
className=""
className="textEllipsis3"
whiteSpace={'pre-wrap'}
sourceName={collection.sourceName}
sourceId={collection.sourceId}
mb={6}
fontSize={['14px', '16px']}
fontSize={'sm'}
/>
<SideTabs
list={tabList}
@@ -220,14 +220,14 @@ const InputDataModal = ({
}}
/>
</Box>
<Flex flexDirection={'column'} px={5} py={3} flex={1} h={'100%'}>
<Box fontSize={'lg'} fontWeight={'bold'} mb={4}>
<Flex flexDirection={'column'} py={3} flex={1} h={'100%'}>
<Box fontSize={'lg'} px={5} fontWeight={'bold'} mb={4}>
{currentTab === TabEnum.content && (
<>{defaultValue.id ? t('dataset.data.Update Data') : t('dataset.data.Input Data')}</>
)}
{currentTab === TabEnum.index && <> {t('dataset.data.Index Edit')}</>}
</Box>
<Box flex={1} overflow={'auto'}>
<Box flex={1} px={5} overflow={'auto'}>
{currentTab === TabEnum.content && (
<>
<Box>
@@ -358,7 +358,7 @@ const InputDataModal = ({
</Grid>
)}
</Box>
<Flex justifyContent={'flex-end'} mt={4}>
<Flex justifyContent={'flex-end'} px={5} mt={4}>
<Button variant={'base'} mr={3} isLoading={loading} onClick={onClose}>
{t('common.Close')}
</Button>

View File

@@ -187,7 +187,7 @@ const Test = ({ datasetId }: { datasetId: string }) => {
</Box>
</Box>
{/* result show */}
<Box p={4} h={['auto', '100%']} overflow={'overlay'} flex={1}>
<Box p={4} h={['auto', '100%']} overflow={'overlay'} flex={'1 0 0'}>
{!datasetTestItem?.results || datasetTestItem.results.length === 0 ? (
<Flex
mt={[10, 0]}
@@ -275,7 +275,7 @@ const Test = ({ datasetId }: { datasetId: string }) => {
<MyIcon name={'kbTest'} w={'14px'} />
<Progress
mx={2}
flex={1}
flex={'1 0 0'}
value={item.score * 100}
size="sm"
borderRadius={'20px'}
@@ -283,7 +283,7 @@ const Test = ({ datasetId }: { datasetId: string }) => {
/>
<Box>{item.score.toFixed(4)}</Box>
</Flex>
<Box px={2} fontSize={'xs'} color={'myGray.600'}>
<Box px={2} fontSize={'xs'} color={'myGray.600'} wordBreak={'break-word'}>
<Box>{item.q}</Box>
<Box>{item.a}</Box>
</Box>

View File

@@ -7,7 +7,6 @@ import { useQuery } from '@tanstack/react-query';
import type { DatasetItemType } from '@fastgpt/global/core/dataset/type.d';
import { getErrText } from '@fastgpt/global/common/error/utils';
import { useSystemStore } from '@/web/common/system/useSystemStore';
import { type ComponentRef } from './components/Info';
import Tabs from '@/components/Tabs';
import dynamic from 'next/dynamic';
import MyIcon from '@/components/Icon';
@@ -25,6 +24,7 @@ import Script from 'next/script';
import CollectionCard from './components/CollectionCard';
import { useDatasetStore } from '@/web/core/dataset/store/dataset';
import { useUserStore } from '@/web/support/user/useUserStore';
import { DatasetTypeMap } from '../../../../../../packages/global/core/dataset/constant';
const DataCard = dynamic(() => import('./components/DataCard'), {
ssr: false
@@ -41,7 +41,6 @@ export enum TabEnum {
}
const Detail = ({ datasetId, currentTab }: { datasetId: string; currentTab: `${TabEnum}` }) => {
const InfoRef = useRef<ComponentRef>(null);
const theme = useTheme();
const { t } = useTranslation();
const { toast } = useToast();
@@ -77,7 +76,6 @@ const Detail = ({ datasetId, currentTab }: { datasetId: string; currentTab: `${T
useQuery([datasetId], () => loadDatasetDetail(datasetId), {
onSuccess(res) {
form.reset(res);
InfoRef.current?.initInput(res.tags?.join(' '));
},
onError(err: any) {
router.replace(`/dataset/list`);
@@ -107,14 +105,24 @@ const Detail = ({ datasetId, currentTab }: { datasetId: string; currentTab: `${T
>
<Flex mb={4} alignItems={'center'}>
<Avatar src={datasetDetail.avatar} w={'34px'} borderRadius={'lg'} />
<Box ml={2} fontWeight={'bold'}>
{datasetDetail.name}
<Box ml={2}>
<Box fontWeight={'bold'}>{datasetDetail.name}</Box>
</Box>
</Flex>
{DatasetTypeMap[datasetDetail.type] && (
<Flex alignItems={'center'} pl={2}>
<MyIcon
name={DatasetTypeMap[datasetDetail.type]?.icon as any}
mr={1}
w={'16px'}
/>
<Box>{t(DatasetTypeMap[datasetDetail.type]?.label)}</Box>
</Flex>
)}
<SideTabs
flex={1}
mx={'auto'}
mt={2}
mt={3}
w={'100%'}
list={tabList}
activeId={currentTab}
@@ -156,7 +164,7 @@ const Detail = ({ datasetId, currentTab }: { datasetId: string; currentTab: `${T
borderRadius={'50%'}
aria-label={''}
/>
{t('core.dataset.All Dataset')}
</Flex>
</Flex>
) : (
@@ -180,9 +188,7 @@ const Detail = ({ datasetId, currentTab }: { datasetId: string; currentTab: `${T
{currentTab === TabEnum.collectionCard && <CollectionCard />}
{currentTab === TabEnum.dataCard && <DataCard />}
{currentTab === TabEnum.test && <Test datasetId={datasetId} />}
{currentTab === TabEnum.info && (
<Info ref={InfoRef} datasetId={datasetId} form={form} />
)}
{currentTab === TabEnum.info && <Info datasetId={datasetId} form={form} />}
</Box>
)}
</Flex>

View File

@@ -1,14 +1,5 @@
import React, { useCallback, useState, useRef } from 'react';
import {
Box,
Flex,
Button,
ModalHeader,
ModalFooter,
ModalBody,
Input,
Image
} from '@chakra-ui/react';
import React, { useCallback, useState } from 'react';
import { Box, Flex, Button, ModalFooter, ModalBody, Input } from '@chakra-ui/react';
import { useSelectFile } from '@/web/common/file/hooks/useSelectFile';
import { useForm } from 'react-hook-form';
import { compressImgFileAndUpload } from '@/web/common/file/controller';
@@ -23,10 +14,11 @@ import MyModal from '@/components/MyModal';
import { postCreateDataset } from '@/web/core/dataset/api';
import type { CreateDatasetParams } from '@/global/core/dataset/api.d';
import MySelect from '@/components/Select';
import { QuestionOutlineIcon } from '@chakra-ui/icons';
import { vectorModelList, qaModelList } from '@/web/common/system/staticData';
import Tag from '@/components/Tag';
import { useTranslation } from 'next-i18next';
import MyRadio from '@/components/common/MyRadio';
import { DatasetTypeEnum } from '@fastgpt/global/core/dataset/constant';
import { feConfigs } from '@/web/common/system/staticData';
const CreateModal = ({ onClose, parentId }: { onClose: () => void; parentId?: string }) => {
const { t } = useTranslation();
@@ -36,16 +28,15 @@ const CreateModal = ({ onClose, parentId }: { onClose: () => void; parentId?: st
const { isPc } = useSystemStore();
const { register, setValue, getValues, handleSubmit } = useForm<CreateDatasetParams>({
defaultValues: {
parentId,
type: DatasetTypeEnum.dataset,
avatar: '/icon/logo.svg',
name: '',
tags: '',
intro: '',
vectorModel: vectorModelList[0].model,
agentModel: qaModelList[0].model,
type: 'dataset',
parentId
agentModel: qaModelList[0].model
}
});
const InputRef = useRef<HTMLInputElement>(null);
const { File, onOpen: onOpenSelectFile } = useSelectFile({
fileType: '.jpg,.png',
@@ -59,8 +50,8 @@ const CreateModal = ({ onClose, parentId }: { onClose: () => void; parentId?: st
try {
const src = await compressImgFileAndUpload({
file,
maxW: 100,
maxH: 100
maxW: 300,
maxH: 300
});
setValue('avatar', src);
setRefresh((state) => !state);
@@ -97,32 +88,67 @@ const CreateModal = ({ onClose, parentId }: { onClose: () => void; parentId?: st
w={'450px'}
>
<ModalBody>
<Box color={'myGray.800'} fontWeight={'bold'}>
</Box>
<Flex mt={3} alignItems={'center'}>
<MyTooltip label={'点击设置头像'}>
<Avatar
flexShrink={0}
src={getValues('avatar')}
w={['28px', '32px']}
h={['28px', '32px']}
cursor={'pointer'}
borderRadius={'md'}
onClick={onOpenSelectFile}
/>
</MyTooltip>
<Input
ml={3}
flex={1}
autoFocus
bg={'myWhite.600'}
maxLength={30}
{...register('name', {
required: '知识库名称不能为空~'
})}
<>
<Box mb={1} color={'myGray.800'} fontWeight={'bold'}>
{t('core.dataset.Dataset Type')}
</Box>
<MyRadio
gridGap={2}
gridTemplateColumns={'repeat(1,1fr)'}
list={[
{
title: t('core.dataset.Common Dataset'),
value: DatasetTypeEnum.dataset,
icon: 'core/dataset/commonDataset',
desc: t('core.dataset.Common Dataset Desc')
},
...(feConfigs.isPlus
? [
{
title: t('core.dataset.Website Dataset'),
value: DatasetTypeEnum.websiteDataset,
icon: 'core/dataset/websiteDataset',
desc: t('core.dataset.Website Dataset Desc')
}
]
: [])
]}
value={getValues('type')}
onChange={(e) => {
setValue('type', e as `${DatasetTypeEnum}`);
setRefresh(!refresh);
}}
/>
</Flex>
</>
<Box mt={5}>
<Box color={'myGray.800'} fontWeight={'bold'}>
</Box>
<Flex mt={1} alignItems={'center'}>
<MyTooltip label={'点击设置头像'}>
<Avatar
flexShrink={0}
src={getValues('avatar')}
w={['28px', '32px']}
h={['28px', '32px']}
cursor={'pointer'}
borderRadius={'md'}
onClick={onOpenSelectFile}
/>
</MyTooltip>
<Input
ml={3}
flex={1}
autoFocus
bg={'myWhite.600'}
placeholder={t('common.Name')}
maxLength={30}
{...register('name', {
required: '知识库名称不能为空~'
})}
/>
</Flex>
</Box>
<Flex mt={6} alignItems={'center'}>
<Box flex={'0 0 100px'}></Box>
<Box flex={1}>
@@ -157,42 +183,14 @@ const CreateModal = ({ onClose, parentId }: { onClose: () => void; parentId?: st
/>
</Box>
</Flex>
<Flex mt={6} alignItems={'center'} w={'100%'}>
<Box flex={'0 0 100px'}>
<MyTooltip label={'用空格隔开多个标签,便于搜索'} forceShow>
<QuestionOutlineIcon ml={1} />
</MyTooltip>
</Box>
<Input
flex={1}
ref={InputRef}
placeholder={'标签,使用空格分割。'}
maxLength={30}
onChange={(e) => {
setValue('tags', e.target.value);
setRefresh(!refresh);
}}
/>
</Flex>
<Flex mt={2} flexWrap={'wrap'}>
{getValues('tags')
.split(' ')
.filter(Boolean)
.map((item, i) => (
<Tag mr={2} mb={2} key={i} whiteSpace={'nowrap'}>
{item}
</Tag>
))}
</Flex>
</ModalBody>
<ModalFooter>
<Button variant={'base'} mr={3} onClick={onClose}>
{t('common.Close')}
</Button>
<Button isLoading={creating} onClick={handleSubmit((data) => onclickCreate(data))}>
{t('common.Confirm Create')}
</Button>
</ModalFooter>

View File

@@ -7,8 +7,7 @@ import {
useDisclosure,
Card,
MenuButton,
Image,
Link
Image
} from '@chakra-ui/react';
import { useRouter } from 'next/router';
import { useDatasetStore } from '@/web/core/dataset/store/dataset';
@@ -28,8 +27,11 @@ import Avatar from '@/components/Avatar';
import MyIcon from '@/components/Icon';
import { serviceSideProps } from '@/web/common/utils/i18n';
import dynamic from 'next/dynamic';
import { FolderAvatarSrc, DatasetTypeEnum } from '@fastgpt/global/core/dataset/constant';
import Tag from '@/components/Tag';
import {
FolderAvatarSrc,
DatasetTypeEnum,
DatasetTypeMap
} from '@fastgpt/global/core/dataset/constant';
import MyMenu from '@/components/MyMenu';
import { useRequest } from '@/web/common/hooks/useRequest';
import { useSystemStore } from '@/web/common/system/useSystemStore';
@@ -55,12 +57,12 @@ const Kb = () => {
const DeleteTipsMap = useRef({
[DatasetTypeEnum.folder]: t('dataset.deleteFolderTips'),
[DatasetTypeEnum.dataset]: t('dataset.deleteDatasetTips')
[DatasetTypeEnum.dataset]: t('dataset.deleteDatasetTips'),
[DatasetTypeEnum.websiteDataset]: t('core.dataset.Delete Website Tips')
});
const { openConfirm, ConfirmModal } = useConfirm({
title: t('common.Delete Warning'),
content: ''
type: 'delete'
});
const { myDatasets, loadDatasets, setDatasets, updateDataset } = useDatasetStore();
const { onOpenModal: onOpenTitleModal, EditModal: EditTitleModal } = useEditTitle({
@@ -110,14 +112,26 @@ const Kb = () => {
errorToast: t('dataset.Export Dataset Limit Error')
});
const { data, refetch } = useQuery(['loadDataset', parentId], () => {
const { data, refetch, isFetching } = useQuery(['loadDataset', parentId], () => {
return Promise.all([loadDatasets(parentId), getDatasetPaths(parentId)]);
});
const paths = data?.[1] || [];
const formatDatasets = useMemo(
() =>
myDatasets.map((item) => {
return {
...item,
label: DatasetTypeMap[item.type]?.label,
icon: DatasetTypeMap[item.type]?.icon
};
}),
[myDatasets]
);
return (
<PageContainer>
<PageContainer isLoading={isFetching}>
<Flex pt={3} px={5} alignItems={'center'}>
{/* url path */}
<ParentPaths
@@ -179,7 +193,7 @@ const Kb = () => {
child: (
<Flex>
<Image src={'/imgs/module/db.png'} alt={''} w={'20px'} mr={1} />
{t('Dataset')}
{t('core.dataset.Dataset')}
</Flex>
),
onClick: onOpenCreateModal
@@ -194,15 +208,15 @@ const Kb = () => {
gridGap={5}
userSelect={'none'}
>
{myDatasets.map((dataset) => (
{formatDatasets.map((dataset) => (
<Card
display={'flex'}
flexDirection={'column'}
key={dataset._id}
py={4}
py={3}
px={5}
cursor={'pointer'}
h={'130px'}
minH={'130px'}
border={theme.borders.md}
boxShadow={'none'}
position={'relative'}
@@ -250,7 +264,7 @@ const Kb = () => {
parentId: dataset._id
}
});
} else if (dataset.type === DatasetTypeEnum.dataset) {
} else {
router.push({
pathname: '/dataset/detail',
query: {
@@ -388,27 +402,22 @@ const Kb = () => {
{dataset.name}
</Box>
</Flex>
<Box flex={'1 0 0'} overflow={'hidden'} pt={2}>
<Flex>
{dataset.tags.filter(Boolean).map((tag, i) => (
<Tag key={i} mr={2} mb={2}>
{tag}
</Tag>
))}
</Flex>
<Box
flex={1}
className={'textEllipsis3'}
py={1}
wordBreak={'break-all'}
fontSize={'sm'}
color={'myGray.500'}
>
{dataset.intro || t('core.dataset.Intro Placeholder')}
</Box>
<Flex alignItems={'center'} fontSize={'sm'}>
<Box flex={1}>
<PermissionIconText permission={dataset.permission} color={'myGray.600'} />
</Box>
{dataset.type === DatasetTypeEnum.folder ? (
<Box color={'myGray.500'}>{t('Folder')}</Box>
) : (
<>
<MyIcon mr={1} name="kbTest" w={'12px'} />
<Box color={'myGray.500'}>{dataset.vectorModel.name}</Box>
</>
)}
<MyIcon mr={1} name={dataset.icon as any} w={'12px'} />
<Box color={'myGray.500'}>{t(dataset.label)}</Box>
</Flex>
</Card>
))}
@@ -417,7 +426,7 @@ const Kb = () => {
<Flex mt={'35vh'} flexDirection={'column'} alignItems={'center'}>
<MyIcon name="empty" w={'48px'} h={'48px'} color={'transparent'} />
<Box mt={2} color={'myGray.500'}>
{t('core.dataset.Empty Dataset Tips')}
</Box>
</Flex>
)}
@@ -429,27 +438,19 @@ const Kb = () => {
onClose={() => setEditFolderData(undefined)}
editCallback={async (name) => {
try {
if (editFolderData.id) {
await putDatasetById({
id: editFolderData.id,
name
});
} else {
await postCreateDataset({
parentId,
name,
type: DatasetTypeEnum.folder,
avatar: FolderAvatarSrc,
tags: ''
});
}
await postCreateDataset({
parentId,
name,
type: DatasetTypeEnum.folder,
avatar: FolderAvatarSrc,
intro: ''
});
refetch();
} catch (error) {
return Promise.reject(error);
}
}}
isEdit={!!editFolderData.id}
name={editFolderData.name}
isEdit={false}
/>
)}
{!!moveDataId && (

View File

@@ -138,8 +138,8 @@ const CreateModal = ({
try {
const src = await compressImgFileAndUpload({
file,
maxW: 100,
maxH: 100
maxW: 300,
maxH: 300
});
setValue('avatar', src);
setRefresh((state) => !state);

View File

@@ -96,18 +96,18 @@ export function request(url: string, data: any, config: ConfigType, method: Meth
* @param {Object} config
* @returns
*/
export function GET<T>(url: string, params = {}, config: ConfigType = {}): Promise<T> {
export function GET<T = undefined>(url: string, params = {}, config: ConfigType = {}): Promise<T> {
return request(url, params, config, 'GET');
}
export function POST<T>(url: string, data = {}, config: ConfigType = {}): Promise<T> {
export function POST<T = undefined>(url: string, data = {}, config: ConfigType = {}): Promise<T> {
return request(url, data, config, 'POST');
}
export function PUT<T>(url: string, data = {}, config: ConfigType = {}): Promise<T> {
export function PUT<T = undefined>(url: string, data = {}, config: ConfigType = {}): Promise<T> {
return request(url, data, config, 'PUT');
}
export function DELETE<T>(url: string, data = {}, config: ConfigType = {}): Promise<T> {
export function DELETE<T = undefined>(url: string, data = {}, config: ConfigType = {}): Promise<T> {
return request(url, data, config, 'DELETE');
}

View File

@@ -20,7 +20,8 @@ export function reRankRecall({ query, inputs }: PostReRankProps) {
Authorization: `Bearer ${model.requestAuth}`
}
}
).finally(() => {
).then((data) => {
console.log('rerank time:', Date.now() - start);
return data;
});
}

View File

@@ -4,7 +4,8 @@ import {
PatchIndexesProps,
UpdateDatasetDataProps
} from '@fastgpt/global/core/dataset/controller';
import { deletePgDataById, insertData2Pg, updatePgDataById } from './pg';
import { deletePgDataById } from '@fastgpt/service/core/dataset/data/pg';
import { insertData2Pg, updatePgDataById } from './pg';
import { Types } from 'mongoose';
import { DatasetDataIndexTypeEnum } from '@fastgpt/global/core/dataset/constant';
import { getDefaultIndex } from '@fastgpt/global/core/dataset/utils';
@@ -213,29 +214,3 @@ export async function updateData2Dataset({
tokenLen
};
}
/* delete all data by datasetIds */
export async function delDataByDatasetId({ datasetIds }: { datasetIds: string[] }) {
datasetIds = datasetIds.map((item) => String(item));
// delete pg data
await deletePgDataById(`dataset_id IN ('${datasetIds.join("','")}')`);
// delete dataset.datas
await MongoDatasetData.deleteMany({ datasetId: { $in: datasetIds } });
}
/**
* delete all data by collectionIds
*/
export async function delDataByCollectionId({ collectionIds }: { collectionIds: string[] }) {
const ids = collectionIds.map((item) => String(item));
// delete pg data
await deletePgDataById(`collection_id IN ('${ids.join("','")}')`);
// delete dataset.datas
await MongoDatasetData.deleteMany({ collectionId: { $in: ids } });
}
/**
* delete one data by mongoDataId
*/
export async function deleteDataByDataId(mongoDataId: string) {
await deletePgDataById(['data_id', mongoDataId]);
await MongoDatasetData.findByIdAndDelete(mongoDataId);
}

View File

@@ -5,7 +5,7 @@ import type {
} from '@fastgpt/global/core/dataset/type.d';
import { PgClient } from '@fastgpt/service/common/pg';
import { getVectorsByText } from '@/service/core/ai/vector';
import { delay } from '@/utils/tools';
import { delay } from '@fastgpt/global/common/system/utils';
import { PgSearchRawType } from '@fastgpt/global/core/dataset/api';
import { MongoDatasetCollection } from '@fastgpt/service/core/dataset/collection/schema';
import { MongoDatasetData } from '@fastgpt/service/core/dataset/data/schema';
@@ -103,31 +103,6 @@ export async function updatePgDataById({
return updatePg();
}
export async function deletePgDataById(
where: ['id' | 'dataset_id' | 'collection_id' | 'data_id', string] | string
) {
let retry = 2;
async function deleteData(): Promise<any> {
try {
await PgClient.delete(PgDatasetTableName, {
where: [where]
});
} catch (error) {
if (--retry < 0) {
return Promise.reject(error);
}
await delay(500);
return deleteData();
}
}
await deleteData();
return {
tokenLen: 0
};
}
// ------------------ search start ------------------
type SearchProps = {
text: string;

View File

@@ -1,5 +1,6 @@
import { MongoDatasetData } from '@fastgpt/service/core/dataset/data/schema';
import { cut } from '@node-rs/jieba';
import { stopWords } from '@fastgpt/global/common/string/jieba';
/**
* Same value judgment
@@ -30,7 +31,7 @@ export function jiebaSplit({ text }: { text: string }) {
return (
tokens
.map((item) => item.replace(/[^\u4e00-\u9fa5a-zA-Z0-9\s]/g, '').trim())
.filter(Boolean)
.filter((item) => item && !stopWords.has(item))
.join(' ') || ''
);
}

View File

@@ -113,10 +113,14 @@ export const dispatchChatCompletion = async (props: ChatProps): Promise<ChatResp
}
]
: []),
...messages.map((item) => ({
...item,
content: modelConstantsData.vision ? formatStr2ChatContent(item.content) : item.content
}))
...(await Promise.all(
messages.map(async (item) => ({
...item,
content: modelConstantsData.vision
? await formatStr2ChatContent(item.content)
: item.content
}))
))
];
const response = await ai.chat.completions.create(

View File

@@ -25,28 +25,29 @@ import { dispatchAppRequest } from './tools/runApp';
import { dispatchRunPlugin } from './plugin/run';
import { dispatchPluginInput } from './plugin/runInput';
import { dispatchPluginOutput } from './plugin/runOutput';
import { AuthUserTypeEnum } from '@fastgpt/global/support/permission/constant';
/* running */
export async function dispatchModules({
res,
appId,
chatId,
modules,
user,
teamId,
tmbId,
user,
appId,
modules,
chatId,
params = {},
variables = {},
stream = false,
detail = false
}: {
res: NextApiResponse;
appId: string;
chatId?: string;
modules: ModuleItemType[];
user: UserType;
teamId: string;
tmbId: string;
user: UserType;
appId: string;
modules: ModuleItemType[];
chatId?: string;
params?: Record<string, any>;
variables?: Record<string, any>;
stream?: boolean;
@@ -176,15 +177,15 @@ export async function dispatchModules({
});
const props: ModuleDispatchProps<Record<string, any>> = {
res,
teamId,
tmbId,
user,
appId,
chatId,
stream,
detail,
variables,
outputs: module.outputs,
user,
teamId,
tmbId,
inputs: params
};

View File

@@ -35,13 +35,11 @@ export async function authUser({
user: UserType;
}
> {
const { userId, teamId, tmbId } = await parseHeaderCert(props);
const result = await parseHeaderCert(props);
return {
userId,
teamId,
tmbId,
user: await getUserAndAuthBalance({ tmbId, minBalance }),
...result,
user: await getUserAndAuthBalance({ tmbId: result.tmbId, minBalance }),
isOwner: true,
canWrite: true
};

View File

@@ -12,14 +12,18 @@ export function createBill(data: CreateBillProps) {
if (data.total === 0) {
addLog.info('0 Bill', data);
}
POST('/support/wallet/bill/createBill', data);
try {
POST('/support/wallet/bill/createBill', data);
} catch (error) {}
}
export function concatBill(data: ConcatBillProps) {
if (!global.systemEnv.pluginBaseUrl) return;
if (data.total === 0) {
addLog.info('0 Bill', data);
}
POST('/support/wallet/bill/concatBill', data);
try {
POST('/support/wallet/bill/concatBill', data);
} catch (error) {}
}
export const pushChatBill = ({
@@ -92,7 +96,7 @@ export const pushQABill = async ({
return { total };
};
export const pushGenerateVectorBill = async ({
export const pushGenerateVectorBill = ({
billId,
teamId,
tmbId,
@@ -250,7 +254,7 @@ export function pushReRankBill({
source: `${BillSourceEnum}`;
}) {
const model = global.reRankModels[0];
if (!model) return;
if (!model) return { total: 0 };
const total = model.price * PRICE_SCALE;
const name = 'wallet.bill.ReRank';
@@ -270,4 +274,6 @@ export function pushReRankBill({
}
]
});
return { total };
}

View File

@@ -1,18 +1,19 @@
import type { NextApiResponse } from 'next';
import { RunningModuleItemType } from '@/types/app';
import type { UserType } from '@fastgpt/global/support/user/type';
import { AuthUserTypeEnum } from '@fastgpt/global/support/permission/constant';
// module dispatch props type
export type ModuleDispatchProps<T> = {
res: NextApiResponse;
teamId: string;
tmbId: string;
user: UserType;
appId: string;
chatId?: string;
stream: boolean;
detail: boolean;
detail: boolean; // response detail
variables: Record<string, any>;
outputs: RunningModuleItemType['outputs'];
user: UserType;
teamId: string;
tmbId: string;
inputs: T;
};

View File

@@ -79,10 +79,3 @@ export const formatTimeToChatTime = (time: Date) => {
// 如果是更久之前,展示某年某月某日
return target.format('YYYY/M/D');
};
export const delay = (ms: number) =>
new Promise((resolve) => {
setTimeout(() => {
resolve('');
}, ms);
});

View File

@@ -160,18 +160,18 @@ function request(
* @param {Object} config
* @returns
*/
export function GET<T>(url: string, params = {}, config: ConfigType = {}): Promise<T> {
export function GET<T = undefined>(url: string, params = {}, config: ConfigType = {}): Promise<T> {
return request(url, params, config, 'GET');
}
export function POST<T>(url: string, data = {}, config: ConfigType = {}): Promise<T> {
export function POST<T = undefined>(url: string, data = {}, config: ConfigType = {}): Promise<T> {
return request(url, data, config, 'POST');
}
export function PUT<T>(url: string, data = {}, config: ConfigType = {}): Promise<T> {
export function PUT<T = undefined>(url: string, data = {}, config: ConfigType = {}): Promise<T> {
return request(url, data, config, 'PUT');
}
export function DELETE<T>(url: string, data = {}, config: ConfigType = {}): Promise<T> {
export function DELETE<T = undefined>(url: string, data = {}, config: ConfigType = {}): Promise<T> {
return request(url, data, config, 'DELETE');
}

View File

@@ -35,9 +35,9 @@ export const uploadFiles = ({
*/
export const compressBase64ImgAndUpload = ({
base64,
maxW = 200,
maxH = 200,
maxSize = 1024 * 100, // 100kb
maxW = 1080,
maxH = 1080,
maxSize = 1024 * 500, // 300kb
expiredTime
}: {
base64: string;
@@ -77,7 +77,7 @@ export const compressBase64ImgAndUpload = ({
}
ctx.drawImage(img, 0, 0, width, height);
const compressedDataUrl = canvas.toDataURL(fileType, 0.8);
const compressedDataUrl = canvas.toDataURL(fileType, 1);
// 移除 canvas 元素
canvas.remove();

View File

@@ -1,6 +1,7 @@
import mammoth from 'mammoth';
import Papa from 'papaparse';
import { compressBase64ImgAndUpload } from './controller';
import { simpleMarkdownText } from '@fastgpt/global/common/string/markdown';
/**
* 读取 txt 文件内容
@@ -182,9 +183,9 @@ export const formatMarkdown = async (rawText: string = '') => {
try {
const str = await compressBase64ImgAndUpload({
base64,
maxW: 800,
maxH: 800,
maxSize: 1024 * 1024 * 2
maxW: 4329,
maxH: 4329,
maxSize: 1024 * 1024 * 5
});
rawText = rawText.replace(base64, str);
} catch (error) {
@@ -199,14 +200,7 @@ export const formatMarkdown = async (rawText: string = '') => {
rawText = rawText.replace(/\s*(!\[.*\]\(.*\))\s*/g, '$1');
}
// replace \
const reg1 = /\\([-.!`_(){}\[\]])/g;
if (reg1.test(rawText)) {
rawText = rawText.replace(/\\([`!*()+-_\[\]{}\\.])/g, '$1');
}
rawText = rawText.replace(/\\\\n/g, '\\n');
return rawText;
return simpleMarkdownText(rawText);
};
/**

View File

@@ -1,29 +1,46 @@
import { useCallback, useRef, useState } from 'react';
import {
AlertDialog,
AlertDialogBody,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogContent,
AlertDialogOverlay,
useDisclosure,
Button
} from '@chakra-ui/react';
import { useCallback, useMemo, useRef, useState } from 'react';
import { useDisclosure, Button, ModalBody, ModalFooter } from '@chakra-ui/react';
import { useTranslation } from 'next-i18next';
import MyModal from '@/components/MyModal';
export const useConfirm = (props?: {
title?: string | null;
content?: string | null;
title?: string;
iconSrc?: string | '';
content?: string;
bg?: string;
showCancel?: boolean;
type?: 'common' | 'delete';
}) => {
const { t } = useTranslation();
const { title = t('Warning'), content, bg, showCancel = true } = props || {};
const map = useMemo(() => {
const map = {
common: {
title: t('common.confirm.Common Tip'),
bg: undefined,
iconSrc: 'common/confirm/commonTip'
},
delete: {
title: t('common.Delete Warning'),
bg: 'red.600',
iconSrc: 'common/confirm/deleteTip'
}
};
if (props?.type && map[props.type]) return map[props.type];
return map.common;
}, [props?.type, t]);
const {
title = map?.title || t('Warning'),
iconSrc = map?.iconSrc,
content,
bg = map?.bg,
showCancel = true
} = props || {};
const [customContent, setCustomContent] = useState(content);
const { isOpen, onOpen, onClose } = useDisclosure();
const cancelRef = useRef(null);
const confirmCb = useRef<any>();
const cancelCb = useRef<any>();
@@ -41,51 +58,41 @@ export const useConfirm = (props?: {
),
ConfirmModal: useCallback(
() => (
<AlertDialog
<MyModal
isOpen={isOpen}
leastDestructiveRef={cancelRef}
autoFocus={false}
onClose={onClose}
iconSrc={iconSrc}
title={title}
maxW={['90vw', '500px']}
>
<AlertDialogOverlay>
<AlertDialogContent maxW={'min(90vw,400px)'}>
<AlertDialogHeader fontSize="lg" fontWeight="bold">
{title}
</AlertDialogHeader>
<ModalBody pt={5}>{customContent}</ModalBody>
<ModalFooter>
{showCancel && (
<Button
variant={'base'}
onClick={() => {
onClose();
typeof cancelCb.current === 'function' && cancelCb.current();
}}
>
{t('Cancel')}
</Button>
)}
<AlertDialogBody whiteSpace={'pre-wrap'} py={0}>
{customContent}
</AlertDialogBody>
<AlertDialogFooter>
{showCancel && (
<Button
variant={'base'}
onClick={() => {
onClose();
typeof cancelCb.current === 'function' && cancelCb.current();
}}
>
{t('Cancel')}
</Button>
)}
<Button
{...(bg && { bg: `${bg} !important` })}
ml={4}
onClick={() => {
onClose();
typeof confirmCb.current === 'function' && confirmCb.current();
}}
>
{t('Confirm')}
</Button>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialogOverlay>
</AlertDialog>
<Button
{...(bg && { bg: `${bg} !important` })}
ml={4}
onClick={() => {
onClose();
typeof confirmCb.current === 'function' && confirmCb.current();
}}
>
{t('Confirm')}
</Button>
</ModalFooter>
</MyModal>
),
[bg, customContent, isOpen, onClose, t, title]
[bg, customContent, iconSrc, isOpen, onClose, showCancel, t, title]
)
};
};

View File

@@ -1,21 +1,27 @@
import React, { useCallback, useRef } from 'react';
import { ModalFooter, ModalBody, Input, useDisclosure, Button, Box } from '@chakra-ui/react';
import MyModal from '@/components/MyModal';
import { useToast } from './useToast';
export const useEditTitle = ({
title,
tip,
placeholder = ''
placeholder = '',
canEmpty = true,
valueRule
}: {
title: string;
tip?: string;
placeholder?: string;
canEmpty?: boolean;
valueRule?: (val: string) => string | void;
}) => {
const { isOpen, onOpen, onClose } = useDisclosure();
const inputRef = useRef<HTMLInputElement | null>(null);
const onSuccessCb = useRef<(content: string) => void | Promise<void>>();
const onErrorCb = useRef<(err: any) => void>();
const { toast } = useToast();
const defaultValue = useRef('');
const onOpenModal = useCallback(
@@ -37,21 +43,43 @@ export const useEditTitle = ({
);
const onclickConfirm = useCallback(async () => {
if (!inputRef.current) return;
if (!inputRef.current || !onSuccessCb.current) return;
const val = inputRef.current.value;
if (!canEmpty && !val) {
inputRef.current.focus();
return;
}
if (valueRule) {
const result = valueRule(val);
if (result) {
return toast({
status: 'warning',
title: result
});
}
}
try {
const val = inputRef.current.value;
await onSuccessCb.current?.(val);
await onSuccessCb.current(val);
onClose();
} catch (err) {
onErrorCb.current?.(err);
}
}, [onClose]);
}, [canEmpty, onClose]);
// eslint-disable-next-line react/display-name
const EditModal = useCallback(
({ maxLength = 30 }: { maxLength?: number }) => (
<MyModal isOpen={isOpen} onClose={onClose} iconSrc="/imgs/modal/edit.svg" title={title}>
({
maxLength = 30,
iconSrc = '/imgs/modal/edit.svg'
}: {
maxLength?: number;
iconSrc?: string;
}) => (
<MyModal isOpen={isOpen} onClose={onClose} iconSrc={iconSrc} title={title} maxW={'500px'}>
<ModalBody>
{!!tip && (
<Box mb={2} color={'myGray.500'} fontSize={'sm'}>

View File

@@ -1,6 +0,0 @@
import { GET, POST, PUT, DELETE } from '@/web/common/api/request';
import type { FetchResultItem } from '@fastgpt/global/common/plugin/types/pluginRes.d';
export const postFetchUrls = (urlList: string[]) =>
POST<FetchResultItem[]>(`/plugins/urlFetch`, { urlList });

View File

@@ -1,6 +1,6 @@
import type { InitDateResponse } from '@/global/common/api/systemRes';
import { getSystemInitData } from '@/web/common/system/api';
import { delay } from '@/utils/tools';
import { delay } from '@fastgpt/global/common/system/utils';
import type { FeConfigsType } from '@fastgpt/global/common/system/types/index.d';
import {
defaultChatModels,

View File

@@ -59,7 +59,7 @@ export const useSystemStore = create<State>()(
state.isPc = val;
});
},
gitStar: 3700,
gitStar: 6100,
async loadGitStar() {
try {
const { data: git } = await axios.get('https://api.github.com/repos/labring/FastGPT');

View File

@@ -0,0 +1,5 @@
import { GET, POST, PUT, DELETE } from '@/web/common/api/request';
import { UrlFetchParams, UrlFetchResponse } from '@fastgpt/global/common/file/api.d';
export const postFetchUrls = (data: UrlFetchParams) =>
POST<UrlFetchResponse>(`/tools/urlFetch`, data);

View File

@@ -1095,7 +1095,7 @@ export const appTemplates: (AppItemType & {
key: 'text',
type: 'textarea',
valueType: 'string',
value: '你好,我是 laf 助手,有什么可以帮助你的?',
value: '你好,有什么可以帮助你的?',
label: '回复的内容',
description:
'可以使用 \\n 来实现连续换行。\n\n可以通过外部模块输入实现回复外部模块输入时会覆盖当前填写的内容',

View File

@@ -1,13 +1,16 @@
import { GET, POST, PUT, DELETE } from '@/web/common/api/request';
import type { ParentTreePathItemType } from '@fastgpt/global/common/parentFolder/type.d';
import type { DatasetItemType } from '@fastgpt/global/core/dataset/type.d';
import type { DatasetItemType, DatasetListItemType } from '@fastgpt/global/core/dataset/type.d';
import type {
DatasetUpdateParams,
GetDatasetCollectionsProps,
GetDatasetDataListProps,
CreateDatasetCollectionParams,
UpdateDatasetCollectionParams
} from '@/global/core/api/datasetReq.d';
import type {
CreateDatasetCollectionParams,
DatasetUpdateBody,
PostWebsiteSyncParams
} from '@fastgpt/global/core/dataset/api.d';
import type { SearchTestProps, SearchTestResponse } from '@/global/core/dataset/api.d';
import type {
PushDatasetDataProps,
@@ -24,12 +27,12 @@ import { PagingData } from '@/types';
/* ======================== dataset ======================= */
export const getDatasets = (data: { parentId?: string; type?: `${DatasetTypeEnum}` }) =>
GET<DatasetItemType[]>(`/core/dataset/list`, data);
GET<DatasetListItemType[]>(`/core/dataset/list`, data);
/**
* get type=dataset list
*/
export const getAllDataset = () => GET<DatasetItemType[]>(`/core/dataset/allDataset`);
export const getAllDataset = () => GET<DatasetListItemType[]>(`/core/dataset/allDataset`);
export const getDatasetPaths = (parentId?: string) =>
GET<ParentTreePathItemType[]>('/core/dataset/paths', { parentId });
@@ -39,7 +42,7 @@ export const getDatasetById = (id: string) => GET<DatasetItemType>(`/core/datase
export const postCreateDataset = (data: CreateDatasetParams) =>
POST<string>(`/core/dataset/create`, data);
export const putDatasetById = (data: DatasetUpdateParams) => PUT(`/core/dataset/update`, data);
export const putDatasetById = (data: DatasetUpdateBody) => PUT<void>(`/core/dataset/update`, data);
export const delDatasetById = (id: string) => DELETE(`/core/dataset/delete?id=${id}`);
@@ -62,7 +65,11 @@ export const postDatasetCollection = (data: CreateDatasetCollectionParams) =>
export const putDatasetCollectionById = (data: UpdateDatasetCollectionParams) =>
POST(`/core/dataset/collection/update`, data);
export const delDatasetCollectionById = (params: { collectionId: string }) =>
DELETE(`/core/dataset/collection/delById`, params);
DELETE(`/core/dataset/collection/delete`, params);
export const postWebsiteSync = (data: PostWebsiteSyncParams) =>
POST(`/plusApi/core/dataset/websiteSync`, data, {
timeout: 600000
}).catch();
/* =============================== data ==================================== */
/* get dataset list */

View File

@@ -1,20 +1,20 @@
import { create } from 'zustand';
import { devtools, persist } from 'zustand/middleware';
import { immer } from 'zustand/middleware/immer';
import type { DatasetItemType } from '@fastgpt/global/core/dataset/type.d';
import type { DatasetItemType, DatasetListItemType } from '@fastgpt/global/core/dataset/type.d';
import { getAllDataset, getDatasets, getDatasetById, putDatasetById } from '@/web/core/dataset/api';
import { defaultDatasetDetail } from '@/constants/dataset';
import type { DatasetUpdateParams } from '@/global/core/api/datasetReq.d';
import type { DatasetUpdateBody } from '@fastgpt/global/core/dataset/api.d';
type State = {
allDatasets: DatasetItemType[];
loadAllDatasets: () => Promise<DatasetItemType[]>;
myDatasets: DatasetItemType[];
allDatasets: DatasetListItemType[];
loadAllDatasets: () => Promise<DatasetListItemType[]>;
myDatasets: DatasetListItemType[];
loadDatasets: (parentId?: string) => Promise<any>;
setDatasets(val: DatasetItemType[]): void;
setDatasets(val: DatasetListItemType[]): void;
datasetDetail: DatasetItemType;
loadDatasetDetail: (id: string, init?: boolean) => Promise<DatasetItemType>;
updateDataset: (data: DatasetUpdateParams) => Promise<any>;
updateDataset: (data: DatasetUpdateBody) => Promise<any>;
};
export const useDatasetStore = create<State>()(
@@ -55,10 +55,12 @@ export const useDatasetStore = create<State>()(
return data;
},
async updateDataset(data) {
await putDatasetById(data);
if (get().datasetDetail._id === data.id) {
set((state) => {
state.datasetDetail = {
...state.datasetDetail,
...get().datasetDetail,
...data
};
});
@@ -74,7 +76,6 @@ export const useDatasetStore = create<State>()(
: item
);
});
await putDatasetById(data);
}
})),
{

View File

@@ -1,6 +1,6 @@
import { getFileViewUrl, postChunks2Dataset } from '@/web/core/dataset/api';
import { TrainingModeEnum } from '@fastgpt/global/core/dataset/constant';
import { delay } from '@/utils/tools';
import { delay } from '@fastgpt/global/common/system/utils';
import { strIsLink } from '@fastgpt/global/common/string/tools';
import type { PushDatasetDataChunkProps } from '@fastgpt/global/core/dataset/api.d';

View File

@@ -26,3 +26,12 @@
span[tabindex='0'] {
line-height: 1;
}
@keyframes zoomStopIcon {
0% {
transform: scale(0.8);
}
100% {
transform: scale(1.2);
}
}

View File

@@ -1,6 +1,6 @@
import { GET } from '@/web/common/api/request';
import type { PaySchema } from '@fastgpt/global/support/wallet/pay/type.d';
import { delay } from '@/utils/tools';
import { delay } from '@fastgpt/global/common/system/utils';
export const getPayOrders = () => GET<PaySchema[]>(`/plusApi/support/wallet/pay/getPayOrders`);