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
2 changes: 2 additions & 0 deletions src/enum/modals/createModal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,6 @@ export enum CreateModalEnum {
CLOSE_BLOCK_ID = 'create-new-reply-block-id',
SUBMIT_ACTION_ID = 'submit-create-action-id',
SUBMIT_BLOCK_ID = 'submit-create-block-id',
NAME_ERROR_BLOCK_ID = 'name-error-block-id',
BODY_ERROR_BLOCK_ID = 'body-error-block-id',
}
213 changes: 127 additions & 86 deletions src/handlers/ExecuteViewSubmitHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import { AIusagePreference } from '../definition/helper/userPreference';
import { listReplyContextualBar } from '../modal/listContextualBar';
import { Receiverstorage } from '../storage/ReceiverStorage';
import { Replacements } from '../definition/helper/message';
import { CreateReplyModal } from '../modal/createModal';

export class ExecuteViewSubmitHandler {
private context: UIKitViewSubmitInteractionContext;
Expand Down Expand Up @@ -130,12 +131,33 @@ export class ExecuteViewSubmitHandler {

const name = nameStateValue ? nameStateValue.trim() : '';
const body = bodyStateValue ? bodyStateValue.trim() : '';
if (!name || !body) {
const errorMessage = `${t('Error_Fill_Required_Fields', language)}`;
await sendNotification(this.read, this.modify, user, room, {
message: errorMessage,
});
return this.context.getInteractionResponder().errorResponse();

// Check for empty fields
const errors = {
nameError: !name,
bodyError: !body
};

// If any errors exist, recreate the modal with error messages
if (errors.nameError || errors.bodyError) {
const modal = await CreateReplyModal(
this.app,
user,
this.read,
this.persistence,
this.modify,
room,
language,
errors
);

if (modal instanceof Error) {
this.app.getLogger().error(modal.message);
return this.context.getInteractionResponder().errorResponse();
}

// Update the view with errors instead of creating a new one
return this.context.getInteractionResponder().updateModalViewResponse(modal);
}

const replyStorage = new ReplyStorage(
Expand Down Expand Up @@ -171,91 +193,14 @@ export class ExecuteViewSubmitHandler {
}
}

private async handleSetUserPreference(
room: IRoom,
user: IUser,
view: IUIKitSurface,
): Promise<IUIKitResponse> {
const languageInput = view.state?.[
UserPreferenceModalEnum.LANGUAGE_INPUT_DROPDOWN_BLOCK_ID
]?.[
UserPreferenceModalEnum.LANGUAGE_INPUT_DROPDOWN_ACTION_ID
] as Language;

const AIpreferenceInput = view.state?.[
UserPreferenceModalEnum.AI_PREFERENCE_DROPDOWN_BLOCK_ID
]?.[
UserPreferenceModalEnum.AI_PREFERENCE_DROPDOWN_ACTION_ID
] as AIusagePreference;

const AIoptionInput =
view.state?.[UserPreferenceModalEnum.AI_OPTION_DROPDOWN_BLOCK_ID]?.[
UserPreferenceModalEnum.AI_OPTION_DROPDOWN_ACTION_ID
];

const OpenAIAPIKeyInput =
view.state?.[UserPreferenceModalEnum.OPEN_AI_API_KEY_BLOCK_ID]?.[
UserPreferenceModalEnum.OPEN_AI_API_KEY_ACTION_ID
];
const OpenAImodelInput =
view.state?.[UserPreferenceModalEnum.OPEN_AI_MODEL_BLOCK_ID]?.[
UserPreferenceModalEnum.OPEN_AI_MODEL_ACTION_ID
];
const GeminiAPIKeyInput =
view.state?.[UserPreferenceModalEnum.GEMINI_API_KEY_BLOCK_ID]?.[
UserPreferenceModalEnum.GEMINI_API_KEY_ACTION_ID
];
const SelfHostedURLInput =
view.state?.[UserPreferenceModalEnum.SELF_HOSTED_URL_BLOCK_ID]?.[
UserPreferenceModalEnum.SELF_HOSTED_URL_ACTION_ID
];

const PromptConfigurationInput =
view.state?.[
UserPreferenceModalEnum.PROMPT_CONFIG_INPUT_BLOCK_ID
]?.[UserPreferenceModalEnum.PROMPT_CONFIG_INPUT_ACTION_ID];

const userPreference = new UserPreferenceStorage(
this.persistence,
this.read.getPersistenceReader(),
user.id,
);

await userPreference.storeUserPreference({
userId: user.id,
language: languageInput,
AIusagePreference: AIpreferenceInput,
AIconfiguration: {
AIPrompt: PromptConfigurationInput,
AIProvider: AIoptionInput,
openAI: {
apiKey: OpenAIAPIKeyInput,
model: OpenAImodelInput,
},
gemini: {
apiKey: GeminiAPIKeyInput,
},
selfHosted: {
url: SelfHostedURLInput,
},
},
});

await sendNotification(this.read, this.modify, user, room, {
message: t('Config_Updated_Successfully', languageInput),
});

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

private async handleSend(
room: IRoom,
room: IRoom,
user: IUser,
view: IUIKitSurface,
replyId: string,
): Promise<IUIKitResponse> {
try {
const replyStorage = new ReplyStorage(
try {
const replyStorage = new ReplyStorage(
this.persistence,
this.read.getPersistenceReader(),
);
Expand Down Expand Up @@ -312,6 +257,83 @@ export class ExecuteViewSubmitHandler {
}
}

private async handleSetUserPreference(
room: IRoom,
user: IUser,
view: IUIKitSurface,
): Promise<IUIKitResponse> {
const languageInput = view.state?.[
UserPreferenceModalEnum.LANGUAGE_INPUT_DROPDOWN_BLOCK_ID
]?.[
UserPreferenceModalEnum.LANGUAGE_INPUT_DROPDOWN_ACTION_ID
] as Language;

const AIpreferenceInput = view.state?.[
UserPreferenceModalEnum.AI_PREFERENCE_DROPDOWN_BLOCK_ID
]?.[
UserPreferenceModalEnum.AI_PREFERENCE_DROPDOWN_ACTION_ID
] as AIusagePreference;

const AIoptionInput =
view.state?.[UserPreferenceModalEnum.AI_OPTION_DROPDOWN_BLOCK_ID]?.[
UserPreferenceModalEnum.AI_OPTION_DROPDOWN_ACTION_ID
];

const OpenAIAPIKeyInput =
view.state?.[UserPreferenceModalEnum.OPEN_AI_API_KEY_BLOCK_ID]?.[
UserPreferenceModalEnum.OPEN_AI_API_KEY_ACTION_ID
];
const OpenAImodelInput =
view.state?.[UserPreferenceModalEnum.OPEN_AI_MODEL_BLOCK_ID]?.[
UserPreferenceModalEnum.OPEN_AI_MODEL_ACTION_ID
];
const GeminiAPIKeyInput =
view.state?.[UserPreferenceModalEnum.GEMINI_API_KEY_BLOCK_ID]?.[
UserPreferenceModalEnum.GEMINI_API_KEY_ACTION_ID
];
const SelfHostedURLInput =
view.state?.[UserPreferenceModalEnum.SELF_HOSTED_URL_BLOCK_ID]?.[
UserPreferenceModalEnum.SELF_HOSTED_URL_ACTION_ID
];

const PromptConfigurationInput =
view.state?.[
UserPreferenceModalEnum.PROMPT_CONFIG_INPUT_BLOCK_ID
]?.[UserPreferenceModalEnum.PROMPT_CONFIG_INPUT_ACTION_ID];

const userPreference = new UserPreferenceStorage(
this.persistence,
this.read.getPersistenceReader(),
user.id,
);

await userPreference.storeUserPreference({
userId: user.id,
language: languageInput,
AIusagePreference: AIpreferenceInput,
AIconfiguration: {
AIPrompt: PromptConfigurationInput,
AIProvider: AIoptionInput,
openAI: {
apiKey: OpenAIAPIKeyInput,
model: OpenAImodelInput,
},
gemini: {
apiKey: GeminiAPIKeyInput,
},
selfHosted: {
url: SelfHostedURLInput,
},
},
});

await sendNotification(this.read, this.modify, user, room, {
message: t('Config_Updated_Successfully', languageInput),
});

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

private async handleDelete(
room: IRoom,
user: IUser,
Expand Down Expand Up @@ -396,6 +418,25 @@ export class ExecuteViewSubmitHandler {
? storedReply.body.trim()
: '';

// Check for empty fields and show red boundaries
const errors: Record<string, string> = {};

if (!name) {
errors[EditModalEnum.REPLY_NAME_BLOCK_ID] = 'This field is required';
}

if (!body) {
errors[EditModalEnum.REPLY_BODY_BLOCK_ID] = 'This field is required';
}

if (Object.keys(errors).length > 0) {
// Return error response with error fields to highlight them with red boundaries
return this.context.getInteractionResponder().viewErrorResponse({
viewId: view.id,
errors,
});
}

const result = await replyStorage.updateReplyById(
user,
replyId,
Expand Down
30 changes: 27 additions & 3 deletions src/modal/createModal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import {
IRead,
IUIKitSurfaceViewParam,
} from '@rocket.chat/apps-engine/definition/accessors';
import { TextObjectType, InputBlock } from '@rocket.chat/ui-kit';
import { TextObjectType, InputBlock, ContextBlock } from '@rocket.chat/ui-kit';

import { QuickRepliesApp } from '../../QuickRepliesApp';
import { IUser } from '@rocket.chat/apps-engine/definition/users';
Expand All @@ -25,10 +25,14 @@ export async function CreateReplyModal(
modify: IModify,
room: IRoom,
language: Language,
errors?: {
nameError?: boolean;
bodyError?: boolean;
}
): Promise<IUIKitSurfaceViewParam | Error> {
const { elementBuilder, blockBuilder } = app.getUtils();

const blocks: InputBlock[] = [];
const blocks: Array<InputBlock | ContextBlock> = [];

const labelReplyName = t('Reply_Name_Label', language);
const placeholderReplyName = t('Reply_Name_Placeholder', language);
Expand All @@ -46,6 +50,17 @@ export async function CreateReplyModal(
},
);

blocks.push(inputReplyName);

// Add name error context block if needed
if (errors?.nameError) {
const nameErrorContext = blockBuilder.createContextBlock({
blockId: CreateModalEnum.NAME_ERROR_BLOCK_ID,
contextElements: ['**❗ Name field is required**']
});
blocks.push(nameErrorContext);
}

const labelReplyBody = t('Reply_Body_Label', language);
const placeholderReplyBody = t('Reply_Body_Placeholder', language);

Expand All @@ -63,7 +78,16 @@ export async function CreateReplyModal(
},
);

blocks.push(inputReplyName, inputReplyBody);
blocks.push(inputReplyBody);

// Add body error context block if needed
if (errors?.bodyError) {
const bodyErrorContext = blockBuilder.createContextBlock({
blockId: CreateModalEnum.BODY_ERROR_BLOCK_ID,
contextElements: ['**❗ Body field is required**']
});
blocks.push(bodyErrorContext);
}

const submit = elementBuilder.addButton(
{ text: t('Create_Button', language), style: ButtonStyle.PRIMARY },
Expand Down