document_service.go 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261
  1. package service
  2. import (
  3. "context"
  4. "errors"
  5. "strconv"
  6. "github.com/2930134478/AI-CS/backend/models"
  7. "github.com/2930134478/AI-CS/backend/repository"
  8. "github.com/2930134478/AI-CS/backend/service/rag"
  9. )
  10. // DocumentService 文档管理服务
  11. type DocumentService struct {
  12. docRepo *repository.DocumentRepository
  13. kbRepo *repository.KnowledgeBaseRepository
  14. documentEmbeddingService *rag.DocumentEmbeddingService
  15. retrievalService *rag.RetrievalService
  16. }
  17. // NewDocumentService 创建文档服务实例
  18. func NewDocumentService(
  19. docRepo *repository.DocumentRepository,
  20. kbRepo *repository.KnowledgeBaseRepository,
  21. documentEmbeddingService *rag.DocumentEmbeddingService,
  22. retrievalService *rag.RetrievalService,
  23. ) *DocumentService {
  24. return &DocumentService{
  25. docRepo: docRepo,
  26. kbRepo: kbRepo,
  27. documentEmbeddingService: documentEmbeddingService,
  28. retrievalService: retrievalService,
  29. }
  30. }
  31. // CreateDocument 创建文档
  32. func (s *DocumentService) CreateDocument(input CreateDocumentInput) (*DocumentSummary, error) {
  33. // 验证知识库是否存在
  34. _, err := s.kbRepo.GetByID(input.KnowledgeBaseID)
  35. if err != nil {
  36. return nil, errors.New("知识库不存在")
  37. }
  38. if input.Title == "" {
  39. return nil, errors.New("文档标题不能为空")
  40. }
  41. if input.Content == "" {
  42. return nil, errors.New("文档内容不能为空")
  43. }
  44. docType := input.Type
  45. if docType == "" {
  46. docType = "document"
  47. }
  48. status := input.Status
  49. if status == "" {
  50. status = "draft"
  51. }
  52. doc := &models.Document{
  53. KnowledgeBaseID: input.KnowledgeBaseID,
  54. Title: input.Title,
  55. Content: input.Content,
  56. Summary: input.Summary,
  57. Type: docType,
  58. Status: status,
  59. EmbeddingStatus: "pending",
  60. }
  61. if err := s.docRepo.Create(doc); err != nil {
  62. return nil, err
  63. }
  64. // 异步向量化
  65. go s.embedDocumentAsync(context.Background(), doc.ID, doc.KnowledgeBaseID, doc.Content)
  66. return s.toSummary(doc), nil
  67. }
  68. // embedDocumentAsync 异步向量化文档
  69. func (s *DocumentService) embedDocumentAsync(ctx context.Context, docID uint, kbID uint, content string) {
  70. // 更新状态为处理中
  71. s.docRepo.UpdateEmbeddingStatus(docID, "processing")
  72. // 向量化
  73. err := s.documentEmbeddingService.EmbedDocument(ctx, docID, kbID, content)
  74. if err != nil {
  75. s.docRepo.UpdateEmbeddingStatus(docID, "failed")
  76. return
  77. }
  78. // 更新状态为已完成
  79. s.docRepo.UpdateEmbeddingStatus(docID, "completed")
  80. }
  81. // GetDocument 获取文档详情
  82. func (s *DocumentService) GetDocument(id uint) (*DocumentSummary, error) {
  83. doc, err := s.docRepo.GetByID(id)
  84. if err != nil {
  85. return nil, err
  86. }
  87. return s.toSummary(doc), nil
  88. }
  89. // ListDocuments 获取文档列表
  90. func (s *DocumentService) ListDocuments(knowledgeBaseID uint, page, pageSize int, keyword string, status string) (*DocumentListResult, error) {
  91. if page < 1 {
  92. page = 1
  93. }
  94. if pageSize < 1 {
  95. pageSize = 20
  96. }
  97. docs, total, err := s.docRepo.GetByKnowledgeBaseID(knowledgeBaseID, page, pageSize, keyword, status)
  98. if err != nil {
  99. return nil, err
  100. }
  101. summaries := make([]DocumentSummary, len(docs))
  102. for i, doc := range docs {
  103. summaries[i] = *s.toSummary(&doc)
  104. }
  105. totalPage := int((total + int64(pageSize) - 1) / int64(pageSize))
  106. return &DocumentListResult{
  107. Documents: summaries,
  108. Total: total,
  109. Page: page,
  110. PageSize: pageSize,
  111. TotalPage: totalPage,
  112. }, nil
  113. }
  114. // UpdateDocument 更新文档
  115. func (s *DocumentService) UpdateDocument(id uint, input UpdateDocumentInput) (*DocumentSummary, error) {
  116. doc, err := s.docRepo.GetByID(id)
  117. if err != nil {
  118. return nil, err
  119. }
  120. needReembed := false
  121. if input.Title != nil {
  122. doc.Title = *input.Title
  123. }
  124. if input.Content != nil {
  125. doc.Content = *input.Content
  126. needReembed = true // 内容变化需要重新向量化
  127. }
  128. if input.Summary != nil {
  129. doc.Summary = *input.Summary
  130. }
  131. if input.Type != nil {
  132. doc.Type = *input.Type
  133. }
  134. if input.Status != nil {
  135. doc.Status = *input.Status
  136. }
  137. if err := s.docRepo.Update(doc); err != nil {
  138. return nil, err
  139. }
  140. // 如果内容变化,重新向量化
  141. if needReembed {
  142. doc.EmbeddingStatus = "pending"
  143. s.docRepo.Update(doc)
  144. go s.embedDocumentAsync(context.Background(), doc.ID, doc.KnowledgeBaseID, doc.Content)
  145. }
  146. return s.toSummary(doc), nil
  147. }
  148. // DeleteDocument 删除文档
  149. func (s *DocumentService) DeleteDocument(id uint) error {
  150. _, err := s.docRepo.GetByID(id)
  151. if err != nil {
  152. return err
  153. }
  154. // 删除向量
  155. if err := s.documentEmbeddingService.DeleteDocumentEmbedding(context.Background(), id); err != nil {
  156. // 记录错误但不阻止删除
  157. }
  158. // 删除文档
  159. return s.docRepo.Delete(id)
  160. }
  161. // UpdateDocumentStatus 更新文档状态
  162. func (s *DocumentService) UpdateDocumentStatus(id uint, status string) error {
  163. return s.docRepo.UpdateStatus(id, status)
  164. }
  165. // PublishDocument 发布文档
  166. func (s *DocumentService) PublishDocument(id uint) error {
  167. return s.UpdateDocumentStatus(id, "published")
  168. }
  169. // UnpublishDocument 取消发布文档
  170. func (s *DocumentService) UnpublishDocument(id uint) error {
  171. return s.UpdateDocumentStatus(id, "draft")
  172. }
  173. // SearchDocuments 向量检索文档
  174. func (s *DocumentService) SearchDocuments(query string, topK int, knowledgeBaseID *uint) ([]DocumentSummary, error) {
  175. results, err := s.retrievalService.Retrieve(context.Background(), query, topK, knowledgeBaseID)
  176. if err != nil {
  177. return nil, err
  178. }
  179. // 获取文档 ID
  180. docIDs := make([]uint, 0, len(results))
  181. for _, result := range results {
  182. // 将 document_id 字符串转换为 uint
  183. docID, err := strconv.ParseUint(result.DocumentID, 10, 64)
  184. if err == nil {
  185. docIDs = append(docIDs, uint(docID))
  186. }
  187. }
  188. // 查询文档详情
  189. if len(docIDs) > 0 {
  190. docs, err := s.docRepo.GetByIDs(docIDs)
  191. if err == nil {
  192. // 保持检索结果的顺序
  193. docMap := make(map[uint]*models.Document)
  194. for i := range docs {
  195. docMap[docs[i].ID] = &docs[i]
  196. }
  197. summaries := make([]DocumentSummary, 0, len(docIDs))
  198. for _, docID := range docIDs {
  199. if doc, ok := docMap[docID]; ok {
  200. summaries = append(summaries, *s.toSummary(doc))
  201. }
  202. }
  203. return summaries, nil
  204. }
  205. }
  206. return []DocumentSummary{}, nil
  207. }
  208. // toSummary 转换为摘要
  209. func (s *DocumentService) toSummary(doc *models.Document) *DocumentSummary {
  210. return &DocumentSummary{
  211. ID: doc.ID,
  212. KnowledgeBaseID: doc.KnowledgeBaseID,
  213. Title: doc.Title,
  214. Content: doc.Content,
  215. Summary: doc.Summary,
  216. Type: doc.Type,
  217. Status: doc.Status,
  218. EmbeddingStatus: doc.EmbeddingStatus,
  219. CreatedAt: doc.CreatedAt,
  220. UpdatedAt: doc.UpdatedAt,
  221. }
  222. }