Bläddra i källkod

Merge branch 'master' into feature/eta2.5.9_api_stat

# Conflicts:
#	utils/redis.go
#	utils/redis/cluster_redis.go
#	utils/redis/standalone_redis.go
xyxie 16 timmar sedan
förälder
incheckning
3c8087729e
78 ändrade filer med 5993 tillägg och 1124 borttagningar
  1. 2 1
      .gitignore
  2. 84 8
      cache/llm.go
  3. 31 2
      controllers/data_manage/ai_predict_model/classify.go
  4. 147 1
      controllers/data_manage/ai_predict_model/index.go
  5. 132 0
      controllers/data_manage/chart_common.go
  6. 1 1
      controllers/data_manage/edb_classify.go
  7. 74 4
      controllers/data_manage/edb_info.go
  8. 28 10
      controllers/data_manage/edb_info_refresh.go
  9. 6 0
      controllers/data_manage/excel/excel_classify.go
  10. 70 33
      controllers/data_manage/excel/excel_info.go
  11. 1 1
      controllers/data_manage/predict_edb_classify.go
  12. 1 0
      controllers/eta_forum/eta_forum.go
  13. 24 3
      controllers/fe_calendar/fe_calendar_matter.go
  14. 206 212
      controllers/llm/abstract.go
  15. 47 0
      controllers/llm/kb_controller.go
  16. 6 1
      controllers/llm/llm_http/response.go
  17. 367 27
      controllers/llm/question.go
  18. 422 0
      controllers/llm/rag_eta_report_abstract.go
  19. 85 1
      controllers/llm/report.go
  20. 0 2
      controllers/llm/user_chat_controller.go
  21. 84 0
      controllers/llm/wechat_platform.go
  22. 4 1
      controllers/sys_admin.go
  23. 4 5
      controllers/sys_role.go
  24. 18 0
      controllers/sys_user.go
  25. 2 0
      controllers/user_login.go
  26. 71 9
      models/ai_predict_model/ai_predict_model_index.go
  27. 5 0
      models/business_conf.go
  28. 7 0
      models/chart_permission.go
  29. 6 0
      models/data_manage/chart_edb_mapping.go
  30. 39 0
      models/data_manage/chart_info.go
  31. 6 4
      models/data_manage/edb_info.go
  32. 7 0
      models/data_manage/excel/excel_info.go
  33. 164 0
      models/rag/ai_task.go
  34. 115 0
      models/rag/ai_task_record.go
  35. 114 0
      models/rag/article_abstract_history.go
  36. 19 4
      models/rag/question.go
  37. 86 0
      models/rag/question_history.go
  38. 5 5
      models/rag/rag_eta_report.go
  39. 292 0
      models/rag/rag_eta_report_abstract.go
  40. 9 0
      models/rag/request/rag_eta_report.go
  41. 1 1
      models/rag/request/wechat_platform.go
  42. 15 0
      models/rag/response/abstract.go
  43. 5 0
      models/rag/response/question.go
  44. 23 1
      models/rag/tag.go
  45. 82 12
      models/rag/wechat_article_abstract.go
  46. 1 1
      models/sandbox/sandbox_classify.go
  47. 1 1
      models/target.go
  48. 108 0
      routers/commentsRouter.go
  49. 1 0
      routers/router.go
  50. 5 5
      services/ai_predict_model_classify.go
  51. 179 4
      services/ai_predict_model_index.go
  52. 1 0
      services/crm_eta.go
  53. 4 2
      services/data/edb_classify.go
  54. 45 0
      services/data/edb_info_relation.go
  55. 40 2
      services/elastic/elastic.go
  56. 317 0
      services/elastic/rag_eta_report_abstract.go
  57. 7 1
      services/elastic/rag_question.go
  58. 16 2
      services/elastic/wechat_article_abstract.go
  59. 2 1
      services/excel/excel_elastic.go
  60. 334 0
      services/llm.go
  61. 9 5
      services/llm/facade/llm_service.go
  62. 517 0
      services/llm_report.go
  63. 21 0
      services/material/material.go
  64. 3 3
      services/residual_analysis_service/residual_analysis_service.go
  65. 291 24
      services/task.go
  66. 506 484
      services/temp_fix.go
  67. 506 173
      services/wechat_platform.go
  68. 0 0
      static/imgs/ai/article/【专题报告】关税来袭黑色怎么看.md
  69. 0 0
      static/imgs/ai/article/【开源宏观】财政支出力度如何12月财政数据点评.md
  70. 0 0
      static/imgs/ai/article/巴菲特2025股东信1000字精华版来了附全文.md
  71. 2 1
      utils/config.go
  72. 19 1
      utils/constants.go
  73. 34 23
      utils/llm/eta_llm/eta_llm_client.go
  74. 1 0
      utils/redis.go
  75. 11 0
      utils/redis/cluster_redis.go
  76. 11 0
      utils/redis/standalone_redis.go
  77. 24 19
      utils/ws/session.go
  78. 60 23
      utils/ws/session_manager.go

+ 2 - 1
.gitignore

