feature: add command feature column to toolset table and other minor table improvements (#5343)

* refactor: Update toolset configuration display name and description

This commit updates the display name and description of the toolset configuration in the custom component. The display name is changed to "Edit tools" and the description is modified to "Modify tool names and descriptions to help agents understand when to use each tool." This improves the clarity and usability of the toolset configuration.

* refactor: Update table modal header description handling

* refactor: Add EditMode.POPOVER option to table schema

* refactor: Add EditMode.POPOVER option to table schema

* refactor: Add table icon to table schema and components

* add style for not editable cells

* Add "commands" field to tool table schema

* refactor: Add "no_blank" and "valid_csv" field parsers

- Added "no_blank" field parser to remove leading and trailing whitespace from strings and throw an error if the string is blank.
- Added "valid_csv" field parser to replace whitespace with commas in strings.

* refactor: Add commands to tool description in ComponentToolkit

* refactor: Improve parsing of commands in validCommands function

* Update src/frontend/src/utils/stringManipulation.ts

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* [autofix.ci] apply automated fixes

* add support for hints on table header

* update descriptions on tool table

* [autofix.ci] apply automated fixes

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
This commit is contained in:
anovazzi1 2025-01-10 09:15:37 -03:00 • committed by GitHub
commit 18acd304a7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 185 additions and 51 deletions

View file

@ -159,6 +159,10 @@ def _format_tool_name(name: str):
return re.sub(r"[^a-zA-Z0-9_-]", "-", name) return re.sub(r"[^a-zA-Z0-9_-]", "-", name)
def _add_commands_to_tool_description(tool_description: str, commands: str):
return f"very_time you see one of those commands {commands} run the tool. tool description is {tool_description}"
class ComponentToolkit: class ComponentToolkit:
def __init__(self, component: Component, metadata: pd.DataFrame | None = None): def __init__(self, component: Component, metadata: pd.DataFrame | None = None):
self.component = component self.component = component
@ -276,6 +280,10 @@ class ComponentToolkit:
tool_metadata = metadata_dict[tag] tool_metadata = metadata_dict[tag]
tool.name = tool_metadata.get("name", tool.name) tool.name = tool_metadata.get("name", tool.name)
tool.description = tool_metadata.get("description", tool.description) tool.description = tool_metadata.get("description", tool.description)
if tool_metadata.get("commands"):
tool.description = _add_commands_to_tool_description(
tool.description, tool_metadata.get("commands")
)
else: else:
msg = f"Expected a StructuredTool or BaseTool, got {type(tool)}" msg = f"Expected a StructuredTool or BaseTool, got {type(tool)}"
raise TypeError(msg) raise TypeError(msg)

View file

@ -20,21 +20,31 @@ TOOL_TABLE_SCHEMA = [
"description": "Describe the purpose of the tool.", "description": "Describe the purpose of the tool.",
"sortable": False, "sortable": False,
"filterable": False, "filterable": False,
"edit_mode": EditMode.INLINE, "edit_mode": EditMode.POPOVER,
}, },
{ {
"name": "tags", "name": "tags",
"display_name": "Tool Identifiers", "display_name": "Tool Identifiers",
"type": "str", "type": "str",
"description": ( "description": ("The default identifiers for the tools and cannot be changed."),
"These are the default identifiers for the tools and cannot be changed. "
"Tool Name and Tool Description are the only editable fields."
),
"disable_edit": True, "disable_edit": True,
"sortable": False, "sortable": False,
"filterable": False, "filterable": False,
"edit_mode": EditMode.INLINE, "edit_mode": EditMode.INLINE,
}, },
{
"name": "commands",
"display_name": "Commands",
"type": "str",
"description": (
"Add commands to the tool. These commands will be used to run the tool. Start all commands with a `/`. "
"You can add multiple commands separated by a comma.\n"
"Example: `/command1`, `/command2`, `/command3`"
),
"sortable": False,
"filterable": False,
"edit_mode": EditMode.INLINE,
},
] ]
TOOLS_METADATA_INFO = "Use the table to configure the tools." TOOLS_METADATA_INFO = "Modify tool names and descriptions to help agents understand when to use each tool."

View file

@ -1209,13 +1209,13 @@ class Component(CustomComponent):
return TableInput( return TableInput(
name=TOOLS_METADATA_INPUT_NAME, name=TOOLS_METADATA_INPUT_NAME,
info=TOOLS_METADATA_INFO, display_name="Edit tools",
display_name="Toolset configuration",
real_time_refresh=True, real_time_refresh=True,
table_schema=TOOL_TABLE_SCHEMA, table_schema=TOOL_TABLE_SCHEMA,
value=tool_data, value=tool_data,
table_icon="Hammer",
trigger_icon="Hammer", trigger_icon="Hammer",
trigger_text="Open toolset", trigger_text="",
table_options=TableOptions( table_options=TableOptions(
block_add=True, block_add=True,
block_delete=True, block_delete=True,
@ -1225,6 +1225,10 @@ class Component(CustomComponent):
block_hide=True, block_hide=True,
block_select=True, block_select=True,
hide_options=True, hide_options=True,
field_parsers={"name": FieldParserType.SNAKE_CASE}, field_parsers={
"name": [FieldParserType.SNAKE_CASE, FieldParserType.NO_BLANK],
"commands": FieldParserType.COMMANDS,
},
description=TOOLS_METADATA_INFO,
), ),
) )

