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),
|
`createdAt` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||||
`updatedAt` DATETIME(3) NOT NULL,
|
`updatedAt` DATETIME(3) NOT NULL,
|
||||||
`deletedAt` DATETIME(3) NULL,
|
`deletedAt` DATETIME(3) NULL,
|
||||||
|
|
||||||
UNIQUE INDEX `AdminConversation_hermesSessionId_key`(`hermesSessionId`),
|
UNIQUE INDEX `AdminConversation_hermesSessionId_key`(`hermesSessionId`),
|
||||||
INDEX `AdminConversation_adminUserId_idx`(`adminUserId`),
|
INDEX `AdminConversation_adminUserId_idx`(`adminUserId`),
|
||||||
INDEX `AdminConversation_hermesSessionId_idx`(`hermesSessionId`),
|
INDEX `AdminConversation_hermesSessionId_idx`(`hermesSessionId`),
|
||||||
PRIMARY KEY (`id`)
|
PRIMARY KEY (`id`)
|
||||||
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
) 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
|
-- AddForeignKey
|
||||||
ALTER TABLE `AdminConversation` ADD CONSTRAINT `AdminConversation_adminUserId_fkey` FOREIGN KEY (`adminUserId`) REFERENCES `AdminUser`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;
|
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())
|
createdAt DateTime @default(now())
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
deletedAt DateTime?
|
deletedAt DateTime?
|
||||||
|
messages AdminMessage[]
|
||||||
|
|
||||||
adminUser AdminUser @relation(fields: [adminUserId], references: [id])
|
adminUser AdminUser @relation(fields: [adminUserId], references: [id])
|
||||||
|
|
||||||
@@index([adminUserId])
|
@@index([adminUserId])
|
||||||
@@index([hermesSessionId])
|
@@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
|
const conversationId = dto.conversationId
|
||||||
?? (await this.conversationService.create(adminUserId)).id;
|
?? (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);
|
const result = await this.callHermes(dto.messages, sessionId);
|
||||||
|
|
||||||
|
// Save assistant reply
|
||||||
|
await this.conversationService.saveMessage(conversationId, 'assistant', result.content);
|
||||||
|
|
||||||
return { ...result, conversationId };
|
return { ...result, conversationId };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { Controller, Get, Post, Patch, Delete, Param, Body, Req, UseGuards } from '@nestjs/common';
|
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 { AdminConversationService } from './admin-conversation.service';
|
||||||
import { CreateConversationDto, UpdateConversationDto } from './dto';
|
import { CreateConversationDto, UpdateConversationDto } from './dto';
|
||||||
import { AdminAuthGuard } from '../../common/guards/admin-auth.guard';
|
import { AdminAuthGuard } from '../../common/guards/admin-auth.guard';
|
||||||
@ -16,6 +16,12 @@ export class AdminConversationController {
|
|||||||
return this.svc.list(req.adminUser.id);
|
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()
|
@Post()
|
||||||
async create(@Req() req: any, @Body() dto: CreateConversationDto) {
|
async create(@Req() req: any, @Body() dto: CreateConversationDto) {
|
||||||
return this.svc.create(req.adminUser.id, dto.title);
|
return this.svc.create(req.adminUser.id, dto.title);
|
||||||
@ -23,7 +29,7 @@ export class AdminConversationController {
|
|||||||
|
|
||||||
@Patch(':id')
|
@Patch(':id')
|
||||||
async update(@Req() req: any, @Param('id') id: string, @Body() dto: UpdateConversationDto) {
|
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 };
|
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) {
|
async create(adminUserId: string, title?: string) {
|
||||||
const hermesSessionId = randomUUID();
|
const hermesSessionId = randomUUID();
|
||||||
return this.prisma.adminConversation.create({
|
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({
|
return this.prisma.adminConversation.updateMany({
|
||||||
where: { id, adminUserId, deletedAt: null },
|
where: { id, adminUserId, deletedAt: null },
|
||||||
data: { title },
|
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) {
|
async getSessionId(conversationId: string, adminUserId: string) {
|
||||||
const conv = await this.prisma.adminConversation.findFirst({
|
const conv = await this.prisma.adminConversation.findFirst({
|
||||||
where: { id: conversationId, adminUserId, deletedAt: null },
|
where: { id: conversationId, adminUserId, deletedAt: null },
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user