@@ -19,4 +19,5 @@ eta_api.exe
 eta_api.exe~
 /static/tmpFile/*
 etalogs/
-/.vscode
+/.vscode
+/fix

+ 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
+}

+ 31 - 2
controllers/data_manage/ai_predict_model/classify.go

@@ -8,6 +8,7 @@ import (
 	"eta/eta_api/models/data_manage"
 	dataSourceModel "eta/eta_api/models/data_source"
 	"eta/eta_api/services"
+	"eta/eta_api/services/data"
 	"eta/eta_api/services/elastic"
 	"eta/eta_api/utils"
 	"fmt"
@@ -444,15 +445,36 @@ func (this *AiPredictModelClassifyController) Remove() {
 			br.ErrMsg = fmt.Sprintf("获取标的信息失败, %v", e)
 			return
 		}
+		if aiIndex != nil && aiIndex.AiPredictModelIndexId <= 0 {
+			br.Ret = 200
+			br.Msg = "删除成功"
+			br.Success = true
+			return
+		}
+
+		// 获取指标图表
+		var chartIds []int
+		chartTypes := []int{utils.CHART_SOURCE_AI_PREDICT_MODEL_DAILY, utils.CHART_SOURCE_AI_PREDICT_MODEL_MONTHLY}
+		for _, v := range chartTypes {
+			ct, e := data_manage.GetAiPredictChartInfoByIndexId(v, req.IndexId)
+			if e != nil && !utils.IsErrNoRow(e) {
+				br.Msg = "操作失败"
+				br.ErrMsg = fmt.Sprintf("获取标的图表信息失败, %v", e)
+				return
+			}
+			if ct != nil && ct.ChartInfoId > 0 {
+				chartIds = append(chartIds, ct.ChartInfoId)
+			}
+		}
 
 		// 删除标的及数据
-		if e = indexOb.RemoveIndexAndData(req.IndexId); e != nil {
+		if e = indexOb.RemoveIndexAndData(req.IndexId, chartIds); e != nil {
 			br.Msg = "操作失败"
 			br.ErrMsg = fmt.Sprintf("删除标的及数据失败, %v", e)
 			return
 		}
 
-		// ES标记删除
+		// ES标记标的/图表删除
 		go func() {
 			indexItem := new(dataSourceModel.SearchDataSource)
 			indexItem.PrimaryId = aiIndex.AiPredictModelIndexId
@@ -470,6 +492,13 @@ func (this *AiPredictModelClassifyController) Remove() {
 				utils.FileLog.Info("AI预测模型-标记删除es失败, %v", e)
 				return
 			}
+
+			if len(chartIds) == 0 {
+				return
+			}
+			for _, v := range chartIds {
+				data.EsDeleteChartInfo(v)
+			}
 		}()
 	}
 

+ 147 - 1
controllers/data_manage/ai_predict_model/index.go

@@ -5,9 +5,11 @@ import (
 	"eta/eta_api/controllers"
 	"eta/eta_api/models"
 	aiPredictModel "eta/eta_api/models/ai_predict_model"
+	"eta/eta_api/models/data_manage"
 	dataSourceModel "eta/eta_api/models/data_source"
 	"eta/eta_api/models/system"
 	"eta/eta_api/services"
+	"eta/eta_api/services/data"
 	"eta/eta_api/services/elastic"
 	"eta/eta_api/utils"
 	"fmt"
@@ -583,7 +585,7 @@ func (this *AiPredictModelIndexController) Import() {
 	}
 
 	// 导入指标
-	if e = services.ImportAiPredictModelIndexAndData(importIndexes); e != nil {
+	if e = services.ImportAiPredictModelIndexAndData(importIndexes, sysUser.AdminId, sysUser.RealName); e != nil {
 		br.Msg = "操作失败"
 		br.ErrMsg = fmt.Sprintf("导入指标数据失败, %v", e)
 		return
@@ -984,3 +986,147 @@ func (this *AiPredictModelIndexController) DashboardDetail() {
 	br.Success = true
 	br.Msg = "获取成功"
 }
+
+// SearchByEs
+// @Title 图表模糊搜索(从es获取)
+// @Description  图表模糊搜索(从es获取)
+// @Param   Keyword   query   string  true       "图表名称"
+// @Param   IsShowMe   query   bool  true       "是否只看我的,true、false"
+// @Param   Source   query   int  true       "来源,14:日度预测,15:月度预测,默认0:全部14+15"
+// @Success 200 {object} data_manage.ChartInfo
+// @router /chart/search_by_es [get]
+func (this *AiPredictModelIndexController) SearchByEs() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = false
+		}
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+	pageSize, _ := this.GetInt("PageSize")
+	currentIndex, _ := this.GetInt("CurrentIndex")
+
+	var startSize int
+	if pageSize <= 0 {
+		pageSize = utils.PageSize20
+	}
+	if currentIndex <= 0 {
+		currentIndex = 1
+	}
+	startSize = paging.StartIndex(currentIndex, pageSize)
+
+	keyword := this.GetString("Keyword")
+	keyword = strings.TrimSpace(keyword)
+	if keyword == "" {
+		keyword = this.GetString("KeyWord")
+		keyword = strings.TrimSpace(keyword)
+	}
+
+	//只看我的
+	isShowMe, _ := this.GetBool("IsShowMe")
+	showSysId := 0
+	if isShowMe {
+		showSysId = sysUser.AdminId
+	}
+
+	source, _ := this.GetInt("Source")
+	sourceList := make([]int, 0)
+	if source <= 0 {
+		sourceList = append(sourceList, utils.CHART_SOURCE_AI_PREDICT_MODEL_MONTHLY, utils.CHART_SOURCE_AI_PREDICT_MODEL_DAILY)
+	} else {
+		sourceList = append(sourceList, source)
+	}
+
+	var searchList []*data_manage.ChartInfoMore
+	var total int64
+	var err error
+
+	// 获取当前账号的不可见指标(AI预测的指标为标的均可见)
+	noPermissionChartIdList := make([]int, 0)
+	//{
+	//	obj := data_manage.EdbInfoNoPermissionAdmin{}
+	//	confList, err := obj.GetAllChartListByAdminId(this.SysUser.AdminId)
+	//	if err != nil && !utils.IsErrNoRow(err) {
+	//		br.Msg = "获取失败"
+	//		br.ErrMsg = "获取不可见指标配置数据失败,Err:" + err.Error()
+	//		return
+	//	}
+	//	for _, v := range confList {
+	//		noPermissionChartIdList = append(noPermissionChartIdList, v.ChartInfoId)
+	//	}
+	//}
+
+	if keyword != "" {
+		searchList, total, err = data.EsSearchChartInfo(keyword, showSysId, sourceList, noPermissionChartIdList, startSize, pageSize)
+	} else {
+		total, searchList, err = data_manage.ChartInfoSearchByEmptyKeyWord(showSysId, sourceList, noPermissionChartIdList, startSize, pageSize)
+		if err != nil && !utils.IsErrNoRow(err) {
+			br.Msg = "获取失败"
+			br.ErrMsg = "获取图表信息失败,Err:" + err.Error()
+			return
+		}
+	}
+
+	finalList := make([]*data_manage.ChartInfoMore, 0)
+	if len(searchList) > 0 {
+		chartInfoIds := ""
+		chartEdbMap := make(map[int][]*data_manage.ChartEdbInfoMapping)
+		for _, v := range searchList {
+			chartInfoIds += strconv.Itoa(v.ChartInfoId) + ","
+		}
+		if chartInfoIds != "" {
+			chartInfoIds = strings.Trim(chartInfoIds, ",")
+			//判断是否需要展示英文标识
+			edbList, e := data_manage.GetChartEdbMappingListByChartInfoIds(chartInfoIds)
+			if e != nil {
+				br.Msg = "获取失败"
+				br.ErrMsg = "获取图表,指标信息失败,Err:" + e.Error()
+				return
+			}
+			for _, v := range edbList {
+				chartEdbMap[v.ChartInfoId] = append(chartEdbMap[v.ChartInfoId], v)
+			}
+		}
+
+		for _, v := range searchList {
+			tmp := new(data_manage.ChartInfoMore)
+			tmp.ChartInfo = v.ChartInfo
+			// 图表数据权限
+			tmp.HaveOperaAuth = true
+			//判断是否需要展示英文标识
+			if edbTmpList, ok := chartEdbMap[v.ChartInfoId]; ok {
+				tmp.IsEnChart = data.CheckIsEnChart(v.ChartNameEn, edbTmpList, v.Source, v.ChartType)
+			}
+			tmp.SearchText = v.SearchText
+			if tmp.SearchText == "" {
+				tmp.SearchText = v.ChartName
+			}
+			finalList = append(finalList, tmp)
+		}
+	}
+	//新增搜索词记录
+	{
+		searchKeyword := new(data_manage.SearchKeyword)
+		searchKeyword.KeyWord = keyword
+		searchKeyword.CreateTime = time.Now()
+		go data_manage.AddSearchKeyword(searchKeyword)
+	}
+
+	page := paging.GetPaging(currentIndex, pageSize, int(total))
+	resp := data_manage.ChartInfoListByEsResp{
+		Paging: page,
+		List:   finalList,
+	}
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = resp
+}

+ 132 - 0
controllers/data_manage/chart_common.go

@@ -9,6 +9,7 @@ import (
 	"eta/eta_api/controllers/data_manage/line_feature"
 	"eta/eta_api/controllers/data_manage/range_analysis"
 	"eta/eta_api/models"
+	aiPredictModel "eta/eta_api/models/ai_predict_model"
 	"eta/eta_api/models/data_manage"
 	"eta/eta_api/models/system"
 	"eta/eta_api/services"
@@ -290,6 +291,29 @@ func (this *ChartInfoController) CommonChartInfoDetailFromUniqueCode() {
 		br.Success = true
 		br.Msg = "获取成功"
 		br.Data = resp
+	case utils.CHART_SOURCE_AI_PREDICT_MODEL_DAILY, utils.CHART_SOURCE_AI_PREDICT_MODEL_MONTHLY:
+		resp, isOk, msg, errMsg := GetAiPredictChartInfoDetailFromUniqueCode(chartInfo, isCache)
+		if !isOk {
+			if strings.Contains(errMsg, utils.ErrNoRow()) {
+				endInfoList := make([]*data_manage.ChartEdbInfoMapping, 0)
+				resp.EdbInfoList = endInfoList
+				resp.ChartInfo = chartInfo
+				resp.Status = false
+
+				br.Data = resp
+				br.Ret = 200
+				br.Success = true
+				br.Msg = "获取成功"
+				return
+			}
+			br.Msg = msg
+			br.ErrMsg = errMsg
+			return
+		}
+		br.Ret = 200
+		br.Success = true
+		br.Msg = "获取成功"
+		br.Data = resp
 	default:
 		br.Msg = "错误的图表"
 		br.ErrMsg = "错误的图表"
@@ -408,6 +432,114 @@ func (this *ChartInfoController) GeneralChartToken() {
 	br.Success = true
 	br.Msg = "获取成功"
 	br.Data = token
+	return
+}
 
+// GetAiPredictChartInfoDetailFromUniqueCode 根据编码获取AI预测模型图表详情
+func GetAiPredictChartInfoDetailFromUniqueCode(chartInfo *data_manage.ChartInfoView, isCache bool) (resp *data_manage.ChartInfoDetailFromUniqueCodeResp, isOk bool, msg, errMsg string) {
+	var err error
+	msg = "获取成功"
+	defer func() {
+		if err != nil {
+			tips := fmt.Sprintf("UniqueCode获取图表详情失败, %v", err)
+			msg = "获取失败"
+			errMsg = fmt.Sprintf(tips)
+			utils.FileLog.Info(tips)
+		}
+	}()
+	if chartInfo == nil {
+		err = fmt.Errorf("图表信息不存在")
+		return
+	}
+	if chartInfo.Source != utils.CHART_SOURCE_AI_PREDICT_MODEL_DAILY && chartInfo.Source != utils.CHART_SOURCE_AI_PREDICT_MODEL_MONTHLY {
+		err = fmt.Errorf("图表来源有误, Source: %d", chartInfo.Source)
+		return
+	}
+	resp = new(data_manage.ChartInfoDetailFromUniqueCodeResp)
+
+	// 获取图表标的
+	edbMappings, e := data_manage.GetChartEdbMappingsByChartInfoId(chartInfo.ChartInfoId)
+	if e != nil {
+		err = fmt.Errorf("获取图表指标关联失败, %v", e)
+		return
+	}
+	if len(edbMappings) == 0 {
+		err = fmt.Errorf("图表指标关联不存在, %v", e)
+		return
+	}
+	indexId := edbMappings[0].EdbInfoId
+	if indexId <= 0 {
+		err = fmt.Errorf("图表标的有误")
+		return
+	}
+	indexOb := new(aiPredictModel.AiPredictModelIndex)
+	indexItem, e := indexOb.GetItemById(indexId)
+	if e != nil {
+		err = fmt.Errorf("获取图表标的失败, %v", e)
+		return
+	}
+	if indexItem != nil && indexItem.AiPredictModelIndexId <= 0 {
+		err = fmt.Errorf("图表标的不存在, IndexId: %d", indexId)
+		return
+	}
+
+	// 获取标的数据
+	indexData := make([]*aiPredictModel.AiPredictModelData, 0)
+	dataSource := aiPredictModel.ModelDataSourceDaily
+	if chartInfo.Source == utils.CHART_SOURCE_AI_PREDICT_MODEL_MONTHLY {
+		dataSource = aiPredictModel.ModelDataSourceMonthly
+	}
+	dataOb := new(aiPredictModel.AiPredictModelData)
+	dataCond := fmt.Sprintf(` AND %s = ?`, dataOb.Cols().IndexCode)
+	dataPars := make([]interface{}, 0)
+	dataPars = append(dataPars, indexItem.IndexCode)
+	list, e := dataOb.GetItemsByCondition(dataCond, dataPars, []string{}, fmt.Sprintf("%s DESC", dataOb.Cols().DataTime))
+	if e != nil {
+		err = fmt.Errorf("获取标的数据失败, %v", e)
+		return
+	}
+	for _, v := range list {
+		if v.Source == dataSource {
+			indexData = append(indexData, v)
+			continue
+		}
+	}
+
+	//判断是否存在缓存,如果存在缓存,那么直接从缓存中获取
+	key := data.GetChartInfoDataKey(chartInfo.ChartInfoId)
+	if utils.Re == nil && isCache {
+		if utils.Re == nil && utils.Rc.IsExist(key) {
+			if chartData, e := utils.Rc.RedisBytes(key); e == nil {
+				e = json.Unmarshal(chartData, &resp)
+				if e != nil || resp == nil {
+					return
+				}
+				isOk = true
+				return
+			}
+		}
+	}
+
+	// 图表详情
+	chartDetail, e := services.GetAiPredictChartDetailByData(indexItem, indexData, dataSource)
+	if e != nil {
+		err = fmt.Errorf("获取图表详情失败, %v", e)
+		return
+	}
+	resp.ChartInfo = chartDetail.ChartInfo
+	resp.EdbInfoList = chartDetail.EdbInfoList
+	resp.XEdbIdValue = chartDetail.XEdbIdValue
+	resp.YDataList = chartDetail.YDataList
+	resp.XDataList = chartDetail.XDataList
+	resp.BarChartInfo = chartDetail.BarChartInfo
+	resp.CorrelationChartInfo = chartDetail.CorrelationChartInfo
+	resp.DataResp = chartDetail.DataResp
+	resp.Status = true
+
+	if utils.Re == nil {
+		jsonData, _ := json.Marshal(resp)
+		_ = utils.Rc.Put(key, jsonData, 10*time.Minute)
+	}
+	isOk = true
 	return
 }

+ 1 - 1
controllers/data_manage/edb_classify.go

@@ -291,7 +291,7 @@ func (this *EdbClassifyController) EditEdbClassify() {
 		return
 	}
 
-	err, errMsg := data.EditEdbClassify(req.ClassifyId, req.ParentId, req.ClassifyName, this.Lang, this.SysUser)
+	err, errMsg := data.EditEdbClassify(req.ClassifyId, req.ParentId, req.ClassifyName, this.Lang, this.SysUser, false)
 	if errMsg != `` {
 		br.Msg = errMsg
 		br.ErrMsg = errMsg

+ 74 - 4
controllers/data_manage/edb_info.go

@@ -2249,7 +2249,15 @@ func (this *EdbInfoController) EdbInfoList() {
 	edbInfoItem.FrequencyEn = data.GetFrequencyEn(edbInfoItem.Frequency)
 	// 获取是否供应商停更
 	edbInfoItem.IsSupplierStop = data.GetIsSupplierStop(edbInfoItem.Source, edbInfoItem.EdbCode)
-
+	relation, err := data.GetEdbRelationListById(edbInfoItem.EdbInfoId, edbInfoItem.Source)
+	if err != nil && !utils.IsErrNoRow(err) {
+		br.Msg = "获取指标引用信息失败"
+		br.ErrMsg = "获取指标引用信息失败,Err:" + err.Error()
+		return
+	}
+	if relation != nil && relation.RelationNum > 0 {
+		edbInfoItem.IsRelation = true
+	}
 	//查询目录
 	classifyList, err, errMsg := data.GetFullClassifyByClassifyId(edbInfoItem.ClassifyId)
 	if err != nil {
@@ -2997,7 +3005,7 @@ func (this *EdbInfoController) EdbInfoRefresh() {
 		br.ErrMsg = "参数错误"
 		return
 	}
-	_, err := data_manage.GetEdbInfoById(edbInfoId)
+	edbInfo, err := data_manage.GetEdbInfoById(edbInfoId)
 	if err != nil {
 		if utils.IsErrNoRow(err) {
 			br.Msg = "数据已被删除,请刷新页面"
@@ -3008,6 +3016,11 @@ func (this *EdbInfoController) EdbInfoRefresh() {
 		br.ErrMsg = "获取指标信息失败,Err:" + err.Error()
 		return
 	}
+	if edbInfo.NoUpdate == 1 {
+		br.Msg = "该指标已停用"
+		br.ErrMsg = "该指标已停用,edbCode:" + edbInfo.EdbCode
+		return
+	}
 	err, isAsync := data.EdbInfoRefreshAllFromBaseV2(edbInfoId, false, false)
 	if err != nil {
 		br.Msg = "刷新失败"
@@ -3899,7 +3912,16 @@ func (this *ChartInfoController) EdbInfoData() {
 	fullEdb.EdbInfo = edbInfo
 	// 是否供应商停更
 	fullEdb.IsSupplierStop = data.GetIsSupplierStop(edbInfo.Source, edbInfo.EdbCode)
-
+	//是否被引用
+	relation, err := data.GetEdbRelationListById(edbInfo.EdbInfoId, edbInfo.Source)
+	if err != nil && !utils.IsErrNoRow(err) {
+		br.Msg = err.Error()
+		br.ErrMsg = "获取指标关联关系失败,err:" + err.Error()
+		return
+	}
+	if relation != nil && relation.RelationNum > 0 {
+		fullEdb.IsRelation = true
+	}
 	fullEdb.ClassifyList = classifyList
 
 	var currClassifyItem *data_manage.EdbClassifyIdItems
@@ -5646,6 +5668,47 @@ func (this *EdbInfoController) EdbChartList() {
 					}
 				}
 			}
+			var wg sync.WaitGroup
+			wg.Add(2)
+			var relationMap =make(map[int]*data_manage.BaseRelationEdbInfo,len(list))
+			var relationList []*data_manage.BaseRelationEdbInfo
+			relationChan := make(chan RelationResult, 2)
+			var edbInfoIds []int
+			var glEdbInfoInfoIds []int
+			for _, v := range list {
+				if v.Source == utils.DATA_SOURCE_MYSTEEL_CHEMICAL {
+					glEdbInfoInfoIds = append(glEdbInfoInfoIds, v.EdbInfoId)
+				} else {
+					edbInfoIds = append(edbInfoIds, v.EdbInfoId)
+				}
+
+			}
+			// 抽象为一个函数减少重复逻辑
+			fetchRelations := func(ids []int, source int) {
+				defer wg.Done()
+				_, relList, getErr := data.GetEdbRelationListByIds(ids, source)
+				relationChan <- RelationResult{
+					List: relList,
+					Err:  getErr,
+				}
+			}
+			go fetchRelations(glEdbInfoInfoIds, utils.DATA_SOURCE_MYSTEEL_CHEMICAL)
+			go fetchRelations(edbInfoIds, -1)
+			wg.Wait()
+			close(relationChan)
+
+			for i := 0; i < 2; i++ {
+				result := <-relationChan
+				if result.Err != nil && !utils.IsErrNoRow(result.Err) {
+					br.Msg = "获取失败"
+					br.ErrMsg = "获取数据引用信息失败,Err:" + e.Error()
+					return
+				}
+				relationList = append(relationList, result.List...)
+			}
+			for _, v := range relationList {
+				relationMap[v.EdbInfoId] = v
+			}
 
 			for _, v := range list {
 				if currClassify, ok := classifyMap[v.ClassifyId]; ok {
@@ -5660,9 +5723,11 @@ func (this *EdbInfoController) EdbChartList() {
 						v.IsSupplierStop = 1
 					}
 				}
+				if relationMap[v.EdbInfoId] != nil && relationMap[v.EdbInfoId].RelationNum > 0 {
+					v.IsRelation = true
+				}
 			}
 		}
-
 		page = paging.GetPaging(currentIndex, pageSize, int(dataCount))
 	}
 
@@ -5676,6 +5741,11 @@ func (this *EdbInfoController) EdbChartList() {
 	br.Data = resp
 }
 
+type RelationResult struct {
+	List []*data_manage.BaseRelationEdbInfo
+	Err  error
+}
+
 // Modify
 // @Title 修改指标信息接口
 // @Description 编辑指标接口

+ 28 - 10
controllers/data_manage/edb_info_refresh.go

@@ -679,7 +679,7 @@ func (c *EdbInfoController) SaveRelationEdbRefreshStatus() {
 	}
 	edbType := 1 //基础指标
 	switch req.Source {
-	case utils.DATA_SOURCE_MYSTEEL_CHEMICAL, utils.DATA_SOURCE_WIND: // wind
+	case utils.DATA_SOURCE_MYSTEEL_CHEMICAL, utils.DATA_SOURCE_WIND, utils.DATA_SOURCE_THS, utils.DATA_SOURCE_PB_FINANCE, utils.DATA_SOURCE_PB: // wind
 	case -1:
 		req.Source = 0
 		edbType = 2
@@ -690,6 +690,10 @@ func (c *EdbInfoController) SaveRelationEdbRefreshStatus() {
 	// todo 批量设置刷新状态修改
 	edbIdList := make([]int, 0)
 	edbCodeList := make([]string, 0)
+	isStop := 0
+	if req.ModifyStatus == `暂停` {
+		isStop = 1
+	}
 	// 指标id列表
 	if req.IsSelectAll {
 		// 如果是列表全选
@@ -712,12 +716,32 @@ func (c *EdbInfoController) SaveRelationEdbRefreshStatus() {
 			if ok {
 				continue
 			}
-
+			if v.RelationNum > 0 && v.IsSupplierStop == 0 && isStop == 1 {
+				br.Msg = "保存失败,停用指标已被引用,指标ID:" + v.EdbCode + "指标名称:" + v.EdbName
+				br.ErrMsg = "保存失败,停用指标已被引用,指标ID:" + v.EdbCode + "指标名称:" + v.EdbName
+				return
+			}
 			// 加入到待配置的指标列表id
 			edbIdList = append(edbIdList, v.EdbInfoId)
 		}
 	} else {
 		edbIdList = req.EdbSelectIdList
+		_, edbRelationList, err := data.GetEdbRelationListByIds(edbIdList, req.Source)
+		if err != nil && !utils.IsErrNoRow(err) {
+			br.Msg = "获取失败"
+			br.ErrMsg = "获取数据失败,Err:" + err.Error()
+			return
+		}
+		for _, v := range edbRelationList {
+			if v.RelationNum > 0 && v.IsSupplierStop == 0 && isStop == 1 {
+				br.Msg = "保存失败,停用指标已被引用,指标ID:" + v.EdbCode + "指标名称:" + v.EdbName
+				br.ErrMsg = "保存失败,停用指标已被引用,指标ID:" + v.EdbCode + "指标名称:" + v.EdbName
+				return
+			}
+			// 加入到待配置的指标列表id
+			// 加入到待配置的指标列表id
+			edbIdList = append(edbIdList, v.EdbInfoId)
+		}
 	}
 
 	if len(edbIdList) <= 0 {
@@ -729,10 +753,10 @@ func (c *EdbInfoController) SaveRelationEdbRefreshStatus() {
 	edbList, e := data_manage.GetEdbInfoByIdList(edbIdList)
 	if e != nil && !utils.IsErrNoRow(e) {
 		br.Msg = "获取失败"
-		br.ErrMsg = "获取数据失败,Err:" + e.Error()
+		br.ErrMsg = "获取数据失败,Err:" + err.Error()
 		return
 	}
-
+	//var edbList []*data_manage.EdbInfo
 	// 如果是钢联化工,那么需要过滤供应商暂停的指标
 	if req.Source == utils.DATA_SOURCE_MYSTEEL_CHEMICAL {
 		// 获取未被供应商暂停的指标
@@ -760,7 +784,6 @@ func (c *EdbInfoController) SaveRelationEdbRefreshStatus() {
 			br.ErrMsg = "获取数据失败,Err:" + err.Error()
 			return
 		}
-
 		if len(edbList) <= 0 {
 			br.Ret = 200
 			br.Success = true
@@ -779,11 +802,6 @@ func (c *EdbInfoController) SaveRelationEdbRefreshStatus() {
 		}
 	}
 
-	isStop := 0
-	if req.ModifyStatus == `暂停` {
-		isStop = 1
-	}
-
 	// 查询计算指标ID
 	// 查询相关的计算指标
 	calculateEdbIdList := make([]int, 0)

+ 6 - 0
controllers/data_manage/excel/excel_classify.go

@@ -9,6 +9,7 @@ import (
 	response2 "eta/eta_api/models/data_manage/excel/response"
 	"eta/eta_api/services/data/data_manage_permission"
 	excel2 "eta/eta_api/services/data/excel"
+	excelService "eta/eta_api/services/excel"
 	"eta/eta_api/utils"
 	"fmt"
 	"sort"
@@ -792,6 +793,11 @@ func (this *ExcelClassifyController) DeleteExcelClassify() {
 			return
 		}
 
+		// 写入ES
+		go func() {
+			excelService.EsAddOrEditExcel(excelInfo.ExcelInfoId)
+		}()
+
 		// 返回下一个表格的信息
 		{
 			var nextItem *excel.ExcelInfo

+ 70 - 33
controllers/data_manage/excel/excel_info.go

@@ -3501,32 +3501,15 @@ func (c *ExcelInfoController) SearchByEs() {
 	page := paging.GetPaging(currentIndex, pageSize, total)
 
 	// 平衡表的查询条件
-	var condBalance string
-	var parsBalance []interface{}
-	if source == utils.BALANCE_TABLE {
-		condBalance += ` AND source = ? AND parent_id = 0 AND balance_type = 0` // 只显示动态表的一级表(不显示子表和静态表)
-		parsBalance = append(parsBalance, source)
-	}
+	//var condBalance string
+	//var parsBalance []interface{}
+	//if source == utils.BALANCE_TABLE {
+	//	condBalance += ` AND source = ? AND parent_id = 0 AND balance_type = 0` // 只显示动态表的一级表(不显示子表和静态表)
+	//	parsBalance = append(parsBalance, source)
+	//}
 
 	// 可见性过滤
 	var queryIds, exceptIds []int
-	{
-		unauthorized, e := excelPermissionModel.GetExcelInfoDataNoPermissionByUserId(sysUser.AdminId, source)
-		if e != nil {
-			br.Msg = "获取失败"
-			br.ErrMsg = fmt.Sprintf("获取无权限表格失败, %v", e)
-			return
-		}
-		if len(unauthorized) > 0 {
-			for _, v := range unauthorized {
-				id, _ := strconv.Atoi(v.DataId)
-				if id == 0 {
-					continue
-				}
-				exceptIds = append(exceptIds, id)
-			}
-		}
-	}
 
 	// 自定义分析表
 	var queryAdminId int
@@ -3580,17 +3563,71 @@ func (c *ExcelInfoController) SearchByEs() {
 	}
 	hasCheck := make(map[int]bool)
 	if isShowMe {
-		// 平衡表查询有权限的表格IDs
-		if source == utils.BALANCE_TABLE {
-			excelIds, e := services.GetBalanceExcelIdsByAdminId(sysUser.AdminId, condBalance, parsBalance, permissionEdbIdList, permissionClassifyIdList)
-			if e != nil {
-				br.Msg = "获取失败"
-				br.ErrMsg = fmt.Sprintf("获取平衡表有权限的表格IDs失败, %v", e)
+		if source == utils.BALANCE_TABLE { //平衡表的,显示同时需要显示协作人相关的图表
+			//找到当前协作人相关的表格ID
+			obj := new(excel3.ExcelWorker)
+			existList, err := obj.GetBySysUserId(sysUser.AdminId)
+			if err != nil {
+				br.Msg = "获取表格协作人失败!"
+				br.ErrMsg = "获取表格协作人失败,Err:" + err.Error()
 				return
 			}
-			if len(excelIds) == 0 {
-				list := make([]*excel3.SearchExcelInfo, 0)
-				resp := response.SearchExcelListResp{
+			var excelIds []int
+			var newCondition string
+			var newPars []interface{}
+			if len(existList) > 0 {
+				for _, v := range existList {
+					excelIds = append(excelIds, v.ExcelInfoId)
+				}
+				newCondition += fmt.Sprintf(` AND source = ? AND (excel_info_id IN (%s) or sys_user_id = ?)`, utils.GetOrmInReplace(len(excelIds)))
+				newPars = append(newPars, source, excelIds, sysUser.AdminId)
+			} else {
+				newCondition += ` AND sys_user_id = ? `
+				newPars = append(newPars, sysUser.AdminId)
+			}
+
+			//获取表格信息
+			tmpList, e := excel3.GetNoContentExcelListByConditionNoPage(newCondition, newPars)
+			if e != nil && !utils.IsErrNoRow(e) {
+				br.Success = true
+				br.Msg = "获取表格信息失败"
+				br.ErrMsg = "获取表格信息失败,Err:" + e.Error()
+				return
+			}
+			classifyIdListTmp := make([]int, 0)
+			for _, v := range tmpList {
+				classifyIdListTmp = append(classifyIdListTmp, v.ExcelClassifyId)
+			}
+			classifyMap := make(map[int]*excel3.ExcelClassify)
+
+			// 分类信息
+			if len(classifyIdListTmp) > 0 {
+				classifyListTmp, e := excel3.GetClassifyByIdList(classifyIdListTmp)
+				if e != nil {
+					br.Msg = "获取表格分类信息失败"
+					br.ErrMsg = "获取表格分类列表数据失败,Err:" + e.Error()
+					return
+				}
+				for _, v := range classifyListTmp {
+					classifyMap[v.ExcelClassifyId] = v
+				}
+			}
+			excelIds = make([]int, 0)
+			for _, v := range tmpList {
+				// 数据权限
+				if classifyInfo, ok := classifyMap[v.ExcelClassifyId]; ok {
+					v.HaveOperaAuth = data_manage_permission.CheckExcelPermissionByPermissionIdList(v.IsJoinPermission, classifyInfo.IsJoinPermission, v.ExcelInfoId, v.ExcelClassifyId, permissionEdbIdList, permissionClassifyIdList)
+					if v.HaveOperaAuth {
+						excelIds = append(excelIds, v.ExcelInfoId)
+					}
+					hasCheck[v.ExcelInfoId] = v.HaveOperaAuth
+				}
+			}
+			if len(excelIds) > 0 {
+				queryIds = excelIds
+			} else {
+				list := make([]*excel3.MyExcelInfoList, 0)
+				resp := response.ExcelListResp{
 					Paging: page,
 					List:   list,
 				}
@@ -3600,7 +3637,7 @@ func (c *ExcelInfoController) SearchByEs() {
 				br.Data = resp
 				return
 			}
-			queryIds = excelIds
+
 		} else {
 			queryAdminId = sysUser.AdminId
 		}

+ 1 - 1
controllers/data_manage/predict_edb_classify.go

@@ -259,7 +259,7 @@ func (this *PredictEdbClassifyController) Edit() {
 		return
 	}
 
-	err, errMsg := data.EditEdbClassify(req.ClassifyId, req.ParentId, req.ClassifyName, this.Lang, this.SysUser)
+	err, errMsg := data.EditEdbClassify(req.ClassifyId, req.ParentId, req.ClassifyName, this.Lang, this.SysUser, true)
 	if errMsg != `` {
 		br.Msg = errMsg
 		br.ErrMsg = errMsg

+ 1 - 0
controllers/eta_forum/eta_forum.go

@@ -247,6 +247,7 @@ func (this *EtaForumController) CommonChartInfoDetailFromUniqueCode() {
 	resp.Status = status
 	resp.DataResp = forumResp.DataResp
 	resp.EdbInfoList = forumResp.EdbInfoList
+	resp.XEdbIdValue = forumResp.XEdbIdValue
 	resp.XDataList = forumResp.XDataList
 	resp.YDataList = forumResp.YDataList
 	br.Ret = 200

+ 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 - 1
controllers/sys_admin.go

@@ -1298,9 +1298,12 @@ func (this *SysAdminController) ResetPass() {
 	_ = utils.Rc.Delete(utils.CACHE_KEY_ADMIN_ID)
 	abnormalKey := fmt.Sprint(utils.CACHE_ABNORMAL_LOGIN, adminInfo.AdminName)
 	errPassKey := fmt.Sprint(utils.CACHE_LOGIN_ERR_PASS, adminInfo.AdminName)
+	mobileAbnormalKey := fmt.Sprint(utils.MOBILE_CACHE_ABNORMAL_LOGIN, adminInfo.AdminName)
+	mobileErrPassKey := fmt.Sprint(utils.MOBILE_CACHE_LOGIN_ERR_PASS, adminInfo.AdminName)
 	_ = utils.Rc.Delete(abnormalKey)
 	_ = utils.Rc.Delete(errPassKey)
-
+	_ = utils.Rc.Delete(mobileAbnormalKey)
+	_ = utils.Rc.Delete(mobileErrPassKey)
 	br.Ret = 200
 	br.Success = true
 	br.IsAddLog = true

+ 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",

+ 18 - 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
@@ -407,6 +422,9 @@ func (this *SysUserController) SystemConfig() {
 	}, system.BusinessConf{
 		ConfKey: "LogoutUrl",
 		ConfVal: conf["LogoutUrl"],
+	}, system.BusinessConf{
+		ConfKey: "UserLogoutUrl",
+		ConfVal: conf["UserLogoutUrl"],
 	})
 
 	br.Data = list

+ 2 - 0
controllers/user_login.go

@@ -881,6 +881,8 @@ func (this *UserLoginController) ForgetResetPass() {
 	abnormalKey := fmt.Sprint(utils.CACHE_ABNORMAL_LOGIN, req.UserName)
 	_ = utils.Rc.Delete(abnormalKey)
 	_ = utils.Rc.Delete(successKey)
+	mobileAbnormalKey := fmt.Sprint(utils.MOBILE_CACHE_ABNORMAL_LOGIN, req.UserName)
+	_ = utils.Rc.Delete(mobileAbnormalKey)
 
 	// 同步ETA用户缓存
 	if utils.BusinessCode == utils.BusinessCodeRelease || utils.BusinessCode == utils.BusinessCodeSandbox {

+ 71 - 9
models/ai_predict_model/ai_predict_model_index.go

@@ -6,6 +6,7 @@ import (
 	"eta/eta_api/utils"
 	"fmt"
 	"github.com/rdlucklib/rdluck_tools/paging"
+	"strconv"
 	"strings"
 	"time"
 )
@@ -229,7 +230,7 @@ type AiPredictModelIndexPageListResp struct {
 }
 
 // RemoveIndexAndData 删除标的及数据
-func (m *AiPredictModelIndex) RemoveIndexAndData(indexId int) (err error) {
+func (m *AiPredictModelIndex) RemoveIndexAndData(indexId int, chartIds []int) (err error) {
 	o := global.DbMap[utils.DbNameIndex]
 	tx := o.Begin()
 	defer func() {
@@ -252,6 +253,21 @@ func (m *AiPredictModelIndex) RemoveIndexAndData(indexId int) (err error) {
 		err = fmt.Errorf("remove index data err: %v", e)
 		return
 	}
+
+	// 删除图表
+	if len(chartIds) == 0 {
+		return
+	}
+	sql = ` DELETE FROM chart_info WHERE chart_info_id IN ?`
+	if e = tx.Exec(sql, chartIds).Error; e != nil {
+		err = fmt.Errorf("remove charts err: %v", e)
+		return
+	}
+	sql = ` DELETE FROM chart_edb_mapping WHERE chart_info_id IN ?`
+	if e = tx.Exec(sql, chartIds).Error; e != nil {
+		err = fmt.Errorf("remove chart mappings err: %v", e)
+		return
+	}
 	return
 }
 
@@ -277,12 +293,18 @@ func GetFirstAiPredictModelIndexByClassifyId(classifyId int) (item *AiPredictMod
 }
 
 type AiPredictModelImportData struct {
-	Index *AiPredictModelIndex
-	Data  []*AiPredictModelData
+	Index  *AiPredictModelIndex
+	Data   []*AiPredictModelData
+	Charts []*AiPredictModelImportCharts
+}
+
+type AiPredictModelImportCharts struct {
+	ChartInfo   *data_manage.ChartInfo
+	EdbMappings []*data_manage.ChartEdbMapping
 }
 
 // ImportIndexAndData 导入数据
-func (m *AiPredictModelIndex) ImportIndexAndData(createIndexes, updateIndexes []*AiPredictModelImportData, updateCols []string) (err error) {
+func (m *AiPredictModelIndex) ImportIndexAndData(createIndexes, updateIndexes []*AiPredictModelImportData, updateCols []string) (chartIds []int, err error) {
 	if len(createIndexes) == 0 && len(updateIndexes) == 0 {
 		return
 	}
@@ -349,23 +371,43 @@ func (m *AiPredictModelIndex) ImportIndexAndData(createIndexes, updateIndexes []
 
 	if len(createIndexes) > 0 {
 		for _, v := range createIndexes {
-			e := tx.Create(v.Index).Error
-			if e != nil {
+			if e := tx.Create(v.Index).Error; e != nil {
 				err = fmt.Errorf("insert index err: %v", e)
 				return
 			}
 
 			indexId := v.Index.AiPredictModelIndexId
 			for _, d := range v.Data {
-				d.AiPredictModelIndexId = int(indexId)
+				d.AiPredictModelIndexId = indexId
 				d.IndexCode = v.Index.IndexCode
 				d.DataTimestamp = d.DataTime.UnixNano() / 1e6
 			}
-			e = tx.CreateInBatches(v.Data, utils.MultiAddNum).Error
-			if e != nil {
+			if e := tx.CreateInBatches(v.Data, utils.MultiAddNum).Error; e != nil {
 				err = fmt.Errorf("insert index data err: %v", e)
 				return
 			}
+
+			// 图表
+			if len(v.Charts) == 0 {
+				continue
+			}
+			for _, ct := range v.Charts {
+				if e := tx.Create(ct.ChartInfo).Error; e != nil {
+					err = fmt.Errorf("insert chart err: %v", e)
+					return
+				}
+				for _, cm := range ct.EdbMappings {
+					cm.ChartInfoId = ct.ChartInfo.ChartInfoId
+					cm.EdbInfoId = indexId
+					time.Sleep(time.Microsecond)
+					cm.UniqueCode = utils.MD5(fmt.Sprint(utils.CHART_PREFIX, "_", indexId, "_", strconv.FormatInt(time.Now().UnixNano(), 10)))
+				}
+				if e := tx.CreateInBatches(ct.EdbMappings, utils.MultiAddNum).Error; e != nil {
+					err = fmt.Errorf("insert chart mapping err: %v", e)
+					return
+				}
+				chartIds = append(chartIds, ct.ChartInfo.ChartInfoId)
+			}
 		}
 	}
 	return
@@ -402,3 +444,23 @@ type AiPredictModelIndexExtraConfig struct {
 		PredictLegendName string `description:"预测图例的名称(通常为Predicted)"`
 	}
 }
+
+func (m *AiPredictModelIndex) GetSortMax() (sort int, err error) {
+	o := global.DbMap[utils.DbNameIndex]
+	sql := `SELECT COALESCE(MAX(sort), 0) AS sort FROM ai_predict_model_index`
+	err = o.Raw(sql).Scan(&sort).Error
+	if err != nil {
+		return
+	}
+	// 查询分类的最大排序
+	sql = `SELECT COALESCE(MAX(sort), 0) AS sort FROM ai_predict_model_classify`
+	var classifySort int
+	err = o.Raw(sql).Scan(&classifySort).Error
+	if err != nil {
+		return
+	}
+	if classifySort > sort {
+		sort = classifySort
+	}
+	return
+}

+ 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
+}

+ 6 - 0
models/data_manage/chart_edb_mapping.go

@@ -388,3 +388,9 @@ func GetRelationEdbInfoListMappingByCondition(condition string, pars []interface
 	err = o.Raw(sql, pars...).Find(&item).Error
 	return
 }
+
+func GetChartEdbMappingsByChartInfoId(chartInfoId int) (list []*ChartEdbInfoMapping, err error) {
+	sql := ` SELECT * FROM chart_edb_mapping AS a WHERE chart_info_id = ? ORDER BY chart_edb_mapping_id ASC`
+	err = global.DbMap[utils.DbNameIndex].Raw(sql, chartInfoId).Find(&list).Error
+	return
+}

+ 39 - 0
models/data_manage/chart_info.go

@@ -2976,3 +2976,42 @@ func GetNextChartByClassifyIdAndSource(classifyId, source int) (item *ChartInfo,
 	err = global.DbMap[utils.DbNameIndex].Raw(sql, classifyId, source).First(&item).Error
 	return
 }
+
+// GetAiPredictChartInfoByIndexId 获取AI预测模型图表
+func GetAiPredictChartInfoByIndexId(source, indexId int) (item *ChartInfo, err error) {
+	sql := `SELECT * FROM chart_info WHERE chart_info_id = (
+		  SELECT chart_info_id FROM chart_edb_mapping WHERE source = ? AND edb_info_id = ?
+		) LIMIT 1`
+	err = global.DbMap[utils.DbNameIndex].Raw(sql, source, indexId).First(&item).Error
+	return
+}
+
+func (m *ChartInfo) AddChartInfoAndEdbMappings(item *ChartInfo, edbMappings []*ChartEdbMapping) (err error) {
+	if item == nil {
+		return
+	}
+	tx := global.DbMap[utils.DbNameIndex].Begin()
+	defer func() {
+		if err != nil {
+			_ = tx.Rollback()
+			return
+		}
+		_ = tx.Commit()
+	}()
+
+	if e := tx.Create(item).Error; e != nil {
+		err = fmt.Errorf("insert chart err: %v", e)
+		return
+	}
+	if len(edbMappings) == 0 {
+		return
+	}
+	for _, v := range edbMappings {
+		v.ChartInfoId = item.ChartInfoId
+	}
+	if e := tx.CreateInBatches(edbMappings, utils.MultiAddNum).Error; e != nil {
+		err = fmt.Errorf("insert chart mapping err: %v", e)
+		return
+	}
+	return
+}

+ 6 - 4
models/data_manage/edb_info.go

@@ -104,6 +104,7 @@ type EdbInfoFullClassify struct {
 	ClassifyList   []*EdbClassifyIdItems
 	HaveOperaAuth  bool `description:"是否有数据权限,默认:false"`
 	IsSupplierStop int  `description:"是否供应商停更:1:停更,0:未停更"`
+	IsRelation     bool `description:"是否被引用,默认:0"`
 }
 
 func AddEdbInfo(item *EdbInfo) (lastId int64, err error) {
@@ -527,6 +528,7 @@ type EdbInfoList struct {
 	MinValue         float64                 `description:"最小值"`
 	MaxValue         float64                 `description:"最大值"`
 	SearchText       string                  `description:"搜索结果(含高亮)"`
+	IsRelation       bool                    `description:"是否被引用"`
 }
 
 // AfterFind
@@ -761,14 +763,14 @@ type EdbInfoMaxAndMinInfo struct {
 }
 
 func (edbInfoMaxAndMinInfo *EdbInfoMaxAndMinInfo) AfterFind(tx *gorm.DB) (err error) {
-			edbInfoMaxAndMinInfo.MinDate = utils.GormDateStrToDateStr(edbInfoMaxAndMinInfo.MinDate)
-			edbInfoMaxAndMinInfo.MaxDate = utils.GormDateStrToDateStr(edbInfoMaxAndMinInfo.MaxDate)
+	edbInfoMaxAndMinInfo.MinDate = utils.GormDateStrToDateStr(edbInfoMaxAndMinInfo.MinDate)
+	edbInfoMaxAndMinInfo.MaxDate = utils.GormDateStrToDateStr(edbInfoMaxAndMinInfo.MaxDate)
 	return
 }
 
 func (edbInfoMaxAndMinInfo *EdbInfoMaxAndMinInfo) ConvertTimeStr() {
-			edbInfoMaxAndMinInfo.MinDate = utils.GormDateStrToDateStr(edbInfoMaxAndMinInfo.MinDate)
-			edbInfoMaxAndMinInfo.MaxDate = utils.GormDateStrToDateStr(edbInfoMaxAndMinInfo.MaxDate)
+	edbInfoMaxAndMinInfo.MinDate = utils.GormDateStrToDateStr(edbInfoMaxAndMinInfo.MinDate)
+	edbInfoMaxAndMinInfo.MaxDate = utils.GormDateStrToDateStr(edbInfoMaxAndMinInfo.MaxDate)
 }
 
 // GetEdbInfoMaxAndMinInfo

+ 7 - 0
models/data_manage/excel/excel_info.go

@@ -816,3 +816,10 @@ type ExcelInfoFreeze struct {
 	FreezeStartCol int  `description:"冻结开始列"`
 	FreezeEndCol   int  `description:"冻结结束列"`
 }
+
+// GetItemById 主键获取表格
+func (m *ExcelInfo) GetItemById(excelInfoId int) (item *ExcelInfo, err error) {
+	sql := ` SELECT * FROM excel_info WHERE excel_info_id = ? LIMIT 1`
+	err = global.DbMap[utils.DbNameIndex].Raw(sql, excelInfoId).First(&item).Error
+	return
+}

+ 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 {

+ 1 - 1
models/sandbox/sandbox_classify.go

@@ -129,7 +129,7 @@ func GetSandboxInfoCountByClassifyId(classifyId int) (count int, err error) {
 	var sql string
 	var pars []interface{}
 	if utils.DbDriverName == utils.DbDriverByDm {
-		sql = `WITH RECURSIVE classify_tree AS (
+		sql = `WITH RECURSIVE classify_tree(sandbox_classify_id, parent_id, level) AS (
     -- 基础查询:获取起始节点
     SELECT sandbox_classify_id, parent_id, 1 as level
     FROM sandbox_classify

+ 1 - 1
models/target.go

@@ -306,7 +306,7 @@ func GetEdbinfoItemList(condition string, pars []interface{}, startSize, pageSiz
 
 // EdbParamsInfo 指标数据结构体
 type EdbParamsInfo struct {
-	Unit      string `gorm:"column(UNIT);" description:"单位" gorm:"column:UNIT"`
+	Unit      string `description:"单位" gorm:"column:UNIT"`
 	Frequency string `gorm:"column:frequency;" description:"单位"`
 }
 

+ 108 - 0
routers/commentsRouter.go

@@ -358,6 +358,15 @@ func init() {
             Filters: nil,
             Params: nil})
 
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/ai_predict_model:AiPredictModelIndexController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/ai_predict_model:AiPredictModelIndexController"],
+        beego.ControllerComments{
+            Method: "SearchByEs",
+            Router: `/chart/search_by_es`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
     beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/ai_predict_model:AiPredictModelIndexController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/ai_predict_model:AiPredictModelIndexController"],
         beego.ControllerComments{
             Method: "DashboardDetail",
@@ -8836,6 +8845,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",
@@ -8845,6 +8863,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",
@@ -8881,6 +8917,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",
@@ -8890,6 +8935,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",
@@ -8971,6 +9061,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",
@@ -8989,6 +9088,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",

+ 5 - 5
services/ai_predict_model_classify.go

@@ -156,7 +156,7 @@ func moveAiPredictModelClassify(parentClassify, edbClassifyInfo, prevClassify, n
 	updateCol := make([]string, 0)
 
 	// 移动对象为分类, 判断分类是否存在
-	if edbClassifyInfo != nil {
+	if edbClassifyInfo != nil && edbClassifyInfo.AiPredictModelClassifyId > 0 {
 		oldParentId := edbClassifyInfo.ParentId
 		oldLevel := edbClassifyInfo.Level
 		var classifyIds []int
@@ -271,7 +271,7 @@ func moveAiPredictModelClassify(parentClassify, edbClassifyInfo, prevClassify, n
 			}
 
 			//如果该分类下存在其他分类,且第一个其他分类的排序等于0,那么需要调整排序
-			if firstClassify != nil && firstClassify.Sort == 0 {
+			if firstClassify != nil && firstClassify.AiPredictModelClassifyId > 0 && firstClassify.Sort == 0 {
 				updateSortStr := ` sort + 1 `
 				_ = aiPredictModel.UpdateAiPredictModelClassifySortByParentId(parentClassifyId, firstClassify.AiPredictModelClassifyId-1, 0, updateSortStr)
 				//该分类下的所有指标也需要+1
@@ -286,7 +286,7 @@ func moveAiPredictModelClassify(parentClassify, edbClassifyInfo, prevClassify, n
 				}
 
 				//如果该分类下存在其他分类,且第一个其他分类的排序等于0,那么需要调整排序
-				if firstEdb != nil && firstEdb.Sort == 0 {
+				if firstEdb != nil && firstEdb.AiPredictModelIndexId > 0 && firstEdb.Sort == 0 {
 					updateSortStr := ` sort + 1 `
 					_ = aiPredictModel.UpdateAiPredictModelIndexSortByClassifyId(parentClassifyId, 0, firstEdb.AiPredictModelIndexId-1, updateSortStr)
 					_ = aiPredictModel.UpdateAiPredictModelClassifySortByParentId(parentClassifyId, 0, 0, updateSortStr)
@@ -403,7 +403,7 @@ func moveAiPredictModelClassify(parentClassify, edbClassifyInfo, prevClassify, n
 			}
 
 			//如果该分类下存在其他分类,且第一个其他分类的排序等于0,那么需要调整排序
-			if firstClassify != nil && firstClassify.Sort == 0 {
+			if firstClassify != nil && firstClassify.AiPredictModelClassifyId> 0 && firstClassify.Sort == 0 {
 				updateSortStr := ` sort + 1 `
 				_ = aiPredictModel.UpdateAiPredictModelClassifySortByParentId(parentClassifyId, firstClassify.AiPredictModelClassifyId-1, 0, updateSortStr)
 				//该分类下的所有指标也需要+1
@@ -418,7 +418,7 @@ func moveAiPredictModelClassify(parentClassify, edbClassifyInfo, prevClassify, n
 				}
 
 				//如果该分类下存在其他分类,且第一个其他分类的排序等于0,那么需要调整排序
-				if firstEdb != nil && firstEdb.Sort == 0 {
+				if firstEdb != nil && firstEdb.AiPredictModelIndexId > 0 && firstEdb.Sort == 0 {
 					updateSortStr := ` sort + 1 `
 					_ = aiPredictModel.UpdateAiPredictModelIndexSortByClassifyId(parentClassifyId, 0, firstEdb.AiPredictModelIndexId-1, updateSortStr)
 					_ = aiPredictModel.UpdateAiPredictModelClassifySortByParentId(parentClassifyId, 0, 0, updateSortStr)

+ 179 - 4
services/ai_predict_model_index.go

@@ -8,10 +8,11 @@ import (
 	"eta/eta_api/utils"
 	"fmt"
 	"sort"
+	"strconv"
 	"time"
 )
 
-func ImportAiPredictModelIndexAndData(imports []*aiPredictModel.AiPredictModelImportData) (err error) {
+func ImportAiPredictModelIndexAndData(imports []*aiPredictModel.AiPredictModelImportData, adminId int, adminRealName string) (err error) {
 	if len(imports) == 0 {
 		return
 	}
@@ -33,6 +34,12 @@ func ImportAiPredictModelIndexAndData(imports []*aiPredictModel.AiPredictModelIm
 	updateCols := []string{indexOb.Cols().ClassifyId, indexOb.Cols().ModelFramework, indexOb.Cols().PredictDate, indexOb.Cols().PredictValue, indexOb.Cols().DirectionAccuracy, indexOb.Cols().AbsoluteDeviation, indexOb.Cols().ExtraConfig, indexOb.Cols().SysUserId, indexOb.Cols().SysUserRealName, indexOb.Cols().ModifyTime}
 	updateIndexes := make([]*aiPredictModel.AiPredictModelImportData, 0)
 	createIndexes := make([]*aiPredictModel.AiPredictModelImportData, 0)
+
+	maxSort, err := indexOb.GetSortMax()
+	if err != nil {
+		err = fmt.Errorf("获取标的最大排序失败, %v", err)
+		return
+	}
 	for _, v := range imports {
 		exist := indexNameItem[v.Index.IndexName]
 		// 编辑
@@ -52,28 +59,42 @@ func ImportAiPredictModelIndexAndData(imports []*aiPredictModel.AiPredictModelIm
 				b, _ := json.Marshal(oldConfig)
 				v.Index.ExtraConfig = string(b)
 			}
-
+			
 			v.Index.AiPredictModelIndexId = exist.AiPredictModelIndexId
 			v.Index.IndexCode = exist.IndexCode
 			updateIndexes = append(updateIndexes, v)
 			continue
 		}
 
-		// 新增
+		// 新增标的/图表
 		indexCode, e := utils.GenerateEdbCode(1, "IPM")
 		if e != nil {
 			err = fmt.Errorf("生成标的编码失败, %v", e)
 			return
 		}
 		v.Index.IndexCode = indexCode
+		v.Charts = GetAiPredictCharts(v.Index.IndexName, adminId, adminRealName)
+		maxSort = maxSort + 1
+		v.Index.Sort = maxSort
 		createIndexes = append(createIndexes, v)
 	}
 
 	// 新增/更新指标
-	if e := indexOb.ImportIndexAndData(createIndexes, updateIndexes, updateCols); e != nil {
+	chartIds, e := indexOb.ImportIndexAndData(createIndexes, updateIndexes, updateCols)
+	if e != nil {
 		err = fmt.Errorf("导入指标失败, %v", e)
 		return
 	}
+
+	// 更新图表ES
+	if len(chartIds) == 0 {
+		return
+	}
+	go func() {
+		for _, v := range chartIds {
+			data.EsAddOrEditChartInfo(v)
+		}
+	}()
 	return
 }
 
@@ -114,8 +135,29 @@ func GetAiPredictChartDetailByData(indexItem *aiPredictModel.AiPredictModelIndex
 		confLeftMax = indexItem.LeftMax
 	}
 
+	// 获取指标对应的图表
+	chartSourceMapping := map[int]int{
+		aiPredictModel.ModelDataSourceMonthly: utils.CHART_SOURCE_AI_PREDICT_MODEL_MONTHLY,
+		aiPredictModel.ModelDataSourceDaily:   utils.CHART_SOURCE_AI_PREDICT_MODEL_DAILY,
+	}
+	chartInfo, e := data_manage.GetAiPredictChartInfoByIndexId(chartSourceMapping[source], indexItem.AiPredictModelIndexId)
+	if e != nil && !utils.IsErrNoRow(e) {
+		err = fmt.Errorf("获取标的图表失败, %v", e)
+		return
+	}
+
 	// 获取曲线图主题样式
 	chartView := new(data_manage.ChartInfoView)
+	if chartInfo != nil && chartInfo.ChartInfoId > 0 {
+		chartView.ChartInfoId = chartInfo.ChartInfoId
+		chartView.ChartName = chartInfo.ChartName
+		chartView.ChartNameEn = chartInfo.ChartNameEn
+		chartView.Source = chartInfo.Source
+		chartView.ChartImage = chartInfo.ChartImage
+	} else {
+		chartView.ChartName = indexItem.IndexName
+		chartView.ChartNameEn = indexItem.IndexName
+	}
 	chartView.ChartType = utils.CHART_SOURCE_DEFAULT
 	chartTheme, e := data.GetChartThemeConfig(0, chartView.ChartType, utils.CHART_TYPE_CURVE)
 	if e != nil {
@@ -264,3 +306,136 @@ func GetAiPredictChartDetailByData(indexItem *aiPredictModel.AiPredictModelIndex
 	resp.EdbInfoList = edbList
 	return
 }
+
+// GetAiPredictCharts 获取AI预测模型图表
+func GetAiPredictCharts(indexName string, adminId int, adminRealName string) (charts []*aiPredictModel.AiPredictModelImportCharts) {
+	charts = make([]*aiPredictModel.AiPredictModelImportCharts, 0)
+
+	// 日度/月度图表
+	frequencyArr := []int{aiPredictModel.ModelDataSourceMonthly, aiPredictModel.ModelDataSourceDaily}
+	sourceMapping := map[int]int{
+		aiPredictModel.ModelDataSourceMonthly: utils.CHART_SOURCE_AI_PREDICT_MODEL_MONTHLY,
+		aiPredictModel.ModelDataSourceDaily:   utils.CHART_SOURCE_AI_PREDICT_MODEL_DAILY,
+	}
+	suffixNameMapping := map[int]string{
+		aiPredictModel.ModelDataSourceMonthly: "预测模型/回测",
+		aiPredictModel.ModelDataSourceDaily:   "预测模型",
+	}
+
+	for _, v := range frequencyArr {
+		chartSource := sourceMapping[v]
+		newChart := new(aiPredictModel.AiPredictModelImportCharts)
+
+		// 新增图表
+		chartName := fmt.Sprintf("%s%s", indexName, suffixNameMapping[v])
+		chartInfo := new(data_manage.ChartInfo)
+		chartInfo.ChartName = chartName
+		chartInfo.ChartNameEn = chartName
+		chartInfo.ChartType = utils.CHART_TYPE_CURVE
+		chartInfo.Calendar = "公历"
+		chartInfo.SysUserId = adminId
+		chartInfo.SysUserRealName = adminRealName
+		chartInfo.CreateTime = time.Now()
+		chartInfo.ModifyTime = time.Now()
+		chartInfo.Source = chartSource
+		time.Sleep(time.Microsecond)
+		chartInfo.UniqueCode = utils.MD5(utils.CHART_PREFIX + "_" + strconv.FormatInt(time.Now().UnixNano(), 10))
+		newChart.ChartInfo = chartInfo
+
+		// chart_edb_mapping中edb_info_id为标的ID
+		edbMapping := new(data_manage.ChartEdbMapping)
+		//edbMapping.EdbInfoId = indexId
+		//edbMapping.UniqueCode = utils.MD5(fmt.Sprint(utils.CHART_PREFIX, "_", indexId, "_", strconv.FormatInt(time.Now().UnixNano(), 10)))
+		edbMapping.Source = chartSource
+		edbMapping.CreateTime = time.Now().Local()
+		edbMapping.ModifyTime = time.Now().Local()
+		newChart.EdbMappings = append(newChart.EdbMappings, edbMapping)
+
+		charts = append(charts, newChart)
+	}
+	return
+}
+
+// FixAiPredictCharts 修复AI预测模型图表
+func FixAiPredictCharts() {
+	var err error
+	defer func() {
+		if err != nil {
+			fmt.Println(err)
+		}
+		fmt.Println("修复完成")
+	}()
+	fmt.Println("开始修复")
+
+	indexOb := new(aiPredictModel.AiPredictModelIndex)
+	indexes, e := indexOb.GetItemsByCondition("", make([]interface{}, 0), []string{}, "")
+	if e != nil {
+		err = fmt.Errorf("获取所有标的失败, %v", e)
+		return
+	}
+	// 日度/月度图表
+	frequencyArr := []int{aiPredictModel.ModelDataSourceMonthly, aiPredictModel.ModelDataSourceDaily}
+	sourceMapping := map[int]int{
+		aiPredictModel.ModelDataSourceMonthly: utils.CHART_SOURCE_AI_PREDICT_MODEL_MONTHLY,
+		aiPredictModel.ModelDataSourceDaily:   utils.CHART_SOURCE_AI_PREDICT_MODEL_DAILY,
+	}
+	suffixNameMapping := map[int]string{
+		aiPredictModel.ModelDataSourceMonthly: "预测模型/回测",
+		aiPredictModel.ModelDataSourceDaily:   "预测模型",
+	}
+	chartOb := new(data_manage.ChartInfo)
+	for _, v := range indexes {
+		for _, fre := range frequencyArr {
+			chartSource := sourceMapping[fre]
+			item, e := data_manage.GetAiPredictChartInfoByIndexId(chartSource, v.AiPredictModelIndexId)
+			if e != nil && !utils.IsErrNoRow(e) {
+				err = fmt.Errorf("获取AI预测模型图表失败, %v", e)
+				return
+			}
+			// 由于标的名称是固定的所以chart_info没有什么可更新的, 已加入过就忽略
+			if item != nil && item.ChartInfoId > 0 {
+				fmt.Printf("标的%d-%d图表已存在, continue\n", v.AiPredictModelIndexId, chartSource)
+				continue
+			}
+
+			// 新增图表
+			chartName := fmt.Sprintf("%s%s", v.IndexName, suffixNameMapping[fre])
+			chartInfo := new(data_manage.ChartInfo)
+			chartInfo.ChartName = chartName
+			chartInfo.ChartNameEn = chartName
+			chartInfo.ChartType = utils.CHART_TYPE_CURVE
+			chartInfo.Calendar = "公历"
+			chartInfo.SysUserId = v.SysUserId
+			chartInfo.SysUserRealName = v.SysUserRealName
+			chartInfo.CreateTime = time.Now()
+			chartInfo.ModifyTime = time.Now()
+			chartInfo.Source = chartSource
+			time.Sleep(time.Microsecond)
+			chartInfo.UniqueCode = utils.MD5(utils.CHART_PREFIX + "_" + strconv.FormatInt(time.Now().UnixNano(), 10))
+
+			// chart_edb_mapping中edb_info_id为标的ID
+			mappings := make([]*data_manage.ChartEdbMapping, 0)
+			edbMapping := new(data_manage.ChartEdbMapping)
+			edbMapping.EdbInfoId = v.AiPredictModelIndexId
+			edbMapping.UniqueCode = utils.MD5(fmt.Sprint(utils.CHART_PREFIX, "_", v.AiPredictModelIndexId, "_", strconv.FormatInt(time.Now().UnixNano(), 10)))
+			edbMapping.Source = chartSource
+			edbMapping.CreateTime = time.Now().Local()
+			edbMapping.ModifyTime = time.Now().Local()
+			mappings = append(mappings, edbMapping)
+
+			// 新增图表
+			if e = chartOb.AddChartInfoAndEdbMappings(chartInfo, mappings); e != nil {
+				err = fmt.Errorf("新增图表及mapping失败, %v", e)
+				return
+			}
+
+			// 写入ES
+			if chartInfo.ChartInfoId <= 0 {
+				err = fmt.Errorf("图表ID有误")
+				return
+			}
+			go data.EsAddOrEditChartInfo(chartInfo.ChartInfoId)
+		}
+	}
+	return
+}

+ 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 中间服务-编码登录

+ 4 - 2
services/data/edb_classify.go

@@ -399,13 +399,15 @@ func AddEdbClassify(classifyName string, parentId, level int, classifyType uint8
 }
 
 // EditEdbClassify 编辑指标分类
-func EditEdbClassify(classifyId, parentId int, classifyName, lang string, sysUser *system.Admin) (err error, errMsg string) {
+func EditEdbClassify(classifyId, parentId int, classifyName, lang string, sysUser *system.Admin, isPreditClassify bool) (err error, errMsg string) {
 	item, err := data_manage.GetEdbClassifyById(classifyId)
 	if err != nil {
 		errMsg = `保存失败`
 		return
 	}
-
+	if isPreditClassify {
+		parentId = item.ParentId // 预测指标的分类编辑时不支持修改父级分类
+	}
 	// 权限校验
 	{
 		// 已授权分类id

+ 45 - 0
services/data/edb_info_relation.go

@@ -474,6 +474,51 @@ func GetEdbRelationList(source, edbType int, classifyId, sysUserId, frequency, k
 	return
 }
 
+// GetEdbRelationList 获取指标引用列表
+func GetEdbRelationListByIds(edbIds []int, source int) (total int, list []*data_manage.BaseRelationEdbInfo, err error) {
+	var pars []interface{}
+	var condition string
+	if len(edbIds) == 0 {
+		return
+	}
+	condition += ` AND e.edb_info_id IN (` + utils.GetOrmInReplace(len(edbIds)) + `)`
+	pars = append(pars, edbIds)
+	list = make([]*data_manage.BaseRelationEdbInfo, 0)
+	// 关联表语句
+	var addFieldStr, joinTableStr string
+	// 供应商停用
+	if source == utils.DATA_SOURCE_MYSTEEL_CHEMICAL {
+		joinTableStr = ` LEFT JOIN base_from_mysteel_chemical_index z ON e.edb_code = z.index_code `
+		addFieldStr = ` ,z.is_supplier_stop `
+	}
+	total, list, err = data_manage.GetEdbInfoRelationList(condition, pars, addFieldStr, joinTableStr, "", 0, len(edbIds))
+	return
+}
+
+// GetEdbRelationList 获取指标引用列表
+func GetEdbRelationListById(edbId int, source int) (item *data_manage.BaseRelationEdbInfo, err error) {
+	var pars []interface{}
+	var condition string
+	condition += ` AND e.edb_info_id = ?`
+	pars = append(pars, edbId)
+	list := make([]*data_manage.BaseRelationEdbInfo, 0)
+	// 关联表语句
+	var addFieldStr, joinTableStr string
+	// 供应商停用
+	if source == utils.DATA_SOURCE_MYSTEEL_CHEMICAL {
+		joinTableStr = ` LEFT JOIN base_from_mysteel_chemical_index z ON e.edb_code = z.index_code `
+		addFieldStr = ` ,z.is_supplier_stop `
+	}
+	_, list, err = data_manage.GetEdbInfoRelationList(condition, pars, addFieldStr, joinTableStr, "", 0, 1)
+	if err != nil {
+		return
+	}
+	if len(list) > 0 {
+		item = list[0]
+	}
+	return
+}
+
 // 查找当前计算指标的所有溯源指标
 func GetEdbListByEdbInfoId(edbInfoList []*data_manage.EdbInfo, needPredict bool) (edbMappingListMap map[int]*data_manage.EdbInfoCalculateMapping, edbInfoMappingRootIdsMap map[int][]int, err error) {
 	if len(edbInfoList) == 0 {

+ 40 - 2
services/elastic/elastic.go

@@ -2138,7 +2138,7 @@ func SearchDataSourceIndex(indexName, keyword string, source, subSource int, cla
 }
 
 // CreateEsIndex 创建ES索引
-func CreateEsIndex(indexName, jsonMapping string) (err error) {
+func CreateEsIndex(indexName, jsonMapping string) (exists bool, err error) {
 	defer func() {
 		if err != nil {
 			tips := fmt.Sprintf("CreateEsIndex err: %v", err)
@@ -2148,11 +2148,12 @@ func CreateEsIndex(indexName, jsonMapping string) (err error) {
 	client := utils.EsClient
 
 	// 校验是否存在
-	exists, e := client.IndexExists(indexName).Do(context.Background())
+	exist, e := client.IndexExists(indexName).Do(context.Background())
 	if e != nil {
 		err = fmt.Errorf("check exists err: %v", e)
 		return
 	}
+	exists = exist
 	if exists {
 		fmt.Printf("索引已存在: %s, 跳过\n", indexName)
 		return
@@ -2167,3 +2168,40 @@ func CreateEsIndex(indexName, jsonMapping string) (err error) {
 	fmt.Printf("create index success: %s\n", createIndex.Index)
 	return
 }
+
+// ClearEsIndex 清空指定索引的所有数据
+func ClearEsIndex(indexName string) (err error) {
+	defer func() {
+		if err != nil {
+			tips := fmt.Sprintf("ClearEsIndex err: %v", err)
+			utils.FileLog.Info(tips)
+		}
+	}()
+
+	client := utils.EsClient
+
+	// 校验索引是否存在
+	exists, e := client.IndexExists(indexName).Do(context.Background())
+	if e != nil {
+		err = fmt.Errorf("check index exists err: %v", e)
+		return
+	}
+	if !exists {
+		err = fmt.Errorf("index does not exist: %s", indexName)
+		return
+	}
+
+	query := elastic.NewMatchAllQuery()
+	res, e := client.DeleteByQuery().
+		Index(indexName).
+		Query(query).
+		ProceedOnVersionConflict(). // 忽略版本冲突
+		Do(context.Background())
+	if e != nil {
+		err = fmt.Errorf("delete by query err: %v", e)
+		return
+	}
+
+	fmt.Printf("Cleared index: %s, Deleted documents count: %d\n", indexName, res.Deleted)
+	return
+}

+ 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"))

+ 2 - 1
services/excel/excel_elastic.go

@@ -17,7 +17,8 @@ func EsAddOrEditExcel(excelId int) {
 			utils.FileLog.Info(tips)
 		}
 	}()
-	item, e := excel2.GetExcelInfoById(excelId)
+	excelOb := new(excel2.ExcelInfo)
+	item, e := excelOb.GetItemById(excelId)
 	if e != nil {
 		err = fmt.Errorf("获取表格失败, %v", e)
 		return

+ 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
+}

+ 21 - 0
services/material/material.go

@@ -3,6 +3,7 @@ package materialService
 import (
 	"encoding/json"
 	"eta/eta_api/models"
+	aiPredictModel "eta/eta_api/models/ai_predict_model"
 	"eta/eta_api/models/data_manage"
 	"eta/eta_api/models/data_manage/excel"
 	"eta/eta_api/models/material"
@@ -163,6 +164,26 @@ func AddToMaterial(req material.SaveAsMaterialReq, opUserId int, opUserName stri
 		}
 
 		oldRsourceUrl = docObj.ResultImg
+	case "ai_predict_model_framework":
+		// AI预测模型框架
+		frameworkOb := new(aiPredictModel.AiPredictModelFramework)
+		framework, e := frameworkOb.GetItemById(req.ObjectId)
+		if e != nil {
+			if utils.IsErrNoRow(e) {
+				errMsg = "AI预测模型框架不存在"
+				err = fmt.Errorf("AI预测模型框架不存在")
+				return
+			}
+			errMsg = "获取AI预测模型框架失败"
+			err = e
+			return
+		}
+		if framework.FrameworkImg == "" {
+			errMsg = "AI预测模型框架封面为空"
+			err = fmt.Errorf("AI预测模型框架封面为空")
+			return
+		}
+		oldRsourceUrl = framework.FrameworkImg
 	default:
 		errMsg = "不支持的类型"
 		err = fmt.Errorf("不支持的类型")

+ 3 - 3
services/residual_analysis_service/residual_analysis_service.go

@@ -992,9 +992,9 @@ func ResidualAnalysisDetail(edbInfoId int) (residual_analysis_model.ResidualAnal
 	pars = []interface{}{}
 
 	condition += ` and edb_info_id in(` + utils.GetOrmInReplace(len(edbInfoIdList)) + `)`
-	for _, id := range edbInfoIdList {
-		pars = append(pars, id)
-	}
+	//for _, id := range edbInfoIdList {
+	pars = append(pars, edbInfoIdList)
+	//}
 
 	edbInfoList, err := data_manage.GetEdbInfoListByCond(condition, pars)
 	if err != nil {

+ 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 - 484
services/temp_fix.go

@@ -1,9 +1,7 @@
 package services
 
 import (
-	aiPredictModel "eta/eta_api/models/ai_predict_model"
 	"eta/eta_api/models/data_manage/excel"
-	dataSourceModel "eta/eta_api/models/data_source"
 	"eta/eta_api/services/elastic"
 	excel2 "eta/eta_api/services/excel"
 	"eta/eta_api/utils"
@@ -29,505 +27,529 @@ func FixNewEs() {
 	}
 	fmt.Println("开始创建表格索引:", utils.EsExcelIndexName)
 	indexMapping := `{"mappings":{"properties":{"ExcelInfoId":{"type":"long"},"Source":{"type":"long"},"ExcelType":{"type":"long"},"ExcelName":{"type":"text","fields":{"keyword":{"type":"keyword","ignore_above":256}}},"UniqueCode":{"type":"text","fields":{"keyword":{"type":"keyword","ignore_above":256}}},"ExcelClassifyId":{"type":"long"},"SysUserId":{"type":"long"},"SysUserRealName":{"type":"text","fields":{"keyword":{"type":"keyword","ignore_above":256}}},"Content":{"type":"text","fields":{"keyword":{"type":"keyword","ignore_above":256}}},"ExcelImage":{"type":"text","fields":{"keyword":{"type":"keyword","ignore_above":256}}},"FileUrl":{"type":"text","fields":{"keyword":{"type":"keyword","ignore_above":256}}},"Sort":{"type":"long"},"IsDelete":{"type":"long"},"ModifyTime":{"type":"text","fields":{"keyword":{"type":"keyword","ignore_above":256}}},"CreateTime":{"type":"text","fields":{"keyword":{"type":"keyword","ignore_above":256}}},"IsJoinPermission":{"type":"long"},"ParentId":{"type":"long"},"BalanceType":{"type":"long"},"UpdateUserId":{"type":"long"},"UpdateUserRealName":{"type":"text","fields":{"keyword":{"type":"keyword","ignore_above":256}}},"RelExcelInfoId":{"type":"long"},"VersionName":{"type":"text","fields":{"keyword":{"type":"keyword","ignore_above":256}}},"SourcesFrom":{"type":"text","fields":{"keyword":{"type":"keyword","ignore_above":256}}}}}}`
-	if e := elastic.CreateEsIndex(utils.EsExcelIndexName, indexMapping); e != nil {
+	tableExists, e := elastic.CreateEsIndex(utils.EsExcelIndexName, indexMapping)
+	if e != nil {
 		err = fmt.Errorf("创建excel索引失败, %v", e)
 		return
 	}
 	fmt.Println("表格索引创建成功")
 
+	// 清空表格数据
+	fmt.Println("开始清空表格数据")
+	if tableExists {
+		if e := elastic.ClearEsIndex(utils.EsExcelIndexName); e != nil {
+			err = fmt.Errorf("清空excel索引数据失败, %v", e)
+			return
+		}
+	}
+	fmt.Println("结束清空表格数据")
+
 	fmt.Println("开始修复表格数据")
 	excels, e := excel.GetExcelInfoAll()
 	if e != nil {
 		err = fmt.Errorf("获取所有excel表格失败, %v", e)
 		return
 	}
+	var fixTotal int
 	for _, v := range excels {
 		fmt.Printf("ExcelId: %d 修复中\n", v.ExcelInfoId)
 		excel2.EsAddOrEditExcel(v.ExcelInfoId)
+		fixTotal += 1
 	}
-	fmt.Println("表格数据修复成功")
+	fmt.Println("表格数据修复成功, Total: ", fixTotal)
 
 	// 数据源
-	fmt.Println("数据源索引名:", utils.EsDataSourceIndexName)
-	if utils.EsDataSourceIndexName == "" {
-		err = fmt.Errorf("数据源索引名为空")
-		return
-	}
-	fmt.Println("开始创建数据源索引:", utils.EsDataSourceIndexName)
-	indexMappingB := `{"mappings":{"properties":{"ClassifyId":{"type":"long"},"CreateTime":{"type":"text","fields":{"keyword":{"type":"keyword","ignore_above":256}}},"EndDate":{"type":"text","fields":{"keyword":{"type":"keyword","ignore_above":256}}},"Frequency":{"type":"text","fields":{"keyword":{"type":"keyword","ignore_above":256}}},"IndexCode":{"type":"text","fields":{"keyword":{"type":"keyword","ignore_above":256}}},"IndexName":{"type":"text","fields":{"keyword":{"type":"keyword","ignore_above":256}}},"IsDeleted":{"type":"long"},"LatestValue":{"type":"text"},"ModifyTime":{"type":"text","fields":{"keyword":{"type":"keyword","ignore_above":256}}},"PrimaryId":{"type":"long"},"Source":{"type":"long"},"SourceName":{"type":"text","fields":{"keyword":{"type":"keyword","ignore_above":256}}},"SubSource":{"type":"long"},"StartDate":{"type":"text","fields":{"keyword":{"type":"keyword","ignore_above":256}}},"Unit":{"type":"text","fields":{"keyword":{"type":"keyword","ignore_above":256}}}}}}`
-	//indexMappingB := `{"settings":{"analysis":{"analyzer":{"ik_analyzer":{"type":"custom","tokenizer":"ik_max_word"},"ngram_analyzer":{"type":"custom","tokenizer":"standard","filter":["lowercase","ngram_filter"]}},"filter":{"ngram_filter":{"type":"ngram","min_gram":1,"max_gram":2,"token_chars":["letter","digit"]}}}},"mappings":{"properties":{"ClassifyId":{"type":"long"},"CreateTime":{"type":"text","fields":{"keyword":{"type":"keyword","ignore_above":256}}},"EndDate":{"type":"text","fields":{"keyword":{"type":"keyword","ignore_above":256}}},"Frequency":{"type":"text","analyzer":"ik_smart"},"IndexCode":{"type":"text","fields":{"ngram":{"type":"text","analyzer":"ngram_analyzer","search_analyzer":"standard"},"keyword":{"type":"keyword","ignore_above":256}}},"IndexName":{"type":"text","fields":{"ik":{"type":"text","analyzer":"ik_analyzer","search_analyzer":"ik_analyzer"},"ngram":{"type":"text","analyzer":"ngram_analyzer","search_analyzer":"standard"},"keyword":{"type":"keyword","ignore_above":256}}},"IsDeleted":{"type":"long"},"LatestValue":{"type":"double"},"ModifyTime":{"type":"text","fields":{"keyword":{"type":"keyword","ignore_above":256}}},"PrimaryId":{"type":"long"},"Source":{"type":"long"},"SourceName":{"type":"text","analyzer":"ik_smart"},"SubSource":{"type":"long"},"StartDate":{"type":"text","fields":{"keyword":{"type":"keyword","ignore_above":256}}},"Unit":{"type":"text","analyzer":"ik_smart"}}}}`
-	if e := elastic.CreateEsIndex(utils.EsDataSourceIndexName, indexMappingB); e != nil {
-		err = fmt.Errorf("创建数据源索引失败, %v", e)
-		return
-	}
-	fmt.Println("数据源索引创建成功")
-
-	fmt.Println("开始修复数据源")
-	var cond string
-	var pars []interface{}
-	// 睿咨得
-	rzdOb := new(dataSourceModel.BaseFromRzdIndex)
-	{
-		list, e := rzdOb.GetItemsByCondition(cond, pars, []string{}, "")
-		if e != nil {
-			err = fmt.Errorf("获取睿咨得失败, %v", e)
-			return
-		}
-		for _, v := range list {
-			indexItem := v.Format2SearchDataSource(v)
-			fmt.Printf("写入中:睿咨得-%d\n", indexItem.PrimaryId)
-			docId := fmt.Sprintf("%d-%d", indexItem.Source, indexItem.PrimaryId)
-			if e := elastic.EsAddOrEditDataSourceIndex(utils.EsDataSourceIndexName, docId, indexItem); e != nil {
-				err = fmt.Errorf("睿咨得-写入es失败, %v", e)
-				return
-			}
-		}
-	}
-
-	// 泛糖科技
-	hisugarOb := new(dataSourceModel.BaseFromHisugarIndex)
-	{
-		list, e := hisugarOb.GetItemsByCondition(cond, pars, []string{}, "")
-		if e != nil {
-			err = fmt.Errorf("获取泛糖科技失败, %v", e)
-			return
-		}
-		for _, v := range list {
-			indexItem := v.Format2SearchDataSource(v)
-			fmt.Printf("写入中:泛糖科技-%d\n", indexItem.PrimaryId)
-			docId := fmt.Sprintf("%d-%d", indexItem.Source, indexItem.PrimaryId)
-			if e := elastic.EsAddOrEditDataSourceIndex(utils.EsDataSourceIndexName, docId, indexItem); e != nil {
-				err = fmt.Errorf("泛糖科技-写入es失败, %v", e)
-				return
-			}
-		}
-	}
-
-	// 粮油商务网
-	lyOb := new(dataSourceModel.BaseFromLyIndex)
-	{
-		list, e := lyOb.GetItemsByCondition(cond, pars, []string{}, "")
-		if e != nil {
-			err = fmt.Errorf("获取粮油商务网失败, %v", e)
-			return
-		}
-		for _, v := range list {
-			indexItem := v.Format2SearchDataSource(v)
-			fmt.Printf("写入中:粮油商务网-%d\n", indexItem.PrimaryId)
-			docId := fmt.Sprintf("%d-%d", indexItem.Source, indexItem.PrimaryId)
-			if e := elastic.EsAddOrEditDataSourceIndex(utils.EsDataSourceIndexName, docId, indexItem); e != nil {
-				err = fmt.Errorf("粮油商务网-写入es失败, %v", e)
-				return
-			}
-		}
-	}
-
-	// 卓创红期
-	sciHqOb := new(dataSourceModel.BaseFromSciHqIndex)
-	{
-		list, e := sciHqOb.GetItemsByCondition(cond, pars, []string{}, "")
-		if e != nil {
-			err = fmt.Errorf("获取卓创红期失败, %v", e)
-			return
-		}
-		for _, v := range list {
-			indexItem := v.Format2SearchDataSource(v)
-			fmt.Printf("写入中:卓创红期-%d\n", indexItem.PrimaryId)
-			docId := fmt.Sprintf("%d-%d", indexItem.Source, indexItem.PrimaryId)
-			if e := elastic.EsAddOrEditDataSourceIndex(utils.EsDataSourceIndexName, docId, indexItem); e != nil {
-				err = fmt.Errorf("卓创红期-写入es失败, %v", e)
-				return
-			}
-		}
-	}
-
-	// 同花顺高频
-	thsHfOb := new(dataSourceModel.BaseFromThsHfIndex)
-	{
-		list, e := thsHfOb.GetItemsByCondition(cond, pars, []string{}, "")
-		if e != nil {
-			err = fmt.Errorf("获取同花顺高频失败, %v", e)
-			return
-		}
-		for _, v := range list {
-			indexItem := v.Format2SearchDataSource(v)
-			fmt.Printf("写入中:同花顺高频-%d\n", indexItem.PrimaryId)
-			docId := fmt.Sprintf("%d-%d", indexItem.Source, indexItem.PrimaryId)
-			if e := elastic.EsAddOrEditDataSourceIndex(utils.EsDataSourceIndexName, docId, indexItem); e != nil {
-				err = fmt.Errorf("同花顺高频-写入es失败, %v", e)
-				return
-			}
-		}
-	}
-
-	// 隆众资讯
-	oilchemOb := new(dataSourceModel.BaseFromOilchemIndex)
-	{
-		list, e := oilchemOb.GetItemsByCondition(cond, pars, []string{}, "")
-		if e != nil {
-			err = fmt.Errorf("获取隆众资讯失败, %v", e)
-			return
-		}
-		for _, v := range list {
-			indexItem := v.Format2SearchDataSource(v)
-			fmt.Printf("写入中:隆众资讯-%d\n", indexItem.PrimaryId)
-			docId := fmt.Sprintf("%d-%d", indexItem.Source, indexItem.PrimaryId)
-			if e := elastic.EsAddOrEditDataSourceIndex(utils.EsDataSourceIndexName, docId, indexItem); e != nil {
-				err = fmt.Errorf("隆众资讯-写入es失败, %v", e)
-				return
-			}
-		}
-	}
-
-	// CCF化纤信息
-	ccfOb := new(dataSourceModel.BaseFromCcfIndex)
-	{
-		list, e := ccfOb.GetItemsByCondition(cond, pars, []string{}, "")
-		if e != nil {
-			err = fmt.Errorf("获取CCF化纤信息失败, %v", e)
-			return
-		}
-		for _, v := range list {
-			indexItem := v.Format2SearchDataSource(v)
-			fmt.Printf("写入中:CCF化纤信息-%d\n", indexItem.PrimaryId)
-			docId := fmt.Sprintf("%d-%d", indexItem.Source, indexItem.PrimaryId)
-			if e := elastic.EsAddOrEditDataSourceIndex(utils.EsDataSourceIndexName, docId, indexItem); e != nil {
-				err = fmt.Errorf("CCF化纤信息-写入es失败, %v", e)
-				return
-			}
-		}
-	}
-
-	// 上海钢联
-	mysteelOb := new(dataSourceModel.BaseFromMysteelChemicalIndex)
-	{
-		list, e := mysteelOb.GetItemsByCondition(cond, pars, []string{}, "")
-		if e != nil {
-			err = fmt.Errorf("获取上海钢联失败, %v", e)
-			return
-		}
-		for _, v := range list {
-			indexItem := v.Format2SearchDataSource(v)
-			fmt.Printf("写入中:上海钢联-%d\n", indexItem.PrimaryId)
-			docId := fmt.Sprintf("%d-%d", indexItem.Source, indexItem.PrimaryId)
-			if e := elastic.EsAddOrEditDataSourceIndex(utils.EsDataSourceIndexName, docId, indexItem); e != nil {
-				err = fmt.Errorf("上海钢联-写入es失败, %v", e)
-				return
-			}
-		}
-	}
-
-	// SMM、有色原始数据库
-	smmOb := new(dataSourceModel.BaseFromSmmIndex)
-	{
-		list, e := smmOb.GetItemsByCondition(cond, pars, []string{}, "")
-		if e != nil {
-			err = fmt.Errorf("获取有色原始数据库失败, %v", e)
-			return
-		}
-		for _, v := range list {
-			indexItem := v.Format2SearchDataSource(v)
-			fmt.Printf("写入中:有色原始数据库-%d\n", indexItem.PrimaryId)
-			docId := fmt.Sprintf("%d-%d", indexItem.Source, indexItem.PrimaryId)
-			if e := elastic.EsAddOrEditDataSourceIndex(utils.EsDataSourceIndexName, docId, indexItem); e != nil {
-				err = fmt.Errorf("有色原始数据库-写入es失败, %v", e)
-				return
-			}
-		}
-	}
-
-	// 百川盈孚
-	baiinfoOb := new(dataSourceModel.BaseFromBaiinfoIndex)
-	{
-		list, e := baiinfoOb.GetItemsByCondition(cond, pars, []string{}, "")
-		if e != nil {
-			err = fmt.Errorf("获取百川盈孚失败, %v", e)
-			return
-		}
-		for _, v := range list {
-			indexItem := v.Format2SearchDataSource(v)
-			fmt.Printf("写入中:百川盈孚-%d\n", indexItem.PrimaryId)
-			docId := fmt.Sprintf("%d-%d", indexItem.Source, indexItem.PrimaryId)
-			if e := elastic.EsAddOrEditDataSourceIndex(utils.EsDataSourceIndexName, docId, indexItem); e != nil {
-				err = fmt.Errorf("百川盈孚-写入es失败, %v", e)
-				return
-			}
-		}
-	}
-
-	// 红桃3
-	sciOb := new(dataSourceModel.BaseFromSciIndex)
-	{
-		list, e := sciOb.GetItemsByCondition(cond, pars, []string{}, "")
-		if e != nil {
-			err = fmt.Errorf("获取红桃3失败, %v", e)
-			return
-		}
-		for _, v := range list {
-			indexItem := v.Format2SearchDataSource(v)
-			fmt.Printf("写入中:红桃3-%d\n", indexItem.PrimaryId)
-			docId := fmt.Sprintf("%d-%d", indexItem.Source, indexItem.PrimaryId)
-			if e := elastic.EsAddOrEditDataSourceIndex(utils.EsDataSourceIndexName, docId, indexItem); e != nil {
-				err = fmt.Errorf("红桃3-写入es失败, %v", e)
-				return
-			}
-		}
-	}
-
-	// 中国煤炭市场网
-	coalmineOb := new(dataSourceModel.BaseFromCoalmineMapping)
-	{
-		list, e := coalmineOb.GetItemsByCondition(cond, pars, []string{}, "")
-		if e != nil {
-			err = fmt.Errorf("获取中国煤炭市场网失败, %v", e)
-			return
-		}
-		for _, v := range list {
-			indexItem := v.Format2SearchDataSource(v)
-			fmt.Printf("写入中:中国煤炭市场网-%d\n", indexItem.PrimaryId)
-			docId := fmt.Sprintf("%d-%d", indexItem.Source, indexItem.PrimaryId)
-			if e := elastic.EsAddOrEditDataSourceIndex(utils.EsDataSourceIndexName, docId, indexItem); e != nil {
-				err = fmt.Errorf("中国煤炭市场网-写入es失败, %v", e)
-				return
-			}
-		}
-	}
-
-	// EIA STEO报告
-	eiaOb := new(dataSourceModel.BaseFromEiaSteoIndex)
-	{
-		list, e := eiaOb.GetItemsByCondition(cond, pars, []string{}, "")
-		if e != nil {
-			err = fmt.Errorf("获取EIA STEO报告失败, %v", e)
-			return
-		}
-		for _, v := range list {
-			indexItem := v.Format2SearchDataSource(v)
-			fmt.Printf("写入中:EIA STEO报告-%d\n", indexItem.PrimaryId)
-			docId := fmt.Sprintf("%d-%d", indexItem.Source, indexItem.PrimaryId)
-			if e := elastic.EsAddOrEditDataSourceIndex(utils.EsDataSourceIndexName, docId, indexItem); e != nil {
-				err = fmt.Errorf("EIA STEO报告-写入es失败, %v", e)
-				return
-			}
-		}
-	}
-
-	// ICPI消费价格指数
-	icpiOb := new(dataSourceModel.BaseFromIcpiIndex)
-	{
-		list, e := icpiOb.GetItemsByCondition(cond, pars, []string{}, "")
-		if e != nil {
-			err = fmt.Errorf("获取ICPI消费价格指数失败, %v", e)
-			return
-		}
-		for _, v := range list {
-			indexItem := v.Format2SearchDataSource(v)
-			fmt.Printf("写入中:ICPI消费价格指数-%d\n", indexItem.PrimaryId)
-			docId := fmt.Sprintf("%d-%d", indexItem.Source, indexItem.PrimaryId)
-			if e := elastic.EsAddOrEditDataSourceIndex(utils.EsDataSourceIndexName, docId, indexItem); e != nil {
-				err = fmt.Errorf("ICPI消费价格指数-写入es失败, %v", e)
-				return
-			}
-		}
-	}
-
-	// 涌益咨询
-	yongyiOb := new(dataSourceModel.BaseFromYongyiIndex)
-	{
-		list, e := yongyiOb.GetItemsByCondition(cond, pars, []string{}, "")
-		if e != nil {
-			err = fmt.Errorf("获取涌益咨询失败, %v", e)
-			return
-		}
-		for _, v := range list {
-			indexItem := v.Format2SearchDataSource(v)
-			fmt.Printf("写入中:涌益咨询-%d\n", indexItem.PrimaryId)
-			docId := fmt.Sprintf("%d-%d", indexItem.Source, indexItem.PrimaryId)
-			if e := elastic.EsAddOrEditDataSourceIndex(utils.EsDataSourceIndexName, docId, indexItem); e != nil {
-				err = fmt.Errorf("涌益咨询-写入es失败, %v", e)
-				return
-			}
-		}
-	}
-
-	// 汾渭数据
-	fenweiOb := new(dataSourceModel.BaseFromFenweiIndex)
-	{
-		list, e := fenweiOb.GetItemsByCondition(cond, pars, []string{}, "")
-		if e != nil {
-			err = fmt.Errorf("获取汾渭数据失败, %v", e)
-			return
-		}
-		for _, v := range list {
-			indexItem := v.Format2SearchDataSource(v)
-			fmt.Printf("写入中:汾渭数据-%d\n", indexItem.PrimaryId)
-			docId := fmt.Sprintf("%d-%d", indexItem.Source, indexItem.PrimaryId)
-			if e := elastic.EsAddOrEditDataSourceIndex(utils.EsDataSourceIndexName, docId, indexItem); e != nil {
-				err = fmt.Errorf("汾渭数据-写入es失败, %v", e)
-				return
-			}
-		}
-	}
-
-	// 卓创数据
-	sci99Ob := new(dataSourceModel.BaseFromSci99Index)
-	{
-		list, e := sci99Ob.GetItemsByCondition(cond, pars, []string{}, "")
-		if e != nil {
-			err = fmt.Errorf("获取卓创数据失败, %v", e)
-			return
-		}
-		for _, v := range list {
-			indexItem := v.Format2SearchDataSource(v)
-			fmt.Printf("写入中:卓创数据-%d\n", indexItem.PrimaryId)
-			docId := fmt.Sprintf("%d-%d", indexItem.Source, indexItem.PrimaryId)
-			if e := elastic.EsAddOrEditDataSourceIndex(utils.EsDataSourceIndexName, docId, indexItem); e != nil {
-				err = fmt.Errorf("卓创数据-写入es失败, %v", e)
-				return
-			}
-		}
-	}
-
-	// 钢联原始指标库
-	glOb := new(dataSourceModel.BaseFromGlIndex)
-	{
-		list, e := glOb.GetItemsByCondition(cond, pars, []string{}, "")
-		if e != nil {
-			err = fmt.Errorf("获取钢联原始指标库失败, %v", e)
-			return
-		}
-		for _, v := range list {
-			indexItem := v.Format2SearchDataSource(v)
-			fmt.Printf("写入中:钢联原始指标库-%d\n", indexItem.PrimaryId)
-			docId := fmt.Sprintf("%d-%d", indexItem.Source, indexItem.PrimaryId)
-			if e := elastic.EsAddOrEditDataSourceIndex(utils.EsDataSourceIndexName, docId, indexItem); e != nil {
-				err = fmt.Errorf("钢联原始指标库-写入es失败, %v", e)
-				return
-			}
-		}
-	}
-
-	// 手工指标录入
-	manualOb := new(dataSourceModel.BaseFromManualEdb)
-	{
-		list, e := manualOb.GetItemsByCondition(cond, pars, []string{}, "")
-		if e != nil {
-			err = fmt.Errorf("获取手工指标失败, %v", e)
-			return
-		}
-		for _, v := range list {
-			indexItem := v.Format2SearchDataSource(v)
-			fmt.Printf("写入中:手工指标-%s\n", indexItem.IndexCode)
-			docId := fmt.Sprintf("%d-%s", indexItem.Source, indexItem.IndexCode)
-			if e := elastic.EsAddOrEditDataSourceIndex(utils.EsDataSourceIndexName, docId, indexItem); e != nil {
-				err = fmt.Errorf("手工指标-写入es失败, %v", e)
-				return
-			}
-		}
-	}
-
-	// Bloomberg
-	bloombergOb := new(dataSourceModel.BaseFromBloombergIndex)
-	{
-		list, e := bloombergOb.GetItemsByCondition(cond, pars, []string{}, "")
-		if e != nil {
-			err = fmt.Errorf("获取Bloomberg失败, %v", e)
-			return
-		}
-		for _, v := range list {
-			indexItem := v.Format2SearchDataSource(v)
-			fmt.Printf("写入中:Bloomberg-%d\n", indexItem.PrimaryId)
-			docId := fmt.Sprintf("%d-%d", indexItem.Source, indexItem.PrimaryId)
-			if e := elastic.EsAddOrEditDataSourceIndex(utils.EsDataSourceIndexName, docId, indexItem); e != nil {
-				err = fmt.Errorf("Bloomberg-写入es失败, %v", e)
-				return
-			}
-		}
-	}
-
-	// 煤炭江湖
-	mtjhOb := new(dataSourceModel.BaseFromMtjhMapping)
-	{
-		list, e := mtjhOb.GetItemsByCondition(cond, pars, []string{}, "")
-		if e != nil {
-			err = fmt.Errorf("获取煤炭江湖失败, %v", e)
-			return
-		}
-		for _, v := range list {
-			indexItem := v.Format2SearchDataSource(v)
-			fmt.Printf("写入中:煤炭江湖-%d\n", indexItem.PrimaryId)
-			docId := fmt.Sprintf("%d-%d", indexItem.Source, indexItem.PrimaryId)
-			if e := elastic.EsAddOrEditDataSourceIndex(utils.EsDataSourceIndexName, docId, indexItem); e != nil {
-				err = fmt.Errorf("煤炭江湖-写入es失败, %v", e)
-				return
-			}
-		}
-	}
-
-	// AI预测模型
-	aiPredictOb := new(aiPredictModel.AiPredictModelIndex)
-	{
-		list, e := aiPredictOb.GetItemsByCondition(cond, pars, []string{}, "")
-		if e != nil {
-			err = fmt.Errorf("获取AI预测模型失败, %v", e)
-			return
-		}
-		for _, v := range list {
-			indexItem := new(dataSourceModel.SearchDataSource)
-			indexItem.PrimaryId = v.AiPredictModelIndexId
-			indexItem.IndexName = v.IndexName
-			indexItem.IndexCode = v.IndexCode
-			indexItem.ClassifyId = v.ClassifyId
-			indexItem.Source = utils.DATA_SOURCE_AI_PREDICT_MODEL
-			indexItem.SourceName = "AI预测模型"
-			indexItem.CreateTime = utils.TimeTransferString(utils.FormatDateTime, v.CreateTime)
-			indexItem.ModifyTime = utils.TimeTransferString(utils.FormatDateTime, v.ModifyTime)
-			fmt.Printf("写入中:AI预测模型-%d\n", indexItem.PrimaryId)
-			docId := fmt.Sprintf("%d-%d", indexItem.Source, indexItem.PrimaryId)
-			if e := elastic.EsAddOrEditDataSourceIndex(utils.EsDataSourceIndexName, docId, indexItem); e != nil {
-				err = fmt.Errorf("AI预测模型-写入es失败, %v", e)
-				return
-			}
-		}
-	}
-
-	// 美国农业部
-	usdaOb := new(dataSourceModel.BaseFromUsdaFasIndex)
-	{
-		list, e := usdaOb.GetItemsByCondition(cond, pars, []string{}, "")
-		if e != nil {
-			err = fmt.Errorf("获取美国农业部失败, %v", e)
-			return
-		}
-		for _, v := range list {
-			indexItem := v.Format2SearchDataSource(v)
-			fmt.Printf("写入中:美国农业部-%d\n", indexItem.PrimaryId)
-			docId := fmt.Sprintf("%d-%d", indexItem.Source, indexItem.PrimaryId)
-			if e := elastic.EsAddOrEditDataSourceIndex(utils.EsDataSourceIndexName, docId, indexItem); e != nil {
-				err = fmt.Errorf("美国农业部-写入es失败, %v", e)
-				return
-			}
-		}
-	}
-
-	// 自有数据
-	businessOb := new(dataSourceModel.BaseFromBusinessIndex)
-	{
-		list, e := businessOb.GetItemsByCondition(cond, pars, []string{}, "")
-		if e != nil {
-			err = fmt.Errorf("获取自有数据失败, %v", e)
-			return
-		}
-		for _, v := range list {
-			indexItem := v.Format2SearchDataSource(v)
-			fmt.Printf("写入中:自有数据-%d\n", indexItem.PrimaryId)
-			docId := fmt.Sprintf("%d-%d", indexItem.Source, indexItem.PrimaryId)
-			if e := elastic.EsAddOrEditDataSourceIndex(utils.EsDataSourceIndexName, docId, indexItem); e != nil {
-				err = fmt.Errorf("自有数据-写入es失败, %v", e)
-				return
-			}
-		}
-	}
+	//fmt.Println("数据源索引名:", utils.EsDataSourceIndexName)
+	//if utils.EsDataSourceIndexName == "" {
+	//	err = fmt.Errorf("数据源索引名为空")
+	//	return
+	//}
+	//fmt.Println("开始创建数据源索引:", utils.EsDataSourceIndexName)
+	//indexMappingB := `{"mappings":{"properties":{"ClassifyId":{"type":"long"},"CreateTime":{"type":"text","fields":{"keyword":{"type":"keyword","ignore_above":256}}},"EndDate":{"type":"text","fields":{"keyword":{"type":"keyword","ignore_above":256}}},"Frequency":{"type":"text","fields":{"keyword":{"type":"keyword","ignore_above":256}}},"IndexCode":{"type":"text","fields":{"keyword":{"type":"keyword","ignore_above":256}}},"IndexName":{"type":"text","fields":{"keyword":{"type":"keyword","ignore_above":256}}},"IsDeleted":{"type":"long"},"LatestValue":{"type":"text"},"ModifyTime":{"type":"text","fields":{"keyword":{"type":"keyword","ignore_above":256}}},"PrimaryId":{"type":"long"},"Source":{"type":"long"},"SourceName":{"type":"text","fields":{"keyword":{"type":"keyword","ignore_above":256}}},"SubSource":{"type":"long"},"StartDate":{"type":"text","fields":{"keyword":{"type":"keyword","ignore_above":256}}},"Unit":{"type":"text","fields":{"keyword":{"type":"keyword","ignore_above":256}}}}}}`
+	////indexMappingB := `{"settings":{"analysis":{"analyzer":{"ik_analyzer":{"type":"custom","tokenizer":"ik_max_word"},"ngram_analyzer":{"type":"custom","tokenizer":"standard","filter":["lowercase","ngram_filter"]}},"filter":{"ngram_filter":{"type":"ngram","min_gram":1,"max_gram":2,"token_chars":["letter","digit"]}}}},"mappings":{"properties":{"ClassifyId":{"type":"long"},"CreateTime":{"type":"text","fields":{"keyword":{"type":"keyword","ignore_above":256}}},"EndDate":{"type":"text","fields":{"keyword":{"type":"keyword","ignore_above":256}}},"Frequency":{"type":"text","analyzer":"ik_smart"},"IndexCode":{"type":"text","fields":{"ngram":{"type":"text","analyzer":"ngram_analyzer","search_analyzer":"standard"},"keyword":{"type":"keyword","ignore_above":256}}},"IndexName":{"type":"text","fields":{"ik":{"type":"text","analyzer":"ik_analyzer","search_analyzer":"ik_analyzer"},"ngram":{"type":"text","analyzer":"ngram_analyzer","search_analyzer":"standard"},"keyword":{"type":"keyword","ignore_above":256}}},"IsDeleted":{"type":"long"},"LatestValue":{"type":"double"},"ModifyTime":{"type":"text","fields":{"keyword":{"type":"keyword","ignore_above":256}}},"PrimaryId":{"type":"long"},"Source":{"type":"long"},"SourceName":{"type":"text","analyzer":"ik_smart"},"SubSource":{"type":"long"},"StartDate":{"type":"text","fields":{"keyword":{"type":"keyword","ignore_above":256}}},"Unit":{"type":"text","analyzer":"ik_smart"}}}}`
+	//_, e = elastic.CreateEsIndex(utils.EsDataSourceIndexName, indexMappingB)
+	//if e != nil {
+	//	err = fmt.Errorf("创建数据源索引失败, %v", e)
+	//	return
+	//}
+	//fmt.Println("数据源索引创建成功")
+	//
+	//// 清空数据源
+	////fmt.Println("开始清空数据源")
+	////if sourceExists {
+	////	if e := elastic.ClearEsIndex(utils.EsDataSourceIndexName); e != nil {
+	////		err = fmt.Errorf("清空数据源索引数据失败, %v", e)
+	////		return
+	////	}
+	////}
+	////fmt.Println("结束清空数据源")
+	//
+	//fmt.Println("开始修复数据源")
+	//var cond string
+	//var pars []interface{}
+	//// 睿咨得
+	//rzdOb := new(dataSourceModel.BaseFromRzdIndex)
+	//{
+	//	list, e := rzdOb.GetItemsByCondition(cond, pars, []string{}, "")
+	//	if e != nil {
+	//		err = fmt.Errorf("获取睿咨得失败, %v", e)
+	//		return
+	//	}
+	//	for _, v := range list {
+	//		indexItem := v.Format2SearchDataSource(v)
+	//		fmt.Printf("写入中:睿咨得-%d\n", indexItem.PrimaryId)
+	//		docId := fmt.Sprintf("%d-%d", indexItem.Source, indexItem.PrimaryId)
+	//		if e := elastic.EsAddOrEditDataSourceIndex(utils.EsDataSourceIndexName, docId, indexItem); e != nil {
+	//			err = fmt.Errorf("睿咨得-写入es失败, %v", e)
+	//			return
+	//		}
+	//	}
+	//}
+	//
+	//// 泛糖科技
+	//hisugarOb := new(dataSourceModel.BaseFromHisugarIndex)
+	//{
+	//	list, e := hisugarOb.GetItemsByCondition(cond, pars, []string{}, "")
+	//	if e != nil {
+	//		err = fmt.Errorf("获取泛糖科技失败, %v", e)
+	//		return
+	//	}
+	//	for _, v := range list {
+	//		indexItem := v.Format2SearchDataSource(v)
+	//		fmt.Printf("写入中:泛糖科技-%d\n", indexItem.PrimaryId)
+	//		docId := fmt.Sprintf("%d-%d", indexItem.Source, indexItem.PrimaryId)
+	//		if e := elastic.EsAddOrEditDataSourceIndex(utils.EsDataSourceIndexName, docId, indexItem); e != nil {
+	//			err = fmt.Errorf("泛糖科技-写入es失败, %v", e)
+	//			return
+	//		}
+	//	}
+	//}
+	//
+	//// 粮油商务网
+	//lyOb := new(dataSourceModel.BaseFromLyIndex)
+	//{
+	//	list, e := lyOb.GetItemsByCondition(cond, pars, []string{}, "")
+	//	if e != nil {
+	//		err = fmt.Errorf("获取粮油商务网失败, %v", e)
+	//		return
+	//	}
+	//	for _, v := range list {
+	//		indexItem := v.Format2SearchDataSource(v)
+	//		fmt.Printf("写入中:粮油商务网-%d\n", indexItem.PrimaryId)
+	//		docId := fmt.Sprintf("%d-%d", indexItem.Source, indexItem.PrimaryId)
+	//		if e := elastic.EsAddOrEditDataSourceIndex(utils.EsDataSourceIndexName, docId, indexItem); e != nil {
+	//			err = fmt.Errorf("粮油商务网-写入es失败, %v", e)
+	//			return
+	//		}
+	//	}
+	//}
+	//
+	//// 卓创红期
+	//sciHqOb := new(dataSourceModel.BaseFromSciHqIndex)
+	//{
+	//	list, e := sciHqOb.GetItemsByCondition(cond, pars, []string{}, "")
+	//	if e != nil {
+	//		err = fmt.Errorf("获取卓创红期失败, %v", e)
+	//		return
+	//	}
+	//	for _, v := range list {
+	//		indexItem := v.Format2SearchDataSource(v)
+	//		fmt.Printf("写入中:卓创红期-%d\n", indexItem.PrimaryId)
+	//		docId := fmt.Sprintf("%d-%d", indexItem.Source, indexItem.PrimaryId)
+	//		if e := elastic.EsAddOrEditDataSourceIndex(utils.EsDataSourceIndexName, docId, indexItem); e != nil {
+	//			err = fmt.Errorf("卓创红期-写入es失败, %v", e)
+	//			return
+	//		}
+	//	}
+	//}
+	//
+	//// 同花顺高频
+	//thsHfOb := new(dataSourceModel.BaseFromThsHfIndex)
+	//{
+	//	list, e := thsHfOb.GetItemsByCondition(cond, pars, []string{}, "")
+	//	if e != nil {
+	//		err = fmt.Errorf("获取同花顺高频失败, %v", e)
+	//		return
+	//	}
+	//	for _, v := range list {
+	//		indexItem := v.Format2SearchDataSource(v)
+	//		fmt.Printf("写入中:同花顺高频-%d\n", indexItem.PrimaryId)
+	//		docId := fmt.Sprintf("%d-%d", indexItem.Source, indexItem.PrimaryId)
+	//		if e := elastic.EsAddOrEditDataSourceIndex(utils.EsDataSourceIndexName, docId, indexItem); e != nil {
+	//			err = fmt.Errorf("同花顺高频-写入es失败, %v", e)
+	//			return
+	//		}
+	//	}
+	//}
+	//
+	//// 隆众资讯
+	//oilchemOb := new(dataSourceModel.BaseFromOilchemIndex)
+	//{
+	//	list, e := oilchemOb.GetItemsByCondition(cond, pars, []string{}, "")
+	//	if e != nil {
+	//		err = fmt.Errorf("获取隆众资讯失败, %v", e)
+	//		return
+	//	}
+	//	for _, v := range list {
+	//		indexItem := v.Format2SearchDataSource(v)
+	//		fmt.Printf("写入中:隆众资讯-%d\n", indexItem.PrimaryId)
+	//		docId := fmt.Sprintf("%d-%d", indexItem.Source, indexItem.PrimaryId)
+	//		if e := elastic.EsAddOrEditDataSourceIndex(utils.EsDataSourceIndexName, docId, indexItem); e != nil {
+	//			err = fmt.Errorf("隆众资讯-写入es失败, %v", e)
+	//			return
+	//		}
+	//	}
+	//}
+	//
+	//// CCF化纤信息
+	//ccfOb := new(dataSourceModel.BaseFromCcfIndex)
+	//{
+	//	list, e := ccfOb.GetItemsByCondition(cond, pars, []string{}, "")
+	//	if e != nil {
+	//		err = fmt.Errorf("获取CCF化纤信息失败, %v", e)
+	//		return
+	//	}
+	//	for _, v := range list {
+	//		indexItem := v.Format2SearchDataSource(v)
+	//		fmt.Printf("写入中:CCF化纤信息-%d\n", indexItem.PrimaryId)
+	//		docId := fmt.Sprintf("%d-%d", indexItem.Source, indexItem.PrimaryId)
+	//		if e := elastic.EsAddOrEditDataSourceIndex(utils.EsDataSourceIndexName, docId, indexItem); e != nil {
+	//			err = fmt.Errorf("CCF化纤信息-写入es失败, %v", e)
+	//			return
+	//		}
+	//	}
+	//}
+	//
+	//// 上海钢联
+	//mysteelOb := new(dataSourceModel.BaseFromMysteelChemicalIndex)
+	//{
+	//	list, e := mysteelOb.GetItemsByCondition(cond, pars, []string{}, "")
+	//	if e != nil {
+	//		err = fmt.Errorf("获取上海钢联失败, %v", e)
+	//		return
+	//	}
+	//	for _, v := range list {
+	//		indexItem := v.Format2SearchDataSource(v)
+	//		fmt.Printf("写入中:上海钢联-%d\n", indexItem.PrimaryId)
+	//		docId := fmt.Sprintf("%d-%d", indexItem.Source, indexItem.PrimaryId)
+	//		if e := elastic.EsAddOrEditDataSourceIndex(utils.EsDataSourceIndexName, docId, indexItem); e != nil {
+	//			err = fmt.Errorf("上海钢联-写入es失败, %v", e)
+	//			return
+	//		}
+	//	}
+	//}
+	//
+	//// SMM、有色原始数据库
+	//smmOb := new(dataSourceModel.BaseFromSmmIndex)
+	//{
+	//	list, e := smmOb.GetItemsByCondition(cond, pars, []string{}, "")
+	//	if e != nil {
+	//		err = fmt.Errorf("获取有色原始数据库失败, %v", e)
+	//		return
+	//	}
+	//	for _, v := range list {
+	//		indexItem := v.Format2SearchDataSource(v)
+	//		fmt.Printf("写入中:有色原始数据库-%d\n", indexItem.PrimaryId)
+	//		docId := fmt.Sprintf("%d-%d", indexItem.Source, indexItem.PrimaryId)
+	//		if e := elastic.EsAddOrEditDataSourceIndex(utils.EsDataSourceIndexName, docId, indexItem); e != nil {
+	//			err = fmt.Errorf("有色原始数据库-写入es失败, %v", e)
+	//			return
+	//		}
+	//	}
+	//}
+	//
+	//// 百川盈孚
+	//baiinfoOb := new(dataSourceModel.BaseFromBaiinfoIndex)
+	//{
+	//	list, e := baiinfoOb.GetItemsByCondition(cond, pars, []string{}, "")
+	//	if e != nil {
+	//		err = fmt.Errorf("获取百川盈孚失败, %v", e)
+	//		return
+	//	}
+	//	for _, v := range list {
+	//		indexItem := v.Format2SearchDataSource(v)
+	//		fmt.Printf("写入中:百川盈孚-%d\n", indexItem.PrimaryId)
+	//		docId := fmt.Sprintf("%d-%d", indexItem.Source, indexItem.PrimaryId)
+	//		if e := elastic.EsAddOrEditDataSourceIndex(utils.EsDataSourceIndexName, docId, indexItem); e != nil {
+	//			err = fmt.Errorf("百川盈孚-写入es失败, %v", e)
+	//			return
+	//		}
+	//	}
+	//}
+	//
+	//// 红桃3
+	//sciOb := new(dataSourceModel.BaseFromSciIndex)
+	//{
+	//	list, e := sciOb.GetItemsByCondition(cond, pars, []string{}, "")
+	//	if e != nil {
+	//		err = fmt.Errorf("获取红桃3失败, %v", e)
+	//		return
+	//	}
+	//	for _, v := range list {
+	//		indexItem := v.Format2SearchDataSource(v)
+	//		fmt.Printf("写入中:红桃3-%d\n", indexItem.PrimaryId)
+	//		docId := fmt.Sprintf("%d-%d", indexItem.Source, indexItem.PrimaryId)
+	//		if e := elastic.EsAddOrEditDataSourceIndex(utils.EsDataSourceIndexName, docId, indexItem); e != nil {
+	//			err = fmt.Errorf("红桃3-写入es失败, %v", e)
+	//			return
+	//		}
+	//	}
+	//}
+	//
+	//// 中国煤炭市场网
+	//coalmineOb := new(dataSourceModel.BaseFromCoalmineMapping)
+	//{
+	//	list, e := coalmineOb.GetItemsByCondition(cond, pars, []string{}, "")
+	//	if e != nil {
+	//		err = fmt.Errorf("获取中国煤炭市场网失败, %v", e)
+	//		return
+	//	}
+	//	for _, v := range list {
+	//		indexItem := v.Format2SearchDataSource(v)
+	//		fmt.Printf("写入中:中国煤炭市场网-%d\n", indexItem.PrimaryId)
+	//		docId := fmt.Sprintf("%d-%d", indexItem.Source, indexItem.PrimaryId)
+	//		if e := elastic.EsAddOrEditDataSourceIndex(utils.EsDataSourceIndexName, docId, indexItem); e != nil {
+	//			err = fmt.Errorf("中国煤炭市场网-写入es失败, %v", e)
+	//			return
+	//		}
+	//	}
+	//}
+	//
+	//// EIA STEO报告
+	//eiaOb := new(dataSourceModel.BaseFromEiaSteoIndex)
+	//{
+	//	list, e := eiaOb.GetItemsByCondition(cond, pars, []string{}, "")
+	//	if e != nil {
+	//		err = fmt.Errorf("获取EIA STEO报告失败, %v", e)
+	//		return
+	//	}
+	//	for _, v := range list {
+	//		indexItem := v.Format2SearchDataSource(v)
+	//		fmt.Printf("写入中:EIA STEO报告-%d\n", indexItem.PrimaryId)
+	//		docId := fmt.Sprintf("%d-%d", indexItem.Source, indexItem.PrimaryId)
+	//		if e := elastic.EsAddOrEditDataSourceIndex(utils.EsDataSourceIndexName, docId, indexItem); e != nil {
+	//			err = fmt.Errorf("EIA STEO报告-写入es失败, %v", e)
+	//			return
+	//		}
+	//	}
+	//}
+	//
+	//// ICPI消费价格指数
+	//icpiOb := new(dataSourceModel.BaseFromIcpiIndex)
+	//{
+	//	list, e := icpiOb.GetItemsByCondition(cond, pars, []string{}, "")
+	//	if e != nil {
+	//		err = fmt.Errorf("获取ICPI消费价格指数失败, %v", e)
+	//		return
+	//	}
+	//	for _, v := range list {
+	//		indexItem := v.Format2SearchDataSource(v)
+	//		fmt.Printf("写入中:ICPI消费价格指数-%d\n", indexItem.PrimaryId)
+	//		docId := fmt.Sprintf("%d-%d", indexItem.Source, indexItem.PrimaryId)
+	//		if e := elastic.EsAddOrEditDataSourceIndex(utils.EsDataSourceIndexName, docId, indexItem); e != nil {
+	//			err = fmt.Errorf("ICPI消费价格指数-写入es失败, %v", e)
+	//			return
+	//		}
+	//	}
+	//}
+	//
+	//// 涌益咨询
+	//yongyiOb := new(dataSourceModel.BaseFromYongyiIndex)
+	//{
+	//	list, e := yongyiOb.GetItemsByCondition(cond, pars, []string{}, "")
+	//	if e != nil {
+	//		err = fmt.Errorf("获取涌益咨询失败, %v", e)
+	//		return
+	//	}
+	//	for _, v := range list {
+	//		indexItem := v.Format2SearchDataSource(v)
+	//		fmt.Printf("写入中:涌益咨询-%d\n", indexItem.PrimaryId)
+	//		docId := fmt.Sprintf("%d-%d", indexItem.Source, indexItem.PrimaryId)
+	//		if e := elastic.EsAddOrEditDataSourceIndex(utils.EsDataSourceIndexName, docId, indexItem); e != nil {
+	//			err = fmt.Errorf("涌益咨询-写入es失败, %v", e)
+	//			return
+	//		}
+	//	}
+	//}
+	//
+	//// 汾渭数据
+	//fenweiOb := new(dataSourceModel.BaseFromFenweiIndex)
+	//{
+	//	list, e := fenweiOb.GetItemsByCondition(cond, pars, []string{}, "")
+	//	if e != nil {
+	//		err = fmt.Errorf("获取汾渭数据失败, %v", e)
+	//		return
+	//	}
+	//	for _, v := range list {
+	//		indexItem := v.Format2SearchDataSource(v)
+	//		fmt.Printf("写入中:汾渭数据-%d\n", indexItem.PrimaryId)
+	//		docId := fmt.Sprintf("%d-%d", indexItem.Source, indexItem.PrimaryId)
+	//		if e := elastic.EsAddOrEditDataSourceIndex(utils.EsDataSourceIndexName, docId, indexItem); e != nil {
+	//			err = fmt.Errorf("汾渭数据-写入es失败, %v", e)
+	//			return
+	//		}
+	//	}
+	//}
+	//
+	//// 卓创数据
+	//sci99Ob := new(dataSourceModel.BaseFromSci99Index)
+	//{
+	//	list, e := sci99Ob.GetItemsByCondition(cond, pars, []string{}, "")
+	//	if e != nil {
+	//		err = fmt.Errorf("获取卓创数据失败, %v", e)
+	//		return
+	//	}
+	//	for _, v := range list {
+	//		indexItem := v.Format2SearchDataSource(v)
+	//		fmt.Printf("写入中:卓创数据-%d\n", indexItem.PrimaryId)
+	//		docId := fmt.Sprintf("%d-%d", indexItem.Source, indexItem.PrimaryId)
+	//		if e := elastic.EsAddOrEditDataSourceIndex(utils.EsDataSourceIndexName, docId, indexItem); e != nil {
+	//			err = fmt.Errorf("卓创数据-写入es失败, %v", e)
+	//			return
+	//		}
+	//	}
+	//}
+	//
+	//// 钢联原始指标库
+	//glOb := new(dataSourceModel.BaseFromGlIndex)
+	//{
+	//	list, e := glOb.GetItemsByCondition(cond, pars, []string{}, "")
+	//	if e != nil {
+	//		err = fmt.Errorf("获取钢联原始指标库失败, %v", e)
+	//		return
+	//	}
+	//	for _, v := range list {
+	//		indexItem := v.Format2SearchDataSource(v)
+	//		fmt.Printf("写入中:钢联原始指标库-%d\n", indexItem.PrimaryId)
+	//		docId := fmt.Sprintf("%d-%d", indexItem.Source, indexItem.PrimaryId)
+	//		if e := elastic.EsAddOrEditDataSourceIndex(utils.EsDataSourceIndexName, docId, indexItem); e != nil {
+	//			err = fmt.Errorf("钢联原始指标库-写入es失败, %v", e)
+	//			return
+	//		}
+	//	}
+	//}
+	//
+	//// 手工指标录入
+	//manualOb := new(dataSourceModel.BaseFromManualEdb)
+	//{
+	//	list, e := manualOb.GetItemsByCondition(cond, pars, []string{}, "")
+	//	if e != nil {
+	//		err = fmt.Errorf("获取手工指标失败, %v", e)
+	//		return
+	//	}
+	//	for _, v := range list {
+	//		indexItem := v.Format2SearchDataSource(v)
+	//		fmt.Printf("写入中:手工指标-%s\n", indexItem.IndexCode)
+	//		docId := fmt.Sprintf("%d-%s", indexItem.Source, indexItem.IndexCode)
+	//		if e := elastic.EsAddOrEditDataSourceIndex(utils.EsDataSourceIndexName, docId, indexItem); e != nil {
+	//			err = fmt.Errorf("手工指标-写入es失败, %v", e)
+	//			return
+	//		}
+	//	}
+	//}
+	//
+	//// Bloomberg
+	//bloombergOb := new(dataSourceModel.BaseFromBloombergIndex)
+	//{
+	//	list, e := bloombergOb.GetItemsByCondition(cond, pars, []string{}, "")
+	//	if e != nil {
+	//		err = fmt.Errorf("获取Bloomberg失败, %v", e)
+	//		return
+	//	}
+	//	for _, v := range list {
+	//		indexItem := v.Format2SearchDataSource(v)
+	//		fmt.Printf("写入中:Bloomberg-%d\n", indexItem.PrimaryId)
+	//		docId := fmt.Sprintf("%d-%d", indexItem.Source, indexItem.PrimaryId)
+	//		if e := elastic.EsAddOrEditDataSourceIndex(utils.EsDataSourceIndexName, docId, indexItem); e != nil {
+	//			err = fmt.Errorf("Bloomberg-写入es失败, %v", e)
+	//			return
+	//		}
+	//	}
+	//}
+	//
+	//// 煤炭江湖
+	//mtjhOb := new(dataSourceModel.BaseFromMtjhMapping)
+	//{
+	//	list, e := mtjhOb.GetItemsByCondition(cond, pars, []string{}, "")
+	//	if e != nil {
+	//		err = fmt.Errorf("获取煤炭江湖失败, %v", e)
+	//		return
+	//	}
+	//	for _, v := range list {
+	//		indexItem := v.Format2SearchDataSource(v)
+	//		fmt.Printf("写入中:煤炭江湖-%d\n", indexItem.PrimaryId)
+	//		docId := fmt.Sprintf("%d-%d", indexItem.Source, indexItem.PrimaryId)
+	//		if e := elastic.EsAddOrEditDataSourceIndex(utils.EsDataSourceIndexName, docId, indexItem); e != nil {
+	//			err = fmt.Errorf("煤炭江湖-写入es失败, %v", e)
+	//			return
+	//		}
+	//	}
+	//}
+	//
+	//// AI预测模型
+	//aiPredictOb := new(aiPredictModel.AiPredictModelIndex)
+	//{
+	//	list, e := aiPredictOb.GetItemsByCondition(cond, pars, []string{}, "")
+	//	if e != nil {
+	//		err = fmt.Errorf("获取AI预测模型失败, %v", e)
+	//		return
+	//	}
+	//	for _, v := range list {
+	//		indexItem := new(dataSourceModel.SearchDataSource)
+	//		indexItem.PrimaryId = v.AiPredictModelIndexId
+	//		indexItem.IndexName = v.IndexName
+	//		indexItem.IndexCode = v.IndexCode
+	//		indexItem.ClassifyId = v.ClassifyId
+	//		indexItem.Source = utils.DATA_SOURCE_AI_PREDICT_MODEL
+	//		indexItem.SourceName = "AI预测模型"
+	//		indexItem.CreateTime = utils.TimeTransferString(utils.FormatDateTime, v.CreateTime)
+	//		indexItem.ModifyTime = utils.TimeTransferString(utils.FormatDateTime, v.ModifyTime)
+	//		fmt.Printf("写入中:AI预测模型-%d\n", indexItem.PrimaryId)
+	//		docId := fmt.Sprintf("%d-%d", indexItem.Source, indexItem.PrimaryId)
+	//		if e := elastic.EsAddOrEditDataSourceIndex(utils.EsDataSourceIndexName, docId, indexItem); e != nil {
+	//			err = fmt.Errorf("AI预测模型-写入es失败, %v", e)
+	//			return
+	//		}
+	//	}
+	//}
+	//
+	//// 美国农业部
+	//usdaOb := new(dataSourceModel.BaseFromUsdaFasIndex)
+	//{
+	//	list, e := usdaOb.GetItemsByCondition(cond, pars, []string{}, "")
+	//	if e != nil {
+	//		err = fmt.Errorf("获取美国农业部失败, %v", e)
+	//		return
+	//	}
+	//	for _, v := range list {
+	//		indexItem := v.Format2SearchDataSource(v)
+	//		fmt.Printf("写入中:美国农业部-%d\n", indexItem.PrimaryId)
+	//		docId := fmt.Sprintf("%d-%d", indexItem.Source, indexItem.PrimaryId)
+	//		if e := elastic.EsAddOrEditDataSourceIndex(utils.EsDataSourceIndexName, docId, indexItem); e != nil {
+	//			err = fmt.Errorf("美国农业部-写入es失败, %v", e)
+	//			return
+	//		}
+	//	}
+	//}
+	//
+	//// 自有数据
+	//businessOb := new(dataSourceModel.BaseFromBusinessIndex)
+	//{
+	//	list, e := businessOb.GetItemsByCondition(cond, pars, []string{}, "")
+	//	if e != nil {
+	//		err = fmt.Errorf("获取自有数据失败, %v", e)
+	//		return
+	//	}
+	//	for _, v := range list {
+	//		indexItem := v.Format2SearchDataSource(v)
+	//		fmt.Printf("写入中:自有数据-%d\n", indexItem.PrimaryId)
+	//		docId := fmt.Sprintf("%d-%d", indexItem.Source, indexItem.PrimaryId)
+	//		if e := elastic.EsAddOrEditDataSourceIndex(utils.EsDataSourceIndexName, docId, indexItem); e != nil {
+	//			err = fmt.Errorf("自有数据-写入es失败, %v", e)
+	//			return
+	//		}
+	//	}
+	//}
 
 	fmt.Println("修复完成")
 }

+ 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
+}

Filskillnaden har hållts tillbaka eftersom den är för stor
+ 0 - 0
static/imgs/ai/article/【专题报告】关税来袭黑色怎么看.md


Filskillnaden har hållts tillbaka eftersom den är för stor
+ 0 - 0
static/imgs/ai/article/【开源宏观】财政支出力度如何12月财政数据点评.md


Filskillnaden har hållts tillbaka eftersom den är för stor
+ 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 // 权益报告库鉴权
 )
 

+ 19 - 1
utils/constants.go

@@ -237,6 +237,9 @@ const (
 	CACHE_ACCESS_TOKEN_LOGIN_NO_TRUST = "pc_eta_admin:login:no_trust:"        //管理后台登录(不可信登录态)
 	CACHE_ABNORMAL_LOGIN              = "pc_eta_admin:login:abnormal:"        //管理后台登录-异常登录
 	CACHE_LOGIN_ERR_PASS              = "pc_eta_admin:login:errPass:"         //管理后台登录-输入错误密码次数
+	MOBILE_CACHE_ABNORMAL_LOGIN       = "mobile_eta_admin:login:abnormal:"        //管理后台登录-异常登录
+	MOBILE_CACHE_LOGIN_ERR_PASS             = "mobile_eta_admin:login:errPass:"         //管理后台登录-输入错误密码次数
+
 	CACHE_FIND_PASS_VERIFY            = "pc_eta_admin:findPass:verify:"       //找回密码校验成功标记
 	CACHE_KEY_MYSTEEL_REFRESH         = "mysteel_chemical:refresh"            //钢联化工刷新
 	CACHE_KEY_DAYNEW_REFRESH          = "admin:day_new:refresh"               //每日资讯拉取企业微信聊天记录
@@ -267,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:"                      //报告图表数据授权
@@ -321,6 +327,8 @@ const (
 	CHART_SOURCE_BALANCE_EXCEL                   = 11 // 平衡表图表
 	CHART_SOURCE_RANGE_ANALYSIS                  = 12 // 	区间分析图表
 	CHART_SOURCE_TRADE_ANALYSIS_PROCESS          = 13 // 持仓分析-建仓过程图表
+	CHART_SOURCE_AI_PREDICT_MODEL_DAILY          = 14 // AI预测模型图表-日度预测
+	CHART_SOURCE_AI_PREDICT_MODEL_MONTHLY        = 15 // AI预测模型图表-月度预测
 )
 
 // 批量配置图表的位置来源
@@ -584,6 +592,7 @@ const (
 const (
 	FICC_ARTICLE_UPDATE_KEY = "FICC_ARTICLE_UPDATE_KEY" //权益报告通知给FICC这边的缓存key
 )
+
 // 图表分类设置精选资源分类
 const (
 	ChartClassifyIsSelected            = 1 // 图表分类设置精选资源分类
@@ -598,3 +607,12 @@ const (
 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报告)
+)

+ 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

@@ -20,6 +20,7 @@ type RedisClient interface {
 	LPush(key string, val interface{}) error
 	Brpop(key string, callback func([]byte))
 	BrpopWithTimeout(key string, timeout time.Duration, callback func([]byte)) error
+	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)

+ 11 - 0
utils/redis/cluster_redis.go

@@ -268,6 +268,17 @@ func (rc *ClusterRedisClient) BrpopWithTimeout(key string, timeout time.Duration
 	callback([]byte(values[1]))
 	return
 }
+// 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的过期时间

+ 11 - 0
utils/redis/standalone_redis.go

@@ -255,6 +255,17 @@ func (rc *StandaloneRedisClient) BrpopWithTimeout(key string, timeout time.Durat
 	callback([]byte(values[1]))
 	return
 }
+// 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的过期时间

+ 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
 		}
 	}
 	// 更新最后活跃时间

Vissa filer visades inte eftersom för många filer har ändrats