From 2709a7cee9f06ffa9ad677a8b4c1cfa483808ac5 Mon Sep 17 00:00:00 2001 From: Endurance Idehen Date: Sun, 31 Dec 2023 16:49:38 -0600 Subject: [PATCH] smart --- Writerside/topics/Overview.md | 4 +- .../chat/discord/myDiscord.module.ts | 2 - .../discord/services/discordThread.service.ts | 106 ++++++++++++++---- src/shared/chatbot-core/chatbot.service.ts | 74 ++++++++++-- 4 files changed, 151 insertions(+), 35 deletions(-) diff --git a/Writerside/topics/Overview.md b/Writerside/topics/Overview.md index 6ccc49a..9a2671d 100644 --- a/Writerside/topics/Overview.md +++ b/Writerside/topics/Overview.md @@ -3,11 +3,11 @@ Knowledge base for SkellyBot ## [Project Notes](ProjectNotes.md) -## [Concepts](Concepts) +## [Concepts](Concepts.md) ```ad-note View in: -- Jetbrains Writerstorm (free route standalone Writerstorm app or Pycharm (etc) plugin) +- Jetbrains Writerside (free route standalone Writerside app or Pycharm (etc) plugin) - Obsidian MD - VS Code Foam ``` \ No newline at end of file diff --git a/src/interfaces/chat/discord/myDiscord.module.ts b/src/interfaces/chat/discord/myDiscord.module.ts index 9e7c561..7677646 100644 --- a/src/interfaces/chat/discord/myDiscord.module.ts +++ b/src/interfaces/chat/discord/myDiscord.module.ts @@ -1,12 +1,10 @@ import { Logger, Module } from '@nestjs/common'; import { DiscordPingService } from './services/discordPing.service'; import { NecordModule } from 'necord'; -import { DiscordChatService } from './services/discordChat.service'; import { GcpModule } from '../../../shared/gcp/gcp.module'; import { NecordConfigService } from './services/necordConfig.service'; import { DiscordReadyLoggingService } from './services/discordReadyLogging.service'; import { DiscordThreadService } from './services/discordThread.service'; -import { ChatbotService } from '../../../shared/chatbot-core/chatbot.service'; import { ChatbotCoreModule } from '../../../shared/chatbot-core/chatbotCore.module'; @Module({ diff --git a/src/interfaces/chat/discord/services/discordThread.service.ts b/src/interfaces/chat/discord/services/discordThread.service.ts index b001b94..f3acea7 100644 --- a/src/interfaces/chat/discord/services/discordThread.service.ts +++ b/src/interfaces/chat/discord/services/discordThread.service.ts @@ -1,15 +1,22 @@ -import { Injectable, Logger } from '@nestjs/common'; +import { Injectable, Logger, OnModuleDestroy } from '@nestjs/common'; import { Context, Options, SlashCommand, SlashCommandContext } from 'necord'; import { DEV_GUILDS } from '../../../../shared/config/constants'; import { TextDto } from '../dto/textDto'; import { ChatbotService } from '../../../../shared/chatbot-core/chatbot.service'; -import { Chatbot } from '../../../../shared/chatbot-core/chatbot.dto'; +import { + ChatInputCommandInteraction, + Client, + Message, + TextChannel, + ThreadChannel, +} from 'discord.js'; @Injectable() -export class DiscordThreadService { +export class DiscordThreadService implements OnModuleDestroy { constructor( private readonly _logger: Logger, private readonly _chatbotService: ChatbotService, + private readonly _client: Client, ) {} @SlashCommand({ @@ -27,9 +34,8 @@ export class DiscordThreadService { this._logger.log( `Creating thread with starting text:'${text}' in channel: name= ${interaction.channel.name}, id=${interaction.channel.id} `, ); - - // @ts-ignore - const thread = await interaction.channel.threads.create({ + const channel = interaction.channel as TextChannel; + const thread = await channel.threads.create({ name: text || 'new thread', autoArchiveDuration: 60, reason: 'wow this is a thread', @@ -37,29 +43,83 @@ export class DiscordThreadService { await this._chatbotService.createChatbot(thread.id); - interaction.client.on('messageCreate', async (message) => { - this._logger.log(`Recieved message ${message.content}`); + this._beginWatchingIncomingMessages(interaction, channel, thread); + await this._sendInitialReply(interaction, channel, thread, text); + } + + /** + * When the bot application dies, we remove all listeners. + */ + onModuleDestroy() { + this._client.removeAllListeners(); + } + + private _beginWatchingIncomingMessages( + interaction: ChatInputCommandInteraction, + channel: TextChannel, + thread: ThreadChannel, + ) { + const t = { ...thread }; + const handleMessageCreation = async (message: Message) => { if (message.author.bot) { return; } + if (message.channelId !== thread.id) { + return; + } - const aiResponse = await this._chatbotService.generateAiResponse( - thread.id, - message.content, - // @ts-ignore - { topic: interaction.channel.topic }, - ); - await message.reply(aiResponse); - }); + this._logger.log(`Received message ${message.content}`); + await this._handleStream(channel, thread, message.content, message); + }; + + interaction.client.on('messageCreate', handleMessageCreation); + } + + private async _sendInitialReply( + interaction: ChatInputCommandInteraction, + channel: TextChannel, + thread: ThreadChannel, + inputText: string, + ) { + const initialMessage = await thread.send(inputText); + await interaction.editReply('Thread Created!'); + await this._handleStream(channel, thread, inputText, initialMessage); + } - const initialMessage = await thread.send(startingText.text); - const aiResponse = await this._chatbotService.generateAiResponse( + private async _handleStream( + channel: TextChannel, + thread: ThreadChannel, + inputText: string, + message: Message, + ) { + const tokenStream = this._chatbotService.streamResponse( thread.id, - startingText.text, - // @ts-ignore - { topic: interaction.channel.topic }, + inputText, + { + topic: channel.topic, + }, ); - await initialMessage.reply(aiResponse); - await interaction.editReply('Thread Created!'); + thread.sendTyping(); + + let initialReply: Message = undefined; + let final = ''; + for await (const current of tokenStream) { + const { theChunk, didResetOccur } = current; + + if (!initialReply) { + initialReply = await message.reply(theChunk); + continue; + } + + if (didResetOccur) { + initialReply = await message.reply(theChunk); + continue; + } + + await initialReply.edit(theChunk); + final = current.data; + } + + this._logger.debug(`Final thingy`, final); } } diff --git a/src/shared/chatbot-core/chatbot.service.ts b/src/shared/chatbot-core/chatbot.service.ts index f5932d2..508fc85 100644 --- a/src/shared/chatbot-core/chatbot.service.ts +++ b/src/shared/chatbot-core/chatbot.service.ts @@ -2,9 +2,16 @@ import { Injectable, Logger } from '@nestjs/common'; import { ChainBuilderService } from '../ai/langchain/chain-builder/chain-builder.service'; import { Chatbot } from './chatbot.dto'; +class StreamResponseOptions { + /** + * Character limit to split the outgoing data + */ + splitAt: number = 1800; +} + @Injectable() export class ChatbotService { - _chatbots: Map = new Map(); + _chatbots: Map = new Map(); constructor( private readonly _logger: Logger, private readonly _chainBuilderService: ChainBuilderService, @@ -14,7 +21,7 @@ export class ChatbotService { const chain = await this._chainBuilderService.createChain(modelName); const chatbot = { chain } as Chatbot; - this._chatbots.set(chatbotId, chatbot); + this._chatbots.set(String(chatbotId), chatbot); return chatbot; } @@ -26,18 +33,69 @@ export class ChatbotService { this._logger.log( `Responding to message ${humanMessage} with chatbotId: ${chatbotId}`, ); - let chatbot: Chatbot; + const chatbot = this.getChatbotById(chatbotId); + return await chatbot.chain.invoke({ + text: humanMessage, + ...additionalArgs, + }); + } + + getChatbotById(chatbotId: string | number) { try { - chatbot = this._chatbots.get(chatbotId); + return this._chatbots.get(String(chatbotId)); } catch (error) { - this._logger.error( - `Could not find 'chatbot' for 'chatbotId': ${chatbotId}`, - ); + this._logger.error(`Could not find chatbot for chatbotId: ${chatbotId}`); throw error; } - return await chatbot.chain.invoke({ + } + async *streamResponse( + chatbotId: string | number, + humanMessage: string, + additionalArgs: any, + options: StreamResponseOptions = new StreamResponseOptions(), + ) { + const normalizedOptions = { + ...new StreamResponseOptions(), + ...options, + }; + const { splitAt } = normalizedOptions; + const chatbot = this.getChatbotById(chatbotId); + const chatStream = await chatbot.chain.stream({ text: humanMessage, ...additionalArgs, }); + + let streamedResult = ''; + let subStreamResult = ''; + let didResetOccur = false; + let tokens = 0; + for await (const chunk of chatStream) { + // the full message + streamedResult += chunk; + tokens++; + if (subStreamResult.length < splitAt) { + subStreamResult += chunk; + } else { + // + subStreamResult = subStreamResult.slice(subStreamResult.length * 0.95); + subStreamResult += chunk; + didResetOccur = true; + } + + if (tokens === 30) { + yield { + data: streamedResult, + theChunk: subStreamResult, + didResetOccur, + }; + tokens = 0; + didResetOccur = false; + } + } + + yield { + data: streamedResult, + theChunk: subStreamResult, + }; } }