Files
twenty_crm/packages/twenty-server/src/engine/metadata-modules/agent/agent-chat.service.ts
Abdul Rahman 8edf59a521 Feat: Agent chat multi thread support (#13216)
Co-authored-by: Raphaël Bosi <71827178+bosiraphael@users.noreply.github.com>
Co-authored-by: neo773 <62795688+neo773@users.noreply.github.com>
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
Co-authored-by: Félix Malfait <felix.malfait@gmail.com>
Co-authored-by: Félix Malfait <felix@twenty.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: github-actions <github-actions@twenty.com>
Co-authored-by: MD Readul Islam <99027968+readul-islam@users.noreply.github.com>
Co-authored-by: readul-islam <developer.readul@gamil.com>
Co-authored-by: Thomas des Francs <tdesfrancs@gmail.com>
Co-authored-by: Guillim <guillim@users.noreply.github.com>
Co-authored-by: Lucas Bordeau <bordeau.lucas@gmail.com>
Co-authored-by: Jean-Baptiste Ronssin <65334819+jbronssin@users.noreply.github.com>
Co-authored-by: kahkashan shaik <93042682+kahkashanshaik@users.noreply.github.com>
Co-authored-by: martmull <martmull@hotmail.fr>
Co-authored-by: Paul Rastoin <45004772+prastoin@users.noreply.github.com>
Co-authored-by: bosiraphael <raphael.bosi@gmail.com>
Co-authored-by: Naifer <161821705+omarNaifer12@users.noreply.github.com>
Co-authored-by: Marie Stoppa <marie.stoppa@essec.edu>
2025-07-16 09:26:40 +02:00

141 lines
3.4 KiB
TypeScript

import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { FileEntity } from 'src/engine/core-modules/file/entities/file.entity';
import {
AgentChatMessageEntity,
AgentChatMessageRole,
} from 'src/engine/metadata-modules/agent/agent-chat-message.entity';
import { AgentChatThreadEntity } from 'src/engine/metadata-modules/agent/agent-chat-thread.entity';
import {
AgentException,
AgentExceptionCode,
} from 'src/engine/metadata-modules/agent/agent.exception';
import { AgentTitleGenerationService } from './agent-title-generation.service';
@Injectable()
export class AgentChatService {
constructor(
@InjectRepository(AgentChatThreadEntity, 'core')
private readonly threadRepository: Repository<AgentChatThreadEntity>,
@InjectRepository(AgentChatMessageEntity, 'core')
private readonly messageRepository: Repository<AgentChatMessageEntity>,
@InjectRepository(FileEntity, 'core')
private readonly fileRepository: Repository<FileEntity>,
private readonly titleGenerationService: AgentTitleGenerationService,
) {}
async createThread(agentId: string, userWorkspaceId: string) {
const thread = this.threadRepository.create({
agentId,
userWorkspaceId,
});
return this.threadRepository.save(thread);
}
async getThreadsForAgent(agentId: string, userWorkspaceId: string) {
return this.threadRepository.find({
where: {
agentId,
userWorkspaceId,
},
order: { createdAt: 'DESC' },
});
}
async getThreadById(threadId: string, userWorkspaceId: string) {
const thread = await this.threadRepository.findOne({
where: {
id: threadId,
userWorkspaceId,
},
});
if (!thread) {
throw new AgentException(
'Thread not found',
AgentExceptionCode.AGENT_EXECUTION_FAILED,
);
}
return thread;
}
async addMessage({
threadId,
role,
content,
fileIds,
}: {
threadId: string;
role: AgentChatMessageRole;
content: string;
fileIds?: string[];
}) {
const message = this.messageRepository.create({
threadId,
role,
content,
});
const savedMessage = await this.messageRepository.save(message);
if (fileIds && fileIds.length > 0) {
for (const fileId of fileIds) {
await this.fileRepository.update(fileId, {
messageId: savedMessage.id,
});
}
}
this.generateTitleIfNeeded(threadId, content);
return savedMessage;
}
async getMessagesForThread(threadId: string, userWorkspaceId: string) {
const thread = await this.threadRepository.findOne({
where: {
id: threadId,
userWorkspaceId,
},
});
if (!thread) {
throw new AgentException(
'Thread not found',
AgentExceptionCode.AGENT_EXECUTION_FAILED,
);
}
return this.messageRepository.find({
where: { threadId },
order: { createdAt: 'ASC' },
relations: ['files'],
});
}
private async generateTitleIfNeeded(
threadId: string,
messageContent: string,
) {
const thread = await this.threadRepository.findOne({
where: { id: threadId },
select: ['id', 'title'],
});
if (!thread || thread.title) {
return;
}
const title =
await this.titleGenerationService.generateThreadTitle(messageContent);
await this.threadRepository.update(threadId, { title });
}
}