2026-05-24 13:58:50 +08:00
|
|
|
import { Injectable, NotFoundException, Optional } from '@nestjs/common';
|
2026-05-19 22:20:29 +08:00
|
|
|
import { ImportCandidateRepository } from './import-candidate.repository';
|
|
|
|
|
import { KnowledgeItemsRepository } from '../knowledge-items/knowledge-items.repository';
|
2026-05-24 13:58:50 +08:00
|
|
|
import { ContentSafetyService } from '../content-safety/content-safety.service';
|
2026-05-19 22:20:29 +08:00
|
|
|
|
|
|
|
|
@Injectable()
|
|
|
|
|
export class ImportCandidateService {
|
|
|
|
|
constructor(
|
|
|
|
|
private readonly repository: ImportCandidateRepository,
|
|
|
|
|
private readonly itemsRepo: KnowledgeItemsRepository,
|
2026-05-24 13:58:50 +08:00
|
|
|
@Optional() private readonly safety?: ContentSafetyService,
|
2026-05-19 22:20:29 +08:00
|
|
|
) {}
|
|
|
|
|
|
2026-05-24 13:58:50 +08:00
|
|
|
private async checkSafety(title: string, content: string, userId: string): Promise<boolean> {
|
|
|
|
|
if (!this.safety) return true;
|
|
|
|
|
const check = await this.safety.check(title + ' ' + (content || '').slice(0, 200), { userId, contentType: 'candidate' });
|
|
|
|
|
return check.safe;
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-19 22:20:29 +08:00
|
|
|
async findBySource(sourceId: string) {
|
|
|
|
|
return this.repository.findBySource(sourceId);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async findOne(id: string) {
|
|
|
|
|
const c = await this.repository.findById(id);
|
|
|
|
|
if (!c) throw new NotFoundException('候选知识点不存在');
|
|
|
|
|
return c;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async accept(id: string) {
|
|
|
|
|
const candidate = await this.repository.findById(id);
|
|
|
|
|
if (!candidate) throw new NotFoundException('候选知识点不存在');
|
|
|
|
|
|
2026-05-24 13:58:50 +08:00
|
|
|
// Content safety check before accepting
|
|
|
|
|
const safe = await this.checkSafety(candidate.title, candidate.content || '', candidate.userId);
|
|
|
|
|
if (!safe) return { status: 'BLOCKED', reason: '内容安全审核未通过' };
|
|
|
|
|
|
2026-05-19 22:20:29 +08:00
|
|
|
await this.repository.updateStatus(id, 'ACCEPTED');
|
|
|
|
|
|
|
|
|
|
// 生成 KnowledgeItem
|
|
|
|
|
await this.itemsRepo.create(candidate.userId, candidate.knowledgeBaseId, {
|
|
|
|
|
title: candidate.title,
|
|
|
|
|
content: (candidate.content as string) ?? '',
|
|
|
|
|
itemType: 'ai_generated',
|
|
|
|
|
orderIndex: candidate.orderIndex,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return { status: 'ACCEPTED' };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async reject(id: string) {
|
|
|
|
|
const candidate = await this.repository.findById(id);
|
|
|
|
|
if (!candidate) throw new NotFoundException('候选知识点不存在');
|
|
|
|
|
return this.repository.updateStatus(id, 'REJECTED');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async batchAccept(sourceId: string) {
|
|
|
|
|
const candidates = await this.repository.findBySource(sourceId);
|
|
|
|
|
const pending = candidates.filter(c => c.status === 'PENDING');
|
|
|
|
|
|
|
|
|
|
for (const c of pending) {
|
|
|
|
|
await this.accept(c.id);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return { accepted: pending.length };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async update(id: string, dto: any) {
|
|
|
|
|
const candidate = await this.repository.findById(id);
|
|
|
|
|
if (!candidate) throw new NotFoundException('候选知识点不存在');
|
|
|
|
|
return this.repository.update(id, dto);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async createCandidates(userId: string, knowledgeBaseId: string, sourceId: string, importId: string, candidates: Array<any>) {
|
2026-05-24 13:58:50 +08:00
|
|
|
// Filter out unsafe candidates
|
2026-05-24 14:25:54 +08:00
|
|
|
const safeCandidates: any[] = [];
|
2026-05-24 13:58:50 +08:00
|
|
|
for (const c of candidates) {
|
|
|
|
|
const safe = await this.checkSafety(c.title || '', c.content || '', userId);
|
|
|
|
|
if (safe) safeCandidates.push(c);
|
|
|
|
|
}
|
|
|
|
|
if (safeCandidates.length === 0) return { created: 0, filtered: candidates.length };
|
|
|
|
|
return this.repository.createMany(userId, knowledgeBaseId, sourceId, importId, safeCandidates);
|
2026-05-19 22:20:29 +08:00
|
|
|
}
|
|
|
|
|
}
|