瀏覽代碼

Merge remote-tracking branch 'origin/master' into eta/2.6.4

# Conflicts:
#	utils/constants.go
Roc 3 天之前
父節點
當前提交
24cf352321
共有 49 個文件被更改,包括 4542 次插入554 次删除
  1. 84 8
      cache/llm.go
  2. 24 3
      controllers/fe_calendar/fe_calendar_matter.go
  3. 206 212
      controllers/llm/abstract.go
  4. 47 0
      controllers/llm/kb_controller.go
  5. 6 1
      controllers/llm/llm_http/response.go
  6. 367 27
      controllers/llm/question.go
  7. 422 0
      controllers/llm/rag_eta_report_abstract.go
  8. 85 1
      controllers/llm/report.go
  9. 0 2
      controllers/llm/user_chat_controller.go
  10. 84 0
      controllers/llm/wechat_platform.go
  11. 4 5
      controllers/sys_role.go
  12. 15 0
      controllers/sys_user.go
  13. 5 0
      models/business_conf.go
  14. 7 0
      models/chart_permission.go
  15. 164 0
      models/rag/ai_task.go
  16. 115 0
      models/rag/ai_task_record.go
  17. 114 0
      models/rag/article_abstract_history.go
  18. 19 4
      models/rag/question.go
  19. 86 0
      models/rag/question_history.go
  20. 5 5
      models/rag/rag_eta_report.go
  21. 292 0
      models/rag/rag_eta_report_abstract.go
  22. 9 0
      models/rag/request/rag_eta_report.go
  23. 1 1
      models/rag/request/wechat_platform.go
  24. 15 0
      models/rag/response/abstract.go
  25. 5 0
      models/rag/response/question.go
  26. 23 1
      models/rag/tag.go
  27. 82 12
      models/rag/wechat_article_abstract.go
  28. 99 0
      routers/commentsRouter.go
  29. 1 0
      routers/router.go
  30. 1 0
      services/crm_eta.go
  31. 317 0
      services/elastic/rag_eta_report_abstract.go
  32. 7 1
      services/elastic/rag_question.go
  33. 16 2
      services/elastic/wechat_article_abstract.go
  34. 334 0
      services/llm.go
  35. 9 5
      services/llm/facade/llm_service.go
  36. 517 0
      services/llm_report.go
  37. 291 24
      services/task.go
  38. 506 173
      services/wechat_platform.go
  39. 0 0
      static/imgs/ai/article/【专题报告】关税来袭黑色怎么看.md
  40. 0 0
      static/imgs/ai/article/【开源宏观】财政支出力度如何12月财政数据点评.md
  41. 0 0
      static/imgs/ai/article/巴菲特2025股东信1000字精华版来了附全文.md
  42. 2 1
      utils/config.go
  43. 13 1
      utils/constants.go
  44. 34 23
      utils/llm/eta_llm/eta_llm_client.go
  45. 1 0
      utils/redis.go
  46. 12 0
      utils/redis/cluster_redis.go
  47. 12 0
      utils/redis/standalone_redis.go
  48. 24 19
      utils/ws/session.go
  49. 60 23
      utils/ws/session_manager.go

+ 84 - 8
cache/llm.go

@@ -5,13 +5,15 @@ import (
 	"fmt"
 )
 
