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

issue#21 add chat apis #22

Merged
merged 31 commits into from
Oct 29, 2024
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
f52f53a
issue#21 add chat apis
ZHallen122 Oct 25, 2024
2dcea74
[autofix.ci] apply automated fixes
autofix-ci[bot] Oct 25, 2024
dbc3017
update module
ZHallen122 Oct 25, 2024
97d5541
registerEnumType
ZHallen122 Oct 25, 2024
4e9630a
schema
ZHallen122 Oct 25, 2024
c76078a
Merge branch 'add-chat-apis' of https://github.com/Sma1lboy/Generator…
ZHallen122 Oct 25, 2024
6693d27
fix chat model
ZHallen122 Oct 26, 2024
78a6119
update add input type
ZHallen122 Oct 26, 2024
e23e8d6
input type
ZHallen122 Oct 26, 2024
1f2ab88
update and fix db error
ZHallen122 Oct 26, 2024
aaea83e
update save message and get messages
ZHallen122 Oct 26, 2024
a6fec39
add protection
ZHallen122 Oct 26, 2024
da1463b
add user input message to history
ZHallen122 Oct 26, 2024
06b6dcb
add message guard
ZHallen122 Oct 27, 2024
7af5bdf
comment
ZHallen122 Oct 27, 2024
1f5066d
add /tags api
ZHallen122 Oct 28, 2024
9c8bc0e
fix UpdateChatTitleInput typo, and add getModelTag
ZHallen122 Oct 28, 2024
2735929
do soft delete
ZHallen122 Oct 28, 2024
a3fd075
fix OneToMany
ZHallen122 Oct 28, 2024
e9bc436
fix error meesage
ZHallen122 Oct 28, 2024
f4238b4
update schema
ZHallen122 Oct 28, 2024
f247a09
add getChatWithUser, update saveMessage
ZHallen122 Oct 29, 2024
c512b4c
fix bug: now use getChatWithUser
ZHallen122 Oct 29, 2024
f3210b2
store the big chunk in one
ZHallen122 Oct 29, 2024
74ff944
update to make sure it only get the available chat
ZHallen122 Oct 29, 2024
1573d72
update chatStream
ZHallen122 Oct 29, 2024
bc6ee4b
add a new ChatSubscriptionGuard
ZHallen122 Oct 29, 2024
3e429b4
refactor: Update message model and resolver to use MessageRole enum
Sma1lboy Oct 29, 2024
61535d3
refactor: Update message model and resolver to use MessageRole enum
Sma1lboy Oct 29, 2024
fc8929c
Merge branch 'main' into add-chat-apis
Sma1lboy Oct 29, 2024
5528efe
Merge branch 'main' into add-chat-apis
Sma1lboy Oct 29, 2024
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
47 changes: 40 additions & 7 deletions backend/src/chat/chat.model.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,49 @@
import { Field, InputType, ObjectType } from '@nestjs/graphql';
import {
Field,
InputType,
ObjectType,
ID,
registerEnumType,
} from '@nestjs/graphql';
import {
Entity,
PrimaryGeneratedColumn,
Column,
ManyToOne,
OneToMany,
JoinColumn,
} from 'typeorm';
import { forwardRef } from '@nestjs/common';
import { Message } from 'src/chat/message.model';
import { SystemBaseModel } from 'src/system-base-model/system-base.model';
import { User } from 'src/user/user.model';

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

@Field({ nullable: true })
@Column({ nullable: true })
title: string;

@Field(() => [Message], { nullable: true })
@OneToMany(() => Message, (message) => message.chat, { cascade: true })
messages: Message[];

@ManyToOne(() => User, (user) => user.chats)
@Field(() => User)
user: User;
}

@ObjectType('ChatCompletionDeltaType')
class ChatCompletionDelta {
@Field({ nullable: true })
content?: string;
}

