Inference Logoinference.sh

Widget Actions

Trigger actions on the backend from user interactions in your widgets.

Actions are attached directly to interactive elements (buttons, inputs, checkboxes) and allow the widget to trigger server-side logic or client-side callbacks without the user submitting a message.

Action Properties

typescript
1interface WidgetAction {2  type: string;                    // Action identifier (e.g., "submit", "delete", "toggle")3  payload?: Record<string, any>;   // Optional data to include with the action4  handler?: "server" | "client";   // Handler location (default: "server")5  loadingBehavior?: "auto" | "self" | "container" | "none"; // Loading state behavior6}

loadingBehavior Options

ValueDescription
autoAutomatic loading state (default)
selfShow loading spinner on the clicked element
containerShow loading state on the entire widget
noneNo loading indicator

Attaching Actions to Elements

Actions are attached directly to elements using onClickAction, onChangeAction, or onCheckedChangeAction:

Button Click Actions

go
1children := []models.WidgetNode{2    {3        Type:    models.WidgetNodeTypeButton,4        Label:   "Submit",5        Variant: "default",6        OnClickAction: &models.WidgetAction{7            Type:            "submit",8            Payload:         map[string]interface{}{"source": "form"},9            LoadingBehavior: "self",10        },11    },12    {13        Type:    models.WidgetNodeTypeButton,14        Label:   "Cancel",15        Variant: "outline",16        OnClickAction: &models.WidgetAction{17            Type: "cancel",18        },19    },20}

Input Change Actions

go
1{2    Type:        models.WidgetNodeTypeInput,3    Name:        "email",4    Placeholder: "Enter email",5    OnChangeAction: &models.WidgetAction{6        Type:    "email_changed",7        Handler: "client",8    },9}

Checkbox Toggle Actions

go
1{2    Type:          models.WidgetNodeTypeCheckbox,3    Name:          "task_completed",4    Label:         "Mark as complete",5    DefaultChecked: false,6    OnCheckedChangeAction: &models.WidgetAction{7        Type:    "toggle_task",8        Payload: map[string]interface{}{"task_id": "123"},9    },10}

Complete Example

go
1widget := &models.Widget{2    Type:  models.WidgetTypeUI,3    Title: "Delete Project?",4    Children: []models.WidgetNode{5        {6            Type: models.WidgetNodeTypeCol,7            Gap:  3,8            Children: []models.WidgetNode{9                {Type: models.WidgetNodeTypeText, Value: "This action cannot be undone. All data will be permanently deleted."},10                {11                    Type:    models.WidgetNodeTypeRow,12                    Gap:     2,13                    Justify: "end",14                    Children: []models.WidgetNode{15                        {16                            Type:    models.WidgetNodeTypeButton,17                            Label:   "Cancel",18                            Variant: "ghost",19                            OnClickAction: &models.WidgetAction{Type: "cancel"},20                        },21                        {22                            Type:    models.WidgetNodeTypeButton,23                            Label:   "Delete",24                            Variant: "destructive",25                            OnClickAction: &models.WidgetAction{26                                Type:            "confirm_delete",27                                Payload:         map[string]interface{}{"project_id": "123"},28                                LoadingBehavior: "self",29                            },30                        },31                    },32                },33            },34        },35    },36}

Handling Actions

Frontend

Capture widget events with the onAction callback in your widget renderer:

typescript
1<WidgetRenderer2  widget={widget}3  onAction={async (action, formData) => {4    if (action.handler === 'client') {5      // Handle client-side action6      handleClientAction(action, formData);7      return;8    }910    // Send to server11    await fetch('/api/widget-action', {12      method: 'POST',13      headers: { 'Content-Type': 'application/json' },14      body: JSON.stringify({ action, formData }),15    });16  }}17/>

Backend

Handle widget actions in your agent:

go
1func (e *Executor) HandleWidgetAction(ctx context.Context, action models.WidgetAction, formData map[string]interface{}) error {2    switch action.Type {3    case "confirm_delete":4        projectID := action.Payload["project_id"].(string)5        return e.deleteProject(ctx, projectID)6    case "cancel":7        return e.cancelAction(ctx)8    case "toggle_task":9        taskID := action.Payload["task_id"].(string)10        return e.toggleTask(ctx, taskID)11    default:12        return fmt.Errorf("unknown action type: %s", action.Type)13    }14}

Common Action Types

TypeDescription
confirmUser confirmed the action
cancelUser cancelled/dismissed the widget
submitForm submission
deleteDelete operation
toggleToggle a boolean value

Form Data

When a widget contains form inputs (input, select, checkbox), the form data is automatically collected and passed to the action handler:

go
1widget := &models.Widget{2    Type:  models.WidgetTypeUI,3    Title: "Create User",4    Children: []models.WidgetNode{5        {6            Type: models.WidgetNodeTypeCol,7            Gap:  3,8            Children: []models.WidgetNode{9                {Type: models.WidgetNodeTypeLabel, Value: "Email", FieldName: "email"},10                {Type: models.WidgetNodeTypeInput, Name: "email", Placeholder: "Enter email"},11                {Type: models.WidgetNodeTypeLabel, Value: "Role", FieldName: "role"},12                {13                    Type: models.WidgetNodeTypeSelect,14                    Name: "role",15                    Options: []models.WidgetSelectOption{16                        {Label: "Admin", Value: "admin"},17                        {Label: "User", Value: "user"},18                    },19                },20                {21                    Type:    models.WidgetNodeTypeRow,22                    Gap:     2,23                    Justify: "end",24                    Children: []models.WidgetNode{25                        {26                            Type: models.WidgetNodeTypeButton,27                            Label: "Create",28                            Variant: "default",29                            OnClickAction: &models.WidgetAction{30                                Type:            "create_user",31                                LoadingBehavior: "self",32                            },33                        },34                    },35                },36            },37        },38    },39}

When the "Create" button is clicked, formData will contain:

json
1{2  "email": "[email protected]",3  "role": "admin"4}

Best Practices

1. Use Descriptive Action Types

go
1// Good2OnClickAction: &models.WidgetAction{Type: "approve_request"}34// Avoid5OnClickAction: &models.WidgetAction{Type: "action1"}

2. Include Context in Payload

go
1OnClickAction: &models.WidgetAction{2    Type: "approve_request",3    Payload: map[string]interface{}{4        "request_id":     request.ID,5        "approval_level": "manager",6    },7}

3. Use Loading States for Async Operations

go
1OnClickAction: &models.WidgetAction{2    Type:            "submit_form",3    LoadingBehavior: "self", // Shows spinner on button during submission4}

4. Validate Action Data on Backend

go
1func (e *Executor) HandleAction(action models.WidgetAction, formData map[string]interface{}) error {2    // Validate required fields3    requestID, ok := action.Payload["request_id"].(string)4    if !ok || requestID == "" {5        return fmt.Errorf("request_id is required")6    }78    // ... handle action9}

we use cookies

we use cookies to ensure you get the best experience on our website. for more information on how we use cookies, please see our cookie policy.

by clicking "accept", you agree to our use of cookies.
learn more.