package ai import ( "errors" "fmt" "fonchain-fiee/pkg/common/qwen" "fonchain-fiee/pkg/service" "strings" "github.com/gin-gonic/gin" ) // VideoVLRequest 视频/图片理解请求参数 type VideoVLRequest struct { Videos []string `json:"videos"` // 视频URL列表 Images []string `json:"images"` // 图片URL列表 Text string `json:"text"` // 可选的文本提示 Model string `json:"model"` // 可选的模型名称,默认使用 qwen3-vl-plus } // AIVideoVL AI理解视频/图片接口 func AIVideoVL(ctx *gin.Context) { var req VideoVLRequest if err := ctx.ShouldBindJSON(&req); err != nil { 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 } Prompt := "请你详细描述视频和图片中的内容分别是什么" // 调用VL函数进行AI理解 result, err := qwen.VL(req.Videos, req.Images, Prompt, 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, errors.New("ai分析帖子内容失败")) } return } // 返回AI返回的数据 service.Success(ctx, result) } // contains 检查字符串是否包含子字符串(不区分大小写) 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"` // 生成的图片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(`基于以下视频和图片的内容描述: %s 请根据以下要求生成竞品报告: 1. 注意不要输出markdown格式来进行排版,请直接输出纯文本 2. 只需要回复竞品报告的内容,其他无关的内容不要输出 3. 输出的内容第一行不要标题,直接输出竞品报告的正文即可 4. 如果需要添加表格,请使用HTML表格格式,例如:
| 列1 | 列2 |
|---|---|
| 数据1 | 数据2 |