solution-design.service.ts 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341
  1. import { GenericCrudService } from '@/server/utils/generic-crud.service';
  2. import { DataSource, Repository } from 'typeorm';
  3. import { SolutionDesign } from './solution-design.entity';
  4. import { SolutionChapter } from './solution-chapter.entity';
  5. import { DocumentService } from '@/server/modules/documents/document.service';
  6. import { FileService } from '@/server/modules/files/file.service';
  7. import { AIService } from '@/server/services/ai.service';
  8. export interface ChapterModificationRequest {
  9. chapterId: number;
  10. instructions: string;
  11. currentContent: string;
  12. }
  13. export interface AIModificationSuggestion {
  14. suggestions: string[];
  15. revisedContent: string;
  16. confidence: number;
  17. }
  18. export class SolutionDesignService extends GenericCrudService<SolutionDesign> {
  19. private chapterRepository: Repository<SolutionChapter>;
  20. private documentService: DocumentService;
  21. private fileService: FileService;
  22. private aiService: AIService;
  23. constructor(dataSource: DataSource) {
  24. super(dataSource, SolutionDesign);
  25. this.chapterRepository = dataSource.getRepository(SolutionChapter);
  26. this.documentService = new DocumentService();
  27. this.fileService = new FileService(dataSource);
  28. this.aiService = new AIService();
  29. }
  30. /**
  31. * 创建方案设计并初始化章节
  32. */
  33. async createSolutionDesign(
  34. data: any,
  35. userId: number,
  36. originalFileId?: number
  37. ): Promise<SolutionDesign> {
  38. const solutionDesign = this.repository.create({
  39. ...data,
  40. userId,
  41. originalFileId: originalFileId || null,
  42. status: 'draft',
  43. progress: 0,
  44. totalChapters: 0,
  45. completedChapters: 0
  46. });
  47. const savedDesign = await this.repository.save(solutionDesign);
  48. // 如果有原始文件,尝试自动提取章节
  49. if (originalFileId) {
  50. await this.autoExtractChapters(savedDesign.id!, originalFileId);
  51. }
  52. const result = await this.getById(savedDesign.id!, ['user', 'originalFile', 'chapters']);
  53. if (!result) {
  54. throw new Error('创建方案设计失败');
  55. }
  56. return result;
  57. }
  58. /**
  59. * 自动从原始文件提取章节
  60. */
  61. private async autoExtractChapters(designId: number, fileId: number): Promise<void> {
  62. try {
  63. const file = await this.fileService.getById(fileId);
  64. if (!file) return;
  65. // 这里可以集成文档解析逻辑,自动识别章节结构
  66. // 暂时创建默认章节
  67. const defaultChapters = [
  68. { chapterNumber: 1, title: '项目概述', content: '请填写项目概述内容' },
  69. { chapterNumber: 2, title: '需求分析', content: '请填写需求分析内容' },
  70. { chapterNumber: 3, title: '技术方案', content: '请填写技术方案内容' },
  71. { chapterNumber: 4, title: '实施计划', content: '请填写实施计划内容' },
  72. { chapterNumber: 5, title: '预算估算', content: '请填写预算估算内容' }
  73. ];
  74. for (const chapterData of defaultChapters) {
  75. const chapter = this.chapterRepository.create({
  76. ...chapterData,
  77. solutionDesignId: designId,
  78. originalContent: chapterData.content,
  79. status: 'pending'
  80. });
  81. await this.chapterRepository.save(chapter);
  82. }
  83. // 更新总章节数
  84. await this.repository.update(designId, {
  85. totalChapters: defaultChapters.length
  86. });
  87. } catch (error) {
  88. console.error('自动提取章节失败:', error);
  89. }
  90. }
  91. /**
  92. * 获取用户的方案设计列表
  93. */
  94. async getUserSolutionDesigns(
  95. userId: number,
  96. page: number = 1,
  97. pageSize: number = 10,
  98. status?: string
  99. ): Promise<[SolutionDesign[], number]> {
  100. const where: any = {
  101. userId,
  102. isDeleted: 0
  103. };
  104. if (status) {
  105. where.status = status;
  106. }
  107. return this.getList(
  108. page,
  109. pageSize,
  110. undefined,
  111. ['title', 'description'],
  112. where,
  113. ['user', 'originalFile'],
  114. { updatedAt: 'DESC' }
  115. );
  116. }
  117. /**
  118. * 添加章节到方案设计
  119. */
  120. async addChapter(
  121. designId: number,
  122. data: any
  123. ): Promise<SolutionChapter> {
  124. const design = await this.getById(designId);
  125. if (!design) {
  126. throw new Error('方案设计不存在');
  127. }
  128. const chapter = this.chapterRepository.create({
  129. ...data,
  130. solutionDesignId: designId,
  131. status: 'pending',
  132. version: 1
  133. });
  134. const savedChapter = await this.chapterRepository.save(chapter);
  135. // 更新总章节数
  136. await this.repository.increment({ id: designId }, 'totalChapters', 1);
  137. return savedChapter as unknown as SolutionChapter;
  138. }
  139. /**
  140. * 获取方案设计的所有章节
  141. */
  142. async getChapters(designId: number): Promise<SolutionChapter[]> {
  143. return this.chapterRepository.find({
  144. where: {
  145. solutionDesignId: designId,
  146. isDeleted: 0
  147. },
  148. order: { chapterNumber: 'ASC' }
  149. });
  150. }
  151. /**
  152. * 更新章节内容
  153. */
  154. async updateChapter(
  155. chapterId: number,
  156. data: any
  157. ): Promise<SolutionChapter> {
  158. const chapter = await this.chapterRepository.findOne({
  159. where: { id: chapterId, isDeleted: 0 }
  160. });
  161. if (!chapter) {
  162. throw new Error('章节不存在');
  163. }
  164. // 如果内容有更新,增加版本号
  165. if (data.content && data.content !== chapter.content) {
  166. data.version = chapter.version + 1;
  167. }
  168. await this.chapterRepository.update(chapterId, data);
  169. return this.chapterRepository.findOneBy({ id: chapterId }) as Promise<SolutionChapter>;
  170. }
  171. /**
  172. * 请求AI修改建议
  173. */
  174. async getAIModificationSuggestions(
  175. request: ChapterModificationRequest
  176. ): Promise<AIModificationSuggestion> {
  177. const prompt = `
  178. 请对以下章节内容提供修改建议:
  179. 原始内容:${request.currentContent}
  180. 修改要求:${request.instructions}
  181. 请提供:
  182. 1. 具体的修改建议列表
  183. 2. 修改后的完整内容
  184. 3. 修改的置信度评分(0-1)
  185. 请以JSON格式返回,包含suggestions、revisedContent、confidence字段。
  186. `;
  187. try {
  188. const response = await this.aiService.generateText(prompt);
  189. return JSON.parse(response);
  190. } catch (error) {
  191. console.error('AI修改建议生成失败:', error);
  192. throw new Error('AI服务暂时不可用');
  193. }
  194. }
  195. /**
  196. * 应用AI修改建议
  197. */
  198. async applyAIModifications(
  199. chapterId: number,
  200. suggestions: AIModificationSuggestion
  201. ): Promise<SolutionChapter> {
  202. const chapter = await this.chapterRepository.findOne({
  203. where: { id: chapterId, isDeleted: 0 }
  204. });
  205. if (!chapter) {
  206. throw new Error('章节不存在');
  207. }
  208. const updateData = {
  209. content: suggestions.revisedContent,
  210. aiSuggestions: JSON.stringify(suggestions.suggestions),
  211. version: chapter.version + 1,
  212. status: 'reviewing'
  213. };
  214. await this.chapterRepository.update(chapterId, updateData);
  215. return this.chapterRepository.findOneBy({ id: chapterId }) as Promise<SolutionChapter>;
  216. }
  217. /**
  218. * 更新方案设计进度
  219. */
  220. async updateProgress(designId: number): Promise<void> {
  221. const completedCount = await this.chapterRepository.count({
  222. where: {
  223. solutionDesignId: designId,
  224. status: 'completed',
  225. isDeleted: 0
  226. }
  227. });
  228. const totalCount = await this.chapterRepository.count({
  229. where: {
  230. solutionDesignId: designId,
  231. isDeleted: 0
  232. }
  233. });
  234. const progress = totalCount > 0 ? Math.round((completedCount / totalCount) * 100) : 0;
  235. await this.repository.update(designId, {
  236. completedChapters: completedCount,
  237. totalChapters: totalCount,
  238. progress,
  239. status: progress === 100 ? 'completed' : 'reviewing'
  240. });
  241. }
  242. /**
  243. * 生成最终文档
  244. */
  245. async generateFinalDocument(designId: number): Promise<Buffer> {
  246. const design = await this.getById(designId, ['chapters']);
  247. if (!design) {
  248. throw new Error('方案设计不存在');
  249. }
  250. if (design.progress < 100) {
  251. throw new Error('请先完成所有章节的修改');
  252. }
  253. // 按章节顺序组织内容
  254. const sortedChapters = design.chapters.sort((a, b) => a.chapterNumber - b.chapterNumber);
  255. const documentContent = sortedChapters
  256. .map(chapter => `# ${chapter.title}\n\n${chapter.content}`)
  257. .join('\n\n');
  258. // 使用文档服务生成最终文档
  259. const buffer = Buffer.from(documentContent, 'utf-8');
  260. if (design.outputFormat === 'pdf') {
  261. return this.documentService.convertWordToPdf(buffer, `solution_${designId}`);
  262. }
  263. return buffer;
  264. }
  265. /**
  266. * 保存最终文档到文件系统
  267. */
  268. async saveFinalDocument(designId: number, buffer: Buffer): Promise<string> {
  269. const design = await this.getById(designId);
  270. if (!design) {
  271. throw new Error('方案设计不存在');
  272. }
  273. const fileName = `solution_${designId}_${Date.now()}.${design.outputFormat}`;
  274. const downloadUrl = await this.documentService.saveToMinio(buffer, fileName);
  275. // 创建文件记录
  276. const fileResult = await this.fileService.createFile({
  277. name: fileName,
  278. size: buffer.length,
  279. type: design.outputFormat === 'pdf' ? 'application/pdf' :
  280. 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
  281. path: downloadUrl
  282. });
  283. // 更新方案设计的输出文件
  284. await this.repository.update(designId, {
  285. outputFileId: fileResult.file.id,
  286. status: 'completed'
  287. });
  288. return downloadUrl;
  289. }
  290. }