From 9533b88a9fc7b586b210fc3984cdcafbd1e568df Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 25 Sep 2025 17:23:26 +0000 Subject: [PATCH] Replace string.match with RegExp.exec for better performance Co-authored-by: asukaminato0721 <30024051+asukaminato0721@users.noreply.github.com> --- web/app/components/base/block-input/index.tsx | 13 ++++++++++--- web/app/components/base/prompt-editor/constants.tsx | 11 ++++++++--- .../nodes/_base/components/variable/utils.ts | 5 ++++- .../workflow/nodes/http/components/curl-panel.tsx | 11 +++++++++-- web/app/components/workflow/utils/node.ts | 2 +- web/i18n-config/auto-gen-i18n.js | 3 ++- web/i18n-config/check-i18n-sync.js | 6 ++++-- web/i18n-config/check-i18n.js | 12 ++++++++---- web/i18n-config/generate-i18n-types.js | 3 ++- web/utils/var.ts | 10 ++++++++-- 10 files changed, 56 insertions(+), 20 deletions(-) diff --git a/web/app/components/base/block-input/index.tsx b/web/app/components/base/block-input/index.tsx index ae6f77fab3..1c305e20b2 100644 --- a/web/app/components/base/block-input/index.tsx +++ b/web/app/components/base/block-input/index.tsx @@ -12,9 +12,15 @@ import { checkKeys } from '@/utils/var' const regex = /\{\{([^}]+)\}\}/g export const getInputKeys = (value: string) => { - const keys = value.match(regex)?.map((item) => { + const matches: string[] = [] + let match + regex.lastIndex = 0 + while ((match = regex.exec(value)) !== null) + matches.push(match[0]) + + const keys = matches.map((item) => { return item.replace('{{', '').replace('}}', '') - }) || [] + }) const keyObj: Record = {} // remove duplicate keys const res: string[] = [] @@ -69,7 +75,8 @@ const BlockInput: FC = ({ const renderSafeContent = (value: string) => { const parts = value.split(/(\{\{[^}]+\}\}|\n)/g) return parts.map((part, index) => { - const variableMatch = part.match(/^\{\{([^}]+)\}\}$/) + const variableRegex = /^\{\{([^}]+)\}\}$/ + const variableMatch = variableRegex.exec(part) if (variableMatch) { return ( { if (!text || typeof text !== 'string') return [] - const allVars = text.match(/{{#([^#]*)#}}/g) - if (allVars && allVars?.length > 0) { + const matches: string[] = [] + const regex = /{{#([^#]*)#}}/g + let match + while ((match = regex.exec(text)) !== null) + matches.push(match[0]) + + if (matches && matches?.length > 0) { // {{#context#}}, {{#query#}} is not input vars - const inputVars = allVars + const inputVars = matches .filter(item => item.includes('.')) .map((item) => { const valueSelector = item.replace('{{#', '').replace('#}}', '').split('.') diff --git a/web/app/components/workflow/nodes/_base/components/variable/utils.ts b/web/app/components/workflow/nodes/_base/components/variable/utils.ts index 10919e198b..7cd487d48e 100644 --- a/web/app/components/workflow/nodes/_base/components/variable/utils.ts +++ b/web/app/components/workflow/nodes/_base/components/variable/utils.ts @@ -1202,7 +1202,10 @@ const matchNotSystemVars = (prompts: string[]) => { prompts.forEach((prompt) => { VAR_REGEX.lastIndex = 0 if (typeof prompt !== 'string') return - allVars.push(...(prompt.match(VAR_REGEX) || [])) + + let match + while ((match = VAR_REGEX.exec(prompt)) !== null) + allVars.push(match[0]) }) const uniqVars = uniq(allVars).map(v => v.replaceAll('{{#', '').replace('#}}', '').split('.'), diff --git a/web/app/components/workflow/nodes/http/components/curl-panel.tsx b/web/app/components/workflow/nodes/http/components/curl-panel.tsx index a5339a1f39..ffb4c67ffb 100644 --- a/web/app/components/workflow/nodes/http/components/curl-panel.tsx +++ b/web/app/components/workflow/nodes/http/components/curl-panel.tsx @@ -29,7 +29,13 @@ const parseCurl = (curlCommand: string): { node: HttpNodeType | null; error: str params: '', body: { type: BodyType.none, data: '' }, } - const args = curlCommand.match(/(?:[^\s"']+|"[^"]*"|'[^']*')+/g) || [] + const regex = /(?:[^\s"']+|"[^"]*"|'[^']*')+/g + const matches: string[] = [] + let match + while ((match = regex.exec(curlCommand)) !== null) + matches.push(match[0]) + + const args = matches let hasData = false for (let i = 1; i < args.length; i++) { @@ -75,7 +81,8 @@ const parseCurl = (curlCommand: string): { node: HttpNodeType | null; error: str // To support command like `curl -F "file=@/path/to/file;type=application/zip"` // the `;type=application/zip` should translate to `Content-Type: application/zip` - const typeMatch = value.match(/^(.+?);type=(.+)$/) + const typeRegex = /^(.+?);type=(.+)$/ + const typeMatch = typeRegex.exec(value) if (typeMatch) { const [, actualValue, mimeType] = typeMatch value = actualValue diff --git a/web/app/components/workflow/utils/node.ts b/web/app/components/workflow/utils/node.ts index 726908bff1..97ca7553e8 100644 --- a/web/app/components/workflow/utils/node.ts +++ b/web/app/components/workflow/utils/node.ts @@ -105,7 +105,7 @@ export function getLoopStartNode(loopId: string): Node { export const genNewNodeTitleFromOld = (oldTitle: string) => { const regex = /^(.+?)\s*\((\d+)\)\s*$/ - const match = oldTitle.match(regex) + const match = regex.exec(oldTitle) if (match) { const title = match[1] diff --git a/web/i18n-config/auto-gen-i18n.js b/web/i18n-config/auto-gen-i18n.js index 8949a15e52..c23e273b61 100644 --- a/web/i18n-config/auto-gen-i18n.js +++ b/web/i18n-config/auto-gen-i18n.js @@ -183,7 +183,8 @@ export default translation if (fs.existsSync(toGenLanguageFilePath)) { const originalContent = fs.readFileSync(toGenLanguageFilePath, 'utf8') // Extract original template literal content for resolutionTooltip - const originalMatch = originalContent.match(/(resolutionTooltip):\s*`([^`]*)`/s) + const regex = /(resolutionTooltip):\s*`([^`]*)`/s + const originalMatch = regex.exec(originalContent) if (originalMatch) { const [fullMatch, key, value] = originalMatch res = res.replace( diff --git a/web/i18n-config/check-i18n-sync.js b/web/i18n-config/check-i18n-sync.js index e67c567f49..4da23dd35c 100644 --- a/web/i18n-config/check-i18n-sync.js +++ b/web/i18n-config/check-i18n-sync.js @@ -10,7 +10,8 @@ function getNamespacesFromConfig() { const configContent = fs.readFileSync(configPath, 'utf8') // Extract NAMESPACES array using regex - const namespacesMatch = configContent.match(/const NAMESPACES = \[([\s\S]*?)\]/) + const namespacesRegex = /const NAMESPACES = \[([\s\S]*?)\]/ + const namespacesMatch = namespacesRegex.exec(configContent) if (!namespacesMatch) { throw new Error('Could not find NAMESPACES array in i18next-config.ts') } @@ -36,7 +37,8 @@ function getNamespacesFromTypes() { const typesContent = fs.readFileSync(typesPath, 'utf8') // Extract namespaces from Messages type - const messagesMatch = typesContent.match(/export type Messages = \{([\s\S]*?)\}/) + const messagesRegex = /export type Messages = \{([\s\S]*?)\}/ + const messagesMatch = messagesRegex.exec(typesContent) if (!messagesMatch) { return null } diff --git a/web/i18n-config/check-i18n.js b/web/i18n-config/check-i18n.js index cc55277613..faf431033c 100644 --- a/web/i18n-config/check-i18n.js +++ b/web/i18n-config/check-i18n.js @@ -157,7 +157,8 @@ async function removeExtraKeysFromFile(language, fileName, extraKeys) { const trimmedLine = line.trim() // Track current object path - const keyMatch = trimmedLine.match(/^(\w+)\s*:\s*{/) + const keyRegex = /^(\w+)\s*:\s*{/ + const keyMatch = keyRegex.exec(trimmedLine) if (keyMatch) { currentPath.push(keyMatch[1]) braceDepth++ @@ -170,7 +171,8 @@ async function removeExtraKeysFromFile(language, fileName, extraKeys) { } // Check if this line matches our target key - const leafKeyMatch = trimmedLine.match(/^(\w+)\s*:/) + const leafKeyRegex = /^(\w+)\s*:/ + const leafKeyMatch = leafKeyRegex.exec(trimmedLine) if (leafKeyMatch) { const fullPath = [...currentPath, leafKeyMatch[1]] const fullPathString = fullPath.join('.') @@ -191,7 +193,8 @@ async function removeExtraKeysFromFile(language, fileName, extraKeys) { const trimmedKeyLine = keyLine.trim() // If key line ends with ":" (not ":", "{ " or complete value), it's likely multiline - if (trimmedKeyLine.endsWith(':') && !trimmedKeyLine.includes('{') && !trimmedKeyLine.match(/:\s*['"`]/)) { + const valueRegex = /:\s*['"`]/ + if (trimmedKeyLine.endsWith(':') && !trimmedKeyLine.includes('{') && !valueRegex.test(trimmedKeyLine)) { // Find the value lines that belong to this key let currentLine = targetLineIndex + 1 let foundValue = false @@ -207,7 +210,8 @@ async function removeExtraKeysFromFile(language, fileName, extraKeys) { } // Check if this line starts a new key (indicates end of current value) - if (trimmed.match(/^\w+\s*:/)) + const keyStartRegex = /^\w+\s*:/ + if (keyStartRegex.test(trimmed)) break // Check if this line is part of the value diff --git a/web/i18n-config/generate-i18n-types.js b/web/i18n-config/generate-i18n-types.js index ba34446962..4cd593cab5 100644 --- a/web/i18n-config/generate-i18n-types.js +++ b/web/i18n-config/generate-i18n-types.js @@ -10,7 +10,8 @@ function getNamespacesFromConfig() { const configContent = fs.readFileSync(configPath, 'utf8') // Extract NAMESPACES array using regex - const namespacesMatch = configContent.match(/const NAMESPACES = \[([\s\S]*?)\]/) + const namespacesRegex = /const NAMESPACES = \[([\s\S]*?)\]/ + const namespacesMatch = namespacesRegex.exec(configContent) if (!namespacesMatch) { throw new Error('Could not find NAMESPACES array in i18next-config.ts') } diff --git a/web/utils/var.ts b/web/utils/var.ts index a9849bdc4b..4fb4a1fb48 100644 --- a/web/utils/var.ts +++ b/web/utils/var.ts @@ -102,11 +102,17 @@ export const getVars = (value: string) => { if (!value) return [] - const keys = value.match(varRegex)?.filter((item) => { + const matches: string[] = [] + let match + varRegex.lastIndex = 0 + while ((match = varRegex.exec(value)) !== null) + matches.push(match[0]) + + const keys = matches.filter((item) => { return ![CONTEXT_PLACEHOLDER_TEXT, HISTORY_PLACEHOLDER_TEXT, QUERY_PLACEHOLDER_TEXT, PRE_PROMPT_PLACEHOLDER_TEXT].includes(item) }).map((item) => { return item.replace('{{', '').replace('}}', '') - }).filter(key => key.length <= MAX_VAR_KEY_LENGTH) || [] + }).filter(key => key.length <= MAX_VAR_KEY_LENGTH) const keyObj: Record = {} // remove duplicate keys const res: string[] = []