This commit is contained in:
morteza-mortezai
2025-11-30 11:18:54 +03:30
parent c40e7bbde9
commit 12c0c2eff7
7 changed files with 29 additions and 152 deletions
+1 -6
View File
@@ -54,13 +54,11 @@ import { ChatService } from "../modules/chat/chat.service";
import { ChatRepo, createChatRepo } from "../modules/chat/repository/chat.repository";
import { ChatMessageRepo, createChatMessageRepo } from "../modules/chat/repository/message.repository";
import { WsAuthService } from "../modules/chat/wsAuth.service";
import { ChatbotGateway } from "../modules/chatbot/chatbot.gateway";
import { ChatbotService } from "../modules/chatbot/providers/chatbot.service";
import { DataContextService } from "../modules/chatbot/providers/data-context.service";
import { LLMService } from "../modules/chatbot/providers/llm.service";
import { WebSocketAuthService } from "../modules/chatbot/providers/websocket-auth.service";
import { ChatbotGateway } from "../modules/chatbot/chatbot.gateway";
import { ChatSessionRepository, createChatSessionRepository } from "../modules/chatbot/repositories/chat-session.repository";
import { ChatMessageRepository, createChatMessageRepository } from "../modules/chatbot/repositories/chat-message.repository";
import { ContactUsRepo, CreateContactUsRepo } from "../modules/contact-us/contactUs.repository";
import { ContactUsService } from "../modules/contact-us/contactUs.service";
import { CouponRepo, CouponUsageRepo, createCouponRepo, createCouponUsageRepo } from "../modules/Coupon/coupon.repository";
@@ -291,9 +289,6 @@ const containerModules = new AsyncContainerModule(async (bind) => {
bind<AboutUsRepo>(IOCTYPES.AboutUsRepo).toDynamicValue(CreateAboutUsRepo).inSingletonScope();
bind<SiteSettingRepo>(IOCTYPES.SiteSettingRepo).toDynamicValue(CreateSiteSettingRepo).inSingletonScope();
bind<NewsletterRepo>(IOCTYPES.NewsletterRepo).toDynamicValue(CreateNewsletterRepo).inSingletonScope();
bind<ChatSessionRepository>(IOCTYPES.ChatbotChatSessionRepository).toDynamicValue(createChatSessionRepository).inSingletonScope();
bind<ChatMessageRepository>(IOCTYPES.ChatbotChatMessageRepository).toDynamicValue(createChatMessageRepository).inSingletonScope();
// #endregion
});
export { containerModules };
+18 -9
View File
@@ -279,12 +279,6 @@ export class ChatbotGateway {
return;
}
// The bot response will be generated asynchronously by the service
// We'll need to emit it when it's ready
// For now, we'll rely on the service's internal async generation
// In a production system, you might want to use Redis pub/sub or similar
// to notify when the bot response is ready
// Emit that bot is typing
socket.emit(WEBSOCKET_EVENTS.BOT_RESPONSE_START, {
status: "success",
@@ -292,9 +286,24 @@ export class ChatbotGateway {
timestamp: new Date().toISOString(),
} as WebSocketResponse);
// Note: The actual bot response will be generated by ChatbotService.generateBotResponse
// which runs asynchronously. In a real implementation, you might want to use
// Redis pub/sub or a message queue to notify when the response is ready
// Generate bot response and wait for it
const botMessage = await this.chatbotService.generateBotResponse(sessionId, userMessageId, ulid);
if (botMessage) {
// Emit bot response to the client
socket.emit(WEBSOCKET_EVENTS.BOT_RESPONSE, {
status: "success",
data: botMessage,
timestamp: new Date().toISOString(),
} as WebSocketResponse);
// Also emit to others in the session (if any)
socket.to(`session_${sessionId}`).emit(WEBSOCKET_EVENTS.BOT_RESPONSE, {
status: "success",
data: botMessage,
timestamp: new Date().toISOString(),
} as WebSocketResponse);
}
} catch (error) {
this.logger.error("Error generating bot response", error);
socket.emit(WEBSOCKET_EVENTS.BOT_RESPONSE, {
@@ -1,29 +0,0 @@
import { Schema, model } from "mongoose";
import { IChatMessage, MessageStatus, MessageType } from "./Abstraction/IChatMessage";
const chatMessageSchema = new Schema<IChatMessage>(
{
content: { type: String, required: true },
type: { type: String, enum: MessageType, required: true },
status: { type: String, enum: MessageStatus, default: MessageStatus.SENT },
session: { type: Schema.Types.ObjectId, ref: "ChatSession", required: true },
sender: { type: Schema.Types.ObjectId, ref: "User", default: null },
metadata: { type: Schema.Types.Mixed, default: null },
responseToId: { type: String, default: null },
tokensUsed: { type: Number, default: 0 },
},
{
timestamps: true,
toJSON: { virtuals: true, versionKey: false },
id: false,
},
);
chatMessageSchema.index({ session: 1, createdAt: -1 });
chatMessageSchema.index({ session: 1, createdAt: 1 });
const ChatMessageModel = model<IChatMessage>("ChatbotMessage", chatMessageSchema);
export { ChatMessageModel, MessageStatus, MessageType };
@@ -1,26 +0,0 @@
import { Schema, model } from "mongoose";
import { ChatSessionStatus, IChatSession } from "./Abstraction/IChatSession";
const chatSessionSchema = new Schema<IChatSession>(
{
title: { type: String, required: true, maxlength: 255 },
user: { type: Schema.Types.ObjectId, ref: "User", required: true },
status: { type: String, enum: ChatSessionStatus, default: ChatSessionStatus.ACTIVE },
context: { type: Schema.Types.Mixed, default: null },
lastMessageAt: { type: Date, default: null },
},
{
timestamps: true,
toJSON: { virtuals: true, versionKey: false },
id: false,
},
);
chatSessionSchema.index({ user: 1, lastMessageAt: -1 });
chatSessionSchema.index({ status: 1 });
const ChatSessionModel = model<IChatSession>("ChatSession", chatSessionSchema);
export { ChatSessionModel, ChatSessionStatus };
@@ -8,8 +8,8 @@ import { IOCTYPES } from "../../../IOC/ioc.types";
import { CacheService } from "../../../utils/cache.service";
import { SendMessageDto } from "../DTO/send-message.dto";
import { IChatContext } from "../interfaces/chatbot.interface";
import { MessageStatus, MessageType } from "../models/chat-message.model";
import { ChatSessionStatus } from "../models/chat-session.model";
import { MessageStatus, MessageType } from "../models/Abstraction/IChatMessage";
import { ChatSessionStatus } from "../models/Abstraction/IChatSession";
interface CachedSession {
id: string;
@@ -170,8 +170,7 @@ export class ChatbotService {
session.lastMessageAt = now;
await this.cacheService.setAsync(sessionKey, JSON.stringify(session), this.CACHE_TTL_SECONDS);
// Generate bot response asynchronously using OpenAI service
this.generateBotResponse(sendDto.sessionId, messageId, ulid);
// Note: Bot response will be generated by the gateway after emitting user message
return this.mapMessageToDto(userMessage);
}
@@ -240,7 +239,11 @@ export class ChatbotService {
};
}
private async generateBotResponse(sessionId: string, userMessageId: string, ulid: string): Promise<void> {
async generateBotResponse(
sessionId: string,
userMessageId: string,
ulid: string,
): Promise<ReturnType<typeof this.mapMessageToDto> | null> {
try {
// Get messages from cache
const messagesKey = this.getMessagesCacheKey(sessionId);
@@ -316,6 +319,7 @@ export class ChatbotService {
await this.cacheService.setAsync(sessionKey, JSON.stringify(session), this.CACHE_TTL_SECONDS);
this.logger.info(`Generated bot response for session ${sessionId}`);
return this.mapMessageToDto(botMessage);
} catch (error) {
this.logger.error(`Failed to generate bot response for session ${sessionId}`, error);
@@ -339,6 +343,7 @@ export class ChatbotService {
messages.push(errorMessage);
await this.cacheService.setAsync(messagesKey, JSON.stringify(messages), this.CACHE_TTL_SECONDS);
return this.mapMessageToDto(errorMessage);
}
}
@@ -1,37 +0,0 @@
import { BaseRepository } from "../../../common/base/repository";
import { ChatMessageModel } from "../models/chat-message.model";
import { IChatMessage, MessageStatus } from "../models/Abstraction/IChatMessage";
class ChatMessageRepository extends BaseRepository<IChatMessage> {
constructor() {
super(ChatMessageModel);
}
async findBySessionWithPagination(sessionId: string, offset = 0, limit = 50) {
return this.model
.find({ session: sessionId })
.populate("sender")
.sort({ createdAt: 1 })
.skip(offset)
.limit(limit)
.exec();
}
async getConversationHistory(sessionId: string, limit = 20) {
return this.model
.find({ session: sessionId })
.sort({ createdAt: -1 })
.limit(limit)
.exec();
}
async markAsRead(messageIds: string[]) {
return this.model.updateMany({ _id: { $in: messageIds } }, { status: MessageStatus.READ });
}
}
function createChatMessageRepository(): ChatMessageRepository {
return new ChatMessageRepository();
}
export { ChatMessageRepository, createChatMessageRepository };
@@ -1,40 +0,0 @@
import { BaseRepository } from "../../../common/base/repository";
import { ChatSessionModel, ChatSessionStatus } from "../models/chat-session.model";
import { IChatSession } from "../models/Abstraction/IChatSession";
class ChatSessionRepository extends BaseRepository<IChatSession> {
constructor() {
super(ChatSessionModel);
}
async findByUserWithMessages(userId: string, limit = 10) {
return this.model
.find({ user: userId })
.populate("messages")
.sort({ lastMessageAt: -1 })
.limit(limit)
.exec();
}
async findActiveByUser(userId: string) {
return this.model
.find({ user: userId, status: ChatSessionStatus.ACTIVE })
.populate("messages")
.sort({ lastMessageAt: -1 })
.exec();
}
async updateLastMessageTime(sessionId: string) {
return this.model.findByIdAndUpdate(sessionId, { lastMessageAt: new Date() }, { new: true });
}
async findByUserAndId(sessionId: string, userId: string) {
return this.model.findOne({ _id: sessionId, user: userId }).populate("messages").populate("messages.sender").exec();
}
}
function createChatSessionRepository(): ChatSessionRepository {
return new ChatSessionRepository();
}
export { ChatSessionRepository, createChatSessionRepository };