This commit is contained in:
Archer
2023-11-20 19:20:55 +08:00
committed by GitHub
parent 9c4eabfc9e
commit 0558379ddb
26 changed files with 571 additions and 497 deletions

View File

@@ -7,7 +7,7 @@
<p align="center"> <p align="center">
<a href="./README_en.md">English</a> | <a href="./README_en.md">English</a> |
<a href="./README.md">简体中文</a> | <a href="./README.md">简体中文</a> |
<a href="./README_ja.md">本語</a> <a href="./README_ja.md"></a>
</p> </p>
FastGPT 是一个基于 LLM 大语言模型的知识库问答系统,提供开箱即用的数据处理、模型调用等能力。同时可以通过 Flow 可视化进行工作流编排,从而实现复杂的问答场景! FastGPT 是一个基于 LLM 大语言模型的知识库问答系统,提供开箱即用的数据处理、模型调用等能力。同时可以通过 Flow 可视化进行工作流编排,从而实现复杂的问答场景!

View File

@@ -7,7 +7,7 @@
<p align="center"> <p align="center">
<a href="./README_en.md">English</a> | <a href="./README_en.md">English</a> |
<a href="./README.md">简体中文</a> | <a href="./README.md">简体中文</a> |
<a href="./README_ja.md">本語</a> <a href="./README_ja.md"></a>
</p> </p>
FastGPT is a knowledge-based Q&A system built on the LLM, offers out-of-the-box data processing and model invocation capabilities, allows for workflow orchestration through Flow visualization! FastGPT is a knowledge-based Q&A system built on the LLM, offers out-of-the-box data processing and model invocation capabilities, allows for workflow orchestration through Flow visualization!

View File

@@ -7,7 +7,7 @@
<p align="center"> <p align="center">
<a href="./README_en.md">English</a> | <a href="./README_en.md">English</a> |
<a href="./README.md">简体中文</a> | <a href="./README.md">简体中文</a> |
<a href="./README_ja.md">本語</a> <a href="./README_ja.md"></a>
</p> </p>
FastGPT は、LLM 上 に 構築 された 知識 ベースの Q&A システムで、すぐに 使 えるデータ 処理 とモデル 呼 び 出 し 機能 を 提供 し、Flow の 可視化 を 通 じてワークフローのオーケストレーションを 可能 にします! FastGPT は、LLM 上 に 構築 された 知識 ベースの Q&A システムで、すぐに 使 えるデータ 処理 とモデル 呼 び 出 し 機能 を 提供 し、Flow の 可視化 を 通 じてワークフローのオーケストレーションを 可能 にします!

View File

@@ -1,5 +1,5 @@
--- ---
title: '升级到 V4.3' title: '升级到 V4.3(需要初始化)'
description: 'FastGPT 从旧版本升级到 V4.3 操作指南' description: 'FastGPT 从旧版本升级到 V4.3 操作指南'
icon: 'upgrade' icon: 'upgrade'
draft: false draft: false

View File

@@ -1,5 +1,5 @@
--- ---
title: '升级到 V4.4' title: '升级到 V4.4(需要初始化)'
description: 'FastGPT 从旧版本升级到 V4.4 操作指南' description: 'FastGPT 从旧版本升级到 V4.4 操作指南'
icon: 'upgrade' icon: 'upgrade'
draft: false draft: false

View File

@@ -1,5 +1,5 @@
--- ---
title: '升级到 V4.4.1' title: '升级到 V4.4.1(需要初始化)'
description: 'FastGPT 从旧版本升级到 V4.4.1 操作指南' description: 'FastGPT 从旧版本升级到 V4.4.1 操作指南'
icon: 'upgrade' icon: 'upgrade'
draft: false draft: false

View File

@@ -1,5 +1,5 @@
--- ---
title: '升级到 V4.4.2' title: '升级到 V4.4.2(需要初始化)'
description: 'FastGPT 从旧版本升级到 V4.4.2 操作指南' description: 'FastGPT 从旧版本升级到 V4.4.2 操作指南'
icon: 'upgrade' icon: 'upgrade'
draft: false draft: false

View File

@@ -1,6 +1,6 @@
--- ---
title: 'V4.4.5' title: 'V4.4.5(需要初始化)'
description: 'FastGPT V4.4.5 更新(需执行升级脚本)' description: 'FastGPT V4.4.5 更新'
icon: 'upgrade' icon: 'upgrade'
draft: false draft: false
toc: true toc: true

View File

