Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(backend): refactor chat service to support creating chats with i… #169

Merged
merged 1 commit into from
Mar 7, 2025
Merged
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
7 changes: 1 addition & 6 deletions backend/src/chat/chat.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,14 @@ import { ChatProxyService, ChatService } from './chat.service';
import { TypeOrmModule } from '@nestjs/typeorm';
import { User } from 'src/user/user.model';
import { Chat } from './chat.model';
import { Message } from 'src/chat/message.model';
import { ChatGuard } from '../guard/chat.guard';
import { AuthModule } from '../auth/auth.module';
import { UserService } from 'src/user/user.service';
import { PubSub } from 'graphql-subscriptions';
import { JwtCacheModule } from 'src/jwt-cache/jwt-cache.module';

@Module({
imports: [
TypeOrmModule.forFeature([Chat, User, Message]),
AuthModule,
JwtCacheModule,
],
imports: [TypeOrmModule.forFeature([Chat, User]), AuthModule, JwtCacheModule],
providers: [
ChatResolver,
ChatProxyService,
Expand Down
71 changes: 52 additions & 19 deletions backend/src/chat/chat.service.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Injectable, Logger, NotFoundException } from '@nestjs/common';
import { ChatCompletionChunk, Chat } from './chat.model';
import { Message, MessageRole } from 'src/chat/message.model';
import { MessageRole } from 'src/chat/message.model';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { User } from 'src/user/user.model';
Expand Down Expand Up @@ -46,7 +46,7 @@ export class ChatService {
private userRepository: Repository<User>,
) {}

async getChatHistory(chatId: string): Promise<Message[]> {
async getChatHistory(chatId: string) {
const chat = await this.chatRepository.findOne({
where: { id: chatId, isDeleted: false },
});
Expand Down Expand Up @@ -83,7 +83,6 @@ export class ChatService {
try {
const messages = chat.messages || [];
chat.messages = messages.filter((message: any) => !message.isDeleted);
console.log(chat);
} catch (error) {
console.error('Error parsing messages JSON:', error);
chat.messages = [];
Expand All @@ -93,31 +92,65 @@ export class ChatService {
}

async createChat(userId: string, newChatInput: NewChatInput): Promise<Chat> {
// Fetch the user entity using the userId
const user = await this.userRepository.findOne({ where: { id: userId } });
if (!user) {
throw new Error('User not found');
}

// Create a new chat and associate it with the user
const newChat = this.chatRepository.create({
title: newChatInput.title,
messages: [],
createdAt: new Date(),
updatedAt: new Date(),
user: user, // Associate the user with the chat
user: user,
});

return await this.chatRepository.save(newChat);
}

async createChatWithMessage(
userId: string,
newChatInput: { title: string; message: string },
): Promise<Chat> {
const user = await this.userRepository.findOne({ where: { id: userId } });
if (!user) {
throw new Error('User not found');
}

// Create a new chat with the initial message
const newChat = this.chatRepository.create({
title: newChatInput.title,
messages: [],
createdAt: new Date(),
updatedAt: new Date(),
user: user,
});

// Save the chat first to get an ID
const savedChat = await this.chatRepository.save(newChat);

// Create the message with the chat's ID as a prefix
const message = {
id: `${savedChat.id}/0`,
content: newChatInput.message,
role: MessageRole.User,
createdAt: new Date(),
updatedAt: new Date(),
isActive: true,
isDeleted: false,
};

// Update the chat with the message
savedChat.messages = [message];
return await this.chatRepository.save(savedChat);
}

async deleteChat(chatId: string): Promise<boolean> {
const chat = await this.chatRepository.findOne({
where: { id: chatId, isDeleted: false },
});

if (chat) {
// Soft delete the chat
chat.isDeleted = true;
chat.isActive = false;
await this.chatRepository.save(chat);
Expand All @@ -131,6 +164,7 @@ export class ChatService {
where: { id: chatId, isDeleted: false },
});
if (chat) {
chat.messages = [];
chat.updatedAt = new Date();
await this.chatRepository.save(chat);
return true;
Expand All @@ -153,14 +187,17 @@ export class ChatService {
return null;
}

async saveMessage(
chatId: string,
messageContent: string,
role: MessageRole,
): Promise<Message> {
// Find the chat instance
async saveMessage(chatId: string, messageContent: string, role: MessageRole) {
const chat = await this.chatRepository.findOne({ where: { id: chatId } });
if (!chat) {
return null;
}

if (!chat.messages) {
chat.messages = [];
}

// Create new message with the chat's ID as a prefix
const message = {
id: `${chat.id}/${chat.messages.length}`,
content: messageContent,
Expand All @@ -170,20 +207,16 @@ export class ChatService {
isActive: true,
isDeleted: false,
};
//if the chat id not exist, dont save this messages
if (!chat) {
return null;
}

chat.messages.push(message);
await this.chatRepository.save(chat);
// Save the message to the database
return message;
}

async getChatWithUser(chatId: string): Promise<Chat | null> {
return this.chatRepository.findOne({
where: { id: chatId, isDeleted: false },
relations: ['user'], // Only load the user relation
relations: ['user'],
});
}
}
21 changes: 13 additions & 8 deletions backend/src/chat/message.model.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
import { Field, ObjectType, ID, registerEnumType } from '@nestjs/graphql';
import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm';
import { SystemBaseModel } from 'src/system-base-model/system-base.model';

/**
* Represents the different roles in a chat conversation
Expand Down Expand Up @@ -30,22 +28,29 @@ registerEnumType(MessageRole, {
name: 'Role',
});

@Entity()
@ObjectType()
export class Message extends SystemBaseModel {
@PrimaryGeneratedColumn('uuid')
export class Message {
@Field(() => ID)
id: string;

@Field()
@Column()
content: string;

@Field(() => MessageRole)
@Column({ type: 'text' })
role: MessageRole;

@Field(() => Date)
createdAt: Date;

@Field(() => Date)
updatedAt: Date;

@Field()
isActive: boolean;

@Field()
isDeleted: boolean;

@Field({ nullable: true })
@Column({ nullable: true })
modelId?: string;
}
4 changes: 3 additions & 1 deletion backend/src/project/project.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -190,8 +190,9 @@ export class ProjectService {
}

// Create chat with proper title
const defaultChat = await this.chatService.createChat(userId, {
const defaultChat = await this.chatService.createChatWithMessage(userId, {
title: projectName || 'New Project Chat',
message: input.description,
});

// Perform the rest of project creation asynchronously
Expand Down Expand Up @@ -550,6 +551,7 @@ export class ProjectService {
}

// Create default chat for the new project
// FIXME(Sma1lboy): this is not correct, we should copy chat as well
const defaultChat = await this.chatService.createChat(userId, {
title: `Fork of ${sourceProject.projectName}`,
});
Expand Down
Loading