add datagrid field type

This commit is contained in:
Henry
2023-10-31 22:12:09 +00:00
parent 5ff720d66c
commit 82074ee7a1
8 changed files with 217 additions and 86 deletions
@@ -1,7 +1,8 @@
import { getBaseClasses, INode, INodeData, INodeParams } from '../../../src' import { convertSchemaToZod, getBaseClasses, INode, INodeData, INodeParams } from '../../../src'
import { BaseOutputParser } from 'langchain/schema/output_parser' import { BaseOutputParser } from 'langchain/schema/output_parser'
import { StructuredOutputParser as LangchainStructuredOutputParser } from 'langchain/output_parsers' import { StructuredOutputParser as LangchainStructuredOutputParser } from 'langchain/output_parsers'
import { CATEGORY } from '../OutputParserHelpers' import { CATEGORY } from '../OutputParserHelpers'
import { z } from 'zod'
class StructuredOutputParser implements INode { class StructuredOutputParser implements INode {
label: string label: string
@@ -24,65 +25,65 @@ class StructuredOutputParser implements INode {
this.icon = 'structure.png' this.icon = 'structure.png'
this.category = CATEGORY this.category = CATEGORY
this.baseClasses = [this.type, ...getBaseClasses(BaseOutputParser)] this.baseClasses = [this.type, ...getBaseClasses(BaseOutputParser)]
//TODO: To extend the structureType to ZodSchema
this.inputs = [ this.inputs = [
{
label: 'Structure Type',
name: 'structureType',
type: 'options',
options: [
{
label: 'Names And Descriptions',
name: 'fromNamesAndDescriptions'
}
],
default: 'fromNamesAndDescriptions'
},
{
label: 'Structure',
name: 'structure',
type: 'string',
rows: 4,
placeholder:
'{' +
' answer: "answer to the question",\n' +
' source: "source used to answer the question, should be a website.",\n' +
'}'
},
{ {
label: 'Autofix', label: 'Autofix',
name: 'autofixParser', name: 'autofixParser',
type: 'boolean', type: 'boolean',
optional: true, optional: true,
description: 'In the event that the first call fails, will make another call to the model to fix any errors.' description: 'In the event that the first call fails, will make another call to the model to fix any errors.'
},
{
label: 'JSON Structure',
name: 'jsonStructure',
type: 'datagrid',
description: 'JSON structure for LLM to return',
datagrid: [
{ field: 'property', headerName: 'Property', editable: true },
{
field: 'type',
headerName: 'Type',
type: 'singleSelect',
valueOptions: ['string', 'number', 'boolean'],
editable: true
},
{ field: 'description', headerName: 'Description', editable: true, flex: 1 }
],
default: [
{
property: 'answer',
type: 'string',
description: `answer to the user's question`
},
{
property: 'source',
type: 'string',
description: `sources used to answer the question, should be websites`
}
],
additionalParams: true
} }
] ]
} }
async init(nodeData: INodeData): Promise<any> { async init(nodeData: INodeData): Promise<any> {
const structureType = nodeData.inputs?.structureType as string const jsonStructure = nodeData.inputs?.jsonStructure as string
const structure = nodeData.inputs?.structure as string
const autoFix = nodeData.inputs?.autofixParser as boolean const autoFix = nodeData.inputs?.autofixParser as boolean
let parsedStructure: any | undefined = undefined try {
if (structure && structureType === 'fromNamesAndDescriptions') { const structuredOutputParser = LangchainStructuredOutputParser.fromZodSchema(z.object(convertSchemaToZod(jsonStructure)))
try {
parsedStructure = JSON.parse(structure)
// NOTE: When we change Flowise to return a json response, the following has to be changed to: JsonStructuredOutputParser // NOTE: When we change Flowise to return a json response, the following has to be changed to: JsonStructuredOutputParser
let structuredOutputParser = LangchainStructuredOutputParser.fromNamesAndDescriptions(parsedStructure) Object.defineProperty(structuredOutputParser, 'autoFix', {
Object.defineProperty(structuredOutputParser, 'autoFix', { enumerable: true,
enumerable: true, configurable: true,
configurable: true, writable: true,
writable: true, value: autoFix
value: autoFix })
}) return structuredOutputParser
return structuredOutputParser } catch (exception) {
} catch (exception) { throw new Error('Invalid JSON in StructuredOutputParser: ' + exception)
throw new Error('Invalid JSON in StructuredOutputParser: ' + exception)
}
} }
throw new Error('Error creating OutputParser.')
} }
} }
@@ -1,5 +1,5 @@
import { ICommonObject, IDatabaseEntity, INode, INodeData, INodeOptionsValue, INodeParams } from '../../../src/Interface' import { ICommonObject, IDatabaseEntity, INode, INodeData, INodeOptionsValue, INodeParams } from '../../../src/Interface'
import { getBaseClasses } from '../../../src/utils' import { convertSchemaToZod, getBaseClasses } from '../../../src/utils'
import { DynamicStructuredTool } from './core' import { DynamicStructuredTool } from './core'
import { z } from 'zod' import { z } from 'zod'
import { DataSource } from 'typeorm' import { DataSource } from 'typeorm'
@@ -87,26 +87,4 @@ class CustomTool_Tools implements INode {
} }
} }
const convertSchemaToZod = (schema: string) => {
try {
const parsedSchema = JSON.parse(schema)
const zodObj: any = {}
for (const sch of parsedSchema) {
if (sch.type === 'string') {
if (sch.required) z.string({ required_error: `${sch.property} required` }).describe(sch.description)
zodObj[sch.property] = z.string().describe(sch.description)
} else if (sch.type === 'number') {
if (sch.required) z.number({ required_error: `${sch.property} required` }).describe(sch.description)
zodObj[sch.property] = z.number().describe(sch.description)
} else if (sch.type === 'boolean') {
if (sch.required) z.boolean({ required_error: `${sch.property} required` }).describe(sch.description)
zodObj[sch.property] = z.boolean().describe(sch.description)
}
}
return zodObj
} catch (e) {
throw new Error(e)
}
}
module.exports = { nodeClass: CustomTool_Tools } module.exports = { nodeClass: CustomTool_Tools }
+2
View File
@@ -6,6 +6,7 @@ export type NodeParamsType =
| 'asyncOptions' | 'asyncOptions'
| 'options' | 'options'
| 'multiOptions' | 'multiOptions'
| 'datagrid'
| 'string' | 'string'
| 'number' | 'number'
| 'boolean' | 'boolean'
@@ -60,6 +61,7 @@ export interface INodeParams {
description?: string description?: string
warning?: string warning?: string
options?: Array<INodeOptionsValue> options?: Array<INodeOptionsValue>
datagrid?: Array<ICommonObject>
credentialNames?: Array<string> credentialNames?: Array<string>
optional?: boolean | INodeDisplay optional?: boolean | INodeDisplay
step?: number step?: number
+28
View File
@@ -3,6 +3,7 @@ import { load } from 'cheerio'
import * as fs from 'fs' import * as fs from 'fs'
import * as path from 'path' import * as path from 'path'
import { JSDOM } from 'jsdom' import { JSDOM } from 'jsdom'
import { z } from 'zod'
import { DataSource } from 'typeorm' import { DataSource } from 'typeorm'
import { ICommonObject, IDatabaseEntity, IMessage, INodeData } from './Interface' import { ICommonObject, IDatabaseEntity, IMessage, INodeData } from './Interface'
import { AES, enc } from 'crypto-js' import { AES, enc } from 'crypto-js'
@@ -546,3 +547,30 @@ export const convertChatHistoryToText = (chatHistory: IMessage[] = []): string =
}) })
.join('\n') .join('\n')
} }
/**
* Convert schema to zod schema
* @param {string} schema
* @returns {ICommonObject}
*/
export const convertSchemaToZod = (schema: string) => {
try {
const parsedSchema = JSON.parse(schema)
const zodObj: ICommonObject = {}
for (const sch of parsedSchema) {
if (sch.type === 'string') {
if (sch.required) z.string({ required_error: `${sch.property} required` }).describe(sch.description)
zodObj[sch.property] = z.string().describe(sch.description)
} else if (sch.type === 'number') {
if (sch.required) z.number({ required_error: `${sch.property} required` }).describe(sch.description)
zodObj[sch.property] = z.number().describe(sch.description)
} else if (sch.type === 'boolean') {
if (sch.required) z.boolean({ required_error: `${sch.property} required` }).describe(sch.description)
zodObj[sch.property] = z.boolean().describe(sch.description)
}
}
return zodObj
} catch (e) {
throw new Error(e)
}
}
@@ -0,0 +1,111 @@
import PropTypes from 'prop-types'
import { useState, useCallback } from 'react'
import { DataGrid as MUIDataGrid, GridActionsCellItem } from '@mui/x-data-grid'
import { IconPlus } from '@tabler/icons'
import { Button } from '@mui/material'
import DeleteIcon from '@mui/icons-material/Delete'
import { cloneDeep } from 'lodash'
import { formatDataGridRows } from 'utils/genericHelper'
export const DataGrid = ({ columns, rows, style, disabled = false, hideFooter = false, onChange }) => {
const [rowValues, setRowValues] = useState(formatDataGridRows(rows) ?? [])
const deleteItem = useCallback(
(id) => () => {
let updatedRows = []
setRowValues((prevRows) => {
let allRows = [...cloneDeep(prevRows)]
allRows = allRows.filter((row) => row.id !== id)
updatedRows = allRows
return allRows
})
onChange(JSON.stringify(updatedRows))
},
// eslint-disable-next-line react-hooks/exhaustive-deps
[]
)
const addCols = (columns) => {
return [
...columns,
{
field: 'actions',
type: 'actions',
width: 80,
getActions: (params) => [
<GridActionsCellItem key={'Delete'} icon={<DeleteIcon />} label='Delete' onClick={deleteItem(params.id)} />
]
}
]
}
const colValues = addCols(columns)
const handleProcessRowUpdate = (newRow) => {
let updatedRows = []
setRowValues((prevRows) => {
let allRows = [...cloneDeep(prevRows)]
const indexToUpdate = allRows.findIndex((row) => row.id === newRow.id)
if (indexToUpdate >= 0) {
allRows[indexToUpdate] = { ...newRow }
}
updatedRows = allRows
return allRows
})
onChange(JSON.stringify(updatedRows))
return newRow
}
const getEmptyJsonObj = () => {
const obj = {}
for (let i = 0; i < colValues.length; i += 1) {
obj[colValues[i]?.field] = ''
}
return obj
}
const addNewRow = () => {
setRowValues((prevRows) => {
let allRows = [...cloneDeep(prevRows)]
const lastRowId = allRows.length ? allRows[allRows.length - 1].id + 1 : 1
allRows.push({
...getEmptyJsonObj(),
id: lastRowId
})
return allRows
})
}
return (
<>
{rowValues && colValues && (
<div style={{ marginTop: 10, height: 210, width: '100%', ...style }}>
<MUIDataGrid
processRowUpdate={handleProcessRowUpdate}
isCellEditable={() => {
return !disabled
}}
hideFooter={hideFooter}
onProcessRowUpdateError={(error) => console.error(error)}
rows={rowValues}
columns={colValues}
/>
</div>
)}
{!disabled && (
<Button sx={{ mt: 1 }} variant='outlined' onClick={addNewRow} startIcon={<IconPlus />}>
Add Item
</Button>
)}
</>
)
}
DataGrid.propTypes = {
rows: PropTypes.array,
columns: PropTypes.array,
style: PropTypes.any,
disabled: PropTypes.bool,
hideFooter: PropTypes.bool,
onChange: PropTypes.func
}
+15
View File
@@ -43,6 +43,7 @@ export const initNode = (nodeData, newNodeId) => {
'asyncOptions', 'asyncOptions',
'options', 'options',
'multiOptions', 'multiOptions',
'datagrid',
'string', 'string',
'number', 'number',
'boolean', 'boolean',
@@ -422,3 +423,17 @@ export const isValidURL = (url) => {
return undefined 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 []
}
}
@@ -14,6 +14,7 @@ import { Dropdown } from 'ui-component/dropdown/Dropdown'
import { MultiDropdown } from 'ui-component/dropdown/MultiDropdown' import { MultiDropdown } from 'ui-component/dropdown/MultiDropdown'
import { AsyncDropdown } from 'ui-component/dropdown/AsyncDropdown' import { AsyncDropdown } from 'ui-component/dropdown/AsyncDropdown'
import { Input } from 'ui-component/input/Input' import { Input } from 'ui-component/input/Input'
import { DataGrid } from 'ui-component/grid/DataGrid'
import { File } from 'ui-component/file/File' import { File } from 'ui-component/file/File'
import { SwitchInput } from 'ui-component/switch/Switch' import { SwitchInput } from 'ui-component/switch/Switch'
import { flowContext } from 'store/context/ReactFlowContext' import { flowContext } from 'store/context/ReactFlowContext'
@@ -258,6 +259,15 @@ const NodeInputHandler = ({ inputAnchor, inputParam, data, disabled = false, isA
value={data.inputs[inputParam.name] ?? inputParam.default ?? false} value={data.inputs[inputParam.name] ?? inputParam.default ?? false}
/> />
)} )}
{inputParam.type === 'datagrid' && (
<DataGrid
disabled={disabled}
columns={inputParam.datagrid}
hideFooter={true}
rows={data.inputs[inputParam.name] ?? JSON.stringify(inputParam.default) ?? []}
onChange={(newValue) => (data.inputs[inputParam.name] = newValue)}
/>
)}
{(inputParam.type === 'string' || inputParam.type === 'password' || inputParam.type === 'number') && ( {(inputParam.type === 'string' || inputParam.type === 'password' || inputParam.type === 'number') && (
<Input <Input
key={data.inputs[inputParam.name]} key={data.inputs[inputParam.name]}
+5 -19
View File
@@ -28,7 +28,7 @@ import useApi from 'hooks/useApi'
// utils // utils
import useNotifier from 'utils/useNotifier' import useNotifier from 'utils/useNotifier'
import { generateRandomGradient } from 'utils/genericHelper' import { generateRandomGradient, formatDataGridRows } from 'utils/genericHelper'
import { HIDE_CANVAS_DIALOG, SHOW_CANVAS_DIALOG } from 'store/actions' import { HIDE_CANVAS_DIALOG, SHOW_CANVAS_DIALOG } from 'store/actions'
const exampleAPIFunc = `/* const exampleAPIFunc = `/*
@@ -142,20 +142,6 @@ const ToolDialog = ({ show, dialogProps, onUseTemplate, onCancel, onConfirm }) =
[deleteItem] [deleteItem]
) )
const formatSchema = (schema) => {
try {
const parsedSchema = JSON.parse(schema)
return parsedSchema.map((sch, index) => {
return {
...sch,
id: index
}
})
} catch (e) {
return []
}
}
useEffect(() => { useEffect(() => {
if (show) dispatch({ type: SHOW_CANVAS_DIALOG }) if (show) dispatch({ type: SHOW_CANVAS_DIALOG })
else dispatch({ type: HIDE_CANVAS_DIALOG }) else dispatch({ type: HIDE_CANVAS_DIALOG })
@@ -167,7 +153,7 @@ const ToolDialog = ({ show, dialogProps, onUseTemplate, onCancel, onConfirm }) =
setToolId(getSpecificToolApi.data.id) setToolId(getSpecificToolApi.data.id)
setToolName(getSpecificToolApi.data.name) setToolName(getSpecificToolApi.data.name)
setToolDesc(getSpecificToolApi.data.description) setToolDesc(getSpecificToolApi.data.description)
setToolSchema(formatSchema(getSpecificToolApi.data.schema)) setToolSchema(formatDataGridRows(getSpecificToolApi.data.schema))
if (getSpecificToolApi.data.func) setToolFunc(getSpecificToolApi.data.func) if (getSpecificToolApi.data.func) setToolFunc(getSpecificToolApi.data.func)
else setToolFunc('') else setToolFunc('')
} }
@@ -180,7 +166,7 @@ const ToolDialog = ({ show, dialogProps, onUseTemplate, onCancel, onConfirm }) =
setToolName(dialogProps.data.name) setToolName(dialogProps.data.name)
setToolDesc(dialogProps.data.description) setToolDesc(dialogProps.data.description)
setToolIcon(dialogProps.data.iconSrc) setToolIcon(dialogProps.data.iconSrc)
setToolSchema(formatSchema(dialogProps.data.schema)) setToolSchema(formatDataGridRows(dialogProps.data.schema))
if (dialogProps.data.func) setToolFunc(dialogProps.data.func) if (dialogProps.data.func) setToolFunc(dialogProps.data.func)
else setToolFunc('') else setToolFunc('')
} else if (dialogProps.type === 'EDIT' && dialogProps.toolId) { } else if (dialogProps.type === 'EDIT' && dialogProps.toolId) {
@@ -191,7 +177,7 @@ const ToolDialog = ({ show, dialogProps, onUseTemplate, onCancel, onConfirm }) =
setToolName(dialogProps.data.name) setToolName(dialogProps.data.name)
setToolDesc(dialogProps.data.description) setToolDesc(dialogProps.data.description)
setToolIcon(dialogProps.data.iconSrc) setToolIcon(dialogProps.data.iconSrc)
setToolSchema(formatSchema(dialogProps.data.schema)) setToolSchema(formatDataGridRows(dialogProps.data.schema))
if (dialogProps.data.func) setToolFunc(dialogProps.data.func) if (dialogProps.data.func) setToolFunc(dialogProps.data.func)
else setToolFunc('') else setToolFunc('')
} else if (dialogProps.type === 'TEMPLATE' && dialogProps.data) { } else if (dialogProps.type === 'TEMPLATE' && dialogProps.data) {
@@ -199,7 +185,7 @@ const ToolDialog = ({ show, dialogProps, onUseTemplate, onCancel, onConfirm }) =
setToolName(dialogProps.data.name) setToolName(dialogProps.data.name)
setToolDesc(dialogProps.data.description) setToolDesc(dialogProps.data.description)
setToolIcon(dialogProps.data.iconSrc) setToolIcon(dialogProps.data.iconSrc)
setToolSchema(formatSchema(dialogProps.data.schema)) setToolSchema(formatDataGridRows(dialogProps.data.schema))
if (dialogProps.data.func) setToolFunc(dialogProps.data.func) if (dialogProps.data.func) setToolFunc(dialogProps.data.func)
else setToolFunc('') else setToolFunc('')
} else if (dialogProps.type === 'ADD') { } else if (dialogProps.type === 'ADD') {