fix chat quote reader (#4125)
This commit is contained in:
parent
d052d0de53
commit
c131c2a7dc
@ -211,7 +211,7 @@ export function useLinkedScroll<
|
||||
// 初始加载
|
||||
useEffect(() => {
|
||||
if (canLoadData) {
|
||||
// 重置初始滚动状态
|
||||
setInitialLoadDone(false);
|
||||
hasScrolledToInitial.current = false;
|
||||
|
||||
loadData({
|
||||
@ -225,10 +225,10 @@ export function useLinkedScroll<
|
||||
// 监听初始加载完成,执行初始滚动
|
||||
useEffect(() => {
|
||||
if (initialLoadDone && dataList.length > 0 && !hasScrolledToInitial.current) {
|
||||
hasScrolledToInitial.current = true;
|
||||
const foundIndex = dataList.findIndex((item) => item._id === initialId);
|
||||
|
||||
if (foundIndex >= 0) {
|
||||
hasScrolledToInitial.current = true;
|
||||
setTimeout(() => {
|
||||
scrollToItem(foundIndex);
|
||||
}, 200);
|
||||
@ -299,7 +299,6 @@ export function useLinkedScroll<
|
||||
setDataList,
|
||||
isLoading,
|
||||
loadData,
|
||||
initialLoadDone,
|
||||
ScrollData,
|
||||
itemRefs,
|
||||
scrollToItem
|
||||
|
||||
@ -457,6 +457,7 @@
|
||||
"core.chat.quote.Read Quote": "View Quote",
|
||||
"core.chat.quote.afterUpdate": "After update",
|
||||
"core.chat.quote.beforeUpdate": "Before update",
|
||||
"core.chat.quote.source": "From: {{source}}",
|
||||
"core.chat.response.Complete Response": "Complete Response",
|
||||
"core.chat.response.Extension model": "Question Optimization Model",
|
||||
"core.chat.response.Read complete response": "View Details",
|
||||
|
||||
@ -460,6 +460,7 @@
|
||||
"core.chat.quote.Read Quote": "查看引用",
|
||||
"core.chat.quote.afterUpdate": "更新后",
|
||||
"core.chat.quote.beforeUpdate": "更新前",
|
||||
"core.chat.quote.source": "来源:{{source}}",
|
||||
"core.chat.response.Complete Response": "完整响应",
|
||||
"core.chat.response.Extension model": "问题优化模型",
|
||||
"core.chat.response.Read complete response": "查看详情",
|
||||
|
||||
@ -456,6 +456,7 @@
|
||||
"core.chat.quote.Read Quote": "檢視引用",
|
||||
"core.chat.quote.afterUpdate": "更新後",
|
||||
"core.chat.quote.beforeUpdate": "更新前",
|
||||
"core.chat.quote.source": "來源:{{source}}",
|
||||
"core.chat.response.Complete Response": "完整回應",
|
||||
"core.chat.response.Extension model": "問題最佳化模型",
|
||||
"core.chat.response.Read complete response": "檢視詳細資料",
|
||||
|
||||
@ -1,35 +1,26 @@
|
||||
import React, { useState, useEffect, useRef } from 'react';
|
||||
import React from 'react';
|
||||
import { Box, Flex } from '@chakra-ui/react';
|
||||
import type { BoxProps } from '@chakra-ui/react';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
|
||||
interface Props extends BoxProps {
|
||||
externalTrigger?: Boolean;
|
||||
isFolded?: boolean;
|
||||
onFoldChange?: (isFolded: boolean) => void;
|
||||
}
|
||||
|
||||
const SideBar = (e?: Props) => {
|
||||
const {
|
||||
w = ['100%', '0 0 250px', '0 0 250px', '0 0 270px', '0 0 290px'],
|
||||
children,
|
||||
externalTrigger,
|
||||
isFolded = false,
|
||||
onFoldChange,
|
||||
...props
|
||||
} = e || {};
|
||||
|
||||
const [isFolded, setIsFolded] = useState(false);
|
||||
|
||||
const prevExternalTriggerRef = useRef<Boolean | undefined>(undefined);
|
||||
|
||||
useEffect(() => {
|
||||
if (externalTrigger && !prevExternalTriggerRef.current && !isFolded) {
|
||||
setIsFolded(true);
|
||||
}
|
||||
|
||||
prevExternalTriggerRef.current = externalTrigger;
|
||||
}, [externalTrigger, isFolded]);
|
||||
|
||||
const handleToggle = () => {
|
||||
const newFolded = !isFolded;
|
||||
setIsFolded(newFolded);
|
||||
if (onFoldChange) {
|
||||
onFoldChange(!isFolded);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
@ -77,20 +77,18 @@ const CollectionQuoteItem = ({
|
||||
}}
|
||||
>
|
||||
{updated && (
|
||||
<Flex
|
||||
position={'absolute'}
|
||||
top={2}
|
||||
right={5}
|
||||
gap={1}
|
||||
bg={'yellow.50'}
|
||||
color={'yellow.500'}
|
||||
px={2}
|
||||
py={1}
|
||||
rounded={'md'}
|
||||
fontSize={'12px'}
|
||||
>
|
||||
<MyIcon name="common/info" w={'14px'} color={'yellow.500'} />
|
||||
{t('common:core.dataset.data.Updated')}
|
||||
<Flex mt={2}>
|
||||
<Box
|
||||
bg={'green.50'}
|
||||
border={'1px solid'}
|
||||
borderRadius={'xs'}
|
||||
borderColor={'green.100'}
|
||||
px={1}
|
||||
color={'green.600'}
|
||||
>
|
||||
{t('common:core.dataset.data.Updated')}
|
||||
</Box>
|
||||
<Box flex={1} borderBottom={'1px dashed'} borderColor={'green.200'} />
|
||||
</Flex>
|
||||
)}
|
||||
<Markdown source={q} />
|
||||
|
||||
@ -18,7 +18,6 @@ import { GetCollectionQuoteDataProps } from '@/web/core/chat/context/chatItemCon
|
||||
import { useUserStore } from '@/web/support/user/useUserStore';
|
||||
import { getCollectionQuote } from '@/web/core/chat/api';
|
||||
import MyIconButton from '@fastgpt/web/components/common/Icon/button';
|
||||
import MyTooltip from '@fastgpt/web/components/common/MyTooltip';
|
||||
import MyBox from '@fastgpt/web/components/common/MyBox';
|
||||
import { getCollectionSourceAndOpen } from '@/web/core/dataset/hooks/readCollectionSource';
|
||||
import { QuoteDataItemType } from '@/service/core/chat/constants';
|
||||
@ -40,10 +39,10 @@ const CollectionReader = ({
|
||||
const [quoteIndex, setQuoteIndex] = useState(0);
|
||||
|
||||
// Get dataset permission
|
||||
const { data: permissionData, loading: isPermissionLoading } = useRequest2(
|
||||
const { data: datasetData, loading: isPermissionLoading } = useRequest2(
|
||||
async () => await getDatasetDataPermission(datasetId),
|
||||
{
|
||||
manual: !userInfo && !datasetId,
|
||||
manual: !userInfo || !datasetId,
|
||||
refreshDeps: [datasetId, userInfo]
|
||||
}
|
||||
);
|
||||
@ -167,21 +166,7 @@ const CollectionReader = ({
|
||||
>
|
||||
{sourceName || t('common:common.UnKnow Source')}
|
||||
</Box>
|
||||
{!!userInfo && permissionData?.permission?.hasReadPer && (
|
||||
<MyTooltip label={t('chat:to_dataset')}>
|
||||
<MyIconButton
|
||||
ml={3}
|
||||
icon="core/dataset/datasetLight"
|
||||
size="1rem"
|
||||
onClick={() => {
|
||||
router.push(
|
||||
`/dataset/detail?datasetId=${datasetId}¤tTab=dataCard&collectionId=${collectionId}`
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</MyTooltip>
|
||||
)}
|
||||
<Box ml={1}>
|
||||
<Box ml={3}>
|
||||
<DownloadButton
|
||||
canAccessRawData={true}
|
||||
onDownload={handleDownload}
|
||||
@ -196,69 +181,94 @@ const CollectionReader = ({
|
||||
onClick={onClose}
|
||||
/>
|
||||
</HStack>
|
||||
<Box fontSize={'mini'} color={'myGray.500'}>
|
||||
{t('common:core.chat.quote.Quote Tip')}
|
||||
</Box>
|
||||
{!isPermissionLoading && (
|
||||
<Box
|
||||
fontSize={'mini'}
|
||||
color={'myGray.500'}
|
||||
onClick={() => {
|
||||
if (!!userInfo && datasetData?.permission?.hasReadPer) {
|
||||
router.push(
|
||||
`/dataset/detail?datasetId=${datasetId}¤tTab=dataCard&collectionId=${collectionId}`
|
||||
);
|
||||
}
|
||||
}}
|
||||
{...(!!userInfo && datasetData?.permission?.hasReadPer
|
||||
? {
|
||||
cursor: 'pointer',
|
||||
_hover: { color: 'primary.600', textDecoration: 'underline' }
|
||||
}
|
||||
: {})}
|
||||
>
|
||||
{t('common:core.chat.quote.source', {
|
||||
source: datasetData?.datasetName
|
||||
})}
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
{/* header control */}
|
||||
{datasetDataList.length > 0 && (
|
||||
<Flex
|
||||
w={'full'}
|
||||
px={4}
|
||||
py={2}
|
||||
alignItems={'center'}
|
||||
borderBottom={'1px solid'}
|
||||
borderColor={'myGray.150'}
|
||||
>
|
||||
{/* 引用序号 */}
|
||||
<Flex fontSize={'mini'} mr={3} alignItems={'center'} gap={1}>
|
||||
<Box as={'span'} color={'myGray.900'}>
|
||||
{t('common:core.chat.Quote')} {quoteIndex + 1}
|
||||
</Box>
|
||||
<Box as={'span'} color={'myGray.500'}>
|
||||
/
|
||||
</Box>
|
||||
<Box as={'span'} color={'myGray.500'}>
|
||||
{filterResults.length}
|
||||
</Box>
|
||||
<Box>
|
||||
<Flex
|
||||
w={'full'}
|
||||
px={4}
|
||||
py={2}
|
||||
alignItems={'center'}
|
||||
borderBottom={'1px solid'}
|
||||
borderColor={'myGray.150'}
|
||||
>
|
||||
{/* 引用序号 */}
|
||||
<Flex fontSize={'mini'} mr={3} alignItems={'center'} gap={1}>
|
||||
<Box as={'span'} color={'myGray.900'}>
|
||||
{t('common:core.chat.Quote')} {quoteIndex + 1}
|
||||
</Box>
|
||||
<Box as={'span'} color={'myGray.500'}>
|
||||
/
|
||||
</Box>
|
||||
<Box as={'span'} color={'myGray.500'}>
|
||||
{filterResults.length}
|
||||
</Box>
|
||||
</Flex>
|
||||
|
||||
{/* 检索分数 */}
|
||||
{!loading &&
|
||||
(!isDeleted ? (
|
||||
<ScoreTag {...formatScore(currentQuoteItem?.score)} />
|
||||
) : (
|
||||
<Flex
|
||||
borderRadius={'sm'}
|
||||
py={1}
|
||||
px={2}
|
||||
color={'red.600'}
|
||||
bg={'red.50'}
|
||||
alignItems={'center'}
|
||||
fontSize={'11px'}
|
||||
>
|
||||
<MyIcon name="common/info" w={'14px'} mr={1} color={'red.600'} />
|
||||
{t('chat:chat.quote.deleted')}
|
||||
</Flex>
|
||||
))}
|
||||
|
||||
<Box flex={1} />
|
||||
|
||||
{/* 检索按钮 */}
|
||||
<Flex gap={1}>
|
||||
<NavButton
|
||||
direction="up"
|
||||
isDisabled={quoteIndex === 0}
|
||||
onClick={() => handleNavigate(quoteIndex - 1)}
|
||||
/>
|
||||
<NavButton
|
||||
direction="down"
|
||||
isDisabled={quoteIndex === filterResults.length - 1}
|
||||
onClick={() => handleNavigate(quoteIndex + 1)}
|
||||
/>
|
||||
</Flex>
|
||||
</Flex>
|
||||
|
||||
{/* 检索分数 */}
|
||||
{!loading &&
|
||||
(!isDeleted ? (
|
||||
<ScoreTag {...formatScore(currentQuoteItem?.score)} />
|
||||
) : (
|
||||
<Flex
|
||||
borderRadius={'sm'}
|
||||
py={1}
|
||||
px={2}
|
||||
color={'red.600'}
|
||||
bg={'red.50'}
|
||||
alignItems={'center'}
|
||||
fontSize={'11px'}
|
||||
>
|
||||
<MyIcon name="common/info" w={'14px'} mr={1} color={'red.600'} />
|
||||
{t('chat:chat.quote.deleted')}
|
||||
</Flex>
|
||||
))}
|
||||
|
||||
<Box flex={1} />
|
||||
|
||||
{/* 检索按钮 */}
|
||||
<Flex gap={1}>
|
||||
<NavButton
|
||||
direction="up"
|
||||
isDisabled={quoteIndex === 0}
|
||||
onClick={() => handleNavigate(quoteIndex - 1)}
|
||||
/>
|
||||
<NavButton
|
||||
direction="down"
|
||||
isDisabled={quoteIndex === filterResults.length - 1}
|
||||
onClick={() => handleNavigate(quoteIndex + 1)}
|
||||
/>
|
||||
</Flex>
|
||||
</Flex>
|
||||
<Box fontSize={'mini'} color={'myGray.500'} bg={'myGray.25'} px={4} py={1}>
|
||||
{t('common:core.chat.quote.Quote Tip')}
|
||||
</Box>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{/* quote list */}
|
||||
@ -282,7 +292,7 @@ const CollectionReader = ({
|
||||
a={item.a}
|
||||
dataId={item._id}
|
||||
collectionId={collectionId}
|
||||
canEdit={!!userInfo && !!permissionData?.permission?.hasWritePer}
|
||||
canEdit={!!userInfo && !!datasetData?.permission?.hasWritePer}
|
||||
/>
|
||||
))}
|
||||
</Flex>
|
||||
|
||||
@ -17,7 +17,17 @@ const DownloadButton = ({
|
||||
return (
|
||||
<MyMenu
|
||||
size={'xs'}
|
||||
Button={<MyIconButton icon="common/download" size={'1rem'} />}
|
||||
Button={
|
||||
<MyIconButton
|
||||
icon="common/download"
|
||||
size={'1rem'}
|
||||
border={'1px solid'}
|
||||
borderColor={'myGray.250'}
|
||||
boxShadow={
|
||||
'0px 1px 2px 0px rgba(19, 51, 107, 0.05), 0px 0px 1px 0px rgba(19, 51, 107, 0.08)'
|
||||
}
|
||||
/>
|
||||
}
|
||||
menuList={[
|
||||
{
|
||||
children: [
|
||||
|
||||
@ -1,9 +1,8 @@
|
||||
import React from 'react';
|
||||
import { SearchDataResponseItemType } from '@fastgpt/global/core/dataset/type';
|
||||
import { ChatItemContext, GetQuoteProps } from '@/web/core/chat/context/chatItemContext';
|
||||
import { GetQuoteProps } from '@/web/core/chat/context/chatItemContext';
|
||||
import CollectionQuoteReader from './CollectionQuoteReader';
|
||||
import QuoteReader from './QuoteReader';
|
||||
import { useContextSelector } from 'use-context-selector';
|
||||
|
||||
const ChatQuoteList = ({
|
||||
rawSearch = [],
|
||||
@ -14,8 +13,6 @@ const ChatQuoteList = ({
|
||||
metadata: GetQuoteProps;
|
||||
onClose: () => void;
|
||||
}) => {
|
||||
const isShowReadRawSource = useContextSelector(ChatItemContext, (v) => v.isShowReadRawSource);
|
||||
|
||||
return (
|
||||
<>
|
||||
{'collectionId' in metadata && (
|
||||
|
||||
@ -6,6 +6,7 @@ import { DatasetErrEnum } from '@fastgpt/global/common/error/code/dataset';
|
||||
|
||||
export type GetQuotePermissionResponse =
|
||||
| {
|
||||
datasetName: string;
|
||||
permission: {
|
||||
hasWritePer: boolean;
|
||||
hasReadPer: boolean;
|
||||
@ -22,7 +23,7 @@ async function handler(req: NextApiRequest): Promise<GetQuotePermissionResponse>
|
||||
}
|
||||
|
||||
try {
|
||||
const { permission } = await authDataset({
|
||||
const { permission, dataset } = await authDataset({
|
||||
req,
|
||||
authToken: true,
|
||||
authApiKey: true,
|
||||
@ -31,6 +32,7 @@ async function handler(req: NextApiRequest): Promise<GetQuotePermissionResponse>
|
||||
});
|
||||
|
||||
return {
|
||||
datasetName: dataset.name,
|
||||
permission: {
|
||||
hasReadPer: permission.hasReadPer,
|
||||
hasWritePer: permission.hasWritePer
|
||||
@ -39,6 +41,7 @@ async function handler(req: NextApiRequest): Promise<GetQuotePermissionResponse>
|
||||
} catch (error) {
|
||||
if (error === DatasetErrEnum.unAuthDataset) {
|
||||
return {
|
||||
datasetName: '',
|
||||
permission: {
|
||||
hasWritePer: false,
|
||||
hasReadPer: false
|
||||
|
||||
@ -65,6 +65,14 @@ const Chat = ({ myApps }: { myApps: AppListItemType[] }) => {
|
||||
const chatRecords = useContextSelector(ChatRecordContext, (v) => v.chatRecords);
|
||||
const totalRecordsCount = useContextSelector(ChatRecordContext, (v) => v.totalRecordsCount);
|
||||
|
||||
const [sidebarFolded, setSidebarFolded] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (quoteData) {
|
||||
setSidebarFolded(true);
|
||||
}
|
||||
}, [quoteData]);
|
||||
|
||||
// Load chat init data
|
||||
const { loading } = useRequest2(
|
||||
async () => {
|
||||
@ -148,7 +156,9 @@ const Chat = ({ myApps }: { myApps: AppListItemType[] }) => {
|
||||
);
|
||||
|
||||
return isPc || !appId ? (
|
||||
<SideBar externalTrigger={!!quoteData}>{Children}</SideBar>
|
||||
<SideBar isFolded={sidebarFolded} onFoldChange={setSidebarFolded}>
|
||||
{Children}
|
||||
</SideBar>
|
||||
) : (
|
||||
<Drawer
|
||||
isOpen={isOpenSlider}
|
||||
@ -161,7 +171,7 @@ const Chat = ({ myApps }: { myApps: AppListItemType[] }) => {
|
||||
<DrawerContent maxWidth={'75vw'}>{Children}</DrawerContent>
|
||||
</Drawer>
|
||||
);
|
||||
}, [t, isPc, appId, isOpenSlider, onCloseSlider, quoteData]);
|
||||
}, [t, isPc, appId, isOpenSlider, onCloseSlider, sidebarFolded]);
|
||||
|
||||
return (
|
||||
<Flex h={'100%'}>
|
||||
|
||||
@ -90,6 +90,14 @@ const OutLink = (props: Props) => {
|
||||
const totalRecordsCount = useContextSelector(ChatRecordContext, (v) => v.totalRecordsCount);
|
||||
const isChatRecordsLoaded = useContextSelector(ChatRecordContext, (v) => v.isChatRecordsLoaded);
|
||||
|
||||
const [sidebarFolded, setSidebarFolded] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (quoteData) {
|
||||
setSidebarFolded(true);
|
||||
}
|
||||
}, [quoteData]);
|
||||
|
||||
const initSign = useRef(false);
|
||||
const { data, loading } = useRequest2(
|
||||
async () => {
|
||||
@ -221,7 +229,9 @@ const OutLink = (props: Props) => {
|
||||
if (showHistory !== '1') return null;
|
||||
|
||||
return isPc ? (
|
||||
<SideBar externalTrigger={!!quoteData}>{Children}</SideBar>
|
||||
<SideBar isFolded={sidebarFolded} onFoldChange={setSidebarFolded}>
|
||||
{Children}
|
||||
</SideBar>
|
||||
) : (
|
||||
<Drawer
|
||||
isOpen={isOpenSlider}
|
||||
@ -236,7 +246,7 @@ const OutLink = (props: Props) => {
|
||||
</DrawerContent>
|
||||
</Drawer>
|
||||
);
|
||||
}, [isOpenSlider, isPc, onCloseSlider, quoteData, showHistory, t]);
|
||||
}, [isOpenSlider, isPc, onCloseSlider, showHistory, t, sidebarFolded]);
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import React, { useCallback, useEffect, useMemo } from 'react';
|
||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import NextHead from '@/components/common/NextHead';
|
||||
import { getTeamChatInfo } from '@/web/core/chat/api';
|
||||
import { useRouter } from 'next/router';
|
||||
@ -70,6 +70,14 @@ const Chat = ({ myApps }: { myApps: AppListItemType[] }) => {
|
||||
const chatRecords = useContextSelector(ChatRecordContext, (v) => v.chatRecords);
|
||||
const totalRecordsCount = useContextSelector(ChatRecordContext, (v) => v.totalRecordsCount);
|
||||
|
||||
const [sidebarFolded, setSidebarFolded] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (quoteData) {
|
||||
setSidebarFolded(true);
|
||||
}
|
||||
}, [quoteData]);
|
||||
|
||||
// get chat app info
|
||||
const { loading } = useRequest2(
|
||||
async () => {
|
||||
@ -166,7 +174,9 @@ const Chat = ({ myApps }: { myApps: AppListItemType[] }) => {
|
||||
);
|
||||
|
||||
return isPc || !appId ? (
|
||||
<SideBar externalTrigger={!!quoteData}>{Children}</SideBar>
|
||||
<SideBar isFolded={sidebarFolded} onFoldChange={setSidebarFolded}>
|
||||
{Children}
|
||||
</SideBar>
|
||||
) : (
|
||||
<Drawer
|
||||
isOpen={isOpenSlider}
|
||||
@ -179,7 +189,7 @@ const Chat = ({ myApps }: { myApps: AppListItemType[] }) => {
|
||||
<DrawerContent maxWidth={'75vw'}>{Children}</DrawerContent>
|
||||
</Drawer>
|
||||
);
|
||||
}, [appId, isOpenSlider, isPc, onCloseSlider, quoteData, t]);
|
||||
}, [appId, isOpenSlider, isPc, onCloseSlider, t, sidebarFolded]);
|
||||
|
||||
return (
|
||||
<Flex h={'100%'}>
|
||||
|
||||
@ -221,6 +221,7 @@ export async function updateData2Dataset({
|
||||
}
|
||||
|
||||
// 4. Update mongo updateTime(便于脏数据检查器识别)
|
||||
const updateTime = mongoData.updateTime;
|
||||
mongoData.updateTime = new Date();
|
||||
await mongoData.save();
|
||||
|
||||
@ -258,7 +259,7 @@ export async function updateData2Dataset({
|
||||
{
|
||||
q: mongoData.q,
|
||||
a: mongoData.a,
|
||||
updateTime: new Date()
|
||||
updateTime: updateTime
|
||||
},
|
||||
...(mongoData.history?.slice(0, 9) || [])
|
||||
]
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user