Merge branch 'main' into feature/OpenAI-Response-API
This commit is contained in:
commit
7867830ed4
|
|
@ -18,7 +18,7 @@ If you like to persist your data (flows, logs, credentials, storage), set these
|
|||
- SECRETKEY_PATH=/root/.flowise
|
||||
- BLOB_STORAGE_PATH=/root/.flowise/storage
|
||||
|
||||
Flowise also support different environment variables to configure your instance. Read [more](https://docs.flowiseai.com/environment-variables)
|
||||
Flowise also support different environment variables to configure your instance. Read [more](https://docs.flowiseai.com/configuration/environment-variables)
|
||||
|
||||
## Queue Mode:
|
||||
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@
|
|||
|
||||
[English](../README.md) | 繁體中文 | [简体中文](./README-ZH.md) | [日本語](./README-JA.md) | [한국어](./README-KR.md)
|
||||
|
||||
<h3>可視化建構 AI/LLM 流程</h3>
|
||||
<h3>可視化建置 AI/LLM 流程</h3>
|
||||
<a href="https://github.com/FlowiseAI/Flowise">
|
||||
<img width="100%" src="https://github.com/FlowiseAI/Flowise/blob/main/images/flowise_agentflow.gif?raw=true"></a>
|
||||
|
||||
|
|
@ -37,16 +37,16 @@
|
|||
|
||||
### Docker Compose
|
||||
|
||||
1. 克隆 Flowise 項目
|
||||
2. 進入項目根目錄的 `docker` 文件夾
|
||||
3. 複製 `.env.example` 文件,粘貼到相同位置,並重命名為 `.env` 文件
|
||||
1. 複製 Flowise 專案
|
||||
2. 進入專案根目錄的 `docker` 資料夾
|
||||
3. 複製 `.env.example` 文件,貼到相同位置,並重新命名為 `.env` 文件
|
||||
4. `docker compose up -d`
|
||||
5. 打開 [http://localhost:3000](http://localhost:3000)
|
||||
6. 您可以通過 `docker compose stop` 停止容器
|
||||
6. 您可以透過 `docker compose stop` 停止容器
|
||||
|
||||
### Docker 映像
|
||||
|
||||
1. 本地構建映像:
|
||||
1. 本地建置映像:
|
||||
```bash
|
||||
docker build --no-cache -t flowise .
|
||||
```
|
||||
|
|
@ -63,7 +63,7 @@
|
|||
|
||||
## 👨💻 開發者
|
||||
|
||||
Flowise 在單個 mono 存儲庫中有 3 個不同的模塊。
|
||||
Flowise 在單個 mono 儲存庫中有 3 個不同的模組。
|
||||
|
||||
- `server`: 提供 API 邏輯的 Node 後端
|
||||
- `ui`: React 前端
|
||||
|
|
@ -79,33 +79,33 @@ Flowise 在單個 mono 存儲庫中有 3 個不同的模塊。
|
|||
|
||||
### 設置
|
||||
|
||||
1. 克隆存儲庫
|
||||
1. 複製儲存庫
|
||||
|
||||
```bash
|
||||
git clone https://github.com/FlowiseAI/Flowise.git
|
||||
```
|
||||
|
||||
2. 進入存儲庫文件夾
|
||||
2. 進入儲存庫文件夾
|
||||
|
||||
```bash
|
||||
cd Flowise
|
||||
```
|
||||
|
||||
3. 安裝所有模塊的所有依賴項:
|
||||
3. 安裝所有模組的所有依賴項:
|
||||
|
||||
```bash
|
||||
pnpm install
|
||||
```
|
||||
|
||||
4. 構建所有代碼:
|
||||
4. 建置所有程式碼:
|
||||
|
||||
```bash
|
||||
pnpm build
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>退出代碼 134(JavaScript 堆內存不足)</summary>
|
||||
如果在運行上述 `build` 腳本時遇到此錯誤,請嘗試增加 Node.js 堆大小並重新運行腳本:
|
||||
<summary>Exit code 134(JavaScript heap out of memory)</summary>
|
||||
如果在運行上述 `build` 腳本時遇到此錯誤,請嘗試增加 Node.js 中的 Heap 記憶體大小並重新運行腳本:
|
||||
|
||||
export NODE_OPTIONS="--max-old-space-size=4096"
|
||||
pnpm build
|
||||
|
|
@ -118,9 +118,9 @@ Flowise 在單個 mono 存儲庫中有 3 個不同的模塊。
|
|||
pnpm start
|
||||
```
|
||||
|
||||
您現在可以訪問 [http://localhost:3000](http://localhost:3000)
|
||||
您現在可以開啟 [http://localhost:3000](http://localhost:3000)
|
||||
|
||||
6. 對於開發構建:
|
||||
6. 對於開發建置:
|
||||
|
||||
- 在 `packages/ui` 中創建 `.env` 文件並指定 `VITE_PORT`(參考 `.env.example`)
|
||||
- 在 `packages/server` 中創建 `.env` 文件並指定 `PORT`(參考 `.env.example`)
|
||||
|
|
@ -130,19 +130,19 @@ Flowise 在單個 mono 存儲庫中有 3 個不同的模塊。
|
|||
pnpm dev
|
||||
```
|
||||
|
||||
任何代碼更改都會自動重新加載應用程序 [http://localhost:8080](http://localhost:8080)
|
||||
任何程式碼更改都會自動重新加載應用程式 [http://localhost:8080](http://localhost:8080)
|
||||
|
||||
## 🌱 環境變量
|
||||
## 🌱 環境變數
|
||||
|
||||
Flowise 支持不同的環境變量來配置您的實例。您可以在 `packages/server` 文件夾中的 `.env` 文件中指定以下變量。閱讀 [更多](https://github.com/FlowiseAI/Flowise/blob/main/CONTRIBUTING.md#-env-variables)
|
||||
Flowise 支持不同的環境變數來配置您的實例。您可以在 `packages/server` 文件夾中的 `.env` 文件中指定以下變數。閱讀 [更多](https://github.com/FlowiseAI/Flowise/blob/main/CONTRIBUTING.md#-env-variables)
|
||||
|
||||
## 📖 文檔
|
||||
|
||||
[Flowise 文檔](https://docs.flowiseai.com/)
|
||||
|
||||
## 🌐 自我托管
|
||||
## 🌐 自行架設
|
||||
|
||||
在您現有的基礎設施中部署 Flowise 自我托管,我們支持各種 [部署](https://docs.flowiseai.com/configuration/deployment)
|
||||
在您現有的基礎設施中部署 Flowise,我們支持各種自行架設選項 [部署](https://docs.flowiseai.com/configuration/deployment)
|
||||
|
||||
- [AWS](https://docs.flowiseai.com/configuration/deployment/aws)
|
||||
- [Azure](https://docs.flowiseai.com/configuration/deployment/azure)
|
||||
|
|
@ -178,9 +178,9 @@ Flowise 支持不同的環境變量來配置您的實例。您可以在 `package
|
|||
|
||||
</details>
|
||||
|
||||
## ☁️ Flowise 雲
|
||||
## ☁️ Flowise 雲端平台
|
||||
|
||||
[開始使用 Flowise 雲](https://flowiseai.com/)
|
||||
[開始使用 Flowise 雲端平台](https://flowiseai.com/)
|
||||
|
||||
## 🙋 支持
|
||||
|
||||
|
|
@ -194,9 +194,9 @@ Flowise 支持不同的環境變量來配置您的實例。您可以在 `package
|
|||
<img src="https://contrib.rocks/image?repo=FlowiseAI/Flowise" />
|
||||
</a>
|
||||
|
||||
請參閱 [貢獻指南](../CONTRIBUTING.md)。如果您有任何問題或問題,請通過 [Discord](https://discord.gg/jbaHfsRVBW) 與我們聯繫。
|
||||
請參閱 [貢獻指南](../CONTRIBUTING.md)。如果您有任何問題或問題,請透過 [Discord](https://discord.gg/jbaHfsRVBW) 與我們聯繫。
|
||||
[](https://star-history.com/#FlowiseAI/Flowise&Date)
|
||||
|
||||
## 📄 許可證
|
||||
|
||||
此存儲庫中的源代碼根據 [Apache 許可證版本 2.0](../LICENSE.md) 提供。
|
||||
此儲存庫中的原始碼根據 [Apache 2.0 授權條款](../LICENSE.md) 授權使用。
|
||||
|
|
|
|||
|
|
@ -746,6 +746,14 @@
|
|||
{
|
||||
"name": "groqChat",
|
||||
"models": [
|
||||
{
|
||||
"label": "openai/gpt-oss-20b",
|
||||
"name": "openai/gpt-oss-20b"
|
||||
},
|
||||
{
|
||||
"label": "openai/gpt-oss-120b",
|
||||
"name": "openai/gpt-oss-120b"
|
||||
},
|
||||
{
|
||||
"label": "meta-llama/llama-4-maverick-17b-128e-instruct",
|
||||
"name": "meta-llama/llama-4-maverick-17b-128e-instruct"
|
||||
|
|
|
|||
|
|
@ -4,6 +4,25 @@ import { GoogleGenerativeAIEmbeddings, GoogleGenerativeAIEmbeddingsParams } from
|
|||
import { TaskType } from '@google/generative-ai'
|
||||
import { MODEL_TYPE, getModels } from '../../../src/modelLoader'
|
||||
|
||||
class GoogleGenerativeAIEmbeddingsWithStripNewLines extends GoogleGenerativeAIEmbeddings {
|
||||
stripNewLines: boolean
|
||||
|
||||
constructor(params: GoogleGenerativeAIEmbeddingsParams & { stripNewLines?: boolean }) {
|
||||
super(params)
|
||||
this.stripNewLines = params.stripNewLines ?? false
|
||||
}
|
||||
|
||||
async embedDocuments(texts: string[]): Promise<number[][]> {
|
||||
const processedTexts = this.stripNewLines ? texts.map((text) => text.replace(/\n/g, ' ')) : texts
|
||||
return super.embedDocuments(processedTexts)
|
||||
}
|
||||
|
||||
async embedQuery(text: string): Promise<number[]> {
|
||||
const processedText = this.stripNewLines ? text.replace(/\n/g, ' ') : text
|
||||
return super.embedQuery(processedText)
|
||||
}
|
||||
}
|
||||
|
||||
class GoogleGenerativeAIEmbedding_Embeddings implements INode {
|
||||
label: string
|
||||
name: string
|
||||
|
|
@ -24,7 +43,7 @@ class GoogleGenerativeAIEmbedding_Embeddings implements INode {
|
|||
this.icon = 'GoogleGemini.svg'
|
||||
this.category = 'Embeddings'
|
||||
this.description = 'Google Generative API to generate embeddings for a given text'
|
||||
this.baseClasses = [this.type, ...getBaseClasses(GoogleGenerativeAIEmbeddings)]
|
||||
this.baseClasses = [this.type, ...getBaseClasses(GoogleGenerativeAIEmbeddingsWithStripNewLines)]
|
||||
this.credential = {
|
||||
label: 'Connect Credential',
|
||||
name: 'credential',
|
||||
|
|
@ -55,6 +74,14 @@ class GoogleGenerativeAIEmbedding_Embeddings implements INode {
|
|||
{ label: 'CLUSTERING', name: 'CLUSTERING' }
|
||||
],
|
||||
default: 'TASK_TYPE_UNSPECIFIED'
|
||||
},
|
||||
{
|
||||
label: 'Strip New Lines',
|
||||
name: 'stripNewLines',
|
||||
type: 'boolean',
|
||||
optional: true,
|
||||
additionalParams: true,
|
||||
description: 'Remove new lines from input text before embedding to reduce token count'
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -71,6 +98,7 @@ class GoogleGenerativeAIEmbedding_Embeddings implements INode {
|
|||
const modelName = nodeData.inputs?.modelName as string
|
||||
const credentialData = await getCredentialData(nodeData.credential ?? '', options)
|
||||
const apiKey = getCredentialParam('googleGenerativeAPIKey', credentialData, nodeData)
|
||||
const stripNewLines = nodeData.inputs?.stripNewLines as boolean
|
||||
|
||||
let taskType: TaskType
|
||||
switch (nodeData.inputs?.tasktype as string) {
|
||||
|
|
@ -93,13 +121,14 @@ class GoogleGenerativeAIEmbedding_Embeddings implements INode {
|
|||
taskType = TaskType.TASK_TYPE_UNSPECIFIED
|
||||
break
|
||||
}
|
||||
const obj: GoogleGenerativeAIEmbeddingsParams = {
|
||||
const obj: GoogleGenerativeAIEmbeddingsParams & { stripNewLines?: boolean } = {
|
||||
apiKey: apiKey,
|
||||
modelName: modelName,
|
||||
taskType: taskType
|
||||
taskType: taskType,
|
||||
stripNewLines
|
||||
}
|
||||
|
||||
const model = new GoogleGenerativeAIEmbeddings(obj)
|
||||
const model = new GoogleGenerativeAIEmbeddingsWithStripNewLines(obj)
|
||||
return model
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,25 @@ import { ICommonObject, INode, INodeData, INodeOptionsValue, INodeParams } from
|
|||
import { MODEL_TYPE, getModels, getRegions } from '../../../src/modelLoader'
|
||||
import { getBaseClasses } from '../../../src/utils'
|
||||
|
||||
class VertexAIEmbeddingsWithStripNewLines extends VertexAIEmbeddings {
|
||||
stripNewLines: boolean
|
||||
|
||||
constructor(params: GoogleVertexAIEmbeddingsInput & { stripNewLines?: boolean }) {
|
||||
super(params)
|
||||
this.stripNewLines = params.stripNewLines ?? false
|
||||
}
|
||||
|
||||
async embedDocuments(texts: string[]): Promise<number[][]> {
|
||||
const processedTexts = this.stripNewLines ? texts.map((text) => text.replace(/\n/g, ' ')) : texts
|
||||
return super.embedDocuments(processedTexts)
|
||||
}
|
||||
|
||||
async embedQuery(text: string): Promise<number[]> {
|
||||
const processedText = this.stripNewLines ? text.replace(/\n/g, ' ') : text
|
||||
return super.embedQuery(processedText)
|
||||
}
|
||||
}
|
||||
|
||||
class GoogleVertexAIEmbedding_Embeddings implements INode {
|
||||
label: string
|
||||
name: string
|
||||
|
|
@ -24,7 +43,7 @@ class GoogleVertexAIEmbedding_Embeddings implements INode {
|
|||
this.icon = 'GoogleVertex.svg'
|
||||
this.category = 'Embeddings'
|
||||
this.description = 'Google vertexAI API to generate embeddings for a given text'
|
||||
this.baseClasses = [this.type, ...getBaseClasses(VertexAIEmbeddings)]
|
||||
this.baseClasses = [this.type, ...getBaseClasses(VertexAIEmbeddingsWithStripNewLines)]
|
||||
this.credential = {
|
||||
label: 'Connect Credential',
|
||||
name: 'credential',
|
||||
|
|
@ -49,6 +68,14 @@ class GoogleVertexAIEmbedding_Embeddings implements INode {
|
|||
type: 'asyncOptions',
|
||||
loadMethod: 'listRegions',
|
||||
optional: true
|
||||
},
|
||||
{
|
||||
label: 'Strip New Lines',
|
||||
name: 'stripNewLines',
|
||||
type: 'boolean',
|
||||
optional: true,
|
||||
additionalParams: true,
|
||||
description: 'Remove new lines from input text before embedding to reduce token count'
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -66,9 +93,11 @@ class GoogleVertexAIEmbedding_Embeddings implements INode {
|
|||
async init(nodeData: INodeData, _: string, options: ICommonObject): Promise<any> {
|
||||
const modelName = nodeData.inputs?.modelName as string
|
||||
const region = nodeData.inputs?.region as string
|
||||
const stripNewLines = nodeData.inputs?.stripNewLines as boolean
|
||||
|
||||
const obj: GoogleVertexAIEmbeddingsInput = {
|
||||
model: modelName
|
||||
const obj: GoogleVertexAIEmbeddingsInput & { stripNewLines?: boolean } = {
|
||||
model: modelName,
|
||||
stripNewLines
|
||||
}
|
||||
|
||||
const authOptions = await buildGoogleCredentials(nodeData, options)
|
||||
|
|
@ -76,7 +105,7 @@ class GoogleVertexAIEmbedding_Embeddings implements INode {
|
|||
|
||||
if (region) obj.location = region
|
||||
|
||||
const model = new VertexAIEmbeddings(obj)
|
||||
const model = new VertexAIEmbeddingsWithStripNewLines(obj)
|
||||
return model
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,6 +9,9 @@ import { getRunningExpressApp } from '../../utils/getRunningExpressApp'
|
|||
import { checkUsageLimit } from '../../utils/quotaUsage'
|
||||
import { RateLimiterManager } from '../../utils/rateLimit'
|
||||
import { getPageAndLimitParams } from '../../utils/pagination'
|
||||
import { WorkspaceUserErrorMessage, WorkspaceUserService } from '../../enterprise/services/workspace-user.service'
|
||||
import { QueryRunner } from 'typeorm'
|
||||
import { GeneralErrorMessage } from '../../utils/constants'
|
||||
|
||||
const checkIfChatflowIsValidForStreaming = async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
|
|
@ -197,6 +200,7 @@ const updateChatflow = async (req: Request, res: Response, next: NextFunction) =
|
|||
}
|
||||
|
||||
const getSinglePublicChatflow = async (req: Request, res: Response, next: NextFunction) => {
|
||||
let queryRunner: QueryRunner | undefined
|
||||
try {
|
||||
if (typeof req.params === 'undefined' || !req.params.id) {
|
||||
throw new InternalFlowiseError(
|
||||
|
|
@ -204,10 +208,23 @@ const getSinglePublicChatflow = async (req: Request, res: Response, next: NextFu
|
|||
`Error: chatflowsController.getSinglePublicChatflow - id not provided!`
|
||||
)
|
||||
}
|
||||
const apiResponse = await chatflowsService.getSinglePublicChatflow(req.params.id)
|
||||
return res.json(apiResponse)
|
||||
const chatflow = await chatflowsService.getChatflowById(req.params.id)
|
||||
if (!chatflow) return res.status(StatusCodes.NOT_FOUND).json({ message: 'Chatflow not found' })
|
||||
if (chatflow.isPublic) return res.status(StatusCodes.OK).json(chatflow)
|
||||
if (!req.user) return res.status(StatusCodes.UNAUTHORIZED).json({ message: GeneralErrorMessage.UNAUTHORIZED })
|
||||
queryRunner = getRunningExpressApp().AppDataSource.createQueryRunner()
|
||||
const workspaceUserService = new WorkspaceUserService()
|
||||
const workspaceUser = await workspaceUserService.readWorkspaceUserByUserId(req.user.id, queryRunner)
|
||||
if (workspaceUser.length === 0)
|
||||
return res.status(StatusCodes.NOT_FOUND).json({ message: WorkspaceUserErrorMessage.WORKSPACE_USER_NOT_FOUND })
|
||||
const workspaceIds = workspaceUser.map((user) => user.workspaceId)
|
||||
if (!workspaceIds.includes(chatflow.workspaceId))
|
||||
return res.status(StatusCodes.BAD_REQUEST).json({ message: 'You are not in the workspace that owns this chatflow' })
|
||||
return res.status(StatusCodes.OK).json(chatflow)
|
||||
} catch (error) {
|
||||
next(error)
|
||||
} finally {
|
||||
if (queryRunner) await queryRunner.release()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -339,31 +339,6 @@ const updateChatflow = async (
|
|||
}
|
||||
}
|
||||
|
||||
// Get specific chatflow via id (PUBLIC endpoint, used when sharing chatbot link)
|
||||
const getSinglePublicChatflow = async (chatflowId: string): Promise<any> => {
|
||||
try {
|
||||
const appServer = getRunningExpressApp()
|
||||
const dbResponse = await appServer.AppDataSource.getRepository(ChatFlow).findOneBy({
|
||||
id: chatflowId
|
||||
})
|
||||
if (dbResponse && dbResponse.isPublic) {
|
||||
return dbResponse
|
||||
} else if (dbResponse && !dbResponse.isPublic) {
|
||||
throw new InternalFlowiseError(StatusCodes.UNAUTHORIZED, `Unauthorized`)
|
||||
}
|
||||
throw new InternalFlowiseError(StatusCodes.NOT_FOUND, `Chatflow ${chatflowId} not found`)
|
||||
} catch (error) {
|
||||
if (error instanceof InternalFlowiseError && error.statusCode === StatusCodes.UNAUTHORIZED) {
|
||||
throw error
|
||||
} else {
|
||||
throw new InternalFlowiseError(
|
||||
StatusCodes.INTERNAL_SERVER_ERROR,
|
||||
`Error: chatflowsService.getSinglePublicChatflow - ${getErrorMessage(error)}`
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Get specific chatflow chatbotConfig via id (PUBLIC endpoint, used to retrieve config for embedded chat)
|
||||
// Safe as public endpoint as chatbotConfig doesn't contain sensitive credential
|
||||
const getSinglePublicChatbotConfig = async (chatflowId: string): Promise<any> => {
|
||||
|
|
@ -438,7 +413,6 @@ export default {
|
|||
getChatflowById,
|
||||
saveChatflow,
|
||||
updateChatflow,
|
||||
getSinglePublicChatflow,
|
||||
getSinglePublicChatbotConfig,
|
||||
checkIfChatflowHasChanged,
|
||||
getAllChatflowsCountByOrganization
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ import marketplacesService from '../marketplaces'
|
|||
import toolsService from '../tools'
|
||||
import variableService from '../variables'
|
||||
import { Platform } from '../../Interface'
|
||||
import { sanitizeNullBytes } from '../../utils/sanitize.util'
|
||||
|
||||
type ExportInput = {
|
||||
agentflow: boolean
|
||||
|
|
@ -753,6 +754,8 @@ const importData = async (importData: ExportData, orgId: string, activeWorkspace
|
|||
importData = await replaceDuplicateIdsForVariable(queryRunner, importData, importData.Variable)
|
||||
}
|
||||
|
||||
importData = sanitizeNullBytes(importData)
|
||||
|
||||
await queryRunner.startTransaction()
|
||||
|
||||
if (importData.AgentFlow.length > 0) await queryRunner.manager.save(ChatFlow, importData.AgentFlow)
|
||||
|
|
|
|||
|
|
@ -1403,6 +1403,29 @@ export const executeAgentFlow = async ({
|
|||
}
|
||||
}
|
||||
|
||||
// Check if startState has been overridden from overrideConfig.startState and is enabled
|
||||
const startAgentflowNode = nodes.find((node) => node.data.name === 'startAgentflow')
|
||||
const isStartStateEnabled =
|
||||
nodeOverrides && startAgentflowNode
|
||||
? nodeOverrides[startAgentflowNode.data.label]?.find((param: any) => param.name === 'startState')?.enabled ?? false
|
||||
: false
|
||||
|
||||
if (isStartStateEnabled && overrideConfig?.startState) {
|
||||
if (Array.isArray(overrideConfig.startState)) {
|
||||
// Handle array format: [{"key": "foo", "value": "foo4"}]
|
||||
const overrideStateObj: ICommonObject = {}
|
||||
for (const item of overrideConfig.startState) {
|
||||
if (item.key && item.value !== undefined) {
|
||||
overrideStateObj[item.key] = item.value
|
||||
}
|
||||
}
|
||||
previousState = { ...previousState, ...overrideStateObj }
|
||||
} else if (typeof overrideConfig.startState === 'object') {
|
||||
// Object override: "startState": {...}
|
||||
previousState = { ...previousState, ...overrideConfig.startState }
|
||||
}
|
||||
}
|
||||
|
||||
agentflowRuntime.state = previousState
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,32 @@
|
|||
export function sanitizeNullBytes(obj: any): any {
|
||||
const stack = [obj]
|
||||
|
||||
while (stack.length) {
|
||||
const current = stack.pop()
|
||||
|
||||
if (Array.isArray(current)) {
|
||||
for (let i = 0; i < current.length; i++) {
|
||||
const val = current[i]
|
||||
if (typeof val === 'string') {
|
||||
// eslint-disable-next-line no-control-regex
|
||||
current[i] = val.replace(/\u0000/g, '')
|
||||
} else if (val && typeof val === 'object') {
|
||||
stack.push(val)
|
||||
}
|
||||
}
|
||||
} else if (current && typeof current === 'object') {
|
||||
for (const key in current) {
|
||||
if (!Object.hasOwnProperty.call(current, key)) continue
|
||||
const val = current[key]
|
||||
if (typeof val === 'string') {
|
||||
// eslint-disable-next-line no-control-regex
|
||||
current[key] = val.replace(/\u0000/g, '')
|
||||
} else if (val && typeof val === 'object') {
|
||||
stack.push(val)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return obj
|
||||
}
|
||||
Loading…
Reference in New Issue