Skip to content

Commit f76534b

Browse files
HenryHengZJJJK801
authored andcommitted
Feature/Add multi select option to composio (FlowiseAI#4122)
* add multi select option to composio * Update pnpm-lock.yaml
1 parent 1e3639c commit f76534b

File tree

6 files changed

+77
-24
lines changed

6 files changed

+77
-24
lines changed

packages/components/nodes/tools/Composio/Composio.ts

+13-3
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ class Composio_Tools implements INode {
4141
constructor() {
4242
this.label = 'Composio'
4343
this.name = 'composio'
44-
this.version = 1.0
44+
this.version = 2.0
4545
this.type = 'Composio'
4646
this.icon = 'composio.svg'
4747
this.category = 'Tools'
@@ -73,7 +73,7 @@ class Composio_Tools implements INode {
7373
{
7474
label: 'Actions to Use',
7575
name: 'actions',
76-
type: 'asyncOptions',
76+
type: 'asyncMultiOptions',
7777
loadMethod: 'listActions',
7878
description: 'Select the actions you want to use',
7979
refresh: true
@@ -216,8 +216,18 @@ class Composio_Tools implements INode {
216216
throw new Error('API Key Required')
217217
}
218218

219+
const _actions = nodeData.inputs?.actions
220+
let actions = []
221+
if (_actions) {
222+
try {
223+
actions = typeof _actions === 'string' ? JSON.parse(_actions) : _actions
224+
} catch (error) {
225+
console.error('Error parsing actions:', error)
226+
}
227+
}
228+
219229
const toolset = new LangchainToolSet({ apiKey: composioApiKey })
220-
const tools = await toolset.getTools({ actions: [nodeData.inputs?.actions as string] })
230+
const tools = await toolset.getTools({ actions })
221231
return tools
222232
}
223233
}

packages/server/src/utils/constants.ts

+1
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ export const OMIT_QUEUE_JOB_DATA = ['componentNodes', 'appDataSource', 'sseStrea
2525

2626
export const INPUT_PARAMS_TYPE = [
2727
'asyncOptions',
28+
'asyncMultiOptions',
2829
'options',
2930
'multiOptions',
3031
'datagrid',

packages/ui/src/ui-component/dropdown/AsyncDropdown.jsx

+33-8
Original file line numberDiff line numberDiff line change
@@ -59,15 +59,27 @@ export const AsyncDropdown = ({
5959
credentialNames = [],
6060
disabled = false,
6161
freeSolo = false,
62-
disableClearable = false
62+
disableClearable = false,
63+
multiple = false
6364
}) => {
6465
const customization = useSelector((state) => state.customization)
6566

6667
const [open, setOpen] = useState(false)
6768
const [options, setOptions] = useState([])
6869
const [loading, setLoading] = useState(false)
69-
const findMatchingOptions = (options = [], value) => options.find((option) => option.name === value)
70-
const getDefaultOptionValue = () => ''
70+
const findMatchingOptions = (options = [], value) => {
71+
if (multiple) {
72+
let values = []
73+
if ('choose an option' !== value && value && typeof value === 'string') {
74+
values = JSON.parse(value)
75+
} else {
76+
values = value
77+
}
78+
return options.filter((option) => values.includes(option.name))
79+
}
80+
return options.find((option) => option.name === value)
81+
}
82+
const getDefaultOptionValue = () => (multiple ? [] : '')
7183
const addNewOption = [{ label: '- Create New -', name: '-create-' }]
7284
let [internalValue, setInternalValue] = useState(value ?? 'choose an option')
7385

@@ -118,6 +130,8 @@ export const AsyncDropdown = ({
118130
freeSolo={freeSolo}
119131
disabled={disabled}
120132
disableClearable={disableClearable}
133+
multiple={multiple}
134+
filterSelectedOptions={multiple}
121135
size='small'
122136
sx={{ mt: 1, width: '100%' }}
123137
open={open}
@@ -130,12 +144,22 @@ export const AsyncDropdown = ({
130144
options={options}
131145
value={findMatchingOptions(options, internalValue) || getDefaultOptionValue()}
132146
onChange={(e, selection) => {
133-
const value = selection ? selection.name : ''
134-
if (isCreateNewOption && value === '-create-') {
135-
onCreateNew()
136-
} else {
147+
if (multiple) {
148+
let value = ''
149+
if (selection.length) {
150+
const selectionNames = selection.map((item) => item.name)
151+
value = JSON.stringify(selectionNames)
152+
}
137153
setInternalValue(value)
138154
onSelect(value)
155+
} else {
156+
const value = selection ? selection.name : ''
157+
if (isCreateNewOption && value === '-create-') {
158+
onCreateNew()
159+
} else {
160+
setInternalValue(value)
161+
onSelect(value)
162+
}
139163
}
140164
}}
141165
PopperComponent={StyledPopper}
@@ -181,5 +205,6 @@ AsyncDropdown.propTypes = {
181205
freeSolo: PropTypes.bool,
182206
credentialNames: PropTypes.array,
183207
disableClearable: PropTypes.bool,
184-
isCreateNewOption: PropTypes.bool
208+
isCreateNewOption: PropTypes.bool,
209+
multiple: PropTypes.bool
185210
}

packages/ui/src/utils/genericHelper.js

+1
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ export const initNode = (nodeData, newNodeId) => {
3535

3636
const whitelistTypes = [
3737
'asyncOptions',
38+
'asyncMultiOptions',
3839
'options',
3940
'multiOptions',
4041
'datagrid',

packages/ui/src/views/canvas/NodeInputHandler.jsx

+3-2
Original file line numberDiff line numberDiff line change
@@ -795,16 +795,17 @@ const NodeInputHandler = ({
795795
value={data.inputs[inputParam.name] ?? inputParam.default ?? 'choose an option'}
796796
/>
797797
)}
798-
{inputParam.type === 'asyncOptions' && (
798+
{(inputParam.type === 'asyncOptions' || inputParam.type === 'asyncMultiOptions') && (
799799
<>
800800
{data.inputParams.length === 1 && <div style={{ marginTop: 10 }} />}
801-
<div key={reloadTimestamp} style={{ display: 'flex', flexDirection: 'row', alignContent: 'center' }}>
801+
<div key={reloadTimestamp} style={{ display: 'flex', flexDirection: 'row', alignItems: 'center', gap: 1 }}>
802802
<AsyncDropdown
803803
disabled={disabled}
804804
name={inputParam.name}
805805
nodeData={data}
806806
value={data.inputs[inputParam.name] ?? inputParam.default ?? 'choose an option'}
807807
freeSolo={inputParam.freeSolo}
808+
multiple={inputParam.type === 'asyncMultiOptions'}
808809
isCreateNewOption={EDITABLE_OPTIONS.includes(inputParam.name)}
809810
onSelect={(newValue) => (data.inputs[inputParam.name] = newValue)}
810811
onCreateNew={() => addAsyncOption(inputParam.name)}

packages/ui/src/views/docstore/DocStoreInputHandler.jsx

+26-11
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { useSelector } from 'react-redux'
44

55
// material-ui
66
import { Box, Typography, IconButton, Button } from '@mui/material'
7-
import { IconArrowsMaximize, IconAlertTriangle } from '@tabler/icons-react'
7+
import { IconRefresh, IconArrowsMaximize, IconAlertTriangle } from '@tabler/icons-react'
88

99
// project import
1010
import { Dropdown } from '@/ui-component/dropdown/Dropdown'
@@ -33,6 +33,7 @@ const DocStoreInputHandler = ({ inputParam, data, disabled = false }) => {
3333
const [expandDialogProps, setExpandDialogProps] = useState({})
3434
const [showManageScrapedLinksDialog, setShowManageScrapedLinksDialog] = useState(false)
3535
const [manageScrapedLinksDialogProps, setManageScrapedLinksDialogProps] = useState({})
36+
const [reloadTimestamp, setReloadTimestamp] = useState(Date.now().toString())
3637

3738
const onExpandDialogClicked = (value, inputParam) => {
3839
const dialogProps = {
@@ -216,19 +217,33 @@ const DocStoreInputHandler = ({ inputParam, data, disabled = false }) => {
216217
value={data.inputs[inputParam.name] ?? inputParam.default ?? 'choose an option'}
217218
/>
218219
)}
219-
{inputParam.type === 'asyncOptions' && (
220+
{(inputParam.type === 'asyncOptions' || inputParam.type === 'asyncMultiOptions') && (
220221
<>
221222
{data.inputParams?.length === 1 && <div style={{ marginTop: 10 }} />}
222223
<div style={{ display: 'flex', flexDirection: 'row' }}>
223-
<AsyncDropdown
224-
key={JSON.stringify(inputParam)}
225-
disabled={disabled}
226-
name={inputParam.name}
227-
nodeData={data}
228-
value={data.inputs[inputParam.name] ?? inputParam.default ?? 'choose an option'}
229-
onSelect={(newValue) => (data.inputs[inputParam.name] = newValue)}
230-
onCreateNew={() => addAsyncOption(inputParam.name)}
231-
/>
224+
<div key={reloadTimestamp} style={{ flex: 1 }}>
225+
<AsyncDropdown
226+
key={JSON.stringify(inputParam)}
227+
disabled={disabled}
228+
name={inputParam.name}
229+
nodeData={data}
230+
freeSolo={inputParam.freeSolo}
231+
multiple={inputParam.type === 'asyncMultiOptions'}
232+
value={data.inputs[inputParam.name] ?? inputParam.default ?? 'choose an option'}
233+
onSelect={(newValue) => (data.inputs[inputParam.name] = newValue)}
234+
onCreateNew={() => addAsyncOption(inputParam.name)}
235+
/>
236+
</div>
237+
{inputParam.refresh && (
238+
<IconButton
239+
title='Refresh'
240+
color='primary'
241+
size='small'
242+
onClick={() => setReloadTimestamp(Date.now().toString())}
243+
>
244+
<IconRefresh />
245+
</IconButton>
246+
)}
232247
</div>
233248
</>
234249
)}

0 commit comments

Comments
 (0)