chore: Update function calling add menu (#40139)

## Description

Update the add menu responsibilites

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

- **New Features**
- Introduced an enhanced menu for adding function calls with options to
create new queries or JavaScript objects and a curated list of available
items.
  
- **Refactor**
- Updated the interface for function call configuration by streamlining
legacy options, resulting in a more consistent and responsive user
experience.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
Hetu Nandu 2025-04-07 18:48:31 +05:30 committed by GitHub
parent df34a10205
commit 326023d0ab
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 160 additions and 57 deletions

View File

@ -1,10 +1,34 @@
import { Button, Text } from "@appsmith/ads";
import React, { useCallback, useState } from "react";
import {
Button,
Flex,
Icon,
Menu,
MenuContent,
MenuGroup,
MenuItem,
MenuSub,
MenuSubContent,
MenuSubTrigger,
MenuTrigger,
Text,
} from "@appsmith/ads";
import React, { useCallback, useMemo, useState } from "react";
import type { FieldArrayFieldsProps } from "redux-form";
import styled from "styled-components";
import { v4 as uuid } from "uuid";
import type { FunctionCallingConfigFormToolField } from "../types";
import type {
FunctionCallingConfigFormToolField,
FunctionCallingEntityType,
FunctionCallingEntityTypeOption,
} from "../types";
import { FunctionCallingConfigToolField } from "./FunctionCallingConfigToolField";
import { useSelector } from "react-redux";
import { selectEntityOptions } from "./selectors";
import { createNewJSCollection } from "actions/jsPaneActions";
import { queryAddURL } from "ee/RouteBuilder";
import history from "utils/history";
import { useDispatch } from "react-redux";
import { getCurrentPageId } from "selectors/editorSelectors";
export interface FunctionCallingConfigFormProps {
formName: string;
@ -29,19 +53,48 @@ export const FunctionCallingConfigForm = ({
formName,
}: FunctionCallingConfigFormProps) => {
const [newlyAddedId, setNewlyAddedId] = useState<string | null>(null);
const options = useSelector(selectEntityOptions);
const dispatch = useDispatch();
const pageId = useSelector(getCurrentPageId);
const handleAddFunctionButtonClick = useCallback(() => {
const id = uuid();
// Get existing entity IDs from the agentFunctions, excluding the currently selected value
const existingEntityIds = useMemo(() => {
const agentFunctionIds = new Set(Object.keys(options.agentFunctions));
fields.push({
id,
description: "",
entityId: "",
isApprovalRequired: false,
entityType: "Query",
});
setNewlyAddedId(id);
}, [fields]);
return agentFunctionIds;
}, [options.agentFunctions]);
// Filter query items to exclude existing entities
const filteredQueryItems = useMemo(() => {
return options.Query.filter((item) => !existingEntityIds.has(item.value));
}, [options.Query, existingEntityIds]);
// Filter JS collection items
const filteredJSCollections = useMemo(() => {
return options.JSCollections.map((collection) => ({
...collection,
// Filter out functions that are already used
functions: collection.functions.filter(
(func) => !existingEntityIds.has(func.value),
),
})).filter((collection) => collection.functions.length > 0);
}, [options.JSCollections, existingEntityIds]);
const handleAddFunctionButtonClick = useCallback(
(option: FunctionCallingEntityTypeOption) => {
const id = uuid();
fields.push({
id,
description: "",
entityId: option.value,
isApprovalRequired: false,
entityType: option.optionGroupType as FunctionCallingEntityType,
});
setNewlyAddedId(id);
},
[fields],
);
const handleRemoveToolButtonClick = useCallback(
(index: number) => {
@ -54,17 +107,100 @@ export const FunctionCallingConfigForm = ({
<>
<Header>
<Text isBold kind="heading-s" renderAs="p">
Function calls
Function Calls
</Text>
<Button
UNSAFE_width="110px"
kind="secondary"
onClick={handleAddFunctionButtonClick}
startIcon="plus"
>
Add Function
</Button>
<Menu>
<MenuTrigger>
<Button UNSAFE_width="110px" kind="secondary" startIcon="plus">
Add Function
</Button>
</MenuTrigger>
<MenuContent align="end" loop width="235px">
{/* Create new options */}
<MenuGroup>
<MenuItem onSelect={() => history.push(queryAddURL({}))}>
<Flex alignItems="center" gap="spaces-2">
<Icon name="plus" size="md" />
New Query
</Flex>
</MenuItem>
<MenuItem
onSelect={() =>
dispatch(
createNewJSCollection(
pageId,
"AI_QUERY_FUNCTION_CALLING_CONFIG",
"onToolCall",
),
)
}
>
<Flex alignItems="center" gap="spaces-2">
<Icon name="plus" size="md" />
New JS Object
</Flex>
</MenuItem>
</MenuGroup>
{/* Query options group */}
{options.Query.length > 0 && (
<MenuGroup>
{filteredQueryItems.map((option) => (
<MenuItem
key={option.value}
onSelect={() => handleAddFunctionButtonClick(option)}
>
<Flex alignItems="center" gap="spaces-2">
{option.icon &&
(typeof option.icon === "string" ? (
<Icon name={option.icon} size="md" />
) : (
option.icon
))}
{option.label}
</Flex>
</MenuItem>
))}
</MenuGroup>
)}
{/* JS Collections group with nested functions */}
{options.JSCollections.length > 0 && (
<MenuGroup>
<MenuGroup>
{filteredJSCollections.map((collection) => (
<MenuSub key={collection.id}>
<MenuSubTrigger>
<Flex alignItems="center" gap="spaces-2">
{collection.icon &&
(typeof collection.icon === "string" ? (
<Icon name={collection.icon} size="md" />
) : (
collection.icon
))}
{collection.name}
</Flex>
</MenuSubTrigger>
<MenuSubContent>
{collection.functions.map((jsFunction) => (
<MenuItem
key={jsFunction.value}
onSelect={() =>
handleAddFunctionButtonClick(jsFunction)
}
>
{jsFunction.label}
</MenuItem>
))}
</MenuSubContent>
</MenuSub>
))}
</MenuGroup>
</MenuGroup>
)}
</MenuContent>
</Menu>
</Header>
{fields.length > 0 && (

View File

@ -18,12 +18,8 @@ import type {
FunctionCallingEntityType,
FunctionCallingEntityTypeOption,
} from "../types";
import { useSelector, useDispatch } from "react-redux";
import { useSelector } from "react-redux";
import { selectEntityOptions } from "./selectors";
import history from "utils/history";
import { queryAddURL } from "ee/RouteBuilder";
import { createNewJSCollection } from "actions/jsPaneActions";
import { getCurrentPageId } from "selectors/editorSelectors";
interface FunctionCallingMenuFieldProps {
children?: React.ReactNode;
@ -86,9 +82,6 @@ const FunctionCallingMenuFieldRender = (props: FieldRenderProps) => {
const { autoFocus, children, disabled, input, onValueChange } = props;
const options = useSelector(selectEntityOptions);
const dispatch = useDispatch();
const pageId = useSelector(getCurrentPageId);
// Get existing entity IDs from the agentFunctions, excluding the currently selected value
const existingEntityIds = useMemo(() => {
const agentFunctionIds = new Set(Object.keys(options.agentFunctions));
@ -166,32 +159,6 @@ const FunctionCallingMenuFieldRender = (props: FieldRenderProps) => {
</Button>
</MenuTrigger>
<MenuContent align="start" loop width="235px">
{/* Create new options */}
<MenuGroup>
<MenuItem onSelect={() => history.push(queryAddURL({}))}>
<Flex alignItems="center" gap="spaces-2">
<Icon name="plus" size="md" />
New Query
</Flex>
</MenuItem>
<MenuItem
onSelect={() =>
dispatch(
createNewJSCollection(
pageId,
"AI_QUERY_FUNCTION_CALLING_CONFIG",
"onToolCall",
),
)
}
>
<Flex alignItems="center" gap="spaces-2">
<Icon name="plus" size="md" />
New JS Object
</Flex>
</MenuItem>
</MenuGroup>
{/* Query options group */}
{filteredQueryItems.length > 0 && (
<MenuGroup>