perf: chat history api;perf: full text error (#4852)

* perf: chat history api

* perf: i18n

* perf: full text
This commit is contained in:
Archer
2025-05-20 22:31:32 +08:00
committed by GitHub
parent 89c9a02650
commit aa55f059d4
12 changed files with 192 additions and 124 deletions

View File

@@ -959,10 +959,16 @@ curl --location --request POST 'http://localhost:3000/api/core/chat/getHistories
{{< markdownify >}}
{{% alert icon=" " context="success" %}}
目前仅能获取到当前 API key 的创建者的对话。
- appId - 应用 Id
- offset - 偏移量,即从第几条数据开始取
- pageSize - 记录数量
- source - 对话源。source=api表示获取通过 API 创建的对话(不会获取到页面上的对话记录)
- startCreateTime - 开始创建时间(可选)
- endCreateTime - 结束创建时间(可选)
- startUpdateTime - 开始更新时间(可选)
- endUpdateTime - 结束更新时间(可选)
{{% /alert %}}
{{< /markdownify >}}

View File

@@ -26,6 +26,7 @@ export type ChatSchema = {
teamId: string;
tmbId: string;
appId: string;
createTime: Date;
updateTime: Date;
title: string;
customTitle: string;

View File

@@ -34,6 +34,10 @@ const ChatSchema = new Schema({
ref: AppCollectionName,
required: true
},
createTime: {
type: Date,
default: () => new Date()
},
updateTime: {
type: Date,
default: () => new Date()

View File

@@ -27,6 +27,7 @@ import { type ChatItemType } from '@fastgpt/global/core/chat/type';
import type { NodeInputKeyEnum } from '@fastgpt/global/core/workflow/constants';
import { datasetSearchQueryExtension } from './utils';
import type { RerankModelItemType } from '@fastgpt/global/core/ai/model.d';
import { addLog } from '../../../common/system/log';
export type SearchDatasetDataProps = {
histories: ChatItemType[];
@@ -544,117 +545,125 @@ export async function searchDatasetData(
};
}
const searchResults = (await MongoDatasetDataText.aggregate(
[
{
$match: {
teamId: new Types.ObjectId(teamId),
$text: { $search: await jiebaSplit({ text: query }) },
datasetId: { $in: datasetIds.map((id) => new Types.ObjectId(id)) },
...(filterCollectionIdList
? {
collectionId: {
$in: filterCollectionIdList.map((id) => new Types.ObjectId(id))
try {
const searchResults = (await MongoDatasetDataText.aggregate(
[
{
$match: {
teamId: new Types.ObjectId(teamId),
$text: { $search: await jiebaSplit({ text: query }) },
datasetId: { $in: datasetIds.map((id) => new Types.ObjectId(id)) },
...(filterCollectionIdList
? {
collectionId: {
$in: filterCollectionIdList.map((id) => new Types.ObjectId(id))
}
}
}
: {}),
...(forbidCollectionIdList && forbidCollectionIdList.length > 0
? {
collectionId: {
$nin: forbidCollectionIdList.map((id) => new Types.ObjectId(id))
: {}),
...(forbidCollectionIdList && forbidCollectionIdList.length > 0
? {
collectionId: {
$nin: forbidCollectionIdList.map((id) => new Types.ObjectId(id))
}
}
}
: {})
: {})
}
},
{
$sort: {
score: { $meta: 'textScore' }
}
},
{
$limit: limit
},
{
$project: {
_id: 1,
collectionId: 1,
dataId: 1,
score: { $meta: 'textScore' }
}
}
},
],
{
$sort: {
score: { $meta: 'textScore' }
}
},
{
$limit: limit
},
{
$project: {
_id: 1,
collectionId: 1,
dataId: 1,
score: { $meta: 'textScore' }
}
...readFromSecondary
}
],
{
...readFromSecondary
}
)) as (DatasetDataTextSchemaType & { score: number })[];
)) as (DatasetDataTextSchemaType & { score: number })[];
// Get data and collections
const [dataList, collections] = await Promise.all([
MongoDatasetData.find(
{
_id: { $in: searchResults.map((item) => item.dataId) }
},
'_id datasetId collectionId updateTime q a chunkIndex indexes',
{ ...readFromSecondary }
).lean(),
MongoDatasetCollection.find(
{
_id: { $in: searchResults.map((item) => item.collectionId) }
},
'_id name fileId rawLink apiFileId externalFileId externalFileUrl',
{ ...readFromSecondary }
).lean()
]);
// Get data and collections
const [dataList, collections] = await Promise.all([
MongoDatasetData.find(
{
_id: { $in: searchResults.map((item) => item.dataId) }
},
'_id datasetId collectionId updateTime q a chunkIndex indexes',
{ ...readFromSecondary }
).lean(),
MongoDatasetCollection.find(
{
_id: { $in: searchResults.map((item) => item.collectionId) }
},
'_id name fileId rawLink apiFileId externalFileId externalFileUrl',
{ ...readFromSecondary }
).lean()
]);
return {
fullTextRecallResults: searchResults
.map((item, index) => {
const collection = collections.find(
(col) => String(col._id) === String(item.collectionId)
);
if (!collection) {
console.log('Collection is not found', item);
return;
}
const data = dataList.find((data) => String(data._id) === String(item.dataId));
if (!data) {
console.log('Data is not found', item);
return;
}
return {
fullTextRecallResults: searchResults
.map((item, index) => {
const collection = collections.find(
(col) => String(col._id) === String(item.collectionId)
);
if (!collection) {
console.log('Collection is not found', item);
return;
}
const data = dataList.find((data) => String(data._id) === String(item.dataId));
if (!data) {
console.log('Data is not found', item);
return;
}
return {
id: String(data._id),
datasetId: String(data.datasetId),
collectionId: String(data.collectionId),
updateTime: data.updateTime,
q: data.q,
a: data.a,
chunkIndex: data.chunkIndex,
indexes: data.indexes,
...getCollectionSourceData(collection),
score: [
{
type: SearchScoreTypeEnum.fullText,
value: item.score || 0,
index
}
]
};
})
.filter((item) => {
if (!item) return false;
return true;
})
.map((item, index) => {
if (!item) return;
return {
...item,
score: item.score.map((item) => ({ ...item, index }))
};
}) as SearchDataResponseItemType[],
tokenLen: 0
};
return {
id: String(data._id),
datasetId: String(data.datasetId),
collectionId: String(data.collectionId),
updateTime: data.updateTime,
q: data.q,
a: data.a,
chunkIndex: data.chunkIndex,
indexes: data.indexes,
...getCollectionSourceData(collection),
score: [
{
type: SearchScoreTypeEnum.fullText,
value: item.score || 0,
index
}
]
};
})
.filter((item) => {
if (!item) return false;
return true;
})
.map((item, index) => {
if (!item) return;
return {
...item,
score: item.score.map((item) => ({ ...item, index }))
};
}) as SearchDataResponseItemType[],
tokenLen: 0
};
} catch (error) {
addLog.error('multiQueryRecall error', error);
return {
fullTextRecallResults: [],
tokenLen: 0
};
}
};
const multiQueryRecall = async ({
embeddingLimit,

View File

@@ -6,10 +6,6 @@ export const getUserFingerprint = async () => {
console.log(result.visitorId);
};
export const hasHttps = () => {
return window.location.protocol === 'https:';
};
export const subRoute = process.env.NEXT_PUBLIC_BASE_URL;
export const getWebReqUrl = (url: string = '') => {

View File

@@ -1,8 +1,6 @@
import { useTranslation } from 'next-i18next';
import { useToast } from './useToast';
import { useCallback } from 'react';
import { hasHttps } from '../common/system/utils';
import { isProduction } from '@fastgpt/global/common/system/constants';
import MyModal from '../components/common/MyModal';
import React from 'react';
import { Box, ModalBody } from '@chakra-ui/react';
@@ -26,7 +24,7 @@ export const useCopyData = () => {
data = data.trim();
try {
if ((hasHttps() || !isProduction) && navigator.clipboard) {
if (navigator.clipboard && window.isSecureContext) {
await navigator.clipboard.writeText(data);
if (title) {
toast({
@@ -36,13 +34,35 @@ export const useCopyData = () => {
});
}
} else {
throw new Error('');
let textArea = document.createElement('textarea');
textArea.value = data;
// 使text area不在viewport同时设置不可见
textArea.style.position = 'absolute';
// @ts-ignore
textArea.style.opacity = 0;
textArea.style.left = '-999999px';
textArea.style.top = '-999999px';
document.body.appendChild(textArea);
textArea.focus();
textArea.select();
await new Promise((res, rej) => {
document.execCommand('copy') ? res('') : rej();
textArea.remove();
}).then(() => {
if (title) {
toast({
title,
status: 'success',
duration
});
}
});
}
} catch (error) {
setCopyContent(data);
}
},
[t, toast]
[setCopyContent, t, toast]
);
return {

View File

@@ -941,6 +941,7 @@
"pay_corporate_payment": "Payment to the public",
"pay_money": "Amount payable",
"pay_success": "Payment successfully",
"pay_year_tip": "Pay 10 months, enjoy 1 year!",
"permission.Collaborator": "Collaborator",
"permission.Default permission": "Default Permission",
"permission.Manage": "Manage",
@@ -1144,7 +1145,7 @@
"support.wallet.subscription.Next plan": "Future Package",
"support.wallet.subscription.Stand plan level": "Subscription Package",
"support.wallet.subscription.Sub plan": "Subscription Package",
"support.wallet.subscription.Sub plan tip": "Free to use {{title}} or upgrade to a higher package",
"support.wallet.subscription.Sub plan tip": "Free to use [{{title}}] or upgrade to a higher package",
"support.wallet.subscription.Team plan and usage": "Package and Usage",
"support.wallet.subscription.Training weight": "Training Priority: {{weight}}",
"support.wallet.subscription.Update extra ai points": "Extra AI Points",
@@ -1159,7 +1160,6 @@
"support.wallet.subscription.function.Points": "{{amount}} AI Points",
"support.wallet.subscription.mode.Month": "Month",
"support.wallet.subscription.mode.Period": "Subscription Period",
"support.wallet.subscription.mode.Ten Year": "Pay 10 months, imagine 1 year!",
"support.wallet.subscription.mode.Year": "Year",
"support.wallet.subscription.mode.Year sale": "Two Months Free",
"support.wallet.subscription.point": "Points",

View File

@@ -940,6 +940,7 @@
"pay_corporate_payment": "对公支付",
"pay_money": "应付金额",
"pay_success": "支付成功",
"pay_year_tip": "支付 10 个月,畅享 1 年!",
"permission.Collaborator": "协作者",
"permission.Default permission": "默认权限",
"permission.Manage": "管理",
@@ -1143,7 +1144,7 @@
"support.wallet.subscription.Next plan": "未来套餐",
"support.wallet.subscription.Stand plan level": "订阅套餐",
"support.wallet.subscription.Sub plan": "订阅套餐",
"support.wallet.subscription.Sub plan tip": "免费使用 {{title}} 或升级更高的套餐",
"support.wallet.subscription.Sub plan tip": "免费使用{{title}}或升级更高的套餐",
"support.wallet.subscription.Team plan and usage": "套餐与用量",
"support.wallet.subscription.Training weight": "训练优先级:{{weight}}",
"support.wallet.subscription.Update extra ai points": "额外 AI 积分",
@@ -1158,7 +1159,6 @@
"support.wallet.subscription.function.Points": "{{amount}} AI 积分",
"support.wallet.subscription.mode.Month": "按月",
"support.wallet.subscription.mode.Period": "订阅周期",
"support.wallet.subscription.mode.Ten Year": "支付10个月畅想1年",
"support.wallet.subscription.mode.Year": "按年",
"support.wallet.subscription.mode.Year sale": "赠送两个月",
"support.wallet.subscription.point": "积分",

View File

@@ -940,6 +940,7 @@
"pay_corporate_payment": "對公支付",
"pay_money": "應付金額",
"pay_success": "支付成功",
"pay_year_tip": "支付 10 個月,暢享 1 年!",
"permission.Collaborator": "協作者",
"permission.Default permission": "預設權限",
"permission.Manage": "管理",
@@ -1143,7 +1144,7 @@
"support.wallet.subscription.Next plan": "未來方案",
"support.wallet.subscription.Stand plan level": "訂閱方案",
"support.wallet.subscription.Sub plan": "訂閱方案",
"support.wallet.subscription.Sub plan tip": "免費使用 {{title}} 或升級更進階的方案",
"support.wallet.subscription.Sub plan tip": "免費使用{{title}}或升級更進階的方案",
"support.wallet.subscription.Team plan and usage": "方案與使用量",
"support.wallet.subscription.Training weight": "訓練優先權:{{weight}}",
"support.wallet.subscription.Update extra ai points": "額外 AI 點數",
@@ -1158,7 +1159,6 @@
"support.wallet.subscription.function.Points": "{{amount}} AI 點數",
"support.wallet.subscription.mode.Month": "按月",
"support.wallet.subscription.mode.Period": "訂閱週期",
"support.wallet.subscription.mode.Ten Year": "支付10個月暢想1年",
"support.wallet.subscription.mode.Year": "按年",
"support.wallet.subscription.mode.Year sale": "贈送兩個月",
"support.wallet.subscription.point": "點數",

View File

@@ -60,6 +60,11 @@ export type InitChatResponse = {
export type GetHistoriesProps = OutLinkChatAuthProps & {
appId?: string;
source?: `${ChatSourceEnum}`;
startCreateTime?: string;
endCreateTime?: string;
startUpdateTime?: string;
endUpdateTime?: string;
};
export type UpdateHistoryProps = OutLinkChatAuthProps & {

View File

@@ -89,7 +89,7 @@ const Standard = ({
mb={2}
mr={'-2'}
>
{t('common:support.wallet.subscription.mode.Ten Year')}
{t('common:pay_year_tip')}
</Box>
<RowTabs
list={[
@@ -110,7 +110,7 @@ const Standard = ({
onChange={(e) => setSelectSubMode(e as `${SubModeEnum}`)}
/>
</Box>
<MyIcon name={'price/pricearrow'} mt={'10px'} ml={'3px'} />
<MyIcon name={'price/pricearrow'} mt={'10px'} ml={'6px'} />
</Flex>
{/* card */}

View File

@@ -20,7 +20,18 @@ async function handler(
req: ApiRequestProps<getHistoriesBody, getHistoriesQuery>,
_res: ApiResponseType<any>
): Promise<PaginationResponse<getHistoriesResponse>> {
const { appId, shareId, outLinkUid, teamId, teamToken, source } = req.body;
const {
appId,
shareId,
outLinkUid,
teamId,
teamToken,
source,
startCreateTime,
endCreateTime,
startUpdateTime,
endUpdateTime
} = req.body;
const { offset, pageSize } = parsePaginationRequest(req);
const match = await (async () => {
@@ -49,7 +60,7 @@ async function handler(
return {
tmbId,
appId,
source
...(source && { source })
};
}
})();
@@ -61,13 +72,29 @@ async function handler(
};
}
const timeMatch: Record<string, any> = {};
if (startCreateTime || endCreateTime) {
timeMatch.createTime = {
...(startCreateTime && { $gte: new Date(startCreateTime) }),
...(endCreateTime && { $lte: new Date(endCreateTime) })
};
}
if (startUpdateTime || endUpdateTime) {
timeMatch.updateTime = {
...(startUpdateTime && { $gte: new Date(startUpdateTime) }),
...(endUpdateTime && { $lte: new Date(endUpdateTime) })
};
}
const mergeMatch = { ...match, ...timeMatch };
const [data, total] = await Promise.all([
await MongoChat.find(match, 'chatId title top customTitle appId updateTime')
await MongoChat.find(mergeMatch, 'chatId title top customTitle appId updateTime')
.sort({ top: -1, updateTime: -1 })
.skip(offset)
.limit(pageSize)
.lean(),
MongoChat.countDocuments(match)
MongoChat.countDocuments(mergeMatch)
]);
return {