diff --git a/src/backend/base/langflow/base/tools/component_tool.py b/src/backend/base/langflow/base/tools/component_tool.py index 30e1d83d4..baba33773 100644 --- a/src/backend/base/langflow/base/tools/component_tool.py +++ b/src/backend/base/langflow/base/tools/component_tool.py @@ -159,6 +159,10 @@ def _format_tool_name(name: str): 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: def __init__(self, component: Component, metadata: pd.DataFrame | None = None): self.component = component @@ -276,6 +280,10 @@ class ComponentToolkit: tool_metadata = metadata_dict[tag] tool.name = tool_metadata.get("name", tool.name) 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: msg = f"Expected a StructuredTool or BaseTool, got {type(tool)}" raise TypeError(msg) diff --git a/src/backend/base/langflow/base/tools/constants.py b/src/backend/base/langflow/base/tools/constants.py index 64bd4c3cf..cb1c15050 100644 --- a/src/backend/base/langflow/base/tools/constants.py +++ b/src/backend/base/langflow/base/tools/constants.py @@ -20,21 +20,31 @@ TOOL_TABLE_SCHEMA = [ "description": "Describe the purpose of the tool.", "sortable": False, "filterable": False, - "edit_mode": EditMode.INLINE, + "edit_mode": EditMode.POPOVER, }, { "name": "tags", "display_name": "Tool Identifiers", "type": "str", - "description": ( - "These are the default identifiers for the tools and cannot be changed. " - "Tool Name and Tool Description are the only editable fields." - ), + "description": ("The default identifiers for the tools and cannot be changed."), "disable_edit": True, "sortable": False, "filterable": False, "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." diff --git a/src/backend/base/langflow/custom/custom_component/component.py b/src/backend/base/langflow/custom/custom_component/component.py index aa51a223c..e3465a172 100644 --- a/src/backend/base/langflow/custom/custom_component/component.py +++ b/src/backend/base/langflow/custom/custom_component/component.py @@ -1209,13 +1209,13 @@ class Component(CustomComponent): return TableInput( name=TOOLS_METADATA_INPUT_NAME, - info=TOOLS_METADATA_INFO, - display_name="Toolset configuration", + display_name="Edit tools", real_time_refresh=True, table_schema=TOOL_TABLE_SCHEMA, value=tool_data, + table_icon="Hammer", trigger_icon="Hammer", - trigger_text="Open toolset", + trigger_text="", table_options=TableOptions( block_add=True, block_delete=True, @@ -1225,6 +1225,10 @@ class Component(CustomComponent): block_hide=True, block_select=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, ), ) diff --git a/src/backend/base/langflow/inputs/input_mixin.py b/src/backend/base/langflow/inputs/input_mixin.py index 3a2c85fa2..7656ae23a 100644 --- a/src/backend/base/langflow/inputs/input_mixin.py +++ b/src/backend/base/langflow/inputs/input_mixin.py @@ -186,6 +186,7 @@ class TableMixin(BaseModel): table_schema: TableSchema | list[Column] | None = None trigger_text: str = Field(default="Open table") trigger_icon: str = Field(default="Table") + table_icon: str = Field(default="Table") table_options: TableOptions | None = None @field_validator("table_schema") diff --git a/src/backend/base/langflow/schema/table.py b/src/backend/base/langflow/schema/table.py index efbce244e..4ec914163 100644 --- a/src/backend/base/langflow/schema/table.py +++ b/src/backend/base/langflow/schema/table.py @@ -15,6 +15,7 @@ class FormatterType(str, Enum): class EditMode(str, Enum): MODAL = "modal" + POPOVER = "popover" INLINE = "inline" @@ -83,6 +84,9 @@ class FieldParserType(str, Enum): KEBAB_CASE = "kebab_case" LOWERCASE = "lowercase" UPPERCASE = "uppercase" + NO_BLANK = "no_blank" + VALID_CSV = ("valid_csv",) + COMMANDS = "commands" class TableOptions(BaseModel): @@ -96,3 +100,4 @@ class TableOptions(BaseModel): hide_options: bool = Field(default=False) field_validators: dict[str, list[FieldValidatorType] | FieldValidatorType] | None = Field(default=None) field_parsers: dict[str, list[FieldParserType] | FieldParserType] | None = Field(default=None) + description: str | None = Field(default=None) diff --git a/src/frontend/src/App.css b/src/frontend/src/App.css index 37a3b6848..91d5f3a14 100644 --- a/src/frontend/src/App.css +++ b/src/frontend/src/App.css @@ -209,3 +209,14 @@ code { resize: none !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; +} diff --git a/src/frontend/src/components/core/parameterRenderComponent/components/TableNodeComponent/index.tsx b/src/frontend/src/components/core/parameterRenderComponent/components/TableNodeComponent/index.tsx index 516a2dadf..5215e2282 100644 --- a/src/frontend/src/components/core/parameterRenderComponent/components/TableNodeComponent/index.tsx +++ b/src/frontend/src/components/core/parameterRenderComponent/components/TableNodeComponent/index.tsx @@ -1,3 +1,4 @@ +import ShadTooltip from "@/components/common/shadTooltipComponent"; import TableModal from "@/modals/tableModal"; import { FormatColumns, generateBackendColumnsFromValue } from "@/utils/utils"; import { DataTypeDefinition, SelectionChangedEvent } from "ag-grid-community"; @@ -20,6 +21,7 @@ export default function TableNodeComponent({ table_options, trigger_icon = "Table", trigger_text = "Open Table", + table_icon, }: InputProps): JSX.Element { const dataTypeDefinitions: { [cellDataType: string]: DataTypeDefinition; @@ -67,7 +69,26 @@ export default function TableNodeComponent({ const componentColumns = columns ? columns : 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: () => ( +
+
{col.headerName}
+ +
+ +
+
+
+ ), + }; + } + return col; + }); function setAllRows() { if (agGrid.current && !agGrid.current.api.isDestroyed()) { const rows: any = []; @@ -107,7 +128,7 @@ export default function TableNodeComponent({ .map((column) => { const isCustomEdit = column.formatter && - ((column.formatter === "text" && column.edit_mode !== "inline") || + ((column.formatter === "text" && column.edit_mode === "modal") || column.formatter === "json"); return { field: column.name, @@ -128,6 +149,8 @@ export default function TableNodeComponent({ >
); case "slider": diff --git a/src/frontend/src/components/core/parameterRenderComponent/types.ts b/src/frontend/src/components/core/parameterRenderComponent/types.ts index e8d631f70..ba8b2d119 100644 --- a/src/frontend/src/components/core/parameterRenderComponent/types.ts +++ b/src/frontend/src/components/core/parameterRenderComponent/types.ts @@ -31,6 +31,7 @@ export type TableComponentType = { table_options?: TableOptionsTypeAPI; trigger_text?: string; trigger_icon?: string; + table_icon?: string; }; export type FloatComponentType = { diff --git a/src/frontend/src/modals/tableModal/index.tsx b/src/frontend/src/modals/tableModal/index.tsx index d09ad26b8..92984243d 100644 --- a/src/frontend/src/modals/tableModal/index.tsx +++ b/src/frontend/src/modals/tableModal/index.tsx @@ -16,11 +16,19 @@ interface TableModalProps extends TableComponentProps { children: React.ReactNode; tableOptions?: TableOptionsTypeAPI; hideColumns?: boolean | string[]; + tableIcon?: string; } const TableModal = forwardRef( ( - { tableTitle, description, children, disabled, ...props }: TableModalProps, + { + tableTitle, + description, + children, + disabled, + tableIcon, + ...props + }: TableModalProps, ref: ForwardedRef, ) => { return ( @@ -37,9 +45,14 @@ const TableModal = forwardRef( disable={disabled} > {children} - + {tableTitle} - + ; field_parsers?: Array; + description?: string; }; diff --git a/src/frontend/src/types/utils/functions.ts b/src/frontend/src/types/utils/functions.ts index fafb21bba..c88b4421f 100644 --- a/src/frontend/src/types/utils/functions.ts +++ b/src/frontend/src/types/utils/functions.ts @@ -27,5 +27,5 @@ export interface ColumnField { description?: string; disable_edit?: boolean; default?: any; // Add this line - edit_mode?: "modal" | "inline"; + edit_mode?: "modal" | "inline" | "popover"; } diff --git a/src/frontend/src/utils/stringManipulation.ts b/src/frontend/src/utils/stringManipulation.ts index cdce60044..246a48712 100644 --- a/src/frontend/src/utils/stringManipulation.ts +++ b/src/frontend/src/utils/stringManipulation.ts @@ -33,6 +33,34 @@ function toUpperCase(str: string): string { 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( str: string, parsers: FieldParserType[] | FieldParserType, @@ -48,25 +76,38 @@ export function parseString( } for (const parser of parsersArray) { - switch (parser) { - case "snake_case": - result = toSnakeCase(result); - break; - case "camel_case": - result = toCamelCase(result); - break; - case "pascal_case": - result = toPascalCase(result); - break; - case "kebab_case": - result = toKebabCase(result); - break; - case "lowercase": - result = toLowerCase(result); - break; - case "uppercase": - result = toUpperCase(result); - break; + try { + switch (parser) { + case "snake_case": + result = toSnakeCase(result); + break; + case "camel_case": + result = toCamelCase(result); + break; + case "pascal_case": + result = toPascalCase(result); + break; + case "kebab_case": + result = toKebabCase(result); + break; + case "lowercase": + result = toLowerCase(result); + break; + case "uppercase": + 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}`); } } diff --git a/src/frontend/src/utils/utils.ts b/src/frontend/src/utils/utils.ts index 8feb62294..502808215 100644 --- a/src/frontend/src/utils/utils.ts +++ b/src/frontend/src/utils/utils.ts @@ -1,4 +1,5 @@ import TableAutoCellRender from "@/components/core/parameterRenderComponent/components/tableComponent/components/tableAutoCellRender"; +import useAlertStore from "@/stores/alertStore"; import { ColumnField, FormatterType } from "@/types/utils/functions"; import { ColDef, ColGroupDef, ValueParserParams } from "ag-grid-community"; import clsx, { ClassValue } from "clsx"; @@ -526,17 +527,26 @@ export function FormatColumns(columns: ColumnField[]): ColDef[] { field: col.name, sortable: col.sortable, filter: col.filterable, - editable: !col.disable_edit, + context: col.description ? { info: col.description } : {}, + cellClass: col.disable_edit ? "cell-disable-edit" : "", valueParser: (params: ValueParserParams) => { - const { context, newValue, colDef } = params; + const { context, newValue, colDef, oldValue } = params; if ( context.field_parsers && context.field_parsers[colDef.field ?? ""] ) { - return parseString( - newValue, - context.field_parsers[colDef.field ?? ""], - ); + try { + return parseString( + 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; }, @@ -551,15 +561,17 @@ export function FormatColumns(columns: ColumnField[]): ColDef[] { formatter: col.formatter, }; if (col.formatter !== FormatterType.text || col.edit_mode !== "inline") { - newCol.cellRenderer = TableAutoCellRender; - } else { - newCol.wrapText = true; - newCol.autoHeight = true; - newCol.cellEditor = "agLargeTextCellEditor"; - newCol.cellEditorPopup = true; - newCol.cellEditorParams = { - maxLength: 100000000, - }; + if (col.edit_mode === "popover") { + newCol.wrapText = true; + newCol.autoHeight = true; + newCol.cellEditor = "agLargeTextCellEditor"; + newCol.cellEditorPopup = true; + newCol.cellEditorParams = { + maxLength: 100000000, + }; + } else { + newCol.cellRenderer = TableAutoCellRender; + } } } return newCol;