浏览代码

Merge branch 'feature/deepseek_rag_2.0' into debug

kobe6258 1 周之前
父节点
当前提交
763d43a6b4

+ 20 - 1
controllers/llm/llm_http/request.go

@@ -1,5 +1,7 @@
 package llm_http
 
+import "encoding/json"
+
 type LLMQuestionReq struct {
 	Question      string `description:"提问"`
 	KnowledgeBase string `description:"知识库"`
@@ -17,4 +19,21 @@ type UserChatRecordReq struct {
 	Content      string `json:"Content" description:"会话名称"`
 	ChatUserType string `json:"ChatUserType" description:"用户类型"`
 	SendTime     string `json:"SendTime" description:"发送时间"`
-}
+}
+
+type GenerateContentReq struct {
+	WechatArticleId int    `json:"WechatArticleId" description:"公众号Id"`
+	Promote         string `json:"Promote" description:"提示词"`
+}
+type SaveContentReq struct {
+	WechatArticleId int             `json:"WechatArticleId" description:"公众号Id"`
+	Title           string          `json:"Title" description:"标题"`
+	Promote         json.RawMessage `json:"Promote" description:"提示词"`
+	AigcContent     json.RawMessage `json:"AigcContent" description:"生成内容"`
+}
+type DeleteContentReq struct {
+	RecordId int
+}
+type ContentListReq struct {
+	WechatArticleId int
+}

+ 11 - 0
controllers/llm/llm_http/response.go