@ObjectType('ChatCompletionChunkType')
export class ChatCompletionChunk {
@Field()
Expand Down Expand Up @@ -37,9 +76,3 @@ class ChatCompletionChoice {
@Field({ nullable: true })
finish_reason: string | null;
}

@InputType('ChatInputType')
export class ChatInput {
@Field()
message: string;
}
24 changes: 21 additions & 3 deletions backend/src/chat/chat.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,28 @@ import { GraphQLModule } from '@nestjs/graphql';
import { ApolloDriver, ApolloDriverConfig } from '@nestjs/apollo';
import { HttpModule } from '@nestjs/axios';
import { ChatResolver } from './chat.resolver';
import { ChatProxyService } from './chat.service';
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';
Sma1lboy marked this conversation as resolved.
Show resolved Hide resolved

@Module({
imports: [HttpModule],
providers: [ChatResolver, ChatProxyService],
imports: [
HttpModule,
TypeOrmModule.forFeature([Chat, User, Message]),
AuthModule,
],
providers: [
ChatResolver,
ChatProxyService,
ChatService,
ChatGuard,
UserService,
],
exports: [ChatService, ChatGuard],
})
export class ChatModule {}
101 changes: 96 additions & 5 deletions backend/src/chat/chat.resolver.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,42 @@
import { Resolver, Subscription, Args } from '@nestjs/graphql';
import { ChatCompletionChunk, ChatInput } from './chat.model';
import { ChatProxyService } from './chat.service';
import { Resolver, Subscription, Args, Query, Mutation } from '@nestjs/graphql';
import { ChatCompletionChunk } from './chat.model';
import { ChatProxyService, ChatService } from './chat.service';
import { UserService } from 'src/user/user.service';
import { Chat } from './chat.model';
import { Message, Role } from 'src/chat/message.model';
import {
NewChatInput,
UpdateChatTitleInput,
ChatInput,
} from 'src/chat/dto/chat.input';
import { UseGuards } from '@nestjs/common';
import { ChatGuard, MessageGuard } from '../guard/chat.guard';
import { GetUserIdFromToken } from '../decorator/get-auth-token';

@Resolver('Chat')
export class ChatResolver {
constructor(private chatProxyService: ChatProxyService) {}
constructor(
private chatProxyService: ChatProxyService,
private chatService: ChatService,
private userService: UserService,
) {}

@Subscription(() => ChatCompletionChunk, {
nullable: true,
resolve: (value) => value,
})
async *chatStream(@Args('input') input: ChatInput) {
const iterator = this.chatProxyService.streamChat(input.message);

this.chatService.saveMessage(input.chatId, null, input.message, Role.User);
try {
for await (const chunk of iterator) {
if (chunk) {
await this.chatService.saveMessage(
input.chatId,
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ont message for one response

chunk.id,
chunk.choices[0].delta.content,
Role.Model,
);
yield chunk;
Sma1lboy marked this conversation as resolved.
Show resolved Hide resolved
}
}
Expand All @@ -24,4 +45,74 @@ export class ChatResolver {
throw new Error('Chat stream failed');
}
}

@Query(() => [String], { nullable: true })
async getAvailableModelTags(
@GetUserIdFromToken() userId: string,
): Promise<string[]> {
try {
const response = await this.chatProxyService.fetchModelTags();
return response.models.data.map((model) => model.id); // Adjust based on model structure
} catch (error) {
throw new Error('Failed to fetch model tags');
}
}

Sma1lboy marked this conversation as resolved.
Show resolved Hide resolved
// this is not the final api.
@Query(() => [Chat], { nullable: true })
async getUserChats(@GetUserIdFromToken() userId: string): Promise<Chat[]> {
const user = await this.userService.getUserChats(userId);
return user ? user.chats : []; // Return chats if user exists, otherwise return an empty array
}

Comment on lines +72 to +77
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Add authorization guard and improve error handling in getUserChats.

The method lacks an authorization guard and could expose user data unintentionally.

Apply these improvements:

+  @UseGuards(AuthGuard)
   @Query(() => [Chat], { nullable: true })
   async getUserChats(@GetUserIdFromToken() userId: string): Promise<Chat[]> {
+    if (!userId) {
+      throw new Error('User ID is required');
+    }
     const user = await this.userService.getUserChats(userId);
-    return user ? user.chats : []; // Return chats if user exists, otherwise return an empty array
+    if (!user) {
+      throw new Error('User not found');
+    }
+    return user.chats;
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// this is not the final api.
@Query(() => [Chat], { nullable: true })
async getUserChats(@GetUserIdFromToken() userId: string): Promise<Chat[]> {
const user = await this.userService.getUserChats(userId);
return user ? user.chats : []; // Return chats if user exists, otherwise return an empty array
}
@UseGuards(AuthGuard)
@Query(() => [Chat], { nullable: true })
async getUserChats(@GetUserIdFromToken() userId: string): Promise<Chat[]> {
if (!userId) {
throw new Error('User ID is required');
}
const user = await this.userService.getUserChats(userId);
if (!user) {
throw new Error('User not found');
}
return user.chats;
}

@UseGuards(MessageGuard)
@Query(() => Message, { nullable: true })
async getMessageDetail(
@GetUserIdFromToken() userId: string,
@Args('messageId') messageId: string,
): Promise<Message> {
return this.chatService.getMessageById(messageId);
}

Sma1lboy marked this conversation as resolved.
Show resolved Hide resolved
// To do: message need a update resolver

@UseGuards(ChatGuard)
@Query(() => [Message])
async getChatHistory(@Args('chatId') chatId: string): Promise<Message[]> {
return this.chatService.getChatHistory(chatId);
}

@UseGuards(ChatGuard)
@Query(() => Chat, { nullable: true })
async getChatDetails(@Args('chatId') chatId: string): Promise<Chat> {
return this.chatService.getChatDetails(chatId);
}

@Mutation(() => Chat)
async createChat(
@GetUserIdFromToken() userId: string,
@Args('newChatInput') newChatInput: NewChatInput,
): Promise<Chat> {
return this.chatService.createChat(userId, newChatInput);
}

@UseGuards(ChatGuard)
@Mutation(() => Boolean)
async deleteChat(@Args('chatId') chatId: string): Promise<boolean> {
return this.chatService.deleteChat(chatId);
}

@UseGuards(ChatGuard)
@Mutation(() => Boolean)
async clearChatHistory(@Args('chatId') chatId: string): Promise<boolean> {
return this.chatService.clearChatHistory(chatId);
}

@UseGuards(ChatGuard)
@Mutation(() => Chat, { nullable: true })
async updateChatTitle(
@Args('updateChatTitleInput') updateChatTitleInput: UpdateChatTitleInput,
): Promise<Chat> {
return this.chatService.updateChatTitle(updateChatTitleInput);
}
}
Comment on lines +122 to +128
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Consider adding validation and transaction handling.

The chat mutations could benefit from additional safeguards:

  1. Title validation
  2. Transaction handling for delete operations

Consider these improvements:

// In UpdateChatTitleInput DTO
export class UpdateChatTitleInput {
  @IsUUID()
  chatId: string;

  @IsString()
  @MinLength(1)
  @MaxLength(100)
  @Matches(/^[a-zA-Z0-9\s-_]+$/, {
    message: 'Title can only contain letters, numbers, spaces, hyphens, and underscores',
  })
  title: string;
}

// In ChatResolver
@UseGuards(ChatGuard)
@Mutation(() => Boolean)
async deleteChat(@Args('chatId') chatId: string): Promise<boolean> {
  // Wrap in transaction
  return this.chatService.transaction(async (transactionManager) => {
    await this.chatService.deleteMessages(chatId, transactionManager);
    return this.chatService.deleteChat(chatId, transactionManager);
  });
}

Loading
Loading