v4.6.1 (#497)
This commit is contained in:
@@ -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)`}
|
||||
borderRadius={['none', 'md']}
|
||||
bg={'white'}
|
||||
overflow={'hidden'}
|
||||
{...(isPc
|
||||
? {
|
||||
border: '1px solid',
|
||||
@@ -289,8 +290,8 @@ ${images.map((img) => JSON.stringify({ src: img.src })).join('\n')}
|
||||
onOpenSelectFile();
|
||||
}}
|
||||
>
|
||||
<MyTooltip label={t('core.chat.Select File')}>
|
||||
<MyIcon name={'core/chat/fileSelect'} />
|
||||
<MyTooltip label={t('core.chat.Select Image')}>
|
||||
<MyIcon name={'core/chat/fileSelect'} w={'18px'} color={'myGray.600'} />
|
||||
</MyTooltip>
|
||||
<File onSelect={onSelectFile} />
|
||||
</Flex>
|
||||
|
||||
@@ -69,12 +69,12 @@ const QuoteModal = ({
|
||||
isCentered
|
||||
minW={['90vw', '600px']}
|
||||
title={
|
||||
<>
|
||||
<Box>
|
||||
知识库引用({rawSearch.length}条)
|
||||
<Box fontSize={'10px'} color={'myGray.500'} fontWeight={'normal'}>
|
||||
注意: 修改知识库内容成功后,此处不会显示变更情况。点击编辑后,会显示知识库最新的内容。
|
||||
</Box>
|
||||
</>
|
||||
</Box>
|
||||
}
|
||||
>
|
||||
<ModalBody pt={0} whiteSpace={'pre-wrap'} textAlign={'justify'} wordBreak={'break-all'}>
|
||||
|
||||
@@ -45,7 +45,6 @@ const SelectMarkCollection = ({
|
||||
isOpen
|
||||
paths={paths}
|
||||
onClose={onClose}
|
||||
parentId={parentId}
|
||||
setParentId={setParentId}
|
||||
tips={t('chat.Select Mark Kb Desc')}
|
||||
>
|
||||
|
||||
@@ -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 |
@@ -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 |
@@ -3,10 +3,7 @@ import type { IconProps } from '@chakra-ui/react';
|
||||
import { Icon } from '@chakra-ui/react';
|
||||
|
||||
const iconPaths = {
|
||||
appFill: () => import('./icons/fill/app.svg'),
|
||||
appLight: () => import('./icons/light/app.svg'),
|
||||
copy: () => import('./icons/copy.svg'),
|
||||
chatSend: () => import('./icons/chatSend.svg'),
|
||||
delete: () => import('./icons/delete.svg'),
|
||||
stop: () => import('./icons/stop.svg'),
|
||||
collectionLight: () => import('./icons/collectionLight.svg'),
|
||||
@@ -89,7 +86,6 @@ const iconPaths = {
|
||||
moveLight: () => import('./icons/light/move.svg'),
|
||||
questionGuide: () => import('./icons/app/questionGuide.svg'),
|
||||
loading: () => import('./icons/light/loading.svg'),
|
||||
pause: () => import('./icons/common/pause.svg'),
|
||||
'core/app/aiLight': () => import('./icons/core/app/aiLight.svg'),
|
||||
'core/app/aiFill': () => import('./icons/core/app/aiFill.svg'),
|
||||
'common/text/t': () => import('./icons/common/text/t.svg'),
|
||||
|
||||
@@ -39,7 +39,7 @@ const MyModal = ({
|
||||
minW={['90vw', '400px']}
|
||||
maxW={maxW}
|
||||
position={'relative'}
|
||||
maxH={'90vh'}
|
||||
maxH={['80vh', '85vh']}
|
||||
{...props}
|
||||
>
|
||||
{!title && onClose && <ModalCloseButton zIndex={1} />}
|
||||
|
||||
@@ -26,9 +26,9 @@ const ParentPaths = (props: {
|
||||
return paths.length === 0 && !!FirstPathDom ? (
|
||||
<>{FirstPathDom}</>
|
||||
) : (
|
||||
<Flex flex={1}>
|
||||
<Flex flex={1} ml={-2}>
|
||||
{concatPaths.map((item, i) => (
|
||||
<Flex key={item.parentId} alignItems={'center'}>
|
||||
<Flex key={item.parentId || i} alignItems={'center'}>
|
||||
<Box
|
||||
fontSize={['sm', 'lg']}
|
||||
py={1}
|
||||
@@ -51,7 +51,7 @@ const ParentPaths = (props: {
|
||||
{item.parentName}
|
||||
</Box>
|
||||
{i !== concatPaths.length - 1 && (
|
||||
<MyIcon name={'rightArrowLight'} color={'myGray.500'} w={['14px', '24px']} />
|
||||
<MyIcon name={'rightArrowLight'} color={'myGray.500'} w={'14px'} />
|
||||
)}
|
||||
</Flex>
|
||||
))}
|
||||
|
||||
@@ -6,6 +6,7 @@ import { useTranslation } from 'next-i18next';
|
||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||
import { Box, Flex, ModalHeader } from '@chakra-ui/react';
|
||||
import MyIcon from '@/components/Icon';
|
||||
import ParentPaths from '@/components/common/ParentPaths';
|
||||
|
||||
type PathItemType = {
|
||||
parentId: string;
|
||||
@@ -14,7 +15,6 @@ type PathItemType = {
|
||||
|
||||
const DatasetSelectContainer = ({
|
||||
isOpen,
|
||||
parentId,
|
||||
setParentId,
|
||||
paths,
|
||||
onClose,
|
||||
@@ -22,7 +22,6 @@ const DatasetSelectContainer = ({
|
||||
children
|
||||
}: {
|
||||
isOpen: boolean;
|
||||
parentId?: string;
|
||||
setParentId: Dispatch<string>;
|
||||
paths: PathItemType[];
|
||||
onClose: () => void;
|
||||
@@ -35,45 +34,17 @@ const DatasetSelectContainer = ({
|
||||
return (
|
||||
<MyModal isOpen={isOpen} onClose={onClose} w={'100%'} maxW={['90vw', '900px']} isCentered>
|
||||
<Flex flexDirection={'column'} h={'90vh'}>
|
||||
<ModalHeader>
|
||||
{!!parentId ? (
|
||||
<Flex
|
||||
flex={1}
|
||||
userSelect={'none'}
|
||||
fontSize={['sm', 'lg']}
|
||||
fontWeight={'normal'}
|
||||
color={'myGray.900'}
|
||||
>
|
||||
{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>
|
||||
)}
|
||||
<ModalHeader fontWeight={'normal'}>
|
||||
<ParentPaths
|
||||
paths={paths.map((path, i) => ({
|
||||
parentId: path.parentId,
|
||||
parentName: path.parentName
|
||||
}))}
|
||||
FirstPathDom={t('chat.Select Mark Kb')}
|
||||
onClick={(e) => {
|
||||
setParentId(e);
|
||||
}}
|
||||
/>
|
||||
{!!tips && (
|
||||
<Box fontSize={'sm'} color={'myGray.500'} fontWeight={'normal'}>
|
||||
{tips}
|
||||
@@ -94,16 +65,7 @@ export function useDatasetSelect() {
|
||||
Promise.all([getDatasets({ parentId }), getDatasetPaths(parentId)])
|
||||
);
|
||||
|
||||
const paths = useMemo(
|
||||
() => [
|
||||
{
|
||||
parentId: '',
|
||||
parentName: t('dataset.My Dataset')
|
||||
},
|
||||
...(data?.[1] || [])
|
||||
],
|
||||
[data, t]
|
||||
);
|
||||
const paths = useMemo(() => [...(data?.[1] || [])], [data]);
|
||||
|
||||
return {
|
||||
parentId,
|
||||
|
||||
@@ -26,6 +26,8 @@ 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';
|
||||
|
||||
export type KbParamsType = {
|
||||
searchSimilarity: number;
|
||||
@@ -54,7 +56,8 @@ export const DatasetSelectModal = ({
|
||||
})
|
||||
);
|
||||
const { toast } = useToast();
|
||||
const { paths, parentId, setParentId, datasets } = useDatasetSelect();
|
||||
const { paths, setParentId, datasets, isLoading } = useDatasetSelect();
|
||||
const { Loading } = useLoading();
|
||||
|
||||
const filterKbList = useMemo(() => {
|
||||
return {
|
||||
@@ -71,9 +74,8 @@ export const DatasetSelectModal = ({
|
||||
<DatasetSelectContainer
|
||||
isOpen={isOpen}
|
||||
paths={paths}
|
||||
parentId={parentId}
|
||||
setParentId={setParentId}
|
||||
tips={'仅能选择同一个索引模型的知识库'}
|
||||
tips={t('dataset.Select Dataset Tips')}
|
||||
onClose={onClose}
|
||||
>
|
||||
<Flex h={'100%'} flexDirection={'column'} flex={'1 0 0'}>
|
||||
@@ -158,7 +160,7 @@ export const DatasetSelectModal = ({
|
||||
if (vectorModel && vectorModel !== item.vectorModel.model) {
|
||||
return toast({
|
||||
status: 'warning',
|
||||
title: '仅能选择同一个索引模型的知识库'
|
||||
title: t('dataset.Select Dataset Tips')
|
||||
});
|
||||
}
|
||||
setSelectedKbList((state) => [
|
||||
@@ -197,14 +199,7 @@ export const DatasetSelectModal = ({
|
||||
})()
|
||||
)}
|
||||
</Grid>
|
||||
{filterKbList.unSelected.length === 0 && (
|
||||
<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>
|
||||
)}
|
||||
{filterKbList.unSelected.length === 0 && <EmptyTip text={t('common.folder.empty')} />}
|
||||
</ModalBody>
|
||||
|
||||
<ModalFooter>
|
||||
@@ -219,9 +214,11 @@ export const DatasetSelectModal = ({
|
||||
onChange(filterKbList);
|
||||
}}
|
||||
>
|
||||
完成
|
||||
{t('common.Done')}
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
|
||||
<Loading fixed={false} loading={isLoading} />
|
||||
</Flex>
|
||||
</DatasetSelectContainer>
|
||||
);
|
||||
|
||||
@@ -12,6 +12,8 @@ import { authCert } from '@fastgpt/service/support/permission/auth/common';
|
||||
import { MongoDatasetData } from '@fastgpt/service/core/dataset/data/schema';
|
||||
import { Types, connectionMongo } from '@fastgpt/service/common/mongo';
|
||||
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;
|
||||
/* pg 中的数据搬到 mongo dataset.datas 中,并做映射 */
|
||||
@@ -22,9 +24,10 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
await connectToDatabase();
|
||||
success = 0;
|
||||
|
||||
jsonRes(res, {
|
||||
data: await init(limit)
|
||||
});
|
||||
await init(limit);
|
||||
await initCollectionFileTeam(limit);
|
||||
|
||||
jsonRes(res, {});
|
||||
} catch (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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -264,7 +264,7 @@ async function initCollectionFileTeam(limit: number) {
|
||||
|
||||
await DatasetFile.updateMany(
|
||||
{
|
||||
userId,
|
||||
'metadata.userId': String(userId),
|
||||
...matchWhere
|
||||
},
|
||||
{
|
||||
|
||||
@@ -60,33 +60,30 @@ import PermissionIconText from '@/components/support/permission/IconText';
|
||||
import QGSwitch from '../QGSwitch';
|
||||
import TTSSelect from '../TTSSelect';
|
||||
import { checkChatSupportSelectFileByModules } from '@/web/core/chat/utils';
|
||||
import { useSticky } from '@/web/common/hooks/useSticky';
|
||||
|
||||
const VariableEditModal = dynamic(() => import('@/components/core/module/VariableEditModal'));
|
||||
const InfoModal = dynamic(() => import('../InfoModal'));
|
||||
const DatasetSelectModal = dynamic(() => import('@/components/core/module/DatasetSelectModal'));
|
||||
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 router = useRouter();
|
||||
const { t } = useTranslation();
|
||||
const { toast } = useToast();
|
||||
const { t } = useTranslation();
|
||||
const { appDetail, updateAppDetail } = useAppStore();
|
||||
const { loadAllDatasets, allDatasets } = useDatasetStore();
|
||||
const { isPc } = useSystemStore();
|
||||
|
||||
const [editVariable, setEditVariable] = useState<VariableItemType>();
|
||||
const [settingAppInfo, setSettingAppInfo] = useState<AppSchema>();
|
||||
|
||||
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>({
|
||||
defaultValues: getDefaultAppForm()
|
||||
});
|
||||
@@ -111,16 +108,21 @@ const Settings = ({ appId }: { appId: string }) => {
|
||||
onClose: onCloseAIChatSetting
|
||||
} = useDisclosure();
|
||||
const {
|
||||
isOpen: isOpenKbSelect,
|
||||
isOpen: isOpenDatasetSelect,
|
||||
onOpen: onOpenKbSelect,
|
||||
onClose: onCloseKbSelect
|
||||
} = useDisclosure();
|
||||
const {
|
||||
isOpen: isOpenKbParams,
|
||||
isOpen: isOpenDatasetParams,
|
||||
onOpen: onOpenKbParams,
|
||||
onClose: onCloseKbParams
|
||||
} = useDisclosure();
|
||||
|
||||
const { openConfirm: openConfirmSave, ConfirmModal: ConfirmSaveModal } = useConfirm({
|
||||
content: t('app.Confirm Save App Tip'),
|
||||
bg: appDetail.type === AppTypeEnum.basic ? '' : 'red.600'
|
||||
});
|
||||
|
||||
const chatModelSelectList = useMemo(() => {
|
||||
return chatModelList.map((item) => ({
|
||||
value: item.model,
|
||||
@@ -133,32 +135,6 @@ const Settings = ({ appId }: { appId: string }) => {
|
||||
[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({
|
||||
mutationFn: async (data: EditFormType) => {
|
||||
const modules = appForm2Modules(data);
|
||||
@@ -173,12 +149,20 @@ const Settings = ({ appId }: { appId: string }) => {
|
||||
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(() => {
|
||||
appModule2Form();
|
||||
}, [appModule2Form]);
|
||||
|
||||
useQuery(['loadAllDatasets'], loadAllDatasets);
|
||||
|
||||
const BoxStyles: BoxProps = {
|
||||
bg: 'myWhite.200',
|
||||
px: 4,
|
||||
@@ -202,110 +186,23 @@ const Settings = ({ appId }: { appId: string }) => {
|
||||
};
|
||||
|
||||
return (
|
||||
<Box
|
||||
h={'100%'}
|
||||
borderRight={'1.5px solid'}
|
||||
borderColor={'myGray.200'}
|
||||
p={4}
|
||||
pt={[0, 4]}
|
||||
pb={10}
|
||||
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}
|
||||
<Box mt={2}>
|
||||
{/* title */}
|
||||
<Flex
|
||||
ref={divRef}
|
||||
position={'sticky'}
|
||||
top={-4}
|
||||
bg={'white'}
|
||||
py={4}
|
||||
bg={'myBlue.100'}
|
||||
position={'relative'}
|
||||
justifyContent={'space-between'}
|
||||
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'}>
|
||||
应用配置
|
||||
<MyTooltip label={'仅包含基础功能,复杂 agent 功能请使用高级编排。'} forceShow>
|
||||
@@ -329,215 +226,217 @@ const Settings = ({ appId }: { appId: string }) => {
|
||||
</Button>
|
||||
</Flex>
|
||||
|
||||
{/* welcome */}
|
||||
<Box mt={5} {...BoxStyles}>
|
||||
<Flex alignItems={'center'}>
|
||||
<Avatar src={'/imgs/module/userGuide.png'} w={'18px'} />
|
||||
<Box mx={2}>对话开场白</Box>
|
||||
<MyTooltip label={welcomeTextTip} forceShow>
|
||||
<QuestionOutlineIcon />
|
||||
</MyTooltip>
|
||||
</Flex>
|
||||
<Textarea
|
||||
mt={2}
|
||||
rows={5}
|
||||
placeholder={welcomeTextTip}
|
||||
borderColor={'myGray.100'}
|
||||
{...register('guide.welcome.text')}
|
||||
/>
|
||||
</Box>
|
||||
{/* variable */}
|
||||
<Box mt={2} {...BoxStyles}>
|
||||
<Flex alignItems={'center'}>
|
||||
<Avatar src={'/imgs/module/variable.png'} objectFit={'contain'} w={'18px'} />
|
||||
<Box ml={2} flex={1}>
|
||||
变量
|
||||
</Box>
|
||||
<Flex {...BoxBtnStyles} onClick={() => setEditVariable(addVariable())}>
|
||||
+ 新增
|
||||
</Flex>
|
||||
</Flex>
|
||||
{variables.length > 0 && (
|
||||
<Box
|
||||
mt={2}
|
||||
borderRadius={'lg'}
|
||||
overflow={'hidden'}
|
||||
borderWidth={'1px'}
|
||||
borderBottom="none"
|
||||
>
|
||||
<TableContainer>
|
||||
<Table bg={'white'}>
|
||||
<Thead>
|
||||
<Tr>
|
||||
<Th>变量名</Th>
|
||||
<Th>变量 key</Th>
|
||||
<Th>必填</Th>
|
||||
<Th></Th>
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody>
|
||||
{variables.map((item, index) => (
|
||||
<Tr key={item.id}>
|
||||
<Td>{item.label} </Td>
|
||||
<Td>{item.key}</Td>
|
||||
<Td>{item.required ? '✔' : ''}</Td>
|
||||
<Td>
|
||||
<MyIcon
|
||||
mr={3}
|
||||
name={'settingLight'}
|
||||
w={'16px'}
|
||||
cursor={'pointer'}
|
||||
onClick={() => setEditVariable(item)}
|
||||
/>
|
||||
<MyIcon
|
||||
name={'delete'}
|
||||
w={'16px'}
|
||||
cursor={'pointer'}
|
||||
onClick={() => removeVariable(index)}
|
||||
/>
|
||||
</Td>
|
||||
</Tr>
|
||||
))}
|
||||
</Tbody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
{/* ai */}
|
||||
<Box mt={5} {...BoxStyles}>
|
||||
<Flex alignItems={'center'}>
|
||||
<Avatar src={'/imgs/module/AI.png'} w={'18px'} />
|
||||
<Box ml={2} flex={1}>
|
||||
{t('app.AI Settings')}
|
||||
</Box>
|
||||
<Flex {...BoxBtnStyles} onClick={onOpenAIChatSetting}>
|
||||
<MyIcon mr={1} name={'settingLight'} w={'14px'} />
|
||||
{t('app.Open AI Advanced Settings')}
|
||||
</Flex>
|
||||
</Flex>
|
||||
|
||||
<Flex alignItems={'center'} mt={5}>
|
||||
<Box {...LabelStyles}>{t('core.ai.Model')}</Box>
|
||||
<Box flex={'1 0 0'}>
|
||||
<MySelect
|
||||
width={'100%'}
|
||||
value={getValues('chatModel.model')}
|
||||
list={chatModelSelectList}
|
||||
onchange={(val: any) => {
|
||||
setValue('chatModel.model', val);
|
||||
const maxToken =
|
||||
chatModelList.find((item) => item.model === getValues('chatModel.model'))
|
||||
?.maxResponse || 4000;
|
||||
const token = maxToken / 2;
|
||||
setValue('chatModel.maxToken', token);
|
||||
setRefresh(!refresh);
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
</Flex>
|
||||
<Flex mt={10} alignItems={'flex-start'}>
|
||||
<Box {...LabelStyles}>
|
||||
{t('core.ai.Prompt')}
|
||||
<MyTooltip label={ChatModelSystemTip} forceShow>
|
||||
<QuestionOutlineIcon display={['none', 'inline']} ml={1} />
|
||||
<Box px={4}>
|
||||
{/* welcome */}
|
||||
<Box {...BoxStyles}>
|
||||
<Flex alignItems={'center'}>
|
||||
<Avatar src={'/imgs/module/userGuide.png'} w={'18px'} />
|
||||
<Box mx={2}>对话开场白</Box>
|
||||
<MyTooltip label={welcomeTextTip} forceShow>
|
||||
<QuestionOutlineIcon />
|
||||
</MyTooltip>
|
||||
</Box>
|
||||
</Flex>
|
||||
<Textarea
|
||||
mt={2}
|
||||
rows={5}
|
||||
placeholder={ChatModelSystemTip}
|
||||
placeholder={welcomeTextTip}
|
||||
borderColor={'myGray.100'}
|
||||
{...register('chatModel.systemPrompt')}
|
||||
></Textarea>
|
||||
</Flex>
|
||||
</Box>
|
||||
{...register('guide.welcome.text')}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
{/* dataset */}
|
||||
<Box mt={5} {...BoxStyles}>
|
||||
<Flex alignItems={'center'}>
|
||||
<Flex alignItems={'center'} flex={1}>
|
||||
<Avatar src={'/imgs/module/db.png'} w={'18px'} />
|
||||
<Box ml={2}>{t('core.dataset.Choose Dataset')}</Box>
|
||||
{/* variable */}
|
||||
<Box mt={2} {...BoxStyles}>
|
||||
<Flex alignItems={'center'}>
|
||||
<Avatar src={'/imgs/module/variable.png'} objectFit={'contain'} w={'18px'} />
|
||||
<Box ml={2} flex={1}>
|
||||
变量
|
||||
</Box>
|
||||
<Flex {...BoxBtnStyles} onClick={() => setEditVariable(addVariable())}>
|
||||
+ 新增
|
||||
</Flex>
|
||||
</Flex>
|
||||
<Flex alignItems={'center'} mr={3} {...BoxBtnStyles} onClick={onOpenKbSelect}>
|
||||
<SmallAddIcon />
|
||||
{t('common.Choose')}
|
||||
</Flex>
|
||||
<Flex alignItems={'center'} {...BoxBtnStyles} onClick={onOpenKbParams}>
|
||||
<MyIcon name={'edit'} w={'14px'} mr={1} />
|
||||
{t('common.Params')}
|
||||
</Flex>
|
||||
</Flex>
|
||||
<Flex mt={1} color={'myGray.600'} fontSize={['sm', 'md']}>
|
||||
{t('core.dataset.Similarity')}: {getValues('dataset.searchSimilarity')},{' '}
|
||||
{t('core.dataset.Search Top K')}: {getValues('dataset.searchLimit')}
|
||||
{getValues('dataset.searchEmptyText') === ''
|
||||
? ''
|
||||
: t('core.dataset.Set Empty Result Tip')}
|
||||
</Flex>
|
||||
<Grid
|
||||
gridTemplateColumns={['repeat(2, minmax(0, 1fr))', 'repeat(3, minmax(0, 1fr))']}
|
||||
my={2}
|
||||
gridGap={[2, 4]}
|
||||
>
|
||||
{selectDatasets.map((item) => (
|
||||
<MyTooltip key={item._id} label={t('core.dataset.Read Dataset')}>
|
||||
<Flex
|
||||
overflow={'hidden'}
|
||||
alignItems={'center'}
|
||||
p={2}
|
||||
bg={'white'}
|
||||
boxShadow={'0 4px 8px -2px rgba(16,24,40,.1),0 2px 4px -2px rgba(16,24,40,.06)'}
|
||||
borderRadius={'md'}
|
||||
border={theme.borders.base}
|
||||
cursor={'pointer'}
|
||||
onClick={() =>
|
||||
router.push({
|
||||
pathname: '/dataset/detail',
|
||||
query: {
|
||||
datasetId: item._id
|
||||
}
|
||||
})
|
||||
}
|
||||
>
|
||||
<Avatar src={item.avatar} w={'18px'} mr={1} />
|
||||
<Box flex={'1 0 0'} w={0} className={'textEllipsis'} fontSize={'sm'}>
|
||||
{item.name}
|
||||
</Box>
|
||||
</Flex>
|
||||
</MyTooltip>
|
||||
))}
|
||||
</Grid>
|
||||
</Box>
|
||||
{variables.length > 0 && (
|
||||
<Box
|
||||
mt={2}
|
||||
borderRadius={'lg'}
|
||||
overflow={'hidden'}
|
||||
borderWidth={'1px'}
|
||||
borderBottom="none"
|
||||
>
|
||||
<TableContainer>
|
||||
<Table bg={'white'}>
|
||||
<Thead>
|
||||
<Tr>
|
||||
<Th>变量名</Th>
|
||||
<Th>变量 key</Th>
|
||||
<Th>必填</Th>
|
||||
<Th></Th>
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody>
|
||||
{variables.map((item, index) => (
|
||||
<Tr key={item.id}>
|
||||
<Td>{item.label} </Td>
|
||||
<Td>{item.key}</Td>
|
||||
<Td>{item.required ? '✔' : ''}</Td>
|
||||
<Td>
|
||||
<MyIcon
|
||||
mr={3}
|
||||
name={'settingLight'}
|
||||
w={'16px'}
|
||||
cursor={'pointer'}
|
||||
onClick={() => setEditVariable(item)}
|
||||
/>
|
||||
<MyIcon
|
||||
name={'delete'}
|
||||
w={'16px'}
|
||||
cursor={'pointer'}
|
||||
onClick={() => removeVariable(index)}
|
||||
/>
|
||||
</Td>
|
||||
</Tr>
|
||||
))}
|
||||
</Tbody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
<Box mt={5} {...BoxStyles}>
|
||||
<TTSSelect
|
||||
value={getValues('tts')}
|
||||
onChange={(e) => {
|
||||
setValue('tts', e);
|
||||
setRefresh((state) => !state);
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
{/* ai */}
|
||||
<Box mt={5} {...BoxStyles}>
|
||||
<Flex alignItems={'center'}>
|
||||
<Avatar src={'/imgs/module/AI.png'} w={'18px'} />
|
||||
<Box ml={2} flex={1}>
|
||||
{t('app.AI Settings')}
|
||||
</Box>
|
||||
<Flex {...BoxBtnStyles} onClick={onOpenAIChatSetting}>
|
||||
<MyIcon mr={1} name={'settingLight'} w={'14px'} />
|
||||
{t('app.Open AI Advanced Settings')}
|
||||
</Flex>
|
||||
</Flex>
|
||||
|
||||
<Box mt={5} {...BoxStyles}>
|
||||
<QGSwitch
|
||||
isChecked={getValues('questionGuide')}
|
||||
size={'lg'}
|
||||
onChange={(e) => {
|
||||
const value = e.target.checked;
|
||||
setValue('questionGuide', value);
|
||||
setRefresh((state) => !state);
|
||||
}}
|
||||
/>
|
||||
<Flex alignItems={'center'} mt={5}>
|
||||
<Box {...LabelStyles}>{t('core.ai.Model')}</Box>
|
||||
<Box flex={'1 0 0'}>
|
||||
<MySelect
|
||||
width={'100%'}
|
||||
value={getValues('chatModel.model')}
|
||||
list={chatModelSelectList}
|
||||
onchange={(val: any) => {
|
||||
setValue('chatModel.model', val);
|
||||
const maxToken =
|
||||
chatModelList.find((item) => item.model === getValues('chatModel.model'))
|
||||
?.maxResponse || 4000;
|
||||
const token = maxToken / 2;
|
||||
setValue('chatModel.maxToken', token);
|
||||
setRefresh(!refresh);
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
</Flex>
|
||||
<Flex mt={10} alignItems={'flex-start'}>
|
||||
<Box {...LabelStyles}>
|
||||
{t('core.ai.Prompt')}
|
||||
<MyTooltip label={ChatModelSystemTip} forceShow>
|
||||
<QuestionOutlineIcon display={['none', 'inline']} ml={1} />
|
||||
</MyTooltip>
|
||||
</Box>
|
||||
<Textarea
|
||||
rows={5}
|
||||
minH={'60px'}
|
||||
placeholder={ChatModelSystemTip}
|
||||
borderColor={'myGray.100'}
|
||||
{...register('chatModel.systemPrompt')}
|
||||
></Textarea>
|
||||
</Flex>
|
||||
</Box>
|
||||
|
||||
{/* dataset */}
|
||||
<Box mt={5} {...BoxStyles}>
|
||||
<Flex alignItems={'center'}>
|
||||
<Flex alignItems={'center'} flex={1}>
|
||||
<Avatar src={'/imgs/module/db.png'} w={'18px'} />
|
||||
<Box ml={2}>{t('core.dataset.Choose Dataset')}</Box>
|
||||
</Flex>
|
||||
<Flex alignItems={'center'} mr={3} {...BoxBtnStyles} onClick={onOpenKbSelect}>
|
||||
<SmallAddIcon />
|
||||
{t('common.Choose')}
|
||||
</Flex>
|
||||
<Flex alignItems={'center'} {...BoxBtnStyles} onClick={onOpenKbParams}>
|
||||
<MyIcon name={'edit'} w={'14px'} mr={1} />
|
||||
{t('common.Params')}
|
||||
</Flex>
|
||||
</Flex>
|
||||
<Flex mt={1} color={'myGray.600'} fontSize={['sm', 'md']}>
|
||||
{t('core.dataset.Similarity')}: {getValues('dataset.searchSimilarity')},{' '}
|
||||
{t('core.dataset.Search Top K')}: {getValues('dataset.searchLimit')}
|
||||
{getValues('dataset.searchEmptyText') === ''
|
||||
? ''
|
||||
: t('core.dataset.Set Empty Result Tip')}
|
||||
</Flex>
|
||||
<Grid
|
||||
gridTemplateColumns={['repeat(2, minmax(0, 1fr))', 'repeat(3, minmax(0, 1fr))']}
|
||||
my={2}
|
||||
gridGap={[2, 4]}
|
||||
>
|
||||
{selectDatasets.map((item) => (
|
||||
<MyTooltip key={item._id} label={t('core.dataset.Read Dataset')}>
|
||||
<Flex
|
||||
overflow={'hidden'}
|
||||
alignItems={'center'}
|
||||
p={2}
|
||||
bg={'white'}
|
||||
boxShadow={'0 4px 8px -2px rgba(16,24,40,.1),0 2px 4px -2px rgba(16,24,40,.06)'}
|
||||
borderRadius={'md'}
|
||||
border={theme.borders.base}
|
||||
cursor={'pointer'}
|
||||
onClick={() =>
|
||||
router.push({
|
||||
pathname: '/dataset/detail',
|
||||
query: {
|
||||
datasetId: item._id
|
||||
}
|
||||
})
|
||||
}
|
||||
>
|
||||
<Avatar src={item.avatar} w={'18px'} mr={1} />
|
||||
<Box flex={'1 0 0'} w={0} className={'textEllipsis'} fontSize={'sm'}>
|
||||
{item.name}
|
||||
</Box>
|
||||
</Flex>
|
||||
</MyTooltip>
|
||||
))}
|
||||
</Grid>
|
||||
</Box>
|
||||
|
||||
{/* tts */}
|
||||
<Box mt={5} {...BoxStyles}>
|
||||
<TTSSelect
|
||||
value={getValues('tts')}
|
||||
onChange={(e) => {
|
||||
setValue('tts', e);
|
||||
setRefresh((state) => !state);
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
{/* whisper */}
|
||||
<Box mt={5} {...BoxStyles}>
|
||||
<QGSwitch
|
||||
isChecked={getValues('questionGuide')}
|
||||
size={'lg'}
|
||||
onChange={(e) => {
|
||||
const value = e.target.checked;
|
||||
setValue('questionGuide', value);
|
||||
setRefresh((state) => !state);
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
<ConfirmSaveModal />
|
||||
<ConfirmDelModal />
|
||||
{settingAppInfo && (
|
||||
<InfoModal defaultApp={settingAppInfo} onClose={() => setSettingAppInfo(undefined)} />
|
||||
)}
|
||||
{editVariable && (
|
||||
<VariableEditModal
|
||||
defaultVariable={editVariable}
|
||||
@@ -573,9 +472,9 @@ const Settings = ({ appId }: { appId: string }) => {
|
||||
defaultData={getValues('chatModel')}
|
||||
/>
|
||||
)}
|
||||
{isOpenKbSelect && (
|
||||
{isOpenDatasetSelect && (
|
||||
<DatasetSelectModal
|
||||
isOpen={isOpenKbSelect}
|
||||
isOpen={isOpenDatasetSelect}
|
||||
activeDatasets={selectDatasets.map((item) => ({
|
||||
datasetId: item._id,
|
||||
vectorModel: item.vectorModel
|
||||
@@ -584,8 +483,7 @@ const Settings = ({ appId }: { appId: string }) => {
|
||||
onChange={replaceKbList}
|
||||
/>
|
||||
)}
|
||||
|
||||
{isOpenKbParams && (
|
||||
{isOpenDatasetParams && (
|
||||
<DatasetParamsModal
|
||||
{...getValues('dataset')}
|
||||
onClose={onCloseKbParams}
|
||||
@@ -601,9 +499,156 @@ const Settings = ({ appId }: { appId: string }) => {
|
||||
)}
|
||||
</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 { userInfo } = useUserStore();
|
||||
const { appDetail } = useAppStore();
|
||||
@@ -703,7 +748,7 @@ const ChatTest = ({ appId }: { appId: string }) => {
|
||||
)}
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
const BasicEdit = ({ appId }: { appId: string }) => {
|
||||
const { isPc } = useSystemStore();
|
||||
|
||||
@@ -99,7 +99,7 @@ const AppDetail = ({ currentTab }: { currentTab: `${TabEnum}` }) => {
|
||||
display={['none', 'flex']}
|
||||
flexDirection={'column'}
|
||||
p={4}
|
||||
w={'200px'}
|
||||
w={'180px'}
|
||||
borderRight={theme.borders.base}
|
||||
>
|
||||
<Flex mb={4} alignItems={'center'}>
|
||||
|
||||
@@ -40,6 +40,7 @@ import { useUserStore } from '@/web/support/user/useUserStore';
|
||||
import PermissionIconText from '@/components/support/permission/IconText';
|
||||
import { PermissionTypeEnum } from '@fastgpt/global/support/permission/constant';
|
||||
import { DatasetItemType } from '@fastgpt/global/core/dataset/type';
|
||||
import ParentPaths from '@/components/common/ParentPaths';
|
||||
|
||||
const CreateModal = dynamic(() => import('./component/CreateModal'), { ssr: false });
|
||||
const MoveModal = dynamic(() => import('./component/MoveModal'), { ssr: false });
|
||||
@@ -113,64 +114,34 @@ const Kb = () => {
|
||||
return Promise.all([loadDatasets(parentId), getDatasetPaths(parentId)]);
|
||||
});
|
||||
|
||||
const paths = useMemo(
|
||||
() => [
|
||||
{
|
||||
parentId: '',
|
||||
parentName: t('dataset.My Dataset')
|
||||
},
|
||||
...(data?.[1] || [])
|
||||
],
|
||||
[data, t]
|
||||
);
|
||||
const paths = data?.[1] || [];
|
||||
|
||||
return (
|
||||
<PageContainer>
|
||||
<Flex pt={3} px={5} alignItems={'center'}>
|
||||
{/* url path */}
|
||||
{!!parentId ? (
|
||||
<Flex flex={1}>
|
||||
{paths.map((item, i) => (
|
||||
<Flex key={item.parentId} mr={2} alignItems={'center'}>
|
||||
<Box
|
||||
fontSize={['sm', 'lg']}
|
||||
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'}>
|
||||
<Image src={'/imgs/module/db.png'} alt={''} mr={2} h={'24px'} />
|
||||
<Box className="textlg" letterSpacing={1} fontSize={'24px'} fontWeight={'bold'}>
|
||||
我的知识库
|
||||
</Box>
|
||||
</Flex>
|
||||
)}
|
||||
<ParentPaths
|
||||
paths={paths.map((path, i) => ({
|
||||
parentId: path.parentId,
|
||||
parentName: path.parentName
|
||||
}))}
|
||||
FirstPathDom={
|
||||
<Flex flex={1} alignItems={'center'}>
|
||||
<Image src={'/imgs/module/db.png'} alt={''} mr={2} h={'24px'} />
|
||||
<Box className="textlg" letterSpacing={1} fontSize={'24px'} fontWeight={'bold'}>
|
||||
{t('dataset.My Dataset')}
|
||||
</Box>
|
||||
</Flex>
|
||||
}
|
||||
onClick={(e) => {
|
||||
router.push({
|
||||
query: {
|
||||
parentId: e
|
||||
}
|
||||
});
|
||||
}}
|
||||
/>
|
||||
{/* create icon */}
|
||||
{userInfo?.team?.canWrite && (
|
||||
<MyMenu
|
||||
offset={[-30, 10]}
|
||||
|
||||
28
projects/app/src/web/common/hooks/useSticky.ts
Normal file
28
projects/app/src/web/common/hooks/useSticky.ts
Normal 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
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user