Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 20 additions & 4 deletions AiChatWorkflowsAutomationApp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,15 @@ import {
} from "@rocket.chat/apps-engine/definition/messages";
import { PostMessageSentToBotHandler } from "./handler/PostMessageSentToBotHandler";
import { ChatAutomationCreate } from "./slashCommands/ChatAutomationCreate";
import { UIKitViewSubmitInteractionContext } from "@rocket.chat/apps-engine/definition/uikit";
import { IUIKitResponse, UIKitBlockInteractionContext, UIKitViewSubmitInteractionContext } from "@rocket.chat/apps-engine/definition/uikit";
import { ExecuteViewSubmitHandler } from "./handler/ExecuteViewSubmitHandler";
import { PostMessageSentHandler } from "./handler/PostMessageSentHandler";
import { ChatAutomation } from "./slashCommands/ChatAutomation";
import { ExecuteBlockActionHandler } from "./handler/ExecuteBlockActionHandler";

export class AiChatWorkflowsAutomationApp
extends App
implements IPostMessageSentToBot, IPostMessageSent
{
implements IPostMessageSentToBot, IPostMessageSent {
constructor(info: IAppInfo, logger: ILogger, accessors: IAppAccessors) {
super(info, logger, accessors);
}
Expand All @@ -46,7 +46,23 @@ export class AiChatWorkflowsAutomationApp
modify
);
}