@@ -15,3 +15,14 @@ type UserChatResp struct {
 type UserChatAddResp struct {
 	SendTime string
 }
+
+type AIGCResp struct {
+	Promote Content
+	Answer Content
+}
+
+type Content struct {
+	Role     string
+	Content  string
+	SendTime string
+}

+ 308 - 0
controllers/llm/promote_controller.go

@@ -0,0 +1,308 @@
+package llm
+
+import (
+	"encoding/json"
+	"eta/eta_api/controllers"
+	"eta/eta_api/controllers/llm/llm_http"
+	"eta/eta_api/models"
+	"eta/eta_api/models/rag"
+	"eta/eta_api/services/llm/facade"
+	"eta/eta_api/utils"
+	"time"
+)
+
+type PromoteController struct {
+	controllers.BaseAuthController
+}
+
+// PromoteTrainRecordList @Title 获取聊天记录
+// @Description 获取聊天记录
+// @Success 101 {object} response.ListResp
+// @router /promote/train_list [get]
+func (pCtrl *PromoteController) PromoteTrainRecordList() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		pCtrl.Data["json"] = br
+		pCtrl.ServeJSON()
+	}()
+	sysUser := pCtrl.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+
+	pageSize, _ := pCtrl.GetInt("PageSize")
+	currentIndex, _ := pCtrl.GetInt("CurrentIndex")
+
+	//var total, startSize int
+	if pageSize <= 0 {
+		pageSize = utils.PageSize5
+	}
+	if currentIndex <= 0 {
+		currentIndex = 1
+	}
+	//startSize = paging.StartIndex(currentIndex, pageSize)
+	//page := paging.GetPaging(currentIndex, pageSize, total)
+	//total, err := rag.CountQuestionList()
+	//if err != nil {
+	//	br.Msg = "获取失败"
+	//	br.ErrMsg = "获取失败,Err:" + err.Error()
+	//	return
+	//}
+	//list, err := models.GetPptV2List(condition, pars, startSize, pageSize)
+	//if err != nil {
+	//	br.Msg = "获取失败"
+	//	br.ErrMsg = "获取失败,Err:" + err.Error()
+	//	return
+	//}
+	br.Data = nil
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取聊天记录成功"
+}
+
+// GenerateContent @Title 生成问答内容
+// @Description 生成问答内容
+// @Success 101 {object} response.ListResp
+// @router /promote/generate_content [post]
+func (pCtrl *PromoteController) GenerateContent() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		pCtrl.Data["json"] = br
+		pCtrl.ServeJSON()
+	}()
+	var gcReq llm_http.GenerateContentReq
+	err := json.Unmarshal(pCtrl.Ctx.Input.RequestBody, &gcReq)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	sysUser := pCtrl.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+	if gcReq.Promote == "" {
+		br.Msg = "提示词不能为空"
+		br.ErrMsg = "提示词不能为空"
+		return
+	}
+	if gcReq.WechatArticleId <= 0 {
+		br.Msg = "公众号文章编号非法"
+		br.ErrMsg = "公众号文章编号非法"
+		return
+	}
+	userContent := llm_http.Content{
+		Content:  gcReq.Promote,
+		Role:     "user",
+		SendTime: time.Now().Format(utils.FormatDateTime),
+	}
+	res, err := facade.AIGCBaseOnPromote(facade.AIGC{
+		Promote:   gcReq.Promote,
+		ArticleId: gcReq.WechatArticleId,
+	})
+	if err != nil {
+		br.Msg = "内容生成失败"
+		br.ErrMsg = "内容生成失败,Err:" + err.Error()
+		return
+	}
+	aiContent := llm_http.Content{
+		Content:  res.Answer,
+		Role:     "assistant",
+		SendTime: time.Now().Format(utils.FormatDateTime),
+	}
+	br.Data = llm_http.AIGCResp{
+		Promote: userContent,
+		Answer:  aiContent,
+	}
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "内容生成成功"
+}
+
+// SavePromoteContent @Title 保存问答内容
+// @Description 生成问答内容
+// @Success 101 {object} response.ListResp
+// @router /promote/save_content [post]
+func (pCtrl *PromoteController) SavePromoteContent() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		pCtrl.Data["json"] = br
+		pCtrl.ServeJSON()
+	}()
+	var gcReq llm_http.SaveContentReq
+	err := json.Unmarshal(pCtrl.Ctx.Input.RequestBody, &gcReq)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	sysUser := pCtrl.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+	if gcReq.Promote == nil {
+		br.Msg = "提示词内容不能为空"
+		br.ErrMsg = "提示词不能为空"
+		return
+	}
+	if gcReq.AigcContent == nil {
+		br.Msg = "回答内容不能为空"
+		br.ErrMsg = "提示词不能为空"
+		return
+	}
+	if gcReq.WechatArticleId <= 0 {
+		br.Msg = "公众号文章编号非法"
+		br.ErrMsg = "公众号文章编号非法"
+		return
+	}
+	var userContent, assistantContent llm_http.Content
+	parseErr := json.Unmarshal([]byte(gcReq.AigcContent), &assistantContent)
+	if parseErr != nil {
+		br.Msg = "内容参数解析异常!"
+		br.ErrMsg = "内容参数解析异,err" + parseErr.Error()
+		return
+	}
+	parseErr = json.Unmarshal([]byte(gcReq.Promote), &userContent)
+	if parseErr != nil {
+		br.Msg = "内容参数解析异常!"
+		br.ErrMsg = "内容参数解析异,err" + parseErr.Error()
+		return
+	}
+	var titile string
+	if gcReq.Title != "" {
+		titile = gcReq.Title
+	} else {
+		titile = userContent.Content
+	}
+	var userSendTime, assistantSendTime time.Time
+	if userContent.SendTime != "" {
+		userSendTime, parseErr = time.ParseInLocation(utils.FormatDateTime, userContent.SendTime, time.Local)
+		if parseErr != nil {
+			br.Msg = "用户发送时间解析异常!"
+			br.ErrMsg = "用户发送时间解析异常,err" + parseErr.Error()
+			return
+		}
+	} else {
+		br.Msg = "用户发送时间不能为空!"
+		br.ErrMsg = "用户发送时间不能为空"
+		return
+	}
+	if assistantContent.SendTime != "" {
+		assistantSendTime, parseErr = time.ParseInLocation(utils.FormatDateTime, assistantContent.SendTime, time.Local)
+		if parseErr != nil {
+			br.Msg = "AI生成时间解析异常!"
+			br.ErrMsg = "AI生成时间解析异常,err" + parseErr.Error()
+			return
+		}
+	} else {
+		br.Msg = "AI生成时间不能为空!"
+		br.ErrMsg = "AI生成时间不能为空"
+		return
+	}
+	saveContentReq := rag.PromoteTrainRecord{
+		WechatArticleId: gcReq.WechatArticleId,
+		Title:           titile,
+		AigcContent:     assistantContent.Content,
+		AigcSendTime:    assistantSendTime,
+		TemplatePromote: userContent.Content,
+		PromoteSendTime: userSendTime,
+		CreatedTime:     time.Now(),
+	}
+	err = saveContentReq.SaveContent()
+	if err != nil {
+		br.Msg = "保存内容失败"
+		br.ErrMsg = "保存内容失败,err:" + err.Error()
+		return
+	}
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "保存内容成功"
+}
+
+// DeletePromoteContent @Title 删除问答内容
+// @Description 生成问答内容
+// @Success 101 {object} response.ListResp
+// @router /promote/delete_content [post]
+func (pCtrl *PromoteController) DeletePromoteContent() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		pCtrl.Data["json"] = br
+		pCtrl.ServeJSON()
+	}()
+	var gcReq llm_http.DeleteContentReq
+	err := json.Unmarshal(pCtrl.Ctx.Input.RequestBody, &gcReq)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	sysUser := pCtrl.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+
+	if gcReq.RecordId <= 0 {
+		br.Msg = "记录编号非法"
+		br.ErrMsg = "记录编号非法"
+		return
+	}
+
+	err = rag.DeleteContent(gcReq.RecordId)
+	if err != nil {
+		br.Msg = "删除内容失败"
+		br.ErrMsg = "删除内容失败,err:" + err.Error()
+		return
+	}
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "删除内容成功"
+}
+
+// PromoteContentList @Title 删除问答内容
+// @Description 生成问答内容
+// @Success 101 {object} response.ListResp
+// @router /promote/content_list [get]
+func (pCtrl *PromoteController) PromoteContentList() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		pCtrl.Data["json"] = br
+		pCtrl.ServeJSON()
+	}()
+	wechatArticleId, _ := pCtrl.GetInt("WechatArticleId")
+	sysUser := pCtrl.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+
+	if wechatArticleId <= 0 {
+		br.Msg = "记录编号非法"
+		br.ErrMsg = "记录编号非法"
+		return
+	}
+
+	list, err := rag.GetRecordList(wechatArticleId)
+	if err != nil {
+		br.Msg = "查询列表失败"
+		br.ErrMsg = "查询列表失败,err:" + err.Error()
+		return
+	}
+	br.Data = list
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "查询列表成功"
+}

