|
|
@@ -0,0 +1,337 @@
|
|
|
+import { GenericCrudService } from '@/server/utils/generic-crud.service';
|
|
|
+import { DataSource, Repository, In } from 'typeorm';
|
|
|
+import { SolutionDesign } from './solution-design.entity';
|
|
|
+import { SolutionChapter } from './solution-chapter.entity';
|
|
|
+import { DocumentService } from '@/server/modules/documents/document.service';
|
|
|
+import { FileService } from '@/server/modules/files/file.service';
|
|
|
+import { AIService } from '@/server/services/ai.service';
|
|
|
+
|
|
|
+export interface ChapterModificationRequest {
|
|
|
+ chapterId: number;
|
|
|
+ instructions: string;
|
|
|
+ currentContent: string;
|
|
|
+}
|
|
|
+
|
|
|
+export interface AIModificationSuggestion {
|
|
|
+ suggestions: string[];
|
|
|
+ revisedContent: string;
|
|
|
+ confidence: number;
|
|
|
+}
|
|
|
+
|
|
|
+export class SolutionDesignService extends GenericCrudService<SolutionDesign> {
|
|
|
+ private chapterRepository: Repository<SolutionChapter>;
|
|
|
+ private documentService: DocumentService;
|
|
|
+ private fileService: FileService;
|
|
|
+ private aiService: AIService;
|
|
|
+
|
|
|
+ constructor(dataSource: DataSource) {
|
|
|
+ super(dataSource, SolutionDesign);
|
|
|
+ this.chapterRepository = dataSource.getRepository(SolutionChapter);
|
|
|
+ this.documentService = new DocumentService(dataSource);
|
|
|
+ this.fileService = new FileService(dataSource);
|
|
|
+ this.aiService = new AIService();
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 创建方案设计并初始化章节
|
|
|
+ */
|
|
|
+ async createSolutionDesign(
|
|
|
+ data: any,
|
|
|
+ userId: number,
|
|
|
+ originalFileId?: number
|
|
|
+ ): Promise<SolutionDesign> {
|
|
|
+ const solutionDesign = this.repository.create({
|
|
|
+ ...data,
|
|
|
+ userId,
|
|
|
+ originalFileId: originalFileId || null,
|
|
|
+ status: 'draft',
|
|
|
+ progress: 0,
|
|
|
+ totalChapters: 0,
|
|
|
+ completedChapters: 0
|
|
|
+ });
|
|
|
+
|
|
|
+ const savedDesign = await this.repository.save(solutionDesign);
|
|
|
+
|
|
|
+ // 如果有原始文件,尝试自动提取章节
|
|
|
+ if (originalFileId) {
|
|
|
+ await this.autoExtractChapters(savedDesign.id, originalFileId);
|
|
|
+ }
|
|
|
+
|
|
|
+ return this.getById(savedDesign.id, ['user', 'originalFile', 'chapters']);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 自动从原始文件提取章节
|
|
|
+ */
|
|
|
+ private async autoExtractChapters(designId: number, fileId: number): Promise<void> {
|
|
|
+ try {
|
|
|
+ const file = await this.fileService.getById(fileId);
|
|
|
+ if (!file) return;
|
|
|
+
|
|
|
+ // 这里可以集成文档解析逻辑,自动识别章节结构
|
|
|
+ // 暂时创建默认章节
|
|
|
+ const defaultChapters = [
|
|
|
+ { chapterNumber: 1, title: '项目概述', content: '请填写项目概述内容' },
|
|
|
+ { chapterNumber: 2, title: '需求分析', content: '请填写需求分析内容' },
|
|
|
+ { chapterNumber: 3, title: '技术方案', content: '请填写技术方案内容' },
|
|
|
+ { chapterNumber: 4, title: '实施计划', content: '请填写实施计划内容' },
|
|
|
+ { chapterNumber: 5, title: '预算估算', content: '请填写预算估算内容' }
|
|
|
+ ];
|
|
|
+
|
|
|
+ for (const chapterData of defaultChapters) {
|
|
|
+ const chapter = this.chapterRepository.create({
|
|
|
+ ...chapterData,
|
|
|
+ solutionDesignId: designId,
|
|
|
+ originalContent: chapterData.content,
|
|
|
+ status: 'pending'
|
|
|
+ });
|
|
|
+ await this.chapterRepository.save(chapter);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 更新总章节数
|
|
|
+ await this.repository.update(designId, {
|
|
|
+ totalChapters: defaultChapters.length
|
|
|
+ });
|
|
|
+
|
|
|
+ } catch (error) {
|
|
|
+ console.error('自动提取章节失败:', error);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取用户的方案设计列表
|
|
|
+ */
|
|
|
+ async getUserSolutionDesigns(
|
|
|
+ userId: number,
|
|
|
+ page: number = 1,
|
|
|
+ pageSize: number = 10,
|
|
|
+ status?: string
|
|
|
+ ): Promise<[SolutionDesign[], number]> {
|
|
|
+ const where: any = {
|
|
|
+ userId,
|
|
|
+ isDeleted: 0
|
|
|
+ };
|
|
|
+
|
|
|
+ if (status) {
|
|
|
+ where.status = status;
|
|
|
+ }
|
|
|
+
|
|
|
+ return this.getList(
|
|
|
+ page,
|
|
|
+ pageSize,
|
|
|
+ undefined,
|
|
|
+ ['title', 'description'],
|
|
|
+ where,
|
|
|
+ ['user', 'originalFile'],
|
|
|
+ { updatedAt: 'DESC' }
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 添加章节到方案设计
|
|
|
+ */
|
|
|
+ async addChapter(
|
|
|
+ designId: number,
|
|
|
+ data: any
|
|
|
+ ): Promise<SolutionChapter> {
|
|
|
+ const design = await this.getById(designId);
|
|
|
+ if (!design) {
|
|
|
+ throw new Error('方案设计不存在');
|
|
|
+ }
|
|
|
+
|
|
|
+ const chapter = this.chapterRepository.create({
|
|
|
+ ...data,
|
|
|
+ solutionDesignId: designId,
|
|
|
+ status: 'pending',
|
|
|
+ version: 1
|
|
|
+ });
|
|
|
+
|
|
|
+ const savedChapter = await this.chapterRepository.save(chapter);
|
|
|
+
|
|
|
+ // 更新总章节数
|
|
|
+ await this.repository.increment({ id: designId }, 'totalChapters', 1);
|
|
|
+
|
|
|
+ return savedChapter;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取方案设计的所有章节
|
|
|
+ */
|
|
|
+ async getChapters(designId: number): Promise<SolutionChapter[]> {
|
|
|
+ return this.chapterRepository.find({
|
|
|
+ where: {
|
|
|
+ solutionDesignId: designId,
|
|
|
+ isDeleted: 0
|
|
|
+ },
|
|
|
+ order: { chapterNumber: 'ASC' }
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 更新章节内容
|
|
|
+ */
|
|
|
+ async updateChapter(
|
|
|
+ chapterId: number,
|
|
|
+ data: any
|
|
|
+ ): Promise<SolutionChapter> {
|
|
|
+ const chapter = await this.chapterRepository.findOne({
|
|
|
+ where: { id: chapterId, isDeleted: 0 }
|
|
|
+ });
|
|
|
+
|
|
|
+ if (!chapter) {
|
|
|
+ throw new Error('章节不存在');
|
|
|
+ }
|
|
|
+
|
|
|
+ // 如果内容有更新,增加版本号
|
|
|
+ if (data.content && data.content !== chapter.content) {
|
|
|
+ data.version = chapter.version + 1;
|
|
|
+ }
|
|
|
+
|
|
|
+ await this.chapterRepository.update(chapterId, data);
|
|
|
+ return this.chapterRepository.findOneBy({ id: chapterId }) as Promise<SolutionChapter>;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 请求AI修改建议
|
|
|
+ */
|
|
|
+ async getAIModificationSuggestions(
|
|
|
+ request: ChapterModificationRequest
|
|
|
+ ): Promise<AIModificationSuggestion> {
|
|
|
+ const prompt = `
|
|
|
+ 请对以下章节内容提供修改建议:
|
|
|
+
|
|
|
+ 原始内容:${request.currentContent}
|
|
|
+
|
|
|
+ 修改要求:${request.instructions}
|
|
|
+
|
|
|
+ 请提供:
|
|
|
+ 1. 具体的修改建议列表
|
|
|
+ 2. 修改后的完整内容
|
|
|
+ 3. 修改的置信度评分(0-1)
|
|
|
+
|
|
|
+ 请以JSON格式返回,包含suggestions、revisedContent、confidence字段。
|
|
|
+ `;
|
|
|
+
|
|
|
+ try {
|
|
|
+ const response = await this.aiService.generateText(prompt);
|
|
|
+ return JSON.parse(response);
|
|
|
+ } catch (error) {
|
|
|
+ console.error('AI修改建议生成失败:', error);
|
|
|
+ throw new Error('AI服务暂时不可用');
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 应用AI修改建议
|
|
|
+ */
|
|
|
+ async applyAIModifications(
|
|
|
+ chapterId: number,
|
|
|
+ suggestions: AIModificationSuggestion
|
|
|
+ ): Promise<SolutionChapter> {
|
|
|
+ const chapter = await this.chapterRepository.findOne({
|
|
|
+ where: { id: chapterId, isDeleted: 0 }
|
|
|
+ });
|
|
|
+
|
|
|
+ if (!chapter) {
|
|
|
+ throw new Error('章节不存在');
|
|
|
+ }
|
|
|
+
|
|
|
+ const updateData = {
|
|
|
+ content: suggestions.revisedContent,
|
|
|
+ aiSuggestions: JSON.stringify(suggestions.suggestions),
|
|
|
+ version: chapter.version + 1,
|
|
|
+ status: 'reviewing'
|
|
|
+ };
|
|
|
+
|
|
|
+ await this.chapterRepository.update(chapterId, updateData);
|
|
|
+ return this.chapterRepository.findOneBy({ id: chapterId }) as Promise<SolutionChapter>;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 更新方案设计进度
|
|
|
+ */
|
|
|
+ async updateProgress(designId: number): Promise<void> {
|
|
|
+ const completedCount = await this.chapterRepository.count({
|
|
|
+ where: {
|
|
|
+ solutionDesignId: designId,
|
|
|
+ status: 'completed',
|
|
|
+ isDeleted: 0
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ const totalCount = await this.chapterRepository.count({
|
|
|
+ where: {
|
|
|
+ solutionDesignId: designId,
|
|
|
+ isDeleted: 0
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ const progress = totalCount > 0 ? Math.round((completedCount / totalCount) * 100) : 0;
|
|
|
+
|
|
|
+ await this.repository.update(designId, {
|
|
|
+ completedChapters: completedCount,
|
|
|
+ totalChapters: totalCount,
|
|
|
+ progress,
|
|
|
+ status: progress === 100 ? 'completed' : 'reviewing'
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 生成最终文档
|
|
|
+ */
|
|
|
+ async generateFinalDocument(designId: number): Promise<Buffer> {
|
|
|
+ const design = await this.getById(designId, ['chapters']);
|
|
|
+ if (!design) {
|
|
|
+ throw new Error('方案设计不存在');
|
|
|
+ }
|
|
|
+
|
|
|
+ if (design.progress < 100) {
|
|
|
+ throw new Error('请先完成所有章节的修改');
|
|
|
+ }
|
|
|
+
|
|
|
+ // 按章节顺序组织内容
|
|
|
+ const sortedChapters = design.chapters.sort((a, b) => a.chapterNumber - b.chapterNumber);
|
|
|
+ const documentContent = sortedChapters
|
|
|
+ .map(chapter => `# ${chapter.title}\n\n${chapter.content}`)
|
|
|
+ .join('\n\n');
|
|
|
+
|
|
|
+ // 使用文档服务生成最终文档
|
|
|
+ const buffer = Buffer.from(documentContent, 'utf-8');
|
|
|
+
|
|
|
+ if (design.outputFormat === 'pdf') {
|
|
|
+ return this.documentService.convertWordToPdf(buffer, `solution_${designId}`);
|
|
|
+ }
|
|
|
+
|
|
|
+ return buffer;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 保存最终文档到文件系统
|
|
|
+ */
|
|
|
+ async saveFinalDocument(designId: number, buffer: Buffer): Promise<string> {
|
|
|
+ const design = await this.getById(designId);
|
|
|
+ if (!design) {
|
|
|
+ throw new Error('方案设计不存在');
|
|
|
+ }
|
|
|
+
|
|
|
+ const fileName = `solution_${designId}_${Date.now()}.${design.outputFormat}`;
|
|
|
+ const downloadUrl = await this.documentService.saveToMinio(buffer, fileName);
|
|
|
+
|
|
|
+ // 创建文件记录
|
|
|
+ const file = await this.fileService.createFile({
|
|
|
+ originalName: fileName,
|
|
|
+ size: buffer.length,
|
|
|
+ mimeType: design.outputFormat === 'pdf' ? 'application/pdf' :
|
|
|
+ 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
|
|
+ storagePath: downloadUrl
|
|
|
+ });
|
|
|
+
|
|
|
+ // 更新方案设计的输出文件
|
|
|
+ await this.repository.update(designId, {
|
|
|
+ outputFileId: file.id,
|
|
|
+ status: 'completed'
|
|
|
+ });
|
|
|
+
|
|
|
+ return downloadUrl;
|
|
|
+ }
|
|
|
+}
|