mirror of
https://github.com/labring/FastGPT.git
synced 2025-07-21 03:35:36 +00:00
perf: chat history api;perf: full text error (#4852)
* perf: chat history api * perf: i18n * perf: full text
This commit is contained in:
@@ -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 >}}
|
||||
|
1
packages/global/core/chat/type.d.ts
vendored
1
packages/global/core/chat/type.d.ts
vendored
@@ -26,6 +26,7 @@ export type ChatSchema = {
|
||||
teamId: string;
|
||||
tmbId: string;
|
||||
appId: string;
|
||||
createTime: Date;
|
||||
updateTime: Date;
|
||||
title: string;
|
||||
customTitle: string;
|
||||
|
@@ -34,6 +34,10 @@ const ChatSchema = new Schema({
|
||||
ref: AppCollectionName,
|
||||
required: true
|
||||
},
|
||||
createTime: {
|
||||
type: Date,
|
||||
default: () => new Date()
|
||||
},
|
||||
updateTime: {
|
||||
type: Date,
|
||||
default: () => new Date()
|
||||
|
@@ -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,
|
||||
|
@@ -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 = '') => {
|
||||
|
@@ -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 {
|
||||
|
@@ -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",
|
||||
|
@@ -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": "积分",
|
||||
|
@@ -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": "點數",
|
||||
|
5
projects/app/src/global/core/chat/api.d.ts
vendored
5
projects/app/src/global/core/chat/api.d.ts
vendored
@@ -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 & {
|
||||
|
@@ -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 */}
|
||||
|
@@ -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 {
|
||||
|
Reference in New Issue
Block a user