+ 2 - 0
controllers/llm/user_chat_controller.go

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

+ 37 - 0
models/rag/article_kb_mapping.go

@@ -0,0 +1,37 @@
+package rag
+
+import (
+	"eta/eta_api/global"
+	"eta/eta_api/utils"
+	"gorm.io/gorm/clause"
+	"time"
+)
+
+type ArticleKbMapping struct {
+	Id              int `gorm:"id;primaryKey"`
+	WechatArticleId int
+	KbId            string
+	CreatedTime     time.Time
+	UpdateTime      time.Time
+}
+
+func (a *ArticleKbMapping) TableName() string {
+	return "article_kb_mapping"
+}
+
+func GetArticleKbMapping(articleId int) (articleKbMapping *ArticleKbMapping, err error) {
+	err = global.DbMap[utils.DbNameAI].Where("wechat_article_id = ?", articleId).First(&articleKbMapping).Error
+	return
+}
+
+func CreateArticleKbMapping(articleKbMapping ArticleKbMapping) (err error) {
+	db := global.DbMap[utils.DbNameAI]
+	db.Clauses(
+		clause.OnConflict{
+			DoNothing: true,
+			Columns:   []clause.Column{{Name: "wechat_article_id"}},
+		},
+	)
+	err = global.DbMap[utils.DbNameAI].Create(&articleKbMapping).Error
+	return
+}

+ 65 - 0
models/rag/promote_train_record.go

@@ -0,0 +1,65 @@
+package rag
+
+import (
+	"eta/eta_api/global"
+	"eta/eta_api/utils"
+	"time"
+)
+
+type PromoteTrainRecord struct {
+	Id              int       `gorm:"id;primaryKey"`
+	Title           string    `gorm:"title"`
+	WechatArticleId int       `gorm:"wechat_article_id"`
+	TemplatePromote string    `gorm:"template_promote"`
+	PromoteSendTime time.Time `gorm:"promote_send_time"`
+	AigcContent     string    `gorm:"aigc_content"`
+	AigcSendTime    time.Time `gorm:"aigc_send_time"`
+	IsDeleted       bool      `gorm:"is_deleted"`
+	CreatedTime     time.Time `gorm:"created_time"`
+	UpdateTime      time.Time `gorm:"update_time"`
+}
+
+func (p *PromoteTrainRecord) ToView() PromoteTrainRecordView {
+	return PromoteTrainRecordView{
+		Id:              p.Id,
+		Title:           p.Title,
+		WechatArticleId: p.WechatArticleId,
+		TemplatePromote: p.TemplatePromote,
+		PromoteSendTime: p.PromoteSendTime.Format(utils.FormatDateTime),
+		AigcContent:     p.AigcContent,
+		AigcSendTime:    p.AigcSendTime.Format(utils.FormatDateTime),
+	}
+}
+
+type PromoteTrainRecordView struct {
+	Id              int
+	Title           string
+	WechatArticleId int
+	TemplatePromote string
+	PromoteSendTime string
+	AigcContent     string
+	AigcSendTime    string
+}
+
+func (p *PromoteTrainRecord) TableName() string {
+	return "promote_train_record"
+}
+
+func (p *PromoteTrainRecord) SaveContent() error {
+	return global.DbMap[utils.DbNameAI].Create(p).Error
+}
+func DeleteContent(id int) error {
+	return global.DbMap[utils.DbNameAI].Model(&PromoteTrainRecord{}).Where("id = ?", id).Update("is_deleted", true).Error
+}
+
+func GetRecordList(wechatArticleId int) (list []PromoteTrainRecordView, err error) {
+	var ormList []PromoteTrainRecord
+	err = global.DbMap[utils.DbNameAI].Model(&PromoteTrainRecord{}).Where("wechat_article_id = ? and is_deleted=?", wechatArticleId, false).Find(&ormList).Error
+	if err!=nil{
+		return
+	}
+	for _, item := range ormList {
+		list = append(list, item.ToView())
+	}
+	return
+}