View file

@ -186,6 +186,7 @@ class TableMixin(BaseModel):
table_schema: TableSchema | list[Column] | None = None table_schema: TableSchema | list[Column] | None = None
trigger_text: str = Field(default="Open table") trigger_text: str = Field(default="Open table")
trigger_icon: str = Field(default="Table") trigger_icon: str = Field(default="Table")
table_icon: str = Field(default="Table")
table_options: TableOptions | None = None table_options: TableOptions | None = None
@field_validator("table_schema") @field_validator("table_schema")

View file

@ -15,6 +15,7 @@ class FormatterType(str, Enum):
class EditMode(str, Enum): class EditMode(str, Enum):
MODAL = "modal" MODAL = "modal"
POPOVER = "popover"
INLINE = "inline" INLINE = "inline"
@ -83,6 +84,9 @@ class FieldParserType(str, Enum):
KEBAB_CASE = "kebab_case" KEBAB_CASE = "kebab_case"
LOWERCASE = "lowercase" LOWERCASE = "lowercase"
UPPERCASE = "uppercase" UPPERCASE = "uppercase"
NO_BLANK = "no_blank"
VALID_CSV = ("valid_csv",)
COMMANDS = "commands"
class TableOptions(BaseModel): class TableOptions(BaseModel):
@ -96,3 +100,4 @@ class TableOptions(BaseModel):
hide_options: bool = Field(default=False) hide_options: bool = Field(default=False)
field_validators: dict[str, list[FieldValidatorType] | FieldValidatorType] | None = Field(default=None) field_validators: dict[str, list[FieldValidatorType] | FieldValidatorType] | None = Field(default=None)
field_parsers: dict[str, list[FieldParserType] | FieldParserType] | None = Field(default=None) field_parsers: dict[str, list[FieldParserType] | FieldParserType] | None = Field(default=None)
description: str | None = Field(default=None)

View file

@ -209,3 +209,14 @@ code {
resize: none !important; resize: none !important;
height: 100% !important; height: 100% !important;
} }
.cell-disable-edit {
cursor: not-allowed;
}
.cell-disable-edit.ag-cell-focus {
outline: none !important;
border: 0px !important;
box-shadow: none !important;
border-radius: 0px !important;
}

View file

