Skip to content

Commit

Permalink
feat(chat): 調整流程與監聽方式
Browse files Browse the repository at this point in the history
  • Loading branch information
sd0xdev committed Aug 27, 2023
1 parent 7fb8de9 commit 639b9a8
Show file tree
Hide file tree
Showing 9 changed files with 418 additions and 156 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,19 @@ import { DataSourceType } from '../interface/data-source-type.enum';
export class MessageController {
constructor(private readonly messageService: MessageService) {}

/**
* @deprecated in v1, use `chatbot:discord:llm:ai:chat` instead
*/
@EventPattern('chatbot:discord:chat')
async handleChatCreatedEvent(data: any) {
await this.messageService.createResponse(data);
}

@EventPattern('chatbot:discord:llm:ai:chat')
async handleLLMAIChatCreatedEvent(data: any) {
await this.messageService.createResponse(data);
}

@EventPattern('chatbot:discord:chat:url:docs')
async handleUrlDocsCreatedEvent(data: any) {
if (data.type === DataSourceType.URL) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,15 @@ import { SetupKeywordService } from './services/setup-keyword/setup-keyword.serv
import { MessageService } from './services/message/message.service';
import { DiscordClientService } from './services/discord-client/discord-client.service';
import { MessageController } from './controller/message.controller';
import { LLMAIChatService } from './services/llmai/llmai.chat.service';

@Module({
providers: [SetupKeywordService, MessageService, DiscordClientService],
providers: [
SetupKeywordService,
MessageService,
DiscordClientService,
LLMAIChatService,
],
controllers: [MessageController],
})
export class DiscordBotModule {
Expand Down
284 changes: 172 additions & 112 deletions apps/yggdrasil-discord-client/src/app/discord-bot/discord-bot.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,18 @@ import { validateYoutubeUrl } from './utils/validate-youtube-url';
import { DataSourceType } from './interface/data-source-type.enum';
import { ClientProxy } from '@nestjs/microservices';
import { DiscordClientService } from './services/discord-client/discord-client.service';
import { lastValueFrom } from 'rxjs';
import {
Subject,
catchError,
finalize,
from,
lastValueFrom,
of,
switchMap,
take,
takeUntil,
toArray,
} from 'rxjs';

@Injectable()
export class DiscordBotService implements OnApplicationBootstrap {
Expand Down Expand Up @@ -61,127 +72,176 @@ export class DiscordBotService implements OnApplicationBootstrap {
};

// message create listeners
private messageCreateListeners = async (
message: Message<boolean>
): Promise<void> => {
if (message.author.bot) return;

const isNotNeedToResponse = this.isNotNeedToResponse(message);
if (isNotNeedToResponse) {
this.asgardLogger.log('not prod and not develop channel, so no response');
return;
}

const isDisableBotChannel = this.isDisableBotChannel(message);
if (isDisableBotChannel) {
this.asgardLogger.log('this channel is disable bot channel');
return;
}

const isNeedToResponse = this.isNeedToResponse(message);
this.asgardLogger.verbose(`isUserCreateMessage: ${isNeedToResponse}`);
private messageCreateListeners = (message: Message<boolean>) => {
this.messageCreateListenerObservers(message).subscribe({
error: (e) => {
this.asgardLogger.error(e);
},
complete: () => {
this.asgardLogger.log(`${message?.author} create listener complete`);
},
});
};

// if message is a reply
this.asgardLogger.log(
`message is a reply: ${message?.reference ? true : false}`
private messageCreateListenerObservers(message: Message<boolean>) {
const stopSignal$ = new Subject();
return from([
this.checkIsBotMessage(message),
this.checkShouldReply(message),
this.checkIsDisableBotChannel(message),
]).pipe(
take(3),
toArray(),
switchMap((mods) => {
if (mods.some((mod) => mod === true)) {
stopSignal$.next('STOP');
}
return of(message);
}),
switchMap(async (message) => {
const isNeedToResponse = this.isNeedToResponse(message);
this.asgardLogger.verbose(`isUserCreateMessage: ${isNeedToResponse}`);

// if message is a reply
this.asgardLogger.log(
`message have reference: ${message?.reference ? true : false}`
);
if (await this.isReferenceAndNeedToResponse(message)) {
this.asgardLogger.verbose('is a reply message');
return this.botClient.emit('chatbot:discord:chat', message);
}

if (!isNeedToResponse) {
return this.asgardLogger.log(
'not user create message, so no response'
);
}

// if message is a url
const messageContent = message.content
.replace(`<@${this.options.discordOptions.discordBotClientId}>`, '')
.trim();

//validate youtube url
const isYtRequest = validateYoutubeUrl(messageContent);
if (isYtRequest) {
this.asgardLogger.log(`message is a youtube url: ${isYtRequest}`);
if (
!this.options.discordOptions?.alphaWhitelistedUserIds?.includes(
message.author.id
)
) {
return this.botClient.emit('chatbot:discord:chat:reply', {
text: '🎁 目前只有白名單的人才能使用此功能,請聯絡管理員開通',
message,
});
}

return this.botClient.emit(
'chatbot:discord:chat:url:youtube',
message
);
}

if (this.hasPDFAttachment(message)) {
return this.botClient.emit('chatbot:discord:chat:url:docs', {
message,
type: DataSourceType.PDF,
});
}

if (this.hasTXTAttachment(message)) {
return this.botClient.emit('chatbot:discord:chat:url:docs', {
message,
type: DataSourceType.TXT,
});
}

if (this.hasImageAttachment(message)) {
return this.botClient.emit('chatbot:discord:chat:url:docs', {
message,
type: DataSourceType.TXT,
});
}

if (messageContent.startsWith('url')) {
return this.botClient.emit('chatbot:discord:chat:url:docs', {
message,
type: DataSourceType.URL,
});
}

if (this.hasAudioWithMP3OrWAVAttachment(message)) {
return this.botClient.emit(
'chatbot:discord:chat:audio:transcription',
message
);
}

if (this.hasAudioAttachment(message)) {
const transcriptionMessage = await lastValueFrom(
this.botClient.send(
'chatbot:discord:chat:audio:transcription',
message
)
);

if (!this.isPermanentChannel(message)) {
return this.asgardLogger.log(
'not in permanent channel, so no response'
);
}

message.content = transcriptionMessage?.content;
}

if (this.isEmptyMessage(message)) {
return this.asgardLogger.log('message is empty, so no response');
}

return this.botClient.emit('chatbot:discord:llm:ai:chat', message);
}),
takeUntil(stopSignal$),
catchError((e) => {
this.asgardLogger.error(e?.message, e?.stack, e);
return of(e);
}),
finalize(() => {
this.asgardLogger.debug('message create listener finalize');
})
);
if (await this.isReferenceAndNeedToResponse(message)) {
this.asgardLogger.verbose('is a reply message');
this.botClient.emit('chatbot:discord:chat', message);
return;
}

if (!isNeedToResponse) {
this.asgardLogger.log('not user create message, so no response');
return;
}
}

// if message is a url
const messageContent = message.content
.replace(`<@${this.options.discordOptions.discordBotClientId}>`, '')
.trim();

//validate youtube url
const isYtRequest = validateYoutubeUrl(messageContent);
if (isYtRequest) {
this.asgardLogger.log(`message is a youtube url: ${isYtRequest}`);
if (
!this.options.discordOptions?.alphaWhitelistedUserIds?.includes(
message.author.id
)
) {
this.botClient.emit('chatbot:discord:chat:reply', {
text: '🎁 目前只有白名單的人才能使用此功能,請聯絡管理員開通',
message,
});
return;
private checkIsDisableBotChannel(message: Message<boolean>): boolean {
return (() => {
const isDisableBotChannel = this.isDisableBotChannel(message);
if (isDisableBotChannel) {
this.asgardLogger.log('this channel is disable bot channel');
}

this.botClient.emit('chatbot:discord:chat:url:youtube', message);
return;
}

if (this.hasPDFAttachment(message)) {
this.botClient.emit('chatbot:discord:chat:url:docs', {
message,
type: DataSourceType.PDF,
});
return;
}

if (this.hasTXTAttachment(message)) {
this.botClient.emit('chatbot:discord:chat:url:docs', {
message,
type: DataSourceType.TXT,
});

return;
}

if (this.hasImageAttachment(message)) {
this.botClient.emit('chatbot:discord:chat:url:docs', {
message,
type: DataSourceType.TXT,
});

return;
}

if (messageContent.startsWith('url')) {
this.botClient.emit('chatbot:discord:chat:url:docs', {
message,
type: DataSourceType.URL,
});

return;
}

if (this.hasAudioWithMP3OrWAVAttachment(message)) {
this.botClient.emit('chatbot:discord:chat:audio:transcription', message);
return;
}
return isDisableBotChannel;
})();
}

if (this.hasAudioAttachment(message)) {
const transcriptionMessage = await lastValueFrom(
this.botClient.send('chatbot:discord:chat:audio:transcription', message)
);
private checkShouldReply(message: Message<boolean>): boolean {
return (() => {
const isNotNeedToResponse = this.isNotNeedToResponse(message);

if (!this.isPermanentChannel(message)) {
this.asgardLogger.log('not in permanent channel, so no response');
return;
if (isNotNeedToResponse) {
this.asgardLogger.log(
'not prod and not develop channel, so no response'
);
}

message.content = transcriptionMessage?.content;
}

if (this.isEmptyMessage(message)) {
this.asgardLogger.log('message is empty, so no response');
return;
}
return isNotNeedToResponse;
})();
}

this.botClient.emit('chatbot:discord:chat', message);
return;
};
private checkIsBotMessage(message: Message<boolean>): boolean {
return (() => {
if (message.author.bot) return true;
})();
}

private isNeedToResponse(message: Message<boolean>) {
const atBot = `<@${this.options.discordOptions.discordBotClientId}>`;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { Test, TestingModule } from '@nestjs/testing';
import { LLMAIChatService } from './llmai.chat.service';
import { AsgardLoggerSupplement } from '@asgard-hub/nest-winston';
import { MessageService } from '../message/message.service';

describe('LLMAIChatService', () => {
let service: LLMAIChatService;

beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
{
provide: AsgardLoggerSupplement.LOGGER_HELPER_SERVICE,
useValue: {},
},
{
provide: 'LLMAI_PACKAGE',
useValue: {
getService: jest.fn().mockImplementation(() => {
return {};
}),
},
},
{
provide: 'DISCORD_BOT_MODULE_OPTIONS',
useValue: {},
},
{
provide: MessageService,
useValue: {},
},
LLMAIChatService,
],
}).compile();

service = module.get<LLMAIChatService>(LLMAIChatService);
});

it('should be defined', () => {
expect(service).toBeDefined();
});
});
Loading

0 comments on commit 639b9a8

Please sign in to comment.