645 lines
22 KiB
JavaScript
645 lines
22 KiB
JavaScript
import moment from 'moment'
|
|
|
|
export const getUniqueNodeId = (nodeData, nodes) => {
|
|
// Get amount of same nodes
|
|
let totalSameNodes = 0
|
|
for (let i = 0; i < nodes.length; i += 1) {
|
|
const node = nodes[i]
|
|
if (node.data.name === nodeData.name) {
|
|
totalSameNodes += 1
|
|
}
|
|
}
|
|
|
|
// Get unique id
|
|
let nodeId = `${nodeData.name}_${totalSameNodes}`
|
|
for (let i = 0; i < nodes.length; i += 1) {
|
|
const node = nodes[i]
|
|
if (node.id === nodeId) {
|
|
totalSameNodes += 1
|
|
nodeId = `${nodeData.name}_${totalSameNodes}`
|
|
}
|
|
}
|
|
return nodeId
|
|
}
|
|
|
|
export const initializeDefaultNodeData = (nodeParams) => {
|
|
const initialValues = {}
|
|
|
|
for (let i = 0; i < nodeParams.length; i += 1) {
|
|
const input = nodeParams[i]
|
|
initialValues[input.name] = input.default || ''
|
|
}
|
|
|
|
return initialValues
|
|
}
|
|
|
|
export const initNode = (nodeData, newNodeId) => {
|
|
const inputAnchors = []
|
|
const inputParams = []
|
|
const incoming = nodeData.inputs ? nodeData.inputs.length : 0
|
|
const outgoing = 1
|
|
|
|
const whitelistTypes = [
|
|
'asyncOptions',
|
|
'options',
|
|
'multiOptions',
|
|
'datagrid',
|
|
'string',
|
|
'number',
|
|
'boolean',
|
|
'password',
|
|
'json',
|
|
'code',
|
|
'date',
|
|
'file',
|
|
'folder'
|
|
]
|
|
|
|
// Inputs
|
|
for (let i = 0; i < incoming; i += 1) {
|
|
const newInput = {
|
|
...nodeData.inputs[i],
|
|
id: `${newNodeId}-input-${nodeData.inputs[i].name}-${nodeData.inputs[i].type}`
|
|
}
|
|
if (whitelistTypes.includes(nodeData.inputs[i].type)) {
|
|
inputParams.push(newInput)
|
|
} else {
|
|
inputAnchors.push(newInput)
|
|
}
|
|
}
|
|
|
|
// Credential
|
|
if (nodeData.credential) {
|
|
const newInput = {
|
|
...nodeData.credential,
|
|
id: `${newNodeId}-input-${nodeData.credential.name}-${nodeData.credential.type}`
|
|
}
|
|
inputParams.unshift(newInput)
|
|
}
|
|
|
|
// Outputs
|
|
const outputAnchors = []
|
|
for (let i = 0; i < outgoing; i += 1) {
|
|
if (nodeData.outputs && nodeData.outputs.length) {
|
|
const options = []
|
|
for (let j = 0; j < nodeData.outputs.length; j += 1) {
|
|
let baseClasses = ''
|
|
let type = ''
|
|
|
|
const outputBaseClasses = nodeData.outputs[j].baseClasses ?? []
|
|
if (outputBaseClasses.length > 1) {
|
|
baseClasses = outputBaseClasses.join('|')
|
|
type = outputBaseClasses.join(' | ')
|
|
} else if (outputBaseClasses.length === 1) {
|
|
baseClasses = outputBaseClasses[0]
|
|
type = outputBaseClasses[0]
|
|
}
|
|
|
|
const newOutputOption = {
|
|
id: `${newNodeId}-output-${nodeData.outputs[j].name}-${baseClasses}`,
|
|
name: nodeData.outputs[j].name,
|
|
label: nodeData.outputs[j].label,
|
|
description: nodeData.outputs[j].description ?? '',
|
|
type
|
|
}
|
|
options.push(newOutputOption)
|
|
}
|
|
const newOutput = {
|
|
name: 'output',
|
|
label: 'Output',
|
|
type: 'options',
|
|
description: nodeData.outputs[0].description ?? '',
|
|
options,
|
|
default: nodeData.outputs[0].name
|
|
}
|
|
outputAnchors.push(newOutput)
|
|
} else {
|
|
const newOutput = {
|
|
id: `${newNodeId}-output-${nodeData.name}-${nodeData.baseClasses.join('|')}`,
|
|
name: nodeData.name,
|
|
label: nodeData.type,
|
|
description: nodeData.description ?? '',
|
|
type: nodeData.baseClasses.join(' | ')
|
|
}
|
|
outputAnchors.push(newOutput)
|
|
}
|
|
}
|
|
|
|
/* Initial
|
|
inputs = [
|
|
{
|
|
label: 'field_label_1',
|
|
name: 'string'
|
|
},
|
|
{
|
|
label: 'field_label_2',
|
|
name: 'CustomType'
|
|
}
|
|
]
|
|
|
|
=> Convert to inputs, inputParams, inputAnchors
|
|
|
|
=> inputs = { 'field': 'defaultvalue' } // Turn into inputs object with default values
|
|
|
|
=> // For inputs that are part of whitelistTypes
|
|
inputParams = [
|
|
{
|
|
label: 'field_label_1',
|
|
name: 'string'
|
|
}
|
|
]
|
|
|
|
=> // For inputs that are not part of whitelistTypes
|
|
inputAnchors = [
|
|
{
|
|
label: 'field_label_2',
|
|
name: 'CustomType'
|
|
}
|
|
]
|
|
*/
|
|
|
|
// Inputs
|
|
if (nodeData.inputs) {
|
|
nodeData.inputAnchors = inputAnchors
|
|
nodeData.inputParams = inputParams
|
|
nodeData.inputs = initializeDefaultNodeData(nodeData.inputs)
|
|
} else {
|
|
nodeData.inputAnchors = []
|
|
nodeData.inputParams = []
|
|
nodeData.inputs = {}
|
|
}
|
|
|
|
// Outputs
|
|
if (nodeData.outputs) {
|
|
nodeData.outputs = initializeDefaultNodeData(outputAnchors)
|
|
} else {
|
|
nodeData.outputs = {}
|
|
}
|
|
nodeData.outputAnchors = outputAnchors
|
|
|
|
// Credential
|
|
if (nodeData.credential) nodeData.credential = ''
|
|
|
|
nodeData.id = newNodeId
|
|
|
|
return nodeData
|
|
}
|
|
|
|
export const isValidConnection = (connection, reactFlowInstance) => {
|
|
const sourceHandle = connection.sourceHandle
|
|
const targetHandle = connection.targetHandle
|
|
const target = connection.target
|
|
|
|
//sourceHandle: "llmChain_0-output-llmChain-BaseChain"
|
|
//targetHandle: "mrlkAgentLLM_0-input-model-BaseLanguageModel"
|
|
|
|
let sourceTypes = sourceHandle.split('-')[sourceHandle.split('-').length - 1].split('|')
|
|
sourceTypes = sourceTypes.map((s) => s.trim())
|
|
let targetTypes = targetHandle.split('-')[targetHandle.split('-').length - 1].split('|')
|
|
targetTypes = targetTypes.map((t) => t.trim())
|
|
|
|
if (targetTypes.some((t) => sourceTypes.includes(t))) {
|
|
let targetNode = reactFlowInstance.getNode(target)
|
|
|
|
if (!targetNode) {
|
|
if (!reactFlowInstance.getEdges().find((e) => e.targetHandle === targetHandle)) {
|
|
return true
|
|
}
|
|
} else {
|
|
const targetNodeInputAnchor =
|
|
targetNode.data.inputAnchors.find((ancr) => ancr.id === targetHandle) ||
|
|
targetNode.data.inputParams.find((ancr) => ancr.id === targetHandle)
|
|
if (
|
|
(targetNodeInputAnchor &&
|
|
!targetNodeInputAnchor?.list &&
|
|
!reactFlowInstance.getEdges().find((e) => e.targetHandle === targetHandle)) ||
|
|
targetNodeInputAnchor?.list
|
|
) {
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
export const convertDateStringToDateObject = (dateString) => {
|
|
if (dateString === undefined || !dateString) return undefined
|
|
|
|
const date = moment(dateString)
|
|
if (!date.isValid) return undefined
|
|
|
|
// Sat Sep 24 2022 07:30:14
|
|
return new Date(date.year(), date.month(), date.date(), date.hours(), date.minutes())
|
|
}
|
|
|
|
export const getFileName = (fileBase64) => {
|
|
let fileNames = []
|
|
if (fileBase64.startsWith('FILE-STORAGE::')) {
|
|
const names = fileBase64.substring(14)
|
|
if (names.includes('[') && names.includes(']')) {
|
|
const files = JSON.parse(names)
|
|
return files.join(', ')
|
|
} else {
|
|
return fileBase64.substring(14)
|
|
}
|
|
}
|
|
if (fileBase64.startsWith('[') && fileBase64.endsWith(']')) {
|
|
const files = JSON.parse(fileBase64)
|
|
for (const file of files) {
|
|
const splitDataURI = file.split(',')
|
|
const filename = splitDataURI[splitDataURI.length - 1].split(':')[1]
|
|
fileNames.push(filename)
|
|
}
|
|
return fileNames.join(', ')
|
|
} else {
|
|
const splitDataURI = fileBase64.split(',')
|
|
const filename = splitDataURI[splitDataURI.length - 1].split(':')[1]
|
|
return filename
|
|
}
|
|
}
|
|
|
|
export const getFolderName = (base64ArrayStr) => {
|
|
try {
|
|
const base64Array = JSON.parse(base64ArrayStr)
|
|
const filenames = []
|
|
for (let i = 0; i < base64Array.length; i += 1) {
|
|
const fileBase64 = base64Array[i]
|
|
const splitDataURI = fileBase64.split(',')
|
|
const filename = splitDataURI[splitDataURI.length - 1].split(':')[1]
|
|
filenames.push(filename)
|
|
}
|
|
return filenames.length ? filenames.join(',') : ''
|
|
} catch (e) {
|
|
return ''
|
|
}
|
|
}
|
|
|
|
export const generateExportFlowData = (flowData) => {
|
|
const nodes = flowData.nodes
|
|
const edges = flowData.edges
|
|
|
|
for (let i = 0; i < nodes.length; i += 1) {
|
|
nodes[i].selected = false
|
|
const node = nodes[i]
|
|
|
|
const newNodeData = {
|
|
id: node.data.id,
|
|
label: node.data.label,
|
|
version: node.data.version,
|
|
name: node.data.name,
|
|
type: node.data.type,
|
|
baseClasses: node.data.baseClasses,
|
|
tags: node.data.tags,
|
|
category: node.data.category,
|
|
description: node.data.description,
|
|
inputParams: node.data.inputParams,
|
|
inputAnchors: node.data.inputAnchors,
|
|
inputs: {},
|
|
outputAnchors: node.data.outputAnchors,
|
|
outputs: node.data.outputs,
|
|
selected: false
|
|
}
|
|
|
|
// Remove password, file & folder
|
|
if (node.data.inputs && Object.keys(node.data.inputs).length) {
|
|
const nodeDataInputs = {}
|
|
for (const input in node.data.inputs) {
|
|
const inputParam = node.data.inputParams.find((inp) => inp.name === input)
|
|
if (inputParam && inputParam.type === 'password') continue
|
|
if (inputParam && inputParam.type === 'file') continue
|
|
if (inputParam && inputParam.type === 'folder') continue
|
|
nodeDataInputs[input] = node.data.inputs[input]
|
|
}
|
|
newNodeData.inputs = nodeDataInputs
|
|
}
|
|
|
|
nodes[i].data = newNodeData
|
|
}
|
|
const exportJson = {
|
|
nodes,
|
|
edges
|
|
}
|
|
return exportJson
|
|
}
|
|
|
|
export const getAvailableNodesForVariable = (nodes, edges, target, targetHandle) => {
|
|
// example edge id = "llmChain_0-llmChain_0-output-outputPrediction-string|json-llmChain_1-llmChain_1-input-promptValues-string"
|
|
// {source} -{sourceHandle} -{target} -{targetHandle}
|
|
const parentNodes = []
|
|
const inputEdges = edges.filter((edg) => edg.target === target && edg.targetHandle === targetHandle)
|
|
if (inputEdges && inputEdges.length) {
|
|
for (let j = 0; j < inputEdges.length; j += 1) {
|
|
const node = nodes.find((nd) => nd.id === inputEdges[j].source)
|
|
parentNodes.push(node)
|
|
}
|
|
}
|
|
return parentNodes
|
|
}
|
|
|
|
export const getUpsertDetails = (nodes, edges) => {
|
|
const vsNodes = nodes.filter(
|
|
(node) =>
|
|
node.data.category === 'Vector Stores' && !node.data.label.includes('Upsert') && !node.data.label.includes('Load Existing')
|
|
)
|
|
const vsNodeIds = vsNodes.map((vs) => vs.data.id)
|
|
|
|
const upsertNodes = []
|
|
const seenVsNodeIds = []
|
|
for (const edge of edges) {
|
|
if (vsNodeIds.includes(edge.source) || vsNodeIds.includes(edge.target)) {
|
|
const vsNode = vsNodes.find((node) => node.data.id === edge.source || node.data.id === edge.target)
|
|
if (!vsNode || seenVsNodeIds.includes(vsNode.data.id)) continue
|
|
seenVsNodeIds.push(vsNode.data.id)
|
|
|
|
// Found Vector Store Node, proceed to find connected Document Loader node
|
|
let connectedDocs = []
|
|
|
|
if (vsNode.data.inputs.document) connectedDocs = [...new Set(vsNode.data.inputs.document)]
|
|
|
|
if (connectedDocs.length) {
|
|
const innerNodes = [vsNode]
|
|
|
|
if (vsNode.data.inputs.embeddings) {
|
|
const embeddingsId = vsNode.data.inputs.embeddings.replace(/{{|}}/g, '').split('.')[0]
|
|
innerNodes.push(nodes.find((node) => node.data.id === embeddingsId))
|
|
}
|
|
|
|
if (vsNode.data.inputs.recordManager) {
|
|
const recordManagerId = vsNode.data.inputs.recordManager.replace(/{{|}}/g, '').split('.')[0]
|
|
innerNodes.push(nodes.find((node) => node.data.id === recordManagerId))
|
|
}
|
|
|
|
for (const doc of connectedDocs) {
|
|
const docId = doc.replace(/{{|}}/g, '').split('.')[0]
|
|
const docNode = nodes.find((node) => node.data.id === docId)
|
|
if (docNode) innerNodes.push(docNode)
|
|
|
|
// Found Document Loader Node, proceed to find connected Text Splitter node
|
|
if (docNode && docNode.data.inputs.textSplitter) {
|
|
const textSplitterId = docNode.data.inputs.textSplitter.replace(/{{|}}/g, '').split('.')[0]
|
|
const textSplitterNode = nodes.find((node) => node.data.id === textSplitterId)
|
|
if (textSplitterNode) innerNodes.push(textSplitterNode)
|
|
}
|
|
}
|
|
|
|
upsertNodes.push({
|
|
vectorNode: vsNode,
|
|
nodes: innerNodes.reverse()
|
|
})
|
|
}
|
|
}
|
|
}
|
|
return upsertNodes
|
|
}
|
|
|
|
export const rearrangeToolsOrdering = (newValues, sourceNodeId) => {
|
|
// RequestsGet and RequestsPost have to be in order before other tools
|
|
newValues.push(`{{${sourceNodeId}.data.instance}}`)
|
|
|
|
const sortKey = (item) => {
|
|
if (item.includes('requestsGet') || item.includes('readFile')) {
|
|
return 0
|
|
} else if (item.includes('requestsPost') || item.includes('writeFile')) {
|
|
return 1
|
|
} else {
|
|
return 2
|
|
}
|
|
}
|
|
|
|
newValues.sort((a, b) => sortKey(a) - sortKey(b))
|
|
}
|
|
|
|
export const throttle = (func, limit) => {
|
|
let lastFunc
|
|
let lastRan
|
|
|
|
return (...args) => {
|
|
if (!lastRan) {
|
|
func(...args)
|
|
lastRan = Date.now()
|
|
} else {
|
|
clearTimeout(lastFunc)
|
|
lastFunc = setTimeout(() => {
|
|
if (Date.now() - lastRan >= limit) {
|
|
func(...args)
|
|
lastRan = Date.now()
|
|
}
|
|
}, limit - (Date.now() - lastRan))
|
|
}
|
|
}
|
|
}
|
|
|
|
export const generateRandomGradient = () => {
|
|
function randomColor() {
|
|
var color = 'rgb('
|
|
for (var i = 0; i < 3; i++) {
|
|
var random = Math.floor(Math.random() * 256)
|
|
color += random
|
|
if (i < 2) {
|
|
color += ','
|
|
}
|
|
}
|
|
color += ')'
|
|
return color
|
|
}
|
|
|
|
var gradient = 'linear-gradient(' + randomColor() + ', ' + randomColor() + ')'
|
|
|
|
return gradient
|
|
}
|
|
|
|
export const getInputVariables = (paramValue) => {
|
|
let returnVal = paramValue
|
|
const variableStack = []
|
|
const inputVariables = []
|
|
let startIdx = 0
|
|
const endIdx = returnVal.length
|
|
|
|
while (startIdx < endIdx) {
|
|
const substr = returnVal.substring(startIdx, startIdx + 1)
|
|
|
|
// Store the opening double curly bracket
|
|
if (substr === '{') {
|
|
variableStack.push({ substr, startIdx: startIdx + 1 })
|
|
}
|
|
|
|
// Found the complete variable
|
|
if (substr === '}' && variableStack.length > 0 && variableStack[variableStack.length - 1].substr === '{') {
|
|
const variableStartIdx = variableStack[variableStack.length - 1].startIdx
|
|
const variableEndIdx = startIdx
|
|
const variableFullPath = returnVal.substring(variableStartIdx, variableEndIdx)
|
|
inputVariables.push(variableFullPath)
|
|
variableStack.pop()
|
|
}
|
|
startIdx += 1
|
|
}
|
|
return inputVariables
|
|
}
|
|
|
|
export const removeDuplicateURL = (message) => {
|
|
const visitedURLs = []
|
|
const newSourceDocuments = []
|
|
|
|
if (!message.sourceDocuments) return newSourceDocuments
|
|
|
|
message.sourceDocuments.forEach((source) => {
|
|
if (source.metadata && source.metadata.source) {
|
|
if (isValidURL(source.metadata.source) && !visitedURLs.includes(source.metadata.source)) {
|
|
visitedURLs.push(source.metadata.source)
|
|
newSourceDocuments.push(source)
|
|
} else if (!isValidURL(source.metadata.source)) {
|
|
newSourceDocuments.push(source)
|
|
}
|
|
} else {
|
|
newSourceDocuments.push(source)
|
|
}
|
|
})
|
|
return newSourceDocuments
|
|
}
|
|
|
|
export const isValidURL = (url) => {
|
|
try {
|
|
return new URL(url)
|
|
} catch (err) {
|
|
return undefined
|
|
}
|
|
}
|
|
|
|
export const formatDataGridRows = (rows) => {
|
|
try {
|
|
const parsedRows = typeof rows === 'string' ? JSON.parse(rows) : rows
|
|
return parsedRows.map((sch, index) => {
|
|
return {
|
|
...sch,
|
|
id: index
|
|
}
|
|
})
|
|
} catch (e) {
|
|
return []
|
|
}
|
|
}
|
|
|
|
export const setLocalStorageChatflow = (chatflowid, chatId) => {
|
|
const chatDetails = localStorage.getItem(`${chatflowid}_INTERNAL`)
|
|
const obj = {}
|
|
if (chatId) obj.chatId = chatId
|
|
|
|
if (!chatDetails) {
|
|
localStorage.setItem(`${chatflowid}_INTERNAL`, JSON.stringify(obj))
|
|
} else {
|
|
try {
|
|
const parsedChatDetails = JSON.parse(chatDetails)
|
|
localStorage.setItem(`${chatflowid}_INTERNAL`, JSON.stringify({ ...parsedChatDetails, ...obj }))
|
|
} catch (e) {
|
|
const chatId = chatDetails
|
|
obj.chatId = chatId
|
|
localStorage.setItem(`${chatflowid}_INTERNAL`, JSON.stringify(obj))
|
|
}
|
|
}
|
|
}
|
|
|
|
export const unshiftFiles = (configData) => {
|
|
const filesConfig = configData.find((config) => config.name === 'files')
|
|
if (filesConfig) {
|
|
configData = configData.filter((config) => config.name !== 'files')
|
|
configData.unshift(filesConfig)
|
|
}
|
|
return configData
|
|
}
|
|
|
|
export const getConfigExamplesForJS = (configData, bodyType, isMultiple, stopNodeId) => {
|
|
let finalStr = ''
|
|
configData = unshiftFiles(configData)
|
|
const loop = Math.min(configData.length, 4)
|
|
for (let i = 0; i < loop; i += 1) {
|
|
const config = configData[i]
|
|
let exampleVal = `"example"`
|
|
if (config.type === 'string') exampleVal = `"example"`
|
|
else if (config.type === 'boolean') exampleVal = `true`
|
|
else if (config.type === 'number') exampleVal = `1`
|
|
else if (config.type === 'json') exampleVal = `{ "key": "val" }`
|
|
else if (config.name === 'files') exampleVal = `input.files[0]`
|
|
finalStr += bodyType === 'json' ? `\n "${config.name}": ${exampleVal},` : `formData.append("${config.name}", ${exampleVal})\n`
|
|
if (i === loop - 1 && bodyType !== 'json')
|
|
finalStr += !isMultiple
|
|
? ``
|
|
: stopNodeId
|
|
? `formData.append("stopNodeId", "${stopNodeId}")\n`
|
|
: `formData.append("question", "Hey, how are you?")\n`
|
|
}
|
|
return finalStr
|
|
}
|
|
|
|
export const getConfigExamplesForPython = (configData, bodyType, isMultiple, stopNodeId) => {
|
|
let finalStr = ''
|
|
configData = unshiftFiles(configData)
|
|
const loop = Math.min(configData.length, 4)
|
|
for (let i = 0; i < loop; i += 1) {
|
|
const config = configData[i]
|
|
let exampleVal = `"example"`
|
|
if (config.type === 'string') exampleVal = `"example"`
|
|
else if (config.type === 'boolean') exampleVal = `true`
|
|
else if (config.type === 'number') exampleVal = `1`
|
|
else if (config.type === 'json') exampleVal = `{ "key": "val" }`
|
|
else if (config.name === 'files') continue
|
|
finalStr += bodyType === 'json' ? `\n "${config.name}": ${exampleVal},` : `\n "${config.name}": ${exampleVal},`
|
|
if (i === loop - 1 && bodyType !== 'json')
|
|
finalStr += !isMultiple
|
|
? `\n`
|
|
: stopNodeId
|
|
? `\n "stopNodeId": "${stopNodeId}"\n`
|
|
: `\n "question": "Hey, how are you?"\n`
|
|
}
|
|
return finalStr
|
|
}
|
|
|
|
export const getConfigExamplesForCurl = (configData, bodyType, isMultiple, stopNodeId) => {
|
|
let finalStr = ''
|
|
configData = unshiftFiles(configData)
|
|
const loop = Math.min(configData.length, 4)
|
|
for (let i = 0; i < loop; i += 1) {
|
|
const config = configData[i]
|
|
let exampleVal = `example`
|
|
if (config.type === 'string') exampleVal = bodyType === 'json' ? `"example"` : `example`
|
|
else if (config.type === 'boolean') exampleVal = `true`
|
|
else if (config.type === 'number') exampleVal = `1`
|
|
else if (config.type === 'json') exampleVal = `{key:val}`
|
|
else if (config.name === 'files')
|
|
exampleVal = `@/home/user1/Desktop/example${config.type.includes(',') ? config.type.split(',')[0] : config.type}`
|
|
finalStr += bodyType === 'json' ? `"${config.name}": ${exampleVal}` : `\n -F "${config.name}=${exampleVal}"`
|
|
if (i === loop - 1)
|
|
finalStr +=
|
|
bodyType === 'json'
|
|
? ` }`
|
|
: !isMultiple
|
|
? ``
|
|
: stopNodeId
|
|
? ` \\\n -F "stopNodeId=${stopNodeId}"`
|
|
: ` \\\n -F "question=Hey, how are you?"`
|
|
else finalStr += bodyType === 'json' ? `, ` : ` \\`
|
|
}
|
|
return finalStr
|
|
}
|
|
|
|
export const getOS = () => {
|
|
let userAgent = window.navigator.userAgent.toLowerCase(),
|
|
macosPlatforms = /(macintosh|macintel|macppc|mac68k|macos)/i,
|
|
windowsPlatforms = /(win32|win64|windows|wince)/i,
|
|
iosPlatforms = /(iphone|ipad|ipod)/i,
|
|
os = null
|
|
|
|
if (macosPlatforms.test(userAgent)) {
|
|
os = 'macos'
|
|
} else if (iosPlatforms.test(userAgent)) {
|
|
os = 'ios'
|
|
} else if (windowsPlatforms.test(userAgent)) {
|
|
os = 'windows'
|
|
} else if (/android/.test(userAgent)) {
|
|
os = 'android'
|
|
} else if (!os && /linux/.test(userAgent)) {
|
|
os = 'linux'
|
|
}
|
|
|
|
return os
|
|
}
|