diff --git a/VideoAnalysisCore/AICore/GPT/DeepSeek/DeepSeekGPTClient.cs b/VideoAnalysisCore/AICore/GPT/DeepSeek/DeepSeekGPTClient.cs index 4795118..4246464 100644 --- a/VideoAnalysisCore/AICore/GPT/DeepSeek/DeepSeekGPTClient.cs +++ b/VideoAnalysisCore/AICore/GPT/DeepSeek/DeepSeekGPTClient.cs @@ -42,7 +42,7 @@ namespace VideoAnalysisCore.AICore.GPT.ChatGPT /// 最大token 不设置默认最大值 16000/8000 /// /// - public override async Task ChatAsync(string task, string postMessages, string title, string model =null, int max_tokens = 16000) + public override async Task ChatAsync(string task, string postMessages, string title, string model =null, int max_tokens = 32000) { Message[] messageArr = [ new Message(postMessages,"user"), @@ -53,7 +53,7 @@ namespace VideoAnalysisCore.AICore.GPT.ChatGPT taskId = task, title = title, model = model ?? ChatGPTType.Deepseek_Reasoner, - max_tokens = model == ChatGPTType.Deepseek_Reasoner ? 16000 : max_tokens, + max_tokens = model == ChatGPTType.Deepseek_Reasoner ? 32000 : max_tokens, stream = true, temperature = 0.2f, messages = messageArr diff --git a/VideoAnalysisCore/AICore/GPT/Dto/QuestionRes.cs b/VideoAnalysisCore/AICore/GPT/Dto/QuestionRes.cs index 5b892aa..c6f8bf9 100644 --- a/VideoAnalysisCore/AICore/GPT/Dto/QuestionRes.cs +++ b/VideoAnalysisCore/AICore/GPT/Dto/QuestionRes.cs @@ -34,9 +34,10 @@ namespace VideoAnalysisCore.AICore.GPT.Dto } public class VideoKnowPointDto { + public float KnowPointWeight { get; set; } public string KnowPoint { get; set; } public string KnowPointId { get; set; } - public float KnowSourceTime { get; set; } + public string KnowSource { get; set; } } public class VideoKnowRes { @@ -64,6 +65,10 @@ namespace VideoAnalysisCore.AICore.GPT.Dto /// public virtual string? KnowPoint { get; set; } /// + /// 知识点权重 + /// + public virtual float? KnowPointWeight { get; set; } + /// /// 知识点ID /// public virtual string? KnowPointId { get; set; } diff --git a/VideoAnalysisCore/AICore/GPT/GPTClient.cs b/VideoAnalysisCore/AICore/GPT/GPTClient.cs index 67c7f45..f176707 100644 --- a/VideoAnalysisCore/AICore/GPT/GPTClient.cs +++ b/VideoAnalysisCore/AICore/GPT/GPTClient.cs @@ -13,6 +13,8 @@ using System.IO; using VideoAnalysisCore.AICore.GPT.ChatGPT; using System.Threading.Tasks; using System.Text.Json; +using FFmpeg.NET.Services; +using NetTaste; namespace VideoAnalysisCore.AICore.GPT { @@ -76,7 +78,7 @@ namespace VideoAnalysisCore.AICore.GPT { throw new Exception("请求GPT服务器失败次数过多"); } - goto PostJsonStream; + goto PostJsonStream; } using var stream = chatResp.Content.ReadAsStream(); using var reader = new StreamReader(stream, Encoding.UTF8); @@ -87,7 +89,7 @@ namespace VideoAnalysisCore.AICore.GPT var splitCount = "data:".Length; var maxLoop = 50 * 200; int threshold = 0; - var startTime= DateTime.Now; + var startTime = DateTime.Now; var endTime = startTime.AddHours(1.5); //最长分析分析时间1.5小时 或者重试读取 1w次 while (maxLoop > 0 && DateTime.Now < endTime) @@ -156,7 +158,7 @@ namespace VideoAnalysisCore.AICore.GPT /// 最大token 不设置默认最大值 16000/8000 /// /// - public async Task ChatAsync(ChatRequest chatRep) + public async Task ChatAsync(ChatRequest chatRep) where T:class,new() { var tryCount = 10; while (tryCount-- > 0) @@ -180,14 +182,20 @@ namespace VideoAnalysisCore.AICore.GPT chatResContent = chatResContent?.Replace("```", ""); chatResContent = chatResContent?.Replace("}{", "},{"); chatResContent = chatResContent?.Replace("}|{", "},{"); - chatResContent = chatResContent?.Trim(); + chatResContent = chatResContent?.Trim(); chatResContent = chatResContent?.ExtractJsonStrings()?.FirstOrDefault(); if (string.IsNullOrEmpty(chatResContent)) throw new Exception($"GPT返回结果无有效JSON =>{chatResp?.res}"); - var startsStr = typeof(T).IsArray ? "[" : "{"; - var endStr = typeof(T).IsArray ? "]" : "}"; + var startsStr = "{"; + var endStr = "}"; + var resT = new T(); + if (resT is Array || resT is System.Collections.IList || resT is System.Collections.IList) + { + startsStr = "["; + endStr = "]"; + } if (!chatResContent.StartsWith(startsStr)) chatResContent = startsStr + chatResContent; if (!chatResContent.EndsWith(endStr)) @@ -271,7 +279,7 @@ namespace VideoAnalysisCore.AICore.GPT /// /// public virtual Task ChatAsync(string task, string postMessages, string title, - string model = null, int max_tokens = 16000) + string model = null, int max_tokens = 16000) where T : class, new() { throw new Exception("需要实现"); } diff --git a/VideoAnalysisCore/AICore/GPT/GTP_Analysis_1.cs b/VideoAnalysisCore/AICore/GPT/GTP_Analysis_1.cs index f076cec..40fb88a 100644 --- a/VideoAnalysisCore/AICore/GPT/GTP_Analysis_1.cs +++ b/VideoAnalysisCore/AICore/GPT/GTP_Analysis_1.cs @@ -22,6 +22,7 @@ using System.Collections.Generic; using UserCenter.Model.Enum; using Dm.filter; using System.Text.RegularExpressions; +using System.Diagnostics; namespace VideoAnalysisCore.AICore.GPT.DeepSeek { @@ -81,28 +82,37 @@ namespace VideoAnalysisCore.AICore.GPT.DeepSeek questionRes = questionRes.Where(s => s != null) .OrderBy(s => s.StartTime).ToList(); var thems = questionRes.Adapt().ToJson(); - var checkResFormat1 = """[{"StartTime":开始秒(number),"TextbookSource":教材来源(string),"KnowPoints":[{"KnowSourceTime":"知识点来源的开始秒(number)","KnowPoint":知识点名称(string),"KnowPointId":知识点Id(string)}]}]"""; + var checkResFormat1 = """[{"StartTime":12.3,"TextbookSource":"课本","KnowPoints":[{"KnowPointWeight":0.5,"KnowSource":"开始时间(秒),匹配的原因","KnowPoint":"知识点名称","KnowPointId":"123"}]}]"""; var knowMessages = - $"我针对{taskInfo.Subject}课堂授课视频分析出了视频的授课阶段片段。\n" + - $"现在需要你通过每个片段的内容总结来分配正确的知识点(单个片段允许多个知识点)。\n" + - $"KnowSourceTime字段 需要提供出知识点被匹配的字幕内容最开始的时间来源。\n" + - $"TextbookSource字段 需要你分析出单个片段内讲述内容所属的教材范围 例如 (范围限定在 课本/试卷/挹青苑/其他)。\n" + - $"这是我的分段 {thems}。\n" + - $"课堂内容与{sections}章节相关\n" + - $"最后请确保分配的知识点是用户提供的,并且一定正确合理!\n" + - $"返回的片段数量与传入片段数量一致!(硬性条件)\n" + - $"输出内容只返回json格式({checkResFormat1})\n" + - $" 格式 (方法点Id|方法点名称) \n" + - $"提供的`知识点名称({knows})。\n"; + $""" + 角色:你是一位{taskInfo.Subject}学科教研老师。 + 任务:为每个【视频分段】分配对应的知识点(可多个),并补充来源信息。 + 字段说明: + - TextbookSource:该分段讲授内容所属教材来源,仅允许取值:课本/试卷/挹青苑/其他 + - KnowPoints:数组。每个元素代表一个知识点匹配结果 + - KnowPoint:知识点名称(必须来自我提供的列表) + - KnowPointId:知识点ID(必须与 KnowPoint 对应) + - KnowPointWeight:知识点在本分段的占比权重(最高为1,单个分段内的知识点权重之和必须等于1) + - KnowSource:该知识点在字幕中被首次提及的“开始秒,为什么匹配的原因(最多50字)” + 强制约束(硬性条件): + 1) 输出数组长度必须与输入分段长度一致,且顺序一致;不得新增/删除/合并分段。 + 2) 每个对象的 StartTime 必须与输入分段的 StartTime 完全一致(不得改动)。 + 3) KnowPoints 允许为空数组(表示未能匹配任何知识点),但不要输出空字符串或用逗号拼接。 + 4) 只输出 JSON(禁止 Markdown/解释/额外文本)。 + 输入分段:{thems} + 章节:{sections} + 知识点列表(Id|Name):{knows} + 输出格式示例:{checkResFormat1} + """; await redisManager.AddTaskLog(taskInfo.Id, "==>2.开始分析视频内容知识点"); - VideoKnowRes[] konwRes; + List konwRes; var knowOK = false; var chatClentArr = new GPTClient[] { chatGPTClient, geminiClient, deepSeekClient }; for (int i = 0; i < 3; i++) { - konwRes = await chatClentArr[i].ChatAsync(taskInfo.Id.ToString(), knowMessages, "知识点"); + konwRes = await chatClentArr[i].ChatAsync>(taskInfo.Id.ToString(), knowMessages, "知识点"); // 分析结果的片段数量与预期不匹配 - if (questionRes.Count() != konwRes.Length) continue; + if (questionRes.Count() != konwRes.Count()) continue; for (int xi = 0; xi < konwRes.Count(); xi++) { questionRes[xi].KnowPoints = konwRes[xi].KnowPoints; @@ -132,7 +142,9 @@ namespace VideoAnalysisCore.AICore.GPT.DeepSeek EndTime = s.EndTime, StageId = StageId, KnowPoint = x.KnowPoint, - KnowSourceTime = x.KnowSourceTime, + KnowPointWeight=x.KnowPointWeight, + TextbookSource = s.TextbookSource, + KnowSource = x.KnowSource, KnowPointId = knowDic[x.KnowPoint].ToString(), TagId = taskInfo.TagId, VideoTaskId = taskInfo.Id, @@ -197,7 +209,7 @@ namespace VideoAnalysisCore.AICore.GPT.DeepSeek var pptFormat = taskInfo.VideoType == AttachmentsInfoType.复习 ? "这堂课是习题课,所讲解内容几乎都是试题。" : string.Empty; - var checkResFormat = """{"Score":85.5,"MinusScore":"简洁的扣分原因","Suggestion":"改进建议"}"""; + var checkResFormat = """{"Score":65.5,"MinusScore":"简洁的扣分原因","Suggestion":"简洁的改进建议"}"""; var checkMessage = $""" 请你担任一位专业的视频内容分析教研老师,擅长评估视频内容的结构和逻辑流暢度。 @@ -209,8 +221,9 @@ namespace VideoAnalysisCore.AICore.GPT.DeepSeek 知识点分配:检查分段内的知识点是否与分段Conten有关联,知识点分配给这个分段是否合理(硬性指标)。 逻辑过渡:评估分段之间的过渡是否自然流畅,后一段是否是前一段内容的合理延伸或转折。 综合评分: - 请基于以上分析,提供一个0-100的综合得分(70分及格)。 - 详细说明打分理由,并逐条对应到上述评估维度。 + 请基于以上分析,提供一个0-100的综合得分(70分及格,打分一定要严谨,总分一定要准确)。 + MinusScore: 详细说明打分理由,并逐条对应到上述评估维度。 + Suggestion: 基于扣分原因提出针对分段方案的改进意见(请忽略掉分段没有结束时间的问题我会自己处理)。 输入数据格式说明: 分段方案: {thems} 字幕文本: 格式为说话人:开始秒:结束秒:内容|下一段字幕。完整内容为:{captions.Captions} @@ -257,8 +270,8 @@ namespace VideoAnalysisCore.AICore.GPT.DeepSeek 输出格式(仅 JSON):{resFormat} """; - var improved = await geminiClient.ChatAsync(taskInfo.Id.ToString(), message, "分段优化"); - if (improved is null || improved.Length != questionRes.Count()) + var improved = await geminiClient.ChatAsync>(taskInfo.Id.ToString(), message, "分段优化"); + if (improved is null || improved.Count() != questionRes.Count()) return null; return improved.OrderBy(s => s.StartTime ?? 0).ToList(); @@ -271,7 +284,7 @@ namespace VideoAnalysisCore.AICore.GPT.DeepSeek private async Task OptimizeSubtitles(VideoTask taskInfo, SenseVoiceRes[] captionsArr, string sections) { - if (!string.IsNullOrEmpty(taskInfo.CaptionsAI)) + if (!string.IsNullOrEmpty(taskInfo.CaptionsAI) && taskInfo.CaptionsAI!="[]") return JsonSerializer.Deserialize(taskInfo.CaptionsAI); var subject = taskInfo.Subject.ToString(); var newCaptionsList = new List(captionsArr.Length); @@ -306,10 +319,10 @@ namespace VideoAnalysisCore.AICore.GPT.DeepSeek $"待优化字幕内容:\n" + $"{nowCaptionStr}\n" + $"最终核对:请确保输出 JSON 中包含的字幕条数与输入的字幕条数完全对应。"; - string[]? resData = null; + List? resData = null; for (int i = 0; i < 3; i++) { - resData = await chatClentArr[i].ChatAsync(taskInfo.Id.ToString(), postMessages, "优化字幕", ChatGPTType.Deepseek_Chat, 8000); + resData = await chatClentArr[i].ChatAsync>(taskInfo.Id.ToString(), postMessages, "优化字幕", ChatGPTType.Deepseek_Chat, 8000); if (resData.Count() == cArr.Count()) break; else @@ -318,7 +331,7 @@ namespace VideoAnalysisCore.AICore.GPT.DeepSeek if (resData.Count() != cArr.Count()) { - resData = cStrArr.ToArray(); + resData = cStrArr.ToList(); await redisManager.AddTaskLog(taskInfo.Id, $"==>字幕优化 分段{s} AI结果数量不匹配 采用原始值"); } newCaptionsList.AddRange(resData.Select((text, i) => new SenseVoiceRes() @@ -366,13 +379,12 @@ namespace VideoAnalysisCore.AICore.GPT.DeepSeek $"完整的课堂标准流程包含以下5个阶段:课程引入/新知讲解/例题精讲/课堂练习/知识总结。\n" + reviewStr + $"讲解知识内容的阶段的细分程度到某个知识点的讲解/认识/例题/总结\n" + + $"不分析课堂作业相关的内容我已经预处理了\n" + $"初步划分阶段:{keyFrameStr}\n" + - $"\n" + - $"内容分析:对每个时间段,提取主要讲解内容:识别关键词(如“例题”“证明”“练习”“总结”)和内容结构。\n" + - $"判断阶段类型:如果内容以解题为主,归类为“例题精讲”;如果涉及新知识讲解,归类为“新知讲解”;以此类推。\n" + - $"内容总结:简述该阶段的核心讲解内容40~150字, 必须完全基于字幕文本可推断的信息,禁止捏造不存在的内容(硬性条件)。\n" + - $"阶段主题:基于内容总结,提炼一个恰当的主题(例如,“柯西不等式的基本应用”)。\n" + - $"输出要求:确保阶段划分合理、无重叠\n" + + $"Stage:判断阶段类型如果内容以解题为主,归类为“例题精讲”;如果涉及新知识讲解,归类为“新知讲解”;以此类推。\n" + + $"Content:简述单个阶段的核心讲解内容40~150字(如“例题”“证明”“练习”“总结”...), 必须完全基于字幕文本可推断的信息,禁止捏造不存在的内容(硬性条件)。\n" + + $"Theme:理解Content,提炼一个精确的主题(例如,“柯西不等式的基本应用”)。\n" + + $"输出要求:确保阶段划分合理、无重叠、\n" + $"作业布置阶段一般出现在末尾如果有" + $"输出格式要求:内容只返回json格式({resFormat})\n" + $"字幕格式(开始秒:内容|下一段字幕).以下是包含时间的视频字幕文本。\n" + @@ -424,7 +436,7 @@ namespace VideoAnalysisCore.AICore.GPT.DeepSeek 输出格式(仅 JSON):{resFormat} """; - var res = await deepSeekClient.ChatAsync(taskInfo.Id.ToString(), message, "作业布置识别"); + var res = await deepSeekClient.ChatAsync(taskInfo.Id.ToString(), message, "作业布置识别", ChatGPTType.Deepseek_Chat, 8000); if (res is null) return null; if (!string.Equals(res.Stage, "作业布置", StringComparison.OrdinalIgnoreCase)) @@ -575,7 +587,7 @@ namespace VideoAnalysisCore.AICore.GPT.DeepSeek $"输出内容只返回json格式为({resFormat})" + $"以下是试题内容" + $"`{sRes.Result.res.value}`"; - var resData = await deepSeekClient.ChatAsync(taskInfo.Id.ToString(), postMessages, "提取试题"); + var resData = await deepSeekClient.ChatAsync>(taskInfo.Id.ToString(), postMessages, "提取试题"); //var resData = await chatClient.ChatAsync(taskInfo.Id.ToString(), postMessages, "提取试题"); if (resData is null || resData.Count() == 0) break; @@ -704,8 +716,7 @@ namespace VideoAnalysisCore.AICore.GPT.DeepSeek if (questionRes is null) continue; //处理分段 知识点 List insertData = await GetVideoKnow(questionRes, taskInfo, sections, knowledgeInfos); - if (homework != null) - questionRes.Add(homework); + //校验结果质量 var checkRes = await VerifySpanQuality(questionRes, taskInfo, captions, sections, Course_Id); @@ -715,35 +726,44 @@ namespace VideoAnalysisCore.AICore.GPT.DeepSeek // 质量复检 if (checkRes != null) { - var improved = await ImproveSpanBySuggestion(questionRes, taskInfo, captions, sections, checkRes.Suggestion); + var improved = await ImproveSpanBySuggestion(questionRes, taskInfo, captions, sections, "扣分原因 {checkRes.MinusScore} \n 改进意见 {checkRes.Suggestion}"); if (improved != null) { var improvedCheck = await VerifySpanQuality(improved, taskInfo, captions, sections, Course_Id); await redisManager.AddTaskLog(taskInfo.Id, $"==>优化后复检得分=>{improvedCheck.Score}"); await redisManager.AddTaskLog(taskInfo.Id, $"==>优化后扣分原因 {improvedCheck.MinusScore}"); - if (improvedCheck != null && improvedCheck.Score >= 90) + if (improvedCheck != null && improvedCheck.Score >= 90 && improvedCheck.Score > checkRes.Score) { questionRes = improved; + if (homework != null && (!questionRes.Any(s => s.Stage == StageEnum.作业布置.ToString()))) + questionRes.Add(homework); + insertData = await GetVideoKnow(questionRes, taskInfo, sections, knowledgeInfos); await videoKonwPointDB.DeleteAsync(s => s.VideoTaskId == taskInfo.Id); await videoTaskStageDB.DeleteAsync(s => s.VideoTaskId == taskInfo.Id); var tStage = insertData.GroupBy(s => s.StageId).Select(s => new VideoTaskStage { - Id=s.Key, + Id = s.Key, TagId = s.First().TagId, - CloudSchoolId =s.First().CloudSchoolId, - StartTime = s.Last().StartTime, - EndTime =s.Last().EndTime, - KnowSourceTime=s.Last().KnowSourceTime, - Stage=s.First().Stage, - Theme=s.First().Theme, - VideoTaskId=taskInfo.Id, + CloudSchoolId = s.First().CloudSchoolId, + StartTime = s.First().StartTime, + EndTime = s.First().EndTime, + Content = s.First().Content, + TextbookSource = s.First().TextbookSource, + Stage = s.First().Stage, + Theme = s.First().Theme, + VideoTaskId = taskInfo.Id, }).ToArray(); await videoTaskStageDB.InsertRangeAsync(tStage); await videoKonwPointDB.InsertRangeAsync(insertData); break; } + else + { + await redisManager.AddTaskLog(taskInfo.Id, $"==>优化之后的得分降低/得分过低"); + continue; + } } } diff --git a/VideoAnalysisCore/AICore/GPT/Gemini/GeminiGPTClient.cs b/VideoAnalysisCore/AICore/GPT/Gemini/GeminiGPTClient.cs index 0bcd8cd..36fdac4 100644 --- a/VideoAnalysisCore/AICore/GPT/Gemini/GeminiGPTClient.cs +++ b/VideoAnalysisCore/AICore/GPT/Gemini/GeminiGPTClient.cs @@ -58,7 +58,7 @@ namespace VideoAnalysisCore.AICore.GPT.ChatGPT stream = true, temperature = 0.2f, messages = messageArr, - max_completion_tokens=16000, + max_completion_tokens= 12288, }; chatReq.modalities = null; diff --git a/VideoAnalysisCore/AICore/SherpaOnnx/SenseVoice.cs b/VideoAnalysisCore/AICore/SherpaOnnx/SenseVoice.cs index 163a25a..99cc18c 100644 --- a/VideoAnalysisCore/AICore/SherpaOnnx/SenseVoice.cs +++ b/VideoAnalysisCore/AICore/SherpaOnnx/SenseVoice.cs @@ -143,7 +143,7 @@ namespace VideoAnalysisCore.AICore.SherpaOnnx throw new Exception("task 音频路径未找到"); if (OR is null) Init(); serviceProvider.GetRequiredService() - .TaskHandle(new WaveReader(filePath), null, SoundHandle, SherpaVadVersion.silero_vad_v5); + .TaskHandle(new WaveReader(filePath), task, SoundHandle, SherpaVadVersion.silero_vad_v5); return Task.CompletedTask; } diff --git a/VideoAnalysisCore/Controllers/LJZK_Controller.cs b/VideoAnalysisCore/Controllers/LJZK_Controller.cs index cf99215..156d155 100644 --- a/VideoAnalysisCore/Controllers/LJZK_Controller.cs +++ b/VideoAnalysisCore/Controllers/LJZK_Controller.cs @@ -35,6 +35,7 @@ namespace VideoAnalysisCore.Controllers private readonly Repository videoTaskDB; private readonly Repository courseInfoDB; private readonly Repository videoKonwPointDB; + private readonly Repository videoTaskStageDB; private readonly Repository nodePackageInfoDB; private readonly Repository videoQuestionDB; private readonly Repository videoQuestionKonwDB; @@ -42,7 +43,7 @@ namespace VideoAnalysisCore.Controllers public LJZK_Controller(IMapper mp, Repository nodesubscriptionDB, Repository videoTaskDB = null, Repository videoKonwPointDB = null - , Repository nodePackageInfoDB = null, Repository videoQuestionDB = null, Repository videoQuestionKonwDB = null, Repository courseInfoDB = null, RedisManager redisManager = null) + , Repository nodePackageInfoDB = null, Repository videoQuestionDB = null, Repository videoQuestionKonwDB = null, Repository courseInfoDB = null, RedisManager redisManager = null, Repository videoTaskStageDB = null) { this.mp = mp; this.nodesubscriptionDB = nodesubscriptionDB; @@ -53,6 +54,7 @@ namespace VideoAnalysisCore.Controllers this.videoQuestionKonwDB = videoQuestionKonwDB; this.courseInfoDB = courseInfoDB; this.redisManager = redisManager; + this.videoTaskStageDB = videoTaskStageDB; } @@ -201,27 +203,49 @@ namespace VideoAnalysisCore.Controllers .ToArrayAsync(); if (konwArr is null || konwArr.Length == 0) return BadRequest("Чֶ"); + + + var stageArr = await videoTaskStageDB.AsQueryable() + .Where(s => s.VideoTaskId == task.Id) + .ToArrayAsync(); + var videoKnowDic = konwArr + .GroupBy(s => s.StageId) + .ToDictionary(s => s.Key); + var videoKnows = stageArr + .Select(s => new VideoKnowRes() + { + Content = s.Content, + StartTime = s.StartTime, + EndTime = s.EndTime, + Theme = s.Theme, + StageId = s.Id, + KnowPoint = videoKnowDic.ContainsKey(s.Id) + ? string.Join(',', videoKnowDic[s.Id].Select(x => x.KnowPoint)) + : string.Empty + }).ToArray(); + var res = new TaskKnowRes() { TagId = task.TagId, Status = task.LastEnum, VideoTaskId = task.Id, - KnowBlockArr = konwArr - .GroupBy(s => s.StartTime) + KnowBlockArr = stageArr .Select(s => new TaskKnowBlock() { - Id = s.First().Id, - Content = s.First().Content, - StartTime = s.First().StartTime, - StageId = s.First().StageId, - EndTime = s.First().EndTime, - Theme = s.First().Theme, - Know = s.Select(x => new TaskKnowInfo() - { - Id = x.Id, - KnowPoint = x.KnowPoint, - KnowPointId = x.KnowPointId - }).ToArray() + Id = s.Id, + Content = s.Content, + StartTime = s.StartTime, + StageId = s.Id, + EndTime = s.EndTime, + Theme = s.Theme, + Know = videoKnowDic.ContainsKey(s.Id) + ? videoKnowDic[s.Id]?.Select(x => new TaskKnowInfo() + { + Id = x.Id, + KnowPoint = x.KnowPoint, + KnowPointId = x.KnowPointId, + })?.ToArray() + : null }).ToArray() }; if (task.VideoType == AttachmentsInfoType.ϰ) diff --git a/VideoAnalysisCore/Controllers/VideoTaskController.cs b/VideoAnalysisCore/Controllers/VideoTaskController.cs index 614c2e5..ce4b059 100644 --- a/VideoAnalysisCore/Controllers/VideoTaskController.cs +++ b/VideoAnalysisCore/Controllers/VideoTaskController.cs @@ -33,9 +33,11 @@ namespace VideoAnalysisCore.Controllers readonly Repository baseService; readonly Repository videoQuestionDB; readonly Repository videoKonwPointDB; + readonly Repository videoTaskStageDB; readonly Repository videoQuestionKonwDB; readonly Repository taskLogDB; + readonly RedisManager redisManager; public readonly SenseVoice senseVoice; public readonly FunASRNano funASRNano; @@ -43,7 +45,7 @@ namespace VideoAnalysisCore.Controllers private readonly IMapper mp; public VideoTaskController(Repository baseService, RedisManager redisManager, Repository videoQuestionDB, - Repository videoQuestionKonwDB, Repository videoKonwPointDB, SenseVoice senseVoice, IMapper mp, Repository taskLogDB, FunASRNano funASRNano) : base(baseService) + Repository videoQuestionKonwDB, Repository videoKonwPointDB, SenseVoice senseVoice, IMapper mp, Repository taskLogDB, FunASRNano funASRNano, Repository videoTaskStageDB) : base(baseService) { this.baseService = baseService; this.redisManager = redisManager; @@ -54,6 +56,7 @@ namespace VideoAnalysisCore.Controllers this.mp = mp; this.taskLogDB = taskLogDB; this.funASRNano = funASRNano; + this.videoTaskStageDB = videoTaskStageDB; } @@ -370,17 +373,23 @@ namespace VideoAnalysisCore.Controllers var konwArr = await videoKonwPointDB.AsQueryable() .Where(s => s.VideoTaskId == nowTask.Id) .ToArrayAsync(); - - var videoKnows = konwArr - .GroupBy(s => s.StartTime) + var stageArr = await videoTaskStageDB.AsQueryable() + .Where(s => s.VideoTaskId == nowTask.Id) + .ToArrayAsync(); + var videoKnowDic = konwArr + .GroupBy(s => s.StageId) + .ToDictionary(s => s.Key); + var videoKnows = stageArr .Select(s => new VideoKnowRes() { - Content = s.First().Content, - StartTime = s.First().StartTime, - EndTime = s.First().EndTime, - Theme = s.First().Theme, - StageId = s.First().StageId, - KnowPoint = string.Join(',', s.Select(x => x.KnowPoint)) + Content = s.Content, + StartTime = s.StartTime, + EndTime = s.EndTime, + Theme = s.Theme, + StageId = s.Id, + KnowPoint = videoKnowDic.ContainsKey(s.Id) + ? string.Join(',', videoKnowDic[s.Id].Select(x => x.KnowPoint)) + : string.Empty }).ToArray(); if (nowTask.VideoType == AttachmentsInfoType.复习) { diff --git a/VideoAnalysisCore/Model/VideoKonwPoint.cs b/VideoAnalysisCore/Model/VideoKonwPoint.cs index c675e46..b386bb5 100644 --- a/VideoAnalysisCore/Model/VideoKonwPoint.cs +++ b/VideoAnalysisCore/Model/VideoKonwPoint.cs @@ -65,9 +65,13 @@ namespace VideoAnalysisCore.Model /// public string? KnowPointId { get; set; } /// - /// 知识点来源 视频秒 + /// 知识点占比权重 /// - public float KnowSourceTime { get; set; } + public float? KnowPointWeight { get; set; } + /// + /// 知识点来源 + /// + public string KnowSource { get; set; } /// /// 内容总结[不写入数据库] /// @@ -84,5 +88,11 @@ namespace VideoAnalysisCore.Model /// [SugarColumn(IsNullable = true)] public long? CloudSchoolId { get; set; } + /// + /// 教材来源 + /// 课本/试卷/挹青苑 ... + /// + [SugarColumn(IsIgnore = true)] + public virtual string? TextbookSource { get; set; } } } diff --git a/VideoAnalysisCore/Model/VideoTaskStage.cs b/VideoAnalysisCore/Model/VideoTaskStage.cs index 4bfb48f..8ed5c89 100644 --- a/VideoAnalysisCore/Model/VideoTaskStage.cs +++ b/VideoAnalysisCore/Model/VideoTaskStage.cs @@ -54,9 +54,9 @@ namespace VideoAnalysisCore.Model /// public string? Theme { get; set; } /// - /// 知识点来源 视频秒 + /// 知识点来源 视频秒,来源原因 /// - public float KnowSourceTime { get; set; } + public string Content { get; set; } /// /// 课程阶段 /// @@ -68,5 +68,11 @@ namespace VideoAnalysisCore.Model /// [SugarColumn(IsNullable = true)] public long? CloudSchoolId { get; set; } + /// + /// 教材来源 + /// 课本/试卷/挹青苑 ... + /// + [SugarColumn(IsNullable = true)] + public virtual string? TextbookSource { get; set; } } }