Add mongo forward read. Fix markdown code block. (#2174)

* perf: read from mongo forward node

* fix: code block ui

* perf: markdown code css
This commit is contained in:
Archer
2024-07-29 09:47:30 +08:00
committed by GitHub
parent 0a9a7691b4
commit 23f22cda18
10 changed files with 191 additions and 118 deletions

View File

@@ -3,6 +3,7 @@ import { imageBaseUrl } from '@fastgpt/global/common/file/image/constants';
import { MongoImage } from './schema'; import { MongoImage } from './schema';
import { ClientSession } from '../../../common/mongo'; import { ClientSession } from '../../../common/mongo';
import { guessBase64ImageType } from '../utils'; import { guessBase64ImageType } from '../utils';
import { readFromSecondary } from '../../mongo/utils';
export function getMongoImgUrl(id: string, extension: string) { export function getMongoImgUrl(id: string, extension: string) {
return `${imageBaseUrl}${id}.${extension}`; return `${imageBaseUrl}${id}.${extension}`;
@@ -44,10 +45,13 @@ export async function uploadMongoImg({
export async function readMongoImg({ id }: { id: string }) { export async function readMongoImg({ id }: { id: string }) {
const formatId = id.replace(/\.[^/.]+$/, ''); const formatId = id.replace(/\.[^/.]+$/, '');
const data = await MongoImage.findById(formatId); const data = await MongoImage.findById(formatId, undefined, {
...readFromSecondary
});
if (!data) { if (!data) {
return Promise.reject('Image not found'); return Promise.reject('Image not found');
} }
return { return {
binary: data.binary, binary: data.binary,
mime: data.metadata?.mime ?? guessBase64ImageType(data.binary.toString('base64')) mime: data.metadata?.mime ?? guessBase64ImageType(data.binary.toString('base64'))

View File

@@ -1,4 +1,6 @@
import { ReadPreference } from './index';
export const readFromSecondary = { export const readFromSecondary = {
readPreference: 'secondaryPreferred', readPreference: ReadPreference.SECONDARY_PREFERRED, // primary | primaryPreferred | secondary | secondaryPreferred | nearest
readConcern: 'local' readConcern: 'local' as any // local | majority | linearizable | available
}; };

41
pnpm-lock.yaml generated
View File

@@ -61,7 +61,7 @@ importers:
version: 4.0.2 version: 4.0.2
next: next:
specifier: 14.2.5 specifier: 14.2.5
version: 14.2.5(@babel/core@7.24.9)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.8) version: 14.2.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.8)
openai: openai:
specifier: 4.53.0 specifier: 4.53.0
version: 4.53.0(encoding@0.1.13) version: 4.53.0(encoding@0.1.13)
@@ -180,7 +180,7 @@ importers:
version: 1.4.5-lts.1 version: 1.4.5-lts.1
next: next:
specifier: 14.2.5 specifier: 14.2.5
version: 14.2.5(@babel/core@7.24.9)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.8) version: 14.2.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.8)
nextjs-cors: nextjs-cors:
specifier: ^2.2.0 specifier: ^2.2.0
version: 2.2.0(next@14.2.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.8)) version: 2.2.0(next@14.2.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.8))
@@ -10036,7 +10036,7 @@ snapshots:
'@chakra-ui/react': 2.8.1(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@emotion/styled@11.11.0(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(framer-motion@9.1.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@chakra-ui/react': 2.8.1(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@emotion/styled@11.11.0(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(framer-motion@9.1.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@emotion/cache': 11.11.0 '@emotion/cache': 11.11.0
'@emotion/react': 11.11.1(@types/react@18.3.1)(react@18.3.1) '@emotion/react': 11.11.1(@types/react@18.3.1)(react@18.3.1)
next: 14.2.5(@babel/core@7.24.9)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.8) next: 14.2.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.8)
react: 18.3.1 react: 18.3.1
'@chakra-ui/number-input@2.1.1(@chakra-ui/system@2.6.1(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@emotion/styled@11.11.0(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(react@18.3.1))(react@18.3.1))(react@18.3.1)': '@chakra-ui/number-input@2.1.1(@chakra-ui/system@2.6.1(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@emotion/styled@11.11.0(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(react@18.3.1))(react@18.3.1))(react@18.3.1)':
@@ -16652,7 +16652,7 @@ snapshots:
hoist-non-react-statics: 3.3.2 hoist-non-react-statics: 3.3.2
i18next: 23.11.5 i18next: 23.11.5
i18next-fs-backend: 2.3.1 i18next-fs-backend: 2.3.1
next: 14.2.5(@babel/core@7.24.9)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.8) next: 14.2.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.8)
react: 18.3.1 react: 18.3.1
react-i18next: 14.1.2(i18next@23.11.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react-i18next: 14.1.2(i18next@23.11.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
@@ -16682,10 +16682,36 @@ snapshots:
- '@babel/core' - '@babel/core'
- babel-plugin-macros - babel-plugin-macros
next@14.2.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.8):
dependencies:
'@next/env': 14.2.5
'@swc/helpers': 0.5.5
busboy: 1.6.0
caniuse-lite: 1.0.30001642
graceful-fs: 4.2.11
postcss: 8.4.31
react: 18.3.1
react-dom: 18.3.1(react@18.3.1)
styled-jsx: 5.1.1(react@18.3.1)
optionalDependencies:
'@next/swc-darwin-arm64': 14.2.5
'@next/swc-darwin-x64': 14.2.5
'@next/swc-linux-arm64-gnu': 14.2.5
'@next/swc-linux-arm64-musl': 14.2.5
'@next/swc-linux-x64-gnu': 14.2.5
'@next/swc-linux-x64-musl': 14.2.5
'@next/swc-win32-arm64-msvc': 14.2.5
'@next/swc-win32-ia32-msvc': 14.2.5
'@next/swc-win32-x64-msvc': 14.2.5
sass: 1.77.8
transitivePeerDependencies:
- '@babel/core'
- babel-plugin-macros
nextjs-cors@2.2.0(next@14.2.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.8)): nextjs-cors@2.2.0(next@14.2.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.8)):
dependencies: dependencies:
cors: 2.8.5 cors: 2.8.5
next: 14.2.5(@babel/core@7.24.9)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.8) next: 14.2.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.8)
nextjs-node-loader@1.1.5(webpack@5.92.1): nextjs-node-loader@1.1.5(webpack@5.92.1):
dependencies: dependencies:
@@ -18058,6 +18084,11 @@ snapshots:
optionalDependencies: optionalDependencies:
'@babel/core': 7.24.9 '@babel/core': 7.24.9
styled-jsx@5.1.1(react@18.3.1):
dependencies:
client-only: 0.0.1
react: 18.3.1
stylis@4.2.0: {} stylis@4.2.0: {}
stylis@4.3.2: {} stylis@4.3.2: {}

