import { Injectable, Inject } from '@nestjs/common'; import { EmbeddingService } from '../infrastructure/embedding/embedding.service'; import { ChunkingService } from '../application/services/chunking.service'; import { IKnowledgeRepository, KNOWLEDGE_REPOSITORY, } from '../domain/repositories/knowledge.repository.interface'; import { KnowledgeArticleEntity, KnowledgeSource, } from '../domain/entities/knowledge-article.entity'; /** * 知识管理服务 * 提供知识库的CRUD操作和处理 */ @Injectable() export class KnowledgeService { constructor( private embeddingService: EmbeddingService, private chunkingService: ChunkingService, @Inject(KNOWLEDGE_REPOSITORY) private knowledgeRepo: IKnowledgeRepository, ) {} /** * 创建知识文章 */ async createArticle(params: { title: string; content: string; category: string; tags?: string[]; source?: KnowledgeSource; sourceUrl?: string; createdBy?: string; autoPublish?: boolean; }): Promise { // 1. 创建文章实体 const article = KnowledgeArticleEntity.create({ title: params.title, content: params.content, category: params.category, tags: params.tags, source: params.source || KnowledgeSource.MANUAL, sourceUrl: params.sourceUrl, createdBy: params.createdBy, }); // 2. 生成文章向量 const embedding = await this.embeddingService.getEmbedding( `${article.title}\n${article.summary}`, ); article.setEmbedding(embedding); // 3. 自动发布(可选) if (params.autoPublish) { article.publish(); } // 4. 保存文章 await this.knowledgeRepo.saveArticle(article); // 5. 分块并保存 await this.processArticleChunks(article); console.log(`[KnowledgeService] Created article: ${article.id} - ${article.title}`); return article; } /** * 处理文章分块 */ private async processArticleChunks(article: KnowledgeArticleEntity): Promise { // 1. 删除旧的块 await this.knowledgeRepo.deleteChunksByArticleId(article.id); // 2. 分块 const chunks = this.chunkingService.chunkArticle(article); // 3. 批量生成向量 const embeddings = await this.embeddingService.getEmbeddings( chunks.map(c => c.content), ); // 4. 设置向量 chunks.forEach((chunk, index) => { chunk.setEmbedding(embeddings[index]); }); // 5. 保存块 await this.knowledgeRepo.saveChunks(chunks); console.log(`[KnowledgeService] Processed ${chunks.length} chunks for article ${article.id}`); } /** * 更新文章 */ async updateArticle( articleId: string, params: { title?: string; content?: string; category?: string; tags?: string[]; updatedBy?: string; }, ): Promise { const article = await this.knowledgeRepo.findArticleById(articleId); if (!article) { throw new Error(`Article not found: ${articleId}`); } // 更新字段 if (params.title || params.content) { article.updateContent( params.title || article.title, params.content || article.content, params.updatedBy, ); // 重新生成向量 const embedding = await this.embeddingService.getEmbedding( `${article.title}\n${article.summary}`, ); article.setEmbedding(embedding); // 重新分块 await this.processArticleChunks(article); } if (params.category) { article.category = params.category; } if (params.tags) { article.tags = params.tags; } await this.knowledgeRepo.updateArticle(article); return article; } /** * 获取文章详情 */ async getArticle(articleId: string): Promise { return this.knowledgeRepo.findArticleById(articleId); } /** * 获取文章列表 */ async listArticles(params: { category?: string; publishedOnly?: boolean; page?: number; pageSize?: number; }): Promise<{ items: KnowledgeArticleEntity[]; total: number; page: number; pageSize: number; }> { const page = params.page || 1; const pageSize = params.pageSize || 20; const offset = (page - 1) * pageSize; const [items, total] = await Promise.all([ this.knowledgeRepo.findArticlesByCategory(params.category || '', { publishedOnly: params.publishedOnly, limit: pageSize, offset, }), this.knowledgeRepo.countArticles({ category: params.category, publishedOnly: params.publishedOnly, }), ]); return { items, total, page, pageSize }; } /** * 搜索文章 */ async searchArticles(params: { query: string; category?: string; useVector?: boolean; }): Promise> { if (params.useVector) { // 向量搜索 const embedding = await this.embeddingService.getEmbedding(params.query); const results = await this.knowledgeRepo.searchArticlesByVector(embedding, { category: params.category, publishedOnly: true, limit: 10, }); return results; } // 关键词搜索 const articles = await this.knowledgeRepo.searchArticles(params.query, { category: params.category, publishedOnly: true, limit: 10, }); return articles.map(article => ({ article })); } /** * 发布文章 */ async publishArticle(articleId: string): Promise { const article = await this.knowledgeRepo.findArticleById(articleId); if (!article) { throw new Error(`Article not found: ${articleId}`); } article.publish(); await this.knowledgeRepo.updateArticle(article); } /** * 取消发布 */ async unpublishArticle(articleId: string): Promise { const article = await this.knowledgeRepo.findArticleById(articleId); if (!article) { throw new Error(`Article not found: ${articleId}`); } article.unpublish(); await this.knowledgeRepo.updateArticle(article); } /** * 删除文章 */ async deleteArticle(articleId: string): Promise { // 先删除分块 await this.knowledgeRepo.deleteChunksByArticleId(articleId); // 再删除文章 await this.knowledgeRepo.deleteArticle(articleId); } /** * 记录用户反馈 */ async recordFeedback(articleId: string, helpful: boolean): Promise { const article = await this.knowledgeRepo.findArticleById(articleId); if (!article) { throw new Error(`Article not found: ${articleId}`); } article.recordFeedback(helpful); await this.knowledgeRepo.updateArticle(article); } /** * 批量导入文章 */ async importArticles( articles: Array<{ title: string; content: string; category: string; tags?: string[]; }>, createdBy?: string, ): Promise<{ success: number; failed: number; errors: string[] }> { let success = 0; let failed = 0; const errors: string[] = []; for (const articleData of articles) { try { await this.createArticle({ ...articleData, source: KnowledgeSource.IMPORT, createdBy, autoPublish: false, // 导入后需要审核 }); success++; } catch (error) { failed++; errors.push(`Failed to import "${articleData.title}": ${(error as Error).message}`); } } return { success, failed, errors }; } /** * 获取知识库统计 */ async getStatistics(): Promise<{ totalArticles: number; publishedArticles: number; byCategory: Record; recentArticles: KnowledgeArticleEntity[]; }> { const categories = ['QMAS', 'GEP', 'IANG', 'TTPS', 'CIES', 'TechTAS', 'GENERAL']; const [total, published, byCategoryResults, recent] = await Promise.all([ this.knowledgeRepo.countArticles(), this.knowledgeRepo.countArticles({ publishedOnly: true }), Promise.all( categories.map(async cat => ({ category: cat, count: await this.knowledgeRepo.countArticles({ category: cat }), })), ), this.knowledgeRepo.findArticlesByCategory('', { limit: 5 }), ]); const byCategory: Record = {}; byCategoryResults.forEach(r => { byCategory[r.category] = r.count; }); return { totalArticles: total, publishedArticles: published, byCategory, recentArticles: recent, }; } }