feat:增加ai生成竞品报告接口

This commit is contained in:
cjy 2026-01-15 16:35:47 +08:00
parent a2d46c4463
commit d097e9a20e
2 changed files with 217 additions and 0 deletions

View File

@ -92,6 +92,7 @@ func MediaRouter(r *gin.RouterGroup) {
{
aiAuth.POST("one-text", serviceAI.OneText)
aiAuth.POST("more-text", serviceAI.MoreText)
aiAuth.POST("generate-report", serviceAI.AICompetitorReport)
}
social := noAuth.Group("social")

View File

@ -2,6 +2,7 @@ package ai
import (
"errors"
"fmt"
"fonchain-fiee/pkg/common/qwen"
"fonchain-fiee/pkg/service"
"strings"
@ -59,3 +60,218 @@ func AIVideoVL(ctx *gin.Context) {
func contains(s, substr string) bool {
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"` // 生成的图片URL1024*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)
}