fix chat quote reader (#4125)

This commit is contained in:
heheer 2025-03-12 18:09:41 +08:00 committed by archer
parent 19f0f110e2
commit 8bc29e6527
No known key found for this signature in database
GPG Key ID: 4446499B846D4A9E
14 changed files with 167 additions and 125 deletions

View File

@ -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

View File

@ -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",

View File

@ -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": "查看详情",

View File

@ -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": "檢視詳細資料",

View File

@ -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 (

View File

@ -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} />

View File

@ -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}&currentTab=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}&currentTab=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>

View File

@ -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: [

View File

@ -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 && (

View File

@ -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

View File

@ -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%'}>

View File

@ -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 (
<>

View File

@ -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%'}>

View File

@ -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) || [])
]