Skip to content

Commit 99f2a58

Browse files
authored
Merge pull request #1770 from rocket-admin/backend_ai_table_schema_context
feat: implement schema change chat functionality with message persistence and thread management
2 parents e6f9a80 + 1209be1 commit 99f2a58

16 files changed

Lines changed: 571 additions & 5 deletions

backend/src/common/application/global-database-context.interface.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,10 @@ import { TableFiltersEntity } from '../../entities/table-filters/table-filters.e
3737
import { TableInfoEntity } from '../../entities/table-info/table-info.entity.js';
3838
import { ITableLogsRepository } from '../../entities/table-logs/repository/table-logs-repository.interface.js';
3939
import { ITableSchemaChangeRepository } from '../../entities/table-schema/repository/table-schema-change.repository.interface.js';
40+
import { ISchemaChangeChatRepository } from '../../entities/table-schema/schema-change-chat/schema-change-chat/repository/schema-change-chat-repository.interface.js';
41+
import { SchemaChangeChatEntity } from '../../entities/table-schema/schema-change-chat/schema-change-chat/schema-change-chat.entity.js';
42+
import { ISchemaChangeChatMessageRepository } from '../../entities/table-schema/schema-change-chat/schema-change-chat-message/repository/schema-change-chat-message-repository.interface.js';
43+
import { SchemaChangeChatMessageEntity } from '../../entities/table-schema/schema-change-chat/schema-change-chat-message/schema-change-chat-message.entity.js';
4044
import { TableSchemaChangeEntity } from '../../entities/table-schema/table-schema-change.entity.js';
4145
import { ITableSettingsRepository } from '../../entities/table-settings/common-table-settings/repository/table-settings.repository.interface.js';
4246
import { TableSettingsEntity } from '../../entities/table-settings/common-table-settings/table-settings.entity.js';
@@ -106,4 +110,6 @@ export interface IGlobalDatabaseContext extends IDatabaseContext {
106110
userAiChatRepository: Repository<UserAiChatEntity> & IUserAiChatRepository;
107111
aiChatMessageRepository: Repository<AiChatMessageEntity> & IAiChatMessageRepository;
108112
tableSchemaChangeRepository: Repository<TableSchemaChangeEntity> & ITableSchemaChangeRepository;
113+
schemaChangeChatRepository: Repository<SchemaChangeChatEntity> & ISchemaChangeChatRepository;
114+
schemaChangeChatMessageRepository: Repository<SchemaChangeChatMessageEntity> & ISchemaChangeChatMessageRepository;
109115
}

