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:
250
projects/app/src/pages/api/core/chat/quote/getCollectionQuote.ts
Normal file
250
projects/app/src/pages/api/core/chat/quote/getCollectionQuote.ts
Normal 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
|
||||
};
|
||||
}
|
||||
102
projects/app/src/pages/api/core/chat/quote/getQuote.ts
Normal file
102
projects/app/src/pages/api/core/chat/quote/getQuote.ts
Normal 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
|
||||
};
|
||||
});
|
||||
}
|
||||
78
projects/app/src/pages/api/core/dataset/collection/export.ts
Normal file
78
projects/app/src/pages/api/core/dataset/collection/export.ts
Normal 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
|
||||
);
|
||||
@@ -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> {
|
||||
|
||||
@@ -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);
|
||||
@@ -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(),
|
||||
|
||||
@@ -37,6 +37,7 @@ async function handler(
|
||||
responseDetail,
|
||||
showRawSource,
|
||||
showNodeStatus,
|
||||
// showFullText,
|
||||
limit,
|
||||
app
|
||||
});
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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}>
|
||||
|
||||
@@ -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 ?? '',
|
||||
|
||||
@@ -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}>
|
||||
|
||||
Reference in New Issue
Block a user