266 lines
5.8 KiB
TypeScript
266 lines
5.8 KiB
TypeScript
import {
|
||
Controller,
|
||
Get,
|
||
Post,
|
||
Put,
|
||
Delete,
|
||
Body,
|
||
Param,
|
||
Query,
|
||
HttpCode,
|
||
HttpStatus,
|
||
UploadedFile,
|
||
UseInterceptors,
|
||
BadRequestException,
|
||
} from '@nestjs/common';
|
||
import { FileInterceptor } from '@nestjs/platform-express';
|
||
import { KnowledgeService } from '../../application/services/knowledge.service';
|
||
import { RAGService } from '../../application/services/rag.service';
|
||
import { TextExtractionService } from '../../application/services/text-extraction.service';
|
||
import { KnowledgeSource } from '../../domain/entities/knowledge-article.entity';
|
||
import {
|
||
CreateArticleDto,
|
||
UpdateArticleDto,
|
||
SearchArticlesDto,
|
||
RetrieveKnowledgeDto,
|
||
ImportArticlesDto,
|
||
FeedbackDto,
|
||
ExtractedTextResponse,
|
||
} from '../../application/dtos/knowledge.dto';
|
||
|
||
@Controller('knowledge')
|
||
export class KnowledgeController {
|
||
constructor(
|
||
private knowledgeService: KnowledgeService,
|
||
private ragService: RAGService,
|
||
private textExtractionService: TextExtractionService,
|
||
) {}
|
||
|
||
/**
|
||
* 创建文章
|
||
*/
|
||
@Post('articles')
|
||
@HttpCode(HttpStatus.CREATED)
|
||
async createArticle(@Body() dto: CreateArticleDto) {
|
||
const article = await this.knowledgeService.createArticle({
|
||
...dto,
|
||
source: KnowledgeSource.MANUAL,
|
||
});
|
||
return {
|
||
success: true,
|
||
data: article,
|
||
};
|
||
}
|
||
|
||
/**
|
||
* 上传文件并提取文本(两步流程的第一步)
|
||
* 返回提取的文本内容,管理员预览编辑后通过 createArticle 保存
|
||
*/
|
||
@Post('articles/upload')
|
||
@UseInterceptors(
|
||
FileInterceptor('file', {
|
||
limits: { fileSize: 200 * 1024 * 1024 },
|
||
}),
|
||
)
|
||
async uploadAndExtract(
|
||
@UploadedFile() file: Express.Multer.File,
|
||
): Promise<{ success: boolean; data: ExtractedTextResponse }> {
|
||
if (!file) {
|
||
throw new BadRequestException('未上传文件');
|
||
}
|
||
|
||
const extracted = await this.textExtractionService.extractText(file);
|
||
|
||
return {
|
||
success: true,
|
||
data: {
|
||
extractedText: extracted.text,
|
||
suggestedTitle: extracted.title,
|
||
wordCount: extracted.wordCount,
|
||
pageCount: extracted.pageCount,
|
||
},
|
||
};
|
||
}
|
||
|
||
/**
|
||
* 获取文章详情
|
||
*/
|
||
@Get('articles/:id')
|
||
async getArticle(@Param('id') id: string) {
|
||
const article = await this.knowledgeService.getArticle(id);
|
||
if (!article) {
|
||
return {
|
||
success: false,
|
||
error: 'Article not found',
|
||
};
|
||
}
|
||
return {
|
||
success: true,
|
||
data: article,
|
||
};
|
||
}
|
||
|
||
/**
|
||
* 获取文章列表
|
||
*/
|
||
@Get('articles')
|
||
async listArticles(
|
||
@Query('category') category?: string,
|
||
@Query('publishedOnly') publishedOnly?: string,
|
||
@Query('page') page?: string,
|
||
@Query('pageSize') pageSize?: string,
|
||
) {
|
||
const result = await this.knowledgeService.listArticles({
|
||
category,
|
||
publishedOnly: publishedOnly === 'true',
|
||
page: page ? parseInt(page) : 1,
|
||
pageSize: pageSize ? parseInt(pageSize) : 20,
|
||
});
|
||
return {
|
||
success: true,
|
||
data: result,
|
||
};
|
||
}
|
||
|
||
/**
|
||
* 更新文章
|
||
*/
|
||
@Put('articles/:id')
|
||
async updateArticle(
|
||
@Param('id') id: string,
|
||
@Body() dto: UpdateArticleDto,
|
||
) {
|
||
const article = await this.knowledgeService.updateArticle(id, dto);
|
||
return {
|
||
success: true,
|
||
data: article,
|
||
};
|
||
}
|
||
|
||
/**
|
||
* 删除文章
|
||
*/
|
||
@Delete('articles/:id')
|
||
@HttpCode(HttpStatus.NO_CONTENT)
|
||
async deleteArticle(@Param('id') id: string) {
|
||
await this.knowledgeService.deleteArticle(id);
|
||
}
|
||
|
||
/**
|
||
* 发布文章
|
||
*/
|
||
@Post('articles/:id/publish')
|
||
async publishArticle(@Param('id') id: string) {
|
||
await this.knowledgeService.publishArticle(id);
|
||
return {
|
||
success: true,
|
||
message: 'Article published',
|
||
};
|
||
}
|
||
|
||
/**
|
||
* 取消发布
|
||
*/
|
||
@Post('articles/:id/unpublish')
|
||
async unpublishArticle(@Param('id') id: string) {
|
||
await this.knowledgeService.unpublishArticle(id);
|
||
return {
|
||
success: true,
|
||
message: 'Article unpublished',
|
||
};
|
||
}
|
||
|
||
/**
|
||
* 搜索文章
|
||
*/
|
||
@Post('articles/search')
|
||
async searchArticles(@Body() dto: SearchArticlesDto) {
|
||
const results = await this.knowledgeService.searchArticles(dto);
|
||
return {
|
||
success: true,
|
||
data: results,
|
||
};
|
||
}
|
||
|
||
/**
|
||
* 记录反馈
|
||
*/
|
||
@Post('articles/:id/feedback')
|
||
async recordFeedback(
|
||
@Param('id') id: string,
|
||
@Body() dto: FeedbackDto,
|
||
) {
|
||
await this.knowledgeService.recordFeedback(id, dto.helpful);
|
||
return {
|
||
success: true,
|
||
message: 'Feedback recorded',
|
||
};
|
||
}
|
||
|
||
/**
|
||
* 批量导入
|
||
*/
|
||
@Post('articles/import')
|
||
async importArticles(@Body() dto: ImportArticlesDto) {
|
||
const result = await this.knowledgeService.importArticles(dto.articles);
|
||
return {
|
||
success: true,
|
||
data: result,
|
||
};
|
||
}
|
||
|
||
/**
|
||
* 获取统计信息
|
||
*/
|
||
@Get('statistics')
|
||
async getStatistics() {
|
||
const stats = await this.knowledgeService.getStatistics();
|
||
return {
|
||
success: true,
|
||
data: stats,
|
||
};
|
||
}
|
||
|
||
// ========== RAG检索接口 ==========
|
||
|
||
/**
|
||
* RAG知识检索(供对话服务调用)
|
||
*/
|
||
@Post('retrieve')
|
||
async retrieveKnowledge(@Body() dto: RetrieveKnowledgeDto) {
|
||
const result = await this.ragService.retrieve(dto);
|
||
return {
|
||
success: true,
|
||
data: result,
|
||
};
|
||
}
|
||
|
||
/**
|
||
* 检索并格式化为提示词
|
||
*/
|
||
@Post('retrieve/prompt')
|
||
async retrieveForPrompt(@Body() dto: RetrieveKnowledgeDto) {
|
||
const context = await this.ragService.retrieveForPrompt({
|
||
query: dto.query,
|
||
userId: dto.userId,
|
||
category: dto.category,
|
||
});
|
||
return {
|
||
success: true,
|
||
data: { context },
|
||
};
|
||
}
|
||
|
||
/**
|
||
* 检查是否离题
|
||
*/
|
||
@Post('check-off-topic')
|
||
async checkOffTopic(@Body() body: { query: string }) {
|
||
const result = await this.ragService.checkOffTopic(body.query);
|
||
return {
|
||
success: true,
|
||
data: result,
|
||
};
|
||
}
|
||
}
|