+ 4 - 1
models/rag/wechat_article.go

@@ -155,7 +155,10 @@ func (m *WechatArticle) GetById(id int) (item *WechatArticle, err error) {
 
 	return
 }
-
+func GetArticleById(id int) (item *WechatArticle, err error) {
+	err = global.DbMap[utils.DbNameAI].Where(fmt.Sprintf("%s = ?", WechatArticleColumns.WechatArticleID), id).First(&item).Error
+	return
+}
 func (m *WechatArticle) GetByLink(link string) (item *WechatArticle, err error) {
 	err = global.DbMap[utils.DbNameAI].Where(fmt.Sprintf("%s = ?", WechatArticleColumns.Link), link).First(&item).Error
 

+ 45 - 0
routers/commentsRouter.go

@@ -8692,6 +8692,51 @@ func init() {
             Filters: nil,
             Params: nil})
 
+    beego.GlobalControllerRouter["eta/eta_api/controllers/llm:PromoteController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/llm:PromoteController"],
+        beego.ControllerComments{
+            Method: "PromoteContentList",
+            Router: `/promote/content_list`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/llm:PromoteController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/llm:PromoteController"],
+        beego.ControllerComments{
+            Method: "DeletePromoteContent",
+            Router: `/promote/delete_content`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/llm:PromoteController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/llm:PromoteController"],
+        beego.ControllerComments{
+            Method: "GenerateContent",
+            Router: `/promote/generate_content`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/llm:PromoteController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/llm:PromoteController"],
+        beego.ControllerComments{
+            Method: "SavePromoteContent",
+            Router: `/promote/save_content`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/llm:PromoteController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/llm:PromoteController"],
+        beego.ControllerComments{
+            Method: "PromoteTrainRecordList",
+            Router: `/promote/train_list`,
+            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: "Add",

+ 1 - 0
routers/router.go

@@ -78,6 +78,7 @@ func init() {
 				&llm.WechatPlatformController{},
 				&llm.QuestionController{},
 				&llm.AbstractController{},
+				&llm.PromoteController{},
 			),
 		),
 		web.NSNamespace("/banner",

+ 4 - 0
services/llm/facade/bus_response/eta_response.go

@@ -6,3 +6,7 @@ type SearchDocsEtaResponse struct {
 	Content string
 	Docs    []eta_llm_http.SearchDocsResponse
 }
+type AIGCEtaResponse struct {
+	Answer string   `json:"answer"`
+	Docs   []string `json:"docs"`
+}

+ 174 - 0
services/llm/facade/llm_service.go

@@ -1,13 +1,22 @@
 package facade
 
 import (
+	"encoding/json"
+	"errors"
+	"eta/eta_api/models/rag"
+	localService "eta/eta_api/services/llm"
 	"eta/eta_api/services/llm/facade/bus_response"
+	"eta/eta_api/utils"
 	"eta/eta_api/utils/llm"
 	"eta/eta_api/utils/llm/eta_llm/eta_llm_http"
 	"eta/eta_api/utils/ws"
 	"fmt"
 	"github.com/gorilla/websocket"
 	"github.com/rdlucklib/rdluck_tools/uuid"
+	"gorm.io/gorm"
+	"os"
+	"strings"
+	"time"
 )
 
 var (
@@ -38,7 +47,172 @@ func LLMKnowledgeBaseSearchDocs(search LLMKnowledgeSearch) (resp bus_response.Se
 	return
 }
 
+// AIGCBaseOnPromote aigc 生成内容
+func AIGCBaseOnPromote(aigc AIGC) (resp bus_response.AIGCEtaResponse, err error) {
+	mapping, queryErr := rag.GetArticleKbMapping(aigc.ArticleId)
+	if queryErr != nil && !errors.Is(queryErr, gorm.ErrRecordNotFound) {
+		utils.FileLog.Error("获取文章知识库信息失败,err: %v", queryErr)
+		err = fmt.Errorf("获取文章知识库信息失败,err: %v", queryErr)
+		return
+	} else {
+		var kbId string
+		var file *os.File
+		params := make(map[string]interface{})
+		if mapping.Id == 0 || mapping.KbId == "" {
+			article, fileErr := rag.GetArticleById(aigc.ArticleId)
+			if fileErr != nil {
+				// 找不到就处理失败
+				utils.FileLog.Error("公众号文章不存在")
+				err = fmt.Errorf("公众号文章不存在")
+				return
+			}
+			// 文章加入到知识库
+			path, fileErr := localService.CreateArticleFile(article)
+			if fileErr != nil {
+				utils.FileLog.Error("创建文章文件失败,err: %v", fileErr)
+				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
+			}
+			uploadResp, httpErr := llmService.UploadFileToTemplate([]*os.File{file}, params)
+			if httpErr != nil {
+				utils.FileLog.Error("上传文件失败,err:", err.Error())
+				err = fmt.Errorf("上传文件失败,err:%v", httpErr)
+				return
+			}
+			data := uploadResp.(eta_llm_http.UploadDocsResponse)
+			//保存映射关系到数据库
+			if data.Id == "" {
+				utils.FileLog.Error("上传文件失败,向量库Id获取失败")
+				err = fmt.Errorf("上传文件失败,向量库Id获取失败")
+				return
+			}
+			err = rag.CreateArticleKbMapping(rag.ArticleKbMapping{
+				WechatArticleId: aigc.ArticleId,
+				KbId:            data.Id,
+				CreatedTime:     time.Now(),
+			})
+			if err != nil {
+				utils.FileLog.Warn("创建文章知识库映射关系失败,err:", err.Error())
+			}
+			kbId = data.Id
+		} else {
+			kbId = mapping.KbId
+		}
+		//知识库对话
+		response, httpErr := llmService.FileChat(aigc.Promote, kbId, nil)
+		if httpErr != nil {
+			utils.FileLog.Error("内容生成失败,err:", err.Error())
+			err = fmt.Errorf("内容生成失败,err:%v", httpErr)
+			return
+		}
+		if !response.Success {
+			utils.FileLog.Error("内容生成失败,code:%v,msg:%v", response.Ret, response.Msg)
+			err = fmt.Errorf("内容生成失败,code:%v,msg:%v", response.Ret, response.Msg)
+			return
+		} else {
+			var dataStr string
+			// 按行分割输入
+			lines := strings.Split(string(response.Data), "\n")
+			// 遍历每一行,提取以 "data:" 开头的内容
+			for _, line := range lines {
+				if !strings.HasPrefix(line, ": ping") && strings.TrimSpace(line) != "" {
+					// 去掉 "data:" 前缀
+					dataStr += line
+				}
+			}
+			// 去除 "data: " 前缀
+			if strings.HasPrefix(dataStr, "data: ") {
+				dataStr = strings.TrimPrefix(dataStr, "data: ")
+				parseErr := json.Unmarshal([]byte(dataStr), &resp)
+				if parseErr != nil {
+					utils.FileLog.Error("内容生成失败,code:%v,msg:%v", parseErr)
+					err = fmt.Errorf("内容生成失败,err:%v", parseErr)
+					return
+				}
+				return
+			} else {
+				var baseResp eta_llm_http.RagBaseResponse
+				parseErr := json.Unmarshal([]byte(dataStr), &baseResp)
+				if parseErr != nil {
+					utils.FileLog.Error("内容生成失败,code:%v,msg:%v", parseErr)
+					err = fmt.Errorf("内容生成失败,err:%v", parseErr)
+					return
+				}
+				if baseResp.Code == 404 {
+					params["PrevId"] = kbId
+					_, putErr := llmService.UploadFileToTemplate([]*os.File{file}, params)
+					if putErr != nil {
+						utils.FileLog.Error("内容生成失败,err:", err.Error())
+						err = fmt.Errorf("内容生成失败,err:%v", httpErr)
+						return
+					}
+				} else {
+					utils.FileLog.Error("内容生成失败,code:%v", baseResp.Code)
+					err = fmt.Errorf("内容生成失败,code:%v", baseResp.Code)
+					return
+				}
+			}
+			gcResp, gcErr := llmService.FileChat(aigc.Promote, kbId, nil)
+			if gcErr != nil {
+				utils.FileLog.Error("内容生成失败,err:%v", gcErr.Error())
+				err = fmt.Errorf("内容生成失败,err:%v", gcErr)
+				return
+			}
+			if !gcResp.Success {
+				utils.FileLog.Error("内容生成失败,code:%v,msg:%v", gcResp.Ret, gcResp.Msg)
+				err = fmt.Errorf("内容生成失败,err:%v", gcResp.Msg)
+			}
+
+			var gcStr string
+			// 按行分割输入
+			lines = strings.Split(string(gcResp.Data), "\n")
+			// 遍历每一行,提取以 "data:" 开头的内容
+			for _, line := range lines {
+				if !strings.HasPrefix(line, ": ping") && strings.TrimSpace(line) != "" {
+					// 去掉 "data:" 前缀
+					gcStr += line
+				}
+			}
+			// 去除 "data: " 前缀
+			if strings.HasPrefix(dataStr, "data: ") {
+				dataStr = strings.TrimPrefix(dataStr, "data: ")
+				parseErr := json.Unmarshal([]byte(dataStr), &resp)
+				if parseErr != nil {
+					utils.FileLog.Error("内容生成失败,code:%v,msg:%v", parseErr)
+					err = fmt.Errorf("内容生成失败,err:%v", parseErr)
+					return
+				}
+			} else {
+				var baseResp eta_llm_http.RagBaseResponse
+				parseErr := json.Unmarshal([]byte(dataStr), &baseResp)
+				if parseErr != nil {
+					utils.FileLog.Error("内容生成失败,code:%v,msg:%v", parseErr)
+					err = fmt.Errorf("内容生成失败,err:%v", parseErr)
+					return
+				}
+				utils.FileLog.Error("内容生成失败,code:%v", baseResp.Code)
+				err = fmt.Errorf("内容生成失败,code:%v", baseResp.Code)
+				return
+			}
+		}
+	}
+	return
+}
+
 type LLMKnowledgeSearch struct {
 	Query             string `json:"Query"`
 	KnowledgeBaseName string `json:"KnowledgeBaseName"`
 }
+
+type AIGC struct {
+	Promote   string
+	ArticleId int
+}

+ 30 - 0
services/llm/promote_service.go

@@ -0,0 +1,30 @@
+package llm
+
+import (
+	"eta/eta_api/models/rag"
+	"eta/eta_api/utils"
+	"fmt"
+	"os"
+)
+
+func CreateArticleFile(item *rag.WechatArticle) (tmpFilePath string, err error) {
+	if item.TextContent == `` {
+		err = fmt.Errorf("生成文章原文文本失败,文章内容为空")
+		return
+	}
+	// 生成临时文件
+	uploadDir := utils.STATIC_DIR + "ai/article"
+	err = os.MkdirAll(uploadDir, utils.DIR_MOD)
+	if err != nil {
+		err = fmt.Errorf("存储目录创建失败,Err:" + err.Error())
+		return
+	}
+	fileName := utils.RemoveSpecialChars(item.Title) + `.md`
+	tmpFilePath = uploadDir + "/" + fileName
+	err = utils.SaveToFile(item.TextContent, tmpFilePath)
+	if err != nil {
+		err = fmt.Errorf("生成临时文件失败,Err:" + err.Error())
+		return
+	}
+	return
+}

文件差异内容过多而无法显示
+ 0 - 0
static/imgs/ai/article/【东海首席】黑色金属专题报告新一轮钢铁供给侧改革能否全面开启.md


+ 1 - 0
static/imgs/ai/article/【功能大盘点辞旧迎新来】中信期货研究邀您共启新程.md

@@ -0,0 +1 @@
+—  END  —来源:中信期货研究所

文件差异内容过多而无法显示
+ 0 - 0
static/imgs/ai/article/辑策略震荡时间久的两大变量进入验证阶段负反馈降本空间风险待破位信号AK3月第1周逻辑.md


+ 120 - 0
utils/llm/eta_llm/eta_llm_client.go

@@ -11,7 +11,9 @@ import (
 	"eta/eta_api/utils/llm/eta_llm/eta_llm_http"
 	"fmt"
 	"io"
+	"mime/multipart"
 	"net/http"
+	"os"
 	"strings"
 	"sync"
 )
@@ -29,6 +31,7 @@ const (
 	KNOWLEDGE_BASE_CHAT_API        = "/chat/kb_chat"
 	DOCUMENT_CHAT_API              = "/chat/file_chat"
 	KNOWLEDGE_BASE_SEARCH_DOCS_API = "/knowledge_base/search_docs"
+	UPLOAD_TEMP_DOCS_API           = "/knowledge_base/upload_temp_docs"
 )
 
 type ETALLMClient struct {
@@ -137,6 +140,82 @@ func (ds *ETALLMClient) KnowledgeBaseChat(query string, KnowledgeBaseName string
 	return ds.DoStreamPost(KNOWLEDGE_BASE_CHAT_API, body)
 }
 
+func (ds *ETALLMClient) FileChat(query string, KnowledgeId string, history []json.RawMessage) (resp eta_llm_http.BaseResponse, err error) {
+	ChatHistory := make([]eta_llm_http.HistoryContent, 0)
+	for _, historyItemStr := range history {
+		var historyItem eta_llm_http.HistoryContentWeb
+		parseErr := json.Unmarshal(historyItemStr, &historyItem)
+		if parseErr != nil {
+			continue
+		}
+		ChatHistory = append(ChatHistory, eta_llm_http.HistoryContent{
+			Content: historyItem.Content,
+			Role:    historyItem.Role,
+		})
+	}
+	kbReq := eta_llm_http.DocumentChatRequest{
+		ModelName:      ds.LlmModel,
+		Query:          query,
+		KnowledgeId:    KnowledgeId,
+		History:        ChatHistory,
+		TopK:           3,
+		ScoreThreshold: 0.5,
+		Stream:         false,
+		Temperature:    0.7,
+		MaxTokens:      0,
+		PromptName:     DEFALUT_PROMPT_NAME,
+	}
+	body, err := json.Marshal(kbReq)
+	if err != nil {
+		utils.FileLog.Error("内容生成失败,序列化请求参数失败,err", err.Error())
+		err = fmt.Errorf("内容生成失败,序列化请求参数失败,err:%v", err)
+		return
+	}
+	return ds.DoPost(DOCUMENT_CHAT_API, body)
+}
+
+func (ds *ETALLMClient) UploadFileToTemplate(files []*os.File, param map[string]interface{}) (data interface{}, err error) {
+	var pervId string
+	if value, ok := param["PrevId"]; ok {
+		pervId = value.(string)
+	}
+	docReq := eta_llm_http.UploadTempDocsRequest{
+		ChunkOverlap:   "150",
+		ChunkSize:      "750",
+		PrevId:         pervId,
+		ZhTitleEnhance: "false",
+	}
+	body, err := json.Marshal(docReq)
+	if err != nil {
+		return
+	}
+	resp, err := ds.DoFile(UPLOAD_TEMP_DOCS_API, body, files)
+	if !resp.Success {
+		err = errors.New(resp.Msg)
+		return
+	}
+	if resp.Data != nil {
+		var uploadDocsRes eta_llm_http.RagBaseResponse
+		err = json.Unmarshal(resp.Data, &uploadDocsRes)
+		if err != nil {
+			err = errors.New("上传临时文件失败,err:" + err.Error())
+			return
+		}
+		if uploadDocsRes.Code != 200 {
+			err = errors.New("上传临时文件失败,err:" + uploadDocsRes.Msg)
+			return
+		}
+		var uploadResult eta_llm_http.UploadDocsResponse
+		err = json.Unmarshal(uploadDocsRes.Data, &uploadResult)
+		if len(uploadResult.FiledFiles) > 0 {
+			utils.FileLog.Warn("上传临时文件失败:", uploadResult.FiledFiles)
+		}
+		data = uploadResult
+		return
+	}
+	return
+}
+
 func (ds *ETALLMClient) SearchKbDocs(query string, KnowledgeBaseName string) (content interface{}, err error) {
 	kbReq := eta_llm_http.KbSearchDocsRequest{
 		Query:             query,
@@ -183,6 +262,47 @@ func (ds *ETALLMClient) DoPost(apiUrl string, body []byte) (baseResp eta_llm_htt
 	}
 	return parseResponse(response)
 }
+
+func (ds *ETALLMClient) DoFile(apiUrl string, body []byte, files []*os.File) (baseResp eta_llm_http.BaseResponse, err error) {
+	var requestBody bytes.Buffer
+	writer := multipart.NewWriter(&requestBody)
+	// 添加文件到请求体
+	for _, file := range files {
+		filePath, err := writer.CreateFormFile("files", file.Name())
+		if err != nil {
+			return baseResp, fmt.Errorf("创建文件表单字段失败: %w", err)
+		}
+		_, err = io.Copy(filePath, file)
+		if err != nil {
+			return baseResp, fmt.Errorf("写入文件内容失败: %w", err)
+		}
+	}
+	var params = make(map[string]string)
+	err = json.Unmarshal(body, &params)
+	if err != nil {
+		return
+	}
+	// 添加其他参数到请求体
+	for key, value := range params {
+		err := writer.WriteField(key, value)
+		if err != nil {
+			return baseResp, fmt.Errorf("写入表单字段失败: %w", err)
+		}
+	}
+
+	// 关闭 writer,完成请求体的构建
+	err = writer.Close()
+	if err != nil {
+		return baseResp, fmt.Errorf("关闭 multipart writer 失败: %w", err)
+	}
+	request, err := http.NewRequest("POST", ds.BaseURL+apiUrl, &requestBody)
+	request.Header.Set("Content-Type", writer.FormDataContentType())
+	response, err := ds.HttpClient.Do(request)
+	if err != nil {
+		return
+	}
+	return parseResponse(response)
+}
 func (ds *ETALLMClient) DoStreamPost(apiUrl string, body []byte) (baseResp *http.Response, err error) {
 	requestReader := bytes.NewReader(body)
 	return ds.HttpClient.Post(ds.BaseURL+apiUrl, CONTENT_TYPE_JSON, requestReader)

+ 7 - 0
utils/llm/eta_llm/eta_llm_http/request.go

@@ -42,3 +42,10 @@ type KbSearchDocsRequest struct {
 	FileName          string      `json:"file_name"`
 	Metadata          interface{} `json:"metadata"`
 }
+
+type UploadTempDocsRequest struct {
+	PrevId         string `json:"prev_id"`
+	ChunkSize      string `json:"chunk_size"`
+	ChunkOverlap   string `json:"chunk_overlap"`
+	ZhTitleEnhance string `json:"zh_title_enhance"`
+}

+ 12 - 1
utils/llm/eta_llm/eta_llm_http/response.go

@@ -9,8 +9,10 @@ type BaseResponse struct {
 	Data    json.RawMessage `json:"data"`
 }
 type SteamResponse struct {
-	Data    ChunkResponse `json:"data"`
+	Data ChunkResponse `json:"data"`
 }
+
+
 // ChunkResponse 定义流式响应的结构体
 type ChunkResponse struct {
 	ID          string   `json:"id"`
@@ -24,6 +26,11 @@ type ChunkResponse struct {
 	Docs        []string `json:"docs"`
 	Choices     []Choice `json:"choices"`
 }
+type RagBaseResponse struct {
+	Data json.RawMessage `json:"data"`
+	Msg  string          `json:"msg"`
+	Code int             `json:"code"`
+}
 
 // Choice 定义选择的结构体
 type Choice struct {
@@ -61,3 +68,7 @@ type Metadata struct {
 	Source string `json:"source"`
 	Id     string `json:"id"`
 }
+type UploadDocsResponse struct {
+	Id         string            `json:"id"`
+	FiledFiles []json.RawMessage `json:"filed_files"`
+}

+ 4 - 0
utils/llm/llm_client.go

@@ -2,7 +2,9 @@ package llm
 
 import (
 	"encoding/json"
+	"eta/eta_api/utils/llm/eta_llm/eta_llm_http"
 	"net/http"
+	"os"
 	"time"
 )
 
@@ -24,4 +26,6 @@ type LLMService interface {
 	KnowledgeBaseChat(query string, KnowledgeBaseName string, history []json.RawMessage) (llmRes *http.Response, err error)
 	DocumentChat(query string, KnowledgeId string, history []json.RawMessage, stream bool) (llmRes *http.Response, err error)
 	SearchKbDocs(query string, KnowledgeBaseName string) (data interface{}, err error)
+	UploadFileToTemplate(files []*os.File, param map[string]interface{}) (data interface{}, err error)
+	FileChat(query string, KnowledgeId string, history []json.RawMessage) (resp eta_llm_http.BaseResponse, err error)
 }

部分文件因为文件数量过多而无法显示