feat: add AdminMessage persistence + conversation title auto-set + messages API
Some checks failed
Deploy API Server / build-and-deploy (push) Failing after 21s
Some checks failed
Deploy API Server / build-and-deploy (push) Failing after 21s
This commit is contained in:
parent
73e52d2201
commit
f2d3f3f13f
@ -7,12 +7,24 @@ CREATE TABLE `AdminConversation` (
|
||||
`createdAt` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
`updatedAt` DATETIME(3) NOT NULL,
|
||||
`deletedAt` DATETIME(3) NULL,
|
||||
|
||||
UNIQUE INDEX `AdminConversation_hermesSessionId_key`(`hermesSessionId`),
|
||||
INDEX `AdminConversation_adminUserId_idx`(`adminUserId`),
|
||||
INDEX `AdminConversation_hermesSessionId_idx`(`hermesSessionId`),
|
||||
PRIMARY KEY (`id`)
|
||||
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE `AdminMessage` (
|
||||
`id` VARCHAR(191) NOT NULL,
|
||||
`conversationId` VARCHAR(191) NOT NULL,
|
||||
`role` VARCHAR(16) NOT NULL,
|
||||
`content` LONGTEXT NOT NULL,
|
||||
`createdAt` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
INDEX `AdminMessage_conversationId_idx`(`conversationId`),
|
||||
INDEX `AdminMessage_createdAt_idx`(`createdAt`),
|
||||
PRIMARY KEY (`id`)
|
||||
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE `AdminConversation` ADD CONSTRAINT `AdminConversation_adminUserId_fkey` FOREIGN KEY (`adminUserId`) REFERENCES `AdminUser`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
ALTER TABLE `AdminMessage` ADD CONSTRAINT `AdminMessage_conversationId_fkey` FOREIGN KEY (`conversationId`) REFERENCES `AdminConversation`(`id`) ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
@ -803,9 +803,23 @@ model AdminConversation {
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
deletedAt DateTime?
|
||||
messages AdminMessage[]
|
||||
|
||||
adminUser AdminUser @relation(fields: [adminUserId], references: [id])
|
||||
|
||||
@@index([adminUserId])
|
||||
@@index([hermesSessionId])
|
||||
}
|
||||
|
||||
model AdminMessage {
|
||||
id String @id @default(cuid())
|
||||
conversationId String
|
||||
role String @db.VarChar(16)
|
||||
content String @db.LongText
|
||||
createdAt DateTime @default(now())
|
||||
|
||||
conversation AdminConversation @relation(fields: [conversationId], references: [id])
|
||||
|
||||
@@index([conversationId])
|
||||
@@index([createdAt])
|
||||
}
|
||||
|
||||
@ -20,8 +20,17 @@ export class AdminAiChatService {
|
||||
const conversationId = dto.conversationId
|
||||
?? (await this.conversationService.create(adminUserId)).id;
|
||||
|
||||
// Save user message
|
||||
const userMsg = dto.messages[dto.messages.length - 1];
|
||||
if (userMsg && userMsg.role === 'user') {
|
||||
await this.conversationService.saveMessage(conversationId, 'user', userMsg.content);
|
||||
}
|
||||
|
||||
const result = await this.callHermes(dto.messages, sessionId);
|
||||
|
||||
// Save assistant reply
|
||||
await this.conversationService.saveMessage(conversationId, 'assistant', result.content);
|
||||
|
||||
return { ...result, conversationId };
|
||||
}
|
||||
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { Controller, Get, Post, Patch, Delete, Param, Body, Req, UseGuards } from '@nestjs/common';
|
||||
import { ApiTags, ApiBearerAuth } from '@nestjs/swagger';
|
||||
import { ApiTags, ApiBearerAuth, ApiOperation } from '@nestjs/swagger';
|
||||
import { AdminConversationService } from './admin-conversation.service';
|
||||
import { CreateConversationDto, UpdateConversationDto } from './dto';
|
||||
import { AdminAuthGuard } from '../../common/guards/admin-auth.guard';
|
||||
@ -16,6 +16,12 @@ export class AdminConversationController {
|
||||
return this.svc.list(req.adminUser.id);
|
||||
}
|
||||
|
||||
@Get(':id/messages')
|
||||
@ApiOperation({ summary: '获取对话的历史消息' })
|
||||
async messages(@Req() req: any, @Param('id') id: string) {
|
||||
return this.svc.getMessages(id, req.adminUser.id);
|
||||
}
|
||||
|
||||
@Post()
|
||||
async create(@Req() req: any, @Body() dto: CreateConversationDto) {
|
||||
return this.svc.create(req.adminUser.id, dto.title);
|
||||
@ -23,7 +29,7 @@ export class AdminConversationController {
|
||||
|
||||
@Patch(':id')
|
||||
async update(@Req() req: any, @Param('id') id: string, @Body() dto: UpdateConversationDto) {
|
||||
await this.svc.update(id, req.adminUser.id, dto.title!);
|
||||
await this.svc.updateTitle(id, req.adminUser.id, dto.title);
|
||||
return { success: true };
|
||||
}
|
||||
|
||||
|
||||
@ -14,6 +14,19 @@ export class AdminConversationService {
|
||||
});
|
||||
}
|
||||
|
||||
async getMessages(conversationId: string, adminUserId: string) {
|
||||
const conv = await this.prisma.adminConversation.findFirst({
|
||||
where: { id: conversationId, adminUserId, deletedAt: null },
|
||||
});
|
||||
if (!conv) return [];
|
||||
|
||||
return this.prisma.adminMessage.findMany({
|
||||
where: { conversationId },
|
||||
orderBy: { createdAt: 'asc' },
|
||||
select: { id: true, role: true, content: true, createdAt: true },
|
||||
});
|
||||
}
|
||||
|
||||
async create(adminUserId: string, title?: string) {
|
||||
const hermesSessionId = randomUUID();
|
||||
return this.prisma.adminConversation.create({
|
||||
@ -22,7 +35,7 @@ export class AdminConversationService {
|
||||
});
|
||||
}
|
||||
|
||||
async update(id: string, adminUserId: string, title: string) {
|
||||
async updateTitle(id: string, adminUserId: string, title: string) {
|
||||
return this.prisma.adminConversation.updateMany({
|
||||
where: { id, adminUserId, deletedAt: null },
|
||||
data: { title },
|
||||
@ -36,6 +49,29 @@ export class AdminConversationService {
|
||||
});
|
||||
}
|
||||
|
||||
async saveMessage(conversationId: string, role: string, content: string) {
|
||||
await this.prisma.adminMessage.create({
|
||||
data: { conversationId, role, content },
|
||||
});
|
||||
// Update conversation timestamp and auto-set title from first user message
|
||||
if (role === 'user') {
|
||||
const conv = await this.prisma.adminConversation.findUnique({
|
||||
where: { id: conversationId },
|
||||
select: { title: true, _count: { select: { messages: true } } },
|
||||
});
|
||||
// Auto-title: use first user message (truncated)
|
||||
const isFirstMessage = (conv?._count?.messages ?? 0) <= 1;
|
||||
const updateData: any = { updatedAt: new Date() };
|
||||
if (isFirstMessage && conv?.title === '新对话') {
|
||||
updateData.title = content.slice(0, 40);
|
||||
}
|
||||
await this.prisma.adminConversation.update({
|
||||
where: { id: conversationId },
|
||||
data: updateData,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async getSessionId(conversationId: string, adminUserId: string) {
|
||||
const conv = await this.prisma.adminConversation.findFirst({
|
||||
where: { id: conversationId, adminUserId, deletedAt: null },
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user