feat:增加ai生成竞品报告接口
This commit is contained in:
parent
a2d46c4463
commit
d097e9a20e
@ -92,6 +92,7 @@ func MediaRouter(r *gin.RouterGroup) {
|
|||||||
{
|
{
|
||||||
aiAuth.POST("one-text", serviceAI.OneText)
|
aiAuth.POST("one-text", serviceAI.OneText)
|
||||||
aiAuth.POST("more-text", serviceAI.MoreText)
|
aiAuth.POST("more-text", serviceAI.MoreText)
|
||||||
|
aiAuth.POST("generate-report", serviceAI.AICompetitorReport)
|
||||||
}
|
}
|
||||||
|
|
||||||
social := noAuth.Group("social")
|
social := noAuth.Group("social")
|
||||||
|
|||||||
@ -2,6 +2,7 @@ package ai
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"fonchain-fiee/pkg/common/qwen"
|
"fonchain-fiee/pkg/common/qwen"
|
||||||
"fonchain-fiee/pkg/service"
|
"fonchain-fiee/pkg/service"
|
||||||
"strings"
|
"strings"
|
||||||
@ -59,3 +60,218 @@ func AIVideoVL(ctx *gin.Context) {
|
|||||||
func contains(s, substr string) bool {
|
func contains(s, substr string) bool {
|
||||||
return strings.Contains(strings.ToLower(s), strings.ToLower(substr))
|
return strings.Contains(strings.ToLower(s), strings.ToLower(substr))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CompetitorReportRequest 竞品报告请求参数
|
||||||
|
type CompetitorReportRequest struct {
|
||||||
|
Videos []string `json:"videos"` // 视频URL列表
|
||||||
|
Images []string `json:"images"` // 图片URL列表
|
||||||
|
TextPrompt string `json:"textPrompt"` // 竞品报告要求文本
|
||||||
|
ImagePrompt string `json:"imagePrompt"` // 图片URL
|
||||||
|
Model string `json:"model"` // 可选的模型名称,默认使用 qwen3-vl-plus
|
||||||
|
}
|
||||||
|
|
||||||
|
// CompetitorReportResponse 竞品报告响应数据
|
||||||
|
type CompetitorReportResponse struct {
|
||||||
|
ImageURL string `json:"image_url,omitempty"` // 生成的图片URL(1024*1024),非必须返回
|
||||||
|
Text string `json:"text,omitempty"` // 竞品报告文本内容,非必须返回
|
||||||
|
}
|
||||||
|
|
||||||
|
// AICompetitorReport 生成竞品报告接口
|
||||||
|
func AICompetitorReport(ctx *gin.Context) {
|
||||||
|
var req CompetitorReportRequest
|
||||||
|
if err := ctx.ShouldBindJSON(&req); err != nil {
|
||||||
|
service.Error(ctx, errors.New("参数错误"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.TextPrompt == "" && req.ImagePrompt == "" {
|
||||||
|
service.Error(ctx, errors.New("文本和图片提示词不能同时为空"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查是否至少提供了视频或图片
|
||||||
|
if len(req.Videos) == 0 && len(req.Images) == 0 {
|
||||||
|
service.Error(ctx, errors.New("至少需要提供一个视频或图片"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(req.Videos) > 1 {
|
||||||
|
service.Error(ctx, errors.New("当前只能选一个视频"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 第一步:调用AI理解视频/图片内容
|
||||||
|
vlPrompt := "请你详细描述视频和图片中的内容分别是什么"
|
||||||
|
vlResult, err := qwen.VL(req.Videos, req.Images, vlPrompt, req.Model)
|
||||||
|
if err != nil {
|
||||||
|
// 检查是否是文件下载超时错误(内容过大)
|
||||||
|
errMsg := err.Error()
|
||||||
|
if contains(errMsg, "Download multimodal file timed out") || contains(errMsg, "timed out") {
|
||||||
|
service.Error(ctx, errors.New("报错内容过大,请重新选择"))
|
||||||
|
} else {
|
||||||
|
service.Error(ctx, fmt.Errorf("AI理解视频图片失败: %v", err))
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取理解后的内容
|
||||||
|
if len(vlResult.Choices) == 0 {
|
||||||
|
service.Error(ctx, errors.New("AI理解返回结果为空"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
vlContent := vlResult.Choices[0].Message.Content
|
||||||
|
|
||||||
|
// 定义协程结果结构
|
||||||
|
type textResult struct {
|
||||||
|
text string
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
type imageResult struct {
|
||||||
|
imageURL string
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
// 根据 TextPrompt 和 ImagePrompt 是否为空决定启动哪些协程
|
||||||
|
needText := req.TextPrompt != ""
|
||||||
|
needImage := req.ImagePrompt != ""
|
||||||
|
|
||||||
|
var textChan chan textResult
|
||||||
|
var imageChan chan imageResult
|
||||||
|
|
||||||
|
// 如果需要生成文本,启动文本生成协程
|
||||||
|
if needText {
|
||||||
|
textChan = make(chan textResult, 1)
|
||||||
|
go func() {
|
||||||
|
// 构建文本生成提示词:理解内容 + 用户要求
|
||||||
|
textPrompt := fmt.Sprintf("基于以下视频和图片的内容描述:\n%s\n\n请根据以下要求生成竞品报告:\n%s", vlContent, req.TextPrompt)
|
||||||
|
|
||||||
|
chatReq, err := buildChatRequest(textPrompt, nil)
|
||||||
|
if err != nil {
|
||||||
|
textChan <- textResult{err: err}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
chatResp, err := qwen.Chat(*chatReq)
|
||||||
|
if err != nil {
|
||||||
|
textChan <- textResult{err: err}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(chatResp.Choices) == 0 {
|
||||||
|
textChan <- textResult{err: errors.New("文本生成返回结果为空")}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
textChan <- textResult{text: chatResp.Choices[0].Message.Content}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果需要生成图片,启动图片生成协程
|
||||||
|
if needImage {
|
||||||
|
imageChan = make(chan imageResult, 1)
|
||||||
|
go func() {
|
||||||
|
// 先请求聊天获取图片提示词
|
||||||
|
imagePromptText := fmt.Sprintf("基于以下视频和图片的内容描述:\n%s\n\n请根据以下要求生成竞品报告图片的提示词:\n%s", vlContent, req.ImagePrompt)
|
||||||
|
|
||||||
|
chatReq, err := buildChatRequest(imagePromptText, nil)
|
||||||
|
if err != nil {
|
||||||
|
imageChan <- imageResult{err: err}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
chatResp, err := qwen.Chat(*chatReq)
|
||||||
|
if err != nil {
|
||||||
|
imageChan <- imageResult{err: err}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(chatResp.Choices) == 0 {
|
||||||
|
imageChan <- imageResult{err: errors.New("图片提示词生成返回结果为空")}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
imagePrompt := chatResp.Choices[0].Message.Content
|
||||||
|
|
||||||
|
// 生成图片(1024*1024),基于理解后的内容使用文生图
|
||||||
|
size := "1024*1024"
|
||||||
|
resultTask, err := qwen.GenerateTextImage(imagePrompt, size)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
imageChan <- imageResult{err: err}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if resultTask.Code != "" {
|
||||||
|
imageChan <- imageResult{err: errors.New("文生图失败: " + resultTask.Message)}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 等待图片生成完成
|
||||||
|
result, err := qwen.ImgTaskResult(resultTask.Output.TaskID)
|
||||||
|
if err != nil {
|
||||||
|
imageChan <- imageResult{err: err}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if result == nil || len(result.Output.Results) == 0 {
|
||||||
|
imageChan <- imageResult{err: errors.New("图片生成失败")}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 返回第一张图片的URL
|
||||||
|
imageChan <- imageResult{imageURL: result.Output.Results[0].URL}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 等待所有启动的协程完成
|
||||||
|
var textRes textResult
|
||||||
|
var imageRes imageResult
|
||||||
|
|
||||||
|
// 根据实际启动的协程数量等待结果
|
||||||
|
if needText && needImage {
|
||||||
|
// 两个协程都启动了,使用循环等待两个都完成
|
||||||
|
completed := 0
|
||||||
|
for completed < 2 {
|
||||||
|
select {
|
||||||
|
case textRes = <-textChan:
|
||||||
|
completed++
|
||||||
|
case imageRes = <-imageChan:
|
||||||
|
completed++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if needText {
|
||||||
|
// 只启动文本生成协程
|
||||||
|
textRes = <-textChan
|
||||||
|
} else if needImage {
|
||||||
|
// 只启动图片生成协程
|
||||||
|
imageRes = <-imageChan
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理文本结果(如果生成了文本)
|
||||||
|
if needText {
|
||||||
|
if textRes.err != nil {
|
||||||
|
service.Error(ctx, fmt.Errorf("生成竞品报告文本失败: %v", textRes.err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理图片结果(如果生成了图片)
|
||||||
|
if needImage {
|
||||||
|
if imageRes.err != nil {
|
||||||
|
service.Error(ctx, fmt.Errorf("生成竞品报告图片失败: %v", imageRes.err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 返回结果(只返回实际生成的内容)
|
||||||
|
result := CompetitorReportResponse{}
|
||||||
|
if needText {
|
||||||
|
result.Text = textRes.text
|
||||||
|
}
|
||||||
|
if needImage {
|
||||||
|
result.ImageURL = imageRes.imageURL
|
||||||
|
}
|
||||||
|
|
||||||
|
service.Success(ctx, result)
|
||||||
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user