View File

@@ -287,18 +287,18 @@ const codeLight: { [key: string]: React.CSSProperties } = {
const CodeLight = ({ const CodeLight = ({
children, children,
className, className,
inline, codeBlock,
match match
}: { }: {
children: React.ReactNode & React.ReactNode[]; children: React.ReactNode & React.ReactNode[];
className?: string; className?: string;
inline?: boolean; codeBlock?: boolean;
match: RegExpExecArray | null; match: RegExpExecArray | null;
}) => { }) => {
const { t } = useTranslation(); const { t } = useTranslation();
const { copyData } = useCopyData(); const { copyData } = useCopyData();
if (!inline) { if (codeBlock) {
const codeBoxName = useMemo(() => { const codeBoxName = useMemo(() => {
const input = match?.['input'] || ''; const input = match?.['input'] || '';
if (!input) return match?.[1]; if (!input) return match?.[1];
@@ -312,7 +312,6 @@ const CodeLight = ({
my={3} my={3}
borderRadius={'md'} borderRadius={'md'}
overflow={'overlay'} overflow={'overlay'}
bg={'myGray.900'}
boxShadow={ boxShadow={
'0px 0px 1px 0px rgba(19, 51, 107, 0.08), 0px 1px 2px 0px rgba(19, 51, 107, 0.05)' '0px 0px 1px 0px rgba(19, 51, 107, 0.08), 0px 1px 2px 0px rgba(19, 51, 107, 0.05)'
} }

View File

@@ -336,7 +336,7 @@
} }
.markdown pre code, .markdown pre code,
.markdown pre tt { .markdown pre tt {
background-color: transparent; background-color: transparent !important;
border: medium none; border: medium none;
} }
.markdown hr { .markdown hr {
@@ -360,13 +360,12 @@
margin: 0; margin: 0;
border: none; border: none;
border-radius: 0; border-radius: 0;
background-color: #292b33 !important; background-color: var(--chakra-colors-gray-900) !important;
overflow-x: auto; overflow-x: auto;
color: #fff; color: #fff;
} }
pre code { pre code {
background-color: #292b33 !important;
width: 100%; width: 100%;
} }

View File

@@ -36,7 +36,7 @@ const Markdown = ({
const components = useMemo<any>( const components = useMemo<any>(
() => ({ () => ({
img: Image, img: Image,
pre: 'div', pre: RewritePre,
p: (pProps: any) => <p {...pProps} dir="auto" />, p: (pProps: any) => <p {...pProps} dir="auto" />,
code: Code, code: Code,
a: A a: A
@@ -70,8 +70,7 @@ export default React.memo(Markdown);
/* Custom dom */ /* Custom dom */
const Code = React.memo(function Code(e: any) { const Code = React.memo(function Code(e: any) {
const { inline, className, children } = e; const { className, codeBlock, children } = e;
const match = /language-(\w+)/.exec(className || ''); const match = /language-(\w+)/.exec(className || '');
const codeType = match?.[1]; const codeType = match?.[1];
@@ -92,11 +91,11 @@ const Code = React.memo(function Code(e: any) {
} }
return ( return (
<CodeLight className={className} inline={inline} match={match}> <CodeLight className={className} codeBlock={codeBlock} match={match}>
{children} {children}
</CodeLight> </CodeLight>
); );
}, [codeType, className, inline, match, children, strChildren]); }, [codeType, className, codeBlock, match, children, strChildren]);
return Component; return Component;
}); });
@@ -149,3 +148,15 @@ const A = React.memo(function A({ children, ...props }: any) {
return <Link {...props}>{children}</Link>; return <Link {...props}>{children}</Link>;
}); });
function RewritePre({ children }: any) {
const modifiedChildren = React.Children.map(children, (child) => {
if (React.isValidElement(child)) {
// @ts-ignore
return React.cloneElement(child, { codeBlock: true });
}
return child;
});
return <>{modifiedChildren}</>;
}

View File

@@ -9,6 +9,7 @@ import { authApp } from '@fastgpt/service/support/permission/app/auth';
import { ChatItemCollectionName } from '@fastgpt/service/core/chat/chatItemSchema'; import { ChatItemCollectionName } from '@fastgpt/service/core/chat/chatItemSchema';
import { NextAPI } from '@/service/middleware/entry'; import { NextAPI } from '@/service/middleware/entry';
import { WritePermissionVal } from '@fastgpt/global/support/permission/constant'; import { WritePermissionVal } from '@fastgpt/global/support/permission/constant';
import { readFromSecondary } from '@fastgpt/service/common/mongo/utils';
async function handler( async function handler(
req: NextApiRequest, req: NextApiRequest,
@@ -39,101 +40,106 @@ async function handler(
}; };
const [data, total] = await Promise.all([ const [data, total] = await Promise.all([
MongoChat.aggregate([ MongoChat.aggregate(
{ $match: where }, [
{ { $match: where },
$sort: { {
userBadFeedbackCount: -1, $sort: {
userGoodFeedbackCount: -1, userBadFeedbackCount: -1,
customFeedbacksCount: -1, userGoodFeedbackCount: -1,
updateTime: -1 customFeedbacksCount: -1,
} updateTime: -1
}, }
{ $skip: (pageNum - 1) * pageSize }, },
{ $limit: pageSize }, { $skip: (pageNum - 1) * pageSize },
{ { $limit: pageSize },
$lookup: { {
from: ChatItemCollectionName, $lookup: {
let: { chatId: '$chatId' }, from: ChatItemCollectionName,
pipeline: [ let: { chatId: '$chatId' },
{ pipeline: [
$match: { {
$expr: { $match: {
$and: [ $expr: {
{ $eq: ['$appId', new Types.ObjectId(appId)] }, $and: [
{ $eq: ['$chatId', '$$chatId'] } { $eq: ['$appId', new Types.ObjectId(appId)] },
] { $eq: ['$chatId', '$$chatId'] }
]
}
}
},
{
$project: {
userGoodFeedback: 1,
userBadFeedback: 1,
customFeedbacks: 1,
adminFeedback: 1
}
}
],
as: 'chatitems'
}
},
{
$addFields: {
userGoodFeedbackCount: {
$size: {
$filter: {
input: '$chatitems',
as: 'item',
cond: { $ifNull: ['$$item.userGoodFeedback', false] }
} }
} }
}, },
{ userBadFeedbackCount: {
$project: { $size: {
userGoodFeedback: 1, $filter: {
userBadFeedback: 1, input: '$chatitems',
customFeedbacks: 1, as: 'item',
adminFeedback: 1 cond: { $ifNull: ['$$item.userBadFeedback', false] }
}
} }
} },
], customFeedbacksCount: {
as: 'chatitems' $size: {
} $filter: {
}, input: '$chatitems',
{ as: 'item',
$addFields: { cond: { $gt: [{ $size: { $ifNull: ['$$item.customFeedbacks', []] } }, 0] }
userGoodFeedbackCount: { }
$size: {
$filter: {
input: '$chatitems',
as: 'item',
cond: { $ifNull: ['$$item.userGoodFeedback', false] }
} }
} },
}, markCount: {
userBadFeedbackCount: { $size: {
$size: { $filter: {
$filter: { input: '$chatitems',
input: '$chatitems', as: 'item',
as: 'item', cond: { $ifNull: ['$$item.adminFeedback', false] }
cond: { $ifNull: ['$$item.userBadFeedback', false] } }
}
}
},
customFeedbacksCount: {
$size: {
$filter: {
input: '$chatitems',
as: 'item',
cond: { $gt: [{ $size: { $ifNull: ['$$item.customFeedbacks', []] } }, 0] }
}
}
},
markCount: {
$size: {
$filter: {
input: '$chatitems',
as: 'item',
cond: { $ifNull: ['$$item.adminFeedback', false] }
} }
} }
} }
},
{
$project: {
_id: 1,
id: '$chatId',
title: 1,
source: 1,
time: '$updateTime',
messageCount: { $size: '$chatitems' },
userGoodFeedbackCount: 1,
userBadFeedbackCount: 1,
customFeedbacksCount: 1,
markCount: 1
}
} }
}, ],
{ {
$project: { ...readFromSecondary
_id: 1,
id: '$chatId',
title: 1,
source: 1,
time: '$updateTime',
messageCount: { $size: '$chatitems' },
userGoodFeedbackCount: 1,
userBadFeedbackCount: 1,
customFeedbacksCount: 1,
markCount: 1
}
} }
]), ),
MongoChat.countDocuments(where) MongoChat.countDocuments(where, { ...readFromSecondary })
]); ]);
return { return {

View File

@@ -11,6 +11,7 @@ import {
import { NextAPI } from '@/service/middleware/entry'; import { NextAPI } from '@/service/middleware/entry';
import { WritePermissionVal } from '@fastgpt/global/support/permission/constant'; import { WritePermissionVal } from '@fastgpt/global/support/permission/constant';
import { CommonErrEnum } from '@fastgpt/global/common/error/code/common'; import { CommonErrEnum } from '@fastgpt/global/common/error/code/common';
import { readFromSecondary } from '@fastgpt/service/common/mongo/utils';
async function handler(req: NextApiRequest, res: NextApiResponse<any>) { async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
let { datasetId } = req.query as { let { datasetId } = req.query as {
@@ -53,7 +54,10 @@ async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
teamId, teamId,
datasetId: { $in: datasets.map((d) => d._id) } datasetId: { $in: datasets.map((d) => d._id) }
}, },
'q a' 'q a',
{
...readFromSecondary
}
) )
.limit(50000) .limit(50000)
.cursor(); .cursor();

View File

@@ -4,6 +4,7 @@ import { authDataset } from '@fastgpt/service/support/permission/dataset/auth';
import { MongoDatasetData } from '@fastgpt/service/core/dataset/data/schema'; import { MongoDatasetData } from '@fastgpt/service/core/dataset/data/schema';
import { MongoDatasetTraining } from '@fastgpt/service/core/dataset/training/schema'; import { MongoDatasetTraining } from '@fastgpt/service/core/dataset/training/schema';
import { ReadPermissionVal } from '@fastgpt/global/support/permission/constant'; import { ReadPermissionVal } from '@fastgpt/global/support/permission/constant';
import { readFromSecondary } from '@fastgpt/service/common/mongo/utils';
export type getDatasetTrainingQueueResponse = { export type getDatasetTrainingQueueResponse = {
rebuildingCount: number; rebuildingCount: number;
@@ -24,8 +25,18 @@ async function handler(
}); });
const [rebuildingCount, trainingCount] = await Promise.all([ const [rebuildingCount, trainingCount] = await Promise.all([
MongoDatasetData.countDocuments({ rebuilding: true, teamId, datasetId }), MongoDatasetData.countDocuments(
MongoDatasetTraining.countDocuments({ teamId, datasetId }) { rebuilding: true, teamId, datasetId },
{
...readFromSecondary
}
),
MongoDatasetTraining.countDocuments(
{ teamId, datasetId },
{
...readFromSecondary
}
)
]); ]);
return { return {

View File

@@ -3,6 +3,7 @@ import { MongoDatasetTraining } from '@fastgpt/service/core/dataset/training/sch
import { authCert } from '@fastgpt/service/support/permission/auth/common'; import { authCert } from '@fastgpt/service/support/permission/auth/common';
import { GetTrainingQueueProps } from '@/global/core/dataset/api'; import { GetTrainingQueueProps } from '@/global/core/dataset/api';
import { NextAPI } from '@/service/middleware/entry'; import { NextAPI } from '@/service/middleware/entry';
import { readFromSecondary } from '@fastgpt/service/common/mongo/utils';
async function handler(req: NextApiRequest) { async function handler(req: NextApiRequest) {
await authCert({ req, authToken: true }); await authCert({ req, authToken: true });
@@ -10,20 +11,25 @@ async function handler(req: NextApiRequest) {
// get queue data // get queue data
// 分别统计 model = vectorModel和agentModel的数量 // 分别统计 model = vectorModel和agentModel的数量
const data = await MongoDatasetTraining.aggregate([ const data = await MongoDatasetTraining.aggregate(
{ [
$match: { {
lockTime: { $lt: new Date('2040/1/1') }, $match: {
$or: [{ model: { $eq: vectorModel } }, { model: { $eq: agentModel } }] lockTime: { $lt: new Date('2040/1/1') },
$or: [{ model: { $eq: vectorModel } }, { model: { $eq: agentModel } }]
}
},
{
$group: {
_id: '$model',
count: { $sum: 1 }
}
} }
}, ],
{ {
$group: { ...readFromSecondary
_id: '$model',
count: { $sum: 1 }
}
} }
]); );
const vectorTrainingCount = data.find((item) => item._id === vectorModel)?.count || 0; const vectorTrainingCount = data.find((item) => item._id === vectorModel)?.count || 0;
const agentTrainingCount = data.find((item) => item._id === agentModel)?.count || 0; const agentTrainingCount = data.find((item) => item._id === agentModel)?.count || 0;