@@ -201,7 +201,8 @@
"Drag Tip": "Click and move", "Drag Tip": "Click and move",
"Move Success": "Move Success", "Move Success": "Move Success",
"No Folder": "There's no subdirectory. Just put it here", "No Folder": "There's no subdirectory. Just put it here",
"Root Path": "Root Folder" "Root Path": "Root Folder",
"empty": "There is nothing to choose from in this directory"
}, },
"input": { "input": {
"Repeat Value": "Repeat Value" "Repeat Value": "Repeat Value"
@@ -237,6 +238,7 @@
"Record": "Speech", "Record": "Speech",
"Restart": "Restart", "Restart": "Restart",
"Select File": "Select file", "Select File": "Select file",
"Select Image": "Select Image",
"Send Message": "Send Message", "Send Message": "Send Message",
"Speaking": "I'm listening...", "Speaking": "I'm listening...",
"Stop Speak": "Stop Speak", "Stop Speak": "Stop Speak",
@@ -286,6 +288,7 @@
"My Dataset": "My Dataset", "My Dataset": "My Dataset",
"Queue Desc": "This data refers to the current amount of training for the entire system. FastGPT uses queued training, and if you have too much data to train, you may need to wait for a while", "Queue Desc": "This data refers to the current amount of training for the entire system. FastGPT uses queued training, and if you have too much data to train, you may need to wait for a while",
"Select Dataset": "Select Dataset", "Select Dataset": "Select Dataset",
"Select Dataset Tips": "Select only knowledge bases with the same index model",
"Select Folder": "Enter folder", "Select Folder": "Enter folder",
"System Data Queue": "Data Queue", "System Data Queue": "Data Queue",
"Training Name": "Dataset Training", "Training Name": "Dataset Training",

View File

@@ -201,7 +201,8 @@
"Drag Tip": "点我可拖动", "Drag Tip": "点我可拖动",
"Move Success": "移动成功", "Move Success": "移动成功",
"No Folder": "没有子目录了,就放这里吧", "No Folder": "没有子目录了,就放这里吧",
"Root Path": "根目录" "Root Path": "根目录",
"empty": "这个目录已经没东西可选了~"
}, },
"input": { "input": {
"Repeat Value": "有重复的值" "Repeat Value": "有重复的值"
@@ -237,6 +238,7 @@
"Record": "语音输入", "Record": "语音输入",
"Restart": "重开对话", "Restart": "重开对话",
"Select File": "选择文件", "Select File": "选择文件",
"Select Image": "选择图片",
"Send Message": "发送", "Send Message": "发送",
"Speaking": "我在听,请说...", "Speaking": "我在听,请说...",
"Stop Speak": "停止录音", "Stop Speak": "停止录音",
@@ -286,6 +288,7 @@
"My Dataset": "我的知识库", "My Dataset": "我的知识库",
"Queue Desc": "该数据是指整个系统当前待训练的数量。{{title}} 采用排队训练的方式,如果待训练的数据过多,可能需要等待一段时间", "Queue Desc": "该数据是指整个系统当前待训练的数量。{{title}} 采用排队训练的方式,如果待训练的数据过多,可能需要等待一段时间",
"Select Dataset": "选择该知识库", "Select Dataset": "选择该知识库",
"Select Dataset Tips": "仅能选择同一个索引模型的知识库",
"Select Folder": "进入文件夹", "Select Folder": "进入文件夹",
"System Data Queue": "排队长度", "System Data Queue": "排队长度",
"Training Name": "数据训练", "Training Name": "数据训练",

View File

@@ -185,6 +185,7 @@ ${images.map((img) => JSON.stringify({ src: img.src })).join('\n')}
boxShadow={isSpeaking ? `0 0 10px rgba(54,111,255,0.4)` : `0 0 10px rgba(0,0,0,0.2)`} boxShadow={isSpeaking ? `0 0 10px rgba(54,111,255,0.4)` : `0 0 10px rgba(0,0,0,0.2)`}
borderRadius={['none', 'md']} borderRadius={['none', 'md']}
bg={'white'} bg={'white'}
overflow={'hidden'}
{...(isPc {...(isPc
? { ? {
border: '1px solid', border: '1px solid',
@@ -289,8 +290,8 @@ ${images.map((img) => JSON.stringify({ src: img.src })).join('\n')}
onOpenSelectFile(); onOpenSelectFile();
}} }}
> >
<MyTooltip label={t('core.chat.Select File')}> <MyTooltip label={t('core.chat.Select Image')}>
<MyIcon name={'core/chat/fileSelect'} /> <MyIcon name={'core/chat/fileSelect'} w={'18px'} color={'myGray.600'} />
</MyTooltip> </MyTooltip>
<File onSelect={onSelectFile} /> <File onSelect={onSelectFile} />
</Flex> </Flex>

View File

@@ -69,12 +69,12 @@ const QuoteModal = ({
isCentered isCentered
minW={['90vw', '600px']} minW={['90vw', '600px']}
title={ title={
<> <Box>
({rawSearch.length}) ({rawSearch.length})
<Box fontSize={'10px'} color={'myGray.500'} fontWeight={'normal'}> <Box fontSize={'10px'} color={'myGray.500'} fontWeight={'normal'}>
注意: 修改知识库内容成功后 注意: 修改知识库内容成功后
</Box> </Box>
</> </Box>
} }
> >
<ModalBody pt={0} whiteSpace={'pre-wrap'} textAlign={'justify'} wordBreak={'break-all'}> <ModalBody pt={0} whiteSpace={'pre-wrap'} textAlign={'justify'} wordBreak={'break-all'}>

View File

@@ -45,7 +45,6 @@ const SelectMarkCollection = ({
isOpen isOpen
paths={paths} paths={paths}
onClose={onClose} onClose={onClose}
parentId={parentId}
setParentId={setParentId} setParentId={setParentId}
tips={t('chat.Select Mark Kb Desc')} tips={t('chat.Select Mark Kb Desc')}
> >

View File

@@ -1 +0,0 @@
<?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="1696179048209" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4182" xmlns:xlink="http://www.w3.org/1999/xlink" width="48" height="48"><path d="M373.333333 85.333333H266.666667a53.393333 53.393333 0 0 0-53.333334 53.333334v746.666666a53.393333 53.393333 0 0 0 53.333334 53.333334h106.666666a53.393333 53.393333 0 0 0 53.333334-53.333334V138.666667a53.393333 53.393333 0 0 0-53.333334-53.333334z m10.666667 800a10.666667 10.666667 0 0 1-10.666667 10.666667H266.666667a10.666667 10.666667 0 0 1-10.666667-10.666667V138.666667a10.666667 10.666667 0 0 1 10.666667-10.666667h106.666666a10.666667 10.666667 0 0 1 10.666667 10.666667z m373.333333-800H650.666667a53.393333 53.393333 0 0 0-53.333334 53.333334v746.666666a53.393333 53.393333 0 0 0 53.333334 53.333334h106.666666a53.393333 53.393333 0 0 0 53.333334-53.333334V138.666667a53.393333 53.393333 0 0 0-53.333334-53.333334z m10.666667 800a10.666667 10.666667 0 0 1-10.666667 10.666667H650.666667a10.666667 10.666667 0 0 1-10.666667-10.666667V138.666667a10.666667 10.666667 0 0 1 10.666667-10.666667h106.666666a10.666667 10.666667 0 0 1 10.666667 10.666667z" p-id="4183"></path></svg>

Before

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -1 +0,0 @@
<?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="1683450443331" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1727" xmlns:xlink="http://www.w3.org/1999/xlink" width="64" height="64"><path d="M58.15222827 227.09272427L492.1075808-23.4520384a39.00952427 39.00952427 0 0 1 39.00952427 0l433.95657066 250.54476267a39.00952427 39.00952427 0 0 1 19.50476267 33.78346666v501.0895232a39.00952427 39.00952427 0 0 1-19.50476267 33.78224747l-433.95657066 250.54476267a39.00952427 39.00952427 0 0 1-39.00952427 0L58.15100907 795.7479616a39.00952427 39.00952427 0 0 1-19.5047616-33.78224747V260.87619093a39.00952427 39.00952427 0 0 1 19.5047616-33.78346666z m63.494096 53.5503232a9.7523808 9.7523808 0 0 0-4.87619094 8.4467808v444.66224746a9.7523808 9.7523808 0 0 0 4.87619094 8.44556267l385.08982826 222.3311232a9.7523808 9.7523808 0 0 0 9.7523808 0l385.08860907-222.329904a9.7523808 9.7523808 0 0 0 4.87619093-8.44678187V289.08982827a9.7523808 9.7523808 0 0 0-4.87619093-8.4467808L516.48853333 58.3131424a9.7523808 9.7523808 0 0 0-9.7523808 0l-385.08982826 222.32990507z m389.56129493 190.72l300.3611424-173.4131808c18.65752427-10.77150507 42.51550507-4.3788192 53.28822933 14.27870506 10.77150507 18.65752427 4.3788192 42.51550507-14.27870506 53.28822827L551.00952427 538.4728384V881.37142827c0 21.54422827-17.46529493 39.00952427-39.00952427 39.00952426-21.54422827 0-39.00952427-17.46529493-39.00952427-39.00952426V539.38712427L172.89386667 366.12632427c-18.65752427-10.77272427-25.05142827-34.63070507-14.27870507-53.28822934 10.77272427-18.65752427 34.63070507-25.05142827 53.28822933-14.278704L511.2076192 471.36304747z" p-id="1728"></path></svg>

Before

Width:  |  Height:  |  Size: 1.7 KiB

View File

@@ -3,10 +3,7 @@ import type { IconProps } from '@chakra-ui/react';
import { Icon } from '@chakra-ui/react'; import { Icon } from '@chakra-ui/react';
const iconPaths = { const iconPaths = {
appFill: () => import('./icons/fill/app.svg'),
appLight: () => import('./icons/light/app.svg'),
copy: () => import('./icons/copy.svg'), copy: () => import('./icons/copy.svg'),
chatSend: () => import('./icons/chatSend.svg'),
delete: () => import('./icons/delete.svg'), delete: () => import('./icons/delete.svg'),
stop: () => import('./icons/stop.svg'), stop: () => import('./icons/stop.svg'),
collectionLight: () => import('./icons/collectionLight.svg'), collectionLight: () => import('./icons/collectionLight.svg'),
@@ -89,7 +86,6 @@ const iconPaths = {
moveLight: () => import('./icons/light/move.svg'), moveLight: () => import('./icons/light/move.svg'),
questionGuide: () => import('./icons/app/questionGuide.svg'), questionGuide: () => import('./icons/app/questionGuide.svg'),
loading: () => import('./icons/light/loading.svg'), loading: () => import('./icons/light/loading.svg'),
pause: () => import('./icons/common/pause.svg'),
'core/app/aiLight': () => import('./icons/core/app/aiLight.svg'), 'core/app/aiLight': () => import('./icons/core/app/aiLight.svg'),
'core/app/aiFill': () => import('./icons/core/app/aiFill.svg'), 'core/app/aiFill': () => import('./icons/core/app/aiFill.svg'),
'common/text/t': () => import('./icons/common/text/t.svg'), 'common/text/t': () => import('./icons/common/text/t.svg'),

View File

@@ -39,7 +39,7 @@ const MyModal = ({
minW={['90vw', '400px']} minW={['90vw', '400px']}
maxW={maxW} maxW={maxW}
position={'relative'} position={'relative'}
maxH={'90vh'} maxH={['80vh', '85vh']}
{...props} {...props}
> >
{!title && onClose && <ModalCloseButton zIndex={1} />} {!title && onClose && <ModalCloseButton zIndex={1} />}

View File

@@ -26,9 +26,9 @@ const ParentPaths = (props: {
return paths.length === 0 && !!FirstPathDom ? ( return paths.length === 0 && !!FirstPathDom ? (
<>{FirstPathDom}</> <>{FirstPathDom}</>
) : ( ) : (
<Flex flex={1}> <Flex flex={1} ml={-2}>
{concatPaths.map((item, i) => ( {concatPaths.map((item, i) => (
<Flex key={item.parentId} alignItems={'center'}> <Flex key={item.parentId || i} alignItems={'center'}>
<Box <Box
fontSize={['sm', 'lg']} fontSize={['sm', 'lg']}
py={1} py={1}
@@ -51,7 +51,7 @@ const ParentPaths = (props: {
{item.parentName} {item.parentName}
</Box> </Box>
{i !== concatPaths.length - 1 && ( {i !== concatPaths.length - 1 && (
<MyIcon name={'rightArrowLight'} color={'myGray.500'} w={['14px', '24px']} /> <MyIcon name={'rightArrowLight'} color={'myGray.500'} w={'14px'} />
)} )}
</Flex> </Flex>
))} ))}

View File

@@ -6,6 +6,7 @@ import { useTranslation } from 'next-i18next';
import { useSystemStore } from '@/web/common/system/useSystemStore'; import { useSystemStore } from '@/web/common/system/useSystemStore';
import { Box, Flex, ModalHeader } from '@chakra-ui/react'; import { Box, Flex, ModalHeader } from '@chakra-ui/react';
import MyIcon from '@/components/Icon'; import MyIcon from '@/components/Icon';
import ParentPaths from '@/components/common/ParentPaths';
type PathItemType = { type PathItemType = {
parentId: string; parentId: string;
@@ -14,7 +15,6 @@ type PathItemType = {
const DatasetSelectContainer = ({ const DatasetSelectContainer = ({
isOpen, isOpen,
parentId,
setParentId, setParentId,
paths, paths,
onClose, onClose,
@@ -22,7 +22,6 @@ const DatasetSelectContainer = ({
children children
}: { }: {
isOpen: boolean; isOpen: boolean;
parentId?: string;
setParentId: Dispatch<string>; setParentId: Dispatch<string>;
paths: PathItemType[]; paths: PathItemType[];
onClose: () => void; onClose: () => void;
@@ -35,45 +34,17 @@ const DatasetSelectContainer = ({
return ( return (
<MyModal isOpen={isOpen} onClose={onClose} w={'100%'} maxW={['90vw', '900px']} isCentered> <MyModal isOpen={isOpen} onClose={onClose} w={'100%'} maxW={['90vw', '900px']} isCentered>
<Flex flexDirection={'column'} h={'90vh'}> <Flex flexDirection={'column'} h={'90vh'}>
<ModalHeader> <ModalHeader fontWeight={'normal'}>
{!!parentId ? ( <ParentPaths
<Flex paths={paths.map((path, i) => ({
flex={1} parentId: path.parentId,
userSelect={'none'} parentName: path.parentName
fontSize={['sm', 'lg']} }))}
fontWeight={'normal'} FirstPathDom={t('chat.Select Mark Kb')}
color={'myGray.900'} onClick={(e) => {
> setParentId(e);
{paths.map((item, i) => ( }}
<Flex key={item.parentId} mr={2} alignItems={'center'}> />
<Box
fontSize={'lg'}
borderRadius={'md'}
{...(i === paths.length - 1
? {
cursor: 'default'
}
: {
cursor: 'pointer',
_hover: {
color: 'myBlue.600'
},
onClick: () => {
setParentId(item.parentId);
}
})}
>
{item.parentName}
</Box>
{i !== paths.length - 1 && (
<MyIcon name={'rightArrowLight'} color={'myGray.500'} w={['18px', '24px']} />
)}
</Flex>
))}
</Flex>
) : (
<Box>{t('chat.Select Mark Kb')}</Box>
)}
{!!tips && ( {!!tips && (
<Box fontSize={'sm'} color={'myGray.500'} fontWeight={'normal'}> <Box fontSize={'sm'} color={'myGray.500'} fontWeight={'normal'}>
{tips} {tips}
@@ -94,16 +65,7 @@ export function useDatasetSelect() {
Promise.all([getDatasets({ parentId }), getDatasetPaths(parentId)]) Promise.all([getDatasets({ parentId }), getDatasetPaths(parentId)])
); );
const paths = useMemo( const paths = useMemo(() => [...(data?.[1] || [])], [data]);
() => [
{
parentId: '',
parentName: t('dataset.My Dataset')
},
...(data?.[1] || [])
],
[data, t]
);
return { return {
parentId, parentId,

View File

@@ -26,6 +26,8 @@ import { useTranslation } from 'next-i18next';
import { useDatasetStore } from '@/web/core/dataset/store/dataset'; import { useDatasetStore } from '@/web/core/dataset/store/dataset';
import { feConfigs } from '@/web/common/system/staticData'; import { feConfigs } from '@/web/common/system/staticData';
import DatasetSelectContainer, { useDatasetSelect } from '@/components/core/dataset/SelectModal'; import DatasetSelectContainer, { useDatasetSelect } from '@/components/core/dataset/SelectModal';
import { useLoading } from '@/web/common/hooks/useLoading';
import EmptyTip from '@/components/EmptyTip';
export type KbParamsType = { export type KbParamsType = {
searchSimilarity: number; searchSimilarity: number;
@@ -54,7 +56,8 @@ export const DatasetSelectModal = ({
}) })
); );
const { toast } = useToast(); const { toast } = useToast();
const { paths, parentId, setParentId, datasets } = useDatasetSelect(); const { paths, setParentId, datasets, isLoading } = useDatasetSelect();
const { Loading } = useLoading();
const filterKbList = useMemo(() => { const filterKbList = useMemo(() => {
return { return {
@@ -71,9 +74,8 @@ export const DatasetSelectModal = ({
<DatasetSelectContainer <DatasetSelectContainer
isOpen={isOpen} isOpen={isOpen}
paths={paths} paths={paths}
parentId={parentId}
setParentId={setParentId} setParentId={setParentId}
tips={'仅能选择同一个索引模型的知识库'} tips={t('dataset.Select Dataset Tips')}
onClose={onClose} onClose={onClose}
> >
<Flex h={'100%'} flexDirection={'column'} flex={'1 0 0'}> <Flex h={'100%'} flexDirection={'column'} flex={'1 0 0'}>
@@ -158,7 +160,7 @@ export const DatasetSelectModal = ({
if (vectorModel && vectorModel !== item.vectorModel.model) { if (vectorModel && vectorModel !== item.vectorModel.model) {
return toast({ return toast({
status: 'warning', status: 'warning',
title: '仅能选择同一个索引模型的知识库' title: t('dataset.Select Dataset Tips')
}); });
} }
setSelectedKbList((state) => [ setSelectedKbList((state) => [
@@ -197,14 +199,7 @@ export const DatasetSelectModal = ({
})() })()
)} )}
</Grid> </Grid>
{filterKbList.unSelected.length === 0 && ( {filterKbList.unSelected.length === 0 && <EmptyTip text={t('common.folder.empty')} />}
<Flex mt={5} flexDirection={'column'} alignItems={'center'}>
<MyIcon name="empty" w={'48px'} h={'48px'} mt={'20vh'} color={'transparent'} />
<Box mt={2} color={'myGray.500'}>
西~
</Box>
</Flex>
)}
</ModalBody> </ModalBody>
<ModalFooter> <ModalFooter>
@@ -219,9 +214,11 @@ export const DatasetSelectModal = ({
onChange(filterKbList); onChange(filterKbList);
}} }}
> >
{t('common.Done')}
</Button> </Button>
</ModalFooter> </ModalFooter>
<Loading fixed={false} loading={isLoading} />
</Flex> </Flex>
</DatasetSelectContainer> </DatasetSelectContainer>
); );

View File

@@ -12,6 +12,8 @@ import { authCert } from '@fastgpt/service/support/permission/auth/common';
import { MongoDatasetData } from '@fastgpt/service/core/dataset/data/schema'; import { MongoDatasetData } from '@fastgpt/service/core/dataset/data/schema';
import { Types, connectionMongo } from '@fastgpt/service/common/mongo'; import { Types, connectionMongo } from '@fastgpt/service/common/mongo';
import { TeamMemberCollectionName } from '@fastgpt/global/support/user/team/constant'; import { TeamMemberCollectionName } from '@fastgpt/global/support/user/team/constant';
import { getUserDefaultTeam } from '@fastgpt/service/support/user/team/controller';
import { getGFSCollection } from '@fastgpt/service/common/file/gridfs/controller';
let success = 0; let success = 0;
/* pg 中的数据搬到 mongo dataset.datas 中,并做映射 */ /* pg 中的数据搬到 mongo dataset.datas 中,并做映射 */
@@ -22,9 +24,10 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
await connectToDatabase(); await connectToDatabase();
success = 0; success = 0;
jsonRes(res, { await init(limit);
data: await init(limit) await initCollectionFileTeam(limit);
});
jsonRes(res, {});
} catch (error) { } catch (error) {
console.log(error); console.log(error);
@@ -103,3 +106,71 @@ async function init(limit: number): Promise<any> {
} }
} }
} }
async function initCollectionFileTeam(limit: number) {
/* init user default Team */
const DatasetFile = getGFSCollection('dataset');
const matchWhere = {
$or: [{ 'metadata.teamId': { $exists: false } }, { 'metadata.teamId': null }]
};
const uniqueUsersWithNoTeamId = await DatasetFile.aggregate([
{
$match: matchWhere
},
{
$group: {
_id: '$metadata.userId', // 按 metadata.userId 分组以去重
userId: { $first: '$metadata.userId' } // 保留第一个出现的 userId
}
},
{
$project: {
_id: 0, // 不显示 _id 字段
userId: 1 // 只显示 userId 字段
}
}
]).toArray();
const users = uniqueUsersWithNoTeamId;
console.log('un init total', users.length);
// limit 组一次
const userArr: any[][] = [];
for (let i = 0; i < users.length; i += limit) {
userArr.push(users.slice(i, i + limit));
}
let success = 0;
for await (const item of userArr) {
await Promise.all(item.map((item) => init(item.userId)));
success += limit;
console.log(success);
}
async function init(userId: string): Promise<any> {
try {
const tmb = await getUserDefaultTeam({
userId
});
await DatasetFile.updateMany(
{
'metadata.userId': String(userId),
...matchWhere
},
{
$set: {
'metadata.teamId': String(tmb.teamId),
'metadata.tmbId': String(tmb.tmbId)
}
}
);
} catch (error) {
if (error === 'team not exist' || error === 'tmbId or userId is required') {
return;
}
console.log(error);
await delay(1000);
return init(userId);
}
}
}

View File

@@ -264,7 +264,7 @@ async function initCollectionFileTeam(limit: number) {
await DatasetFile.updateMany( await DatasetFile.updateMany(
{ {
userId, 'metadata.userId': String(userId),
...matchWhere ...matchWhere
}, },
{ {

View File

@@ -60,33 +60,30 @@ import PermissionIconText from '@/components/support/permission/IconText';
import QGSwitch from '../QGSwitch'; import QGSwitch from '../QGSwitch';
import TTSSelect from '../TTSSelect'; import TTSSelect from '../TTSSelect';
import { checkChatSupportSelectFileByModules } from '@/web/core/chat/utils'; import { checkChatSupportSelectFileByModules } from '@/web/core/chat/utils';
import { useSticky } from '@/web/common/hooks/useSticky';
const VariableEditModal = dynamic(() => import('@/components/core/module/VariableEditModal')); const VariableEditModal = dynamic(() => import('@/components/core/module/VariableEditModal'));
const InfoModal = dynamic(() => import('../InfoModal')); const InfoModal = dynamic(() => import('../InfoModal'));
const DatasetSelectModal = dynamic(() => import('@/components/core/module/DatasetSelectModal')); const DatasetSelectModal = dynamic(() => import('@/components/core/module/DatasetSelectModal'));
const AIChatSettingsModal = dynamic(() => import('@/components/core/module/AIChatSettingsModal')); const AIChatSettingsModal = dynamic(() => import('@/components/core/module/AIChatSettingsModal'));
const Settings = ({ appId }: { appId: string }) => { function ConfigForm({
divRef,
isSticky
}: {
divRef: React.RefObject<HTMLDivElement>;
isSticky: boolean;
}) {
const theme = useTheme(); const theme = useTheme();
const router = useRouter(); const router = useRouter();
const { t } = useTranslation();
const { toast } = useToast(); const { toast } = useToast();
const { t } = useTranslation();
const { appDetail, updateAppDetail } = useAppStore(); const { appDetail, updateAppDetail } = useAppStore();
const { loadAllDatasets, allDatasets } = useDatasetStore(); const { loadAllDatasets, allDatasets } = useDatasetStore();
const { isPc } = useSystemStore(); const { isPc } = useSystemStore();
const [editVariable, setEditVariable] = useState<VariableItemType>(); const [editVariable, setEditVariable] = useState<VariableItemType>();
const [settingAppInfo, setSettingAppInfo] = useState<AppSchema>();
const [refresh, setRefresh] = useState(false); const [refresh, setRefresh] = useState(false);
const { openConfirm: openConfirmSave, ConfirmModal: ConfirmSaveModal } = useConfirm({
content: t('app.Confirm Save App Tip'),
bg: appDetail.type === AppTypeEnum.basic ? '' : 'red.600'
});
const { openConfirm: openConfirmDel, ConfirmModal: ConfirmDelModal } = useConfirm({
content: t('app.Confirm Del App Tip')
});
const { register, setValue, getValues, reset, handleSubmit, control } = useForm<EditFormType>({ const { register, setValue, getValues, reset, handleSubmit, control } = useForm<EditFormType>({
defaultValues: getDefaultAppForm() defaultValues: getDefaultAppForm()
}); });
@@ -111,16 +108,21 @@ const Settings = ({ appId }: { appId: string }) => {
onClose: onCloseAIChatSetting onClose: onCloseAIChatSetting
} = useDisclosure(); } = useDisclosure();
const { const {
isOpen: isOpenKbSelect, isOpen: isOpenDatasetSelect,
onOpen: onOpenKbSelect, onOpen: onOpenKbSelect,
onClose: onCloseKbSelect onClose: onCloseKbSelect
} = useDisclosure(); } = useDisclosure();
const { const {
isOpen: isOpenKbParams, isOpen: isOpenDatasetParams,
onOpen: onOpenKbParams, onOpen: onOpenKbParams,
onClose: onCloseKbParams onClose: onCloseKbParams
} = useDisclosure(); } = useDisclosure();
const { openConfirm: openConfirmSave, ConfirmModal: ConfirmSaveModal } = useConfirm({
content: t('app.Confirm Save App Tip'),
bg: appDetail.type === AppTypeEnum.basic ? '' : 'red.600'
});
const chatModelSelectList = useMemo(() => { const chatModelSelectList = useMemo(() => {
return chatModelList.map((item) => ({ return chatModelList.map((item) => ({
value: item.model, value: item.model,
@@ -133,32 +135,6 @@ const Settings = ({ appId }: { appId: string }) => {
[allDatasets, datasets] [allDatasets, datasets]
); );
/* 点击删除 */
const { mutate: handleDelModel, isLoading } = useRequest({
mutationFn: async () => {
if (!appDetail) return null;
await delModelById(appDetail._id);
return 'success';
},
onSuccess(res) {
if (!res) return;
toast({
title: t('common.Delete Success'),
status: 'success'
});
router.replace(`/app/list`);
},
errorToast: t('common.Delete Failed')
});
const appModule2Form = useCallback(() => {
const formVal = appModules2Form(appDetail.modules);
reset(formVal);
setTimeout(() => {
setRefresh((state) => !state);
}, 100);
}, [appDetail.modules, reset]);
const { mutate: onSubmitSave, isLoading: isSaving } = useRequest({ const { mutate: onSubmitSave, isLoading: isSaving } = useRequest({
mutationFn: async (data: EditFormType) => { mutationFn: async (data: EditFormType) => {
const modules = appForm2Modules(data); const modules = appForm2Modules(data);
@@ -173,12 +149,20 @@ const Settings = ({ appId }: { appId: string }) => {
errorToast: t('common.Save Failed') errorToast: t('common.Save Failed')
}); });
const appModule2Form = useCallback(() => {
const formVal = appModules2Form(appDetail.modules);
reset(formVal);
setTimeout(() => {
setRefresh((state) => !state);
}, 100);
}, [appDetail.modules, reset]);
useQuery(['loadAllDatasets'], loadAllDatasets);
useEffect(() => { useEffect(() => {
appModule2Form(); appModule2Form();
}, [appModule2Form]); }, [appModule2Form]);
useQuery(['loadAllDatasets'], loadAllDatasets);
const BoxStyles: BoxProps = { const BoxStyles: BoxProps = {
bg: 'myWhite.200', bg: 'myWhite.200',
px: 4, px: 4,
@@ -202,110 +186,23 @@ const Settings = ({ appId }: { appId: string }) => {
}; };
return ( return (
<Box <Box mt={2}>
h={'100%'} {/* title */}
borderRight={'1.5px solid'} <Flex
borderColor={'myGray.200'} ref={divRef}
p={4} position={'sticky'}
pt={[0, 4]} top={-4}
pb={10} bg={'white'}
overflow={'overlay'}
>
<Flex alignItems={'flex-end'}>
<Box fontSize={['md', 'xl']} fontWeight={'bold'}>
<PermissionIconText permission={appDetail.permission} />
</Box>
<Box ml={1} color={'myGray.500'} fontSize={'sm'}>
(AppId:{' '}
<Box as={'span'} userSelect={'all'}>
{appId}
</Box>
)
</Box>
</Flex>
{/* basic info */}
<Box
border={theme.borders.base}
borderRadius={'lg'}
mt={2}
px={5}
py={4} py={4}
bg={'myBlue.100'} justifyContent={'space-between'}
position={'relative'} alignItems={'center'}
zIndex={10}
px={4}
{...(isSticky && {
borderBottom: theme.borders.base,
boxShadow: '0 2px 10px rgba(0,0,0,0.12)'
})}
> >
<Flex alignItems={'center'} py={2}>
<Avatar src={appDetail.avatar} borderRadius={'md'} w={'28px'} />
<Box ml={3} fontWeight={'bold'} fontSize={'lg'}>
{appDetail.name}
</Box>
{appDetail.isOwner && (
<IconButton
className="delete"
position={'absolute'}
top={4}
right={4}
size={'sm'}
icon={<MyIcon name={'delete'} w={'14px'} />}
variant={'base'}
borderRadius={'md'}
aria-label={'delete'}
_hover={{
bg: 'myGray.100',
color: 'red.600'
}}
isLoading={isLoading}
onClick={openConfirmDel(handleDelModel)}
/>
)}
</Flex>
<Box
flex={1}
my={2}
className={'textEllipsis3'}
wordBreak={'break-all'}
color={'myGray.600'}
>
{appDetail.intro || '快来给应用一个介绍~'}
</Box>
<Flex>
<Button
size={['sm', 'md']}
variant={'base'}
leftIcon={<MyIcon name={'chat'} w={'16px'} />}
onClick={() => router.push(`/chat?appId=${appId}`)}
>
</Button>
<Button
mx={3}
size={['sm', 'md']}
variant={'base'}
leftIcon={<MyIcon name={'shareLight'} w={'16px'} />}
onClick={() => {
router.replace({
query: {
appId,
currentTab: 'outLink'
}
});
}}
>
</Button>
{appDetail.isOwner && (
<Button
size={['sm', 'md']}
variant={'base'}
leftIcon={<MyIcon name={'settingLight'} w={'16px'} />}
onClick={() => setSettingAppInfo(appDetail)}
>
</Button>
)}
</Flex>
</Box>
<Flex mt={5} justifyContent={'space-between'} alignItems={'center'}>
<Box fontSize={['md', 'xl']} fontWeight={'bold'}> <Box fontSize={['md', 'xl']} fontWeight={'bold'}>
<MyTooltip label={'仅包含基础功能,复杂 agent 功能请使用高级编排。'} forceShow> <MyTooltip label={'仅包含基础功能,复杂 agent 功能请使用高级编排。'} forceShow>
@@ -329,8 +226,9 @@ const Settings = ({ appId }: { appId: string }) => {
</Button> </Button>
</Flex> </Flex>
<Box px={4}>
{/* welcome */} {/* welcome */}
<Box mt={5} {...BoxStyles}> <Box {...BoxStyles}>
<Flex alignItems={'center'}> <Flex alignItems={'center'}>
<Avatar src={'/imgs/module/userGuide.png'} w={'18px'} /> <Avatar src={'/imgs/module/userGuide.png'} w={'18px'} />
<Box mx={2}></Box> <Box mx={2}></Box>
@@ -346,6 +244,7 @@ const Settings = ({ appId }: { appId: string }) => {
{...register('guide.welcome.text')} {...register('guide.welcome.text')}
/> />
</Box> </Box>
{/* variable */} {/* variable */}
<Box mt={2} {...BoxStyles}> <Box mt={2} {...BoxStyles}>
<Flex alignItems={'center'}> <Flex alignItems={'center'}>
@@ -446,6 +345,7 @@ const Settings = ({ appId }: { appId: string }) => {
</Box> </Box>
<Textarea <Textarea
rows={5} rows={5}
minH={'60px'}
placeholder={ChatModelSystemTip} placeholder={ChatModelSystemTip}
borderColor={'myGray.100'} borderColor={'myGray.100'}
{...register('chatModel.systemPrompt')} {...register('chatModel.systemPrompt')}
@@ -511,6 +411,7 @@ const Settings = ({ appId }: { appId: string }) => {
</Grid> </Grid>
</Box> </Box>
{/* tts */}
<Box mt={5} {...BoxStyles}> <Box mt={5} {...BoxStyles}>
<TTSSelect <TTSSelect
value={getValues('tts')} value={getValues('tts')}
@@ -521,6 +422,7 @@ const Settings = ({ appId }: { appId: string }) => {
/> />
</Box> </Box>
{/* whisper */}
<Box mt={5} {...BoxStyles}> <Box mt={5} {...BoxStyles}>
<QGSwitch <QGSwitch
isChecked={getValues('questionGuide')} isChecked={getValues('questionGuide')}
@@ -532,12 +434,9 @@ const Settings = ({ appId }: { appId: string }) => {
}} }}
/> />
</Box> </Box>
</Box>
<ConfirmSaveModal /> <ConfirmSaveModal />
<ConfirmDelModal />
{settingAppInfo && (
<InfoModal defaultApp={settingAppInfo} onClose={() => setSettingAppInfo(undefined)} />
)}
{editVariable && ( {editVariable && (
<VariableEditModal <VariableEditModal
defaultVariable={editVariable} defaultVariable={editVariable}
@@ -573,9 +472,9 @@ const Settings = ({ appId }: { appId: string }) => {
defaultData={getValues('chatModel')} defaultData={getValues('chatModel')}
/> />
)} )}
{isOpenKbSelect && ( {isOpenDatasetSelect && (
<DatasetSelectModal <DatasetSelectModal
isOpen={isOpenKbSelect} isOpen={isOpenDatasetSelect}
activeDatasets={selectDatasets.map((item) => ({ activeDatasets={selectDatasets.map((item) => ({
datasetId: item._id, datasetId: item._id,
vectorModel: item.vectorModel vectorModel: item.vectorModel
@@ -584,8 +483,7 @@ const Settings = ({ appId }: { appId: string }) => {
onChange={replaceKbList} onChange={replaceKbList}
/> />
)} )}
{isOpenDatasetParams && (
{isOpenKbParams && (
<DatasetParamsModal <DatasetParamsModal
{...getValues('dataset')} {...getValues('dataset')}
onClose={onCloseKbParams} onClose={onCloseKbParams}
@@ -601,9 +499,156 @@ const Settings = ({ appId }: { appId: string }) => {
)} )}
</Box> </Box>
); );
}; }
const ChatTest = ({ appId }: { appId: string }) => { function Settings({ appId }: { appId: string }) {
const theme = useTheme();
const router = useRouter();
const { t } = useTranslation();
const { toast } = useToast();
const { parentRef, divRef, isSticky } = useSticky();
const { appDetail } = useAppStore();
const [settingAppInfo, setSettingAppInfo] = useState<AppSchema>();
const { openConfirm: openConfirmDel, ConfirmModal: ConfirmDelModal } = useConfirm({
content: t('app.Confirm Del App Tip')
});
/* 点击删除 */
const { mutate: handleDelModel, isLoading } = useRequest({
mutationFn: async () => {
if (!appDetail) return null;
await delModelById(appDetail._id);
return 'success';
},
onSuccess(res) {
if (!res) return;
toast({
title: t('common.Delete Success'),
status: 'success'
});
router.replace(`/app/list`);
},
errorToast: t('common.Delete Failed')
});
return (
<Box
ref={parentRef}
h={'100%'}
borderRight={'1.5px solid'}
borderColor={'myGray.200'}
pt={[0, 4]}
pb={10}
overflow={'overlay'}
>
<Box px={4}>
<Flex alignItems={'flex-end'}>
<Box fontSize={['md', 'xl']} fontWeight={'bold'}>
<PermissionIconText permission={appDetail.permission} />
</Box>
<Box ml={1} color={'myGray.500'} fontSize={'sm'}>
(AppId:{' '}
<Box as={'span'} userSelect={'all'}>
{appId}
</Box>
)
</Box>
</Flex>
{/* basic info */}
<Box
border={theme.borders.base}
borderRadius={'lg'}
mt={2}
px={5}
py={4}
bg={'myBlue.100'}
position={'relative'}
>
<Flex alignItems={'center'} py={2}>
<Avatar src={appDetail.avatar} borderRadius={'md'} w={'28px'} />
<Box ml={3} fontWeight={'bold'} fontSize={'lg'}>
{appDetail.name}
</Box>
{appDetail.isOwner && (
<IconButton
className="delete"
position={'absolute'}
top={4}
right={4}
size={'sm'}
icon={<MyIcon name={'delete'} w={'14px'} />}
variant={'base'}
borderRadius={'md'}
aria-label={'delete'}
_hover={{
bg: 'myGray.100',
color: 'red.600'
}}
isLoading={isLoading}
onClick={openConfirmDel(handleDelModel)}
/>
)}
</Flex>
<Box
flex={1}
my={2}
className={'textEllipsis3'}
wordBreak={'break-all'}
color={'myGray.600'}
>
{appDetail.intro || '快来给应用一个介绍~'}
</Box>
<Flex>
<Button
size={['sm', 'md']}
variant={'base'}
leftIcon={<MyIcon name={'chat'} w={'16px'} />}
onClick={() => router.push(`/chat?appId=${appId}`)}
>
</Button>
<Button
mx={3}
size={['sm', 'md']}
variant={'base'}
leftIcon={<MyIcon name={'shareLight'} w={'16px'} />}
onClick={() => {
router.replace({
query: {
appId,
currentTab: 'outLink'
}
});
}}
>
</Button>
{appDetail.isOwner && (
<Button
size={['sm', 'md']}
variant={'base'}
leftIcon={<MyIcon name={'settingLight'} w={'16px'} />}
onClick={() => setSettingAppInfo(appDetail)}
>
</Button>
)}
</Flex>
</Box>
</Box>
{/* config form */}
<ConfigForm divRef={divRef} isSticky={isSticky} />
<ConfirmDelModal />
{settingAppInfo && (
<InfoModal defaultApp={settingAppInfo} onClose={() => setSettingAppInfo(undefined)} />
)}
</Box>
);
}
function ChatTest({ appId }: { appId: string }) {
const { t } = useTranslation(); const { t } = useTranslation();
const { userInfo } = useUserStore(); const { userInfo } = useUserStore();
const { appDetail } = useAppStore(); const { appDetail } = useAppStore();
@@ -703,7 +748,7 @@ const ChatTest = ({ appId }: { appId: string }) => {
)} )}
</Flex> </Flex>
); );
}; }
const BasicEdit = ({ appId }: { appId: string }) => { const BasicEdit = ({ appId }: { appId: string }) => {
const { isPc } = useSystemStore(); const { isPc } = useSystemStore();

View File

@@ -99,7 +99,7 @@ const AppDetail = ({ currentTab }: { currentTab: `${TabEnum}` }) => {
display={['none', 'flex']} display={['none', 'flex']}
flexDirection={'column'} flexDirection={'column'}
p={4} p={4}
w={'200px'} w={'180px'}
borderRight={theme.borders.base} borderRight={theme.borders.base}
> >
<Flex mb={4} alignItems={'center'}> <Flex mb={4} alignItems={'center'}>

View File

@@ -40,6 +40,7 @@ import { useUserStore } from '@/web/support/user/useUserStore';
import PermissionIconText from '@/components/support/permission/IconText'; import PermissionIconText from '@/components/support/permission/IconText';
import { PermissionTypeEnum } from '@fastgpt/global/support/permission/constant'; import { PermissionTypeEnum } from '@fastgpt/global/support/permission/constant';
import { DatasetItemType } from '@fastgpt/global/core/dataset/type'; import { DatasetItemType } from '@fastgpt/global/core/dataset/type';
import ParentPaths from '@/components/common/ParentPaths';
const CreateModal = dynamic(() => import('./component/CreateModal'), { ssr: false }); const CreateModal = dynamic(() => import('./component/CreateModal'), { ssr: false });
const MoveModal = dynamic(() => import('./component/MoveModal'), { ssr: false }); const MoveModal = dynamic(() => import('./component/MoveModal'), { ssr: false });
@@ -113,64 +114,34 @@ const Kb = () => {
return Promise.all([loadDatasets(parentId), getDatasetPaths(parentId)]); return Promise.all([loadDatasets(parentId), getDatasetPaths(parentId)]);
}); });
const paths = useMemo( const paths = data?.[1] || [];
() => [
{
parentId: '',
parentName: t('dataset.My Dataset')
},
...(data?.[1] || [])
],
[data, t]
);
return ( return (
<PageContainer> <PageContainer>
<Flex pt={3} px={5} alignItems={'center'}> <Flex pt={3} px={5} alignItems={'center'}>
{/* url path */} {/* url path */}
{!!parentId ? ( <ParentPaths
<Flex flex={1}> paths={paths.map((path, i) => ({
{paths.map((item, i) => ( parentId: path.parentId,
<Flex key={item.parentId} mr={2} alignItems={'center'}> parentName: path.parentName
<Box }))}
fontSize={['sm', 'lg']} FirstPathDom={
px={[0, 2]}
py={1}
borderRadius={'md'}
{...(i === paths.length - 1
? {
cursor: 'default'
}
: {
cursor: 'pointer',
_hover: {
bg: 'myGray.100'
},
onClick: () => {
router.push({
query: {
parentId: item.parentId
}
});
}
})}
>
{item.parentName}
</Box>
{i !== paths.length - 1 && (
<MyIcon name={'rightArrowLight'} color={'myGray.500'} w={['18px', '24px']} />
)}
</Flex>
))}
</Flex>
) : (
<Flex flex={1} alignItems={'center'}> <Flex flex={1} alignItems={'center'}>
<Image src={'/imgs/module/db.png'} alt={''} mr={2} h={'24px'} /> <Image src={'/imgs/module/db.png'} alt={''} mr={2} h={'24px'} />
<Box className="textlg" letterSpacing={1} fontSize={'24px'} fontWeight={'bold'}> <Box className="textlg" letterSpacing={1} fontSize={'24px'} fontWeight={'bold'}>
{t('dataset.My Dataset')}
</Box> </Box>
</Flex> </Flex>
)} }
onClick={(e) => {
router.push({
query: {
parentId: e
}
});
}}
/>
{/* create icon */}
{userInfo?.team?.canWrite && ( {userInfo?.team?.canWrite && (
<MyMenu <MyMenu
offset={[-30, 10]} offset={[-30, 10]}

View File

@@ -0,0 +1,28 @@
import { useEffect, useRef, useState } from 'react';
export function useSticky(props?: { threshold?: number }) {
const { threshold = 20 } = props || {};
const parentRef = useRef<HTMLDivElement>(null);
const divRef = useRef<HTMLDivElement>(null);
const [isSticky, setIsSticky] = useState(false);
useEffect(() => {
const cb = () => {
if (!divRef.current) return;
const rect = divRef.current.getBoundingClientRect();
const isSticky = rect.top <= threshold;
setIsSticky(isSticky);
};
parentRef.current?.addEventListener('scroll', cb);
return () => {
parentRef.current?.removeEventListener('scroll', cb);
};
}, [threshold]);
return {
parentRef,
divRef,
isSticky
};
}