From ece9a964f9ac314e4a50e9e21dad8fcf96eeb4b7 Mon Sep 17 00:00:00 2001 From: cjy Date: Mon, 2 Mar 2026 17:07:17 +0800 Subject: [PATCH 1/2] =?UTF-8?q?feat:=20ai=E7=94=9F=E6=88=90=E4=B8=80?= =?UTF-8?q?=E4=B8=AA=E6=95=B0=E6=8D=AE=E5=88=86=E6=9E=90=E6=AD=A3=E6=96=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pkg/service/cast/analysis.go | 93 ++++++++++++++++++++++++++++++++++++ 1 file changed, 93 insertions(+) diff --git a/pkg/service/cast/analysis.go b/pkg/service/cast/analysis.go index f9eb76e0..ae13a631 100644 --- a/pkg/service/cast/analysis.go +++ b/pkg/service/cast/analysis.go @@ -10,9 +10,11 @@ import ( "fonchain-fiee/api/bundle" "fonchain-fiee/api/cast" "fonchain-fiee/pkg/cache" + "fonchain-fiee/pkg/common/qwen" "fonchain-fiee/pkg/e" modelCast "fonchain-fiee/pkg/model/cast" "fonchain-fiee/pkg/model/login" + modelQwen "fonchain-fiee/pkg/model/qwen" "fonchain-fiee/pkg/service" "fonchain-fiee/pkg/service/bundle/common" "fonchain-fiee/pkg/utils" @@ -607,10 +609,101 @@ func ArtistMetricsSeries(ctx *gin.Context) { respMap["videoCount"] = videoCount respMap["imageCount"] = imageCount + // 调用 AI 分析数据 + analysis, err := generateArtistMetricsAnalysis(resp) + if err != nil { + zap.L().Error("生成艺人指标分析失败", zap.Error(err)) + // AI 分析失败不影响主业务,返回空字符串 + respMap["analysis"] = "" + } else { + respMap["analysis"] = analysis + } + service.Success(ctx, respMap) return } +// generateArtistMetricsAnalysis 调用 AI 分析艺人指标数据 +func generateArtistMetricsAnalysis(resp *cast.ArtistMetricsSeriesResp) (string, error) { + if resp == nil { + return "", errors.New("数据为空") + } + + // 构建分析用的数据摘要 + var dataSummary strings.Builder + dataSummary.WriteString("艺人各平台数据表现如下:\n") + + // 粉丝数 + if resp.FansSeries != nil { + dataSummary.WriteString(fmt.Sprintf("粉丝数总数: %d (周期类型: %d, 开始日期: %d, 结束日期: %d)\n", + resp.FansSeries.FansCount, resp.FansSeries.PeriodType, resp.FansSeries.StartDate, resp.FansSeries.EndDate)) + } + + // 播放量 + if resp.ViewsSeries != nil { + dataSummary.WriteString(fmt.Sprintf("播放量总数: %d (周期类型: %d, 开始日期: %d, 结束日期: %d)\n", + resp.ViewsSeries.ViewsCount, resp.ViewsSeries.PeriodType, resp.ViewsSeries.StartDate, resp.ViewsSeries.EndDate)) + } + + // 点赞数 + if resp.LikesSeries != nil { + dataSummary.WriteString(fmt.Sprintf("点赞数总数: %d (周期类型: %d, 开始日期: %d, 结束日期: %d)\n", + resp.LikesSeries.LikesCount, resp.LikesSeries.PeriodType, resp.LikesSeries.StartDate, resp.LikesSeries.EndDate)) + } + + // 评论数 + if resp.CommentsSeries != nil { + dataSummary.WriteString(fmt.Sprintf("评论数总数: %d (周期类型: %d, 开始日期: %d, 结束日期: %d)\n", + resp.CommentsSeries.CommentsCount, resp.CommentsSeries.PeriodType, resp.CommentsSeries.StartDate, resp.CommentsSeries.EndDate)) + } + + // 分享数 + if resp.SharesSeries != nil { + dataSummary.WriteString(fmt.Sprintf("分享数总数: %d (周期类型: %d, 开始日期: %d, 结束日期: %d)\n", + resp.SharesSeries.SharesCount, resp.SharesSeries.PeriodType, resp.SharesSeries.StartDate, resp.SharesSeries.EndDate)) + } + + // 最佳发布时间 + if resp.BestPostTime != nil { + dataSummary.WriteString(fmt.Sprintf("最佳发布时间: %s\n", resp.BestPostTime.DetailJSON)) + } + + // 最活跃日期 + if resp.MostActiveDay != nil { + dataSummary.WriteString(fmt.Sprintf("最活跃日期: %s\n", resp.MostActiveDay.DetailJSON)) + } + + // 构建 prompt + prompt := fmt.Sprintf(`根据以下艺人各平台运营数据分析运营的各平台数据表现,结合相关数据简要表述优点,字数在200字内:\n%s`, dataSummary.String()) + + // 调用 AI + req := modelQwen.ChatRequest{ + Model: "qwen-plus", + Messages: []modelQwen.Message{ + { + Role: "user", + Content: []modelQwen.Content{ + { + Type: "text", + Text: prompt, + }, + }, + }, + }, + } + + respAI, err := qwen.Chat(req) + if err != nil { + return "", err + } + + if respAI == nil || len(respAI.Choices) == 0 { + return "", errors.New("AI 返回结果为空") + } + + return respAI.Choices[0].Message.Content, nil +} + // ArtistMetricsDailyWindow 艺人指标日窗口 func ArtistMetricsDailyWindow(ctx *gin.Context) { var req *cast.ArtistMetricsDailyWindowReq From 7cc03c02fa4be6f2dba0687739876928f6c19ed8 Mon Sep 17 00:00:00 2001 From: cjy Date: Tue, 3 Mar 2026 09:17:50 +0800 Subject: [PATCH 2/2] =?UTF-8?q?fix:=20=E4=BC=98=E5=8C=96=E6=8F=90=E7=A4=BA?= =?UTF-8?q?=E8=AF=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pkg/service/ai/video_vl.go | 30 +++++++++++++++++++----------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/pkg/service/ai/video_vl.go b/pkg/service/ai/video_vl.go index 8fa0eeef..b8441d1d 100644 --- a/pkg/service/ai/video_vl.go +++ b/pkg/service/ai/video_vl.go @@ -100,21 +100,21 @@ type DataPerformanceData struct { // CompetitorReportResponse 竞品报告响应数据 type CompetitorReportResponse struct { - ImageURL string `json:"image_url,omitempty"` // 生成的图片URL(1024*1024),非必须返回 - Text string `json:"text,omitempty"` // 竞品报告文本内容,非必须返回 - JsonData *CompetitorReportData `json:"json_data,omitempty"` // 竞品报告JSON数据 + ImageURL string `json:"image_url,omitempty"` // 生成的图片URL(1024*1024),非必须返回 + Text string `json:"text,omitempty"` // 竞品报告文本内容,非必须返回 + JsonData *CompetitorReportData `json:"json_data,omitempty"` // 竞品报告JSON数据 } // CompetitorReportJSON AI返回的JSON结构 type CompetitorReportJSON struct { HighlightAnalysis HighlightAnalysis `json:"highlight_analysis"` - DataPerformance DataPerformance `json:"data_performance_analysis"` - OverallSummary string `json:"overall_summary_and_optimization"` + DataPerformance DataPerformance `json:"data_performance_analysis"` + OverallSummary string `json:"overall_summary_and_optimization"` } type HighlightAnalysis struct { - Summary string `json:"summary"` - Points Points `json:"points"` + Summary string `json:"summary"` + Points Points `json:"points"` } type Points struct { @@ -127,9 +127,9 @@ type Points struct { } type DataPerformance struct { - Views string `json:"views"` - Completion string `json:"completion_rate,omitempty"` - Engagement string `json:"engagement"` + Views string `json:"views"` + Completion string `json:"completion_rate,omitempty"` + Engagement string `json:"engagement"` } // convertJSONToText 将 JSON 转换为纯文本格式 @@ -321,9 +321,14 @@ func AICompetitorReport(ctx *gin.Context) { isVideo := len(req.Videos) > 0 // 构建文本生成提示词:理解内容 + 用户要求(JSON格式) + // 重要:必须明确要求使用英文标点符号,确保返回的JSON符合规范 + // 重要:必须基于内容给出分析性回复,即使没有提供具体数据 var textPrompt string if isVideo { textPrompt = fmt.Sprintf(`你必须严格输出以下JSON格式,不要输出任何其他内容。输出必须以 { 开头并以 } 结束。 +重要提示: +1. 所有字符串值必须使用英文标点符号,包括英文逗号, 英文句号. 英文冒号: 英文引号" 等。禁止使用中文标点符号。 +2. 即使没有提供具体数据,也要基于视频和图片内容给出分析性回复。禁止回复"未提供数据"、"暂无数据"等类似内容,而应该根据内容分析数据表现(如根据时长分析完播率潜力、根据内容质量分析互动潜力等)。 基于以下视频和图片的内容描述: %s @@ -334,7 +339,10 @@ func AICompetitorReport(ctx *gin.Context) { JSON结构是固定的,请将内容填充到对应的value中,禁止修改key,禁止添加额外字段,禁止输出任何说明文字: {"highlight_analysis":{"summary":"[78字以内的概述]","points":{"theme":"[标题亮点,最多60字]","narrative":"[题材亮点,最多60字]","content":"[内容亮点,最多60字]","copywriting":"[文案亮点,最多60字]","data":"[数据亮点,最多60字]","music":"[配乐亮点,仅视频,最多60字]"}},"data_performance_analysis":{"views":"[浏览量表现,最多60字]","completion_rate":"[完播率表现,仅视频,最多60字]","engagement":"[点赞/分享/评论表现,最多60字]"},"overall_summary_and_optimization":"[整体总结及可优化建议,最多300字]"}`, vlContent, req.TextPrompt) } else { - textPrompt = fmt.Sprintf(`你必须严格输出以下JSON格式,不要输出任何其他内容。输出必须以 { 开头并以 } 结束。 + textPrompt = fmt.Sprintf(`你必须严格输出以下JSON格式,不要输出任何其他内容。输出开头并以 }必须以 { 结束。 +重要提示: +1. 所有字符串值必须使用英文标点符号,包括英文逗号, 英文句号. 英文冒号: 英文引号" 等。禁止使用中文标点符号。 +2. 即使没有提供具体数据,也要基于视频和图片内容给出分析性回复。禁止回复"未提供数据"、"暂无数据"等类似内容,而应该根据内容分析数据表现(如根据内容质量分析互动潜力等)。 基于以下视频和图片的内容描述: %s