@ -1,3 +1,4 @@
import ShadTooltip from "@/components/common/shadTooltipComponent";
import TableModal from "@/modals/tableModal"; import TableModal from "@/modals/tableModal";
import { FormatColumns, generateBackendColumnsFromValue } from "@/utils/utils"; import { FormatColumns, generateBackendColumnsFromValue } from "@/utils/utils";
import { DataTypeDefinition, SelectionChangedEvent } from "ag-grid-community"; import { DataTypeDefinition, SelectionChangedEvent } from "ag-grid-community";
@ -20,6 +21,7 @@ export default function TableNodeComponent({
table_options, table_options,
trigger_icon = "Table", trigger_icon = "Table",
trigger_text = "Open Table", trigger_text = "Open Table",
table_icon,
}: InputProps<any[], TableComponentType>): JSX.Element { }: InputProps<any[], TableComponentType>): JSX.Element {
const dataTypeDefinitions: { const dataTypeDefinitions: {
[cellDataType: string]: DataTypeDefinition<any>; [cellDataType: string]: DataTypeDefinition<any>;
@ -67,7 +69,26 @@ export default function TableNodeComponent({
const componentColumns = columns const componentColumns = columns
? columns ? columns
: generateBackendColumnsFromValue(value ?? [], table_options); : generateBackendColumnsFromValue(value ?? [], table_options);
const AgColumns = FormatColumns(componentColumns); let AgColumns = FormatColumns(componentColumns);
// add info to each column
AgColumns = AgColumns.map((col) => {
if (col.context?.info) {
return {
...col,
headerComponent: () => (
<div className="flex items-center gap-1">
<div>{col.headerName}</div>
<ShadTooltip content={col.context?.info}>
<div>
<ForwardedIconComponent name="Info" className="h-4 w-4" />
</div>
</ShadTooltip>
</div>
),
};
}
return col;
});
function setAllRows() { function setAllRows() {
if (agGrid.current && !agGrid.current.api.isDestroyed()) { if (agGrid.current && !agGrid.current.api.isDestroyed()) {
const rows: any = []; const rows: any = [];
@ -107,7 +128,7 @@ export default function TableNodeComponent({
.map((column) => { .map((column) => {
const isCustomEdit = const isCustomEdit =
column.formatter && column.formatter &&
((column.formatter === "text" && column.edit_mode !== "inline") || ((column.formatter === "text" && column.edit_mode === "modal") ||
column.formatter === "json"); column.formatter === "json");
return { return {
field: column.name, field: column.name,
@ -128,6 +149,8 @@ export default function TableNodeComponent({
> >
<div className="flex w-full items-center gap-3" data-testid={"div-" + id}> <div className="flex w-full items-center gap-3" data-testid={"div-" + id}>
<TableModal <TableModal
stopEditingWhenCellsLoseFocus={true}
tableIcon={table_icon}
tableOptions={table_options} tableOptions={table_options}
dataTypeDefinitions={dataTypeDefinitions} dataTypeDefinitions={dataTypeDefinitions}
autoSizeStrategy={{ type: "fitGridWidth", defaultMinWidth: 100 }} autoSizeStrategy={{ type: "fitGridWidth", defaultMinWidth: 100 }}

View file

@ -180,6 +180,7 @@ export function ParameterRenderComponent({
table_options={templateData?.table_options} table_options={templateData?.table_options}
trigger_icon={templateData?.trigger_icon} trigger_icon={templateData?.trigger_icon}
trigger_text={templateData?.trigger_text} trigger_text={templateData?.trigger_text}
table_icon={templateData?.table_icon}
/> />
); );
case "slider": case "slider":

View file

@ -31,6 +31,7 @@ export type TableComponentType = {
table_options?: TableOptionsTypeAPI; table_options?: TableOptionsTypeAPI;
trigger_text?: string; trigger_text?: string;
trigger_icon?: string; trigger_icon?: string;
table_icon?: string;
}; };
export type FloatComponentType = { export type FloatComponentType = {

View file

@ -16,11 +16,19 @@ interface TableModalProps extends TableComponentProps {
children: React.ReactNode; children: React.ReactNode;
tableOptions?: TableOptionsTypeAPI; tableOptions?: TableOptionsTypeAPI;
hideColumns?: boolean | string[]; hideColumns?: boolean | string[];
tableIcon?: string;
} }
const TableModal = forwardRef<AgGridReact, TableModalProps>( const TableModal = forwardRef<AgGridReact, TableModalProps>(
( (
{ tableTitle, description, children, disabled, ...props }: TableModalProps, {
tableTitle,
description,
children,
disabled,
tableIcon,
...props
}: TableModalProps,
ref: ForwardedRef<AgGridReact>, ref: ForwardedRef<AgGridReact>,
) => { ) => {
return ( return (
@ -37,9 +45,14 @@ const TableModal = forwardRef<AgGridReact, TableModalProps>(
disable={disabled} disable={disabled}
> >
<BaseModal.Trigger asChild>{children}</BaseModal.Trigger> <BaseModal.Trigger asChild>{children}</BaseModal.Trigger>
<BaseModal.Header description={description}> <BaseModal.Header
description={props.tableOptions?.description ?? description}
>
<span className="pr-2">{tableTitle}</span> <span className="pr-2">{tableTitle}</span>
<ForwardedIconComponent name="Table" className="mr-2 h-4 w-4" /> <ForwardedIconComponent
name={tableIcon ?? "Table"}
className="mr-2 h-4 w-4"
/>
</BaseModal.Header> </BaseModal.Header>
<BaseModal.Content> <BaseModal.Content>
<TableComponent <TableComponent

View file

@ -301,7 +301,10 @@ export type FieldParserType =
| "pascal_case" | "pascal_case"
| "kebab_case" | "kebab_case"
| "lowercase" | "lowercase"
| "uppercase"; | "uppercase"
| "no_blank"
| "valid_csv"
| "commands";
export type TableOptionsTypeAPI = { export type TableOptionsTypeAPI = {
block_add?: boolean; block_add?: boolean;
@ -316,4 +319,5 @@ export type TableOptionsTypeAPI = {
FieldValidatorType | { [key: string]: FieldValidatorType } FieldValidatorType | { [key: string]: FieldValidatorType }
>; >;
field_parsers?: Array<FieldParserType | { [key: string]: FieldParserType }>; field_parsers?: Array<FieldParserType | { [key: string]: FieldParserType }>;
description?: string;
}; };

View file

@ -27,5 +27,5 @@ export interface ColumnField {
description?: string; description?: string;
disable_edit?: boolean; disable_edit?: boolean;
default?: any; // Add this line default?: any; // Add this line
edit_mode?: "modal" | "inline"; edit_mode?: "modal" | "inline" | "popover";
} }

View file

@ -33,6 +33,34 @@ function toUpperCase(str: string): string {
return str.toUpperCase(); return str.toUpperCase();
} }
function noBlank(str: string): string {
const trim = str.trim();
if (trim === "") {
throw new Error("String is blank");
}
return trim;
}
function validCsv(str: string): string {
return str.trim().replace(/\s+/g, ",");
}
function validCommands(str: string): string {
return str
.trim()
.split(/[\s,]+/)
.flatMap((cmd) => {
cmd = cmd.trim();
cmd = cmd.replace(/\\/g, "/");
return cmd
.split("/")
.filter((part) => part.length > 0)
.map((part) => `/${part}`);
})
.filter((cmd) => cmd.length > 1)
.join(", ");
}
export function parseString( export function parseString(
str: string, str: string,
parsers: FieldParserType[] | FieldParserType, parsers: FieldParserType[] | FieldParserType,
@ -48,25 +76,38 @@ export function parseString(
} }
for (const parser of parsersArray) { for (const parser of parsersArray) {
switch (parser) { try {
case "snake_case": switch (parser) {
result = toSnakeCase(result); case "snake_case":
break; result = toSnakeCase(result);
case "camel_case": break;
result = toCamelCase(result); case "camel_case":
break; result = toCamelCase(result);
case "pascal_case": break;
result = toPascalCase(result); case "pascal_case":
break; result = toPascalCase(result);
case "kebab_case": break;
result = toKebabCase(result); case "kebab_case":
break; result = toKebabCase(result);
case "lowercase": break;
result = toLowerCase(result); case "lowercase":
break; result = toLowerCase(result);
case "uppercase": break;
result = toUpperCase(result); case "uppercase":
break; result = toUpperCase(result);
break;
case "no_blank":
result = noBlank(result);
break;
case "valid_csv":
result = validCsv(result);
break;
case "commands":
result = validCommands(result);
break;
}
} catch (error) {
throw new Error(`Error in parser ${parser}`);
} }
} }

View file

@ -1,4 +1,5 @@
import TableAutoCellRender from "@/components/core/parameterRenderComponent/components/tableComponent/components/tableAutoCellRender"; import TableAutoCellRender from "@/components/core/parameterRenderComponent/components/tableComponent/components/tableAutoCellRender";
import useAlertStore from "@/stores/alertStore";
import { ColumnField, FormatterType } from "@/types/utils/functions"; import { ColumnField, FormatterType } from "@/types/utils/functions";
import { ColDef, ColGroupDef, ValueParserParams } from "ag-grid-community"; import { ColDef, ColGroupDef, ValueParserParams } from "ag-grid-community";
import clsx, { ClassValue } from "clsx"; import clsx, { ClassValue } from "clsx";
@ -526,17 +527,26 @@ export function FormatColumns(columns: ColumnField[]): ColDef<any>[] {
field: col.name, field: col.name,
sortable: col.sortable, sortable: col.sortable,
filter: col.filterable, filter: col.filterable,
editable: !col.disable_edit, context: col.description ? { info: col.description } : {},
cellClass: col.disable_edit ? "cell-disable-edit" : "",
valueParser: (params: ValueParserParams) => { valueParser: (params: ValueParserParams) => {
const { context, newValue, colDef } = params; const { context, newValue, colDef, oldValue } = params;
if ( if (
context.field_parsers && context.field_parsers &&
context.field_parsers[colDef.field ?? ""] context.field_parsers[colDef.field ?? ""]
) { ) {
return parseString( try {
newValue, return parseString(
context.field_parsers[colDef.field ?? ""], newValue,
); context.field_parsers[colDef.field ?? ""],
);
} catch (error: any) {
useAlertStore.getState().setErrorData({
title: "Error parsing string",
list: [String(error.message ?? error)],
});
return oldValue;
}
} }
return newValue; return newValue;
}, },
@ -551,15 +561,17 @@ export function FormatColumns(columns: ColumnField[]): ColDef<any>[] {
formatter: col.formatter, formatter: col.formatter,
}; };
if (col.formatter !== FormatterType.text || col.edit_mode !== "inline") { if (col.formatter !== FormatterType.text || col.edit_mode !== "inline") {
newCol.cellRenderer = TableAutoCellRender; if (col.edit_mode === "popover") {
} else { newCol.wrapText = true;
newCol.wrapText = true; newCol.autoHeight = true;
newCol.autoHeight = true; newCol.cellEditor = "agLargeTextCellEditor";
newCol.cellEditor = "agLargeTextCellEditor"; newCol.cellEditorPopup = true;
newCol.cellEditorPopup = true; newCol.cellEditorParams = {
newCol.cellEditorParams = { maxLength: 100000000,
maxLength: 100000000, };
}; } else {
newCol.cellRenderer = TableAutoCellRender;
}
} }
} }
return newCol; return newCol;