backend/src/common/application/global-database-context.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,12 @@ import { ITableLogsRepository } from '../../entities/table-logs/repository/table
6464
import { TableLogsEntity } from '../../entities/table-logs/table-logs.entity.js';
6565
import { customTableSchemaChangeRepositoryExtension } from '../../entities/table-schema/repository/custom-table-schema-change-repository-extension.js';
6666
import { ITableSchemaChangeRepository } from '../../entities/table-schema/repository/table-schema-change.repository.interface.js';
67+
import { schemaChangeChatRepositoryExtension } from '../../entities/table-schema/schema-change-chat/schema-change-chat/repository/schema-change-chat-repository.extension.js';
68+
import { ISchemaChangeChatRepository } from '../../entities/table-schema/schema-change-chat/schema-change-chat/repository/schema-change-chat-repository.interface.js';
69+
import { SchemaChangeChatEntity } from '../../entities/table-schema/schema-change-chat/schema-change-chat/schema-change-chat.entity.js';
70+
import { schemaChangeChatMessageRepositoryExtension } from '../../entities/table-schema/schema-change-chat/schema-change-chat-message/repository/schema-change-chat-message-repository.extension.js';
71+
import { ISchemaChangeChatMessageRepository } from '../../entities/table-schema/schema-change-chat/schema-change-chat-message/repository/schema-change-chat-message-repository.interface.js';
72+
import { SchemaChangeChatMessageEntity } from '../../entities/table-schema/schema-change-chat/schema-change-chat-message/schema-change-chat-message.entity.js';
6773
import { TableSchemaChangeEntity } from '../../entities/table-schema/table-schema-change.entity.js';
6874
import { ITableSettingsRepository } from '../../entities/table-settings/common-table-settings/repository/table-settings.repository.interface.js';
6975
import { tableSettingsCustomRepositoryExtension } from '../../entities/table-settings/common-table-settings/repository/table-settings-custom-repository-extension.js';
@@ -157,6 +163,9 @@ export class GlobalDatabaseContext implements IGlobalDatabaseContext {
157163
private _userAiChatRepository: Repository<UserAiChatEntity> & IUserAiChatRepository;
158164
private _aiChatMessageRepository: Repository<AiChatMessageEntity> & IAiChatMessageRepository;
159165
private _tableSchemaChangeRepository: Repository<TableSchemaChangeEntity> & ITableSchemaChangeRepository;
166+
private _schemaChangeChatRepository: Repository<SchemaChangeChatEntity> & ISchemaChangeChatRepository;
167+
private _schemaChangeChatMessageRepository: Repository<SchemaChangeChatMessageEntity> &
168+
ISchemaChangeChatMessageRepository;
160169

161170
public constructor(
162171
@Inject(BaseType.DATA_SOURCE)
@@ -264,6 +273,12 @@ export class GlobalDatabaseContext implements IGlobalDatabaseContext {
264273
this._tableSchemaChangeRepository = this.appDataSource
265274
.getRepository(TableSchemaChangeEntity)
266275
.extend(customTableSchemaChangeRepositoryExtension);
276+
this._schemaChangeChatRepository = this.appDataSource
277+
.getRepository(SchemaChangeChatEntity)
278+
.extend(schemaChangeChatRepositoryExtension);
279+
this._schemaChangeChatMessageRepository = this.appDataSource
280+
.getRepository(SchemaChangeChatMessageEntity)
281+
.extend(schemaChangeChatMessageRepositoryExtension);
267282
}
268283

269284
public get userRepository(): Repository<UserEntity> & IUserRepository {
@@ -428,6 +443,15 @@ export class GlobalDatabaseContext implements IGlobalDatabaseContext {
428443
return this._tableSchemaChangeRepository;
429444
}
430445

446+
public get schemaChangeChatRepository(): Repository<SchemaChangeChatEntity> & ISchemaChangeChatRepository {
447+
return this._schemaChangeChatRepository;
448+
}
449+
450+
public get schemaChangeChatMessageRepository(): Repository<SchemaChangeChatMessageEntity> &
451+
ISchemaChangeChatMessageRepository {
452+
return this._schemaChangeChatMessageRepository;
453+
}
454+
431455
public startTransaction(): Promise<void> {
432456
this._queryRunner = this.appDataSource.createQueryRunner();
433457
this._queryRunner.startTransaction();

backend/src/entities/table-schema/application/data-structures/generate-schema-change.ds.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,5 @@ export class GenerateSchemaChangeDs {
33
userPrompt: string;
44
userId: string;
55
masterPassword?: string;
6+
threadId?: string | null;
67
}

backend/src/entities/table-schema/application/data-transfer-objects/generate-schema-change.dto.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { ApiProperty } from '@nestjs/swagger';
2-
import { IsNotEmpty, IsString, MaxLength, MinLength } from 'class-validator';
2+
import { IsNotEmpty, IsOptional, IsString, IsUUID, MaxLength, MinLength } from 'class-validator';
33

44
export class GenerateSchemaChangeDto {
55
@ApiProperty({
@@ -15,4 +15,15 @@ export class GenerateSchemaChangeDto {
1515
@MinLength(1)
1616
@MaxLength(2000)
1717
userPrompt: string;
18+
19+
@ApiProperty({
20+
type: String,
21+
required: false,
22+
nullable: true,
23+
description:
24+
'Optional thread ID to continue an existing conversation. When supplied, prior turns are prepended to the AI prompt, giving the model context for iterative refinement (e.g. "now also add an index", "rename it to created_at"). Omit to start a fresh thread; the returned threadId can be passed back on the next call.',
25+
})
26+
@IsOptional()
27+
@IsUUID()
28+
threadId?: string;
1829
}

backend/src/entities/table-schema/application/data-transfer-objects/schema-change-batch-response.dto.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,12 @@ export class SchemaChangeBatchResponseDto {
1313
description: 'Generated changes ordered by orderInBatch (dependency order — parents first).',
1414
})
1515
changes: SchemaChangeResponseDto[];
16+
17+
@ApiProperty({
18+
required: false,
19+
nullable: true,
20+
description:
21+
'Conversation thread ID. Present when the request used or created a chat thread. Pass it back as the threadId query param on subsequent generate calls to continue the conversation with full prior context.',
22+
})
23+
threadId?: string | null;
1624
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import { MessageRole } from '../../../../ai/ai-conversation-history/ai-chat-messages/message-role.enum.js';
2+
import { SchemaChangeChatMessageEntity } from '../schema-change-chat-message.entity.js';
3+
import { ISchemaChangeChatMessageRepository } from './schema-change-chat-message-repository.interface.js';
4+
5+
export const schemaChangeChatMessageRepositoryExtension: ISchemaChangeChatMessageRepository = {
6+
async findMessagesForChat(chatId: string): Promise<SchemaChangeChatMessageEntity[]> {
7+
return await this.createQueryBuilder('schema_change_chat_message')
8+
.where('schema_change_chat_message.chat_id = :chatId', { chatId })
9+
.orderBy('schema_change_chat_message.created_at', 'ASC')
10+
.getMany();
11+
},
12+
13+
async deleteMessagesForChat(chatId: string): Promise<void> {
14+
await this.createQueryBuilder()
15+
.delete()
16+
.from('schema_change_chat_message')
17+
.where('chat_id = :chatId', { chatId })
18+
.execute();
19+
},
20+
21+
async saveMessage(
22+
chatId: string,
23+
message: string,
24+
role: MessageRole,
25+
batchId?: string | null,
26+
): Promise<SchemaChangeChatMessageEntity> {
27+
const newMessage = this.create({
28+
chat_id: chatId,
29+
message,
30+
role,
31+
batch_id: batchId ?? null,
32+
});
33+
return await this.save(newMessage);
34+
},
35+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { MessageRole } from '../../../../ai/ai-conversation-history/ai-chat-messages/message-role.enum.js';
2+
import { SchemaChangeChatMessageEntity } from '../schema-change-chat-message.entity.js';
3+
4+
export interface ISchemaChangeChatMessageRepository {
5+
findMessagesForChat(chatId: string): Promise<SchemaChangeChatMessageEntity[]>;
6+
deleteMessagesForChat(chatId: string): Promise<void>;
7+
saveMessage(
8+
chatId: string,
9+
message: string,
10+
role: MessageRole,
11+
batchId?: string | null,
12+
): Promise<SchemaChangeChatMessageEntity>;
13+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import {
2+
Column,
3+
CreateDateColumn,
4+
Entity,
5+
JoinColumn,
6+
ManyToOne,
7+
PrimaryGeneratedColumn,
8+
Relation,
9+
UpdateDateColumn,
10+
} from 'typeorm';
11+
import { MessageRole } from '../../../ai/ai-conversation-history/ai-chat-messages/message-role.enum.js';
12+
import { SchemaChangeChatEntity } from '../schema-change-chat/schema-change-chat.entity.js';
13+
14+
@Entity('schema_change_chat_message')
15+
export class SchemaChangeChatMessageEntity {
16+
@PrimaryGeneratedColumn('uuid')
17+
id: string;
18+
19+
@Column({ default: null, type: 'text' })
20+
message: string;
21+
22+
@Column({ nullable: true, default: null, type: 'enum', enum: MessageRole })
23+
role: MessageRole;
24+
25+
@Column({ type: 'uuid', nullable: true, default: null })
26+
batch_id: string | null;
27+
28+
@CreateDateColumn({ type: 'timestamp', default: () => 'CURRENT_TIMESTAMP' })
29+
created_at: Date;
30+
31+
@UpdateDateColumn({ type: 'timestamp', nullable: true, default: null })
32+
updated_at: Date;
33+
34+
@ManyToOne(
35+
() => SchemaChangeChatEntity,
36+
(chat) => chat.messages,
37+
{ onDelete: 'CASCADE' },
38+
)
39+
@JoinColumn({ name: 'chat_id' })
40+
chat: Relation<SchemaChangeChatEntity>;
41+
42+
@Column()
43+
chat_id: string;
44+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import { SchemaChangeChatEntity } from '../schema-change-chat.entity.js';
2+
import { ISchemaChangeChatRepository } from './schema-change-chat-repository.interface.js';
3+
4+
export const schemaChangeChatRepositoryExtension: ISchemaChangeChatRepository = {
5+
async findChatByIdAndUserId(chatId: string, userId: string): Promise<SchemaChangeChatEntity | null> {
6+
return await this.createQueryBuilder('schema_change_chat')
7+
.where('schema_change_chat.id = :chatId', { chatId })
8+
.andWhere('schema_change_chat.user_id = :userId', { userId })
9+
.getOne();
10+
},
11+
12+
async findChatWithMessagesByIdAndUserId(chatId: string, userId: string): Promise<SchemaChangeChatEntity | null> {
13+
return await this.createQueryBuilder('schema_change_chat')
14+
.leftJoinAndSelect('schema_change_chat.messages', 'messages')
15+
.where('schema_change_chat.id = :chatId', { chatId })
16+
.andWhere('schema_change_chat.user_id = :userId', { userId })
17+
.orderBy('messages.created_at', 'ASC')
18+
.getOne();
19+
},
20+
21+
async findChatsForConnection(connectionId: string, userId: string): Promise<SchemaChangeChatEntity[]> {
22+
return await this.createQueryBuilder('schema_change_chat')
23+
.where('schema_change_chat.connection_id = :connectionId', { connectionId })
24+
.andWhere('schema_change_chat.user_id = :userId', { userId })
25+
.orderBy('schema_change_chat.created_at', 'DESC')
26+
.getMany();
27+
},
28+
29+
async createChatForUser(userId: string, connectionId: string, name?: string): Promise<SchemaChangeChatEntity> {
30+
const newChat = this.create({
31+
user_id: userId,
32+
connection_id: connectionId,
33+
name: name || null,
34+
});
35+
return await this.save(newChat);
36+
},
37+
38+
async updateChatName(chatId: string, name: string): Promise<void> {
39+
await this.createQueryBuilder()
40+
.update(SchemaChangeChatEntity)
41+
.set({ name })
42+
.where('id = :chatId', { chatId })
43+
.execute();
44+
},
45+
46+
async updateLastBatchId(chatId: string, batchId: string): Promise<void> {
47+
await this.createQueryBuilder()
48+
.update(SchemaChangeChatEntity)
49+
.set({ last_batch_id: batchId })
50+
.where('id = :chatId', { chatId })
51+
.execute();
52+
},
53+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import { SchemaChangeChatEntity } from '../schema-change-chat.entity.js';
2+
3+
export interface ISchemaChangeChatRepository {
4+
findChatByIdAndUserId(chatId: string, userId: string): Promise<SchemaChangeChatEntity | null>;
5+
findChatWithMessagesByIdAndUserId(chatId: string, userId: string): Promise<SchemaChangeChatEntity | null>;
6+
findChatsForConnection(connectionId: string, userId: string): Promise<SchemaChangeChatEntity[]>;
7+
createChatForUser(userId: string, connectionId: string, name?: string): Promise<SchemaChangeChatEntity>;
8+
updateChatName(chatId: string, name: string): Promise<void>;
9+
updateLastBatchId(chatId: string, batchId: string): Promise<void>;
10+
}

0 commit comments

Comments
 (0)