chat quote reader (#3912)

* init chat quote full text reader

* linked structure

* dataset data linked

* optimize code

* fix ts build

* test finish

* delete log

* fix

* fix ts

* fix ts

* remove nextId

* initial scroll

* fix

* fix
This commit is contained in:
heheer
2025-03-11 19:44:33 +08:00
committed by archer
parent 16832caaf6
commit ac7091f8d6
64 changed files with 2676 additions and 369 deletions

View File

@@ -0,0 +1,250 @@
import { NextAPI } from '@/service/middleware/entry';
import { authChatCrud, authCollectionInChat } from '@/service/support/permission/auth/chat';
import { DatasetDataSchemaType } from '@fastgpt/global/core/dataset/type';
import { MongoDatasetData } from '@fastgpt/service/core/dataset/data/schema';
import { ApiRequestProps } from '@fastgpt/service/type/next';
import { LinkedListResponse, LinkedPaginationProps } from '@fastgpt/web/common/fetch/type';
import { FilterQuery, Types } from 'mongoose';
import { dataFieldSelector, processChatTimeFilter } from './getQuote';
export type GetCollectionQuoteProps = LinkedPaginationProps & {
chatTime: Date;
isInitialLoad: boolean;
collectionId: string;
chatItemId: string;
appId: string;
chatId: string;
shareId?: string;
outLinkUid?: string;
teamId?: string;
teamToken?: string;
};
export type GetCollectionQuoteRes = LinkedListResponse<DatasetDataSchemaType>;
type BaseMatchType = FilterQuery<DatasetDataSchemaType>;
async function handler(
req: ApiRequestProps<GetCollectionQuoteProps>
): Promise<GetCollectionQuoteRes> {
const {
initialId,
initialIndex,
prevId,
prevIndex,
nextId,
nextIndex,
chatTime,
isInitialLoad,
collectionId,
chatItemId,
appId,
chatId,
shareId,
outLinkUid,
teamId,
teamToken,
pageSize = 15
} = req.body;
const limitedPageSize = Math.min(pageSize, 30);
await Promise.all([
authChatCrud({
req,
authToken: true,
appId,
chatId,
shareId,
outLinkUid,
teamId,
teamToken
}),
authCollectionInChat({ appId, chatId, chatItemId, collectionId })
]);
const baseMatch: BaseMatchType = {
collectionId,
$or: [
{ updateTime: { $lt: new Date(chatTime) } },
{ history: { $elemMatch: { updateTime: { $lt: new Date(chatTime) } } } }
]
};
if (initialId && initialIndex !== undefined) {
return await handleInitialLoad(
initialId,
initialIndex,
limitedPageSize,
chatTime,
chatItemId,
isInitialLoad,
baseMatch
);
}
if ((prevId && prevIndex !== undefined) || (nextId && nextIndex !== undefined)) {
return await handlePaginatedLoad(
prevId,
prevIndex,
nextId,
nextIndex,
limitedPageSize,
chatTime,
chatItemId,
baseMatch
);
}
return { list: [], hasMorePrev: false, hasMoreNext: false };
}
export default NextAPI(handler);
async function handleInitialLoad(
initialId: string,
initialIndex: number,
pageSize: number,
chatTime: Date,
chatItemId: string,
isInitialLoad: boolean,
baseMatch: BaseMatchType
): Promise<GetCollectionQuoteRes> {
const centerNode = await MongoDatasetData.findOne(
{
_id: new Types.ObjectId(initialId)
},
dataFieldSelector
).lean();
if (!centerNode) {
if (isInitialLoad) {
const list = await MongoDatasetData.find(baseMatch, dataFieldSelector)
.sort({ chunkIndex: 1, _id: -1 })
.limit(pageSize)
.lean();
const listRes = list.map((item, index) => ({
...item,
index: item.chunkIndex
}));
const hasMoreNext = list.length === pageSize;
return {
list: listRes,
hasMorePrev: false,
hasMoreNext
};
}
return Promise.reject('centerNode not found');
}
const prevHalfSize = Math.floor(pageSize / 2);
const nextHalfSize = pageSize - prevHalfSize - 1;
const { list: prevList, hasMore: hasMorePrev } = await getPrevNodes(
initialId,
initialIndex,
prevHalfSize,
baseMatch
);
const { list: nextList, hasMore: hasMoreNext } = await getNextNodes(
initialId,
initialIndex,
nextHalfSize,
baseMatch
);
const resultList = [...prevList, centerNode, ...nextList];
const list = processChatTimeFilter(resultList, chatTime);
return {
list: list.map((item) => ({
...item,
index: item.chunkIndex
})),
hasMorePrev,
hasMoreNext
};
}
async function handlePaginatedLoad(
prevId: string | undefined,
prevIndex: number | undefined,
nextId: string | undefined,
nextIndex: number | undefined,
pageSize: number,
chatTime: Date,
chatItemId: string,
baseMatch: BaseMatchType
): Promise<GetCollectionQuoteRes> {
const { list, hasMore } =
prevId && prevIndex !== undefined
? await getPrevNodes(prevId, prevIndex, pageSize, baseMatch)
: await getNextNodes(nextId!, nextIndex!, pageSize, baseMatch);
const processedList = processChatTimeFilter(list, chatTime);
return {
list: processedList.map((item) => ({
...item,
index: item.chunkIndex
})),
hasMorePrev: !!prevId && hasMore,
hasMoreNext: !!nextId && hasMore
};
}
async function getPrevNodes(
initialId: string,
initialIndex: number,
limit: number,
baseMatch: BaseMatchType
) {
const match: BaseMatchType = {
...baseMatch,
$or: [
{ chunkIndex: { $lte: initialIndex } },
{ chunkIndex: initialIndex, _id: { $lte: new Types.ObjectId(initialId) } }
]
};
const list = await MongoDatasetData.find(match, dataFieldSelector)
.sort({ chunkIndex: -1, _id: 1 })
.limit(limit)
.lean();
return {
list: list.filter((item) => String(item._id) !== initialId).reverse(),
hasMore: list.length === limit
};
}
async function getNextNodes(
initialId: string,
initialIndex: number,
limit: number,
baseMatch: BaseMatchType
) {
const match: BaseMatchType = {
...baseMatch,
$or: [
{ chunkIndex: { $gte: initialIndex } },
{ chunkIndex: initialIndex, _id: { $gte: new Types.ObjectId(initialId) } }
]
};
const list = await MongoDatasetData.find(match, dataFieldSelector)
.sort({ chunkIndex: 1, _id: -1 })
.limit(limit)
.lean();
return {
list: list.filter((item) => String(item._id) !== initialId),
hasMore: list.length === limit
};
}

View File

@@ -0,0 +1,102 @@
import { NextAPI } from '@/service/middleware/entry';
import { authChatCrud, authCollectionInChat } from '@/service/support/permission/auth/chat';
import { DatasetDataSchemaType } from '@fastgpt/global/core/dataset/type';
import { MongoDatasetData } from '@fastgpt/service/core/dataset/data/schema';
import { ApiRequestProps } from '@fastgpt/service/type/next';
export type GetQuoteDataProps = {
datasetDataIdList: string[];
chatTime: Date;
collectionIdList: string[];
chatItemId: string;
appId: string;
chatId: string;
shareId?: string;
outLinkUid?: string;
teamId?: string;
teamToken?: string;
};
export type GetQuoteDataRes = {
quoteList: DatasetDataSchemaType[];
};
export const dataFieldSelector =
'_id datasetId collectionId q a chunkIndex history updateTime currentChatItemId prevId';
async function handler(req: ApiRequestProps<GetQuoteDataProps>): Promise<GetQuoteDataRes> {
const {
datasetDataIdList,
chatTime,
collectionIdList,
chatItemId,
chatId,
appId,
shareId,
outLinkUid,
teamId,
teamToken
} = req.body;
await authChatCrud({
req,
authToken: true,
appId,
chatId,
shareId,
outLinkUid,
teamId,
teamToken
});
await Promise.all(
collectionIdList.map(async (collectionId) => {
await authCollectionInChat({ appId, chatId, chatItemId, collectionId });
})
);
const list = await MongoDatasetData.find(
{ _id: { $in: datasetDataIdList } },
dataFieldSelector
).lean();
const quoteList = processChatTimeFilter(list, chatTime);
return {
quoteList
};
}
export default NextAPI(handler);
export function processChatTimeFilter(list: DatasetDataSchemaType[], chatTime?: Date) {
if (!chatTime) return list;
return list.map((item) => {
if (!item.history) return item;
const { history, ...rest } = item;
const formatedChatTime = new Date(chatTime);
if (item.updateTime <= formatedChatTime) {
return rest;
}
const latestHistoryIndex = history.findIndex(
(historyItem: any) => historyItem.updateTime <= formatedChatTime
);
if (latestHistoryIndex === -1) return rest;
const latestHistory = history[latestHistoryIndex];
return {
...rest,
q: latestHistory?.q || item.q,
a: latestHistory?.a || item.a,
updated: true
};
});
}

View File

@@ -0,0 +1,78 @@
import { NextAPI } from '@/service/middleware/entry';
import { ReadPermissionVal } from '@fastgpt/global/support/permission/constant';
import { useIPFrequencyLimit } from '@fastgpt/service/common/middle/reqFrequencyLimit';
import { readFromSecondary } from '@fastgpt/service/common/mongo/utils';
import { responseWriteController } from '@fastgpt/service/common/response';
import { addLog } from '@fastgpt/service/common/system/log';
import { MongoDatasetData } from '@fastgpt/service/core/dataset/data/schema';
import { authDatasetCollection } from '@fastgpt/service/support/permission/dataset/auth';
import { ApiRequestProps } from '@fastgpt/service/type/next';
import { NextApiResponse } from 'next';
export type ExportCollectionBody = {
collectionId: string;
chatTime: Date;
};
async function handler(req: ApiRequestProps<ExportCollectionBody, {}>, res: NextApiResponse) {
let { collectionId, chatTime } = req.body;
const { teamId, collection } = await authDatasetCollection({
req,
authToken: true,
collectionId,
per: ReadPermissionVal
});
const where = {
teamId,
datasetId: collection.datasetId,
collectionId,
...(chatTime
? {
$or: [
{ updateTime: { $lt: new Date(chatTime) } },
{ history: { $elemMatch: { updateTime: { $lt: new Date(chatTime) } } } }
]
}
: {})
};
res.setHeader('Content-Type', 'text/csv; charset=utf-8;');
res.setHeader('Content-Disposition', 'attachment; filename=usage.csv; ');
const cursor = MongoDatasetData.find(where, 'q a', {
...readFromSecondary,
batchSize: 1000
})
.sort({ chunkIndex: 1 })
.limit(50000)
.cursor();
const write = responseWriteController({
res,
readStream: cursor
});
cursor.on('data', (doc) => {
const res = doc.a ? `\n${doc.q}\n${doc.a}` : `\n${doc.q}`;
write(res);
});
cursor.on('end', () => {
cursor.close();
res.end();
});
cursor.on('error', (err) => {
addLog.error(`export usage error`, err);
res.status(500);
res.end();
});
}
export default NextAPI(
useIPFrequencyLimit({ id: 'export-usage', seconds: 60, limit: 1, force: true }),
handler
);

View File

@@ -7,9 +7,7 @@ import { BucketNameEnum, ReadFileBaseUrl } from '@fastgpt/global/common/file/con
import { ReadPermissionVal } from '@fastgpt/global/support/permission/constant';
import { OutLinkChatAuthProps } from '@fastgpt/global/support/permission/chat';
import { DatasetErrEnum } from '@fastgpt/global/common/error/code/dataset';
import { MongoChatItem } from '@fastgpt/service/core/chat/chatItemSchema';
import { AIChatItemType, ChatHistoryItemResType } from '@fastgpt/global/core/chat/type';
import { authChatCrud } from '@/service/support/permission/auth/chat';
import { authChatCrud, authCollectionInChat } from '@/service/support/permission/auth/chat';
import { getCollectionWithDataset } from '@fastgpt/service/core/dataset/controller';
import { useApiDatasetRequest } from '@fastgpt/service/core/dataset/apiDataset/api';
import { POST } from '@fastgpt/service/common/api/plusRequest';
@@ -29,57 +27,6 @@ export type readCollectionSourceResponse = {
value: string;
};
const authCollectionInChat = async ({
collectionId,
appId,
chatId,
chatItemId
}: {
collectionId: string;
appId: string;
chatId: string;
chatItemId: string;
}) => {
try {
const chatItem = (await MongoChatItem.findOne(
{
appId,
chatId,
dataId: chatItemId
},
'responseData'
).lean()) as AIChatItemType;
if (!chatItem) return Promise.reject(DatasetErrEnum.unAuthDatasetFile);
// 找 responseData 里,是否有该文档 id
const responseData = chatItem.responseData || [];
const flatResData: ChatHistoryItemResType[] =
responseData
?.map((item) => {
return [
item,
...(item.pluginDetail || []),
...(item.toolDetail || []),
...(item.loopDetail || [])
];
})
.flat() || [];
if (
flatResData.some((item) => {
if (item.quoteList) {
return item.quoteList.some((quote) => quote.collectionId === collectionId);
}
return false;
})
) {
return true;
}
} catch (error) {}
return Promise.reject(DatasetErrEnum.unAuthDatasetFile);
};
async function handler(
req: ApiRequestProps<readCollectionSourceBody, readCollectionSourceQuery>
): Promise<readCollectionSourceResponse> {

View File

@@ -0,0 +1,53 @@
import type { NextApiRequest } from 'next';
import { NextAPI } from '@/service/middleware/entry';
import { ReadPermissionVal } from '@fastgpt/global/support/permission/constant';
import { authDataset } from '@fastgpt/service/support/permission/dataset/auth';
import { DatasetErrEnum } from '@fastgpt/global/common/error/code/dataset';
export type GetQuotePermissionResponse =
| {
permission: {
hasWritePer: boolean;
hasReadPer: boolean;
};
}
| undefined;
async function handler(req: NextApiRequest): Promise<GetQuotePermissionResponse> {
const { id: datasetId } = req.query as {
id?: string;
};
if (!datasetId) {
return Promise.reject('datasetId is required');
}
try {
const { permission } = await authDataset({
req,
authToken: true,
authApiKey: true,
datasetId,
per: ReadPermissionVal
});
return {
permission: {
hasReadPer: permission.hasReadPer,
hasWritePer: permission.hasWritePer
}
};
} catch (error) {
if (error === DatasetErrEnum.unAuthDataset) {
return {
permission: {
hasWritePer: false,
hasReadPer: false
}
};
}
return Promise.reject(error);
}
}
export default NextAPI(handler);

View File

@@ -45,7 +45,7 @@ async function handler(
const [list, total] = await Promise.all([
MongoDatasetData.find(match, '_id datasetId collectionId q a chunkIndex')
.sort({ chunkIndex: 1, updateTime: -1 })
.sort({ chunkIndex: 1, _id: -1 })
.skip(offset)
.limit(pageSize)
.lean(),

View File

@@ -37,6 +37,7 @@ async function handler(
responseDetail,
showRawSource,
showNodeStatus,
// showFullText,
limit,
app
});

View File

@@ -59,6 +59,7 @@ import { getWorkflowResponseWrite } from '@fastgpt/service/core/workflow/dispatc
import { WORKFLOW_MAX_RUN_TIMES } from '@fastgpt/service/core/workflow/constants';
import { getPluginInputsFromStoreNodes } from '@fastgpt/global/core/app/plugin/utils';
import { ExternalProviderType } from '@fastgpt/global/core/workflow/runtime/type';
import { FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
type FastGptWebChatProps = {
chatId?: string; // undefined: get histories from messages, '': new chat, 'xxxxx': get histories from db

View File

@@ -1,4 +1,4 @@
import React, { useCallback, useEffect, useMemo } from 'react';
import React, { useCallback, useEffect, useMemo, useState, useRef } from 'react';
import NextHead from '@/components/common/NextHead';
import { useRouter } from 'next/router';
import { getInitChatInfo } from '@/web/core/chat/api';
@@ -36,6 +36,7 @@ import ChatItemContextProvider, { ChatItemContext } from '@/web/core/chat/contex
import ChatRecordContextProvider, {
ChatRecordContext
} from '@/web/core/chat/context/chatRecordContext';
import ChatQuoteList from '@/pageComponents/chat/ChatQuoteList';
const CustomPluginRunBox = dynamic(() => import('@/pageComponents/chat/CustomPluginRunBox'));
@@ -58,6 +59,8 @@ const Chat = ({ myApps }: { myApps: AppListItemType[] }) => {
const isPlugin = useContextSelector(ChatItemContext, (v) => v.isPlugin);
const chatBoxData = useContextSelector(ChatItemContext, (v) => v.chatBoxData);
const setChatBoxData = useContextSelector(ChatItemContext, (v) => v.setChatBoxData);
const quoteData = useContextSelector(ChatItemContext, (v) => v.quoteData);
const setQuoteData = useContextSelector(ChatItemContext, (v) => v.setQuoteData);
const chatRecords = useContextSelector(ChatRecordContext, (v) => v.chatRecords);
const totalRecordsCount = useContextSelector(ChatRecordContext, (v) => v.totalRecordsCount);
@@ -138,13 +141,14 @@ const Chat = ({ myApps }: { myApps: AppListItemType[] }) => {
},
[appId, chatId, onUpdateHistoryTitle, setChatBoxData, forbidLoadChat]
);
const RenderHistorySlider = useMemo(() => {
const Children = (
<ChatHistorySlider confirmClearText={t('common:core.chat.Confirm to clear history')} />
);
return isPc || !appId ? (
<SideBar>{Children}</SideBar>
<SideBar externalTrigger={!!quoteData}>{Children}</SideBar>
) : (
<Drawer
isOpen={isOpenSlider}
@@ -157,7 +161,7 @@ const Chat = ({ myApps }: { myApps: AppListItemType[] }) => {
<DrawerContent maxWidth={'75vw'}>{Children}</DrawerContent>
</Drawer>
);
}, [appId, isOpenSlider, isPc, onCloseSlider, t]);
}, [t, isPc, appId, isOpenSlider, onCloseSlider, quoteData]);
return (
<Flex h={'100%'}>
@@ -169,7 +173,14 @@ const Chat = ({ myApps }: { myApps: AppListItemType[] }) => {
</Box>
)}
<PageContainer isLoading={loading} flex={'1 0 0'} w={0} p={[0, '16px']} position={'relative'}>
<PageContainer
isLoading={loading}
flex={'1 0 0'}
w={0}
p={[0, '16px']}
pr={quoteData ? '8px !important' : '16px'}
position={'relative'}
>
<Flex h={'100%'} flexDirection={['column', 'row']}>
{/* pc always show history. */}
{RenderHistorySlider}
@@ -215,6 +226,16 @@ const Chat = ({ myApps }: { myApps: AppListItemType[] }) => {
</Flex>
</Flex>
</PageContainer>
{quoteData && (
<PageContainer w={['full', '588px']} insertProps={{ bg: 'white' }}>
<ChatQuoteList
chatTime={quoteData.chatTime}
rawSearch={quoteData.rawSearch}
metadata={quoteData.metadata}
onClose={() => setQuoteData(undefined)}
/>
</PageContainer>
)}
</Flex>
);
};
@@ -278,6 +299,7 @@ const Render = (props: { appId: string; isStandalone?: string }) => {
showRouteToAppDetail={isStandalone !== '1'}
showRouteToDatasetDetail={isStandalone !== '1'}
isShowReadRawSource={true}
// isShowFullText={true}
showNodeStatus
>
<ChatRecordContextProvider params={chatRecordProviderParams}>

View File

@@ -37,6 +37,7 @@ import { useChatStore } from '@/web/core/chat/context/useChatStore';
import { ChatSourceEnum } from '@fastgpt/global/core/chat/constants';
import { useI18nLng } from '@fastgpt/web/hooks/useI18n';
import { AppSchema } from '@fastgpt/global/core/app/type';
import ChatQuoteList from '@/pageComponents/chat/ChatQuoteList';
const CustomPluginRunBox = dynamic(() => import('@/pageComponents/chat/CustomPluginRunBox'));
@@ -49,6 +50,7 @@ type Props = {
authToken: string;
customUid: string;
showRawSource: boolean;
// showFullText: boolean;
showNodeStatus: boolean;
};
@@ -81,6 +83,8 @@ const OutLink = (props: Props) => {
const resetVariables = useContextSelector(ChatItemContext, (v) => v.resetVariables);
const isPlugin = useContextSelector(ChatItemContext, (v) => v.isPlugin);
const setChatBoxData = useContextSelector(ChatItemContext, (v) => v.setChatBoxData);
const quoteData = useContextSelector(ChatItemContext, (v) => v.quoteData);
const setQuoteData = useContextSelector(ChatItemContext, (v) => v.setQuoteData);
const chatRecords = useContextSelector(ChatRecordContext, (v) => v.chatRecords);
const totalRecordsCount = useContextSelector(ChatRecordContext, (v) => v.totalRecordsCount);
@@ -217,7 +221,7 @@ const OutLink = (props: Props) => {
if (showHistory !== '1') return null;
return isPc ? (
<SideBar>{Children}</SideBar>
<SideBar externalTrigger={!!quoteData}>{Children}</SideBar>
) : (
<Drawer
isOpen={isOpenSlider}
@@ -232,10 +236,10 @@ const OutLink = (props: Props) => {
</DrawerContent>
</Drawer>
);
}, [isOpenSlider, isPc, onCloseSlider, showHistory, t]);
}, [isOpenSlider, isPc, onCloseSlider, quoteData, showHistory, t]);
return (
<>
<Box h={'full'} display={quoteData ? 'flex' : ''}>
<NextHead
title={props.appName || data?.app?.name || 'AI'}
desc={props.appIntro || data?.app?.intro}
@@ -291,7 +295,17 @@ const OutLink = (props: Props) => {
</Flex>
</Flex>
</PageContainer>
</>
{quoteData && (
<PageContainer w={['full', '800px']} py={5}>
<ChatQuoteList
chatTime={quoteData.chatTime}
rawSearch={quoteData.rawSearch}
metadata={quoteData.metadata}
onClose={() => setQuoteData(undefined)}
/>
</PageContainer>
)}
</Box>
);
};
@@ -340,6 +354,7 @@ const Render = (props: Props) => {
showRouteToAppDetail={false}
showRouteToDatasetDetail={false}
isShowReadRawSource={props.showRawSource}
// isShowFullText={props.showFullText}
showNodeStatus={props.showNodeStatus}
>
<ChatRecordContextProvider params={chatRecordProviderParams}>
@@ -383,6 +398,7 @@ export async function getServerSideProps(context: any) {
appAvatar: app?.associatedApp?.avatar ?? '',
appIntro: app?.associatedApp?.intro ?? 'AI',
showRawSource: app?.showRawSource ?? false,
// showFullText: app?.showFullText ?? false,
showNodeStatus: app?.showNodeStatus ?? false,
shareId: shareId ?? '',
authToken: authToken ?? '',

View File

@@ -33,6 +33,7 @@ import ChatRecordContextProvider, {
import { useChatStore } from '@/web/core/chat/context/useChatStore';
import { useMount } from 'ahooks';
import { ChatSourceEnum } from '@fastgpt/global/core/chat/constants';
import ChatQuoteList from '@/pageComponents/chat/ChatQuoteList';
const CustomPluginRunBox = dynamic(() => import('@/pageComponents/chat/CustomPluginRunBox'));
type Props = { appId: string; chatId: string; teamId: string; teamToken: string };
@@ -63,6 +64,8 @@ const Chat = ({ myApps }: { myApps: AppListItemType[] }) => {
const resetVariables = useContextSelector(ChatItemContext, (v) => v.resetVariables);
const chatBoxData = useContextSelector(ChatItemContext, (v) => v.chatBoxData);
const setChatBoxData = useContextSelector(ChatItemContext, (v) => v.setChatBoxData);
const quoteData = useContextSelector(ChatItemContext, (v) => v.quoteData);
const setQuoteData = useContextSelector(ChatItemContext, (v) => v.setQuoteData);
const chatRecords = useContextSelector(ChatRecordContext, (v) => v.chatRecords);
const totalRecordsCount = useContextSelector(ChatRecordContext, (v) => v.totalRecordsCount);
@@ -163,7 +166,7 @@ const Chat = ({ myApps }: { myApps: AppListItemType[] }) => {
);
return isPc || !appId ? (
<SideBar>{Children}</SideBar>
<SideBar externalTrigger={!!quoteData}>{Children}</SideBar>
) : (
<Drawer
isOpen={isOpenSlider}
@@ -176,7 +179,7 @@ const Chat = ({ myApps }: { myApps: AppListItemType[] }) => {
<DrawerContent maxWidth={'75vw'}>{Children}</DrawerContent>
</Drawer>
);
}, [appId, isOpenSlider, isPc, onCloseSlider, t]);
}, [appId, isOpenSlider, isPc, onCloseSlider, quoteData, t]);
return (
<Flex h={'100%'}>
@@ -231,6 +234,17 @@ const Chat = ({ myApps }: { myApps: AppListItemType[] }) => {
</Flex>
</Flex>
</PageContainer>
{quoteData && (
<PageContainer w={['full', '800px']} py={5}>
<ChatQuoteList
chatTime={quoteData.chatTime}
rawSearch={quoteData.rawSearch}
metadata={quoteData.metadata}
onClose={() => setQuoteData(undefined)}
/>
</PageContainer>
)}
</Flex>
);
};
@@ -300,6 +314,7 @@ const Render = (props: Props) => {
showRouteToAppDetail={false}
showRouteToDatasetDetail={false}
isShowReadRawSource={true}
// isShowFullText={true}
showNodeStatus
>
<ChatRecordContextProvider params={chatRecordProviderParams}>