From ab35567c0b2627514bfc391bd00cc5c71b74a29e Mon Sep 17 00:00:00 2001 From: Sushen Oli Date: Sat, 29 Mar 2025 13:34:59 +0545 Subject: [PATCH 1/5] Add action button --- app/ThreadSummarizerApp.ts | 27 +++++++++++++++++++++++++++ app/app.json | 6 ++++++ app/enum/ActionButton.ts | 4 ++++ app/i18n/en.json | 3 +++ 4 files changed, 40 insertions(+) create mode 100644 app/enum/ActionButton.ts create mode 100644 app/i18n/en.json diff --git a/app/ThreadSummarizerApp.ts b/app/ThreadSummarizerApp.ts index b810d5d..3f20e6f 100644 --- a/app/ThreadSummarizerApp.ts +++ b/app/ThreadSummarizerApp.ts @@ -7,6 +7,9 @@ import { App } from '@rocket.chat/apps-engine/definition/App'; import { IAppInfo } from '@rocket.chat/apps-engine/definition/metadata'; import { SummarizeCommand } from './commands/SummarizeCommand'; import { settings } from './settings/settings'; +import { ActionButton } from './enum/ActionButton'; +import { MessageActionContext, UIActionButtonContext } from '@rocket.chat/apps-engine/definition/ui'; + export class ThreadSummarizerApp extends App { constructor(info: IAppInfo, logger: ILogger, accessors: IAppAccessors) { @@ -14,6 +17,30 @@ export class ThreadSummarizerApp extends App { } public async extendConfiguration(configuration: IConfigurationExtend) { + + configuration.ui.registerButton({ + actionId: ActionButton.OPEN_SUMMARIZE_MODAL_ACTION, + labelI18n: ActionButton.OPEN_SUMMARIZE_MODAL_LABEL, + context: UIActionButtonContext.MESSAGE_ACTION, + + when: { + messageActionContext: [ + MessageActionContext.MESSAGE + ] + } + }) + + configuration.ui.registerButton({ + actionId: ActionButton.OPEN_SUMMARIZE_MODAL_ACTION, + labelI18n: ActionButton.OPEN_SUMMARIZE_MODAL_LABEL, + context: UIActionButtonContext.MESSAGE_BOX_ACTION, + + when: { + messageActionContext: [ + MessageActionContext.MESSAGE + ] + } + }) await Promise.all([ ...settings.map((setting) => configuration.settings.provideSetting(setting) diff --git a/app/app.json b/app/app.json index 165b856..c38fcea 100644 --- a/app/app.json +++ b/app/app.json @@ -46,6 +46,12 @@ }, { "name": "upload.read" + }, + { + "name": "ui.registerButtons" + }, + { + "name": "ui.interact" } ] } \ No newline at end of file diff --git a/app/enum/ActionButton.ts b/app/enum/ActionButton.ts new file mode 100644 index 0000000..d5d8982 --- /dev/null +++ b/app/enum/ActionButton.ts @@ -0,0 +1,4 @@ +export enum ActionButton { + OPEN_SUMMARIZE_MODAL_ACTION = 'open-summarize-model-action', + OPEN_SUMMARIZE_MODAL_LABEL = 'Summarize_the_thread' +} \ No newline at end of file diff --git a/app/i18n/en.json b/app/i18n/en.json new file mode 100644 index 0000000..37e832e --- /dev/null +++ b/app/i18n/en.json @@ -0,0 +1,3 @@ +{ + "Summarize_the_thread": "Summarize the thread" +} \ No newline at end of file From 013a77301e9e2d6d06f6667acdbc626096a93589 Mon Sep 17 00:00:00 2001 From: Sushen Oli Date: Sat, 5 Apr 2025 19:25:13 +0545 Subject: [PATCH 2/5] add UI for summarizing messages --- app/ThreadSummarizerApp.ts | 111 ++++- app/enum/ActionButton.ts | 4 +- app/enum/keys.ts | 2 + app/enum/modal/summarizeModal.ts | 11 + app/handlers/ExecuteActionButtonHandler.ts | 53 +++ app/handlers/ExecuteBlockActionHandler.ts | 54 +++ app/handlers/ExecuteViewCloseHandler.ts | 24 + app/handlers/ExecuteViewSubmitHandler.ts | 442 ++++++++++++++++++ app/helpers/createTextCompletion.ts | 1 - app/i18n/en.json | 2 +- app/lib/BlockBuilder.ts | 42 ++ app/lib/ElementBuilder.ts | 155 ++++++ app/lib/dataStore.ts | 53 +++ app/modal/summarizeModal.ts | 190 ++++++++ app/ui-kit/Block/IBlockBuilder.ts | 10 + app/ui-kit/Block/IInputBlock.ts | 4 + app/ui-kit/Element/IButtonElement.ts | 5 + app/ui-kit/Element/IElementBuilder.ts | 13 + .../Element/IMultiStaticSelectElement.ts | 17 + app/ui-kit/Element/IStaticSelectElement.ts | 17 + 20 files changed, 1184 insertions(+), 26 deletions(-) create mode 100644 app/enum/keys.ts create mode 100644 app/enum/modal/summarizeModal.ts create mode 100644 app/handlers/ExecuteActionButtonHandler.ts create mode 100644 app/handlers/ExecuteBlockActionHandler.ts create mode 100644 app/handlers/ExecuteViewCloseHandler.ts create mode 100644 app/handlers/ExecuteViewSubmitHandler.ts create mode 100644 app/lib/BlockBuilder.ts create mode 100644 app/lib/ElementBuilder.ts create mode 100644 app/lib/dataStore.ts create mode 100644 app/modal/summarizeModal.ts create mode 100644 app/ui-kit/Block/IBlockBuilder.ts create mode 100644 app/ui-kit/Block/IInputBlock.ts create mode 100644 app/ui-kit/Element/IButtonElement.ts create mode 100644 app/ui-kit/Element/IElementBuilder.ts create mode 100644 app/ui-kit/Element/IMultiStaticSelectElement.ts create mode 100644 app/ui-kit/Element/IStaticSelectElement.ts diff --git a/app/ThreadSummarizerApp.ts b/app/ThreadSummarizerApp.ts index 3f20e6f..0c5c617 100644 --- a/app/ThreadSummarizerApp.ts +++ b/app/ThreadSummarizerApp.ts @@ -1,14 +1,23 @@ import { IAppAccessors, IConfigurationExtend, + IHttp, ILogger, + IModify, + IPersistence, + IRead, } from '@rocket.chat/apps-engine/definition/accessors'; import { App } from '@rocket.chat/apps-engine/definition/App'; import { IAppInfo } from '@rocket.chat/apps-engine/definition/metadata'; import { SummarizeCommand } from './commands/SummarizeCommand'; import { settings } from './settings/settings'; import { ActionButton } from './enum/ActionButton'; -import { MessageActionContext, UIActionButtonContext } from '@rocket.chat/apps-engine/definition/ui'; +import { UIActionButtonContext, IUIActionButtonDescriptor } from '@rocket.chat/apps-engine/definition/ui'; +import { IUIKitResponse, UIKitActionButtonInteractionContext, UIKitBlockInteractionContext, UIKitViewCloseInteractionContext, UIKitViewSubmitInteractionContext } from '@rocket.chat/apps-engine/definition/uikit'; +import { ActionButtonHandler } from './handlers/ExecuteActionButtonHandler'; +import { ExecuteBlockActionHandler } from './handlers/ExecuteBlockActionHandler'; +import { ExecuteViewClosedHandler } from './handlers/ExecuteViewCloseHandler'; +import { ExecuteViewSubmitHandler } from './handlers/ExecuteViewSubmitHandler'; export class ThreadSummarizerApp extends App { @@ -18,29 +27,14 @@ export class ThreadSummarizerApp extends App { public async extendConfiguration(configuration: IConfigurationExtend) { - configuration.ui.registerButton({ - actionId: ActionButton.OPEN_SUMMARIZE_MODAL_ACTION, - labelI18n: ActionButton.OPEN_SUMMARIZE_MODAL_LABEL, - context: UIActionButtonContext.MESSAGE_ACTION, - - when: { - messageActionContext: [ - MessageActionContext.MESSAGE - ] - } - }) - - configuration.ui.registerButton({ - actionId: ActionButton.OPEN_SUMMARIZE_MODAL_ACTION, - labelI18n: ActionButton.OPEN_SUMMARIZE_MODAL_LABEL, + const summarizeMessageButton: IUIActionButtonDescriptor = { + actionId: ActionButton.SUMMARIZE_MESSAGES_ACTION, + labelI18n: ActionButton.SUMMARIZE_MESSAGES_LABEL, context: UIActionButtonContext.MESSAGE_BOX_ACTION, + } + + configuration.ui.registerButton(summarizeMessageButton); - when: { - messageActionContext: [ - MessageActionContext.MESSAGE - ] - } - }) await Promise.all([ ...settings.map((setting) => configuration.settings.provideSetting(setting) @@ -50,4 +44,77 @@ export class ThreadSummarizerApp extends App { ), ]); } + + public async executeActionButtonHandler( + context: UIKitActionButtonInteractionContext, + read: IRead, + http: IHttp, + persistence: IPersistence, + modify: IModify, + ): Promise { + const handler = new ActionButtonHandler().executor( + this, + context, + read, + http, + persistence, + modify, + ) + + return await handler + } + + public async executeBlockActionHandler( + context: UIKitBlockInteractionContext, + read: IRead, + http: IHttp, + persistence: IPersistence, + modify: IModify, + ): Promise { + const {threadId }= context.getInteractionData(); + this.getLogger().debug(threadId, "threaid") + const handler = new ExecuteBlockActionHandler( + this, + read, + http, + persistence, + modify, + context, + ); + + return await handler.handleActions(context); + } + + public async executeViewClosedHandler( + context: UIKitViewCloseInteractionContext, + persistence: IPersistence, + + ): Promise { + const handler = new ExecuteViewClosedHandler( + context, + persistence + ); + + return await handler.handleActions(); + } + + public async executeViewSubmitHandler( + context: UIKitViewSubmitInteractionContext, + read: IRead, + http: IHttp, + persistence: IPersistence, + modify: IModify, + ) { + const handler = new ExecuteViewSubmitHandler( + this, + read, + http, + persistence, + modify, + context, + ); + + return await handler.handleActions(context); + } + } diff --git a/app/enum/ActionButton.ts b/app/enum/ActionButton.ts index d5d8982..9d915ac 100644 --- a/app/enum/ActionButton.ts +++ b/app/enum/ActionButton.ts @@ -1,4 +1,4 @@ export enum ActionButton { - OPEN_SUMMARIZE_MODAL_ACTION = 'open-summarize-model-action', - OPEN_SUMMARIZE_MODAL_LABEL = 'Summarize_the_thread' + SUMMARIZE_MESSAGES_ACTION = 'summarize_messages', + SUMMARIZE_MESSAGES_LABEL = 'Summarize_messages', } \ No newline at end of file diff --git a/app/enum/keys.ts b/app/enum/keys.ts new file mode 100644 index 0000000..6a58f82 --- /dev/null +++ b/app/enum/keys.ts @@ -0,0 +1,2 @@ +export const ROOM_ID_KEY = 'room-id-key'; +export const THREAD_ID_KEY = 'thread-id-key'; \ No newline at end of file diff --git a/app/enum/modal/summarizeModal.ts b/app/enum/modal/summarizeModal.ts new file mode 100644 index 0000000..b253df2 --- /dev/null +++ b/app/enum/modal/summarizeModal.ts @@ -0,0 +1,11 @@ +export enum SummarizeModalEnum { + FILTER_SUMMARIES_DROPDOWN_BLOCK_ID = 'filter-summaries-block-id', + FILTER_SUMMARIES_DROPDOWN_ACTION_ID = 'filter-summaries-action-id', + CLOSE_ACTION_ID = 'close-action-id', + CLOSE_BLOCK_ID = 'close-block-id', + SUBMIT_ACTION_ID = 'submit-action-id', + SUBMIT_BLOCK_ID = 'submit-block-id', + USER_LISTS_BLOCK_ID = 'user-list-block-id', + USER_LISTS_ACTION_ID = 'user-list-action-id', + VIEW_ID = 'summarize-view-id' +} \ No newline at end of file diff --git a/app/handlers/ExecuteActionButtonHandler.ts b/app/handlers/ExecuteActionButtonHandler.ts new file mode 100644 index 0000000..ec21674 --- /dev/null +++ b/app/handlers/ExecuteActionButtonHandler.ts @@ -0,0 +1,53 @@ +import { + IHttp, + IModify, + IPersistence, + IRead, +} from "@rocket.chat/apps-engine/definition/accessors"; +import { + IUIKitResponse, + UIKitActionButtonInteractionContext, +} from "@rocket.chat/apps-engine/definition/uikit"; +import { ThreadSummarizerApp } from "../ThreadSummarizerApp"; +import { summarizeModal } from "../modal/summarizeModal"; +import { clearData, storeData } from "../lib/dataStore"; +import { ROOM_ID_KEY, THREAD_ID_KEY } from "../enum/keys"; + +export class ActionButtonHandler { + public async executor( + app: ThreadSummarizerApp, + context: UIKitActionButtonInteractionContext, + read: IRead, + http: IHttp, + persistence: IPersistence, + modify: IModify, + ): Promise { + const { triggerId, user, room, threadId, } = context.getInteractionData(); + + await clearData(persistence, user.id, THREAD_ID_KEY); + await clearData(persistence, user.id, ROOM_ID_KEY); + + const modal = await summarizeModal( + app, + read, + user, + ); + if(user.id){ + const roomId = room.id; + await storeData(persistence, user.id, ROOM_ID_KEY, {roomId}); + if (threadId) { + await storeData(persistence, user.id, THREAD_ID_KEY, { threadId }); + } + } + + + if (triggerId) { + await modify + .getUiController() + .openSurfaceView(modal, { triggerId: triggerId }, user); + } + + + return context.getInteractionResponder().successResponse(); + } +} diff --git a/app/handlers/ExecuteBlockActionHandler.ts b/app/handlers/ExecuteBlockActionHandler.ts new file mode 100644 index 0000000..5e7acef --- /dev/null +++ b/app/handlers/ExecuteBlockActionHandler.ts @@ -0,0 +1,54 @@ +import { + IUIKitResponse, + UIKitBlockInteractionContext, +} from '@rocket.chat/apps-engine/definition/uikit'; +import { + IHttp, + IModify, + IPersistence, + IRead, +} from '@rocket.chat/apps-engine/definition/accessors'; + + +import { SummarizeModalEnum } from '../enum/modal/summarizeModal'; +import { ThreadSummarizerApp } from '../ThreadSummarizerApp'; +import { summarizeModal } from '../modal/summarizeModal'; + + +export class ExecuteBlockActionHandler { + private context: UIKitBlockInteractionContext; + constructor( + protected readonly app: ThreadSummarizerApp, + protected readonly read: IRead, + protected readonly http: IHttp, + protected readonly persistence: IPersistence, + protected readonly modify: IModify, + context: UIKitBlockInteractionContext, + ) { + this.context = context; + } + + public async handleActions(context: UIKitBlockInteractionContext): Promise { + const { + actionId, + user, + value, + } = context.getInteractionData(); + + + + if(actionId === SummarizeModalEnum.FILTER_SUMMARIES_DROPDOWN_ACTION_ID) { + const updatedModal = await summarizeModal( + this.app, + this.read, + user, + value + ) + + return this.context + .getInteractionResponder() + .updateModalViewResponse(updatedModal) + } + return this.context.getInteractionResponder().successResponse(); + } +} diff --git a/app/handlers/ExecuteViewCloseHandler.ts b/app/handlers/ExecuteViewCloseHandler.ts new file mode 100644 index 0000000..96978e7 --- /dev/null +++ b/app/handlers/ExecuteViewCloseHandler.ts @@ -0,0 +1,24 @@ +import { + IUIKitResponse, + UIKitViewCloseInteractionContext, +} from '@rocket.chat/apps-engine/definition/uikit'; + +import { IPersistence } from '@rocket.chat/apps-engine/definition/accessors'; + +export class ExecuteViewClosedHandler { + private context: UIKitViewCloseInteractionContext; + private persistence: IPersistence; + constructor( + context: UIKitViewCloseInteractionContext, + persistence: IPersistence + ) { + this.context = context; + this.persistence = persistence + } + + public async handleActions(): Promise { + + return this.context.getInteractionResponder().successResponse(); + + } +} diff --git a/app/handlers/ExecuteViewSubmitHandler.ts b/app/handlers/ExecuteViewSubmitHandler.ts new file mode 100644 index 0000000..00f46a0 --- /dev/null +++ b/app/handlers/ExecuteViewSubmitHandler.ts @@ -0,0 +1,442 @@ +import { + IUIKitResponse, + UIKitViewSubmitInteractionContext, +} from '@rocket.chat/apps-engine/definition/uikit'; +import { + IHttp, + IModify, + IPersistence, + IRead, +} from '@rocket.chat/apps-engine/definition/accessors'; +import { ThreadSummarizerApp } from '../ThreadSummarizerApp'; +import { SummarizeModalEnum } from '../enum/modal/summarizeModal'; +import { notifyMessage } from '../helpers/notifyMessage'; +import { IRoom } from '@rocket.chat/apps-engine/definition/rooms'; +import { IUser } from '@rocket.chat/apps-engine/definition/users'; +import { IMessageRaw } from '@rocket.chat/apps-engine/definition/messages'; +import { createTextCompletion } from '../helpers/createTextCompletion'; +import { createAssignedTasksPrompt, createFileSummaryPrompt, createFollowUpQuestionsPrompt, createParticipantsSummaryPrompt, createSummaryPrompt, createSummaryPromptByTopics } from '../constants/prompts'; +import { ROOM_ID_KEY, THREAD_ID_KEY } from '../enum/keys'; +import { getData } from '../lib/dataStore'; + +export class ExecuteViewSubmitHandler { + private context: UIKitViewSubmitInteractionContext; + + constructor( + protected readonly app: ThreadSummarizerApp, + protected readonly read: IRead, + protected readonly http: IHttp, + protected readonly persistence: IPersistence, + protected readonly modify: IModify, + context: UIKitViewSubmitInteractionContext, + ) { + this.context = context; + } + + public async handleActions(context: UIKitViewSubmitInteractionContext): Promise { + const { view, user} = context.getInteractionData(); + const{ roomId } = await getData( + this.read.getPersistenceReader(), + user.id, + ROOM_ID_KEY, + ); + + const { threadId } = await getData( + this.read.getPersistenceReader(), + user.id, + THREAD_ID_KEY, + ) + + if(roomId){ + const room = (await this.read.getRoomReader().getById(roomId)) as IRoom; + + const ViewData = view.id.split('---'); + const viewId = ViewData[0].trim(); + + let unreadCount: number | undefined; + let startDate: Date | undefined; + let usernames: string[] | undefined; + const now = new Date(); + const filter = view.state?.[SummarizeModalEnum.FILTER_SUMMARIES_DROPDOWN_BLOCK_ID]?.[ + SummarizeModalEnum.FILTER_SUMMARIES_DROPDOWN_ACTION_ID + ] + const anyMatchedUsername = false; + + if(viewId === SummarizeModalEnum.VIEW_ID) { + switch (filter) { + case 'today': + startDate = new Date( + now.getFullYear(), + now.getMonth(), + now.getDate(), + 0, + 0, + 0, + 0 + ); + break; + case 'week': + startDate = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000); + break; + case 'unread': + unreadCount = await this.read + .getUserReader() + .getUserUnreadMessageCount(user.id); + break; + default: + usernames = view.state?.[SummarizeModalEnum.USER_LISTS_BLOCK_ID]?.[ + SummarizeModalEnum.USER_LISTS_ACTION_ID + ] + } + } + + const addOns = await this.app + .getAccessors() + .environmentReader.getSettings() + .getValueById('add-ons'); + const xAuthToken = await this.app + .getAccessors() + .environmentReader.getSettings() + .getValueById('x-auth-token'); + const xUserId = await this.app + .getAccessors() + .environmentReader.getSettings() + .getValueById('x-user-id'); + + + let messages: string; + if (!threadId) { + messages = await this.getRoomMessages( + room, + this.read, + user, + this.http, + addOns, + xAuthToken, + xUserId, + startDate, + unreadCount, + usernames, + anyMatchedUsername + ); + } else { + messages = await this.getThreadMessages( + room, + this.read, + user, + this.http, + threadId, + addOns, + xAuthToken, + xUserId, + startDate, + unreadCount, + usernames, + anyMatchedUsername + ); + } + + if (!messages || messages.trim().length === 0) { + await notifyMessage( + room, + this.read, + user, + 'There are no messages to summarize in this channel.', + threadId + ); + return this.context.getInteractionResponder().successResponse();; + } + + await notifyMessage(room, this.read, user, messages, threadId); + + let summary: string; + if (!threadId) { + const prompt = createSummaryPromptByTopics(messages); + summary = await createTextCompletion( + this.app, + room, + this.read, + user, + this.http, + prompt, + threadId + ); + } else { + const prompt = createSummaryPrompt(messages); + summary = await createTextCompletion( + this.app, + room, + this.read, + user, + this.http, + prompt, + threadId + ); + } + await notifyMessage(room, this.read, user, summary, threadId); + + if (addOns.includes('assigned-tasks')) { + const assignedTasksPrompt = createAssignedTasksPrompt(messages); + const assignedTasks = await createTextCompletion( + this.app, + room, + this.read, + user, + this.http, + assignedTasksPrompt, + threadId + ); + await notifyMessage(room, this.read, user, assignedTasks, threadId); + } + + if (addOns.includes('follow-up-questions')) { + const followUpQuestionsPrompt = createFollowUpQuestionsPrompt(messages); + const followUpQuestions = await createTextCompletion( + this.app, + room, + this.read, + user, + this.http, + followUpQuestionsPrompt, + threadId + ); + await notifyMessage(room, this.read, user, followUpQuestions, threadId); + } + + if (addOns.includes('participants-summary')) { + const participantsSummaryPrompt = + createParticipantsSummaryPrompt(messages); + const participantsSummary = await createTextCompletion( + this.app, + room, + this.read, + user, + this.http, + participantsSummaryPrompt, + threadId + ); + await notifyMessage(room, this.read, user, participantsSummary, threadId); + } + + } + + return this.context.getInteractionResponder().successResponse(); + + + } + + private async getFileSummary( + fileId: string, + read: IRead, + room: IRoom, + user: IUser, + http: IHttp, + xAuthToken: string, + xUserId: string, + threadId?: string + ): Promise { + const uploadReader = read.getUploadReader(); + const file = await uploadReader.getById(fileId); + if (file && file.type === 'text/plain') { + const response = await fetch(file.url, { + method: 'GET', + headers: { + 'X-Auth-Token': xAuthToken, + 'X-User-Id': xUserId, + }, + }); + const fileContent = await response.text(); + const fileSummaryPrompt = createFileSummaryPrompt(fileContent); + return createTextCompletion( + this.app, + room, + read, + user, + http, + fileSummaryPrompt, + threadId + ); + } + return 'File type is not supported'; + } + + private async getRoomMessages( + room: IRoom, + read: IRead, + user: IUser, + http: IHttp, + addOns: string[], + xAuthToken: string, + xUserId: string, + startDate?: Date, + unreadCount?: number, + usernames?: string[], + anyMatchedUsername?: boolean + ): Promise { + const messages: IMessageRaw[] = await read + .getRoomReader() + .getMessages(room.id, { + limit: Math.min(unreadCount || 100, 100), + sort: { createdAt: 'asc' }, + }); + + let filteredMessages = messages; + + if (usernames) { + filteredMessages = messages.filter((message) => { + const isMatched = usernames.includes(message.sender.username); + if (isMatched) { + anyMatchedUsername = true; + } + return isMatched; + }); + + if (!anyMatchedUsername) { + return `Please enter a valid command! + You can try: + \t 1. /chat-summary + \t 2. /chat-summary today + \t 3. /chat-summary week + \t 4. /chat-summary unread + \t 5. /chat-summary @ or /chat-summary @ @ + \t 6. /chat-summary help + \t 7. /chat-summary help `; + } + } + + if (startDate) { + const today = new Date(); + filteredMessages = messages.filter((message) => { + const createdAt = new Date(message.createdAt); + return createdAt >= startDate && createdAt <= today; + }); + } + + const messageTexts: string[] = []; + for (const message of filteredMessages) { + if (message.text) { + messageTexts.push( + `Message at ${message.createdAt}\n${message.sender.name}: ${message.text}\n` + ); + } + if (addOns.includes('file-summary') && message.file) { + if (!xAuthToken || !xUserId) { + await notifyMessage( + room, + read, + user, + 'Personal Access Token and User ID must be filled in settings to enable file summary add-on' + ); + continue; + } + const fileSummary = await this.getFileSummary( + message.file._id, + read, + room, + user, + http, + xAuthToken, + xUserId + ); + messageTexts.push('File Summary: ' + fileSummary); + } + } + return messageTexts.join('\n'); + } + + private async getThreadMessages( + room: IRoom, + read: IRead, + user: IUser, + http: IHttp, + threadId: string, + addOns: string[], + xAuthToken: string, + xUserId: string, + startDate?: Date, + unreadCount?: number, + usernames?: string[], + anyMatchedUsername?: boolean + ): Promise { + const threadReader = read.getThreadReader(); + const thread = await threadReader.getThreadById(threadId); + if (!thread) { + await notifyMessage(room, read, user, 'Thread not found'); + throw new Error('Thread not found'); + } + + let filteredMessages = thread; + if (usernames) { + filteredMessages = thread.filter((message) => { + const isMatched = usernames.includes(message.sender.username); + if (isMatched) { + anyMatchedUsername = true; + } + return isMatched; + }); + + if (!anyMatchedUsername) { + return `Please enter a valid command! + You can try: + \t 1. /chat-summary + \t 2. /chat-summary today + \t 3. /chat-summary week + \t 4. /chat-summary unread + \t 5. /chat-summary `; + } + } + if (startDate) { + const today = new Date(); + filteredMessages = thread.filter((message) => { + if (!message.createdAt) return false; + const createdAt = new Date(message.createdAt); + return createdAt >= startDate && createdAt <= today; + }); + } + + if (unreadCount && unreadCount > 0) { + if (unreadCount > 100) { + unreadCount = 100; + } + filteredMessages = filteredMessages.slice(-unreadCount); + } + + const messageTexts: string[] = []; + for (const message of filteredMessages) { + if (message.text) { + messageTexts.push(`${message.sender.name}: ${message.text}`); + } + if (addOns.includes('file-summary') && message.file) { + if (!xAuthToken || !xUserId) { + await notifyMessage( + room, + read, + user, + 'Personal Access Token and User ID must be filled in settings to enable file summary add-on' + ); + continue; + } + const fileSummary = await this.getFileSummary( + message.file._id, + read, + room, + user, + http, + xAuthToken, + xUserId, + threadId + ); + messageTexts.push('File Summary: ' + fileSummary); + } + } + + // threadReader repeats the first message once, so here we remove it + if (messageTexts.length > 0) { + messageTexts.shift(); + } + return messageTexts.join('\n'); + } + + + +} diff --git a/app/helpers/createTextCompletion.ts b/app/helpers/createTextCompletion.ts index 8d74916..6288bbc 100644 --- a/app/helpers/createTextCompletion.ts +++ b/app/helpers/createTextCompletion.ts @@ -70,7 +70,6 @@ export async function createTextCompletion( ); if (!response || !response.data) { - app.getLogger().log('No response data received from AI.'); return 'Something went wrong. Please try again later.'; } diff --git a/app/i18n/en.json b/app/i18n/en.json index 37e832e..f20d9a6 100644 --- a/app/i18n/en.json +++ b/app/i18n/en.json @@ -1,3 +1,3 @@ { - "Summarize_the_thread": "Summarize the thread" + "Summarize_messages": "Summarize messages" } \ No newline at end of file diff --git a/app/lib/BlockBuilder.ts b/app/lib/BlockBuilder.ts new file mode 100644 index 0000000..990aafd --- /dev/null +++ b/app/lib/BlockBuilder.ts @@ -0,0 +1,42 @@ +import { + LayoutBlockType, + TextObjectType, + InputBlock, + DividerBlock, +} from '@rocket.chat/ui-kit'; +import { IBlockBuilder } from '../ui-kit/Block/IBlockBuilder'; +import { InputBlockParam } from '../ui-kit/Block/IInputBlock'; + +export class BlockBuilder implements IBlockBuilder { + constructor(private readonly appId: string) {} + + public createInputBlock(param: InputBlockParam): InputBlock { + const { text, element, blockId, hint, optional } = param; + + const inputBlock: InputBlock = { + type: LayoutBlockType.INPUT, + label: { + type: TextObjectType.PLAIN_TEXT, + text, + }, + appId: this.appId, + element, + hint, + optional, + blockId, + }; + + return inputBlock; + } + + public createDividerBlock(blockId?: string | undefined): DividerBlock { + const dividerBlock: DividerBlock = { + type: LayoutBlockType.DIVIDER, + appId: this.appId, + blockId, + }; + + return dividerBlock; + } + +} diff --git a/app/lib/ElementBuilder.ts b/app/lib/ElementBuilder.ts new file mode 100644 index 0000000..9768aa8 --- /dev/null +++ b/app/lib/ElementBuilder.ts @@ -0,0 +1,155 @@ +import { ButtonParam } from '../ui-kit/Element/IButtonElement'; +import { + IElementBuilder, + ElementInteractionParam, +} from '../ui-kit/Element/IElementBuilder'; +import { + ButtonElement, + BlockElementType, + TextObjectType, + Option, + StaticSelectElement, + MultiStaticSelectElement +} from '@rocket.chat/ui-kit'; +import { + StaticSelectElementParam, + StaticSelectOptionsParam, +} from '../ui-kit/Element/IStaticSelectElement'; +import { MultiStaticSelectElementParam, MultiStaticSelectOptionsParam } from '../ui-kit/Element/IMultiStaticSelectElement'; + +export class ElementBuilder implements IElementBuilder { + constructor(private readonly appId: string) {} + public addButton( + param: ButtonParam, + interaction: ElementInteractionParam, + ): ButtonElement { + const { text, url, value, style } = param; + const { blockId, actionId } = interaction; + const button: ButtonElement = { + type: BlockElementType.BUTTON, + text: { + type: TextObjectType.PLAIN_TEXT, + text, + }, + appId: this.appId, + blockId, + actionId, + url, + value, + style, + }; + return button; + } + + public addDropDown( + param: StaticSelectElementParam, + interaction: ElementInteractionParam, + ): StaticSelectElement { + const { + placeholder, + options, + optionGroups, + initialOption, + initialValue, + dispatchActionConfig, + } = param; + const { blockId, actionId } = interaction; + const dropDown: StaticSelectElement = { + type: BlockElementType.STATIC_SELECT, + placeholder: { + type: TextObjectType.PLAIN_TEXT, + text: placeholder, + }, + options, + optionGroups, + initialOption, + initialValue, + appId: this.appId, + blockId, + actionId, + dispatchActionConfig, + }; + return dropDown; + } + + public addMultiStaticSelect( + param: MultiStaticSelectElementParam, + interaction: ElementInteractionParam, + ): MultiStaticSelectElement { + const { + placeholder, + options, + optionGroups, + dispatchActionConfig, + } = param; + const { blockId, actionId } = interaction; + const dropDown: MultiStaticSelectElement = { + type: BlockElementType.MULTI_STATIC_SELECT, + placeholder: { + type: TextObjectType.PLAIN_TEXT, + text: placeholder, + }, + options, + optionGroups, + appId: this.appId, + blockId, + actionId, + dispatchActionConfig, + }; + return dropDown; + } + + + public createDropDownOptions( + param: StaticSelectOptionsParam, + ): Array