-type WechatArticleOp struct {
+// WechatPlatformOp
+// @Description: 微信公众号操作请求
+type WechatPlatformOp struct {
 	Source           string
 	WechatPlatformId int
 }
 
 // AddWechatArticleOpToCache
-// @Description: 将公众号文章操作加入缓存
+// @Description: 将公众号操作加入缓存
 // @param wechatPlatformId
 // @param source
 // @return bool
@@ -21,7 +23,7 @@ func AddWechatArticleOpToCache(wechatPlatformId int, source string) bool {
 		return true
 	}
 
-	record := new(WechatArticleOp)
+	record := new(WechatPlatformOp)
 	record.Source = source
 	record.WechatPlatformId = wechatPlatformId
 	if utils.Re == nil {
@@ -36,23 +38,32 @@ func AddWechatArticleOpToCache(wechatPlatformId int, source string) bool {
 	return false
 }
 
+// WechatArticleOp
+// @Description: 微信公众号文章操作
+type WechatArticleOp struct {
+	Source          string
+	WechatArticleId int
+	QuestionId      int
+}
+
 // AddWechatArticleLlmOpToCache
 // @Description: 将公众号文章llm操作加入缓存
 // @param wechatPlatformId
 // @param source
 // @return bool
-func AddWechatArticleLlmOpToCache(wechatPlatformId int, source string) bool {
+func AddWechatArticleLlmOpToCache(wechatArticleId, questionId int, source string) bool {
 	// 如果不在发布和调试模式,那么就不加入缓存
 	if !utils.InArrayByStr([]string{utils.BusinessCodeRelease, utils.BusinessCodeDebug}, utils.BusinessCode) {
 		return true
 	}
 	record := new(WechatArticleOp)
 	record.Source = source
-	record.WechatPlatformId = wechatPlatformId
+	record.WechatArticleId = wechatArticleId
+	record.QuestionId = questionId
 	if utils.Re == nil {
 		err := utils.Rc.LPush(utils.CACHE_WECHAT_PLATFORM_ARTICLE_KNOWLEDGE, record)
 
-		utils.FileLog.Info(fmt.Sprintf("将公众号文章llm操作加入缓存 加入缓存 AddWechatArticleLlmOpToCache LPush: 操作类型:%s,公众号id:%d", source, wechatPlatformId))
+		utils.FileLog.Info(fmt.Sprintf("将公众号文章llm操作加入缓存 加入缓存 AddWechatArticleLlmOpToCache LPush: 操作类型:%s,公众号稳扎id:%d", source, wechatArticleId))
 		if err != nil {
 			fmt.Println("AddWechatArticleOpToCache LPush Err:" + err.Error())
 		}
@@ -61,7 +72,7 @@ func AddWechatArticleLlmOpToCache(wechatPlatformId int, source string) bool {
 	return false
 }
 
-type RagEtaReportOpOp struct {
+type RagEtaReportOp struct {
 	Source          string
 	ReportId        int
 	ReportChapterId int
@@ -76,7 +87,7 @@ type RagEtaReportOpOp struct {
 // @param source string
 // @return bool
 func RagEtaReportOpToCache(reportId, reportChapterId int, source string) bool {
-	record := new(RagEtaReportOpOp)
+	record := new(RagEtaReportOp)
 	record.Source = source
 	record.ReportId = reportId
 	record.ReportChapterId = reportChapterId
@@ -91,3 +102,68 @@ func RagEtaReportOpToCache(reportId, reportChapterId int, source string) bool {
 	}
 	return false
 }
+
+// RagEtaReportLlmOp
+// @Description:
+type RagEtaReportLlmOp struct {
+	RagEtaReportId int
+	QuestionId     int
+	ForceGenerate  bool
+}
+
+// AddRagEtaReportLlmOpToCache
+// @Description: 将ETA报告llm操作加入缓存
+// @author: Roc
+// @datetime 2025-04-24 13:59:16
+// @param ragEtaReportId int
+// @param questionId int
+// @return bool
+func AddRagEtaReportLlmOpToCache(ragEtaReportId, questionId int, forceGenerate bool) bool {
+	// 如果不在发布和调试模式,那么就不加入缓存
+	if !utils.InArrayByStr([]string{utils.BusinessCodeRelease, utils.BusinessCodeDebug}, utils.BusinessCode) {
+		return true
+	}
+	record := new(RagEtaReportLlmOp)
+	record.RagEtaReportId = ragEtaReportId
+	record.QuestionId = questionId
+	record.ForceGenerate = forceGenerate
+	if utils.Re != nil {
+		return false
+	}
+
+	err := utils.Rc.LPush(utils.CACHE_ETA_REPORT_KNOWLEDGE_LLM, record)
+	utils.FileLog.Info(fmt.Sprintf("将eta报告llm操作加入缓存 加入缓存 RagEtaReportLlmOpToCache LPush: ETA报告id:%d", ragEtaReportId))
+	if err != nil {
+		fmt.Println("RagEtaReportLlmOpToCache LPush Err:" + err.Error())
+	}
+	return true
+}
+
+type AiTaskRecordOp struct {
+	AiTaskRecordId int
+}
+
+// AddAiTaskRecordOpToCache
+// @Description: AI任务操作调度入队列
+// @author: Roc
+// @datetime 2025-04-24 09:41:11
+// @param aiTaskRecordId int
+// @return bool
+func AddAiTaskRecordOpToCache(aiTaskRecordId int) bool {
+	// 如果不在发布和调试模式,那么就不加入缓存
+	if !utils.InArrayByStr([]string{utils.BusinessCodeRelease, utils.BusinessCodeDebug}, utils.BusinessCode) {
+		return true
+	}
+	record := new(AiTaskRecordOp)
+	record.AiTaskRecordId = aiTaskRecordId
+	if utils.Re == nil {
+		err := utils.Rc.LPush(utils.CACHE_AI_ARTICLE_ABSTRACT_LLM_TASK, record)
+
+		utils.FileLog.Info(fmt.Sprintf("将AI任务操作调度入队列 加入缓存 AddAiTaskRecordOpToCache LPush: 记录id:%d", aiTaskRecordId))
+		if err != nil {
+			fmt.Println("AddAiTaskRecordOpToCache LPush Err:" + err.Error())
+		}
+		return true
+	}
+	return false
+}

+ 24 - 3
controllers/fe_calendar/fe_calendar_matter.go

@@ -351,7 +351,8 @@ func (this *FeCalendarMatterController) PermissionList() {
 		br.ErrMsg = "获取品种列表失败, Err: " + e.Error()
 		return
 	}
-	resp := make([]*models.SimpleChartPermission, 0)
+	var resp models.FaCalendarPermissionResp
+	list := make([]*models.SimpleChartPermission, 0)
 	parentPermissions := make(map[int][]*models.SimpleChartPermission, 0)
 	for _, v := range permissions {
 		if v.ParentId > 0 {
@@ -361,12 +362,32 @@ func (this *FeCalendarMatterController) PermissionList() {
 			parentPermissions[v.ParentId] = append(parentPermissions[v.ParentId], models.FormatChartPermission2Simple(v))
 			continue
 		}
-		resp = append(resp, models.FormatChartPermission2Simple(v))
+		list = append(list, models.FormatChartPermission2Simple(v))
 	}
-	for _, v := range resp {
+	for _, v := range list {
 		v.Children = parentPermissions[v.ChartPermissionId]
 	}
+	lastEditPermissionId := 0
+	lastEditPermissionName := ""
+	// 查询最近被编辑过的品种ID
+	matterOb := new(fe_calendar.FeCalendarMatter)
+	cond := ""
+	pars := make([]interface{}, 0)
+	order := fmt.Sprintf(`%s Desc`, fe_calendar.FeCalendarMatterCols.ModifyTime)
+	matter, e := matterOb.GetItemByCondition(cond, pars, order)
+	if e != nil && e.Error() != utils.ErrNoRow() {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取事项列表失败, Err: " + e.Error()
+		return
+	}
+	if e == nil {
+		lastEditPermissionId = matter.ChartPermissionId
+		lastEditPermissionName = matter.ChartPermissionName
+	}
 
+	resp.CheckedPermissionId = lastEditPermissionId
+	resp.CheckedPermissionName = lastEditPermissionName
+	resp.List = list
 	br.Data = resp
 	br.Ret = 200
 	br.Success = true

+ 206 - 212
controllers/llm/abstract.go

@@ -13,6 +13,8 @@ import (
 	"eta/eta_api/utils"
 	"fmt"
 	"github.com/rdlucklib/rdluck_tools/paging"
+	"strconv"
+	"strings"
 )
 
 // AbstractController
@@ -45,7 +47,26 @@ func (c *AbstractController) List() {
 	pageSize, _ := c.GetInt("PageSize")
 	currentIndex, _ := c.GetInt("CurrentIndex")
 	keyWord := c.GetString("KeyWord")
-	tagId, _ := c.GetInt("TagId")
+	tagIdStr := c.GetString("TagId")
+	questionId, _ := c.GetInt("QuestionId")
+
+	tagIdList := make([]int, 0)
+	if tagIdStr != `` {
+		tagIdStrList := strings.Split(tagIdStr, `,`)
+		for _, v := range tagIdStrList {
+			if v == `0` {
+				continue
+			}
+
+			tagId, tmpErr := strconv.Atoi(v)
+			if tmpErr != nil {
+				br.Msg = "标签ID有误"
+				br.ErrMsg = fmt.Sprintf("标签ID有误, %s", v)
+				return
+			}
+			tagIdList = append(tagIdList, tagId)
+		}
+	}
 
 	var startSize int
 	if pageSize <= 0 {
@@ -57,7 +78,7 @@ func (c *AbstractController) List() {
 	startSize = utils.StartIndex(currentIndex, pageSize)
 
 	// 获取列表
-	total, viewList, err := getAbstractList(keyWord, tagId, startSize, pageSize)
+	total, viewList, err := getAbstractList(keyWord, tagIdList, questionId, startSize, pageSize)
 	if err != nil {
 		br.Msg = "获取失败"
 		br.ErrMsg = "获取失败,Err:" + err.Error()
@@ -76,60 +97,91 @@ func (c *AbstractController) List() {
 	br.Data = resp
 }
 
-func getAbstractList(keyWord string, tagId int, startSize, pageSize int) (total int, viewList []rag.WechatArticleAbstractView, err error) {
+func getAbstractList(keyWord string, tagList []int, questionId int, startSize, pageSize int) (total int, viewList []rag.WechatArticleAbstractView, err error) {
+	//if keyWord == `` {
+	//	var condition string
+	//	var pars []interface{}
+	//	condition += fmt.Sprintf(` AND c.%s = ?`, rag.WechatPlatformColumns.Enabled)
+	//	pars = append(pars, 1)
+	//
+	//	if keyWord != "" {
+	//		condition += fmt.Sprintf(` AND a.%s like ?`, rag.WechatArticleAbstractColumns.Content)
+	//		pars = append(pars, `%`+keyWord+`%`)
+	//	}
+	//
+	//	if tagId > 0 {
+	//		condition += fmt.Sprintf(` AND d.%s = ?`, rag.WechatPlatformTagMappingColumns.TagID)
+	//		pars = append(pars, tagId)
+	//	}
+	//
+	//	obj := new(rag.WechatArticleAbstract)
+	//	tmpTotal, list, tmpErr := obj.GetPageListByTagAndPlatformCondition(condition, pars, startSize, pageSize)
+	//	if tmpErr != nil {
+	//		err = tmpErr
+	//		return
+	//	}
+	//	total = tmpTotal
+	//	viewList = obj.WechatArticleAbstractItem(list)
+	//} else {
+	//	sortMap := map[string]string{
+	//		//"ModifyTime":              "desc",
+	//		//"WechatArticleAbstractId": "desc",
+	//	}
+	//
+	//	obj := new(rag.WechatPlatform)
+	//	platformList, tmpErr := obj.GetListByCondition(` AND enabled = 1 `, []interface{}{}, 0, 100000)
+	//	if tmpErr != nil {
+	//		err = tmpErr
+	//		return
+	//	}
+	//	platformIdList := make([]int, 0)
+	//	for _, v := range platformList {
+	//		platformIdList = append(platformIdList, v.WechatPlatformId)
+	//	}
+	//	tagList := make([]int, 0)
+	//	if tagId > 0 {
+	//		tagList = append(tagList, tagId)
+	//	}
+	//	tmpTotal, list, tmpErr := elastic.WechatArticleAbstractEsSearch(keyWord, tagList, platformIdList, startSize, pageSize, sortMap)
+	//	if tmpErr != nil {
+	//		err = tmpErr
+	//		return
+	//	}
+	//	total = int(tmpTotal)
+	//	if list != nil && len(list) > 0 {
+	//		viewList = list[0].ToViewList(list)
+	//	}
+	//}
+
+	sortMap := map[string]string{
+		//"ModifyTime":              "desc",
+		//"WechatArticleAbstractId": "desc",
+	}
 	if keyWord == `` {
-		var condition string
-		var pars []interface{}
-		condition += fmt.Sprintf(` AND c.%s = ?`, rag.WechatPlatformColumns.Enabled)
-		pars = append(pars, 1)
-
-		if keyWord != "" {
-			condition += fmt.Sprintf(` AND a.%s like ?`, rag.WechatArticleAbstractColumns.Content)
-			pars = append(pars, `%`+keyWord+`%`)
-		}
-
-		if tagId > 0 {
-			condition += fmt.Sprintf(` AND d.%s = ?`, rag.WechatPlatformTagMappingColumns.TagID)
-			pars = append(pars, tagId)
-		}
-
-		obj := new(rag.WechatArticleAbstract)
-		tmpTotal, list, tmpErr := obj.GetPageListByTagAndPlatformCondition(condition, pars, startSize, pageSize)
-		if tmpErr != nil {
-			err = tmpErr
-			return
-		}
-		total = tmpTotal
-		viewList = obj.WechatArticleAbstractItem(list)
-	} else {
-		sortMap := map[string]string{
-			//"ModifyTime":              "desc",
+		sortMap = map[string]string{
+			"CreateTime": "desc",
 			//"WechatArticleAbstractId": "desc",
 		}
+	}
 
-		obj := new(rag.WechatPlatform)
-		platformList, tmpErr := obj.GetListByCondition(` AND enabled = 1 `, []interface{}{}, 0, 100000)
-		if tmpErr != nil {
-			err = tmpErr
-			return
-		}
-		platformIdList := make([]int, 0)
-		for _, v := range platformList {
-			platformIdList = append(platformIdList, v.WechatPlatformId)
-		}
-		tagList := make([]int, 0)
-		if tagId > 0 {
-			tagList = append(tagList, tagId)
-		}
-		tmpTotal, list, tmpErr := elastic.WechatArticleAbstractEsSearch(keyWord, tagList, platformIdList, startSize, pageSize, sortMap)
-		if tmpErr != nil {
-			err = tmpErr
-			return
-		}
-		total = int(tmpTotal)
-		if list != nil && len(list) > 0 {
-			viewList = list[0].ToViewList(list)
-		}
+	obj := new(rag.WechatPlatform)
+	platformList, tmpErr := obj.GetListByCondition(` AND enabled = 1 `, []interface{}{}, 0, 100000)
+	if tmpErr != nil {
+		err = tmpErr
+		return
+	}
+	platformIdList := make([]int, 0)
+	for _, v := range platformList {
+		platformIdList = append(platformIdList, v.WechatPlatformId)
+	}
+	tmpTotal, list, tmpErr := elastic.WechatArticleAbstractEsSearch(keyWord, tagList, platformIdList, questionId, startSize, pageSize, sortMap)
+	if tmpErr != nil {
+		err = tmpErr
+		return
+	}
+	total = int(tmpTotal)
+	if list != nil && len(list) > 0 {
+		viewList = list[0].ToViewList(list)
 	}
 
 	return
@@ -160,82 +212,14 @@ func (c *AbstractController) Del() {
 		return
 	}
 
-	vectorKeyList := make([]string, 0)
-	wechatArticleAbstractIdList := make([]int, 0)
-
-	obj := rag.WechatArticleAbstract{}
-
-	if !req.IsSelectAll {
-		list, err := obj.GetByIdList(req.WechatArticleAbstractIdList)
-		if err != nil {
-			br.Msg = "修改失败"
-			br.ErrMsg = "修改失败,查找问题失败,Err:" + err.Error()
-			if utils.IsErrNoRow(err) {
-				br.Msg = "问题不存在"
-				br.IsSendEmail = false
-			}
-			return
-		}
-		if len(list) > 0 {
-			for _, v := range list {
-				// 有加入到向量库,那么就加入到待删除的向量库list中
-				if v.VectorKey != `` {
-					vectorKeyList = append(vectorKeyList, v.VectorKey)
-				}
-				wechatArticleAbstractIdList = append(wechatArticleAbstractIdList, v.WechatArticleAbstractId)
-			}
-		}
-	} else {
-		notIdMap := make(map[int]bool)
-		for _, v := range req.NotWechatArticleAbstractIdList {
-			notIdMap[v] = true
-		}
-
-		_, list, err := getAbstractList(req.KeyWord, req.TagId, 0, 100000)
-		if err != nil {
-			br.Msg = "修改失败"
-			br.ErrMsg = "修改失败,查找问题失败,Err:" + err.Error()
-			if utils.IsErrNoRow(err) {
-				br.Msg = "问题不存在"
-				br.IsSendEmail = false
-			}
-			return
-		}
-		if len(list) > 0 {
-			for _, v := range list {
-				if notIdMap[v.WechatArticleAbstractId] {
-					continue
-				}
-				// 有加入到向量库,那么就加入到待删除的向量库list中
-				if v.VectorKey != `` {
-					vectorKeyList = append(vectorKeyList, v.VectorKey)
-				}
-				wechatArticleAbstractIdList = append(wechatArticleAbstractIdList, v.WechatArticleAbstractId)
-			}
-		}
-	}
-
-	// 删除向量库
-	err = services.DelLlmDoc(vectorKeyList, wechatArticleAbstractIdList)
-	if err != nil {
-		br.Msg = "删除失败"
-		br.ErrMsg = "删除向量库失败,Err:" + err.Error()
-		return
-	}
-
 	// 删除摘要
-	err = obj.DelByIdList(wechatArticleAbstractIdList)
+	err = services.DelWechatArticleAbstract(req.WechatArticleAbstractIdList)
 	if err != nil {
 		br.Msg = "删除失败"
 		br.ErrMsg = "删除失败,Err:" + err.Error()
 		return
 	}
 
-	// 删除es数据
-	for _, wechatArticleAbstractId := range wechatArticleAbstractIdList {
-		go services.DelEsWechatArticleAbstract(wechatArticleAbstractId)
-	}
-
 	br.Ret = 200
 	br.Success = true
 	br.Msg = `删除成功`
@@ -270,57 +254,76 @@ func (c *AbstractController) VectorDel() {
 	wechatArticleAbstractIdList := make([]int, 0)
 
 	obj := rag.WechatArticleAbstract{}
-
-	if !req.IsSelectAll {
-		list, err := obj.GetByIdList(req.WechatArticleAbstractIdList)
-		if err != nil {
-			br.Msg = "修改失败"
-			br.ErrMsg = "修改失败,查找问题失败,Err:" + err.Error()
-			if utils.IsErrNoRow(err) {
-				br.Msg = "问题不存在"
-				br.IsSendEmail = false
-			}
-			return
-		}
-		if len(list) > 0 {
-			for _, v := range list {
-				// 有加入到向量库,那么就加入到待删除的向量库list中
-				if v.VectorKey != `` {
-					vectorKeyList = append(vectorKeyList, v.VectorKey)
-				}
-				wechatArticleAbstractIdList = append(wechatArticleAbstractIdList, v.WechatArticleAbstractId)
-			}
-		}
-	} else {
-		notIdMap := make(map[int]bool)
-		for _, v := range req.NotWechatArticleAbstractIdList {
-			notIdMap[v] = true
-		}
-		_, list, err := getAbstractList(req.KeyWord, req.TagId, 0, 100000)
-		if err != nil {
-			br.Msg = "修改失败"
-			br.ErrMsg = "修改失败,查找问题失败,Err:" + err.Error()
-			if utils.IsErrNoRow(err) {
-				br.Msg = "问题不存在"
-				br.IsSendEmail = false
-			}
-			return
+	list, err := obj.GetByIdList(req.WechatArticleAbstractIdList)
+	if err != nil {
+		br.Msg = "修改失败"
+		br.ErrMsg = "修改失败,查找问题失败,Err:" + err.Error()
+		if utils.IsErrNoRow(err) {
+			br.Msg = "问题不存在"
+			br.IsSendEmail = false
 		}
-		if len(list) > 0 {
-			for _, v := range list {
-				if notIdMap[v.WechatArticleAbstractId] {
-					continue
-				}
-
-				// 有加入到向量库,那么就加入到待删除的向量库list中
-				if v.VectorKey != `` {
-					vectorKeyList = append(vectorKeyList, v.VectorKey)
-				}
-				wechatArticleAbstractIdList = append(wechatArticleAbstractIdList, v.WechatArticleAbstractId)
+		return
+	}
+	if len(list) > 0 {
+		for _, v := range list {
+			// 有加入到向量库,那么就加入到待删除的向量库list中
+			if v.VectorKey != `` {
+				vectorKeyList = append(vectorKeyList, v.VectorKey)
 			}
+			wechatArticleAbstractIdList = append(wechatArticleAbstractIdList, v.WechatArticleAbstractId)
 		}
 	}
 
+	//if !req.IsSelectAll {
+	//	list, err := obj.GetByIdList(req.WechatArticleAbstractIdList)
+	//	if err != nil {
+	//		br.Msg = "修改失败"
+	//		br.ErrMsg = "修改失败,查找问题失败,Err:" + err.Error()
+	//		if utils.IsErrNoRow(err) {
+	//			br.Msg = "问题不存在"
+	//			br.IsSendEmail = false
+	//		}
+	//		return
+	//	}
+	//	if len(list) > 0 {
+	//		for _, v := range list {
+	//			// 有加入到向量库,那么就加入到待删除的向量库list中
+	//			if v.VectorKey != `` {
+	//				vectorKeyList = append(vectorKeyList, v.VectorKey)
+	//			}
+	//			wechatArticleAbstractIdList = append(wechatArticleAbstractIdList, v.WechatArticleAbstractId)
+	//		}
+	//	}
+	//} else {
+	//	notIdMap := make(map[int]bool)
+	//	for _, v := range req.NotWechatArticleAbstractIdList {
+	//		notIdMap[v] = true
+	//	}
+	//	_, list, err := getAbstractList(req.KeyWord, req.TagId, 0, 100000)
+	//	if err != nil {
+	//		br.Msg = "修改失败"
+	//		br.ErrMsg = "修改失败,查找问题失败,Err:" + err.Error()
+	//		if utils.IsErrNoRow(err) {
+	//			br.Msg = "问题不存在"
+	//			br.IsSendEmail = false
+	//		}
+	//		return
+	//	}
+	//	if len(list) > 0 {
+	//		for _, v := range list {
+	//			if notIdMap[v.WechatArticleAbstractId] {
+	//				continue
+	//			}
+	//
+	//			// 有加入到向量库,那么就加入到待删除的向量库list中
+	//			if v.VectorKey != `` {
+	//				vectorKeyList = append(vectorKeyList, v.VectorKey)
+	//			}
+	//			wechatArticleAbstractIdList = append(wechatArticleAbstractIdList, v.WechatArticleAbstractId)
+	//		}
+	//	}
+	//}
+
 	// 删除摘要库
 	err = services.DelLlmDoc(vectorKeyList, wechatArticleAbstractIdList)
 	if err != nil {
@@ -364,57 +367,48 @@ func (c *AbstractController) AddVector() {
 		return
 	}
 
-	wechatArticleAbstractIdList := make([]int, 0)
-
 	obj := rag.WechatArticleAbstract{}
-
-	if !req.IsSelectAll {
-		list, err := obj.GetByIdList(req.WechatArticleAbstractIdList)
-		if err != nil {
-			br.Msg = "修改失败"
-			br.ErrMsg = "修改失败,查找问题失败,Err:" + err.Error()
-			if utils.IsErrNoRow(err) {
-				br.Msg = "问题不存在"
-				br.IsSendEmail = false
-			}
-			return
-		}
-		if len(list) > 0 {
-			for _, v := range list {
-				wechatArticleAbstractIdList = append(wechatArticleAbstractIdList, v.WechatArticleAbstractId)
-			}
-		}
-	} else {
-		notIdMap := make(map[int]bool)
-		for _, v := range req.NotWechatArticleAbstractIdList {
-			notIdMap[v] = true
-		}
-
-		_, list, err := getAbstractList(req.KeyWord, req.TagId, 0, 100000)
-		if err != nil {
-			br.Msg = "修改失败"
-			br.ErrMsg = "修改失败,查找问题失败,Err:" + err.Error()
-			if utils.IsErrNoRow(err) {
-				br.Msg = "问题不存在"
-				br.IsSendEmail = false
-			}
-			return
-		}
-		if len(list) > 0 {
-			for _, v := range list {
-				if notIdMap[v.WechatArticleAbstractId] {
-					continue
-				}
-				wechatArticleAbstractIdList = append(wechatArticleAbstractIdList, v.WechatArticleAbstractId)
-			}
+	list, err := obj.GetByIdList(req.WechatArticleAbstractIdList)
+	if err != nil {
+		br.Msg = "修改失败"
+		br.ErrMsg = "修改失败,查找问题失败,Err:" + err.Error()
+		if utils.IsErrNoRow(err) {
+			br.Msg = "问题不存在"
+			br.IsSendEmail = false
 		}
+		return
 	}
-
-	for _, wechatArticleAbstractId := range wechatArticleAbstractIdList {
-		cache.AddWechatArticleLlmOpToCache(wechatArticleAbstractId, ``)
+	for _, v := range list {
+		cache.AddWechatArticleLlmOpToCache(v.WechatArticleId, v.QuestionId, ``)
 	}
 
 	br.Ret = 200
 	br.Success = true
 	br.Msg = `添加向量库中,请稍后查看`
 }
+
+//func init() {
+//	//微信文章
+//	//{
+//	//	obj := rag.WechatArticle{}
+//	//	item, tmpErr := obj.GetById(1722)
+//	//	if tmpErr != nil {
+//	//		// 找不到就处理失败
+//	//		return
+//	//	}
+//	//	services.GenerateWechatArticleAbstract(item, false)
+//	//}
+//
+//	// ETA报告
+//	{
+//		obj := rag.RagEtaReport{}
+//		item, tmpErr := obj.GetById(1)
+//		if tmpErr != nil {
+//			// 找不到就处理失败
+//			return
+//		}
+//		services.GenerateRagEtaReportAbstract(item, false)
+//	}
+//
+//	fmt.Println("结束")
+//}

+ 47 - 0
controllers/llm/kb_controller.go

@@ -3,6 +3,7 @@ package llm
 import (
 	"encoding/json"
 	"eta/eta_api/controllers"
+	"eta/eta_api/controllers/llm/llm_http"
 	"eta/eta_api/models"
 	"eta/eta_api/services/llm/facade"
 )
@@ -49,3 +50,49 @@ func (kbctrl *KbController) SearchDocs() {
 	br.Success = true
 	br.Msg = "获取成功"
 }
+
+// KnowledgeList
+// @Title 获取知识库列表
+// @Description  获取知识库列表
+// @Success 101 {object} response.ListResp
+// @router /knowledge/list [get]
+func (ucCtrl *UserChatController) KnowledgeList() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		ucCtrl.Data["json"] = br
+		ucCtrl.ServeJSON()
+	}()
+	sysUser := ucCtrl.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+	// 获取基础配置, 若未配置则直接返回
+	conf, e := models.GetBusinessConf()
+	if e != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取基础配置失败, Err: " + e.Error()
+		return
+	}
+
+	list := make([]llm_http.KnowledgeList, 0)
+	if conf[models.KnowledgeBaseName] != "" {
+		list = append(list, llm_http.KnowledgeList{
+			KnowledgeName: conf[models.KnowledgeBaseName],
+			Name:          "弘则公共知识库",
+		})
+	}
+	if conf[models.PrivateKnowledgeBaseName] != "" {
+		list = append(list, llm_http.KnowledgeList{
+			KnowledgeName: conf[models.PrivateKnowledgeBaseName],
+			Name:          "弘则私有知识库",
+		})
+	}
+
+	br.Data = list
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取知识库列表成功"
+}

+ 6 - 1
controllers/llm/llm_http/response.go

@@ -18,7 +18,7 @@ type UserChatAddResp struct {
 
 type AIGCResp struct {
 	Promote Content
-	Answer Content
+	Answer  Content
 }
 
 type Content struct {
@@ -26,3 +26,8 @@ type Content struct {
 	Content  string
 	SendTime string
 }
+
+type KnowledgeList struct {
+	Name          string
+	KnowledgeName string
+}

+ 367 - 27
controllers/llm/question.go

@@ -17,7 +17,7 @@ import (
 )
 
 // QuestionController
-// @Description: 问题库管理
+// @Description: 提示词库管理
 type QuestionController struct {
 	controllers.BaseAuthController
 }
@@ -28,6 +28,7 @@ type QuestionController struct {
 // @Param   PageSize   query   int  true       "每页数据条数"
 // @Param   CurrentIndex   query   int  true       "当前页页码,从1开始"
 // @Param   KeyWord   query   string  true       "搜索关键词"
+// @Param   IsQueryDefault   query   bool  true       "是否默认:true,或者false"
 // @Success 200 {object} []*rag.QuestionListListResp
 // @router /question/list [get]
 func (c *QuestionController) List() {
@@ -46,6 +47,7 @@ func (c *QuestionController) List() {
 	pageSize, _ := c.GetInt("PageSize")
 	currentIndex, _ := c.GetInt("CurrentIndex")
 	keyWord := c.GetString("KeyWord")
+	isQueryDefault, _ := c.GetBool("IsQueryDefault")
 
 	var startSize int
 	if pageSize <= 0 {
@@ -64,9 +66,15 @@ func (c *QuestionController) List() {
 		var pars []interface{}
 
 		if keyWord != "" {
-			condition += fmt.Sprintf(` AND %s like ?`, rag.QuestionColumns.QuestionContent)
+			condition += fmt.Sprintf(` AND %s like ? `, rag.QuestionColumns.QuestionContent)
 			pars = append(pars, `%`+keyWord+`%`)
 		}
+
+		if isQueryDefault {
+			condition += fmt.Sprintf(` AND %s = ? `, rag.QuestionColumns.IsDefault)
+			pars = append(pars, 1)
+		}
+
 		obj := new(rag.Question)
 		tmpTotal, list, err := obj.GetPageListByCondition(condition, pars, startSize, pageSize)
 		if err != nil {
@@ -84,7 +92,7 @@ func (c *QuestionController) List() {
 			//"ArticleCreateTime": "desc",
 			//"WechatArticleId":   "desc",
 		}
-		tmpTotal, list, err := elastic.RagQuestionEsSearch(keyWord, startSize, pageSize, sortMap)
+		tmpTotal, list, err := elastic.RagQuestionEsSearch(keyWord, isQueryDefault, startSize, pageSize, sortMap)
 		if err != nil {
 			br.Msg = "获取失败"
 			br.ErrMsg = "获取失败,Err:" + err.Error()
@@ -114,6 +122,7 @@ func (c *QuestionController) List() {
 // @Param   PageSize   query   int  true       "每页数据条数"
 // @Param   CurrentIndex   query   int  true       "当前页页码,从1开始"
 // @Param   KeyWord   query   string  true       "搜索关键词"
+// @Param   IsQueryDefault   query   bool  true       "是否默认:true,或者false"
 // @Success 200 {object} []*rag.QuestionListListResp
 // @router /question/title/list [get]
 func (c *QuestionController) TitleList() {
@@ -132,6 +141,7 @@ func (c *QuestionController) TitleList() {
 	pageSize, _ := c.GetInt("PageSize")
 	currentIndex, _ := c.GetInt("CurrentIndex")
 	keyWord := c.GetString("KeyWord")
+	isQueryDefault, _ := c.GetBool("IsQueryDefault")
 
 	var startSize int
 	if pageSize <= 0 {
@@ -153,6 +163,12 @@ func (c *QuestionController) TitleList() {
 			condition += fmt.Sprintf(` AND %s like ?`, rag.QuestionColumns.QuestionContent)
 			pars = append(pars, `%`+keyWord+`%`)
 		}
+
+		if isQueryDefault {
+			condition += fmt.Sprintf(` AND %s = ? `, rag.QuestionColumns.IsDefault)
+			pars = append(pars, 1)
+		}
+
 		obj := new(rag.Question)
 		tmpTotal, list, err := obj.GetTitlePageListByCondition(condition, pars, startSize, pageSize)
 		if err != nil {
@@ -170,16 +186,38 @@ func (c *QuestionController) TitleList() {
 			//"ArticleCreateTime": "desc",
 			//"WechatArticleId":   "desc",
 		}
-		tmpTotal, list, err := elastic.RagQuestionEsSearch(keyWord, startSize, pageSize, sortMap)
+		tmpTotal, esList, err := elastic.RagQuestionEsSearch(keyWord, isQueryDefault, startSize, pageSize, sortMap)
 		if err != nil {
 			br.Msg = "获取失败"
 			br.ErrMsg = "获取失败,Err:" + err.Error()
 			return
 		}
 		total = int(tmpTotal)
-		if list != nil && len(list) > 0 {
-			viewList = list[0].ToViewList(list)
+
+		if total > 0 {
+			questionIdList := make([]int, 0)
+			for _, v := range esList {
+				questionIdList = append(questionIdList, v.QuestionId)
+			}
+			var condition string
+			var pars []interface{}
+
+			condition += fmt.Sprintf(` AND %s in (?)`, rag.QuestionColumns.QuestionId)
+			pars = append(pars, `%`+keyWord+`%`)
+
+			obj := new(rag.Question)
+			tmpTotal, list, err := obj.GetTitlePageListByCondition(condition, pars, startSize, pageSize)
+			if err != nil {
+				br.Msg = "获取失败"
+				br.ErrMsg = "获取失败,Err:" + err.Error()
+				return
+			}
+			if list != nil && len(list) > 0 {
+				viewList = list[0].ListToViewList(list)
+			}
+			total = tmpTotal
 		}
+
 	}
 
 	page := paging.GetPaging(currentIndex, pageSize, total)
@@ -197,7 +235,7 @@ func (c *QuestionController) TitleList() {
 // Detail
 // @Title 列表
 // @Description 列表
-// @Param   QuestionId   query   int  true       "问题id"
+// @Param   QuestionId   query   int  true       "提示词id"
 // @Success 200 {object} []*rag.QuestionListListResp
 // @router /question/detail [get]
 func (c *QuestionController) Detail() {
@@ -215,8 +253,8 @@ func (c *QuestionController) Detail() {
 	}
 	questionId, _ := c.GetInt("QuestionId")
 	if questionId <= 0 {
-		br.Msg = "问题id不能为空"
-		br.ErrMsg = "问题id不能为空"
+		br.Msg = "提示词id不能为空"
+		br.ErrMsg = "提示词id不能为空"
 		return
 	}
 
@@ -236,8 +274,8 @@ func (c *QuestionController) Detail() {
 }
 
 // Add
-// @Title 新增问题
-// @Description 新增问题
+// @Title 新增提示词
+// @Description 新增提示词
 // @Param	request	body request.AddQuestionReq true "type json string"
 // @Success 200 Ret=200 新增成功
 // @router /question/add [post]
@@ -256,14 +294,14 @@ func (c *QuestionController) Add() {
 	}
 	req.Content = strings.TrimSpace(req.Content)
 	if req.Content == "" {
-		br.Msg = "请输入问题"
+		br.Msg = "请输入提示词"
 		br.IsSendEmail = false
 		return
 	}
 	//obj := rag.Question{}
 	//_, err = obj.GetByCondition(` AND question_content = ? `, []interface{}{req.Content})
 	//if err == nil {
-	//	br.Msg = "问题已入库,请不要重复添加"
+	//	br.Msg = "提示词已入库,请不要重复添加"
 	//	br.IsSendEmail = false
 	//	return
 	//}
@@ -293,8 +331,8 @@ func (c *QuestionController) Add() {
 }
 
 // Edit
-// @Title 编辑问题
-// @Description 编辑问题
+// @Title 编辑提示词
+// @Description 编辑提示词
 // @Param	request	body request.EditQuestionReq true "type json string"
 // @Success 200 Ret=200 新增成功
 // @router /question/edit [post]
@@ -304,6 +342,7 @@ func (c *QuestionController) Edit() {
 		c.Data["json"] = br
 		c.ServeJSON()
 	}()
+
 	var req request.EditQuestionReq
 	err := json.Unmarshal(c.Ctx.Input.RequestBody, &req)
 	if err != nil {
@@ -312,13 +351,13 @@ func (c *QuestionController) Edit() {
 		return
 	}
 	if req.QuestionId <= 0 {
-		br.Msg = "问题id不能为空"
+		br.Msg = "提示词id不能为空"
 		br.IsSendEmail = false
 		return
 	}
 	req.Content = strings.TrimSpace(req.Content)
 	if req.Content == "" {
-		br.Msg = "请输入问题"
+		br.Msg = "请输入提示词"
 		br.IsSendEmail = false
 		return
 	}
@@ -327,17 +366,37 @@ func (c *QuestionController) Edit() {
 	item, err := obj.GetByID(req.QuestionId)
 	if err != nil {
 		br.Msg = "修改失败"
-		br.ErrMsg = "修改失败,查找问题失败,Err:" + err.Error()
+		br.ErrMsg = "修改失败,查找提示词失败,Err:" + err.Error()
 		if utils.IsErrNoRow(err) {
-			br.Msg = "问题不存在"
+			br.Msg = "提示词不存在"
 			br.IsSendEmail = false
 		}
 		return
 	}
+
+	// 编辑提示词:
+	if item.IsDefault == 1 {
+		total, err := services.GetNotFinishGenerateAbstractTaskNumByQuestionId(item.QuestionId)
+		if err != nil {
+			br.Msg = "修改失败"
+			br.ErrMsg = "权限校验失败,Err:" + err.Error()
+			return
+		}
+		if total > 0 {
+			br.Msg = "当前提示词正在生成摘要,请稍后再修改"
+			return
+		}
+	}
+
+	// 添加问题的历史记录
+	rag.AddQuestionHistoryByQuestion(item)
+
 	item.QuestionTitle = utils.GetFirstNChars(req.Content, 20)
 	item.QuestionContent = req.Content
+	item.Version++
+	item.GenerateStatus = `undo`
 	item.ModifyTime = time.Now()
-	err = item.Update([]string{"question_title", "question_content", "modify_time"})
+	err = item.Update([]string{"question_title", "question_content", `version`, `generate_status`, "modify_time"})
 	if err != nil {
 		br.Msg = "修改失败"
 		br.ErrMsg = "修改失败,Err:" + err.Error()
@@ -348,12 +407,12 @@ func (c *QuestionController) Edit() {
 
 	br.Ret = 200
 	br.Success = true
-	br.Msg = `添加成功`
+	br.Msg = `修改成功`
 }
 
 // Del
-// @Title 删除问题
-// @Description 删除问题
+// @Title 删除提示词
+// @Description 删除提示词
 // @Param	request	body request.EditQuestionReq true "type json string"
 // @Success 200 Ret=200 新增成功
 // @router /question/del [post]
@@ -371,7 +430,7 @@ func (c *QuestionController) Del() {
 		return
 	}
 	if req.QuestionId <= 0 {
-		br.Msg = "问题id不能为空"
+		br.Msg = "提示词id不能为空"
 		br.IsSendEmail = false
 		return
 	}
@@ -380,13 +439,23 @@ func (c *QuestionController) Del() {
 	item, err := obj.GetByID(req.QuestionId)
 	if err != nil {
 		br.Msg = "修改失败"
-		br.ErrMsg = "修改失败,查找问题失败,Err:" + err.Error()
+		br.ErrMsg = "修改失败,查找提示词失败,Err:" + err.Error()
 		if utils.IsErrNoRow(err) {
-			br.Msg = "问题不存在"
+			br.Msg = "提示词不存在"
 			br.IsSendEmail = false
 		}
 		return
 	}
+
+	// 删除提示词:若删除默认提示词,提示:当前提示词不允许删除;若删除非默认提示词:提示删除成功(项目eta4.0,时间:2025-4-16 17:39:38)
+	if item.IsDefault == 1 {
+		br.Msg = "当前提示词不允许删除!"
+		return
+	}
+
+	// 添加问题的历史记录
+	rag.AddQuestionHistoryByQuestion(item)
+
 	err = item.Del()
 	if err != nil {
 		br.Msg = "删除失败"
@@ -402,8 +471,279 @@ func (c *QuestionController) Del() {
 	br.Msg = `删除成功`
 }
 
+// SetDefault
+// @Title 设置默认提示词
+// @Description 设置默认提示词
+// @Param	request	body request.EditQuestionReq true "type json string"
+// @Success 200 Ret=200 设置成功
+// @router /question/default/set [post]
+func (c *QuestionController) SetDefault() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		c.Data["json"] = br
+		c.ServeJSON()
+	}()
+	var req request.EditQuestionReq
+	err := json.Unmarshal(c.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	if req.QuestionId <= 0 {
+		br.Msg = "提示词id不能为空"
+		br.IsSendEmail = false
+		return
+	}
+
+	obj := rag.Question{}
+	item, err := obj.GetByID(req.QuestionId)
+	if err != nil {
+		br.Msg = "修改失败"
+		br.ErrMsg = "修改失败,查找提示词失败,Err:" + err.Error()
+		if utils.IsErrNoRow(err) {
+			br.Msg = "提示词不存在"
+			br.IsSendEmail = false
+		}
+		return
+	}
+
+	// 判断是否正在取消默认提示词(删除历史摘要)
+	{
+		cacheKey := services.GetDelAbstractByQuestionIdCacheKey(item.QuestionId)
+		if utils.Rc.IsExist(cacheKey) {
+			br.Msg = "取消设置默认提示词后,删除历史摘要中,请稍后再试!"
+			br.IsSendEmail = false
+			return
+		}
+	}
+
+	if item.IsDefault == 1 {
+		br.Msg = "该提示词已经是默认提示词,无需设置"
+		br.IsSendEmail = false
+		return
+	}
+	item.IsDefault = 1
+	item.GenerateStatus = `undo`
+	item.ModifyTime = time.Now()
+	err = item.Update([]string{"is_default", "generate_status", "modify_time"})
+	if err != nil {
+		br.Msg = "设置失败"
+		br.ErrMsg = "设置失败,Err:" + err.Error()
+		return
+	}
+	// 新增/编辑ES数据
+	go services.AddOrEditEsRagQuestion(item.QuestionId)
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = `设置成功`
+}
+
+// UnSetDefault
+// @Title 取消设置默认提示词
+// @Description 取消设置默认提示词
+// @Param	request	body request.EditQuestionReq true "type json string"
+// @Success 200 Ret=200 设置成功
+// @router /question/default/unset [post]
+func (c *QuestionController) UnSetDefault() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		c.Data["json"] = br
+		c.ServeJSON()
+	}()
+	var req request.EditQuestionReq
+	err := json.Unmarshal(c.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	if req.QuestionId <= 0 {
+		br.Msg = "提示词id不能为空"
+		br.IsSendEmail = false
+		return
+	}
+
+	obj := rag.Question{}
+	item, err := obj.GetByID(req.QuestionId)
+	if err != nil {
+		br.Msg = "修改失败"
+		br.ErrMsg = "修改失败,查找提示词失败,Err:" + err.Error()
+		if utils.IsErrNoRow(err) {
+			br.Msg = "提示词不存在"
+			br.IsSendEmail = false
+		}
+		return
+	}
+
+	if item.IsDefault == 0 {
+		br.Msg = "该提示词不是默认提示词,无需取消"
+		br.IsSendEmail = false
+		return
+	}
+
+	// 如果是取消已经设置成默认的提示词,那么需要判断是否有正在生成摘要的提示词任务,如果存在的话,那么就不允许取消
+	auth, err := services.CheckOpQuestionAuth()
+	if err != nil {
+		br.Msg = "修改失败"
+		br.ErrMsg = "权限校验失败,Err:" + err.Error()
+		return
+	}
+	if !auth {
+		br.Msg = "当前有提示词正在生成摘要,请稍后再修改"
+		return
+	}
+
+	item.IsDefault = 0
+	item.GenerateStatus = `undo`
+	item.ModifyTime = time.Now()
+	err = item.Update([]string{"is_default", "generate_status", "modify_time"})
+	if err != nil {
+		br.Msg = "取消设置失败"
+		br.ErrMsg = "取消设置失败,Err:" + err.Error()
+		return
+	}
+	// 新增/编辑ES数据
+	go services.AddOrEditEsRagQuestion(item.QuestionId)
+
+	// 对应的提示词生成的摘要库和向量库内容也取消,同时需要加锁,不允许重复操作
+	go services.DelAbstractByQuestionId(item.QuestionId)
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = `取消设置成功`
+}
+
+// GenerateAbstract
+// @Title 生成摘要
+// @Description 生成摘要
+// @Param	request	body request.EditQuestionReq true "type json string"
+// @Success 200 Ret=200 设置成功
+// @router /question/abstract/generate [post]
+func (c *QuestionController) GenerateAbstract() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		c.Data["json"] = br
+		c.ServeJSON()
+	}()
+	var req request.EditQuestionReq
+	err := json.Unmarshal(c.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	if req.QuestionId <= 0 {
+		br.Msg = "提示词id不能为空"
+		br.IsSendEmail = false
+		return
+	}
+
+	obj := rag.Question{}
+	item, err := obj.GetByID(req.QuestionId)
+	if err != nil {
+		br.Msg = "修改失败"
+		br.ErrMsg = "修改失败,查找提示词失败,Err:" + err.Error()
+		if utils.IsErrNoRow(err) {
+			br.Msg = "提示词不存在"
+			br.IsSendEmail = false
+		}
+		return
+	}
+
+	if item.IsDefault != 1 {
+		br.Msg = "该提示词不是默认提示词,不允许生成"
+		br.IsSendEmail = false
+		return
+	}
+	if item.GenerateStatus != `undo` {
+		br.Msg = "该提示词已经生成过摘要,不允许重复生成"
+		br.IsSendEmail = false
+		return
+	}
+
+	// 如果是需要对提示词做摘要的生成,那么需要判断是否有正在生成摘要的提示词任务,如果存在的话,那么就不允许生成(暂定,后面可以改成加到任务中去,等上一个批次的任务完成后,继续该任务)
+	auth, err := services.CheckOpQuestionAuth()
+	if err != nil {
+		br.Msg = "修改失败"
+		br.ErrMsg = "权限校验失败,Err:" + err.Error()
+		return
+	}
+	if !auth {
+		br.Msg = "当前有提示词正在生成摘要,请稍后再重新生成"
+		return
+	}
+
+	// 标记摘要生成状态,避免重复生成
+	item.GenerateStatus = `done`
+	item.ModifyTime = time.Now()
+	err = item.Update([]string{"generate_status", "modify_time"})
+	if err != nil {
+		br.Msg = "取消设置失败"
+		br.ErrMsg = "取消设置失败,Err:" + err.Error()
+		return
+	}
+	// 新增/编辑ES数据
+	go services.AddOrEditEsRagQuestion(item.QuestionId)
+
+	// 添加任务
+	services.AddGenerateAbstractTask(item, c.SysUser)
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = `摘要生成中`
+}
+
+// CheckOpAuth
+// @Title 获取
+// @Description 列表
+// @Success 200 {object} []*rag.QuestionListListResp
+// @router /question/op_auth/check [get]
+func (c *QuestionController) CheckOpAuth() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		c.Data["json"] = br
+		c.ServeJSON()
+	}()
+
+	sysUser := c.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		return
+	}
+
+	// 如果是需要对提示词做摘要的生成,那么需要判断是否有正在生成摘要的提示词任务,如果存在的话,那么就不允许生成(暂定,后面可以改成加到任务中去,等上一个批次的任务完成后,继续该任务)
+	auth, err := services.CheckOpQuestionAuth()
+	if err != nil {
+		br.Msg = "修改失败"
+		br.ErrMsg = "权限校验失败,Err:" + err.Error()
+		return
+	}
+
+	var status, tip string
+	if auth {
+		status = `done`
+		tip = `新摘要生成成功!`
+	} else {
+		status = `processing`
+		tip = `新摘要生成中...`
+	}
+
+	resp := response.QuestionOpAuthResp{
+		Status: status,
+		Tip:    tip,
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = resp
+}
+
 //func init() {
-//	// 问题加到es
+//	// 提示词加到es
 //	{
 //		obj := rag.Question{}
 //		list, _ := obj.GetListByCondition(``, ` `, []interface{}{}, 0, 10000)

+ 422 - 0
controllers/llm/rag_eta_report_abstract.go

@@ -0,0 +1,422 @@
+package llm
+
+import (
+	"encoding/json"
+	"eta/eta_api/cache"
+	"eta/eta_api/controllers"
+	"eta/eta_api/models"
+	"eta/eta_api/models/rag"
+	"eta/eta_api/models/rag/request"
+	"eta/eta_api/models/rag/response"
+	"eta/eta_api/services"
+	"eta/eta_api/services/elastic"
+	"eta/eta_api/utils"
+	"fmt"
+	"github.com/rdlucklib/rdluck_tools/paging"
+	"strconv"
+	"strings"
+)
+
+// RagEtaReportAbstractController
+// @Description: ETA报告摘要管理
+type RagEtaReportAbstractController struct {
+	controllers.BaseAuthController
+}
+
+// List
+// @Title ETA报告摘要列表
+// @Description ETA报告摘要列表
+// @Param   PageSize   query   int  true       "每页数据条数"
+// @Param   CurrentIndex   query   int  true       "当前页页码,从1开始"
+// @Param   KeyWord   query   string  true       "搜索关键词"
+// @Success 200 {object} []*rag.QuestionListListResp
+// @router /abstract/eta_report/list [get]
+func (c *RagEtaReportAbstractController) List() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		c.Data["json"] = br
+		c.ServeJSON()
+	}()
+
+	sysUser := c.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		return
+	}
+	pageSize, _ := c.GetInt("PageSize")
+	currentIndex, _ := c.GetInt("CurrentIndex")
+	keyWord := c.GetString("KeyWord")
+	tagIdStr := c.GetString("TagId")
+	questionId, _ := c.GetInt("QuestionId")
+
+	tagIdList := make([]int, 0)
+	if tagIdStr != `` {
+		tagIdStrList := strings.Split(tagIdStr, `,`)
+		for _, v := range tagIdStrList {
+			if v == `0` {
+				continue
+			}
+
+			tagId, tmpErr := strconv.Atoi(v)
+			if tmpErr != nil {
+				br.Msg = "标签ID有误"
+				br.ErrMsg = fmt.Sprintf("标签ID有误, %s", v)
+				return
+			}
+			tagIdList = append(tagIdList, tagId)
+		}
+	}
+
+	var startSize int
+	if pageSize <= 0 {
+		pageSize = utils.PageSize20
+	}
+	if currentIndex <= 0 {
+		currentIndex = 1
+	}
+	startSize = utils.StartIndex(currentIndex, pageSize)
+
+	// 获取列表
+	total, viewList, err := getRagEtaReportAbstractList(keyWord, tagIdList, questionId, startSize, pageSize)
+	if err != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取失败,Err:" + err.Error()
+		return
+	}
+
+	page := paging.GetPaging(currentIndex, pageSize, total)
+	resp := response.RagEtaReportAbstractListListResp{
+		List:   viewList,
+		Paging: page,
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = resp
+}
+
+func getRagEtaReportAbstractList(keyWord string, tagList []int, questionId, startSize, pageSize int) (total int, viewList []rag.RagEtaReportAbstractView, err error) {
+	//if keyWord == `` {
+	//	var condition string
+	//	var pars []interface{}
+	//	condition += fmt.Sprintf(` AND c.%s = ?`, rag.WechatPlatformColumns.Enabled)
+	//	pars = append(pars, 1)
+	//
+	//	if keyWord != "" {
+	//		condition += fmt.Sprintf(` AND a.%s like ?`, rag.WechatArticleAbstractColumns.Content)
+	//		pars = append(pars, `%`+keyWord+`%`)
+	//	}
+	//
+	//	if tagId > 0 {
+	//		condition += fmt.Sprintf(` AND d.%s = ?`, rag.WechatPlatformTagMappingColumns.TagID)
+	//		pars = append(pars, tagId)
+	//	}
+	//
+	//	obj := new(rag.WechatArticleAbstract)
+	//	tmpTotal, list, tmpErr := obj.GetPageListByTagAndPlatformCondition(condition, pars, startSize, pageSize)
+	//	if tmpErr != nil {
+	//		err = tmpErr
+	//		return
+	//	}
+	//	total = tmpTotal
+	//	viewList = obj.WechatArticleAbstractItem(list)
+	//} else {
+	//	sortMap := map[string]string{
+	//		//"ModifyTime":              "desc",
+	//		//"WechatArticleAbstractId": "desc",
+	//	}
+	//
+	//	obj := new(rag.WechatPlatform)
+	//	platformList, tmpErr := obj.GetListByCondition(` AND enabled = 1 `, []interface{}{}, 0, 100000)
+	//	if tmpErr != nil {
+	//		err = tmpErr
+	//		return
+	//	}
+	//	platformIdList := make([]int, 0)
+	//	for _, v := range platformList {
+	//		platformIdList = append(platformIdList, v.WechatPlatformId)
+	//	}
+	//	tagList := make([]int, 0)
+	//	if tagId > 0 {
+	//		tagList = append(tagList, tagId)
+	//	}
+	//	tmpTotal, list, tmpErr := elastic.WechatArticleAbstractEsSearch(keyWord, tagList, platformIdList, startSize, pageSize, sortMap)
+	//	if tmpErr != nil {
+	//		err = tmpErr
+	//		return
+	//	}
+	//	total = int(tmpTotal)
+	//	if list != nil && len(list) > 0 {
+	//		viewList = list[0].ToViewList(list)
+	//	}
+	//}
+
+	sortMap := map[string]string{
+		//"ModifyTime":              "desc",
+		//"WechatArticleAbstractId": "desc",
+	}
+	if keyWord == `` {
+		sortMap = map[string]string{
+			"CreateTime": "desc",
+			//"WechatArticleAbstractId": "desc",
+		}
+	}
+
+	tmpTotal, list, tmpErr := elastic.RagEtaReportAbstractEsSearch(keyWord, tagList, questionId, startSize, pageSize, sortMap)
+	if tmpErr != nil {
+		err = tmpErr
+		return
+	}
+	total = int(tmpTotal)
+	if list != nil && len(list) > 0 {
+		viewList = list[0].ToViewList(list)
+	}
+
+	return
+}
+
+// Del
+// @Title 删除ETA报告摘要摘要
+// @Description 删除ETA报告摘要摘要
+// @Param	request	body request.BeachOpRagEtaReportAbstractReq true "type json string"
+// @Success 200 Ret=200 删除成功
+// @router /abstract/eta_report/del [post]
+func (c *RagEtaReportAbstractController) Del() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		c.Data["json"] = br
+		c.ServeJSON()
+	}()
+	var req request.BeachOpRagEtaReportAbstractReq
+	err := json.Unmarshal(c.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	if len(req.RagEtaReportAbstractIdList) <= 0 && !req.IsSelectAll {
+		br.Msg = "请选择摘要"
+		br.IsSendEmail = false
+		return
+	}
+
+	// 删除摘要
+	err = services.DelRagEtaReportAbstract(req.RagEtaReportAbstractIdList)
+	if err != nil {
+		br.Msg = "删除失败"
+		br.ErrMsg = "删除失败,Err:" + err.Error()
+		return
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = `删除成功`
+}
+
+// VectorDel
+// @Title 删除ETA报告摘要向量库
+// @Description 删除ETA报告摘要向量库
+// @Param	request	body request.BeachOpRagEtaReportAbstractReq true "type json string"
+// @Success 200 Ret=200 删除成功
+// @router /abstract/eta_report/vector/del [post]
+func (c *RagEtaReportAbstractController) VectorDel() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		c.Data["json"] = br
+		c.ServeJSON()
+	}()
+	var req request.BeachOpRagEtaReportAbstractReq
+	err := json.Unmarshal(c.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	if len(req.RagEtaReportAbstractIdList) <= 0 && !req.IsSelectAll {
+		br.Msg = "请选择摘要"
+		br.IsSendEmail = false
+		return
+	}
+
+	vectorKeyList := make([]string, 0)
+	wechatArticleAbstractIdList := make([]int, 0)
+
+	obj := rag.RagEtaReportAbstract{}
+	list, err := obj.GetByIdList(req.RagEtaReportAbstractIdList)
+	if err != nil {
+		br.Msg = "修改失败"
+		br.ErrMsg = "修改失败,查找问题失败,Err:" + err.Error()
+		if utils.IsErrNoRow(err) {
+			br.Msg = "问题不存在"
+			br.IsSendEmail = false
+		}
+		return
+	}
+	if len(list) > 0 {
+		for _, v := range list {
+			// 有加入到向量库,那么就加入到待删除的向量库list中
+			if v.VectorKey != `` {
+				vectorKeyList = append(vectorKeyList, v.VectorKey)
+			}
+			wechatArticleAbstractIdList = append(wechatArticleAbstractIdList, v.RagEtaReportAbstractId)
+		}
+	}
+
+	//if !req.IsSelectAll {
+	//	list, err := obj.GetByIdList(req.RagEtaReportAbstractIdList)
+	//	if err != nil {
+	//		br.Msg = "修改失败"
+	//		br.ErrMsg = "修改失败,查找问题失败,Err:" + err.Error()
+	//		if utils.IsErrNoRow(err) {
+	//			br.Msg = "问题不存在"
+	//			br.IsSendEmail = false
+	//		}
+	//		return
+	//	}
+	//	if len(list) > 0 {
+	//		for _, v := range list {
+	//			// 有加入到向量库,那么就加入到待删除的向量库list中
+	//			if v.VectorKey != `` {
+	//				vectorKeyList = append(vectorKeyList, v.VectorKey)
+	//			}
+	//			wechatArticleAbstractIdList = append(wechatArticleAbstractIdList, v.RagEtaReportAbstractId)
+	//		}
+	//	}
+	//} else {
+	//	notIdMap := make(map[int]bool)
+	//	for _, v := range req.NotRagEtaReportAbstractIdList {
+	//		notIdMap[v] = true
+	//	}
+	//	_, list, err := getRagEtaReportAbstractList(req.KeyWord, req.TagId, 0, 100000)
+	//	if err != nil {
+	//		br.Msg = "修改失败"
+	//		br.ErrMsg = "修改失败,查找问题失败,Err:" + err.Error()
+	//		if utils.IsErrNoRow(err) {
+	//			br.Msg = "问题不存在"
+	//			br.IsSendEmail = false
+	//		}
+	//		return
+	//	}
+	//	if len(list) > 0 {
+	//		for _, v := range list {
+	//			if notIdMap[v.RagEtaReportAbstractId] {
+	//				continue
+	//			}
+	//
+	//			// 有加入到向量库,那么就加入到待删除的向量库list中
+	//			if v.VectorKey != `` {
+	//				vectorKeyList = append(vectorKeyList, v.VectorKey)
+	//			}
+	//			wechatArticleAbstractIdList = append(wechatArticleAbstractIdList, v.RagEtaReportAbstractId)
+	//		}
+	//	}
+	//}
+
+	// 删除摘要库
+	err = services.DelRagReportLlmDoc(vectorKeyList, wechatArticleAbstractIdList)
+	if err != nil {
+		br.Msg = "删除失败"
+		br.ErrMsg = "删除失败,Err:" + err.Error()
+		return
+	}
+
+	// 修改ES数据
+	for _, wechatArticleAbstractId := range wechatArticleAbstractIdList {
+		go services.AddOrEditEsRagEtaReportAbstract(wechatArticleAbstractId)
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = `删除成功`
+}
+
+// AddVector
+// @Title 新增ETA报告摘要向量库
+// @Description 新增ETA报告摘要向量库
+// @Param	request	body request.BeachOpRagEtaReportAbstractReq true "type json string"
+// @Success 200 Ret=200 新增成功
+// @router /abstract/eta_report/vector/add [post]
+func (c *RagEtaReportAbstractController) AddVector() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		c.Data["json"] = br
+		c.ServeJSON()
+	}()
+	var req request.BeachOpRagEtaReportAbstractReq
+	err := json.Unmarshal(c.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	if len(req.RagEtaReportAbstractIdList) <= 0 && !req.IsSelectAll {
+		br.Msg = "请选择摘要"
+		br.IsSendEmail = false
+		return
+	}
+
+	obj := rag.RagEtaReportAbstract{}
+	list, err := obj.GetByIdList(req.RagEtaReportAbstractIdList)
+	if err != nil {
+		br.Msg = "修改失败"
+		br.ErrMsg = "修改失败,查找问题失败,Err:" + err.Error()
+		if utils.IsErrNoRow(err) {
+			br.Msg = "问题不存在"
+			br.IsSendEmail = false
+		}
+		return
+	}
+	if len(list) > 0 {
+		for _, v := range list {
+			cache.AddRagEtaReportLlmOpToCache(v.RagEtaReportId, v.QuestionId, false)
+		}
+	}
+
+	//if !req.IsSelectAll {
+	//	list, err := obj.GetByIdList(req.RagEtaReportAbstractIdList)
+	//	if err != nil {
+	//		br.Msg = "修改失败"
+	//		br.ErrMsg = "修改失败,查找问题失败,Err:" + err.Error()
+	//		if utils.IsErrNoRow(err) {
+	//			br.Msg = "问题不存在"
+	//			br.IsSendEmail = false
+	//		}
+	//		return
+	//	}
+	//	if len(list) > 0 {
+	//		for _, v := range list {
+	//			wechatArticleAbstractIdList = append(wechatArticleAbstractIdList, v.RagEtaReportAbstractId)
+	//		}
+	//	}
+	//} else {
+	//	notIdMap := make(map[int]bool)
+	//	for _, v := range req.NotRagEtaReportAbstractIdList {
+	//		notIdMap[v] = true
+	//	}
+	//
+	//	_, list, err := getRagEtaReportAbstractList(req.KeyWord, req.TagId, 0, 100000)
+	//	if err != nil {
+	//		br.Msg = "修改失败"
+	//		br.ErrMsg = "修改失败,查找问题失败,Err:" + err.Error()
+	//		if utils.IsErrNoRow(err) {
+	//			br.Msg = "问题不存在"
+	//			br.IsSendEmail = false
+	//		}
+	//		return
+	//	}
+	//	if len(list) > 0 {
+	//		for _, v := range list {
+	//			if notIdMap[v.RagEtaReportAbstractId] {
+	//				continue
+	//			}
+	//			wechatArticleAbstractIdList = append(wechatArticleAbstractIdList, v.RagEtaReportAbstractId)
+	//		}
+	//	}
+	//}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = `添加向量库中,请稍后查看`
+}

+ 85 - 1
controllers/llm/report.go

@@ -68,7 +68,7 @@ func (c *RagEtaReportController) ArticleList() {
 	}
 
 	obj := new(rag.RagEtaReport)
-	tmpTotal, list, err := obj.GetPageListByCondition(condition, pars, startSize, pageSize)
+	tmpTotal, list, err := obj.GetPageListByCondition(``, condition, pars, startSize, pageSize)
 	if err != nil {
 		br.Msg = "获取失败"
 		br.ErrMsg = "获取失败,Err:" + err.Error()
@@ -235,6 +235,90 @@ func (c *RagEtaReportController) ArticleDel() {
 	br.Msg = "删除成功"
 }
 
+// AbstractList
+// @Title 摘要列表
+// @Description 我关注的接口
+// @Param   PageSize   query   int  true       "每页数据条数"
+// @Param   CurrentIndex   query   int  true       "当前页页码,从1开始"
+// @Param   RagEtaReportId   query   int  true       "知识库与eta报告关联的id"
+// @Success 200 {object} []*rag.WechatPlatform
+// @router /eta_report/article/abstract/list [get]
+func (c *RagEtaReportController) AbstractList() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		c.Data["json"] = br
+		c.ServeJSON()
+	}()
+
+	sysUser := c.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		return
+	}
+	pageSize, _ := c.GetInt("PageSize")
+	currentIndex, _ := c.GetInt("CurrentIndex")
+	ragEtaReportId, _ := c.GetInt("RagEtaReportId")
+
+	var startSize int
+	if pageSize <= 0 {
+		pageSize = utils.PageSize20
+	}
+	if currentIndex <= 0 {
+		currentIndex = 1
+	}
+	startSize = utils.StartIndex(currentIndex, pageSize)
+
+	//var condition string
+	//var pars []interface{}
+	//
+	//if keyWord != "" {
+	//	condition += fmt.Sprintf(` AND (b.%s like ? or a.%s like ? ) `, rag.WechatPlatformColumns.Nickname, rag.WechatArticleColumns.Title)
+	//	pars = append(pars, `%`+keyWord+`%`, `%`+keyWord+`%`)
+	//}
+	//
+	//if wechatPlatformId > 0 {
+	//	condition += fmt.Sprintf(` AND a.%s = ?`, rag.WechatArticleColumns.WechatPlatformID)
+	//	pars = append(pars, wechatPlatformId)
+	//}
+	//
+	//condition += fmt.Sprintf(` AND b.%s = ? `, rag.WechatPlatformColumns.Enabled)
+	//pars = append(pars, 1)
+
+	var total int
+
+	var condition string
+	var pars []interface{}
+
+	condition += fmt.Sprintf(` AND a.%s  = ?  `, rag.RagEtaReportAbstractColumns.RagEtaReportId)
+	pars = append(pars, ragEtaReportId)
+
+	obj := new(rag.RagEtaReportAbstract)
+	tmpTotal, tmpList, err := obj.GetPageListByPlatformCondition(condition, pars, startSize, pageSize)
+	if err != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取失败,Err:" + err.Error()
+		return
+	}
+	total = tmpTotal
+
+	list := make([]rag.RagEtaReportAbstractView, 0)
+	for _, v := range tmpList {
+		list = append(list, v.ToView())
+	}
+
+	page := paging.GetPaging(currentIndex, pageSize, total)
+	resp := response.RagEtaReportItemAbstractListListResp{
+		List:   list,
+		Paging: page,
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = resp
+}
+
 //// 修复历史ETA报告到知识库
 //func init() {
 //	idList, err := models.GetAllPublishReportId()

+ 0 - 2
controllers/llm/user_chat_controller.go

@@ -330,5 +330,3 @@ func (ucCtrl *UserChatController) ChatRecordList() {
 	br.Success = true
 	br.Msg = "获取聊天记录成功"
 }
-
-

+ 84 - 0
controllers/llm/wechat_platform.go

@@ -725,6 +725,90 @@ func (c *WechatPlatformController) ArticleDel() {
 	br.Msg = "删除成功"
 }
 
+// AbstractList
+// @Title 摘要列表
+// @Description 我关注的接口
+// @Param   PageSize   query   int  true       "每页数据条数"
+// @Param   CurrentIndex   query   int  true       "当前页页码,从1开始"
+// @Param   WechatArticleId   query   int  true       "文章id"
+// @Success 200 {object} []*rag.WechatPlatform
+// @router /wechat_platform/article/abstract/list [get]
+func (c *WechatPlatformController) AbstractList() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		c.Data["json"] = br
+		c.ServeJSON()
+	}()
+
+	sysUser := c.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		return
+	}
+	pageSize, _ := c.GetInt("PageSize")
+	currentIndex, _ := c.GetInt("CurrentIndex")
+	wechatArticleId, _ := c.GetInt("WechatArticleId")
+
+	var startSize int
+	if pageSize <= 0 {
+		pageSize = utils.PageSize20
+	}
+	if currentIndex <= 0 {
+		currentIndex = 1
+	}
+	startSize = utils.StartIndex(currentIndex, pageSize)
+
+	//var condition string
+	//var pars []interface{}
+	//
+	//if keyWord != "" {
+	//	condition += fmt.Sprintf(` AND (b.%s like ? or a.%s like ? ) `, rag.WechatPlatformColumns.Nickname, rag.WechatArticleColumns.Title)
+	//	pars = append(pars, `%`+keyWord+`%`, `%`+keyWord+`%`)
+	//}
+	//
+	//if wechatPlatformId > 0 {
+	//	condition += fmt.Sprintf(` AND a.%s = ?`, rag.WechatArticleColumns.WechatPlatformID)
+	//	pars = append(pars, wechatPlatformId)
+	//}
+	//
+	//condition += fmt.Sprintf(` AND b.%s = ? `, rag.WechatPlatformColumns.Enabled)
+	//pars = append(pars, 1)
+
+	var total int
+
+	var condition string
+	var pars []interface{}
+
+	condition += fmt.Sprintf(` AND a.%s  = ?  `, rag.WechatArticleAbstractColumns.WechatArticleID)
+	pars = append(pars, wechatArticleId)
+
+	obj := new(rag.WechatArticleAbstract)
+	tmpTotal, tmpList, err := obj.GetPageListByPlatformCondition(condition, pars, startSize, pageSize)
+	if err != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取失败,Err:" + err.Error()
+		return
+	}
+	total = tmpTotal
+
+	list := make([]rag.WechatArticleAbstractView, 0)
+	for _, v := range tmpList {
+		list = append(list, v.ToView())
+	}
+
+	page := paging.GetPaging(currentIndex, pageSize, total)
+	resp := response.WechatArticleItemAbstractListListResp{
+		List:   list,
+		Paging: page,
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = resp
+}
+
 //func init() {
 //	//obj := rag.WechatPlatform{}
 //	//item, _ := obj.GetByID(2)

+ 4 - 5
controllers/sys_role.go

@@ -724,11 +724,10 @@ func (this *SysRoleController) SystemConfig() {
 	}, system.BusinessConf{
 		ConfKey: "LoginUrl",
 		ConfVal: conf["LoginUrl"],
-	},
-        system.BusinessConf{
-        ConfKey: "KnowledgeBaseName",
-    	ConfVal: conf["KnowledgeBaseName"],
-    })
+	}, system.BusinessConf{
+		ConfKey: models.KnowledgeBaseName,
+		ConfVal: conf[models.KnowledgeBaseName],
+	})
 
 	osc := system.BusinessConf{
 		ConfKey: "ObjectStorageClient",

+ 15 - 0
controllers/sys_user.go

@@ -371,6 +371,21 @@ func (this *SysUserController) AuthCodeLogin() {
 		return
 	}
 
+	// 查询一下用户是否被禁用
+	sysAdmin, e := system.GetSysUserById(data.AdminId)
+	if e != nil && !utils.IsErrNoRow(e) {
+		br.Msg = "获取失败"
+		br.ErrMsg = fmt.Sprintf("获取用户信息失败, %v", e)
+		return
+	}
+	if sysAdmin != nil && sysAdmin.Enabled != 1 {
+		br.Ret = 408
+		br.Msg = "您的账号已被禁用,如需登录,请联系管理员"
+		j, _ := json.Marshal(data)
+		br.ErrMsg = fmt.Sprintf("AuthCodeLogin, 账户信息异常:%s", j)
+		return
+	}
+
 	br.Data = data
 	br.Ret = 200
 	br.Success = true

+ 5 - 0
models/business_conf.go

@@ -61,10 +61,12 @@ const (
 	BusinessConfEsIndexNameDataSource        = "EsIndexNameDataSource"        // ES索引名称-数据源
 	LLMInitConfig                            = "llmInitConfig"
 	KnowledgeBaseName                        = "KnowledgeBaseName"                // 摘要库
+	PrivateKnowledgeBaseName                 = "PrivateKnowledgeBaseName"         // 私有摘要库
 	KnowledgeArticleName                     = "KnowledgeArticleName"             // 原文库
 	BusinessConfEsWechatArticle              = "EsIndexNameWechatArticle"         // ES索引名称-微信文章
 	BusinessConfEsWechatArticleAbstract      = "EsIndexNameWechatArticleAbstract" // ES索引名称-微信文章摘要
 	BusinessConfEsRagQuestion                = "EsIndexNameRagQuestion"           // ES索引名称-知识库问题
+	BusinessConfEsRagEtaReportAbstract       = "EsIndexNameRagEtaReportAbstract"  // ES索引名称-eta报告摘要
 	BusinessConfIsOpenChartExpired           = "IsOpenChartExpired"               // 是否开启图表有效期鉴权/报告禁止复制
 	BusinessConfReportChartExpiredTime       = "ReportChartExpiredTime"           // 图表有效期鉴权时间,单位:分钟
 	BusinessConfOssUrlReplace                = "OssUrlReplace"                    // OSS地址替换-兼容内网客户用
@@ -282,6 +284,9 @@ func InitBusinessConf() {
 	if BusinessConfMap[BusinessConfEsRagQuestion] != "" {
 		utils.EsRagQuestionName = BusinessConfMap[BusinessConfEsRagQuestion]
 	}
+	if BusinessConfMap[BusinessConfEsRagEtaReportAbstract] != "" {
+		utils.EsRagEtaReportAbstractName = BusinessConfMap[BusinessConfEsRagEtaReportAbstract]
+	}
 	confStr := BusinessConfMap[LLMInitConfig]
 	if confStr != "" {
 		var config LLMConfig

+ 7 - 0
models/chart_permission.go

@@ -284,6 +284,7 @@ type SimpleChartPermission struct {
 	ChartPermissionName string                   `description:"品种名称"`
 	Sort                int                      `description:"排序"`
 	Children            []*SimpleChartPermission `description:"子分类"`
+	//IsLatestEdit        bool                     `description:"是否是最后一级"`
 }
 
 func FormatChartPermission2Simple(origin *ChartPermission) (item *SimpleChartPermission) {
@@ -321,3 +322,9 @@ func GetChartPermissionByIdList(permissionIdList []int) (items []*ChartPermissio
 	err = global.DbMap[utils.DbNameReport].Raw(sql, permissionIdList).Find(&items).Error
 	return
 }
+
+type FaCalendarPermissionResp struct {
+	List                  []*SimpleChartPermission
+	CheckedPermissionId   int
+	CheckedPermissionName string
+}

+ 164 - 0
models/rag/ai_task.go

@@ -0,0 +1,164 @@
+package rag
+
+import (
+	"database/sql"
+	"eta/eta_api/global"
+	"eta/eta_api/utils"
+	"fmt"
+	"time"
+)
+
+// AiTask ai这边的任务表
+type AiTask struct {
+	AiTaskID                int       `gorm:"primaryKey;column:ai_task_id" description:"-"`
+	TaskName                string    `gorm:"column:task_name" description:"任务名称"`
+	TaskType                string    `gorm:"column:task_type" description:"任务类型"`
+	Status                  string    `gorm:"column:status" description:"任务状态"`
+	StartTime               time.Time `gorm:"column:start_time" description:"开始时间"`
+	EndTime                 time.Time `gorm:"column:end_time" description:"结束时间"`
+	CreateTime              time.Time `gorm:"column:create_time" description:"创建时间"`
+	UpdateTime              time.Time `gorm:"column:update_time" description:"更新时间"`
+	Parameters              string    `gorm:"column:parameters" description:"执行参数"`
+	Logs                    string    `gorm:"column:logs" description:"日志"`
+	Errormessage            string    `gorm:"column:ErrorMessage" description:"错误信息"`
+	Priority                int       `gorm:"column:priority" description:"优先级"`
+	RetryCount              int       `gorm:"column:retry_count" description:"重试次数"`
+	EstimatedCompletionTime time.Time `gorm:"column:estimated_completion_time" description:"预计完成时间"`
+	ActualCompletitonTime   time.Time `gorm:"column:actual_completiton_time" description:"实际完成时间"`
+	Remark                  string    `gorm:"column:remark" description:"备注"`
+	SysUserID               int       `gorm:"column:sys_user_id" description:"任务创建人id"`
+	SysUserRealName         string    `gorm:"column:sys_user_real_name" description:"任务创建人名称"`
+}
+
+// TableName get sql table name.获取数据库表名
+func (m *AiTask) TableName() string {
+	return "ai_task"
+}
+
+// AiTaskColumns get sql column name.获取数据库列名
+var AiTaskColumns = struct {
+	AiTaskID                string
+	TaskName                string
+	TaskType                string
+	Status                  string
+	StartTime               string
+	EndTime                 string
+	CreateTime              string
+	UpdateTime              string
+	Parameters              string
+	Logs                    string
+	Errormessage            string
+	Priority                string
+	RetryCount              string
+	EstimatedCompletionTime string
+	ActualCompletitonTime   string
+	Remark                  string
+	SysUserID               string
+	SysUserRealName         string
+}{
+	AiTaskID:                "ai_task_id",
+	TaskName:                "task_name",
+	TaskType:                "task_type",
+	Status:                  "status",
+	StartTime:               "start_time",
+	EndTime:                 "end_time",
+	CreateTime:              "create_time",
+	UpdateTime:              "update_time",
+	Parameters:              "parameters",
+	Logs:                    "logs",
+	Errormessage:            "ErrorMessage",
+	Priority:                "priority",
+	RetryCount:              "retry_count",
+	EstimatedCompletionTime: "estimated_completion_time",
+	ActualCompletitonTime:   "actual_completiton_time",
+	Remark:                  "remark",
+	SysUserID:               "sys_user_id",
+	SysUserRealName:         "sys_user_real_name",
+}
+
+func (m *AiTask) Create() (err error) {
+	err = global.DbMap[utils.DbNameAI].Create(&m).Error
+
+	return
+}
+
+func (m *AiTask) Update(updateCols []string) (err error) {
+	err = global.DbMap[utils.DbNameAI].Select(updateCols).Updates(&m).Error
+
+	return
+}
+
+func (m *AiTask) Del() (err error) {
+	err = global.DbMap[utils.DbNameAI].Delete(&m).Error
+
+	return
+}
+
+func (m *AiTask) GetByID(id int) (item *AiTask, err error) {
+	err = global.DbMap[utils.DbNameAI].Where(fmt.Sprintf("%s = ?", AiTaskColumns.AiTaskID), id).First(&item).Error
+
+	return
+}
+
+func (m *AiTask) GetByCondition(condition string, pars []interface{}) (item *AiTask, err error) {
+	sqlStr := fmt.Sprintf(`SELECT * FROM %s WHERE 1=1 %s`, m.TableName(), condition)
+	err = global.DbMap[utils.DbNameAI].Raw(sqlStr, pars...).First(&item).Error
+
+	return
+}
+
+func (m *AiTask) GetListByCondition(field, condition string, pars []interface{}, startSize, pageSize int) (items []*AiTask, err error) {
+	if field == "" {
+		field = "*"
+	}
+	sqlStr := fmt.Sprintf(`SELECT %s FROM %s WHERE 1=1 %s order by ai_task_id desc LIMIT ?,?`, field, m.TableName(), condition)
+	pars = append(pars, startSize, pageSize)
+	err = global.DbMap[utils.DbNameAI].Raw(sqlStr, pars...).Find(&items).Error
+
+	return
+}
+
+func (m *AiTask) GetCountByCondition(condition string, pars []interface{}) (total int, err error) {
+	var intNull sql.NullInt64
+	sqlStr := fmt.Sprintf(`SELECT COUNT(1) total FROM %s WHERE 1=1 %s`, m.TableName(), condition)
+	err = global.DbMap[utils.DbNameAI].Raw(sqlStr, pars...).Scan(&intNull).Error
+	if err == nil && intNull.Valid {
+		total = int(intNull.Int64)
+	}
+
+	return
+}
+
+// AddAiTask
+// @Description: 添加Ai模块的任务
+// @author: Roc
+// @datetime 2025-04-16 16:55:36
+// @param aiTask *AiTask
+// @param aiRecordList []*AiTaskRecord
+// @return err error
+func AddAiTask(aiTask *AiTask, aiRecordList []*AiTaskRecord) (err error) {
+	to := global.DbMap[utils.DbNameAI].Begin()
+	defer func() {
+		if err != nil {
+			_ = to.Rollback()
+		} else {
+			_ = to.Commit()
+		}
+	}()
+
+	err = to.Create(aiTask).Error
+	if err != nil {
+		return
+	}
+
+	for _, aiTaskRecord := range aiRecordList {
+		aiTaskRecord.AiTaskID = aiTask.AiTaskID
+	}
+
+	err = to.CreateInBatches(aiRecordList, utils.MultiAddNum).Error
+	if err != nil {
+		return
+	}
+
+	return
+}

+ 115 - 0
models/rag/ai_task_record.go

@@ -0,0 +1,115 @@
+package rag
+
+import (
+	"database/sql"
+	"eta/eta_api/global"
+	"eta/eta_api/utils"
+	"fmt"
+	"time"
+)
+
+// AiTaskRecord AI任务的子记录
+type AiTaskRecord struct {
+	AiTaskRecordID int       `gorm:"primaryKey;column:ai_task_record_id" json:"-"` // 任务记录id
+	AiTaskID       int       `gorm:"column:ai_task_id" json:"aiTaskId"`            // 任务id
+	Parameters     string    `gorm:"column:parameters" json:"parameters"`          // 子任务参数
+	Status         string    `gorm:"column:status" json:"status"`                  // 状态
+	Remark         string    `gorm:"column:remark" json:"remark"`                  // 备注
+	ModifyTime     time.Time `gorm:"column:modify_time" json:"modifyTime"`         // 最后一次修改时间
+	CreateTime     time.Time `gorm:"column:create_time" json:"createTime"`         // 任务创建时间
+}
+
+// TableName get sql table name.获取数据库表名
+func (m *AiTaskRecord) TableName() string {
+	return "ai_task_record"
+}
+
+// AiTaskRecordColumns get sql column name.获取数据库列名
+var AiTaskRecordColumns = struct {
+	AiTaskRecordID string
+	AiTaskID       string
+	Parameters     string
+	Status         string
+	Remark         string
+	ModifyTime     string
+	CreateTime     string
+}{
+	AiTaskRecordID: "ai_task_record_id",
+	AiTaskID:       "ai_task_id",
+	Parameters:     "parameters",
+	Status:         "status",
+	Remark:         "remark",
+	ModifyTime:     "modify_time",
+	CreateTime:     "create_time",
+}
+
+func (m *AiTaskRecord) Create() (err error) {
+	err = global.DbMap[utils.DbNameAI].Create(&m).Error
+
+	return
+}
+
+func (m *AiTaskRecord) Update(updateCols []string) (err error) {
+	err = global.DbMap[utils.DbNameAI].Select(updateCols).Updates(&m).Error
+
+	return
+}
+
+func (m *AiTaskRecord) Del() (err error) {
+	err = global.DbMap[utils.DbNameAI].Delete(&m).Error
+
+	return
+}
+
+func (m *AiTaskRecord) GetByID(id int) (item *AiTaskRecord, err error) {
+	err = global.DbMap[utils.DbNameAI].Where(fmt.Sprintf("%s = ?", AiTaskRecordColumns.AiTaskRecordID), id).First(&item).Error
+
+	return
+}
+
+func (m *AiTaskRecord) GetByCondition(condition string, pars []interface{}) (item *AiTaskRecord, err error) {
+	sqlStr := fmt.Sprintf(`SELECT * FROM %s WHERE 1=1 %s`, m.TableName(), condition)
+	err = global.DbMap[utils.DbNameAI].Raw(sqlStr, pars...).First(&item).Error
+
+	return
+}
+
+func (m *AiTaskRecord) GetAllListByCondition(field, condition string, pars []interface{}) (items []*AiTaskRecord, err error) {
+	if field == "" {
+		field = "*"
+	}
+	sqlStr := fmt.Sprintf(`SELECT %s FROM %s WHERE 1=1 %s order by ai_task_record_id desc `, field, m.TableName(), condition)
+	err = global.DbMap[utils.DbNameAI].Raw(sqlStr, pars...).Find(&items).Error
+
+	return
+}
+
+func (m *AiTaskRecord) GetListByCondition(field, condition string, pars []interface{}, startSize, pageSize int) (items []*AiTaskRecord, err error) {
+	if field == "" {
+		field = "*"
+	}
+	sqlStr := fmt.Sprintf(`SELECT %s FROM %s WHERE 1=1 %s order by ai_task_record_id desc LIMIT ?,?`, field, m.TableName(), condition)
+	pars = append(pars, startSize, pageSize)
+	err = global.DbMap[utils.DbNameAI].Raw(sqlStr, pars...).Find(&items).Error
+
+	return
+}
+
+func (m *AiTaskRecord) GetCountByCondition(condition string, pars []interface{}) (total int, err error) {
+	var intNull sql.NullInt64
+	sqlStr := fmt.Sprintf(`SELECT COUNT(1) total FROM %s WHERE 1=1 %s`, m.TableName(), condition)
+	err = global.DbMap[utils.DbNameAI].Raw(sqlStr, pars...).Scan(&intNull).Error
+	if err == nil && intNull.Valid {
+		total = int(intNull.Int64)
+	}
+
+	return
+}
+
+// QuestionGenerateAbstractParam
+// @Description:
+type QuestionGenerateAbstractParam struct {
+	QuestionId  int    `json:"questionId"`
+	ArticleType string `json:"articleType"`
+	ArticleId   int    `json:"articleId"`
+}

+ 114 - 0
models/rag/article_abstract_history.go

@@ -0,0 +1,114 @@
+package rag
+
+import (
+	"eta/eta_api/global"
+	"eta/eta_api/utils"
+	"time"
+)
+
+// ArticleAbstractHistory 文章/报告摘要历史记录表
+type ArticleAbstractHistory struct {
+	ArticleAbstractHistoryID int       `gorm:"primaryKey;column:article_abstract_history_id" description:"-"`
+	Source                   int8      `gorm:"column:source" description:"来源,0:公众号文章,1:eta报告"`
+	ArticleAbstractID        int       `gorm:"column:article_abstract_id" description:"文章/报告摘要id"`
+	ArticleID                int       `gorm:"column:article_id" description:"文章/报告Id"`
+	QuestionId               int       `gorm:"column:question_id" description:"提示词Id"`
+	Tags                     string    `gorm:"column:tags" description:"标签"`
+	TagsName                 string    `gorm:"column:tags_name" description:"标签名,多个用英文逗号隔开"`
+	QuestionContent          string    `gorm:"column:question_content" description:"questionContent"`
+	Content                  string    `gorm:"column:content" description:"摘要内容"`
+	Version                  int       `gorm:"column:version" description:"版本号"`
+	VectorKey                string    `gorm:"column:vector_key" description:"向量key标识"`
+	ModifyTime               time.Time `gorm:"column:modify_time" description:"modifyTime"`
+	CreateTime               time.Time `gorm:"column:create_time" description:"createTime"`
+}
+
+// TableName get sql table name.获取数据库表名
+func (m *ArticleAbstractHistory) TableName() string {
+	return "article_abstract_history"
+}
+
+// ArticleAbstractHistoryColumns get sql column name.获取数据库列名
+var ArticleAbstractHistoryColumns = struct {
+	ArticleAbstractHistoryID string
+	Source                   string
+	ArticleAbstractID        string
+	ArticleID                string
+	QuestionId               string
+	Tags                     string
+	TagsName                 string
+	QuestionContent          string
+	Content                  string
+	Version                  string
+	VectorKey                string
+	ModifyTime               string
+	CreateTime               string
+}{
+	ArticleAbstractHistoryID: "article_abstract_history_id",
+	Source:                   "source",
+	ArticleAbstractID:        "article_abstract_id",
+	ArticleID:                "article_id",
+	QuestionId:               "question_id",
+	Tags:                     "tags",
+	TagsName:                 "tags_name",
+	QuestionContent:          "question_content",
+	Content:                  "content",
+	Version:                  "version",
+	VectorKey:                "vector_key",
+	ModifyTime:               "modify_time",
+	CreateTime:               "create_time",
+}
+
+func (m *ArticleAbstractHistory) Create() (err error) {
+	err = global.DbMap[utils.DbNameAI].Create(&m).Error
+
+	return
+}
+
+// AddArticleAbstractHistoryByWechatArticleAbstract
+// @Description: 根据eta报告摘要添加历史记录
+// @author: Roc
+// @datetime 2025-04-17 14:05:10
+// @param item *WechatArticleAbstract
+func AddArticleAbstractHistoryByWechatArticleAbstract(item *WechatArticleAbstract) {
+	history := &ArticleAbstractHistory{
+		ArticleAbstractHistoryID: 0,
+		Source:                   0,
+		ArticleAbstractID:        item.WechatArticleAbstractId,
+		ArticleID:                item.WechatArticleId,
+		QuestionId:               item.QuestionId,
+		Tags:                     item.Tags,
+		TagsName:                 item.TagsName,
+		QuestionContent:          item.QuestionContent,
+		Content:                  item.Content,
+		Version:                  item.Version,
+		VectorKey:                item.VectorKey,
+		ModifyTime:               time.Now(),
+		CreateTime:               time.Now(),
+	}
+	_ = history.Create()
+}
+
+// AddArticleAbstractHistoryByWechatArticleAbstract
+// @Description: 根据eta报告摘要添加历史记录
+// @author: Roc
+// @datetime 2025-04-17 14:05:10
+// @param item *WechatArticleAbstract
+func AddArticleAbstractHistoryByRagEtaReportAbstract(item *RagEtaReportAbstract) {
+	history := &ArticleAbstractHistory{
+		ArticleAbstractHistoryID: 0,
+		Source:                   0,
+		ArticleAbstractID:        item.RagEtaReportAbstractId,
+		ArticleID:                item.RagEtaReportId,
+		QuestionId:               item.QuestionId,
+		Tags:                     item.Tags,
+		TagsName:                 item.TagsName,
+		QuestionContent:          item.QuestionContent,
+		Content:                  item.Content,
+		Version:                  item.Version,
+		VectorKey:                item.VectorKey,
+		ModifyTime:               time.Now(),
+		CreateTime:               time.Now(),
+	}
+	_ = history.Create()
+}

+ 19 - 4
models/rag/question.go

@@ -13,6 +13,9 @@ type Question struct {
 	QuestionTitle   string    `gorm:"column:question_title;type:varchar(255);comment:问题标题;" description:"问题标题"`
 	QuestionContent string    `gorm:"column:question_content;type:varchar(255);comment:问题内容;" description:"问题内容"`
 	Sort            int       `gorm:"column:sort;type:int(11);comment:排序;default:0;" description:"排序"`
+	Version         int       `gorm:"column:version" description:"问题版本"`
+	GenerateStatus  string    `gorm:"column:generate_status;type:enum('undo', 'done');comment:生成摘要状态;default:NULL;" description:"生成摘要状态"`
+	IsDefault       int       `gorm:"column:is_default;type:int(1);comment:是否默认提示词;default:NULL;" description:"是否默认提示词"`
 	SysUserId       int       `gorm:"column:sys_user_id;type:int(11);comment:添加人id;default:0;" description:"添加人id"`
 	SysUserRealName string    `gorm:"column:sys_user_real_name;type:varchar(255);comment:添加人真实名称;" description:"添加人真实名称"`
 	ModifyTime      time.Time `gorm:"column:modify_time;type:datetime;default:NULL;" description:"modify_time"`
@@ -26,17 +29,23 @@ func (m *Question) TableName() string {
 
 // QuestionColumns get sql column name.获取数据库列名
 var QuestionColumns = struct {
-	QuestionID      string
+	QuestionId      string
 	QuestionTitle   string
 	QuestionContent string
 	Sort            string
+	Version         string
+	GenerateStatus  string
+	IsDefault       string
 	ModifyTime      string
 	CreateTime      string
 }{
-	QuestionID:      "question_id",
+	QuestionId:      "question_id",
 	QuestionTitle:   "question_title",
 	QuestionContent: "question_content",
 	Sort:            "sort",
+	Version:         "version",
+	GenerateStatus:  "generate_status",
+	IsDefault:       "is_default",
 	ModifyTime:      "modify_time",
 	CreateTime:      "create_time",
 }
@@ -64,6 +73,9 @@ type QuestionView struct {
 	QuestionTitle   string `gorm:"column:question_title;type:varchar(255);comment:问题标题;" description:"问题标题"`
 	QuestionContent string `gorm:"column:question_content;type:varchar(255);comment:问题内容;" description:"问题内容"`
 	Sort            int    `gorm:"column:sort;type:int(11);comment:排序;default:0;" description:"排序"`
+	Version         int    `gorm:"column:version" description:"问题版本"`
+	GenerateStatus  string `gorm:"column:generate_status;type:enum('undo', 'done');comment:生成摘要状态;default:NULL;" description:"生成摘要状态"`
+	IsDefault       int    `gorm:"column:is_default;type:int(1);comment:是否默认提示词;default:NULL;" description:"是否默认提示词"`
 	SysUserId       int    `gorm:"column:sys_user_id;type:int(11);comment:添加人id;default:0;" description:"添加人id"`
 	SysUserRealName string `gorm:"column:sys_user_real_name;type:varchar(255);comment:添加人真实名称;" description:"添加人真实名称"`
 	ModifyTime      string `gorm:"column:modify_time;type:datetime;default:NULL;" description:"modify_time"`
@@ -84,6 +96,9 @@ func (m *Question) ToView() QuestionView {
 		QuestionTitle:   m.QuestionTitle,
 		QuestionContent: m.QuestionContent,
 		Sort:            m.Sort,
+		Version:         m.Version,
+		GenerateStatus:  m.GenerateStatus,
+		IsDefault:       m.IsDefault,
 		SysUserId:       m.SysUserId,
 		SysUserRealName: m.SysUserRealName,
 		ModifyTime:      modifyTime,
@@ -101,7 +116,7 @@ func (m *Question) ListToViewList(list []*Question) (wechatArticleViewList []Que
 }
 
 func (m *Question) GetByID(id int) (item *Question, err error) {
-	err = global.DbMap[utils.DbNameAI].Where(fmt.Sprintf("%s = ?", QuestionColumns.QuestionID), id).First(&item).Error
+	err = global.DbMap[utils.DbNameAI].Where(fmt.Sprintf("%s = ?", QuestionColumns.QuestionId), id).First(&item).Error
 
 	return
 }
@@ -154,7 +169,7 @@ func (m *Question) GetTitlePageListByCondition(condition string, pars []interfac
 		return
 	}
 	if total > 0 {
-		items, err = m.GetListByCondition(`question_id,question_title,sort`, condition, pars, startSize, pageSize)
+		items, err = m.GetListByCondition(`question_id,question_title,sort,version,generate_status,is_default`, condition, pars, startSize, pageSize)
 	}
 
 	return

+ 86 - 0
models/rag/question_history.go

@@ -0,0 +1,86 @@
+package rag
+
+import (
+	"eta/eta_api/global"
+	"eta/eta_api/utils"
+	"time"
+)
+
+// QuestionHistory 问题历史列表
+type QuestionHistory struct {
+	QuestionHistoryID int       `gorm:"primaryKey;column:question_history_id" description:"-"`
+	QuestionId        int       `gorm:"column:question_id" description:"问题ID"`
+	QuestionTitle     string    `gorm:"column:question_title" description:"问题标题"`
+	QuestionContent   string    `gorm:"column:question_content" description:"问题内容"`
+	Sort              int       `gorm:"column:sort" description:"排序"`
+	Version           int       `gorm:"column:version" description:"问题版本"`
+	GenerateStatus    string    `gorm:"column:generate_status" description:"生成摘要状态"`
+	IsDefault         int       `gorm:"column:is_default" description:"是否默认提示词"`
+	SysUserID         int       `gorm:"column:sys_user_id" description:"添加人id"`
+	SysUserRealName   string    `gorm:"column:sys_user_real_name" description:"添加人名称"`
+	ModifyTime        time.Time `gorm:"column:modify_time" description:"modifyTime"`
+	CreateTime        time.Time `gorm:"column:create_time" description:"createTime"`
+}
+
+// TableName get sql table name.获取数据库表名
+func (m *QuestionHistory) TableName() string {
+	return "question_history"
+}
+
+// QuestionHistoryColumns get sql column name.获取数据库列名
+var QuestionHistoryColumns = struct {
+	QuestionHistoryID string
+	QuestionId        string
+	QuestionTitle     string
+	QuestionContent   string
+	Sort              string
+	Version           string
+	GenerateStatus    string
+	IsDefault         string
+	SysUserID         string
+	SysUserRealName   string
+	ModifyTime        string
+	CreateTime        string
+}{
+	QuestionHistoryID: "question_history_id",
+	QuestionId:        "question_id",
+	QuestionTitle:     "question_title",
+	QuestionContent:   "question_content",
+	Sort:              "sort",
+	Version:           "version",
+	GenerateStatus:    "generate_status",
+	IsDefault:         "is_default",
+	SysUserID:         "sys_user_id",
+	SysUserRealName:   "sys_user_real_name",
+	ModifyTime:        "modify_time",
+	CreateTime:        "create_time",
+}
+
+func (m *QuestionHistory) Create() (err error) {
+	err = global.DbMap[utils.DbNameAI].Create(&m).Error
+
+	return
+}
+
+// AddQuestionHistoryByQuestion
+// @Description: 根据提示词创建提示词历史记录
+// @author: Roc
+// @datetime 2025-04-17 10:44:15
+// @param item *Question
+func AddQuestionHistoryByQuestion(item *Question) {
+	history := &QuestionHistory{
+		QuestionHistoryID: 0,
+		QuestionId:        item.QuestionId,
+		QuestionTitle:     item.QuestionTitle,
+		QuestionContent:   item.QuestionContent,
+		Sort:              item.Sort,
+		Version:           item.Version,
+		GenerateStatus:    item.GenerateStatus,
+		IsDefault:         item.IsDefault,
+		SysUserID:         item.SysUserId,
+		SysUserRealName:   item.SysUserRealName,
+		ModifyTime:        time.Now(),
+		CreateTime:        time.Now(),
+	}
+	_ = history.Create()
+}

+ 5 - 5
models/rag/rag_eta_report.go

@@ -31,7 +31,7 @@ func (m *RagEtaReport) TableName() string {
 
 // RagEtaReportColumns get sql column name.获取数据库列名
 var RagEtaReportColumns = struct {
-	RagEtaReportID  string
+	RagEtaReportId  string
 	ReportID        string
 	ReportChapterID string
 	Title           string
@@ -44,7 +44,7 @@ var RagEtaReportColumns = struct {
 	ModifyTime      string
 	CreateTime      string
 }{
-	RagEtaReportID:  "rag_eta_report_id",
+	RagEtaReportId:  "rag_eta_report_id",
 	ReportID:        "report_id",
 	ReportChapterID: "report_chapter_id",
 	Title:           "title",
@@ -121,7 +121,7 @@ func (m *RagEtaReport) Update(updateCols []string) (err error) {
 }
 
 func (m *RagEtaReport) GetById(id int) (item *RagEtaReport, err error) {
-	err = global.DbMap[utils.DbNameAI].Where(fmt.Sprintf("%s = ?", RagEtaReportColumns.RagEtaReportID), id).First(&item).Error
+	err = global.DbMap[utils.DbNameAI].Where(fmt.Sprintf("%s = ?", RagEtaReportColumns.RagEtaReportId), id).First(&item).Error
 
 	return
 }
@@ -154,14 +154,14 @@ func (m *RagEtaReport) GetCountByCondition(condition string, pars []interface{})
 	return
 }
 
-func (m *RagEtaReport) GetPageListByCondition(condition string, pars []interface{}, startSize, pageSize int) (total int, items []*RagEtaReport, err error) {
+func (m *RagEtaReport) GetPageListByCondition(field, condition string, pars []interface{}, startSize, pageSize int) (total int, items []*RagEtaReport, err error) {
 
 	total, err = m.GetCountByCondition(condition, pars)
 	if err != nil {
 		return
 	}
 	if total > 0 {
-		items, err = m.GetListByCondition(``, condition, pars, startSize, pageSize)
+		items, err = m.GetListByCondition(field, condition, pars, startSize, pageSize)
 	}
 
 	return

+ 292 - 0
models/rag/rag_eta_report_abstract.go

@@ -0,0 +1,292 @@
+package rag
+
+import (
+	"database/sql"
+	"eta/eta_api/global"
+	"eta/eta_api/utils"
+	"fmt"
+	"time"
+)
+
+// RagEtaReportAbstract 报告摘要
+type RagEtaReportAbstract struct {
+	RagEtaReportAbstractId int       `gorm:"primaryKey;column:rag_eta_report_abstract_id" description:"-"`
+	RagEtaReportId         int       `gorm:"column:rag_eta_report_id" description:"ETA报告id"`
+	Content                string    `gorm:"column:content" description:"摘要内容"`
+	QuestionId             int       `gorm:"column:question_id" description:"提示词Id"`
+	QuestionContent        string    `gorm:"column:question_content" description:"questionContent"`
+	Version                int       `gorm:"column:version" description:"版本号"`
+	Tags                   string    `gorm:"column:tags" description:"标签"`
+	TagsName               string    `gorm:"column:tags_name" description:"标签名,多个用英文逗号隔开"`
+	VectorKey              string    `gorm:"column:vector_key" description:"向量key标识"`
+	ModifyTime             time.Time `gorm:"column:modify_time" description:"modifyTime"`
+	CreateTime             time.Time `gorm:"column:create_time" description:"createTime"`
+}
+
+// TableName get sql table name.获取数据库表名
+func (m *RagEtaReportAbstract) TableName() string {
+	return "rag_eta_report_abstract"
+}
+
+// RagEtaReportAbstractColumns get sql column name.获取数据库列名
+var RagEtaReportAbstractColumns = struct {
+	RagEtaReportAbstractId string
+	RagEtaReportId         string
+	Content                string
+	QuestionId             string
+	QuestionContent        string
+	Version                string
+	Tags                   string
+	TagsName               string
+	VectorKey              string
+	ModifyTime             string
+	CreateTime             string
+}{
+	RagEtaReportAbstractId: "rag_eta_report_abstract_id",
+	RagEtaReportId:         "rag_eta_report_id",
+	Content:                "content",
+	QuestionId:             "question_id",
+	QuestionContent:        "question_content",
+	Version:                "version",
+	Tags:                   "tags",
+	TagsName:               "tags_name",
+	VectorKey:              "vector_key",
+	ModifyTime:             "modify_time",
+	CreateTime:             "create_time",
+}
+
+func (m *RagEtaReportAbstract) Create() (err error) {
+	err = global.DbMap[utils.DbNameAI].Create(&m).Error
+
+	return
+}
+
+func (m *RagEtaReportAbstract) Update(updateCols []string) (err error) {
+	err = global.DbMap[utils.DbNameAI].Select(updateCols).Updates(&m).Error
+
+	return
+}
+
+func (m *RagEtaReportAbstract) Del() (err error) {
+	err = global.DbMap[utils.DbNameAI].Delete(&m).Error
+
+	return
+}
+
+func (m *RagEtaReportAbstract) GetById(id int) (item *RagEtaReportAbstract, err error) {
+	err = global.DbMap[utils.DbNameAI].Where(fmt.Sprintf("%s = ?", RagEtaReportAbstractColumns.RagEtaReportAbstractId), id).First(&item).Error
+
+	return
+}
+
+func (m *RagEtaReportAbstract) GetByIdList(idList []int) (items []*RagEtaReportAbstract, err error) {
+	err = global.DbMap[utils.DbNameAI].Where(fmt.Sprintf("%s in (?) ", RagEtaReportAbstractColumns.RagEtaReportAbstractId), idList).Find(&items).Error
+
+	return
+}
+
+func (m *RagEtaReportAbstract) GetListByQuestionId(questionId int) (items []*RagEtaReportAbstract, err error) {
+	err = global.DbMap[utils.DbNameAI].Where(fmt.Sprintf("%s = ? ", RagEtaReportAbstractColumns.QuestionId), questionId).Find(&items).Error
+
+	return
+}
+
+func (m *RagEtaReportAbstract) GetListByCondition(field, condition string, pars []interface{}, startSize, pageSize int) (items []*RagEtaReportAbstract, err error) {
+	if field == "" {
+		field = "*"
+	}
+	sqlStr := fmt.Sprintf(`SELECT %s FROM %s WHERE 1=1 %s  order by rag_eta_report_abstract_id desc LIMIT ?,?`, field, m.TableName(), condition)
+	pars = append(pars, startSize, pageSize)
+	err = global.DbMap[utils.DbNameAI].Raw(sqlStr, pars...).Find(&items).Error
+
+	return
+}
+
+func (m *RagEtaReportAbstract) DelByIdList(idList []int) (err error) {
+	if len(idList) <= 0 {
+		return
+	}
+	sqlStr := fmt.Sprintf(`delete from %s where %s in (?)`, m.TableName(), RagEtaReportAbstractColumns.RagEtaReportAbstractId)
+	err = global.DbMap[utils.DbNameAI].Exec(sqlStr, idList).Error
+
+	return
+}
+
+// GetByRagEtaReportId
+// @Description: 根据报告id获取摘要
+// @author: Roc
+// @receiver m
+// @datetime 2025-03-07 10:00:59
+// @param id int
+// @return item *RagEtaReportAbstract
+// @return err error
+func (m *RagEtaReportAbstract) GetByRagEtaReportId(id int) (item *RagEtaReportAbstract, err error) {
+	err = global.DbMap[utils.DbNameAI].Where(fmt.Sprintf("%s = ?", RagEtaReportAbstractColumns.RagEtaReportId), id).Order(fmt.Sprintf(`%s DESC`, RagEtaReportAbstractColumns.RagEtaReportAbstractId)).First(&item).Error
+
+	return
+}
+
+// GetByRagEtaReportIdAndQuestionId
+// @Description: 根据报告id和提示词ID获取摘要
+// @author: Roc
+// @receiver m
+// @datetime 2025-04-17 17:39:27
+// @param articleId int
+// @param questionId int
+// @return item *RagEtaReportAbstract
+// @return err error
+func (m *RagEtaReportAbstract) GetByRagEtaReportIdAndQuestionId(articleId, questionId int) (item *RagEtaReportAbstract, err error) {
+	err = global.DbMap[utils.DbNameAI].Where(fmt.Sprintf("%s = ? AND %s = ? ", RagEtaReportAbstractColumns.RagEtaReportId, RagEtaReportAbstractColumns.QuestionId), articleId, questionId).Order(fmt.Sprintf(`%s DESC`, RagEtaReportAbstractColumns.RagEtaReportAbstractId)).First(&item).Error
+
+	return
+}
+
+type RagEtaReportAbstractView struct {
+	RagEtaReportAbstractId int    `gorm:"primaryKey;column:rag_eta_report_abstract_id" description:"-"`
+	RagEtaReportId         int    `gorm:"column:rag_eta_report_id" description:"ETA报告id"`
+	Abstract               string `gorm:"column:abstract;type:longtext;comment:摘要内容;" description:"摘要内容"`
+	QuestionId             int    `gorm:"column:question_id" description:"提示词Id"`
+	QuestionContent        string `gorm:"column:question_content" description:"questionContent"`
+	Version                int    `gorm:"column:version" description:"版本号"`
+	Tags                   string `gorm:"column:tags" description:"标签"`
+	TagsName               string `gorm:"column:tags_name" description:"标签名,多个用英文逗号隔开"`
+	VectorKey              string `gorm:"column:vector_key" description:"向量key标识"`
+	ModifyTime             string `gorm:"column:modify_time;type:datetime;default:NULL;" description:"modify_time"`
+	CreateTime             string `gorm:"column:create_time;type:datetime;default:NULL;" description:"create_time"`
+	Title                  string `gorm:"column:title;type:varchar(255);comment:标题;" description:"标题"`
+}
+
+type RagEtaReportAbstractItem struct {
+	RagEtaReportAbstractId int       `gorm:"primaryKey;column:rag_eta_report_abstract_id" description:"-"`
+	RagEtaReportId         int       `gorm:"column:rag_eta_report_id" description:"ETA报告id"`
+	Content                string    `gorm:"column:content" description:"摘要内容"`
+	QuestionId             int       `gorm:"column:question_id" description:"提示词Id"`
+	QuestionContent        string    `gorm:"column:question_content" description:"questionContent"`
+	Version                int       `gorm:"column:version" description:"版本号"`
+	Tags                   string    `gorm:"column:tags" description:"标签"`
+	TagsName               string    `gorm:"column:tags_name" description:"标签名,多个用英文逗号隔开"`
+	VectorKey              string    `gorm:"column:vector_key" description:"向量key标识"`
+	ModifyTime             time.Time `gorm:"column:modify_time;type:datetime;default:NULL;" description:"modify_time"`
+	CreateTime             time.Time `gorm:"column:create_time;type:datetime;default:NULL;" description:"create_time"`
+	Title                  string    `gorm:"column:title;type:varchar(255);comment:标题;" description:"标题"`
+}
+
+func (m *RagEtaReportAbstractItem) ToView() RagEtaReportAbstractView {
+	return RagEtaReportAbstractView{
+		RagEtaReportAbstractId: m.RagEtaReportAbstractId,
+		RagEtaReportId:         m.RagEtaReportId,
+		Abstract:               m.Content,
+		Version:                m.Version,
+		VectorKey:              m.VectorKey,
+		ModifyTime:             utils.DateStrToDateTimeStr(m.ModifyTime),
+		CreateTime:             utils.DateStrToDateTimeStr(m.CreateTime),
+		Title:                  m.Title,
+		QuestionId:             m.QuestionId,
+		Tags:                   m.Tags,
+		TagsName:               m.TagsName,
+		QuestionContent:        m.QuestionContent,
+	}
+}
+
+func (m *RagEtaReportAbstract) EtaReportAbstractItem(list []*RagEtaReportAbstractItem) (etaReportAbstractViewList []RagEtaReportAbstractView) {
+	etaReportAbstractViewList = make([]RagEtaReportAbstractView, 0)
+
+	for _, v := range list {
+		etaReportAbstractViewList = append(etaReportAbstractViewList, v.ToView())
+	}
+	return
+}
+
+func (m *RagEtaReportAbstract) GetListByPlatformCondition(field, condition string, pars []interface{}, startSize, pageSize int) (items []*RagEtaReportAbstractItem, err error) {
+	if field == "" {
+		field = "*"
+	}
+	sqlStr := fmt.Sprintf(`SELECT %s FROM %s AS a 
+          WHERE 1=1  %s order by a.modify_time DESC,a.rag_eta_report_abstract_id DESC LIMIT ?,?`, field, m.TableName(), condition)
+	pars = append(pars, startSize, pageSize)
+	err = global.DbMap[utils.DbNameAI].Raw(sqlStr, pars...).Find(&items).Error
+
+	return
+}
+
+func (m *RagEtaReportAbstract) GetCountByPlatformCondition(condition string, pars []interface{}) (total int, err error) {
+	var intNull sql.NullInt64
+	sqlStr := fmt.Sprintf(`SELECT COUNT(1) total FROM %s AS a 
+          WHERE 1=1  %s`, m.TableName(), condition)
+	err = global.DbMap[utils.DbNameAI].Raw(sqlStr, pars...).Scan(&intNull).Error
+	if err == nil && intNull.Valid {
+		total = int(intNull.Int64)
+	}
+
+	return
+}
+
+func (m *RagEtaReportAbstract) GetPageListByPlatformCondition(condition string, pars []interface{}, startSize, pageSize int) (total int, items []*RagEtaReportAbstractItem, err error) {
+
+	total, err = m.GetCountByPlatformCondition(condition, pars)
+	if err != nil {
+		return
+	}
+	if total > 0 {
+		items, err = m.GetListByPlatformCondition(``, condition, pars, startSize, pageSize)
+	}
+
+	return
+}
+
+func (m *RagEtaReportAbstract) GetListByTagAndPlatformCondition(field, condition string, pars []interface{}, startSize, pageSize int) (items []*RagEtaReportAbstractItem, err error) {
+	if field == "" {
+		field = "*"
+	}
+	sqlStr := fmt.Sprintf(`SELECT %s FROM %s AS a 
+          JOIN wechat_article AS b ON a.rag_eta_report_id=b.rag_eta_report_id
+          JOIN wechat_platform AS c ON b.wechat_platform_id=c.wechat_platform_id
+          JOIN wechat_platform_tag_mapping AS d ON c.wechat_platform_id=d.wechat_platform_id
+          WHERE 1=1 AND b.is_deleted=0 %s  order by a.modify_time DESC,a.rag_eta_report_abstract_id DESC LIMIT ?,?`, field, m.TableName(), condition)
+	pars = append(pars, startSize, pageSize)
+	err = global.DbMap[utils.DbNameAI].Raw(sqlStr, pars...).Find(&items).Error
+
+	return
+}
+
+func (m *RagEtaReportAbstract) GetCountByTagAndPlatformCondition(condition string, pars []interface{}) (total int, err error) {
+	var intNull sql.NullInt64
+	sqlStr := fmt.Sprintf(`SELECT COUNT(1) total FROM %s AS a 
+          JOIN wechat_article AS b ON a.rag_eta_report_id=b.rag_eta_report_id
+          JOIN wechat_platform AS c ON b.wechat_platform_id=c.wechat_platform_id
+          JOIN wechat_platform_tag_mapping AS d ON c.wechat_platform_id=d.wechat_platform_id
+          WHERE 1=1 AND b.is_deleted=0 %s`, m.TableName(), condition)
+	err = global.DbMap[utils.DbNameAI].Raw(sqlStr, pars...).Scan(&intNull).Error
+	if err == nil && intNull.Valid {
+		total = int(intNull.Int64)
+	}
+
+	return
+}
+
+func (m *RagEtaReportAbstract) GetPageListByTagAndPlatformCondition(condition string, pars []interface{}, startSize, pageSize int) (total int, items []*RagEtaReportAbstractItem, err error) {
+
+	total, err = m.GetCountByTagAndPlatformCondition(condition, pars)
+	if err != nil {
+		return
+	}
+	if total > 0 {
+		items, err = m.GetListByTagAndPlatformCondition(`a.rag_eta_report_abstract_id,a.rag_eta_report_id,a.content AS abstract,a.version,a.vector_key,a.modify_time,a.create_time,b.title,b.link,d.tag_id`, condition, pars, startSize, pageSize)
+	}
+
+	return
+}
+
+// DelVectorKey
+// @Description: 批量删除向量库
+// @author: Roc
+// @receiver m
+// @datetime 2025-03-12 16:47:52
+// @param ragEtaReportAbstractIdList []int
+// @return err error
+func (m *RagEtaReportAbstract) DelVectorKey(ragEtaReportAbstractIdList []int) (err error) {
+	sqlStr := fmt.Sprintf(`UPDATE %s set vector_key = '' WHERE rag_eta_report_abstract_id IN (?)`, m.TableName())
+	err = global.DbMap[utils.DbNameAI].Exec(sqlStr, ragEtaReportAbstractIdList).Error
+
+	return
+}

+ 9 - 0
models/rag/request/rag_eta_report.go

@@ -0,0 +1,9 @@
+package request
+
+type BeachOpRagEtaReportAbstractReq struct {
+	RagEtaReportAbstractIdList    []int  `description:"摘要id"`
+	NotRagEtaReportAbstractIdList []int  `description:"不需要的摘要id"`
+	KeyWord                       string `description:"关键字"`
+	TagId                         string `description:"标签id"`
+	IsSelectAll                   bool   `description:"是否选择所有摘要"`
+}

+ 1 - 1
models/rag/request/wechat_platform.go

@@ -28,6 +28,6 @@ type BeachOpAbstractReq struct {
 	WechatArticleAbstractIdList    []int  `description:"摘要id"`
 	NotWechatArticleAbstractIdList []int  `description:"不需要的摘要id"`
 	KeyWord                        string `description:"关键字"`
-	TagId                          int    `description:"标签id"`
+	TagId                          string `description:"标签id"`
 	IsSelectAll                    bool   `description:"是否选择所有摘要"`
 }

+ 15 - 0
models/rag/response/abstract.go

@@ -9,3 +9,18 @@ type AbstractListListResp struct {
 	List   []rag.WechatArticleAbstractView
 	Paging *paging.PagingItem `description:"分页数据"`
 }
+
+type RagEtaReportAbstractListListResp struct {
+	List   []rag.RagEtaReportAbstractView
+	Paging *paging.PagingItem `description:"分页数据"`
+}
+
+type RagEtaReportItemAbstractListListResp struct {
+	List   []rag.RagEtaReportAbstractView
+	Paging *paging.PagingItem `description:"分页数据"`
+}
+
+type WechatArticleItemAbstractListListResp struct {
+	List   []rag.WechatArticleAbstractView
+	Paging *paging.PagingItem `description:"分页数据"`
+}

+ 5 - 0
models/rag/response/question.go

@@ -9,3 +9,8 @@ type QuestionListListResp struct {
 	List   []rag.QuestionView
 	Paging *paging.PagingItem `description:"分页数据"`
 }
+
+type QuestionOpAuthResp struct {
+	Status string `description:"状态"`
+	Tip    string `description:"提示信息"`
+}

+ 23 - 1
models/rag/tag.go

@@ -5,6 +5,7 @@ import (
 	"eta/eta_api/global"
 	"eta/eta_api/utils"
 	"fmt"
+	"strings"
 	"time"
 )
 
@@ -37,6 +38,12 @@ var TagColumns = struct {
 	CreateTime: "create_time",
 }
 
+func (m *Tag) Create() (err error) {
+	err = global.DbMap[utils.DbNameAI].Create(&m).Error
+
+	return
+}
+
 func (m *Tag) GetByID(TagId int) (item *Tag, err error) {
 	err = global.DbMap[utils.DbNameAI].Where(fmt.Sprintf("%s = ?", TagColumns.TagID), TagId).First(&item).Error
 
@@ -70,7 +77,6 @@ func (m *Tag) GetCountByCondition(condition string, pars []interface{}) (total i
 }
 
 func (m *Tag) GetPageListByCondition(condition string, pars []interface{}, startSize, pageSize int) (total int, items []*Tag, err error) {
-
 	total, err = m.GetCountByCondition(condition, pars)
 	if err != nil {
 		return
@@ -81,3 +87,19 @@ func (m *Tag) GetPageListByCondition(condition string, pars []interface{}, start
 
 	return
 }
+
+var aiAbstractTagMap map[string]int
+
+func (m *Tag) GetTagIdByName(tagName string) (tagId int, err error) {
+	tagName = strings.TrimSpace(tagName)
+	tagId, ok := aiAbstractTagMap[tagName]
+	if ok {
+		return
+	}
+
+	var item *Tag
+	sqlStr := fmt.Sprintf(`SELECT * FROM %s WHERE %s = ? `, m.TableName(), TagColumns.TagName)
+	err = global.DbMap[utils.DbNameAI].Raw(sqlStr, tagName).First(&item).Error
+
+	return
+}

+ 82 - 12
models/rag/wechat_article_abstract.go

@@ -11,11 +11,15 @@ import (
 type WechatArticleAbstract struct {
 	WechatArticleAbstractId int       `gorm:"column:wechat_article_abstract_id;type:int(9) UNSIGNED;primaryKey;not null;" description:"wechat_article_abstract_id"`
 	WechatArticleId         int       `gorm:"column:wechat_article_id;type:int(9) UNSIGNED;comment:关联的微信报告id;default:0;" description:"关联的微信报告id"`
-	Content                 string    `gorm:"column:content;type:longtext;comment:摘要内容;" description:"content"` // 摘要内容
+	Content                 string    `gorm:"column:content;type:longtext;comment:摘要内容;" description:"摘要内容"`
 	Version                 int       `gorm:"column:version;type:int(10) UNSIGNED;comment:版本号;default:1;" description:"版本号"`
 	VectorKey               string    `gorm:"column:vector_key;type:varchar(255);comment:向量key标识;" description:"向量key标识"`
 	ModifyTime              time.Time `gorm:"column:modify_time;type:datetime;default:NULL;" description:"modify_time"`
 	CreateTime              time.Time `gorm:"column:create_time;type:datetime;default:NULL;" description:"create_time"`
+	QuestionId              int       `gorm:"column:question_id" description:"提示词Id"`
+	Tags                    string    `gorm:"column:tags" description:"标签"`
+	TagsName                string    `gorm:"column:tags_name" description:"标签名,多个用英文逗号隔开"`
+	QuestionContent         string    `gorm:"column:question_content" description:"提示词内容"`
 }
 
 // TableName get sql table name.获取数据库表名
@@ -27,6 +31,10 @@ func (m *WechatArticleAbstract) TableName() string {
 var WechatArticleAbstractColumns = struct {
 	WechatArticleAbstractID string
 	WechatArticleID         string
+	QuestionId              string
+	Tags                    string
+	TagsName                string
+	QuestionContent         string
 	Content                 string
 	Version                 string
 	ModifyTime              string
@@ -34,6 +42,9 @@ var WechatArticleAbstractColumns = struct {
 }{
 	WechatArticleAbstractID: "wechat_article_abstract_id",
 	WechatArticleID:         "wechat_article_id",
+	QuestionId:              "question_id",
+	TagsName:                "tags_name",
+	QuestionContent:         "question_content",
 	Content:                 "content",
 	Version:                 "version",
 	ModifyTime:              "modify_time",
@@ -70,6 +81,12 @@ func (m *WechatArticleAbstract) GetByIdList(idList []int) (items []*WechatArticl
 	return
 }
 
+func (m *WechatArticleAbstract) GetListByQuestionId(questionId int) (items []*WechatArticleAbstract, err error) {
+	err = global.DbMap[utils.DbNameAI].Where(fmt.Sprintf("%s = ? ", WechatArticleAbstractColumns.QuestionId), questionId).Find(&items).Error
+
+	return
+}
+
 func (m *WechatArticleAbstract) GetListByCondition(field, condition string, pars []interface{}, startSize, pageSize int) (items []*WechatArticleAbstract, err error) {
 	if field == "" {
 		field = "*"
@@ -105,6 +122,21 @@ func (m *WechatArticleAbstract) GetByWechatArticleId(id int) (item *WechatArticl
 	return
 }
 
+// GetByWechatArticleIdAndQuestionId
+// @Description: 根据报告id和提示词ID获取摘要
+// @author: Roc
+// @receiver m
+// @datetime 2025-04-17 17:39:27
+// @param articleId int
+// @param questionId int
+// @return item *WechatArticleAbstract
+// @return err error
+func (m *WechatArticleAbstract) GetByWechatArticleIdAndQuestionId(articleId, questionId int) (item *WechatArticleAbstract, err error) {
+	err = global.DbMap[utils.DbNameAI].Where(fmt.Sprintf("%s = ? AND %s = ? ", WechatArticleAbstractColumns.WechatArticleID, WechatArticleAbstractColumns.QuestionId), articleId, questionId).Order(fmt.Sprintf(`%s DESC`, WechatArticleAbstractColumns.WechatArticleAbstractID)).First(&item).Error
+
+	return
+}
+
 type WechatArticleAbstractView struct {
 	WechatArticleAbstractId int    `gorm:"column:wechat_article_abstract_id;type:int(9) UNSIGNED;primaryKey;not null;" description:"wechat_article_abstract_id"`
 	WechatArticleId         int    `gorm:"column:wechat_article_id;type:int(9) UNSIGNED;comment:关联的微信报告id;default:0;" description:"关联的微信报告id"`
@@ -117,12 +149,16 @@ type WechatArticleAbstractView struct {
 	Title                   string `gorm:"column:title;type:varchar(255);comment:标题;" description:"标题"`
 	Link                    string `gorm:"column:link;type:varchar(255);comment:链接;" description:"链接"`
 	TagId                   int    `gorm:"column:tag_id;type:int(9) UNSIGNED;comment:品种id;default:0;" description:"品种id"`
+	QuestionId              int    `gorm:"column:question_id" description:"提示词Id"`
+	Tags                    string `gorm:"column:tags" description:"标签"`
+	TagsName                string `gorm:"column:tags_name" description:"标签名,多个用英文逗号隔开"`
+	QuestionContent         string `gorm:"column:question_content" description:"提示词内容"`
 }
 
 type WechatArticleAbstractItem struct {
 	WechatArticleAbstractId int       `gorm:"column:wechat_article_abstract_id;type:int(9) UNSIGNED;primaryKey;not null;" description:"wechat_article_abstract_id"`
 	WechatArticleId         int       `gorm:"column:wechat_article_id;type:int(9) UNSIGNED;comment:关联的微信报告id;default:0;" description:"关联的微信报告id"`
-	Abstract                string    `gorm:"column:abstract;type:longtext;comment:摘要内容;" description:"摘要内容"` //
+	Content                 string    `gorm:"column:content;type:longtext;comment:摘要内容;" description:"摘要内容"`
 	Version                 int       `gorm:"column:version;type:int(10) UNSIGNED;comment:版本号;default:1;" description:"版本号"`
 	VectorKey               string    `gorm:"column:vector_key;type:varchar(255);comment:向量key标识;" description:"向量key标识"`
 	ModifyTime              time.Time `gorm:"column:modify_time;type:datetime;default:NULL;" description:"modify_time"`
@@ -130,13 +166,17 @@ type WechatArticleAbstractItem struct {
 	Title                   string    `gorm:"column:title;type:varchar(255);comment:标题;" description:"标题"`
 	Link                    string    `gorm:"column:link;type:varchar(255);comment:链接;" description:"链接"`
 	TagId                   int       `gorm:"column:tag_id;type:int(9) UNSIGNED;comment:品种id;default:0;" description:"品种id"`
+	QuestionId              int       `gorm:"column:question_id" description:"提示词Id"`
+	Tags                    string    `gorm:"column:tags" description:"标签"`
+	TagsName                string    `gorm:"column:tags_name" description:"标签名,多个用英文逗号隔开"`
+	QuestionContent         string    `gorm:"column:question_content" description:"提示词内容"`
 }
 
 func (m *WechatArticleAbstractItem) ToView() WechatArticleAbstractView {
 	return WechatArticleAbstractView{
 		WechatArticleAbstractId: m.WechatArticleAbstractId,
 		WechatArticleId:         m.WechatArticleId,
-		Abstract:                m.Abstract,
+		Abstract:                m.Content,
 		Version:                 m.Version,
 		VectorKey:               m.VectorKey,
 		ModifyTime:              utils.DateStrToDateTimeStr(m.ModifyTime),
@@ -144,16 +184,47 @@ func (m *WechatArticleAbstractItem) ToView() WechatArticleAbstractView {
 		Title:                   m.Title,
 		Link:                    m.Link,
 		TagId:                   m.TagId,
+		QuestionId:              m.QuestionId,
+		Tags:                    m.Tags,
+		TagsName:                m.TagsName,
+		QuestionContent:         m.QuestionContent,
 	}
 }
 
-func (m *WechatArticleAbstract) WechatArticleAbstractItem(list []*WechatArticleAbstractItem) (wechatArticleViewList []WechatArticleAbstractView) {
-	wechatArticleViewList = make([]WechatArticleAbstractView, 0)
+type WechatPlatArticleAbstractItem struct {
+	WechatArticleAbstractId int       `gorm:"column:wechat_article_abstract_id;type:int(9) UNSIGNED;primaryKey;not null;" description:"wechat_article_abstract_id"`
+	WechatArticleId         int       `gorm:"column:wechat_article_id;type:int(9) UNSIGNED;comment:关联的微信报告id;default:0;" description:"关联的微信报告id"`
+	Abstract                string    `gorm:"column:abstract;type:longtext;comment:摘要内容;" description:"摘要内容"` //
+	Version                 int       `gorm:"column:version;type:int(10) UNSIGNED;comment:版本号;default:1;" description:"版本号"`
+	VectorKey               string    `gorm:"column:vector_key;type:varchar(255);comment:向量key标识;" description:"向量key标识"`
+	ModifyTime              time.Time `gorm:"column:modify_time;type:datetime;default:NULL;" description:"modify_time"`
+	CreateTime              time.Time `gorm:"column:create_time;type:datetime;default:NULL;" description:"create_time"`
+	Title                   string    `gorm:"column:title;type:varchar(255);comment:标题;" description:"标题"`
+	Link                    string    `gorm:"column:link;type:varchar(255);comment:链接;" description:"链接"`
+	TagId                   int       `gorm:"column:tag_id;type:int(9) UNSIGNED;comment:品种id;default:0;" description:"品种id"`
+	QuestionId              int       `gorm:"column:question_id" description:"提示词Id"`
+	Tags                    string    `gorm:"column:tags" description:"标签"`
+	TagsName                string    `gorm:"column:tags_name" description:"标签名,多个用英文逗号隔开"`
+	QuestionContent         string    `gorm:"column:question_content" description:"提示词内容"`
+}
 
-	for _, v := range list {
-		wechatArticleViewList = append(wechatArticleViewList, v.ToView())
+func (m *WechatPlatArticleAbstractItem) ToView() WechatArticleAbstractView {
+	return WechatArticleAbstractView{
+		WechatArticleAbstractId: m.WechatArticleAbstractId,
+		WechatArticleId:         m.WechatArticleId,
+		Abstract:                m.Abstract,
+		Version:                 m.Version,
+		VectorKey:               m.VectorKey,
+		ModifyTime:              utils.DateStrToDateTimeStr(m.ModifyTime),
+		CreateTime:              utils.DateStrToDateTimeStr(m.CreateTime),
+		Title:                   m.Title,
+		Link:                    m.Link,
+		TagId:                   m.TagId,
+		QuestionId:              m.QuestionId,
+		Tags:                    m.Tags,
+		TagsName:                m.TagsName,
+		QuestionContent:         m.QuestionContent,
 	}
-	return
 }
 
 func (m *WechatArticleAbstract) GetListByPlatformCondition(field, condition string, pars []interface{}, startSize, pageSize int) (items []*WechatArticleAbstractItem, err error) {
@@ -185,19 +256,18 @@ func (m *WechatArticleAbstract) GetCountByPlatformCondition(condition string, pa
 }
 
 func (m *WechatArticleAbstract) GetPageListByPlatformCondition(condition string, pars []interface{}, startSize, pageSize int) (total int, items []*WechatArticleAbstractItem, err error) {
-
 	total, err = m.GetCountByPlatformCondition(condition, pars)
 	if err != nil {
 		return
 	}
 	if total > 0 {
-		items, err = m.GetListByPlatformCondition(`a.wechat_article_abstract_id,a.wechat_article_id,a.content AS abstract,a.version,a.vector_key,b.title,b.link,a.modify_time,a.create_time`, condition, pars, startSize, pageSize)
+		items, err = m.GetListByPlatformCondition(`a.*`, condition, pars, startSize, pageSize)
 	}
 
 	return
 }
 
-func (m *WechatArticleAbstract) GetListByTagAndPlatformCondition(field, condition string, pars []interface{}, startSize, pageSize int) (items []*WechatArticleAbstractItem, err error) {
+func (m *WechatArticleAbstract) GetListByTagAndPlatformCondition(field, condition string, pars []interface{}, startSize, pageSize int) (items []*WechatPlatArticleAbstractItem, err error) {
 	if field == "" {
 		field = "*"
 	}
@@ -227,7 +297,7 @@ func (m *WechatArticleAbstract) GetCountByTagAndPlatformCondition(condition stri
 	return
 }
 
-func (m *WechatArticleAbstract) GetPageListByTagAndPlatformCondition(condition string, pars []interface{}, startSize, pageSize int) (total int, items []*WechatArticleAbstractItem, err error) {
+func (m *WechatArticleAbstract) GetPageListByTagAndPlatformCondition(condition string, pars []interface{}, startSize, pageSize int) (total int, items []*WechatPlatArticleAbstractItem, err error) {
 
 	total, err = m.GetCountByTagAndPlatformCondition(condition, pars)
 	if err != nil {

+ 99 - 0
routers/commentsRouter.go

@@ -8809,6 +8809,15 @@ func init() {
             Filters: nil,
             Params: nil})
 
+    beego.GlobalControllerRouter["eta/eta_api/controllers/llm:QuestionController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/llm:QuestionController"],
+        beego.ControllerComments{
+            Method: "GenerateAbstract",
+            Router: `/question/abstract/generate`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
     beego.GlobalControllerRouter["eta/eta_api/controllers/llm:QuestionController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/llm:QuestionController"],
         beego.ControllerComments{
             Method: "Add",
@@ -8818,6 +8827,24 @@ func init() {
             Filters: nil,
             Params: nil})
 
+    beego.GlobalControllerRouter["eta/eta_api/controllers/llm:QuestionController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/llm:QuestionController"],
+        beego.ControllerComments{
+            Method: "SetDefault",
+            Router: `/question/default/set`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/llm:QuestionController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/llm:QuestionController"],
+        beego.ControllerComments{
+            Method: "UnSetDefault",
+            Router: `/question/default/unset`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
     beego.GlobalControllerRouter["eta/eta_api/controllers/llm:QuestionController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/llm:QuestionController"],
         beego.ControllerComments{
             Method: "Del",
@@ -8854,6 +8881,15 @@ func init() {
             Filters: nil,
             Params: nil})
 
+    beego.GlobalControllerRouter["eta/eta_api/controllers/llm:QuestionController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/llm:QuestionController"],
+        beego.ControllerComments{
+            Method: "CheckOpAuth",
+            Router: `/question/op_auth/check`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
     beego.GlobalControllerRouter["eta/eta_api/controllers/llm:QuestionController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/llm:QuestionController"],
         beego.ControllerComments{
             Method: "TitleList",
@@ -8863,6 +8899,51 @@ func init() {
             Filters: nil,
             Params: nil})
 
+    beego.GlobalControllerRouter["eta/eta_api/controllers/llm:RagEtaReportAbstractController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/llm:RagEtaReportAbstractController"],
+        beego.ControllerComments{
+            Method: "Del",
+            Router: `/abstract/eta_report/del`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/llm:RagEtaReportAbstractController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/llm:RagEtaReportAbstractController"],
+        beego.ControllerComments{
+            Method: "List",
+            Router: `/abstract/eta_report/list`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/llm:RagEtaReportAbstractController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/llm:RagEtaReportAbstractController"],
+        beego.ControllerComments{
+            Method: "AddVector",
+            Router: `/abstract/eta_report/vector/add`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/llm:RagEtaReportAbstractController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/llm:RagEtaReportAbstractController"],
+        beego.ControllerComments{
+            Method: "VectorDel",
+            Router: `/abstract/eta_report/vector/del`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/llm:RagEtaReportController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/llm:RagEtaReportController"],
+        beego.ControllerComments{
+            Method: "AbstractList",
+            Router: `/eta_report/article/abstract/list`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
     beego.GlobalControllerRouter["eta/eta_api/controllers/llm:RagEtaReportController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/llm:RagEtaReportController"],
         beego.ControllerComments{
             Method: "ArticleDel",
@@ -8944,6 +9025,15 @@ func init() {
             Filters: nil,
             Params: nil})
 
+    beego.GlobalControllerRouter["eta/eta_api/controllers/llm:UserChatController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/llm:UserChatController"],
+        beego.ControllerComments{
+            Method: "KnowledgeList",
+            Router: `/knowledge/list`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
     beego.GlobalControllerRouter["eta/eta_api/controllers/llm:WechatPlatformController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/llm:WechatPlatformController"],
         beego.ControllerComments{
             Method: "TagList",
@@ -8962,6 +9052,15 @@ func init() {
             Filters: nil,
             Params: nil})
 
+    beego.GlobalControllerRouter["eta/eta_api/controllers/llm:WechatPlatformController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/llm:WechatPlatformController"],
+        beego.ControllerComments{
+            Method: "AbstractList",
+            Router: `/wechat_platform/article/abstract/list`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
     beego.GlobalControllerRouter["eta/eta_api/controllers/llm:WechatPlatformController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/llm:WechatPlatformController"],
         beego.ControllerComments{
             Method: "ArticleDel",

+ 1 - 0
routers/router.go

@@ -80,6 +80,7 @@ func init() {
 				&llm.AbstractController{},
 				&llm.PromoteController{},
 				&llm.RagEtaReportController{},
+				&llm.RagEtaReportAbstractController{},
 			),
 		),
 		web.NSNamespace("/banner",

+ 1 - 0
services/crm_eta.go

@@ -121,6 +121,7 @@ type GetCrmTokenData struct {
 	AdminId         int    `description:"系统用户id"`
 	ProductName     string `description:"产品名称:admin,ficc,权益"`
 	Authority       int    `description:"管理权限,0:无,1:部门负责人,2:小组负责人,或者ficc销售主管,4:ficc销售组长"`
+	Enabled         int    `description:"禁启用状态:0-禁用;1-启用"`
 }
 
 // CodeLoginFromMiddleServer 中间服务-编码登录

+ 317 - 0
services/elastic/rag_eta_report_abstract.go

@@ -0,0 +1,317 @@
+package elastic
+
+import (
+	"context"
+	"encoding/json"
+	"eta/eta_api/models/rag"
+	"eta/eta_api/utils"
+	"fmt"
+	"github.com/olivere/elastic/v7"
+	"strings"
+	"time"
+)
+
+// 摘要索引
+var EsRagEtaReportAbstractName = utils.EsRagEtaReportAbstractName
+
+type RagEtaReportAbstractItem struct {
+	RagEtaReportAbstractId int       `gorm:"primaryKey;column:rag_eta_report_abstract_id" description:"-"`
+	RagEtaReportId         int       `gorm:"column:rag_eta_report_id" description:"ETA报告id"`
+	Abstract               string    `gorm:"column:abstract;type:longtext;comment:摘要内容;" description:"摘要内容"`
+	QuestionId             int       `gorm:"column:question_id" description:"提示词Id"`
+	Version                int       `gorm:"column:version" description:"版本号"`
+	VectorKey              string    `gorm:"column:vector_key" description:"向量key标识"`
+	ModifyTime             time.Time `gorm:"column:modify_time" description:"modifyTime"`
+	CreateTime             time.Time `gorm:"column:create_time" description:"createTime"`
+	Title                  string    `gorm:"column:title;type:varchar(255);comment:标题;" description:"标题"`
+	TagIdList              []int     `description:"品种id列表"`
+	TagNameList            []string  `description:"品种名称列表"`
+}
+
+func (m *RagEtaReportAbstractItem) ToView() rag.RagEtaReportAbstractView {
+	var modifyTime, createTime string
+
+	if !m.CreateTime.IsZero() {
+		createTime = m.CreateTime.Format(utils.FormatDateTime)
+	}
+	if !m.ModifyTime.IsZero() {
+		modifyTime = m.ModifyTime.Format(utils.FormatDateTime)
+	}
+
+	//tagId := 0
+	//if len(m.TagIdList) > 0 {
+	//	tagId = m.TagIdList[0]
+	//}
+	tagsName := ``
+	if len(m.TagNameList) > 0 {
+		tagsName = strings.Join(m.TagNameList, `,`)
+	}
+
+	return rag.RagEtaReportAbstractView{
+		RagEtaReportAbstractId: m.RagEtaReportAbstractId,
+		RagEtaReportId:         m.RagEtaReportId,
+		Abstract:               m.Abstract,
+		QuestionId:             m.QuestionId,
+		//QuestionContent:        m.,
+		Version: m.Version,
+		//Tags:                   m.Ta,
+		VectorKey:  m.VectorKey,
+		ModifyTime: modifyTime,
+		CreateTime: createTime,
+		Title:      m.Title,
+		TagsName:   tagsName,
+	}
+}
+
+func (m *RagEtaReportAbstractItem) ToViewList(list []*RagEtaReportAbstractItem) (wechatArticleViewList []rag.RagEtaReportAbstractView) {
+	wechatArticleViewList = make([]rag.RagEtaReportAbstractView, 0)
+
+	for _, v := range list {
+		wechatArticleViewList = append(wechatArticleViewList, v.ToView())
+	}
+	return
+}
+
+// RagEtaReportEsAddOrEdit
+// @Description: 新增/编辑微信文章
+// @author: Roc
+// @datetime 2025-03-13 10:24:05
+// @param docId string
+// @param item RagEtaReportAndPlatform
+// @return err error
+func RagEtaReportAbstractEsAddOrEdit(docId string, item RagEtaReportAbstractItem) (err error) {
+	if docId == "" {
+		return
+	}
+	if EsRagEtaReportAbstractName == `` {
+		return
+	}
+	defer func() {
+		if err != nil {
+			fmt.Println("RagEtaReportEsAddOrEdit Err:", err.Error())
+		}
+	}()
+	client := utils.EsClient
+
+	resp, err := client.Index().Index(EsRagEtaReportAbstractName).Id(docId).BodyJson(item).Refresh("true").Do(context.Background())
+	if err != nil {
+		fmt.Println("新增失败:", err.Error())
+		return err
+	}
+	if resp.Status == 0 {
+		fmt.Println("新增成功", resp.Result)
+		err = nil
+	} else {
+		fmt.Println("RagEtaReportEsAddOrEdit", resp.Status, resp.Result)
+	}
+
+	return
+}
+
+// RagEtaReportEsDel
+// @Description: 删除微信文章
+// @author: Roc
+// @datetime 2025-03-13 10:23:55
+// @param docId string
+// @return err error
+func RagEtaReportAbstractEsDel(docId string) (err error) {
+	if docId == "" {
+		return
+	}
+	if EsRagEtaReportAbstractName == `` {
+		return
+	}
+	defer func() {
+		if err != nil {
+			fmt.Println("EsDeleteEdbInfoData Err:", err.Error())
+		}
+	}()
+	client := utils.EsClient
+
+	resp, err := client.Delete().Index(EsRagEtaReportAbstractName).Id(docId).Refresh(`true`).Do(context.Background())
+	if err != nil {
+		return
+	}
+	if resp.Status == 0 {
+		fmt.Println("删除成功")
+	} else {
+		fmt.Println("RagEtaReportEsDel", resp.Status, resp.Result)
+	}
+
+	return
+}
+
+// RagEtaReportAbstractEsSearch
+// @Description: 搜索
+// @author: Roc
+// @datetime 2025-03-13 19:54:54
+// @param keywordStr string
+// @param tagIdList []int
+// @param from int
+// @param size int
+// @param sortMap map[string]string
+// @return total int64
+// @return list []*RagEtaReportAbstractItem
+// @return err error
+func RagEtaReportAbstractEsSearch(keywordStr string, tagIdList []int, questionId, from, size int, sortMap map[string]string) (total int64, list []*RagEtaReportAbstractItem, err error) {
+	indexName := EsRagEtaReportAbstractName
+	list = make([]*RagEtaReportAbstractItem, 0)
+	defer func() {
+		if err != nil {
+			fmt.Println("RagEtaReportAbstractEsSearch Err:", err.Error())
+		}
+	}()
+
+	query := elastic.NewBoolQuery()
+
+	if len(tagIdList) > 0 {
+		termsList := make([]interface{}, 0)
+		for _, v := range tagIdList {
+			termsList = append(termsList, v)
+		}
+		query = query.Must(elastic.NewTermsQuery("TagIdList", termsList...))
+	}
+
+	// 提示词id
+	if questionId > 0 {
+		query = query.Must(elastic.NewTermsQuery("QuestionId", questionId))
+	}
+
+	// 名字匹配
+	if keywordStr != `` {
+		query = query.Must(elastic.NewMultiMatchQuery(keywordStr, "Abstract"))
+	}
+
+	// 排序
+	sortList := make([]*elastic.FieldSort, 0)
+	// 如果没有关键字,那么就走指标id倒序
+
+	for orderKey, orderType := range sortMap {
+		switch orderType {
+		case "asc":
+			sortList = append(sortList, elastic.NewFieldSort(orderKey).Asc())
+		case "desc":
+			sortList = append(sortList, elastic.NewFieldSort(orderKey).Desc())
+
+		}
+
+	}
+
+	return searchRagEtaReportAbstract(indexName, query, sortList, from, size)
+}
+
+// searchEdbInfoDataV2 查询es中的数据
+func searchRagEtaReportAbstract(indexName string, query elastic.Query, sortList []*elastic.FieldSort, from, size int) (total int64, list []*RagEtaReportAbstractItem, err error) {
+	total, err = searchRagEtaReportAbstractTotal(indexName, query)
+	if err != nil {
+		return
+	}
+
+	// 获取列表数据
+	list, err = searchRagEtaReportAbstractList(indexName, query, sortList, from, size)
+	if err != nil {
+		return
+	}
+
+	return
+}
+
+// searchEdbInfoDataTotal
+// @Description: 查询es中的数量
+// @author: Roc
+// @datetime 2024-12-23 11:19:04
+// @param indexName string
+// @param query elastic.Query
+// @return total int64
+// @return err error
+func searchRagEtaReportAbstractTotal(indexName string, query elastic.Query) (total int64, err error) {
+	defer func() {
+		if err != nil {
+			fmt.Println("searchRagEtaReportAbstractTotal Err:", err.Error())
+		}
+	}()
+	client := utils.EsClient
+
+	//根据条件数量统计
+	requestTotalHits := client.Count(indexName).Query(query)
+	total, err = requestTotalHits.Do(context.Background())
+	if err != nil {
+		return
+	}
+
+	return
+}
+
+// searchEdbInfoDataList
+// @Description: 查询es中的明细数据
+// @author: Roc
+// @datetime 2024-12-23 11:18:48
+// @param indexName string
+// @param query elastic.Query
+// @param sortList []*elastic.FieldSort
+// @param from int
+// @param size int
+// @return list []*data_manage.EdbInfoList
+// @return err error
+func searchRagEtaReportAbstractList(indexName string, query elastic.Query, sortList []*elastic.FieldSort, from, size int) (list []*RagEtaReportAbstractItem, err error) {
+	list = make([]*RagEtaReportAbstractItem, 0)
+	defer func() {
+		if err != nil {
+			fmt.Println("searchRagEtaReportAbstractList Err:", err.Error())
+		}
+	}()
+	client := utils.EsClient
+	// 高亮
+	highlight := elastic.NewHighlight()
+	highlight = highlight.Fields(elastic.NewHighlighterField("Content"))
+	highlight = highlight.PreTags("<font color='red'>").PostTags("</font>")
+
+	//request := client.Search(indexName).Highlight(highlight).From(from).Size(size) // sets the JSON request
+	request := client.Search(indexName).From(from).Size(size) // sets the JSON request
+
+	// 如果有指定排序,那么就按照排序来
+	if len(sortList) > 0 {
+		for _, v := range sortList {
+			request = request.SortBy(v)
+		}
+	}
+
+	searchMap := make(map[string]string)
+
+	searchResp, err := request.Query(query).Do(context.Background())
+	if err != nil {
+		return
+	}
+	//fmt.Println(searchResp)
+	//fmt.Println(searchResp.Status)
+	if searchResp.Status != 0 {
+		return
+	}
+	//total = searchResp.TotalHits()
+	if searchResp.Hits != nil {
+		for _, v := range searchResp.Hits.Hits {
+			if _, ok := searchMap[v.Id]; !ok {
+				itemJson, tmpErr := v.Source.MarshalJSON()
+				if tmpErr != nil {
+					err = tmpErr
+					fmt.Println("movieJson err:", err)
+					return
+				}
+				item := new(RagEtaReportAbstractItem)
+				tmpErr = json.Unmarshal(itemJson, &item)
+				if tmpErr != nil {
+					fmt.Println("json.Unmarshal movieJson err:", tmpErr)
+					err = tmpErr
+					return
+				}
+				if len(v.Highlight["Content"]) > 0 {
+					item.Abstract = v.Highlight["Content"][0]
+				}
+				list = append(list, item)
+				searchMap[v.Id] = v.Id
+			}
+		}
+	}
+
+	return
+}

+ 7 - 1
services/elastic/rag_question.go

@@ -18,6 +18,7 @@ type RagQuestionItem struct {
 	QuestionTitle   string    `gorm:"column:question_title;type:varchar(255);comment:问题标题;" description:"问题标题"`
 	QuestionContent string    `gorm:"column:question_content;type:varchar(255);comment:问题内容;" description:"问题内容"`
 	Sort            int       `gorm:"column:sort;type:int(11);comment:排序;default:0;" description:"排序"`
+	IsDefault       int       `gorm:"column:is_default;type:int(1);comment:是否默认提示词;default:NULL;" description:"是否默认提示词"`
 	SysUserId       int       `gorm:"column:sys_user_id;type:int(11);comment:添加人id;default:0;" description:"添加人id"`
 	SysUserRealName string    `gorm:"column:sys_user_real_name;type:varchar(255);comment:添加人真实名称;" description:"添加人真实名称"`
 	ModifyTime      time.Time `gorm:"column:modify_time;type:datetime;default:NULL;" description:"modify_time"`
@@ -39,6 +40,7 @@ func (m *RagQuestionItem) ToView() rag.QuestionView {
 		QuestionTitle:   m.QuestionTitle,
 		QuestionContent: m.QuestionContent,
 		Sort:            m.Sort,
+		IsDefault:       m.IsDefault,
 		SysUserId:       m.SysUserId,
 		SysUserRealName: m.SysUserRealName,
 		ModifyTime:      modifyTime,
@@ -137,7 +139,7 @@ func RagQuestionEsDel(docId string) (err error) {
 // @return total int64
 // @return list []*RagQuestionItem
 // @return err error
-func RagQuestionEsSearch(keywordStr string, from, size int, sortMap map[string]string) (total int64, list []*RagQuestionItem, err error) {
+func RagQuestionEsSearch(keywordStr string, isQueryDefault bool, from, size int, sortMap map[string]string) (total int64, list []*RagQuestionItem, err error) {
 	indexName := EsRagQuestionName
 	list = make([]*RagQuestionItem, 0)
 	defer func() {
@@ -153,6 +155,10 @@ func RagQuestionEsSearch(keywordStr string, from, size int, sortMap map[string]s
 		query = query.Must(elastic.NewMultiMatchQuery(keywordStr, "QuestionContent"))
 	}
 
+	if isQueryDefault {
+		query = query.Must(elastic.NewTermQuery("IsDefault", 1))
+	}
+
 	// 排序
 	sortList := make([]*elastic.FieldSort, 0)
 	// 如果没有关键字,那么就走指标id倒序

+ 16 - 2
services/elastic/wechat_article_abstract.go

@@ -7,6 +7,7 @@ import (
 	"eta/eta_api/utils"
 	"fmt"
 	"github.com/olivere/elastic/v7"
+	"strings"
 	"time"
 )
 
@@ -17,7 +18,8 @@ type WechatArticleAbstractItem struct {
 	WechatArticleAbstractId int       `gorm:"column:wechat_article_abstract_id;type:int(9) UNSIGNED;primaryKey;not null;" description:"wechat_article_abstract_id"`
 	WechatArticleId         int       `gorm:"column:wechat_article_id;type:int(9) UNSIGNED;comment:关联的微信报告id;default:0;" description:"关联的微信报告id"`
 	WechatPlatformId        int       `gorm:"column:wechat_platform_id;type:int(9) UNSIGNED;comment:微信公众号id;default:0;" description:"微信公众号id"`
-	Abstract                string    `gorm:"column:abstract;type:longtext;comment:摘要内容;" description:"摘要内容"` //
+	Abstract                string    `gorm:"column:abstract;type:longtext;comment:摘要内容;" description:"摘要内容"`
+	QuestionId              int       `gorm:"column:question_id" description:"提示词Id"`
 	Version                 int       `gorm:"column:version;type:int(10) UNSIGNED;comment:版本号;default:1;" description:"版本号"`
 	VectorKey               string    `gorm:"column:vector_key;type:varchar(255);comment:向量key标识;" description:"向量key标识"`
 	ModifyTime              time.Time `gorm:"column:modify_time;type:datetime;default:NULL;" description:"modify_time"`
@@ -25,6 +27,7 @@ type WechatArticleAbstractItem struct {
 	Title                   string    `gorm:"column:title;type:varchar(255);comment:标题;" description:"标题"`
 	Link                    string    `gorm:"column:link;type:varchar(255);comment:链接;" description:"链接"`
 	TagIdList               []int     `description:"品种id列表"`
+	TagNameList             []string  `description:"品种名称列表"`
 }
 
 func (m *WechatArticleAbstractItem) ToView() rag.WechatArticleAbstractView {
@@ -41,18 +44,24 @@ func (m *WechatArticleAbstractItem) ToView() rag.WechatArticleAbstractView {
 	if len(m.TagIdList) > 0 {
 		tagId = m.TagIdList[0]
 	}
+	tagsName := ``
+	if len(m.TagNameList) > 0 {
+		tagsName = strings.Join(m.TagNameList, `,`)
+	}
 	return rag.WechatArticleAbstractView{
 		WechatArticleAbstractId: m.WechatArticleAbstractId,
 		WechatArticleId:         m.WechatArticleId,
 		WechatPlatformId:        m.WechatPlatformId,
 		Abstract:                m.Abstract,
 		Version:                 m.Version,
+		QuestionId:              m.QuestionId,
 		VectorKey:               m.VectorKey,
 		ModifyTime:              modifyTime,
 		CreateTime:              createTime,
 		Title:                   m.Title,
 		Link:                    m.Link,
 		TagId:                   tagId,
+		TagsName:                tagsName,
 	}
 }
 
@@ -147,7 +156,7 @@ func WechatArticleAbstractEsDel(docId string) (err error) {
 // @return total int64
 // @return list []*WechatArticleAbstractItem
 // @return err error
-func WechatArticleAbstractEsSearch(keywordStr string, tagIdList, platformIdList []int, from, size int, sortMap map[string]string) (total int64, list []*WechatArticleAbstractItem, err error) {
+func WechatArticleAbstractEsSearch(keywordStr string, tagIdList, platformIdList []int, questionId, from, size int, sortMap map[string]string) (total int64, list []*WechatArticleAbstractItem, err error) {
 	indexName := EsWechatArticleAbstractName
 	list = make([]*WechatArticleAbstractItem, 0)
 	defer func() {
@@ -177,6 +186,11 @@ func WechatArticleAbstractEsSearch(keywordStr string, tagIdList, platformIdList
 		query = query.Must(elastic.NewTermsQuery("WechatPlatformId", termsList...))
 	}
 
+	// 提示词id
+	if questionId > 0 {
+		query = query.Must(elastic.NewTermsQuery("QuestionId", questionId))
+	}
+
 	// 名字匹配
 	if keywordStr != `` {
 		query = query.Must(elastic.NewMultiMatchQuery(keywordStr, "Abstract"))

+ 334 - 0
services/llm.go

@@ -0,0 +1,334 @@
+package services
+
+import (
+	"encoding/json"
+	"eta/eta_api/cache"
+	"eta/eta_api/models/rag"
+	"eta/eta_api/models/system"
+	"eta/eta_api/utils"
+	"fmt"
+	"time"
+)
+
+// AddGenerateAbstractTask
+// @Description: 添加全部报告(微信文章/ETA报告)生成摘要任务
+// @author: Roc
+// @datetime 2025-04-16 17:02:18
+// @param question *rag.Question
+// @param sysUser *system.Admin
+func AddGenerateAbstractTask(question *rag.Question, sysUser *system.Admin) {
+	// 找出所有公众号文章Id
+	wechatArticleIdList, err := getAllWechatArticleIdList()
+	if err != nil {
+		return
+	}
+
+	// 找出所有Eta报告
+	ragEtaReportIdList, err := getAllEtaReportIdList()
+	if err != nil {
+		return
+	}
+
+	taskName := fmt.Sprintf("自动生成摘要%s-%s", time.Now().Format(utils.FormatShortDateTimeUnSpace), question.QuestionTitle)
+
+	aiTask := &rag.AiTask{
+		AiTaskID: 0,
+		TaskName: taskName,
+		TaskType: utils.AI_TASK_TYPE_GENERATE_ABSTRACT,
+		Status:   "init",
+		//StartTime:               time.Time{},
+		//EndTime:                 time.Time{},
+		CreateTime:   time.Now(),
+		UpdateTime:   time.Now(),
+		Parameters:   fmt.Sprint(question.QuestionId),
+		Logs:         "",
+		Errormessage: "",
+		Priority:     0,
+		RetryCount:   0,
+		//EstimatedCompletionTime: time.Time{},
+		//ActualCompletitonTime:   time.Time{},
+		Remark:          "",
+		SysUserID:       sysUser.AdminId,
+		SysUserRealName: sysUser.RealName,
+	}
+
+	taskRecordList := make([]*rag.AiTaskRecord, 0)
+	// 微信文章
+	for _, wechatArticleId := range wechatArticleIdList {
+		param := rag.QuestionGenerateAbstractParam{
+			QuestionId:  question.QuestionId,
+			ArticleType: `wechat_article`,
+			ArticleId:   wechatArticleId,
+		}
+		paramByte, tmpErr := json.Marshal(param)
+		if tmpErr != nil {
+			return
+		}
+		taskRecord := &rag.AiTaskRecord{
+			AiTaskRecordID: 0,
+			AiTaskID:       0,
+			Parameters:     string(paramByte),
+			Status:         "待处理",
+			Remark:         "",
+			ModifyTime:     time.Now(),
+			CreateTime:     time.Now(),
+		}
+		taskRecordList = append(taskRecordList, taskRecord)
+	}
+
+	// eta报告
+	for _, ragEtaReportId := range ragEtaReportIdList {
+		param := rag.QuestionGenerateAbstractParam{
+			QuestionId:  question.QuestionId,
+			ArticleType: `rag_eta_report`,
+			ArticleId:   ragEtaReportId,
+		}
+		paramByte, tmpErr := json.Marshal(param)
+		if tmpErr != nil {
+			return
+		}
+		taskRecord := &rag.AiTaskRecord{
+			AiTaskRecordID: 0,
+			AiTaskID:       0,
+			Parameters:     string(paramByte),
+			Status:         "待处理",
+			Remark:         "",
+			ModifyTime:     time.Now(),
+			CreateTime:     time.Now(),
+		}
+		taskRecordList = append(taskRecordList, taskRecord)
+	}
+
+	// 创建AI模块的任务,用于后面的任务调度去生成摘要
+	err = rag.AddAiTask(aiTask, taskRecordList)
+	if err != nil {
+		return
+	}
+
+	// 添加到缓存队列中
+	go addTaskToCache(aiTask.AiTaskID)
+
+	return
+}
+
+func addTaskToCache(aiTaskId int) {
+	var err error
+	defer func() {
+		if err != nil {
+			utils.FileLog.Error("addTaskToCache error: %v", err)
+		}
+	}()
+	obj := rag.AiTaskRecord{}
+	list, err := obj.GetAllListByCondition("*", ` AND ai_task_id = ? `, []interface{}{aiTaskId})
+	if err != nil {
+		return
+	}
+	for _, item := range list {
+		cache.AddAiTaskRecordOpToCache(item.AiTaskRecordID)
+	}
+}
+
+// getAllWechatArticleIdList
+// @Description: 获取所有的微信文章Id列表
+// @author: Roc
+// @datetime 2025-04-16 17:18:31
+// @return wechatArticleIdList []int
+// @return err error
+func getAllWechatArticleIdList() (wechatArticleIdList []int, err error) {
+	wechatArticleIdList = make([]int, 0)
+	pageSize := 10000
+	currentIndex := 1
+
+	// 注意,默认是10000条,如果超过10000条,需要分页查询
+	// 避免死循环
+	for {
+		tmpWechatArticleIdList, tmpErr := getWechatArticleIdList(currentIndex, pageSize)
+		if tmpErr != nil {
+			return
+		}
+		wechatArticleIdList = append(wechatArticleIdList, tmpWechatArticleIdList...)
+		if len(tmpWechatArticleIdList) < pageSize {
+			return
+		}
+		currentIndex++
+
+		// 超过100次,那么也退出,避免死循环
+		if currentIndex > 100 {
+			return
+		}
+	}
+
+}
+
+// getWechatArticleIdList
+// @Description: 分页获取微信文章Id列表
+// @author: Roc
+// @datetime 2025-04-16 17:18:44
+// @param currentIndex int
+// @param pageSize int
+// @return wechatArticleIdList []int
+// @return err error
+func getWechatArticleIdList(currentIndex, pageSize int) (wechatArticleIdList []int, err error) {
+	wechatArticleIdList = make([]int, 0)
+	var condition string
+	var pars []interface{}
+
+	var startSize int
+	if pageSize <= 0 {
+		pageSize = utils.PageSize20
+	}
+	if currentIndex <= 0 {
+		currentIndex = 1
+	}
+	startSize = utils.StartIndex(currentIndex, pageSize)
+
+	condition += fmt.Sprintf(` AND %s = ? `, rag.WechatArticleColumns.IsDeleted)
+	pars = append(pars, 0)
+
+	obj := new(rag.WechatArticle)
+	list, err := obj.GetListByCondition(` wechat_article_id `, condition, pars, startSize, pageSize)
+	if err != nil {
+		return
+	}
+	for _, item := range list {
+		wechatArticleIdList = append(wechatArticleIdList, item.WechatArticleId)
+	}
+
+	return
+}
+
+// getAllEtaReportIdList
+// @Description: 获取所有的eta报告Id列表
+// @author: Roc
+// @datetime 2025-04-16 17:19:29
+// @return ragEtaReportIdList []int
+// @return err error
+func getAllEtaReportIdList() (ragEtaReportIdList []int, err error) {
+	ragEtaReportIdList = make([]int, 0)
+	pageSize := 10000
+	currentIndex := 1
+
+	// 注意,默认是10000条,如果超过10000条,需要分页查询
+	// 避免死循环
+	for {
+		tmpRagEtaReportIdList, tmpErr := getEtaReportIdList(currentIndex, pageSize)
+		if tmpErr != nil {
+			return
+		}
+		ragEtaReportIdList = append(ragEtaReportIdList, tmpRagEtaReportIdList...)
+		if len(tmpRagEtaReportIdList) < pageSize {
+			return
+		}
+		currentIndex++
+
+		// 超过100次,那么也退出,避免死循环
+		if currentIndex > 100 {
+			return
+		}
+	}
+
+}
+
+// getEtaReportIdList
+// @Description: 分页获取eta报告Id列表
+// @author: Roc
+// @datetime 2025-04-16 17:19:14
+// @param currentIndex int
+// @param pageSize int
+// @return ragEtaReportIdList []int
+// @return err error
+func getEtaReportIdList(currentIndex, pageSize int) (ragEtaReportIdList []int, err error) {
+	ragEtaReportIdList = make([]int, 0)
+	var condition string
+	var pars []interface{}
+
+	var startSize int
+	if pageSize <= 0 {
+		pageSize = utils.PageSize20
+	}
+	if currentIndex <= 0 {
+		currentIndex = 1
+	}
+	startSize = utils.StartIndex(currentIndex, pageSize)
+
+	condition += fmt.Sprintf(` AND %s = ? AND %s = ? `, rag.RagEtaReportColumns.IsDeleted, rag.RagEtaReportColumns.IsPublished)
+	pars = append(pars, 0, 1)
+
+	obj := new(rag.RagEtaReport)
+	list, err := obj.GetListByCondition(` rag_eta_report_id `, condition, pars, startSize, pageSize)
+	if err != nil {
+		return
+	}
+	for _, item := range list {
+		ragEtaReportIdList = append(ragEtaReportIdList, item.RagEtaReportId)
+	}
+
+	return
+}
+
+// CheckOpQuestionAuth
+// @Description: 校验是否有权限操作提示词
+// @author: Roc
+// @datetime 2025-04-16 17:33:01
+// @return auth bool
+// @return err error
+func CheckOpQuestionAuth() (auth bool, err error) {
+	total, err := getNotFinishGenerateAbstractTaskNum()
+	if err != nil {
+		return
+	}
+	// 存在未完成的任务,则无权限
+	if total > 0 {
+		return
+	}
+
+	auth = true
+
+	return
+}
+
+// getNotFinishGenerateAbstractTaskNum
+// @Description: 获取未完成的生成摘要任务的数量
+// @author: Roc
+// @datetime 2025-04-16 17:31:12
+// @return total int
+// @return err error
+func getNotFinishGenerateAbstractTaskNum() (total int, err error) {
+	obj := rag.AiTask{}
+
+	var condition string
+	var pars []interface{}
+
+	condition += fmt.Sprintf(` AND %s NOT IN (?)  AND %s = ? `, rag.AiTaskColumns.Status, rag.AiTaskColumns.TaskType)
+	pars = append(pars, []string{`done`, `failed`}, utils.AI_TASK_TYPE_GENERATE_ABSTRACT)
+
+	total, err = obj.GetCountByCondition(condition, pars)
+	if err != nil {
+		return
+	}
+
+	return
+}
+
+// GetNotFinishGenerateAbstractTaskNumByQuestionId
+// @Description: 根据提示词ID获取未完成的生成摘要任务的数量
+// @author: Roc
+// @datetime 2025-04-16 17:31:12
+// @return total int
+// @return err error
+func GetNotFinishGenerateAbstractTaskNumByQuestionId(questionId int) (total int, err error) {
+	obj := rag.AiTask{}
+
+	var condition string
+	var pars []interface{}
+
+	condition += fmt.Sprintf(` AND %s NOT IN (?)  AND %s = ?  AND %s = ? `, rag.AiTaskColumns.Status, rag.AiTaskColumns.TaskType, rag.AiTaskColumns.Parameters)
+	pars = append(pars, []string{`done`, `failed`}, utils.AI_TASK_TYPE_GENERATE_ABSTRACT, fmt.Sprint(questionId))
+
+	total, err = obj.GetCountByCondition(condition, pars)
+	if err != nil {
+		return
+	}
+
+	return
+}

+ 9 - 5
services/llm/facade/llm_service.go

@@ -98,10 +98,12 @@ func AIGCBaseOnPromote(aigc AIGC) (resp bus_response.AIGCEtaResponse, err error)
 				err = fmt.Errorf("创建文章文件失败,err: %v", fileErr)
 				return
 			}
+			file, err = os.Open(path)
 			defer func() {
-				_ = os.Remove(path)
+				_ = file.Close()
+				err = os.Remove(path)
+				fmt.Println(err)
 			}()
-			file, err = os.Open(path)
 			if err != nil {
 				utils.FileLog.Error("打开文件失败,err:", err)
 				return
@@ -188,14 +190,16 @@ func AIGCBaseOnPromote(aigc AIGC) (resp bus_response.AIGCEtaResponse, err error)
 				err = fmt.Errorf("创建文章文件失败,err: %v", fileErr)
 				return
 			}
-			defer func() {
-				_ = os.Remove(path)
-			}()
+
 			file, err = os.Open(path)
 			if err != nil {
 				utils.FileLog.Error("打开文件失败,err:", err)
 				return
 			}
+			defer func() {
+				_ = file.Close()
+				_ = os.Remove(path)
+			}()
 			_, httpErr = llmService.UploadFileToTemplate([]*os.File{file}, param)
 			if httpErr != nil {
 				utils.FileLog.Error("上传文件失败,err:", err.Error())

+ 517 - 0
services/llm_report.go

@@ -1,13 +1,20 @@
 package services
 
 import (
+	"encoding/json"
+	"errors"
+	"eta/eta_api/cache"
 	"eta/eta_api/models"
 	"eta/eta_api/models/rag"
+	"eta/eta_api/services/elastic"
+	"eta/eta_api/services/llm"
 	"eta/eta_api/utils"
 	"fmt"
 	"golang.org/x/net/html"
 	"golang.org/x/net/html/atom"
+	"os"
 	"regexp"
+	"strconv"
 	"strings"
 	"time"
 )
@@ -179,6 +186,8 @@ func handleReportAddOrModifyKnowledge(reportId, reportChapterId int, title, auth
 		err = item.Create()
 	}
 
+	cache.AddRagEtaReportLlmOpToCache(item.RagEtaReportId, 0, true)
+
 	return
 }
 
@@ -244,6 +253,9 @@ func ReportUnPublishedKnowledgeByReportId(reportId int) {
 			errList = append(errList, fmt.Sprintf("第%d章:%s,异常:\n%s", item.ReportChapterId, item.Title, err.Error()))
 			continue
 		}
+
+		// 删除摘要
+		err = DelRagEtaReportAbstract([]int{item.RagEtaReportId})
 	}
 
 	return
@@ -270,3 +282,508 @@ func getArticleContent(content *strings.Builder, htmlContentNode *html.Node) {
 		getArticleContent(content, c)
 	}
 }
+
+// GenerateRagEtaReportAbstract
+// @Description: 文章摘要生成(默认提示词批量生成)
+// @author: Roc
+// @datetime 2025-04-24 11:24:53
+// @param item *rag.RagEtaReport
+// @param forceGenerate bool
+func GenerateRagEtaReportAbstract(item *rag.RagEtaReport, forceGenerate bool) {
+	var err error
+	defer func() {
+		if err != nil {
+			utils.FileLog.Error("文章摘要生成(默认提示词批量生成)失败,err:%v", err)
+		}
+	}()
+	// 内容为空,那就不需要生成摘要
+	if item.TextContent == `` {
+		return
+	}
+
+	questionObj := rag.Question{}
+	questionList, err := questionObj.GetListByCondition(``, ` AND is_default = 1 `, []interface{}{}, 0, 100)
+	if err != nil {
+		err = fmt.Errorf("获取问题列表失败,Err:" + err.Error())
+		return
+	}
+
+	// 没问题就不生成了
+	if len(questionList) <= 0 {
+		return
+	}
+
+	for _, question := range questionList {
+		GenerateRagEtaReportAbstractByQuestion(item, question, forceGenerate)
+	}
+
+	return
+}
+
+// GenerateRagEtaReportAbstractByQuestion
+// @Description: ETA报告摘要生成(根据提示词生成)
+// @author: Roc
+// @datetime 2025-04-24 11:23:49
+// @param item *rag.RagEtaReport
+// @param question *rag.Question
+// @param forceGenerate bool
+// @return err error
+func GenerateRagEtaReportAbstractByQuestion(item *rag.RagEtaReport, question *rag.Question, forceGenerate bool) (err error) {
+	defer func() {
+		if err != nil {
+			utils.FileLog.Error("文章摘要生成(根据提示词生成)失败,err:%v", err)
+		}
+	}()
+
+	// 内容为空,那就不需要生成摘要
+	if item.TextContent == `` {
+		return
+	}
+
+	abstractObj := rag.RagEtaReportAbstract{}
+	abstractItem, err := abstractObj.GetByRagEtaReportIdAndQuestionId(item.RagEtaReportId, question.QuestionId)
+	// 如果找到了,同时不是强制生成,那么就直接处理到知识库中
+	if err == nil && !forceGenerate {
+		// 摘要已经生成,不需要重复生成,只需要重新加入到向量库中
+		ReportAbstractToKnowledge(item, abstractItem, false)
+
+		return
+	}
+	// 如果是没找到数据,那么就将报错置空
+	if err != nil && utils.IsErrNoRow(err) {
+		err = nil
+	}
+
+	//你现在是一名资深的期货行业分析师,请基于以下的问题进行汇总总结,如果不能正常总结出来,那么就只需要回复我:sorry
+	questionStr := fmt.Sprintf(`%s\n%s`, `你现在是一名资深的期货行业分析师,请基于以下的问题进行汇总总结,如果不能正常总结出来,那么就只需要回复我:sorry。以下是问题:`, question.QuestionContent)
+	//开始对话
+	abstract, industryTags, tmpErr := getAnswerByContent(item.RagEtaReportId, utils.AI_ARTICLE_SOURCE_ETA_REPORT, questionStr)
+	if tmpErr != nil {
+		err = fmt.Errorf("LLM对话失败,Err:" + tmpErr.Error())
+		return
+	}
+
+	// 添加问答记录
+	//if len(addArticleChatRecordList) > 0 {
+	//	recordObj := rag.RagEtaReportChatRecord{}
+	//	err = recordObj.CreateInBatches(addArticleChatRecordList)
+	//	if err != nil {
+	//		return
+	//	}
+	//}
+
+	if abstract == `` {
+		return
+	}
+	if abstract == `sorry` || strings.Index(abstract, `根据已知信息无法回答该问题`) == 0 {
+		return
+	}
+
+	//if abstract == `sorry` || strings.Index(abstract, `根据已知信息无法回答该问题`) == 0 {
+	//	item.AbstractStatus = 2
+	//	item.ModifyTime = time.Now()
+	//	err = item.Update([]string{"AbstractStatus", "ModifyTime"})
+	//	return
+	//}
+	//item.AbstractStatus = 1
+	//item.ModifyTime = time.Now()
+	//err = item.Update([]string{"AbstractStatus", "ModifyTime"})
+
+	var tagIdJsonStr string
+	var tagNameJsonStr string
+	// 标签ID
+	{
+		tagIdList := make([]int, 0)
+		tagNameList := make([]string, 0)
+		tagIdMap := make(map[int]bool)
+
+		if abstractItem != nil && abstractItem.Tags != `` {
+			tmpErr = json.Unmarshal([]byte(abstractItem.Tags), &tagIdList)
+			if tmpErr != nil {
+				utils.FileLog.Info(fmt.Sprintf("json.Unmarshal 失败,标签数据:%s,Err:%s", abstractItem.Tags, tmpErr.Error()))
+			} else {
+				for _, tagId := range tagIdList {
+					tagIdMap[tagId] = true
+				}
+			}
+			if abstractItem.TagsName != `` {
+				tagNameList = strings.Split(abstractItem.TagsName, ",")
+			}
+		}
+		for _, tagName := range industryTags {
+			tagId, tmpErr := GetTagIdByName(tagName)
+			if tmpErr != nil {
+				utils.FileLog.Info(fmt.Sprintf("获取标签ID失败,标签名称:%s,Err:%s", tagName, tmpErr.Error()))
+			}
+			if _, ok := tagIdMap[tagId]; !ok {
+				tagIdList = append(tagIdList, tagId)
+				tagNameList = append(tagNameList, tagName)
+				tagIdMap[tagId] = true
+			}
+		}
+		//for _, tagName := range varietyTags {
+		//	tagId, tmpErr := GetTagIdByName(tagName)
+		//	if tmpErr != nil {
+		//		utils.FileLog.Info(fmt.Sprintf("获取标签ID失败,标签名称:%s,Err:%s", tagName, tmpErr.Error()))
+		//	}
+		//	if _, ok := tagIdMap[tagId]; !ok {
+		//		tagIdList = append(tagIdList, tagId)
+		//		tagIdMap[tagId] = true
+		//	}
+		//}
+
+		tagIdJsonByte, tmpErr := json.Marshal(tagIdList)
+		if tmpErr != nil {
+			utils.FileLog.Info(fmt.Sprintf("标签ID序列化失败,Err:%s", tmpErr.Error()))
+		} else {
+			tagIdJsonStr = string(tagIdJsonByte)
+		}
+
+		tagNameJsonStr = strings.Join(tagNameList, `,`)
+	}
+
+	if abstractItem == nil || abstractItem.RagEtaReportAbstractId <= 0 {
+		abstractItem = &rag.RagEtaReportAbstract{
+			RagEtaReportAbstractId: 0,
+			RagEtaReportId:         item.RagEtaReportId,
+			Content:                abstract,
+			QuestionId:             question.QuestionId,
+			QuestionContent:        question.QuestionContent,
+			Version:                1,
+			Tags:                   tagIdJsonStr,
+			TagsName:               tagNameJsonStr,
+			VectorKey:              "",
+			ModifyTime:             time.Now(),
+			CreateTime:             time.Now(),
+		}
+		err = abstractItem.Create()
+	} else {
+		// 添加历史记录
+		rag.AddArticleAbstractHistoryByRagEtaReportAbstract(abstractItem)
+
+		abstractItem.Content = abstract
+		abstractItem.Version++
+		abstractItem.ModifyTime = time.Now()
+		abstractItem.Tags = tagIdJsonStr
+		abstractItem.TagsName = tagNameJsonStr
+		abstractItem.QuestionContent = question.QuestionContent
+		err = abstractItem.Update([]string{"content", "version", "modify_time", "tags", "tags_name", "question_content"})
+	}
+
+	if err != nil {
+		return
+	}
+
+	// 数据入ES库
+	go AddOrEditEsRagEtaReportAbstract(abstractItem.RagEtaReportAbstractId)
+
+	ReportAbstractToKnowledge(item, abstractItem, false)
+
+	return
+}
+
+// AddOrEditEsRagEtaReportAbstract
+// @Description: 新增/编辑微信文章摘要入ES
+// @author: Roc
+// @datetime 2025-03-13 14:13:47
+// @param articleAbstractId int
+func AddOrEditEsRagEtaReportAbstract(ragEtaReportAbstractId int) {
+	if utils.EsRagEtaReportAbstractName == `` {
+		return
+	}
+
+	var err error
+	defer func() {
+		if err != nil {
+			utils.FileLog.Error("添加ETA报告微信信息到ES失败,err:%v", err)
+			fmt.Println("添加ETA报告微信信息到ES失败,err:", err)
+		}
+	}()
+	obj := rag.RagEtaReportAbstract{}
+	abstractInfo, err := obj.GetById(ragEtaReportAbstractId)
+	if err != nil {
+		err = fmt.Errorf("获取ETA报告文章信息失败,Err:" + err.Error())
+		return
+	}
+	ragEtaReportObj := rag.RagEtaReport{}
+	articleInfo, err := ragEtaReportObj.GetById(abstractInfo.RagEtaReportAbstractId)
+	if err != nil {
+		err = fmt.Errorf("获取ETA报告文章信息失败,Err:" + err.Error())
+		return
+	}
+
+	tagIdList := make([]int, 0)
+	if abstractInfo.Tags != `` {
+		err = json.Unmarshal([]byte(abstractInfo.Tags), &tagIdList)
+		if err != nil {
+			err = fmt.Errorf("报告标签ID转int失败,Err:" + err.Error())
+			utils.FileLog.Info(fmt.Sprintf("json.Unmarshal 报告标签ID转int失败,标签数据:%s,Err:%s", abstractInfo.Tags, err.Error()))
+		}
+	}
+
+	tagNameList := make([]string, 0)
+	if abstractInfo.TagsName != `` {
+		tagNameList = strings.Split(abstractInfo.TagsName, ",")
+	}
+
+	esItem := elastic.RagEtaReportAbstractItem{
+		RagEtaReportAbstractId: abstractInfo.RagEtaReportAbstractId,
+		RagEtaReportId:         abstractInfo.RagEtaReportId,
+		Abstract:               abstractInfo.Content,
+		QuestionId:             abstractInfo.QuestionId,
+		Version:                abstractInfo.Version,
+		VectorKey:              abstractInfo.VectorKey,
+		ModifyTime:             abstractInfo.ModifyTime,
+		CreateTime:             abstractInfo.CreateTime,
+		Title:                  articleInfo.Title,
+		TagIdList:              tagIdList,
+		TagNameList:            tagNameList,
+	}
+
+	err = elastic.RagEtaReportAbstractEsAddOrEdit(strconv.Itoa(abstractInfo.RagEtaReportAbstractId), esItem)
+}
+
+// DelEsRagEtaReportAbstract
+// @Description: 删除ES中的ETA报告
+// @author: Roc
+// @datetime 2025-04-21 11:08:09
+// @param articleAbstractId int
+func DelEsRagEtaReportAbstract(articleAbstractId int) {
+	if utils.EsRagEtaReportAbstractName == `` {
+		return
+	}
+
+	var err error
+	defer func() {
+		if err != nil {
+			utils.FileLog.Error("删除ES中的ETA报告失败,err:%v", err)
+			fmt.Println("删除ES中的ETA报告失败,err:", err)
+		}
+	}()
+
+	err = elastic.RagEtaReportAbstractEsDel(strconv.Itoa(articleAbstractId))
+}
+
+// WechatArticleAbstractToKnowledge
+// @Description: 摘要入向量库
+// @author: Roc
+// @datetime 2025-03-10 16:14:59
+// @param wechatArticleItem *rag.RagEtaReport
+// @param abstractItem *rag.RagEtaReportAbstract
+func ReportAbstractToKnowledge(ragEtaReport *rag.RagEtaReport, abstractItem *rag.RagEtaReportAbstract, isReUpload bool) {
+	if abstractItem.Content == `` {
+		return
+	}
+	// 已经生成了,那就不处理了
+	if abstractItem.VectorKey != `` && !isReUpload {
+		return
+	}
+	var err error
+	defer func() {
+		if err != nil {
+			utils.FileLog.Error("摘要入向量库失败,err:%v", err)
+			fmt.Println("摘要入向量库失败,err:", err)
+		}
+
+		// 数据入ES库
+		go AddOrEditEsRagEtaReportAbstract(abstractItem.RagEtaReportAbstractId)
+	}()
+
+	// 生成临时文件
+	//dateDir := time.Now().Format("20060102")
+	//uploadDir :=  + "./static/ai/article/" + dateDir
+	uploadDir := "./static/ai/abstract"
+	err = os.MkdirAll(uploadDir, utils.DIR_MOD)
+	if err != nil {
+		err = fmt.Errorf("存储目录创建失败,Err:" + err.Error())
+		return
+	}
+	fileName := utils.MD5(fmt.Sprintf("%d_%d", utils.AI_ARTICLE_SOURCE_ETA_REPORT, ragEtaReport.RagEtaReportId)) + `.md`
+	tmpFilePath := uploadDir + "/" + fileName
+	err = utils.SaveToFile(abstractItem.Content, tmpFilePath)
+	if err != nil {
+		err = fmt.Errorf("生成临时文件失败,Err:" + err.Error())
+		return
+	}
+	defer func() {
+		os.Remove(tmpFilePath)
+	}()
+
+	knowledgeArticleName := models.BusinessConfMap[models.PrivateKnowledgeBaseName]
+	// 上传临时文件到LLM
+	uploadFileResp, err := llm.UploadDocsToKnowledge(tmpFilePath, knowledgeArticleName)
+	if err != nil {
+		err = fmt.Errorf("上传文章原文到知识库失败,Err:" + err.Error())
+		return
+	}
+
+	if len(uploadFileResp.FailedFiles) > 0 {
+		for _, v := range uploadFileResp.FailedFiles {
+			err = fmt.Errorf("上传文章原文到知识库失败,Err:" + v)
+		}
+	}
+
+	abstractItem.VectorKey = tmpFilePath
+	abstractItem.ModifyTime = time.Now()
+	err = abstractItem.Update([]string{"vector_key", "modify_time"})
+
+}
+
+// DelRagReportLlmDoc
+// @Description: 删除ETA报告的摘要向量库
+// @author: Roc
+// @datetime 2025-04-23 13:24:51
+// @param vectorKeyList []string
+// @param abstractIdList []int
+// @return err error
+func DelRagReportLlmDoc(vectorKeyList []string, abstractIdList []int) (err error) {
+	defer func() {
+		if err != nil {
+			utils.FileLog.Error("删除摘要向量库文件失败,err:%v", err)
+			fmt.Println("删除摘要向量库文件失败,err:", err)
+		}
+	}()
+
+	// 没有就不删除
+	if len(vectorKeyList) <= 0 {
+		return
+	}
+
+	_, err = llm.DelDocsToKnowledge(models.BusinessConfMap[models.PrivateKnowledgeBaseName], vectorKeyList)
+	obj := rag.RagEtaReportAbstract{}
+	err = obj.DelVectorKey(abstractIdList)
+
+	return
+}
+
+// DelRagEtaReportAbstract
+// @Description: 删除ETA报告摘要
+// @author: Roc
+// @datetime 2025-04-23 17:36:22
+// @param abstractIdList []int
+// @return err error
+func DelRagEtaReportAbstract(abstractIdList []int) (err error) {
+	obj := rag.RagEtaReportAbstract{}
+
+	list, err := obj.GetByIdList(abstractIdList)
+	if err != nil {
+		if !utils.IsErrNoRow(err) {
+			err = errors.New("删除向量库失败,Err:" + err.Error())
+		} else {
+			err = nil
+		}
+		return
+	}
+
+	err = delRagEtaReportAbstract(list)
+
+	return
+}
+
+// DelRagEtaReportAbstractByQuestionId
+// @Description: 根据提示词ID删除ETA报告摘要
+// @author: Roc
+// @datetime 2025-04-23 17:36:22
+// @param abstractIdList []int
+// @return err error
+func DelRagEtaReportAbstractByQuestionId(questionId int) (err error) {
+	obj := rag.RagEtaReportAbstract{}
+
+	list, err := obj.GetListByQuestionId(questionId)
+	if err != nil {
+		if !utils.IsErrNoRow(err) {
+			err = errors.New("删除向量库失败,Err:" + err.Error())
+		} else {
+			err = nil
+		}
+		return
+	}
+
+	err = delRagEtaReportAbstract(list)
+
+	return
+}
+
+// delRagEtaReportAbstract
+// @Description: 删除摘要
+// @author: Roc
+// @datetime 2025-04-24 15:19:19
+// @param list []*rag.RagEtaReportAbstract
+// @return err error
+func delRagEtaReportAbstract(list []*rag.RagEtaReportAbstract) (err error) {
+	obj := rag.RagEtaReportAbstract{}
+
+	vectorKeyList := make([]string, 0)
+	newAbstractIdList := make([]int, 0)
+
+	if len(list) > 0 {
+		for _, v := range list {
+			// 有加入到向量库,那么就加入到待删除的向量库list中
+			if v.VectorKey != `` {
+				vectorKeyList = append(vectorKeyList, v.VectorKey)
+			}
+			newAbstractIdList = append(newAbstractIdList, v.RagEtaReportAbstractId)
+		}
+	}
+	// 删除向量库
+	err = DelRagReportLlmDoc(vectorKeyList, newAbstractIdList)
+	if err != nil {
+		err = errors.New("删除向量库失败,Err:" + err.Error())
+		return
+	}
+
+	// 删除摘要
+	err = obj.DelByIdList(newAbstractIdList)
+	if err != nil {
+		err = errors.New("删除失败,Err:" + err.Error())
+		return
+	}
+
+	// 删除es数据
+	for _, wechatArticleAbstractId := range newAbstractIdList {
+		DelEsRagEtaReportAbstract(wechatArticleAbstractId)
+	}
+
+	return
+}
+
+// GetDelAbstractByQuestionIdCacheKey
+// @Description: 获取删除微信文章/ETA报告摘要的缓存key
+// @author: Roc
+// @datetime 2025-04-24 15:44:41
+// @param questionId int
+// @return string
+func GetDelAbstractByQuestionIdCacheKey(questionId int) string {
+	return fmt.Sprintf("%s%d", utils.CACHE_AI_ARTICLE_ABSTRACT_DEL, questionId)
+}
+
+// DelAbstractByQuestionId
+// @Description: 根据提示词ID删除微信文章/报告摘要
+// @author: Roc
+// @datetime 2025-04-24 15:37:28
+// @param questionId int
+func DelAbstractByQuestionId(questionId int) {
+	cacheKey := GetDelAbstractByQuestionIdCacheKey(questionId)
+	if !utils.Rc.SetNX(cacheKey, 1, 30*time.Minute) {
+		utils.FileLog.Error("根据提示词删除摘要失败,提示词ID:%d,系统处理中,请稍后重试!", questionId)
+		return
+	}
+
+	defer func() {
+		utils.Rc.Delete(cacheKey)
+	}()
+
+	// 删除微信文章摘要
+	err := DelWechatArticleAbstractByQuestionId(questionId)
+	if err != nil {
+		utils.FileLog.Error("根据提示词摘要删除微信文章摘要失败,提示词ID:%d;原因:%s", questionId, err.Error())
+	}
+
+	// 删除ETA报告摘要
+	err = DelRagEtaReportAbstractByQuestionId(questionId)
+	if err != nil {
+		utils.FileLog.Error("根据提示词删除ETA报告摘要失败,提示词ID:%d;原因:%s", questionId, err.Error())
+	}
+
+	return
+}

+ 291 - 24
services/task.go

@@ -75,8 +75,14 @@ func Task() {
 	go HandleWechatArticleLLmOp()
 
 	// 队列任务将eta报告同步到知识库操作
+	go HandleEtaReportUpdateOp()
+
+	// 定时任务进行eta报告进行LLM操作
 	go HandleEtaReportKnowledgeLLmOp()
 
+	// 定时任务进行进行AI报告/文章的摘要任务LLM操作
+	go HandleAiArticleAbstractLlmOp()
+
 	// 权益报告监听入库
 	go AutoInsertRaiReport()
 
@@ -589,8 +595,8 @@ func ModifyEsEnglishReport() {
 //	return rnd.Float64()*11000 - 1000
 //}
 
-// HandleSearchByWechatOp
-// @Description: 处理微信爬虫
+// HandleWechatArticleOp
+// @Description: 处理ETA报告加入到知识库
 func HandleWechatArticleOp() {
 	defer func() {
 		if err := recover(); err != nil {
@@ -600,7 +606,7 @@ func HandleWechatArticleOp() {
 	obj := rag.WechatPlatform{}
 	for {
 		utils.Rc.Brpop(utils.CACHE_WECHAT_PLATFORM_ARTICLE, func(b []byte) {
-			wechatArticleOp := new(cache.WechatArticleOp)
+			wechatArticleOp := new(cache.WechatPlatformOp)
 			if err := json.Unmarshal(b, &wechatArticleOp); err != nil {
 				fmt.Println("json unmarshal wrong!")
 				return
@@ -630,39 +636,66 @@ func HandleWechatArticleLLmOp() {
 			fmt.Println("[HandleWechatArticleLLmOp]", err)
 		}
 	}()
-	obj := rag.WechatArticle{}
 	for {
-		utils.Rc.Brpop(utils.CACHE_WECHAT_PLATFORM_ARTICLE_KNOWLEDGE, func(b []byte) {
-			wechatArticleOp := new(cache.WechatArticleOp)
-			if err := json.Unmarshal(b, &wechatArticleOp); err != nil {
-				fmt.Println("json unmarshal wrong!")
-				return
-			}
-			item, tmpErr := obj.GetById(wechatArticleOp.WechatPlatformId)
-			if tmpErr != nil {
-				// 找不到就处理失败
-				return
-			}
+		utils.Rc.Brpop(utils.CACHE_WECHAT_PLATFORM_ARTICLE_KNOWLEDGE, handleWechatArticleLLmOp)
+	}
+}
 
-			// 文章加入到知识库
-			ArticleToKnowledge(item)
-			// 生成摘要
-			//GenerateArticleAbstract(item)
-		})
+// handleWechatArticleLLmOp
+// @Description: 处理微信文章加入知识库
+// @author: Roc
+// @datetime 2025-04-24 13:33:08
+// @param b []byte
+func handleWechatArticleLLmOp(b []byte) {
+	var err error
+	defer func() {
+		if err != nil {
+			utils.FileLog.Error("[handleWechatArticleLLmOp] params:%s;err:%s", string(b), err.Error())
+		}
+	}()
+	obj := rag.WechatArticle{}
+	wechatArticleOp := new(cache.WechatArticleOp)
+	if err = json.Unmarshal(b, &wechatArticleOp); err != nil {
+		fmt.Println("json unmarshal wrong!")
+		return
+	}
+	item, err := obj.GetById(wechatArticleOp.WechatArticleId)
+	if err != nil {
+		// 找不到就处理失败
+		return
+	}
+
+	// 文章加入到知识库
+	ArticleToKnowledge(item)
+
+	// 生成摘要
+	if wechatArticleOp.QuestionId <= 0 {
+		// 全部摘要生成
+		GenerateWechatArticleAbstract(item, false)
+	} else {
+		questionObj := rag.Question{}
+		questionInfo, tmpErr := questionObj.GetByID(wechatArticleOp.QuestionId)
+		if tmpErr != nil {
+			err = tmpErr
+			return
+		}
+
+		// 指定指定摘要生成
+		err = GenerateWechatArticleAbstractByQuestion(item, questionInfo, false)
 	}
 }
 
-// HandleEtaReportKnowledgeLLmOp
+// HandleEtaReportUpdateOp
 // @Description: 处理eta报告加入知识库操作
-func HandleEtaReportKnowledgeLLmOp() {
+func HandleEtaReportUpdateOp() {
 	defer func() {
 		if err := recover(); err != nil {
-			fmt.Println("[HandleEtaReportKnowledgeLLmOp]", err)
+			fmt.Println("[HandleEtaReportUpdateOp]", err)
 		}
 	}()
 	for {
 		utils.Rc.Brpop(utils.CACHE_ETA_REPORT_KNOWLEDGE, func(b []byte) {
-			ragEtaReportOpOp := new(cache.RagEtaReportOpOp)
+			ragEtaReportOpOp := new(cache.RagEtaReportOp)
 			if err := json.Unmarshal(b, &ragEtaReportOpOp); err != nil {
 				fmt.Println("json unmarshal wrong!")
 				return
@@ -677,3 +710,237 @@ func HandleEtaReportKnowledgeLLmOp() {
 		})
 	}
 }
+
+// HandleEtaReportKnowledgeLLmOp
+// @Description: 处理微信文章加入知识库
+func HandleEtaReportKnowledgeLLmOp() {
+	defer func() {
+		if err := recover(); err != nil {
+			fmt.Println("[HandleEtaReportKnowledgeLLmOp]", err)
+		}
+	}()
+
+	for {
+		utils.Rc.Brpop(utils.CACHE_ETA_REPORT_KNOWLEDGE_LLM, handleEtaReportKnowledgeLLmOp)
+	}
+}
+
+// handleEtaReportKnowledgeLLmOp
+// @Description: 处理微信文章加入知识库操作
+// @author: Roc
+// @datetime 2025-04-24 14:04:10
+// @param b []byte
+func handleEtaReportKnowledgeLLmOp(b []byte) {
+	var err error
+	defer func() {
+		if err != nil {
+			utils.FileLog.Error("[handleEtaReportKnowledgeLLmOp] params:%s;err:%s", string(b), err.Error())
+		}
+	}()
+
+	obj := rag.RagEtaReport{}
+	wechatArticleOp := new(cache.RagEtaReportLlmOp)
+	if err = json.Unmarshal(b, &wechatArticleOp); err != nil {
+		fmt.Println("json unmarshal wrong!")
+		return
+	}
+	item, err := obj.GetById(wechatArticleOp.RagEtaReportId)
+	if err != nil {
+		// 找不到就处理失败
+		return
+	}
+
+	// 已经删除的就不做操作了
+	if item.IsDeleted == 1 {
+		return
+	}
+
+	// 未发布的就不操作了
+	if item.IsPublished != 1 {
+		return
+	}
+
+	// 文章加入到知识库
+	//ArticleToKnowledge(item)
+
+	// 生成摘要
+
+	if wechatArticleOp.QuestionId <= 0 {
+		// 全部提示词摘要生成
+		GenerateRagEtaReportAbstract(item, wechatArticleOp.ForceGenerate)
+	} else {
+		questionObj := rag.Question{}
+		questionInfo, tmpErr := questionObj.GetByID(wechatArticleOp.QuestionId)
+		if tmpErr != nil {
+			err = tmpErr
+			return
+		}
+
+		// 全部提示词摘要生成
+		err = GenerateRagEtaReportAbstractByQuestion(item, questionInfo, wechatArticleOp.ForceGenerate)
+	}
+}
+
+// HandleAiArticleAbstractLlmOp
+// @Description: 处理AI库的报告摘要生成(批量任务)
+// @author: Roc
+// @datetime 2025-04-24 10:25:51
+func HandleAiArticleAbstractLlmOp() {
+	defer func() {
+		if err := recover(); err != nil {
+			fmt.Println("[HandleAiArticleAbstractLlmOp]", err)
+		}
+	}()
+	for {
+		utils.Rc.Brpop(utils.CACHE_AI_ARTICLE_ABSTRACT_LLM_TASK, handleAiArticleAbstractLlmOp)
+	}
+}
+
+var aiTaskHandleIdMap = map[int]bool{}
+
+// todo 任务开始时间
+
+// handleAiArticleAbstractLlmOp
+// @Description: 处理AI库的报告摘要生成(批量任务)
+// @author: Roc
+// @datetime 2025-04-24 11:26:05
+// @param b []byte
+func handleAiArticleAbstractLlmOp(b []byte) {
+	var err error
+	defer func() {
+		if err != nil {
+			utils.FileLog.Error("[handleAiArticleAbstractLlmOp] params:%s;err:%s", string(b), err.Error())
+		}
+	}()
+	obj := rag.AiTaskRecord{}
+	aiTaskRecordOp := new(cache.AiTaskRecordOp)
+	if err = json.Unmarshal(b, &aiTaskRecordOp); err != nil {
+		fmt.Println("json unmarshal wrong!")
+		return
+	}
+	item, err := obj.GetByID(aiTaskRecordOp.AiTaskRecordId)
+	if err != nil {
+		err = fmt.Errorf("查找任务记录状态失败, err: %s", err.Error())
+		return
+	}
+
+	// 如果没有处理过该任务,那么就标记该任务开始
+	if _, ok := aiTaskHandleIdMap[item.AiTaskID]; !ok {
+		aiTaskObj := rag.AiTask{}
+		aiTaskInfo, tmpErr := aiTaskObj.GetByID(item.AiTaskID)
+		if tmpErr != nil {
+			err = fmt.Errorf("查找任务失败, err: %s", tmpErr.Error())
+			return
+		}
+		// 如果任务是初始化,那么就标记开始
+		if aiTaskInfo.Status == `init` {
+			aiTaskInfo.StartTime = time.Now()
+			aiTaskInfo.Status = `processing`
+			aiTaskInfo.UpdateTime = time.Now()
+			tmpErr = aiTaskInfo.Update([]string{`start_time`, "status", "update_time"})
+			if tmpErr != nil {
+				utils.FileLog.Error("标记任务开始状态失败, err: %s", tmpErr.Error())
+			}
+		}
+
+	}
+
+	// 处理完成后标记任务状态
+	defer func() {
+		// 修改任务状态
+		todoCount, tmpErr := obj.GetCountByCondition(fmt.Sprintf(` AND %s = ? AND %s = ? `, rag.AiTaskColumns.AiTaskID, rag.AiTaskColumns.Status), []interface{}{item.AiTaskID, `待处理`})
+		if tmpErr != nil {
+			err = fmt.Errorf("查找剩余任务数量失败, err: %s", tmpErr.Error())
+			return
+		}
+		if todoCount <= 0 {
+			aiTaskObj := rag.AiTask{}
+			aiTaskInfo, tmpErr := aiTaskObj.GetByID(item.AiTaskID)
+			if tmpErr != nil {
+				err = fmt.Errorf("查找任务失败, err: %s", tmpErr.Error())
+				return
+			}
+			aiTaskInfo.EndTime = time.Now()
+			aiTaskInfo.Status = `done`
+			aiTaskInfo.UpdateTime = time.Now()
+			tmpErr = aiTaskInfo.Update([]string{`end_time`, "status", "update_time"})
+			if tmpErr != nil {
+				utils.FileLog.Error("标记任务状态失败, err: %s", tmpErr.Error())
+			}
+		}
+
+		return
+	}()
+
+	// 不是待处理就不处理
+	if item.Status != `待处理` {
+		return
+	}
+
+	// 处理完成后标记记录状态
+	defer func() {
+		status := `处理成功`
+		remark := ``
+		if err != nil {
+			status = `处理失败`
+			remark = err.Error()
+		}
+		item.Status = status
+		item.Remark = remark
+		item.ModifyTime = time.Now()
+		tmpErr := item.Update([]string{"status", "remark", "modify_time"})
+		if tmpErr != nil {
+			utils.FileLog.Error("标记任务记录状态失败, err: %s", tmpErr.Error())
+		}
+	}()
+
+	var params rag.QuestionGenerateAbstractParam
+	if err = json.Unmarshal([]byte(item.Parameters), &params); err != nil {
+		fmt.Println("json unmarshal wrong!")
+		return
+	}
+
+	// 查找提示词
+	questionObj := rag.Question{}
+	questionInfo, tmpErr := questionObj.GetByID(params.QuestionId)
+	if tmpErr != nil {
+		// 找不到就处理失败
+		err = fmt.Errorf("查找提示词失败, err: %s", err.Error())
+		return
+	}
+
+	switch params.ArticleType {
+	case `wechat_article`:
+		articleObj := rag.WechatArticle{}
+		articleInfo, tmpErr := articleObj.GetById(params.ArticleId)
+		if tmpErr != nil {
+			err = tmpErr
+			// 找不到就处理失败
+			return
+		}
+		// 生成摘要
+		err = GenerateWechatArticleAbstractByQuestion(articleInfo, questionInfo, true)
+	case `rag_eta_report`:
+		articleObj := rag.RagEtaReport{}
+		articleInfo, tmpErr := articleObj.GetById(params.ArticleId)
+		if tmpErr != nil {
+			err = tmpErr
+			// 找不到就处理失败
+			return
+		}
+
+		// 已经删除的就不做操作了
+		if articleInfo.IsDeleted == 1 {
+			return
+		}
+
+		// 未发布的就不操作了
+		if articleInfo.IsPublished != 1 {
+			return
+		}
+
+		// 生成摘要
+		err = GenerateRagEtaReportAbstractByQuestion(articleInfo, questionInfo, true)
+
+	}
+}

+ 506 - 173
services/wechat_platform.go

@@ -2,11 +2,14 @@ package services
 
 import (
 	"bytes"
+	"encoding/json"
+	"errors"
 	"eta/eta_api/cache"
 	"eta/eta_api/models"
 	"eta/eta_api/models/rag"
 	"eta/eta_api/services/elastic"
 	"eta/eta_api/services/llm"
+	"eta/eta_api/services/llm/facade"
 	"eta/eta_api/utils"
 	"eta/eta_api/utils/llm/eta_llm/eta_llm_http"
 	"fmt"
@@ -14,6 +17,7 @@ import (
 	"html"
 	"os"
 	"path"
+	"regexp"
 	"strconv"
 	"strings"
 	"time"
@@ -176,7 +180,7 @@ func AddWechatArticle(item *rag.WechatPlatform, articleLink string, articleDetai
 	go replaceWechatArticleCoverPic(obj)
 
 	// 文章入库成功后,需要将相关信息入摘要库
-	go cache.AddWechatArticleLlmOpToCache(obj.WechatArticleId, ``)
+	go cache.AddWechatArticleLlmOpToCache(obj.WechatArticleId, 0, ``)
 
 }
 
@@ -238,12 +242,93 @@ func BeachAddWechatArticle(item *rag.WechatPlatform, num int) {
 	return
 }
 
+//
+//// GenerateArticleAbstract
+//// @Description: 文章摘要生成
+//// @author: Roc
+//// @datetime 2025-03-10 16:17:53
+//// @param item *rag.WechatArticle
+//func GenerateArticleAbstract(item *rag.WechatArticle, forceGenerate bool) {
+//	var err error
+//	defer func() {
+//		if err != nil {
+//			utils.FileLog.Error("文章转临时文件失败,err:%v", err)
+//			fmt.Println("文章转临时文件失败,err:", err)
+//		}
+//	}()
+//
+//	// 内容为空,那就不需要生成摘要
+//	if item.TextContent == `` {
+//		return
+//	}
+//
+//	abstractObj := rag.WechatArticleAbstract{}
+//	tmpAbstractItem, err := abstractObj.GetByWechatArticleId(item.WechatArticleId)
+//	// 如果找到了,同时不是强制生成,那么就直接处理到知识库中
+//	if err == nil && !forceGenerate {
+//		// 摘要已经生成,不需要重复生成,只需要重新加入到向量库中
+//		WechatArticleAbstractToKnowledge(item, tmpAbstractItem, false)
+//
+//		return
+//	}
+//	if !utils.IsErrNoRow(err) {
+//		return
+//	}
+//
+//	//开始对话
+//	abstract, addArticleChatRecordList, tmpErr := getAnswerByContent(item.WechatArticleId, utils.AI_ARTICLE_SOURCE_ETA_REPORT)
+//	if tmpErr != nil {
+//		err = fmt.Errorf("LLM对话失败,Err:" + tmpErr.Error())
+//		return
+//	}
+//
+//	// 添加问答记录
+//	if len(addArticleChatRecordList) > 0 {
+//		recordObj := rag.WechatArticleChatRecord{}
+//		err = recordObj.CreateInBatches(addArticleChatRecordList)
+//		if err != nil {
+//			return
+//		}
+//	}
+//
+//	if abstract != `` {
+//		if abstract == `sorry` || strings.Index(abstract, `根据已知信息无法回答该问题`) == 0 {
+//			item.AbstractStatus = 2
+//			item.ModifyTime = time.Now()
+//			err = item.Update([]string{"AbstractStatus", "ModifyTime"})
+//			return
+//		}
+//		item.AbstractStatus = 1
+//		item.ModifyTime = time.Now()
+//		err = item.Update([]string{"AbstractStatus", "ModifyTime"})
+//
+//		abstractItem := &rag.WechatArticleAbstract{
+//			WechatArticleAbstractId: 0,
+//			WechatArticleId:         item.WechatArticleId,
+//			Content:                 abstract,
+//			Version:                 0,
+//			VectorKey:               "",
+//			ModifyTime:              time.Now(),
+//			CreateTime:              time.Now(),
+//		}
+//		err = abstractItem.Create()
+//		if err != nil {
+//			return
+//		}
+//
+//		// 数据入ES库
+//		go AddOrEditEsWechatArticleAbstract(abstractItem.WechatArticleAbstractId)
+//
+//		WechatArticleAbstractToKnowledge(item, abstractItem, false)
+//	}
+//}
+
 // GenerateArticleAbstract
-// @Description: 文章摘要生成
+// @Description: 文章摘要生成(默认提示词批量生成)
 // @author: Roc
 // @datetime 2025-03-10 16:17:53
 // @param item *rag.WechatArticle
-func GenerateArticleAbstract(item *rag.WechatArticle) {
+func GenerateWechatArticleAbstract(item *rag.WechatArticle, forceGenerate bool) {
 	var err error
 	defer func() {
 		if err != nil {
@@ -257,203 +342,175 @@ func GenerateArticleAbstract(item *rag.WechatArticle) {
 		return
 	}
 
-	abstractObj := rag.WechatArticleAbstract{}
-	tmpAbstractItem, err := abstractObj.GetByWechatArticleId(item.WechatArticleId)
-	if err == nil {
-		// 摘要已经生成,不需要重复生成
-		AbstractToKnowledge(item, tmpAbstractItem, false)
-
-		return
-	}
-	if !utils.IsErrNoRow(err) {
+	questionObj := rag.Question{}
+	questionList, err := questionObj.GetListByCondition(``, ` AND is_default = 1 `, []interface{}{}, 0, 100)
+	if err != nil {
+		err = fmt.Errorf("获取问题列表失败,Err:" + err.Error())
 		return
 	}
 
-	// 生成临时文件
-	dateDir := time.Now().Format("20060102")
-	uploadDir := "./static/ai/" + dateDir
-	err = os.MkdirAll(uploadDir, utils.DIR_MOD)
-	if err != nil {
-		err = fmt.Errorf("存储目录创建失败,Err:" + err.Error())
+	// 没问题就不生成了
+	if len(questionList) <= 0 {
 		return
 	}
-	randStr := utils.GetRandStringNoSpecialChar(28)
-	fileName := randStr + `.md`
-	tmpFilePath := uploadDir + "/" + fileName
-	err = utils.SaveToFile(item.TextContent, tmpFilePath)
-	if err != nil {
-		err = fmt.Errorf("生成临时文件失败,Err:" + err.Error())
-		return
+
+	for _, question := range questionList {
+		GenerateWechatArticleAbstractByQuestion(item, question, forceGenerate)
 	}
+
+	return
+}
+
+// GenerateWechatArticleAbstractByQuestion
+// @Description: 文章摘要生成(根据提示词生成)
+// @author: Roc
+// @datetime 2025-04-24 11:23:27
+// @param item *rag.WechatArticle
+// @param question *rag.Question
+// @param forceGenerate bool
+// @return err error
+func GenerateWechatArticleAbstractByQuestion(item *rag.WechatArticle, question *rag.Question, forceGenerate bool) (err error) {
 	defer func() {
-		os.Remove(tmpFilePath)
+		if err != nil {
+			utils.FileLog.Error("文章摘要生成(根据提示词生成)失败,err:%v", err)
+		}
 	}()
 
-	// 上传临时文件到LLM
-	tmpFileResp, err := llm.UploadTempDocs(tmpFilePath)
-	if err != nil {
-		err = fmt.Errorf("上传临时文件到LLM失败,Err:" + err.Error())
+	// 内容为空,那就不需要生成摘要
+	if item.TextContent == `` {
 		return
 	}
 
-	if tmpFileResp.Data.Id == `` {
-		err = fmt.Errorf("上传临时文件到LLM失败,Err:上传失败")
+	abstractObj := rag.WechatArticleAbstract{}
+	abstractItem, err := abstractObj.GetByWechatArticleIdAndQuestionId(item.WechatArticleId, question.QuestionId)
+	// 如果找到了,同时不是强制生成,那么就直接处理到知识库中
+	if err == nil && !forceGenerate {
+		// 摘要已经生成,不需要重复生成,只需要重新加入到向量库中
+		WechatArticleAbstractToKnowledge(item, abstractItem, false)
+
 		return
 	}
-	tmpDocId := tmpFileResp.Data.Id
 
-	//tmpDocId := `c4d2ee902808408c8b8ed398b33be103` // 钢材
-	//tmpDocId := `2dde8afe62d24525a814e74e0a5e35e4` // 钢材
-	//tmpDocId := `7634cc1086c04b3687682220a2cf1a48` //
+	// 如果是没找到数据,那么就将报错置空
+	if err != nil && utils.IsErrNoRow(err) {
+		err = nil
+	}
 
+	//你现在是一名资深的期货行业分析师,请基于以下的问题进行汇总总结,如果不能正常总结出来,那么就只需要回复我:sorry
+	questionStr := fmt.Sprintf(`%s\n%s`, `你现在是一名资深的期货行业分析师,请基于以下的问题进行汇总总结,如果不能正常总结出来,那么就只需要回复我:sorry。以下是问题:`, question.QuestionContent)
 	//开始对话
-	abstract, addArticleChatRecordList, tmpErr := getAnswerByContent(item.WechatArticleId, tmpDocId)
+	abstract, industryTags, tmpErr := getAnswerByContent(item.WechatArticleId, utils.AI_ARTICLE_SOURCE_WECHAT, questionStr)
 	if tmpErr != nil {
 		err = fmt.Errorf("LLM对话失败,Err:" + tmpErr.Error())
 		return
 	}
 
-	// 添加问答记录
-	if len(addArticleChatRecordList) > 0 {
-		recordObj := rag.WechatArticleChatRecord{}
-		err = recordObj.CreateInBatches(addArticleChatRecordList)
-		if err != nil {
-			return
-		}
+	if abstract == `` {
+		return
 	}
 
-	if abstract != `` {
-		if abstract == `sorry` || strings.Index(abstract, `根据已知信息无法回答该问题`) == 0 {
-			item.AbstractStatus = 2
-			item.ModifyTime = time.Now()
-			err = item.Update([]string{"AbstractStatus", "ModifyTime"})
-			return
-		}
-		item.AbstractStatus = 1
+	if abstract == `sorry` || strings.Index(abstract, `根据已知信息无法回答该问题`) == 0 {
+		item.AbstractStatus = 2
 		item.ModifyTime = time.Now()
 		err = item.Update([]string{"AbstractStatus", "ModifyTime"})
+		return
+	}
+
+	var tagIdJsonStr string
+	var tagNameJsonStr string
+	// 标签ID
+	{
+		tagIdList := make([]int, 0)
+		tagNameList := make([]string, 0)
+		tagIdMap := make(map[int]bool)
+
+		if abstractItem != nil && abstractItem.Tags != `` {
+			tmpErr = json.Unmarshal([]byte(abstractItem.Tags), &tagIdList)
+			if tmpErr != nil {
+				utils.FileLog.Info(fmt.Sprintf("json.Unmarshal Tags 失败,标签数据:%s,Err:%s", abstractItem.Tags, tmpErr.Error()))
+			} else {
+				for _, tagId := range tagIdList {
+					tagIdMap[tagId] = true
+				}
+			}
+		}
+		if abstractItem.TagsName != `` {
+			tagNameList = strings.Split(abstractItem.TagsName, ",")
+		}
+		for _, tagName := range industryTags {
+			tagId, tmpErr := GetTagIdByName(tagName)
+			if tmpErr != nil {
+				utils.FileLog.Info(fmt.Sprintf("获取标签ID失败,标签名称:%s,Err:%s", tagName, tmpErr.Error()))
+			}
+			if _, ok := tagIdMap[tagId]; !ok {
+				tagIdList = append(tagIdList, tagId)
+				tagNameList = append(tagNameList, tagName)
+				tagIdMap[tagId] = true
+			}
+		}
+		//for _, tagName := range varietyTags {
+		//	tagId, tmpErr := GetTagIdByName(tagName)
+		//	if tmpErr != nil {
+		//		utils.FileLog.Info(fmt.Sprintf("获取标签ID失败,标签名称:%s,Err:%s", tagName, tmpErr.Error()))
+		//	}
+		//	if _, ok := tagIdMap[tagId]; !ok {
+		//		tagIdList = append(tagIdList, tagId)
+		//		tagIdMap[tagId] = true
+		//	}
+		//}
+
+		tagIdJsonByte, tmpErr := json.Marshal(tagIdList)
+		if tmpErr != nil {
+			utils.FileLog.Info(fmt.Sprintf("标签ID序列化失败,Err:%s", tmpErr.Error()))
+		} else {
+			tagIdJsonStr = string(tagIdJsonByte)
+		}
+
+		tagNameJsonStr = strings.Join(tagNameList, `,`)
+	}
 
-		abstractItem := &rag.WechatArticleAbstract{
+	item.AbstractStatus = 1
+	item.ModifyTime = time.Now()
+	err = item.Update([]string{"AbstractStatus", "ModifyTime"})
+
+	if abstractItem == nil || abstractItem.WechatArticleAbstractId <= 0 {
+		abstractItem = &rag.WechatArticleAbstract{
 			WechatArticleAbstractId: 0,
 			WechatArticleId:         item.WechatArticleId,
 			Content:                 abstract,
-			Version:                 0,
+			Version:                 1,
 			VectorKey:               "",
 			ModifyTime:              time.Now(),
 			CreateTime:              time.Now(),
+			QuestionId:              question.QuestionId,
+			Tags:                    tagIdJsonStr,
+			TagsName:                tagNameJsonStr,
+			QuestionContent:         question.QuestionContent,
 		}
 		err = abstractItem.Create()
-		if err != nil {
-			return
-		}
-
-		// 数据入ES库
-		go AddOrEditEsWechatArticleAbstract(abstractItem.WechatArticleAbstractId)
-
-		AbstractToKnowledge(item, abstractItem, false)
-	}
-}
-
-// ReGenerateArticleAbstract
-// @Description: 文章摘要重新生成
-// @author: Roc
-// @datetime 2025-03-10 16:17:53
-// @param item *rag.WechatArticle
-func ReGenerateArticleAbstract(item *rag.WechatArticle) {
-	var err error
-	defer func() {
-		if err != nil {
-			utils.FileLog.Error("文章转临时文件失败,err:%v", err)
-			fmt.Println("文章转临时文件失败,err:", err)
-		}
-	}()
-
-	abstractObj := rag.WechatArticleAbstract{}
-	abstractItem, err := abstractObj.GetByWechatArticleId(item.WechatArticleId)
-	if err != nil {
-		if utils.IsErrNoRow(err) {
-			// 直接生成
-			GenerateArticleAbstract(item)
-			return
-		}
-		// 异常了
-		return
-	}
+	} else {
+		// 添加历史记录
+		rag.AddArticleAbstractHistoryByWechatArticleAbstract(abstractItem)
 
-	// 生成临时文件
-	dateDir := time.Now().Format("20060102")
-	uploadDir := "./static/ai/" + dateDir
-	err = os.MkdirAll(uploadDir, utils.DIR_MOD)
-	if err != nil {
-		err = fmt.Errorf("存储目录创建失败,Err:" + err.Error())
-		return
-	}
-	randStr := utils.GetRandStringNoSpecialChar(28)
-	fileName := randStr + `.md`
-	tmpFilePath := uploadDir + "/" + fileName
-	err = utils.SaveToFile(item.TextContent, tmpFilePath)
-	if err != nil {
-		err = fmt.Errorf("生成临时文件失败,Err:" + err.Error())
-		return
+		abstractItem.Content = abstract
+		abstractItem.Version++
+		abstractItem.ModifyTime = time.Now()
+		abstractItem.Tags = tagIdJsonStr
+		abstractItem.TagsName = tagNameJsonStr
+		abstractItem.QuestionContent = question.QuestionContent
+		err = abstractItem.Update([]string{"content", "version", "modify_time", "tags", "tags_name", "question_content"})
 	}
-	defer func() {
-		os.Remove(tmpFilePath)
-	}()
 
-	// 上传临时文件到LLM
-	tmpFileResp, err := llm.UploadTempDocs(tmpFilePath)
 	if err != nil {
-		err = fmt.Errorf("上传临时文件到LLM失败,Err:" + err.Error())
-		return
-	}
-
-	if tmpFileResp.Data.Id == `` {
-		err = fmt.Errorf("上传临时文件到LLM失败,Err:上传失败")
 		return
 	}
-	tmpDocId := tmpFileResp.Data.Id
-
-	//tmpDocId := `c4d2ee902808408c8b8ed398b33be103` // 钢材
-	//tmpDocId := `2dde8afe62d24525a814e74e0a5e35e4` // 钢材
-	//tmpDocId := `7634cc1086c04b3687682220a2cf1a48` //
-
-	//开始对话
-	abstract, addArticleChatRecordList, tmpErr := getAnswerByContent(item.WechatArticleId, tmpDocId)
-	if tmpErr != nil {
-		err = fmt.Errorf("LLM对话失败,Err:" + tmpErr.Error())
-		return
-	}
-
-	// 添加问答记录
-	if len(addArticleChatRecordList) > 0 {
-		recordObj := rag.WechatArticleChatRecord{}
-		err = recordObj.CreateInBatches(addArticleChatRecordList)
-		if err != nil {
-			return
-		}
-	}
 
-	if abstract != `` {
-		if abstract == `sorry` || strings.Index(abstract, `根据已知信息无法回答该问题`) == 0 {
-			item.AbstractStatus = 2
-			item.ModifyTime = time.Now()
-			err = item.Update([]string{"AbstractStatus", "ModifyTime"})
-			return
-		}
-		item.AbstractStatus = 1
-		item.ModifyTime = time.Now()
-		err = item.Update([]string{"AbstractStatus", "ModifyTime"})
+	// 数据入ES库
+	go AddOrEditEsWechatArticleAbstract(abstractItem.WechatArticleAbstractId)
 
-		abstractItem.Content = abstract
-		abstractItem.Version = abstractObj.Version + 1
-		abstractItem.ModifyTime = time.Now()
-		err = abstractItem.Update([]string{"content", "version", "modify_time"})
-		if err != nil {
-			return
-		}
+	WechatArticleAbstractToKnowledge(item, abstractItem, false)
 
-		AbstractToKnowledge(item, abstractItem, true)
-	}
+	return
 }
 
 // DelDoc
@@ -529,7 +586,62 @@ func DelLlmDoc(vectorKeyList []string, wechatArticleAbstractIdList []int) (err e
 	return
 }
 
-func getAnswerByContent(wechatArticleId int, docId string) (answer string, addArticleChatRecordList []*rag.WechatArticleChatRecord, err error) {
+func getAnswerByContent(articleId int, source int, questionStr string) (answer string, tagNameList []string, err error) {
+	//addArticleChatRecordList = make([]*rag.WechatArticleChatRecord, 0)
+
+	result, err := facade.AIGCBaseOnPromote(facade.AIGC{
+		Promote:   questionStr,
+		Source:    source,
+		ArticleId: articleId,
+		LLMModel:  `deepseek-r1:32b`,
+	})
+	if err != nil {
+		return
+	}
+
+	// JSON字符串转字节
+	//answerByte, err := json.Marshal(result)
+	//if err != nil {
+	//	return
+	//}
+	//originalAnswer := string(answerByte)
+
+	// 提取 </think> 后面的内容
+	thinkEndIndex := strings.Index(result.Answer, "</think>")
+	if thinkEndIndex != -1 {
+		answer = strings.TrimSpace(result.Answer[thinkEndIndex+len("</think>"):])
+	} else {
+		answer = result.Answer
+	}
+
+	answer = strings.TrimSpace(answer)
+
+	// 提取标签
+	tagNameList = extractLabels(answer)
+
+	//// 待入库的数据
+	//addArticleChatRecordList = append(addArticleChatRecordList, &rag.WechatArticleChatRecord{
+	//	WechatArticleChatRecordId: 0,
+	//	WechatArticleId:           articleId,
+	//	ChatUserType:              "user",
+	//	Content:                   questionStr,
+	//	SendTime:                  time.Now(),
+	//	CreatedTime:               time.Now(),
+	//	UpdateTime:                time.Now(),
+	//}, &rag.WechatArticleChatRecord{
+	//	WechatArticleChatRecordId: 0,
+	//	WechatArticleId:           articleId,
+	//	ChatUserType:              "assistant",
+	//	Content:                   originalAnswer,
+	//	SendTime:                  time.Now(),
+	//	CreatedTime:               time.Now(),
+	//	UpdateTime:                time.Now(),
+	//})
+
+	return
+}
+
+func getAnswerByContentBak(wechatArticleId int, docId string) (answer string, addArticleChatRecordList []*rag.WechatArticleChatRecord, err error) {
 	historyList := make([]eta_llm_http.HistoryContent, 0)
 	addArticleChatRecordList = make([]*rag.WechatArticleChatRecord, 0)
 
@@ -649,13 +761,13 @@ func ArticleToKnowledge(item *rag.WechatArticle) {
 
 }
 
-// AbstractToKnowledge
+// WechatArticleAbstractToKnowledge
 // @Description: 摘要入向量库
 // @author: Roc
 // @datetime 2025-03-10 16:14:59
 // @param wechatArticleItem *rag.WechatArticle
 // @param abstractItem *rag.WechatArticleAbstract
-func AbstractToKnowledge(wechatArticleItem *rag.WechatArticle, abstractItem *rag.WechatArticleAbstract, isReUpload bool) {
+func WechatArticleAbstractToKnowledge(wechatArticleItem *rag.WechatArticle, abstractItem *rag.WechatArticleAbstract, isReUpload bool) {
 	if abstractItem.Content == `` {
 		return
 	}
@@ -683,7 +795,7 @@ func AbstractToKnowledge(wechatArticleItem *rag.WechatArticle, abstractItem *rag
 		err = fmt.Errorf("存储目录创建失败,Err:" + err.Error())
 		return
 	}
-	fileName := utils.RemoveSpecialChars(wechatArticleItem.Title) + `.md`
+	fileName := utils.MD5(fmt.Sprintf("%d_%d", utils.AI_ARTICLE_SOURCE_WECHAT, wechatArticleItem.WechatArticleId)) + `.md`
 	tmpFilePath := uploadDir + "/" + fileName
 	err = utils.SaveToFile(abstractItem.Content, tmpFilePath)
 	if err != nil {
@@ -985,17 +1097,19 @@ func AddOrEditEsWechatArticleAbstract(articleAbstractId int) {
 		return
 	}
 
-	// 公众号平台关联的标签品种
-	tagObj := rag.WechatPlatformTagMapping{}
-	tagMappingList, err := tagObj.GetListByCondition(` AND wechat_platform_id = ? `, []interface{}{articleInfo.WechatPlatformId}, 0, 10000)
-	if err != nil {
-		err = fmt.Errorf("获取公众号平台关联的品种信息失败,Err:" + err.Error())
-		return
+	// 标签ID
+	tagIdList := make([]int, 0)
+	if abstractInfo.Tags != `` {
+		err = json.Unmarshal([]byte(abstractInfo.Tags), &tagIdList)
+		if err != nil {
+			err = fmt.Errorf("报告标签ID转int失败,Err:" + err.Error())
+			utils.FileLog.Info(fmt.Sprintf("json.Unmarshal 报告标签ID转int失败,标签数据:%s,Err:%s", abstractInfo.Tags, err.Error()))
+		}
 	}
 
-	tagIdList := make([]int, 0)
-	for _, v := range tagMappingList {
-		tagIdList = append(tagIdList, v.TagId)
+	tagNameList := make([]string, 0)
+	if abstractInfo.TagsName != `` {
+		tagNameList = strings.Split(abstractInfo.TagsName, ",")
 	}
 
 	esItem := elastic.WechatArticleAbstractItem{
@@ -1003,18 +1117,162 @@ func AddOrEditEsWechatArticleAbstract(articleAbstractId int) {
 		WechatArticleId:         abstractInfo.WechatArticleId,
 		WechatPlatformId:        articleInfo.WechatPlatformId,
 		Abstract:                abstractInfo.Content,
+		QuestionId:              abstractInfo.QuestionId,
 		Version:                 abstractInfo.Version,
 		VectorKey:               abstractInfo.VectorKey,
-		ModifyTime:              articleInfo.ModifyTime,
-		CreateTime:              articleInfo.CreateTime,
+		ModifyTime:              abstractInfo.ModifyTime,
+		CreateTime:              abstractInfo.CreateTime,
 		Title:                   articleInfo.Title,
 		Link:                    articleInfo.Link,
 		TagIdList:               tagIdList,
+		TagNameList:             tagNameList,
 	}
 
 	err = elastic.WechatArticleAbstractEsAddOrEdit(strconv.Itoa(articleAbstractId), esItem)
 }
 
+// DelWechatArticleAbstract
+// @Description: 删除微信文章摘要
+// @author: Roc
+// @datetime 2025-04-23 17:36:22
+// @param abstractIdList []int
+// @return err error
+func DelWechatArticleAbstract(abstractIdList []int) (err error) {
+	obj := rag.WechatArticleAbstract{}
+
+	list, err := obj.GetByIdList(abstractIdList)
+	if err != nil {
+		if !utils.IsErrNoRow(err) {
+			err = errors.New("删除向量库失败,Err:" + err.Error())
+		} else {
+			err = nil
+		}
+		return
+	}
+
+	err = delWechatArticleAbstract(list)
+
+	return
+}
+
+// DelWechatArticleAbstract
+// @Description: 删除微信文章摘要
+// @author: Roc
+// @datetime 2025-04-23 17:36:22
+// @param abstractIdList []int
+// @return err error
+func DelWechatArticleAbstractByQuestionId(questionId int) (err error) {
+	obj := rag.WechatArticleAbstract{}
+
+	list, err := obj.GetListByQuestionId(questionId)
+	if err != nil {
+		if !utils.IsErrNoRow(err) {
+			err = errors.New("删除向量库失败,Err:" + err.Error())
+		} else {
+			err = nil
+		}
+		return
+	}
+
+	err = delWechatArticleAbstract(list)
+
+	return
+}
+
+// delRagEtaReportAbstract
+// @Description: 删除摘要
+// @author: Roc
+// @datetime 2025-04-24 15:19:19
+// @param list []*rag.RagEtaReportAbstract
+// @return err error
+func delWechatArticleAbstract(list []*rag.WechatArticleAbstract) (err error) {
+	obj := rag.RagEtaReportAbstract{}
+
+	vectorKeyList := make([]string, 0)
+	newAbstractIdList := make([]int, 0)
+
+	if len(list) > 0 {
+		for _, v := range list {
+			// 有加入到向量库,那么就加入到待删除的向量库list中
+			if v.VectorKey != `` {
+				vectorKeyList = append(vectorKeyList, v.VectorKey)
+			}
+			newAbstractIdList = append(newAbstractIdList, v.WechatArticleAbstractId)
+		}
+	}
+
+	//if !req.IsSelectAll {
+	//	list, err := obj.GetByIdList(req.RagEtaReportAbstractIdList)
+	//	if err != nil {
+	//		br.Msg = "修改失败"
+	//		br.ErrMsg = "修改失败,查找问题失败,Err:" + err.Error()
+	//		if utils.IsErrNoRow(err) {
+	//			br.Msg = "问题不存在"
+	//			br.IsSendEmail = false
+	//		}
+	//		return
+	//	}
+	//	if len(list) > 0 {
+	//		for _, v := range list {
+	//			// 有加入到向量库,那么就加入到待删除的向量库list中
+	//			if v.VectorKey != `` {
+	//				vectorKeyList = append(vectorKeyList, v.VectorKey)
+	//			}
+	//			wechatArticleAbstractIdList = append(wechatArticleAbstractIdList, v.RagEtaReportAbstractId)
+	//		}
+	//	}
+	//} else {
+	//	notIdMap := make(map[int]bool)
+	//	for _, v := range req.NotRagEtaReportAbstractIdList {
+	//		notIdMap[v] = true
+	//	}
+	//
+	//	_, list, err := getRagEtaReportAbstractList(req.KeyWord, req.TagId, 0, 100000)
+	//	if err != nil {
+	//		br.Msg = "修改失败"
+	//		br.ErrMsg = "修改失败,查找问题失败,Err:" + err.Error()
+	//		if utils.IsErrNoRow(err) {
+	//			br.Msg = "问题不存在"
+	//			br.IsSendEmail = false
+	//		}
+	//		return
+	//	}
+	//	if len(list) > 0 {
+	//		for _, v := range list {
+	//			if notIdMap[v.RagEtaReportAbstractId] {
+	//				continue
+	//			}
+	//			// 有加入到向量库,那么就加入到待删除的向量库list中
+	//			if v.VectorKey != `` {
+	//				vectorKeyList = append(vectorKeyList, v.VectorKey)
+	//			}
+	//			wechatArticleAbstractIdList = append(wechatArticleAbstractIdList, v.RagEtaReportAbstractId)
+	//		}
+	//	}
+	//}
+
+	// 删除向量库
+	err = DelLlmDoc(vectorKeyList, newAbstractIdList)
+	if err != nil {
+		err = errors.New("删除向量库失败,Err:" + err.Error())
+		return
+	}
+
+	// 删除摘要
+	err = obj.DelByIdList(newAbstractIdList)
+	if err != nil {
+		err = errors.New("删除失败,Err:" + err.Error())
+		return
+	}
+
+	// 删除es数据
+	for _, wechatArticleAbstractId := range newAbstractIdList {
+		DelEsWechatArticleAbstract(wechatArticleAbstractId)
+	}
+
+	return
+}
+
 // DelEsWechatArticleAbstract
 // @Description: 删除ES中的微信文章摘要
 // @author: Roc
@@ -1028,8 +1286,8 @@ func DelEsWechatArticleAbstract(articleAbstractId int) {
 	var err error
 	defer func() {
 		if err != nil {
-			utils.FileLog.Error("添加公众号微信信息到ES失败,err:%v", err)
-			fmt.Println("添加公众号微信信息到ES失败,err:", err)
+			utils.FileLog.Error("删除公众号微信信息到ES失败,err:%v", err)
+			fmt.Println("删除公众号微信信息到ES失败,err:", err)
 		}
 	}()
 
@@ -1065,6 +1323,7 @@ func AddOrEditEsRagQuestion(questionId int) {
 		QuestionTitle:   questionInfo.QuestionTitle,
 		QuestionContent: questionInfo.QuestionContent,
 		Sort:            questionInfo.Sort,
+		IsDefault:       questionInfo.IsDefault,
 		SysUserId:       questionInfo.SysUserId,
 		SysUserRealName: questionInfo.SysUserRealName,
 		ModifyTime:      questionInfo.ModifyTime,
@@ -1094,3 +1353,77 @@ func DelEsRagQuestion(questionId int) {
 
 	err = elastic.RagQuestionEsDel(strconv.Itoa(questionId))
 }
+
+// extractLabels
+// @Description: 提取摘要中的标签并去重
+// @author: Roc
+// @datetime 2025-04-18 17:16:05
+// @param text string
+// @return industryTags []string
+// @return varietyTags []string
+func extractLabels(text string) (tags []string) {
+	reTag := regexp.MustCompile(`【([^】]*)】`)
+
+	// 提取所有标签
+	tagMatches := reTag.FindAllStringSubmatch(text, -1)
+	tagSet := make(map[string]bool)
+	for _, match := range tagMatches {
+		if len(match) > 1 {
+			tagSet[match[1]] = true
+		}
+	}
+
+	// 将去重后的标签转换为切片
+	for tag := range tagSet {
+		// 为空串就不处理
+		if tag == `` {
+			continue
+		}
+		tags = append(tags, tag)
+	}
+	return
+}
+
+var aiAbstractTagMap = map[string]int{}
+
+// GetTagIdByName
+// @Description: 获取标签ID
+// @author: Roc
+// @datetime 2025-04-18 17:25:46
+// @param tagName string
+// @return tagId int
+// @return err error
+func GetTagIdByName(tagName string) (tagId int, err error) {
+	tagName = strings.TrimSpace(tagName)
+	tagId, ok := aiAbstractTagMap[tagName]
+	if ok {
+		return
+	}
+
+	obj := rag.Tag{}
+	item, err := obj.GetByCondition(fmt.Sprintf(` AND  %s = ? `, rag.TagColumns.TagName), []interface{}{tagName})
+	if err != nil {
+		if !utils.IsErrNoRow(err) {
+			err = fmt.Errorf("获取标签失败,Err:" + err.Error())
+			return
+		}
+
+		item = &rag.Tag{
+			TagId:      0,
+			TagName:    tagName,
+			Sort:       0,
+			ModifyTime: time.Now(),
+			CreateTime: time.Now(),
+		}
+		err = item.Create()
+		if err != nil {
+			err = fmt.Errorf("添加标签失败,Err:" + err.Error())
+			return
+		}
+	}
+
+	tagId = item.TagId
+	aiAbstractTagMap[tagName] = tagId
+
+	return
+}

文件差異過大導致無法顯示
+ 0 - 0
static/imgs/ai/article/【专题报告】关税来袭黑色怎么看.md


文件差異過大導致無法顯示
+ 0 - 0
static/imgs/ai/article/【开源宏观】财政支出力度如何12月财政数据点评.md


文件差異過大導致無法顯示
+ 0 - 0
static/imgs/ai/article/巴菲特2025股东信1000字精华版来了附全文.md


+ 2 - 1
utils/config.go

@@ -149,6 +149,7 @@ var (
 	EsWechatArticleName            string // ES索引名称-微信文章
 	EsWechatArticleAbstractName    string // ES索引名称-微信文章摘要
 	EsRagQuestionName              string // ES索引名称-知识库问题
+	EsRagEtaReportAbstractName     string // ES索引名称-ETA报告摘要
 )
 
 var (
@@ -303,7 +304,7 @@ var (
 )
 
 var (
-	RaiReportLibUrl string // 权益报告库地址
+	RaiReportLibUrl           string // 权益报告库地址
 	RaiReportLibAuthorization string // 权益报告库鉴权
 )
 

+ 13 - 1
utils/constants.go

@@ -270,7 +270,10 @@ const (
 	CACHE_EXCEL_REFRESH                     = "CACHE_EXCEL_REFRESH"                   // 表格刷新
 	CACHE_WECHAT_PLATFORM_ARTICLE           = "wechat_platform:article:op:"           //微信文章处理
 	CACHE_WECHAT_PLATFORM_ARTICLE_KNOWLEDGE = "wechat_platform:article:knowledge:op:" //微信文章入知识库处理
-	CACHE_ETA_REPORT_KNOWLEDGE              = "eta:report:knowledge:op:"              //eta报告入知识库处理
+	CACHE_ETA_REPORT_KNOWLEDGE              = "eta:report:knowledge:op:"              //eta报告入AI库处理
+	CACHE_ETA_REPORT_KNOWLEDGE_LLM          = "eta:report:knowledge:llm:op:"          //eta报告入知识库处理
+	CACHE_AI_ARTICLE_ABSTRACT_LLM_TASK      = "eta:ai:article:abstract:llm:task:op:"  //微信文章/eta报告的摘要重新生成处理(任务调度)
+	CACHE_AI_ARTICLE_ABSTRACT_DEL           = "eta:ai:article:abstract:del:op:"       //微信文章/eta报告的摘要删删除缓存,避免有人在删除的过程中,又将该提示词做摘要生成
 	CACHE_CHART_AUTH                        = "eta:chart:auth:"                       //图表数据授权
 	CACHE_REPORT_SHARE_AUTH                 = "eta:report:auth:share:"                //报告短链与报告图表授权映射key
 	CACHE_REPORT_AUTH                       = "eta:report:auth:"                      //报告图表数据授权
@@ -605,6 +608,15 @@ const (
 	DATA_SOURCE_NAME_RADISH_RESEARCH = "萝卜投研" // 萝卜投研 -> 105
 )
 
+const (
+	AI_TASK_TYPE_GENERATE_ABSTRACT = `question_generate_abstract` // AI任务去批量生成摘要
+)
+
+const (
+	AI_ARTICLE_SOURCE_WECHAT     = 0 // AI文章来源(微信公众号)
+	AI_ARTICLE_SOURCE_ETA_REPORT = 1 // AI文章来源(ETA报告)
+)
+
 const (
 	INDEX_TASK_TYPE_AI_MODEL_TRAIN = `ai_predict_model_train`
 	INDEX_TASK_TYPE_AI_MODEL_RUN   = `ai_predict_model_run`

+ 34 - 23
utils/llm/eta_llm/eta_llm_client.go

@@ -341,48 +341,59 @@ func parseResponse(response *http.Response) (baseResp eta_llm_http.BaseResponse,
 	baseResp.Data = bodyBytes
 	return
 }
-func ParseStreamResponse(response *http.Response) (contentChan chan string, errChan chan error, closeChan chan struct{}) {
+func ParseStreamResponse(response *http.Response) (contentChan chan string, errChan chan error, closeChan chan struct{}, closeLlmChan chan bool) {
 	contentChan = make(chan string, 10)
 	errChan = make(chan error, 10)
 	closeChan = make(chan struct{})
+	closeLlmChan = make(chan bool, 1)
 	go func() {
 		defer close(contentChan)
 		defer close(errChan)
 		defer close(closeChan)
+		defer close(closeLlmChan)
+
 		scanner := bufio.NewScanner(response.Body)
 		scanner.Split(bufio.ScanLines)
+
 		for scanner.Scan() {
-			line := scanner.Text()
-			if line == "" {
-				continue
-			}
-			// 忽略 "ping" 行
-			if strings.HasPrefix(line, ": ping") {
-				continue
-			}
-			// 去除 "data: " 前缀
-			if strings.HasPrefix(line, "data: ") {
-				line = strings.TrimPrefix(line, "data: ")
-			}
-			var chunk eta_llm_http.ChunkResponse
-			if err := json.Unmarshal([]byte(line), &chunk); err != nil {
-				fmt.Println("解析错误的line:" + line)
-				errChan <- fmt.Errorf("解析 JSON 块失败: %w", err)
+			select {
+			case <-closeLlmChan:
 				return
-			}
-			// 处理每个 chunk
-			if chunk.Choices != nil && len(chunk.Choices) > 0 {
-				for _, choice := range chunk.Choices {
-					if choice.Delta.Content != "" {
-						contentChan <- choice.Delta.Content
+			default:
+				line := scanner.Text()
+				if line == "" {
+					continue
+				}
+				// 忽略 "ping" 行
+				if strings.HasPrefix(line, ": ping") {
+					continue
+				}
+				// 去除 "data: " 前缀
+				if strings.HasPrefix(line, "data: ") {
+					line = strings.TrimPrefix(line, "data: ")
+				}
+				var chunk eta_llm_http.ChunkResponse
+				if err := json.Unmarshal([]byte(line), &chunk); err != nil {
+					fmt.Println("解析错误的line:" + line)
+					errChan <- fmt.Errorf("解析 JSON 块失败: %w", err)
+					return
+				}
+				// 处理每个 chunk
+				if chunk.Choices != nil && len(chunk.Choices) > 0 {
+					for _, choice := range chunk.Choices {
+						if choice.Delta.Content != "" {
+							contentChan <- choice.Delta.Content
+						}
 					}
 				}
 			}
+
 		}
 		if err := scanner.Err(); err != nil {
 			errChan <- fmt.Errorf("读取响应体失败: %w", err)
 			return
 		}
+
 	}()
 	return
 }

+ 1 - 0
utils/redis.go

@@ -19,6 +19,7 @@ type RedisClient interface {
 	IsExist(key string) bool
 	LPush(key string, val interface{}) error
 	Brpop(key string, callback func([]byte))
+	LLen(key string) (int64, error)
 	GetRedisTTL(key string) time.Duration
 	Incrby(key string, num int) (interface{}, error)
 	Do(commandName string, args ...interface{}) (reply interface{}, err error)

+ 12 - 0
utils/redis/cluster_redis.go

@@ -249,6 +249,18 @@ func (rc *ClusterRedisClient) Brpop(key string, callback func([]byte)) {
 
 }
 
+// LLen
+// @Description: 获取list中剩余的数据数
+// @author: Roc
+// @receiver rc
+// @datetime 2025-04-25 10:58:25
+// @param key string
+// @return int64
+// @return error
+func (rc *ClusterRedisClient) LLen(key string) (int64, error) {
+	return rc.redisClient.LLen(context.TODO(), key).Result()
+}
+
 // GetRedisTTL
 // @Description: 获取key的过期时间
 // @receiver rc

+ 12 - 0
utils/redis/standalone_redis.go

@@ -237,6 +237,18 @@ func (rc *StandaloneRedisClient) Brpop(key string, callback func([]byte)) {
 
 }
 
+// LLen
+// @Description: 获取list中剩余的数据数
+// @author: Roc
+// @receiver rc
+// @datetime 2025-04-25 10:58:25
+// @param key string
+// @return int64
+// @return error
+func (rc *StandaloneRedisClient) LLen(key string) (int64, error) {
+	return rc.redisClient.LLen(context.TODO(), key).Result()
+}
+
 // GetRedisTTL
 // @Description: 获取key的过期时间
 // @receiver rc

+ 24 - 19
utils/ws/session.go

@@ -12,22 +12,25 @@ import (
 
 // Session 会话结构
 type Session struct {
-	Id          string
-	UserId      int
-	Conn        *websocket.Conn
-	LastActive  time.Time
-	Latency     *LatencyMeasurer
-	History     []json.RawMessage
-	CloseChan   chan struct{}
-	MessageChan chan string
-	mu          sync.RWMutex
-	sessionOnce sync.Once
+	Id           string
+	UserId       int
+	Conn         *websocket.Conn
+	LastActive   time.Time
+	Latency      *LatencyMeasurer
+	History      []json.RawMessage
+	CloseChan    chan struct{}
+	MessageChan  chan string
+	mu           sync.RWMutex
+	sessionOnce  sync.Once
+	CloseLlmChan *chan bool
+	LLMStatus    int8 // llm提问状态,0:未提问,1:提问中,-1:暂停提问
 }
 
 type Message struct {
-	KbName string `json:"KbName"`
-	Query  string `json:"Query"`
-	ChatId int    `json:"ChatId"`
+	KbName      string `json:"KbName"`
+	Query       string `json:"Query"`
+	ChatId      int    `json:"ChatId"`
+	MessageType string `json:"MessageType"`
 	//LastTopics []json.RawMessage `json:"LastTopics"`
 }
 
@@ -48,13 +51,15 @@ func (s *Session) readPump() {
 		}
 		// 更新活跃时间
 		s.UpdateActivity()
+
 		// 处理消息
-		if err = manager.HandleMessage(s.UserId, s.Id, message); err != nil {
-			//写应答
-			_ = s.writeWithTimeout("<think></think>")
-			_ = s.writeWithTimeout(err.Error())
-			_ = s.writeWithTimeout("<EOF/>")
-		}
+		//if err = manager.HandleMessage(s.UserId, s.Id, message); err != nil {
+		//	//写应答
+		//	_ = s.writeWithTimeout("<think></think>")
+		//	_ = s.writeWithTimeout(err.Error())
+		//	_ = s.writeWithTimeout("<EOF/>")
+		//}
+		go manager.HandleMessage(s.UserId, s.Id, message)
 	}
 }
 

+ 60 - 23
utils/ws/session_manager.go

@@ -55,29 +55,53 @@ func Manager() *ConnectionManager {
 }
 
 // HandleMessage 消息处理核心逻辑
-func (manager *ConnectionManager) HandleMessage(userID int, sessionID string, message []byte) error {
-
+func (manager *ConnectionManager) HandleMessage(userID int, sessionID string, message []byte) {
+	var err error
 	session, exists := manager.GetSession(sessionID)
 	if !exists {
-		return errors.New("session not found")
+		err = errors.New("session not found")
+		return
 	}
+
 	if strings.ToLower(string(message)) == "pong" {
 		session.UpdateActivity()
 		fmt.Printf("收到心跳消息,续期长连接:%v", session.LastActive)
-		return nil
-	}
-	if !Allow(userID, QA_LIMITER) {
-		_ = session.Conn.WriteMessage(websocket.TextMessage, []byte("<think></think>"))
-		_ = session.Conn.WriteMessage(websocket.TextMessage, []byte("您提问的太频繁了,请稍后再试"))
-		_ = session.Conn.WriteMessage(websocket.TextMessage, []byte("<EOF/>"))
-		return nil
+		return
 	}
+	defer func() {
+		if err != nil {
+			//写应答
+			_ = session.writeWithTimeout("<think></think>")
+			_ = session.writeWithTimeout(err.Error())
+			_ = session.writeWithTimeout("<EOF/>")
+		}
+	}()
 	var userMessage Message
-	err := json.Unmarshal(message, &userMessage)
+	err = json.Unmarshal(message, &userMessage)
 	if err != nil {
 		utils.FileLog.Error(fmt.Sprintf("消息格式错误:%s", string(message)))
 		fmt.Printf("消息格式错误:%s", string(message))
-		return errors.New("消息格式错误:" + err.Error())
+		err = errors.New("消息格式错误:" + err.Error())
+		return
+	}
+
+	if userMessage.MessageType == `stop` {
+		if session.LLMStatus == 1 {
+			// 标记llm提问状态:暂停提问
+			session.LLMStatus = -1
+		}
+		if session.CloseLlmChan != nil {
+			*session.CloseLlmChan <- true
+		}
+		return
+	}
+
+	// 限流
+	if !Allow(userID, QA_LIMITER) {
+		_ = session.Conn.WriteMessage(websocket.TextMessage, []byte("<think></think>"))
+		_ = session.Conn.WriteMessage(websocket.TextMessage, []byte("您提问的太频繁了,请稍后再试"))
+		_ = session.Conn.WriteMessage(websocket.TextMessage, []byte("<EOF/>"))
+		return
 	}
 	// 处理业务逻辑
 	//session.History = append(session.History, userMessage.LastTopics...)
@@ -104,29 +128,37 @@ func (manager *ConnectionManager) HandleMessage(userID int, sessionID string, me
 	}()
 	if resp == nil {
 		utils.FileLog.Error("知识库问答失败: 无应答")
-		return errors.New("知识库问答失败: 无应答")
+		err = errors.New("知识库问答失败: 无应答")
+		return
 	}
 	if err != nil {
 		utils.FileLog.Error(fmt.Sprintf("知识库问答失败: httpCode:%d,错误信息:%s", resp.StatusCode, http.StatusText(resp.StatusCode)))
 		err = errors.New(fmt.Sprintf("知识库问答失败: httpCode:%d,错误信息:%s", resp.StatusCode, http.StatusText(resp.StatusCode)))
-		return err
+		return
 	}
 
 	if resp.StatusCode != http.StatusOK {
 		utils.FileLog.Error(fmt.Sprintf("知识库问答失败: httpCode:%d,错误信息:%s", resp.StatusCode, http.StatusText(resp.StatusCode)))
 		err = errors.New(fmt.Sprintf("知识库问答失败: httpCode:%d,错误信息:%s", resp.StatusCode, http.StatusText(resp.StatusCode)))
-		return err
+		return
 	}
+
 	// 解析流式响应
-	contentChan, errChan, closeChan := eta_llm.ParseStreamResponse(resp)
+	contentChan, errChan, closeChan, closeLlmChan := eta_llm.ParseStreamResponse(resp)
+	session.CloseLlmChan = &closeLlmChan
+	// 标记llm提问状态:提问中
+	session.LLMStatus = 1
 	emptyContent := true
 	// 处理流式数据并发送到 WebSocket
 	for {
 		select {
 		case content, ok := <-contentChan:
-			if !ok {
-				err = errors.New("未知的错误异常")
-				return err
+			if !ok && session.LLMStatus != -1 {
+				err = errors.New("未知的内容错误异常")
+
+				// 标记llm提问状态:未提问
+				session.LLMStatus = 0
+				return
 			}
 			session.UpdateActivity()
 			if emptyContent {
@@ -135,20 +167,25 @@ func (manager *ConnectionManager) HandleMessage(userID int, sessionID string, me
 			// 发送消息到 WebSocket
 			_ = session.Conn.WriteMessage(websocket.TextMessage, []byte(content))
 		case chanErr, ok := <-errChan:
-			if !ok {
+			if !ok && session.LLMStatus != -1 {
 				err = errors.New("未知的错误异常")
-			} else {
+			} else if chanErr != nil {
 				err = errors.New(chanErr.Error())
 			}
+			// 标记llm提问状态:未提问
+			session.LLMStatus = 0
 			// 发送错误消息到 WebSocket
-			return err
+			return
 		case <-closeChan:
 			if emptyContent {
 				_ = session.Conn.WriteMessage(websocket.TextMessage, []byte("<think></think>"))
 				_ = session.Conn.WriteMessage(websocket.TextMessage, []byte("暂时找不到答案"))
 			}
 			_ = session.Conn.WriteMessage(websocket.TextMessage, []byte("<EOF/>"))
-			return nil
+			// 标记llm提问状态:未提问
+			session.LLMStatus = 0
+
+			return
 		}
 	}
 	// 更新最后活跃时间

部分文件因文件數量過多而無法顯示