mirror of
https://github.com/labring/FastGPT.git
synced 2025-07-23 21:13:50 +00:00
Test parse cite and add tool call parallel (#4737)
* add quote response filter (#4727) * chatting * add quote response filter * add test * remove comment * perf: cite hidden * perf: format llm response * feat: comment * update default chunk size * update default chunk size --------- Co-authored-by: heheer <heheer@sealos.io>
This commit is contained in:
@@ -21,16 +21,16 @@ import MyBox from '@fastgpt/web/components/common/MyBox';
|
||||
import { getCollectionSourceData } from '@fastgpt/global/core/dataset/collection/utils';
|
||||
import Markdown from '.';
|
||||
import { getSourceNameIcon } from '@fastgpt/global/core/dataset/utils';
|
||||
import { Types } from 'mongoose';
|
||||
|
||||
const A = ({ children, chatAuthData, ...props }: any) => {
|
||||
const A = ({ children, chatAuthData, showAnimation, ...props }: any) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const { isOpen, onOpen, onClose } = useDisclosure();
|
||||
const content = useMemo(() => String(children), [children]);
|
||||
|
||||
// empty href link
|
||||
if (!props.href && typeof children?.[0] === 'string') {
|
||||
const text = useMemo(() => String(children), [children]);
|
||||
|
||||
return (
|
||||
<MyTooltip label={t('common:core.chat.markdown.Quick Question')}>
|
||||
<Button
|
||||
@@ -38,16 +38,23 @@ const A = ({ children, chatAuthData, ...props }: any) => {
|
||||
size={'xs'}
|
||||
borderRadius={'md'}
|
||||
my={1}
|
||||
onClick={() => eventBus.emit(EventNameEnum.sendQuestion, { text })}
|
||||
onClick={() => eventBus.emit(EventNameEnum.sendQuestion, { text: content })}
|
||||
>
|
||||
{text}
|
||||
{content}
|
||||
</Button>
|
||||
</MyTooltip>
|
||||
);
|
||||
}
|
||||
|
||||
// Quote
|
||||
if (props.href?.startsWith('QUOTE') && typeof children?.[0] === 'string') {
|
||||
// Cite
|
||||
if (
|
||||
(props.href?.startsWith('CITE') || props.href?.startsWith('QUOTE')) &&
|
||||
typeof children?.[0] === 'string'
|
||||
) {
|
||||
if (!Types.ObjectId.isValid(content)) {
|
||||
return <></>;
|
||||
}
|
||||
|
||||
const {
|
||||
data: quoteData,
|
||||
loading,
|
||||
@@ -74,6 +81,7 @@ const A = ({ children, chatAuthData, ...props }: any) => {
|
||||
onClose={onClose}
|
||||
onOpen={() => {
|
||||
onOpen();
|
||||
if (showAnimation) return;
|
||||
getQuoteDataById(String(children));
|
||||
}}
|
||||
trigger={'hover'}
|
||||
@@ -90,7 +98,7 @@ const A = ({ children, chatAuthData, ...props }: any) => {
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent boxShadow={'lg'} w={'500px'} maxW={'90vw'} py={4}>
|
||||
<MyBox isLoading={loading}>
|
||||
<MyBox isLoading={loading || showAnimation}>
|
||||
<PopoverArrow />
|
||||
<PopoverBody py={0} px={0} fontSize={'sm'}>
|
||||
<Flex px={4} pb={1} justifyContent={'space-between'}>
|
||||
|
@@ -60,9 +60,9 @@ const MarkdownRender = ({
|
||||
img: Image,
|
||||
pre: RewritePre,
|
||||
code: Code,
|
||||
a: (props: any) => <A {...props} chatAuthData={chatAuthData} />
|
||||
a: (props: any) => <A {...props} showAnimation={showAnimation} chatAuthData={chatAuthData} />
|
||||
};
|
||||
}, [chatAuthData]);
|
||||
}, [chatAuthData, showAnimation]);
|
||||
|
||||
const formatSource = useMemo(() => {
|
||||
if (showAnimation || forbidZhFormat) return source;
|
||||
|
@@ -27,14 +27,14 @@ export const mdTextFormat = (text: string) => {
|
||||
return match;
|
||||
});
|
||||
|
||||
// 处理 [quote:id] 格式引用,将 [quote:675934a198f46329dfc6d05a] 转换为 [675934a198f46329dfc6d05a](QUOTE)
|
||||
// 处理 [quote:id] 格式引用,将 [quote:675934a198f46329dfc6d05a] 转换为 [675934a198f46329dfc6d05a](CITE)
|
||||
text = text
|
||||
// .replace(
|
||||
// /([\u4e00-\u9fa5\u3000-\u303f])([a-zA-Z0-9])|([a-zA-Z0-9])([\u4e00-\u9fa5\u3000-\u303f])/g,
|
||||
// '$1$3 $2$4'
|
||||
// )
|
||||
// 处理 格式引用,将 [675934a198f46329dfc6d05a] 转换为 [675934a198f46329dfc6d05a](QUOTE)
|
||||
.replace(/\[([a-f0-9]{24})\](?!\()/g, '[$1](QUOTE)');
|
||||
// 处理 格式引用,将 [675934a198f46329dfc6d05a] 转换为 [675934a198f46329dfc6d05a](CITE)
|
||||
.replace(/\[([a-f0-9]{24})\](?!\()/g, '[$1](CITE)');
|
||||
|
||||
// 处理链接后的中文标点符号,增加空格
|
||||
text = text.replace(/(https?:\/\/[^\s,。!?;:、]+)([,。!?;:、])/g, '$1 $2');
|
||||
|
@@ -240,11 +240,6 @@ const ChatItem = (props: Props) => {
|
||||
quoteId?: string;
|
||||
}) => {
|
||||
if (!setQuoteData) return;
|
||||
if (isChatting)
|
||||
return toast({
|
||||
title: t('chat:chat.waiting_for_response'),
|
||||
status: 'info'
|
||||
});
|
||||
|
||||
const collectionIdList = collectionId
|
||||
? [collectionId]
|
||||
@@ -277,18 +272,7 @@ const ChatItem = (props: Props) => {
|
||||
}
|
||||
});
|
||||
},
|
||||
[
|
||||
setQuoteData,
|
||||
isChatting,
|
||||
toast,
|
||||
t,
|
||||
quoteList,
|
||||
isShowReadRawSource,
|
||||
appId,
|
||||
chatId,
|
||||
chat.dataId,
|
||||
outLinkAuthData
|
||||
]
|
||||
[setQuoteData, quoteList, isShowReadRawSource, appId, chatId, chat.dataId, outLinkAuthData]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
|
@@ -96,8 +96,6 @@ const RenderText = React.memo(function RenderText({
|
||||
text: string;
|
||||
chatItemDataId: string;
|
||||
}) {
|
||||
const isResponseDetail = useContextSelector(ChatItemContext, (v) => v.isResponseDetail);
|
||||
|
||||
const appId = useContextSelector(ChatBoxContext, (v) => v.appId);
|
||||
const chatId = useContextSelector(ChatBoxContext, (v) => v.chatId);
|
||||
const outLinkAuthData = useContextSelector(ChatBoxContext, (v) => v.outLinkAuthData);
|
||||
@@ -106,10 +104,8 @@ const RenderText = React.memo(function RenderText({
|
||||
if (!text) return '';
|
||||
|
||||
// Remove quote references if not showing response detail
|
||||
return isResponseDetail
|
||||
? text
|
||||
: text.replace(/\[([a-f0-9]{24})\]\(QUOTE\)/g, '').replace(/\[([a-f0-9]{24})\](?!\()/g, '');
|
||||
}, [text, isResponseDetail]);
|
||||
return text;
|
||||
}, [text]);
|
||||
|
||||
const chatAuthData = useCreation(() => {
|
||||
return { appId, chatId, chatItemDataId, ...outLinkAuthData };
|
||||
|
@@ -12,7 +12,7 @@ import { getWebLLMModel } from '@/web/common/system/utils';
|
||||
const SearchParamsTip = ({
|
||||
searchMode,
|
||||
similarity = 0,
|
||||
limit = 1500,
|
||||
limit = 5000,
|
||||
responseEmptyText,
|
||||
usingReRank = false,
|
||||
datasetSearchUsingExtensionQuery,
|
||||
|
@@ -5,8 +5,8 @@ import { useTranslation } from 'next-i18next';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
||||
import { updatePasswordByOld } from '@/web/support/user/api';
|
||||
import { checkPasswordRule } from '@/web/support/user/login/constants';
|
||||
import { useToast } from '@fastgpt/web/hooks/useToast';
|
||||
import { checkPasswordRule } from '@fastgpt/global/common/string/password';
|
||||
|
||||
type FormType = {
|
||||
oldPsw: string;
|
||||
|
@@ -1,7 +1,7 @@
|
||||
import React, { Dispatch } from 'react';
|
||||
import { FormControl, Box, Input, Button } from '@chakra-ui/react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { LoginPageTypeEnum, checkPasswordRule } from '@/web/support/user/login/constants';
|
||||
import { LoginPageTypeEnum } from '@/web/support/user/login/constants';
|
||||
import { postFindPassword } from '@/web/support/user/api';
|
||||
import { useSendCode } from '@/web/support/user/hooks/useSendCode';
|
||||
import type { ResLogin } from '@/global/support/api/userRes.d';
|
||||
@@ -9,6 +9,7 @@ import { useToast } from '@fastgpt/web/hooks/useToast';
|
||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
||||
import { checkPasswordRule } from '@fastgpt/global/common/string/password';
|
||||
|
||||
interface Props {
|
||||
setPageType: Dispatch<`${LoginPageTypeEnum}`>;
|
||||
|
@@ -1,7 +1,7 @@
|
||||
import React, { Dispatch } from 'react';
|
||||
import { FormControl, Box, Input, Button } from '@chakra-ui/react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { LoginPageTypeEnum, checkPasswordRule } from '@/web/support/user/login/constants';
|
||||
import { LoginPageTypeEnum } from '@/web/support/user/login/constants';
|
||||
import { postRegister } from '@/web/support/user/api';
|
||||
import { useSendCode } from '@/web/support/user/hooks/useSendCode';
|
||||
import type { ResLogin } from '@/global/support/api/userRes';
|
||||
@@ -19,6 +19,7 @@ import {
|
||||
getSourceDomain,
|
||||
removeFastGPTSem
|
||||
} from '@/web/support/marketing/utils';
|
||||
import { checkPasswordRule } from '@fastgpt/global/common/string/password';
|
||||
|
||||
interface Props {
|
||||
loginSuccess: (e: ResLogin) => void;
|
||||
|
@@ -16,7 +16,7 @@ import { reRankRecall } from '@fastgpt/service/core/ai/rerank';
|
||||
import { aiTranscriptions } from '@fastgpt/service/core/ai/audio/transcriptions';
|
||||
import { isProduction } from '@fastgpt/global/common/system/constants';
|
||||
import * as fs from 'fs';
|
||||
import { llmCompletionsBodyFormat, llmResponseToAnswerText } from '@fastgpt/service/core/ai/utils';
|
||||
import { llmCompletionsBodyFormat, formatLLMResponse } from '@fastgpt/service/core/ai/utils';
|
||||
|
||||
export type testQuery = { model: string; channelId?: number };
|
||||
|
||||
@@ -78,7 +78,7 @@ const testLLMModel = async (model: LLMModelItemType, headers: Record<string, str
|
||||
model
|
||||
);
|
||||
|
||||
const { response, isStreamResponse } = await createChatCompletion({
|
||||
const { response } = await createChatCompletion({
|
||||
modelData: model,
|
||||
body: requestBody,
|
||||
options: {
|
||||
@@ -88,7 +88,7 @@ const testLLMModel = async (model: LLMModelItemType, headers: Record<string, str
|
||||
}
|
||||
}
|
||||
});
|
||||
const { text: answer } = await llmResponseToAnswerText(response);
|
||||
const { text: answer } = await formatLLMResponse(response);
|
||||
|
||||
if (answer) {
|
||||
return answer;
|
||||
|
@@ -9,7 +9,10 @@ import { authChatCrud } from '@/service/support/permission/auth/chat';
|
||||
import { MongoApp } from '@fastgpt/service/core/app/schema';
|
||||
import { AppErrEnum } from '@fastgpt/global/common/error/code/app';
|
||||
import { ChatItemValueTypeEnum, ChatRoleEnum } from '@fastgpt/global/core/chat/constants';
|
||||
import { filterPublicNodeResponseData } from '@fastgpt/global/core/chat/utils';
|
||||
import {
|
||||
filterPublicNodeResponseData,
|
||||
removeAIResponseCite
|
||||
} from '@fastgpt/global/core/chat/utils';
|
||||
import { GetChatTypeEnum } from '@/global/core/chat/constants';
|
||||
import { PaginationProps, PaginationResponse } from '@fastgpt/web/common/fetch/type';
|
||||
import { ChatItemType } from '@fastgpt/global/core/chat/type';
|
||||
@@ -83,6 +86,13 @@ async function handler(
|
||||
}
|
||||
});
|
||||
}
|
||||
if (!responseDetail) {
|
||||
histories.forEach((item) => {
|
||||
if (item.obj === ChatRoleEnum.AI) {
|
||||
item.value = removeAIResponseCite(item.value, false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
list: isPlugin ? histories : transformPreviewHistories(histories, responseDetail),
|
||||
|
@@ -19,7 +19,7 @@ async function handler(req: ApiRequestProps<SearchTestProps>): Promise<SearchTes
|
||||
const {
|
||||
datasetId,
|
||||
text,
|
||||
limit = 1500,
|
||||
limit = 5000,
|
||||
similarity,
|
||||
searchMode,
|
||||
embeddingWeight,
|
||||
|
@@ -30,6 +30,7 @@ import {
|
||||
concatHistories,
|
||||
filterPublicNodeResponseData,
|
||||
getChatTitleFromChatMessage,
|
||||
removeAIResponseCite,
|
||||
removeEmptyUserInput
|
||||
} from '@fastgpt/global/core/chat/utils';
|
||||
import { updateApiKeyUsage } from '@fastgpt/service/support/openapi/tools';
|
||||
@@ -74,7 +75,7 @@ export type Props = ChatCompletionCreateParams &
|
||||
responseChatItemId?: string;
|
||||
stream?: boolean;
|
||||
detail?: boolean;
|
||||
parseQuote?: boolean;
|
||||
retainDatasetCite?: boolean;
|
||||
variables: Record<string, any>; // Global variables or plugin inputs
|
||||
};
|
||||
|
||||
@@ -107,7 +108,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
|
||||
stream = false,
|
||||
detail = false,
|
||||
parseQuote = false,
|
||||
retainDatasetCite = false,
|
||||
messages = [],
|
||||
variables = {},
|
||||
responseChatItemId = getNanoid(),
|
||||
@@ -187,6 +188,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
chatId
|
||||
});
|
||||
})();
|
||||
retainDatasetCite = retainDatasetCite && !!responseDetail;
|
||||
const isPlugin = app.type === AppTypeEnum.plugin;
|
||||
|
||||
// Check message type
|
||||
@@ -291,7 +293,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
chatConfig,
|
||||
histories: newHistories,
|
||||
stream,
|
||||
parseQuote,
|
||||
retainDatasetCite,
|
||||
maxRunTimes: WORKFLOW_MAX_RUN_TIMES,
|
||||
workflowStreamResponse: workflowResponseWrite
|
||||
});
|
||||
@@ -406,17 +408,18 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
|
||||
return assistantResponses;
|
||||
})();
|
||||
const formatResponseContent = removeAIResponseCite(responseContent, retainDatasetCite);
|
||||
const error = flowResponses[flowResponses.length - 1]?.error;
|
||||
|
||||
res.json({
|
||||
...(detail ? { responseData: feResponseData, newVariables } : {}),
|
||||
error,
|
||||
id: chatId || '',
|
||||
id: saveChatId,
|
||||
model: '',
|
||||
usage: { prompt_tokens: 1, completion_tokens: 1, total_tokens: 1 },
|
||||
choices: [
|
||||
{
|
||||
message: { role: 'assistant', content: responseContent },
|
||||
message: { role: 'assistant', content: formatResponseContent },
|
||||
finish_reason: 'stop',
|
||||
index: 0
|
||||
}
|
||||
|
@@ -30,6 +30,7 @@ import {
|
||||
concatHistories,
|
||||
filterPublicNodeResponseData,
|
||||
getChatTitleFromChatMessage,
|
||||
removeAIResponseCite,
|
||||
removeEmptyUserInput
|
||||
} from '@fastgpt/global/core/chat/utils';
|
||||
import { updateApiKeyUsage } from '@fastgpt/service/support/openapi/tools';
|
||||
@@ -74,7 +75,7 @@ export type Props = ChatCompletionCreateParams &
|
||||
responseChatItemId?: string;
|
||||
stream?: boolean;
|
||||
detail?: boolean;
|
||||
parseQuote?: boolean;
|
||||
retainDatasetCite?: boolean;
|
||||
variables: Record<string, any>; // Global variables or plugin inputs
|
||||
};
|
||||
|
||||
@@ -107,7 +108,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
|
||||
stream = false,
|
||||
detail = false,
|
||||
parseQuote = false,
|
||||
retainDatasetCite = false,
|
||||
messages = [],
|
||||
variables = {},
|
||||
responseChatItemId = getNanoid(),
|
||||
@@ -187,6 +188,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
chatId
|
||||
});
|
||||
})();
|
||||
retainDatasetCite = retainDatasetCite && !!responseDetail;
|
||||
const isPlugin = app.type === AppTypeEnum.plugin;
|
||||
|
||||
// Check message type
|
||||
@@ -290,7 +292,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
chatConfig,
|
||||
histories: newHistories,
|
||||
stream,
|
||||
parseQuote,
|
||||
retainDatasetCite,
|
||||
maxRunTimes: WORKFLOW_MAX_RUN_TIMES,
|
||||
workflowStreamResponse: workflowResponseWrite,
|
||||
version: 'v2',
|
||||
@@ -401,6 +403,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
|
||||
return assistantResponses;
|
||||
})();
|
||||
const formatResponseContent = removeAIResponseCite(responseContent, retainDatasetCite);
|
||||
const error = flowResponses[flowResponses.length - 1]?.error;
|
||||
|
||||
res.json({
|
||||
@@ -411,7 +414,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
usage: { prompt_tokens: 1, completion_tokens: 1, total_tokens: 1 },
|
||||
choices: [
|
||||
{
|
||||
message: { role: 'assistant', content: responseContent },
|
||||
message: { role: 'assistant', content: formatResponseContent },
|
||||
finish_reason: 'stop',
|
||||
index: 0
|
||||
}
|
||||
|
@@ -87,6 +87,7 @@ const OutLink = (props: Props) => {
|
||||
const setChatBoxData = useContextSelector(ChatItemContext, (v) => v.setChatBoxData);
|
||||
const quoteData = useContextSelector(ChatItemContext, (v) => v.quoteData);
|
||||
const setQuoteData = useContextSelector(ChatItemContext, (v) => v.setQuoteData);
|
||||
const isResponseDetail = useContextSelector(ChatItemContext, (v) => v.isResponseDetail);
|
||||
|
||||
const chatRecords = useContextSelector(ChatRecordContext, (v) => v.chatRecords);
|
||||
const totalRecordsCount = useContextSelector(ChatRecordContext, (v) => v.totalRecordsCount);
|
||||
@@ -162,7 +163,8 @@ const OutLink = (props: Props) => {
|
||||
},
|
||||
responseChatItemId,
|
||||
chatId: completionChatId,
|
||||
...outLinkAuthData
|
||||
...outLinkAuthData,
|
||||
retainDatasetCite: isResponseDetail
|
||||
},
|
||||
onMessage: generatingMessage,
|
||||
abortCtrl: controller
|
||||
@@ -200,6 +202,7 @@ const OutLink = (props: Props) => {
|
||||
chatId,
|
||||
customVariables,
|
||||
outLinkAuthData,
|
||||
isResponseDetail,
|
||||
onUpdateHistoryTitle,
|
||||
setChatBoxData,
|
||||
forbidLoadChat,
|
||||
|
@@ -17,7 +17,7 @@ import {
|
||||
} from '@fastgpt/service/common/string/tiktoken/index';
|
||||
import { pushDataListToTrainingQueueByCollectionId } from '@fastgpt/service/core/dataset/training/controller';
|
||||
import { loadRequestMessages } from '@fastgpt/service/core/chat/utils';
|
||||
import { llmCompletionsBodyFormat, llmResponseToAnswerText } from '@fastgpt/service/core/ai/utils';
|
||||
import { llmCompletionsBodyFormat, formatLLMResponse } from '@fastgpt/service/core/ai/utils';
|
||||
import { LLMModelItemType } from '@fastgpt/global/core/ai/model.d';
|
||||
import {
|
||||
chunkAutoChunkSize,
|
||||
@@ -140,7 +140,7 @@ ${replaceVariable(Prompt_AgentQA.fixedText, { text })}`;
|
||||
modelData
|
||||
)
|
||||
});
|
||||
const { text: answer, usage } = await llmResponseToAnswerText(chatResponse);
|
||||
const { text: answer, usage } = await formatLLMResponse(chatResponse);
|
||||
const inputTokens = usage?.prompt_tokens || (await countGptMessagesTokens(messages));
|
||||
const outputTokens = usage?.completion_tokens || (await countPromptTokens(answer));
|
||||
|
||||
|
@@ -37,6 +37,7 @@ import { saveChat } from '@fastgpt/service/core/chat/saveChat';
|
||||
import { DispatchNodeResponseKeyEnum } from '@fastgpt/global/core/workflow/runtime/constants';
|
||||
import { createChatUsage } from '@fastgpt/service/support/wallet/usage/controller';
|
||||
import { UsageSourceEnum } from '@fastgpt/global/support/wallet/usage/constants';
|
||||
import { removeDatasetCiteText } from '@fastgpt/service/core/ai/utils';
|
||||
|
||||
export const pluginNodes2InputSchema = (
|
||||
nodes: { flowNodeType: FlowNodeTypeEnum; inputs: FlowNodeInputItemType[] }[]
|
||||
@@ -288,7 +289,7 @@ export const callMcpServerTool = async ({ key, toolName, inputs }: toolCallProps
|
||||
})();
|
||||
|
||||
// Format response content
|
||||
responseContent = responseContent.trim().replace(/\[\w+\]\(QUOTE\)/g, '');
|
||||
responseContent = removeDatasetCiteText(responseContent.trim(), false);
|
||||
|
||||
return responseContent;
|
||||
};
|
||||
|
@@ -132,7 +132,7 @@ export const streamFetch = ({
|
||||
variables,
|
||||
detail: true,
|
||||
stream: true,
|
||||
parseQuote: true
|
||||
retainDatasetCite: data.retainDatasetCite ?? true
|
||||
})
|
||||
};
|
||||
|
||||
|
@@ -4,22 +4,3 @@ export enum LoginPageTypeEnum {
|
||||
forgetPassword = 'forgetPassword',
|
||||
wechat = 'wechat'
|
||||
}
|
||||
|
||||
export const checkPasswordRule = (password: string) => {
|
||||
const patterns = [
|
||||
/\d/, // Contains digits
|
||||
/[a-z]/, // Contains lowercase letters
|
||||
/[A-Z]/, // Contains uppercase letters
|
||||
/[!@#$%^&*()_+=-]/ // Contains special characters
|
||||
];
|
||||
const validChars = /^[\dA-Za-z!@#$%^&*()_+=-]{6,100}$/;
|
||||
|
||||
// Check length and valid characters
|
||||
if (!validChars.test(password)) return false;
|
||||
|
||||
// Count how many patterns are satisfied
|
||||
const matchCount = patterns.filter((pattern) => pattern.test(password)).length;
|
||||
|
||||
// Must satisfy at least 2 patterns
|
||||
return matchCount >= 2;
|
||||
};
|
||||
|
Reference in New Issue
Block a user