public async executeBlockActionHandler(
context: UIKitBlockInteractionContext,
read: IRead,
http: IHttp,
persistence: IPersistence,
modify: IModify
): Promise<IUIKitResponse> {
const handler = new ExecuteBlockActionHandler(
this,
read,
http,
modify,
persistence,
context
);
return await handler.handleActions();
}
public async executeViewSubmitHandler(
context: UIKitViewSubmitInteractionContext,
read: IRead,
Expand Down
7 changes: 1 addition & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,12 +46,7 @@ You can create chat workflow automations using **two methods**:
| Command | Description |
|--------|-------------|
| `/chat-automation ping` | Sends a hello message to your Direct Messages (DM) from the app |
| `/chat-automation list` | Displays a list of all created workflows |
| `/chat-automation delete <id>` | Deletes the workflow with the specified ID |
| `/chat-automation enable <id>` | Enables the workflow with the specified ID |
| `/chat-automation disable <id>` | Disables the workflow with the specified ID |
| `/chat-automation notification off <id>` | Disables notifications for a workflow trigger |
| `/chat-automation notification on <id>` | Enables notifications for a workflow trigger |
| `/chat-automation list` | Displays a list of all created workflows where u can delete and edit them as well |

---

Expand Down
19 changes: 7 additions & 12 deletions definitions/MessageEnum.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,17 @@
export enum MessageEnum {
SUCCESS_MESSAGE_APP_DM_DIRECT = `_Success! The Chat Automation workflow has been created._
SUCCESS_MESSAGE_APP_DM_DIRECT = `_Success! The Chat Automation workflow has been created._
_For more details, please open the thread._`,
SUCCESS_MESSAGE_APP_DM_REASONING = `_Success! The Chat Automation workflow has been created._
SUCCESS_MESSAGE_APP_DM_REASONING = `_Success! The Chat Automation workflow has been created._
_Here are the details:_`,
SUCCESS_MESSAGE_UI_MODAL = `_Success! The Chat Automation workflow has been created._
Automation command:
Automation command:
`,
CONTINUE_IN_THREAD_MESSAGE = `For the current command, please continue the conversation in this thread.
CONTINUE_IN_THREAD_MESSAGE = `For the current command, please continue the conversation in this thread.
To create a new command, start a new message - do not reply in this thread.`,
WORKFLOW_NOT_FOUND_DM = `_No automation workflows found that were created using Chat. Please create a workflow using Chat first._`,
WORKFLOW_NOT_FOUND_UI = `_No automation workflows found that were created using the UI Block. Please create a workflow using the UI Block first._`,
CHAT_AUTOMATION_COMMAND_INSTRUCTION_MESSAGE = `🛠️ *Available Slash Commands*\n\n` +
`• \`/chat-automation ping\` – Sends a hello message to your DM\n` +
`• \`/chat-automation list\` – Lists all created workflows\n` +
`• \`/chat-automation delete <id>\` – Deletes the workflow with the specified ID\n` +
`• \`/chat-automation enable <id>\` – Enables the workflow with the specified ID\n` +
`• \`/chat-automation disable <id>\` – Disables the workflow with the specified ID\n` +
`• \`/chat-automation notification on <id>\` – Enables notifications for a workflow\n` +
`• \`/chat-automation notification off <id>\` – Disables notifications for a workflow\n\n` +
`👉 *Please provide a filter or action*, e.g., \`ping\`, \`list\`, \`delete workflowId_1753828680384\`, \`notification off workflowId_1753828680384\``,
`• \`/chat-automation ping\` – Sends a hello message to your DM\n` +
`• \`/chat-automation list\` – Lists all created workflows View, search, edit, and delete workflows \n` +
`👉 *Please provide a filter or action*, e.g., \`ping\`, \`list\``
}
1 change: 1 addition & 0 deletions definitions/ModalsEnum.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export enum Modals {
AutomationCreate = "automation-create-modal",
ContextualBar = "ContextualBar-modal"
}
7 changes: 7 additions & 0 deletions definitions/ModalsEnum/DeleteWorkflowModal.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export enum DeleteWorkflowModalEnum {
VIEW_ID = 'delete_workflow_modal',
WORKFLOW_ID_BLOCK = 'workflow_id_block',
CONFIRM_BLOCK = 'confirm_delete_block',
CANCEL_ACTION = 'cancel_delete_action',
SUBMIT_ACTION = 'confirm_delete_submit',
}
17 changes: 17 additions & 0 deletions definitions/ModalsEnum/Editworkflow.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
export enum EditWorkflowModalEnum {
VIEW_ID = 'edit_workflow_modal',
USERS_BLOCK = 'edit_users_block',
USERS_ACTION = 'users',
CHANNELS_BLOCK = 'edit_channels_block',
CHANNELS_ACTION = 'channels',
CONDITION_BLOCK = 'edit_condition_block',
CONDITION_ACTION = 'condition',
ACTION_BLOCK = 'edit_action_block',
ACTION_ACTION = 'action',
RESPONSE_BLOCK = 'edit_response_block',
RESPONSE_ACTION = 'response',
NOTIFY_BLOCK = 'edit_notify_block',
NOTIFY_ACTION = 'notify',
ACTIVE_BLOCK = 'edit_active_block',
ACTIVE_ACTION = 'active',
}
24 changes: 24 additions & 0 deletions definitions/ModalsEnum/ListContextualBar.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
export enum WorkflowContextualBarEnum {
VIEW_ID = 'workflow_list_contextual_bar',
SEARCH_BLOCK_ID = 'workflow_search_block',
SEARCH_ACTION_ID = 'workflow_search_action',
CLOSE_ACTION_ID = 'workflow_close_action',
CLOSE_BLOCK_ID = 'workflow_close_block',
OVERFLOW_ACTION_ID = 'workflow_overflow_action',
EDIT_ACTION_VALUE = 'edit',
DELETE_ACTION_VALUE = 'delete',
}

export enum ModalsAction {
dispatchActionConfigOnInput = 'on_character_entered',
dispatchActionConfigOnSelect = 'on_item_selected',
}

export interface IWorkflow {
id: string;
command: string;
toNotify: boolean;
isActive: boolean;
createdViaUI: boolean;
}

107 changes: 107 additions & 0 deletions handler/ExecuteBlockActionHandler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import {
IHttp,
IModify,
IPersistence,
IRead,
} from "@rocket.chat/apps-engine/definition/accessors";
import {
UIKitBlockInteractionContext,
IUIKitResponse,
} from "@rocket.chat/apps-engine/definition/uikit";
import { AiChatWorkflowsAutomationApp } from "../AiChatWorkflowsAutomationApp";
import { getTriggerResponse } from "../utils/PersistenceMethods";
import { WorkflowContextualBarExecutor } from "./ExecuteContextBar";
import { createDeleteWorkflowModal } from "../modals/DeleteModal";
import { createEditWorkflowModal } from "../modals/EditModal";
import { WorkflowContextualBarEnum } from "../definitions/ModalsEnum/ListContextualBar";

export class ExecuteBlockActionHandler {
private context: UIKitBlockInteractionContext;

constructor(
private readonly app: AiChatWorkflowsAutomationApp,
private readonly read: IRead,
private readonly http: IHttp,
private readonly modify: IModify,
private readonly persistence: IPersistence,
context: UIKitBlockInteractionContext,
) {
this.context = context;
}

public async handleActions(): Promise<IUIKitResponse> {
const { actionId } = this.context.getInteractionData();

switch (true) {
// Search and close actions
case actionId === WorkflowContextualBarEnum.SEARCH_ACTION_ID:
case actionId === WorkflowContextualBarEnum.CLOSE_ACTION_ID:
return await this.handleContextualBarActions();
case actionId?.startsWith(WorkflowContextualBarEnum.OVERFLOW_ACTION_ID):
return await this.handleOverflowAction();
default:
return this.context.getInteractionResponder().successResponse();
}
}

private async handleContextualBarActions(): Promise<IUIKitResponse> {
const executor = new WorkflowContextualBarExecutor(
this.app,
this.read,
this.modify,
this.persistence,
);
return await executor.handleActions(this.context);
}

private async handleOverflowAction(): Promise<IUIKitResponse> {
const { value } = this.context.getInteractionData();

if (!value) {
return this.context.getInteractionResponder().errorResponse();
}

// Parse value format: "edit_workflowId_123" or "delete_workflowId_123"
const valueParts = value.split('_');
const actionType = valueParts[0];
const workflowId = valueParts.slice(1).join('_');

switch (actionType) {
case WorkflowContextualBarEnum.EDIT_ACTION_VALUE:
return await this.openEditModal(workflowId);
case WorkflowContextualBarEnum.DELETE_ACTION_VALUE:
return await this.openDeleteModal(workflowId);
default:
return this.context.getInteractionResponder().errorResponse();
}
}

private async openEditModal(workflowId: string): Promise<IUIKitResponse> {
try {
const workflow = await getTriggerResponse(this.read, workflowId);

if (!workflow) {
console.error(`[openEditModal] Workflow not found: ${workflowId}`);
return this.context.getInteractionResponder().errorResponse();
}

const modal = await createEditWorkflowModal(workflow);
return this.context.getInteractionResponder().openModalViewResponse(modal);

} catch (error) {
console.error(`[openEditModal] Error:`, error);
return this.context.getInteractionResponder().errorResponse();
}
}

private async openDeleteModal(workflowId: string): Promise<IUIKitResponse> {
try {
const workflow = await getTriggerResponse(this.read, workflowId);
const modal = await createDeleteWorkflowModal(workflowId, workflow?.command);
return this.context.getInteractionResponder().openModalViewResponse(modal);
} catch (error) {
console.error(`[openDeleteModal] Error:`, error);
return this.context.getInteractionResponder().errorResponse();
}
}
}
80 changes: 80 additions & 0 deletions handler/ExecuteContextBar.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import {
IUIKitResponse,
UIKitBlockInteractionContext,
} from '@rocket.chat/apps-engine/definition/uikit';
import { IRead, IModify, IPersistence } from '@rocket.chat/apps-engine/definition/accessors';
import { listWorkflowContextualBar } from '../modals/ListContextualBar';
import { findTriggerResponsesByCreatorAndLLM } from '../utils/PersistenceMethods';
import { WorkflowContextualBarEnum } from '../definitions/ModalsEnum/ListContextualBar';

export class WorkflowContextualBarExecutor {
constructor(
private readonly app: any,
private readonly read: IRead,
private readonly modify: IModify,
private readonly persistence: IPersistence,
) { }

public async handleActions(
context: UIKitBlockInteractionContext,
): Promise<IUIKitResponse> {
const data = context.getInteractionData();
const { actionId, user, room, value } = data;
// Handle search input
if (actionId === WorkflowContextualBarEnum.SEARCH_ACTION_ID) {

// Fetch workflows again
const chatWorkflows = await findTriggerResponsesByCreatorAndLLM(
this.read,
user.id,
true
);

const uiWorkflows = await findTriggerResponsesByCreatorAndLLM(
this.read,
user.id,
false
);

const transformedChatWorkflows = chatWorkflows.map((cmd) => ({
id: cmd.data.id,
command: cmd.data.command,
toNotify: cmd.data.toNotify,
isActive: cmd.data.isActive,
createdViaUI: false,
}));

const transformedUIWorkflows = uiWorkflows.map((cmd) => ({
id: cmd.data.id,
command: cmd.data.command,
toNotify: cmd.data.toNotify,
isActive: cmd.data.isActive,
createdViaUI: true,
}));
if (value) {
console.log(`Search value: "${value}"`);
const updatedView = await listWorkflowContextualBar(
transformedChatWorkflows,
transformedUIWorkflows,
value as string,
);

return context.getInteractionResponder().updateContextualBarViewResponse(updatedView);
} else {
const updatedView = await listWorkflowContextualBar(
transformedChatWorkflows,
transformedUIWorkflows,
);

return context.getInteractionResponder().updateContextualBarViewResponse(updatedView);
}
}

// Handle close button
if (actionId === WorkflowContextualBarEnum.CLOSE_ACTION_ID) {
return context.getInteractionResponder().successResponse();
}

return context.getInteractionResponder().successResponse();
}
}
Loading