solution-design.service.ts 9.2 KB

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