From b4b02cfbd386f46304518f20adfe7a22bb594a94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B0=8F=E8=82=A5=E7=BE=8A?= <1048382248@qq.com> Date: Thu, 15 Jan 2026 18:14:15 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=20=E8=AF=84=E9=89=B4?= =?UTF-8?q?=E7=BB=93=E6=9E=9C=E4=BC=98=E5=8C=96=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../AICore/GPT/Dto/QuestionRes.cs | 15 + .../AICore/GPT/GTP_Analysis_1.cs | 275 +++++++++++++++--- .../AICore/GPT/Gemini/GeminiGPTClient.cs | 5 +- VideoAnalysisCore/Model/VideoKonwPoint.cs | 9 +- VideoAnalysisCore/Model/VideoTaskStage.cs | 72 +++++ 5 files changed, 337 insertions(+), 39 deletions(-) create mode 100644 VideoAnalysisCore/Model/VideoTaskStage.cs diff --git a/VideoAnalysisCore/AICore/GPT/Dto/QuestionRes.cs b/VideoAnalysisCore/AICore/GPT/Dto/QuestionRes.cs index 50bd345..5b892aa 100644 --- a/VideoAnalysisCore/AICore/GPT/Dto/QuestionRes.cs +++ b/VideoAnalysisCore/AICore/GPT/Dto/QuestionRes.cs @@ -32,6 +32,12 @@ namespace VideoAnalysisCore.AICore.GPT.Dto public virtual string? Content { get; set; } } + public class VideoKnowPointDto + { + public string KnowPoint { get; set; } + public string KnowPointId { get; set; } + public float KnowSourceTime { get; set; } + } public class VideoKnowRes { /// @@ -50,6 +56,10 @@ namespace VideoAnalysisCore.AICore.GPT.Dto public virtual long? StageId { get; set; } public virtual VideoQuestionShowDto[]? QuestionArr { get; set; } /// + /// 知识点列表 + /// + public virtual VideoKnowPointDto[]? KnowPoints { get; set; } + /// /// 知识点 /// public virtual string? KnowPoint { get; set; } @@ -65,6 +75,11 @@ namespace VideoAnalysisCore.AICore.GPT.Dto /// 内容总结 /// public virtual string? Content { get; set; } + /// + /// 教材来源 + /// 课本/试卷/挹青苑 ... + /// + public virtual string? TextbookSource { get; set; } } public class FileNameInfo diff --git a/VideoAnalysisCore/AICore/GPT/GTP_Analysis_1.cs b/VideoAnalysisCore/AICore/GPT/GTP_Analysis_1.cs index 8499e0e..f076cec 100644 --- a/VideoAnalysisCore/AICore/GPT/GTP_Analysis_1.cs +++ b/VideoAnalysisCore/AICore/GPT/GTP_Analysis_1.cs @@ -37,6 +37,7 @@ namespace VideoAnalysisCore.AICore.GPT.DeepSeek private readonly RedisManager redisManager; private readonly Repository videoTaskDB; private readonly Repository videoKonwPointDB; + private readonly Repository videoTaskStageDB; private readonly Repository videoQuestionDB; private readonly Repository videoQuestionKonwDB; private readonly Repository knowledgeInfoDB; @@ -49,7 +50,7 @@ namespace VideoAnalysisCore.AICore.GPT.DeepSeek /// public GTP_Analysis_1(DeepSeekGPTClient moonshotClient, Repository criteria, Repository videoTaskDB, Repository knowledgeInfoDB, Repository videoKonwPointDB, SimpLetexClient simpLetexClient, - Repository videoQuestionDB, OssClient ossClient, Repository videoQuestionKonwDB, RedisManager redisManager, BestAIClient chatGPTClient, GeminiGPTClient geminiClient) + Repository videoQuestionDB, OssClient ossClient, Repository videoQuestionKonwDB, RedisManager redisManager, BestAIClient chatGPTClient, GeminiGPTClient geminiClient, Repository videoTaskStageDB) { deepSeekClient = moonshotClient; criteriaDB = criteria; @@ -63,12 +64,13 @@ namespace VideoAnalysisCore.AICore.GPT.DeepSeek this.redisManager = redisManager; this.chatGPTClient = chatGPTClient; this.geminiClient = geminiClient; + this.videoTaskStageDB = videoTaskStageDB; } /// /// 获取分段内容对应的章节知识点 /// /// - private async Task> GetVideoKnow(VideoKnowRes[] questionRes, VideoTask taskInfo, + private async Task> GetVideoKnow(List questionRes, VideoTask taskInfo, string sections, List knowledgeInfos) { var knows = string.Join(',', knowledgeInfos.Select(s => s.Id + "|" + s.Name)); @@ -77,30 +79,35 @@ namespace VideoAnalysisCore.AICore.GPT.DeepSeek .GroupBy(s => s.Name) .ToDictionary(s => s.First().Name, s => s.First().Id); questionRes = questionRes.Where(s => s != null) - .OrderBy(s => s.StartTime).ToArray(); + .OrderBy(s => s.StartTime).ToList(); var thems = questionRes.Adapt().ToJson(); - var checkResFormat1 = """[{"StartTime":开始秒(number),"KnowPoint":知识点名称(string),"KnowPointId":知识点Id(string)}]"""; + var checkResFormat1 = """[{"StartTime":开始秒(number),"TextbookSource":教材来源(string),"KnowPoints":[{"KnowSourceTime":"知识点来源的开始秒(number)","KnowPoint":知识点名称(string),"KnowPointId":知识点Id(string)}]}]"""; var knowMessages = $"我针对{taskInfo.Subject}课堂授课视频分析出了视频的授课阶段片段。\n" + - $"现在需要你通过每个片段的内容总结来分配正确的知识点(单个片段允许多个知识点用逗号','分割)。\n" + + $"现在需要你通过每个片段的内容总结来分配正确的知识点(单个片段允许多个知识点)。\n" + + $"KnowSourceTime字段 需要提供出知识点被匹配的字幕内容最开始的时间来源。\n" + + $"TextbookSource字段 需要你分析出单个片段内讲述内容所属的教材范围 例如 (范围限定在 课本/试卷/挹青苑/其他)。\n" + $"这是我的分段 {thems}。\n" + $"课堂内容与{sections}章节相关\n" + $"最后请确保分配的知识点是用户提供的,并且一定正确合理!\n" + - $"返回的片段数量与传入片段数量一致(硬性条件)!\n" + + $"返回的片段数量与传入片段数量一致!(硬性条件)\n" + $"输出内容只返回json格式({checkResFormat1})\n" + $" 格式 (方法点Id|方法点名称) \n" + $"提供的`知识点名称({knows})。\n"; await redisManager.AddTaskLog(taskInfo.Id, "==>2.开始分析视频内容知识点"); VideoKnowRes[] konwRes; var knowOK = false; - var chatClentArr = new GPTClient[] { chatGPTClient, geminiClient,deepSeekClient }; + var chatClentArr = new GPTClient[] { chatGPTClient, geminiClient, deepSeekClient }; for (int i = 0; i < 3; i++) { konwRes = await chatClentArr[i].ChatAsync(taskInfo.Id.ToString(), knowMessages, "知识点"); // 分析结果的片段数量与预期不匹配 - if (questionRes.Length != konwRes.Length) continue; + if (questionRes.Count() != konwRes.Length) continue; for (int xi = 0; xi < konwRes.Count(); xi++) - questionRes[xi].KnowPoint = konwRes[xi].KnowPoint; + { + questionRes[xi].KnowPoints = konwRes[xi].KnowPoints; + questionRes[xi].TextbookSource = konwRes[xi].TextbookSource; + } knowOK = true; break; } @@ -111,13 +118,12 @@ namespace VideoAnalysisCore.AICore.GPT.DeepSeek } return questionRes - .Where(s => !string.IsNullOrEmpty(s.KnowPoint)) + .Where(s => s.KnowPoints != null && s.KnowPoints.Length > 0) .SelectMany( s => { - var ks = s.KnowPoint.Split(",").Distinct(); var StageId = Yitter.IdGenerator.YitIdHelper.NextId(); - return ks.Where(x => knowDic.ContainsKey(x)) + return s.KnowPoints.Where(x => knowDic.ContainsKey(x.KnowPoint)) .Select(x => new VideoKonwPoint() { Content = s.Content, @@ -125,8 +131,9 @@ namespace VideoAnalysisCore.AICore.GPT.DeepSeek StartTime = s.StartTime, EndTime = s.EndTime, StageId = StageId, - KnowPoint = x, - KnowPointId = knowDic[x].ToString(), + KnowPoint = x.KnowPoint, + KnowSourceTime = x.KnowSourceTime, + KnowPointId = knowDic[x.KnowPoint].ToString(), TagId = taskInfo.TagId, VideoTaskId = taskInfo.Id, CloudSchoolId = taskInfo.CloudSchoolId, @@ -183,26 +190,24 @@ namespace VideoAnalysisCore.AICore.GPT.DeepSeek /// 检查AI切片结果质量 /// /// - private async Task VerifySpanQuality(VideoKnowRes[] questionRes, VideoTask taskInfo, TotalCaptionsDto captions, string sections, long course_Id) + private async Task VerifySpanQuality(List questionRes, VideoTask taskInfo, TotalCaptionsDto captions, string sections, long course_Id) { //校验结果质量 var thems = questionRes.Adapt().ToJson(); var pptFormat = taskInfo.VideoType == AttachmentsInfoType.复习 ? "这堂课是习题课,所讲解内容几乎都是试题。" : string.Empty; - var checkResFormat = """{"Score":打分(number),"MinusScore":简洁的扣分原因(string)",Suggestion":改进建议(string)""";//,"Data":优化后的分段(array)}"""; + var checkResFormat = """{"Score":85.5,"MinusScore":"简洁的扣分原因","Suggestion":"改进建议"}"""; var checkMessage = $""" 请你担任一位专业的视频内容分析教研老师,擅长评估视频内容的结构和逻辑流暢度。 核心任务: 请根据我提供的【视频分段方案】【完整字幕文本】,对该分段方案进行严谨评估。 - 评估维度与具体标准: - 时间间隔检查(硬性指标) - 严格检查每个分段与下一个分段的开始时间之间的间隔是否大于40秒。 + 补充上下文:{pptFormat} + 本节课所属章节:{sections} 内容结构与主题合理性: - 分段方案的准确度:评估单个分段内的Content是否与对应时间段内的字幕文本内容匹配(硬性指标,不能存在捏造的内容) - 主题凝聚力:评估单个分段内的内容是否围绕一个清晰、统一的主题展开,是否存在主题混杂或跳跃的情况。 + 分段准确性:评估单个分段内的课堂字幕内容与分段的Theme/Conten匹配、是否存在错误,捏造的情况(硬性指标)。 + 知识点分配:检查分段内的知识点是否与分段Conten有关联,知识点分配给这个分段是否合理(硬性指标)。 逻辑过渡:评估分段之间的过渡是否自然流畅,后一段是否是前一段内容的合理延伸或转折。 - 知识点分配:检查分段内的知识点是否与分段内容有关联,知识点分配给这个分段是否合理(这项很重要)。 综合评分: 请基于以上分析,提供一个0-100的综合得分(70分及格)。 详细说明打分理由,并逐条对应到上述评估维度。 @@ -214,6 +219,50 @@ namespace VideoAnalysisCore.AICore.GPT.DeepSeek """; return await chatGPTClient.ChatAsync(taskInfo.Id.ToString(), checkMessage, "结果检查"); } + /// + /// 采用改进意见 + /// + /// + /// + /// + /// + /// + /// + private async Task?> ImproveSpanBySuggestion(List questionRes, VideoTask taskInfo, TotalCaptionsDto captions, string sections, string suggestion) + { + if (string.IsNullOrWhiteSpace(suggestion)) + return null; + + var thems = questionRes.ToJson(); + var pptFormat = taskInfo.VideoType == AttachmentsInfoType.复习 + ? "这堂课是习题课,所讲解内容几乎都是试题。" + : string.Empty; + var resFormat = """[{"StartTime":0.0,"EndTime":12.3,"Theme":"主题","Content":"内容总结","KnowPoint":"知识点(可为空)"}]"""; + var message = + $""" + 请你担任一位专业的视频内容分析教研老师,擅长根据评估意见修复视频分段方案。 + 目标:在不改变片段数量的前提下,基于【改进意见】对【分段方案】做最小必要修改,使其更符合课堂内容的自然结构。 + 补充上下文:{pptFormat} + 本节课所属章节:{sections} + 强制约束: + 1) 输出数组长度必须与输入分段方案一致(硬性条件)。 + 2) StartTime 必须严格递增。 + 3) Content 必须完全基于字幕文本可推断的信息,禁止捏造不存在的内容(硬性条件)。 + 4) 忽略对于每段的结束时间的优化处理。 + 5) 不要新增字段,不要输出解释性文字,只输出 JSON。 + 输入数据: + 分段方案:{thems} + 改进意见:{suggestion} + 字幕文本:格式为说话人:开始秒:结束秒:内容|下一段字幕。完整内容为:{captions.Captions} + 输出格式(仅 JSON):{resFormat} + """; + + var improved = await geminiClient.ChatAsync(taskInfo.Id.ToString(), message, "分段优化"); + if (improved is null || improved.Length != questionRes.Count()) + return null; + + return improved.OrderBy(s => s.StartTime ?? 0).ToList(); + } /// /// 优化字幕 @@ -230,7 +279,7 @@ namespace VideoAnalysisCore.AICore.GPT.DeepSeek var totalCount = captionsArr.Length / spanCount + 1; await redisManager.AddTaskLog(taskInfo.Id, $"==>字幕优化"); - var chatClentArr = new GPTClient[] { deepSeekClient,chatGPTClient, geminiClient }; + var chatClentArr = new GPTClient[] { deepSeekClient, chatGPTClient, geminiClient }; await Parallel.ForAsync(0, totalCount, new ParallelOptions() { MaxDegreeOfParallelism = 1 }, async (s, c) => @@ -293,7 +342,7 @@ namespace VideoAnalysisCore.AICore.GPT.DeepSeek /// 视频AI分析字幕 /// /// - private async Task Analytics(VideoTask taskInfo, + private async Task> Analytics(VideoTask taskInfo, TotalCaptionsDto captions, string sections) { var tryCount = 10; @@ -306,32 +355,31 @@ namespace VideoAnalysisCore.AICore.GPT.DeepSeek ? $"请分析授课中字幕描述的知识内容,然后基于视频整体知识点讲解提炼出不同的阶段以便对老师上课内容切片提取为知识库,所以请确保阶段的内容准确性" : $"授课中老师的PPT在这些时间段内进行了切换{taskInfo.PPTKeyFrame},理应这些时间段内的讲述内容也发生了变化,请你基于PPT变化时间点结合字幕描述的知识内容提炼出不同的切片。" + $"每个阶段的起始和结束应接近这些时间点(例如,以时间点为中心,扩展至内容自然过渡处)。"; - var resFormat = """[{"StartTime":开始秒(number),"EndTime":结束秒(number),"Stage":阶段(string),"Theme":主题(string),"Content":内容总结(string)}]"""; + var resFormat = """[{"StartTime":开始秒(number),"EndTime":结束秒(number),"Stage":阶段(string),"Theme":阶段主题(string),"Content":内容总结(string)}]"""; var reviewStr = taskInfo?.VideoType == AttachmentsInfoType.复习 ? $"但本堂课是习题课,所以大部分阶段是不同的例题讲解内容。\n" : string.Empty; var postMessages = string.Empty; postMessages = $"请通过视频字幕内容分析出视频中课堂的授课知识点切片\n" + - $"阶段的细分程度到某个知识点的讲解/认识/例题/总结\n" + $"课堂内容与{taskInfo.Subject}学科下的{sections}章节相关。\n" + $"完整的课堂标准流程包含以下5个阶段:课程引入/新知讲解/例题精讲/课堂练习/知识总结。\n" + reviewStr + + $"讲解知识内容的阶段的细分程度到某个知识点的讲解/认识/例题/总结\n" + $"初步划分阶段:{keyFrameStr}\n" + $"\n" + $"内容分析:对每个时间段,提取主要讲解内容:识别关键词(如“例题”“证明”“练习”“总结”)和内容结构。\n" + $"判断阶段类型:如果内容以解题为主,归类为“例题精讲”;如果涉及新知识讲解,归类为“新知讲解”;以此类推。\n" + - $"内容总结:简述该阶段的核心讲解内容70~200字,确保内容与阶段时间内授课内容符合。\n" + + $"内容总结:简述该阶段的核心讲解内容40~150字, 必须完全基于字幕文本可推断的信息,禁止捏造不存在的内容(硬性条件)。\n" + $"阶段主题:基于内容总结,提炼一个恰当的主题(例如,“柯西不等式的基本应用”)。\n" + - $"输出要求:确保阶段划分合理、无` 重叠,且时长符合要求,并且每个阶段的时长需要超过60秒如果时长不够去考虑合并到相邻的阶段\n" + + $"输出要求:确保阶段划分合理、无重叠\n" + + $"作业布置阶段一般出现在末尾如果有" + $"输出格式要求:内容只返回json格式({resFormat})\n" + $"字幕格式(开始秒:内容|下一段字幕).以下是包含时间的视频字幕文本。\n" + $"字幕列表 {captions.Captions} 字幕结束!"; await redisManager.AddTaskLog(taskInfo.Id, $"开始分析视频内容 {tryCount}"); - //return await chatGPTClient.ChatAsync(taskInfo.Id.ToString(), postMessages, "分析字幕"); - var res = await geminiClient.ChatAsync(taskInfo.Id.ToString(), postMessages, "分析字幕"); - //var r2 = await chatClient.ChatAsync(taskInfo.Id.ToString(), postMessages, "分析字幕"); + var res = await geminiClient.ChatAsync>(taskInfo.Id.ToString(), postMessages, "分析字幕"); return res; } catch (Exception ex) @@ -342,6 +390,122 @@ namespace VideoAnalysisCore.AICore.GPT.DeepSeek return null; } + private async Task DetectHomeworkAssignment(VideoTask taskInfo, TotalCaptionsDto captions, string sections) + { + if (captions is null || string.IsNullOrWhiteSpace(captions.Captions)) + return null; + + var parts = captions.Captions + .Split('|', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); + if (parts.Length == 0) + return null; + + var tail = string.Join('|', parts.Skip(Math.Max(0, parts.Length - 80))); + var resFormat = """{"StartTime":123.4,"EndTime":456.7,"Stage":"作业布置|无作业","Theme":"课后作业布置","Content":"作业内容(可包含条目)"}"""; + var pptFormat = taskInfo.VideoType == AttachmentsInfoType.复习 + ? "这堂课是习题课,作业可能是布置练习/订正/讲义整理。" + : string.Empty; + + var message = + $""" + 请你担任一位专业的课堂教研老师,擅长从课堂结尾字幕中识别老师布置的课后作业内容。 + 目标:只分析我提供的【最后80条字幕】,判断是否存在作业布置,并提取作业内容,输出为一个 VideoKnowRes 对象。 + 补充上下文:{pptFormat} + 本节课所属章节:{sections} + 识别要点(供参考):作业/课后/回家/练习/习题/订正/预习/背诵/阅读/抄写/完成/下节课/交作业 等。 + 强制约束: + 1) 必须完全基于字幕文本,不允许捏造。 + 2) 如果没有明确作业布置:Stage 必须输出为“无作业”,Content 输出空字符串。 + 3) 如果识别到作业布置:Stage 必须输出为“作业布置”,Theme 固定为“课后作业布置”。 + 4) StartTime/EndTime 尽量取与作业布置相关字幕的起止秒(从字幕文本中推断)。 + 4) 只输出 JSON,不要输出任何解释。 + 输入字幕(最后50条,格式为 说话人:开始秒:结束秒:内容|下一条字幕): + {tail} + 输出格式(仅 JSON):{resFormat} + """; + + var res = await deepSeekClient.ChatAsync(taskInfo.Id.ToString(), message, "作业布置识别"); + if (res is null) + return null; + if (!string.Equals(res.Stage, "作业布置", StringComparison.OrdinalIgnoreCase)) + return null; + if (string.IsNullOrWhiteSpace(res.Content)) + return null; + return res; + } + + private VideoKnowRes[] MergeHomeworkStage(VideoKnowRes[] segments, VideoKnowRes homeworkStage, float maxVideoTime) + { + if (homeworkStage is null) + return segments; + + if (segments is null) + segments = []; + + var ordered = segments + .Where(s => s != null) + .OrderBy(s => s.StartTime ?? 0) + .ToList(); + + if (ordered.Any(s => + (!string.IsNullOrWhiteSpace(s.Stage) && s.Stage.Contains("作业")) || + (!string.IsNullOrWhiteSpace(s.Theme) && s.Theme.Contains("作业")))) + return ordered.ToArray(); + + var end = homeworkStage.EndTime ?? maxVideoTime; + if (end <= 0) + return ordered.ToArray(); + + var start = homeworkStage.StartTime ?? Math.Max(0, end - 120); + + if (end - start < 1) + { + start = Math.Max(0, end - 30); + if (end - start < 1) + end = start + 30; + } + + if (maxVideoTime > 0 && end > maxVideoTime) + end = maxVideoTime; + + if (ordered.Count > 0) + { + var last = ordered[^1]; + var lastStart = last.StartTime ?? 0; + var lastEnd = last.EndTime ?? lastStart; + + if (start - lastEnd > 40) + start = lastEnd; + + if (start <= lastStart) + start = lastStart + 0.01f; + + if (start >= end) + { + end = start + 30; + if (maxVideoTime > 0 && end > maxVideoTime) + end = maxVideoTime; + } + + if (last.EndTime is null || last.EndTime > start) + last.EndTime = start; + } + + var homeworkContent = homeworkStage.Content; + var homeworkTheme = string.IsNullOrWhiteSpace(homeworkStage.Theme) ? "课后作业布置" : homeworkStage.Theme; + + ordered.Add(new VideoKnowRes() + { + StartTime = start, + EndTime = end, + Stage = "作业布置", + Theme = homeworkTheme, + Content = homeworkContent + }); + + return ordered.ToArray(); + } + @@ -524,25 +688,66 @@ namespace VideoAnalysisCore.AICore.GPT.DeepSeek captionsArr = await OptimizeSubtitles(taskInfo, captionsArr, sections); //合并字幕 var captions = ExpandFunction.GetSpeakerCaptions(captionsArr); + var homework = await DetectHomeworkAssignment(taskInfo, captions, sections); + if (homework != null) + { + await redisManager.AddTaskLog(taskInfo.Id, $"==>识别到作业布置 {homework.Content}"); + await redisManager.Redis.HMSetAsync(RedisExpandKey.Task(task), "Homework", homework); + } var maxVideoTime = captions?.TimeBase?.LastOrDefault()?.End ?? 0; - VideoKnowRes[]? questionRes = null; + List? questionRes = null; var tryCount = 20; while (tryCount-- > 0) { //视频字幕分析 questionRes = await Analytics(taskInfo, captions, sections); - if (questionRes is null) continue; //处理分段 知识点 - var insertData = await GetVideoKnow(questionRes, taskInfo, sections, knowledgeInfos); + List insertData = await GetVideoKnow(questionRes, taskInfo, sections, knowledgeInfos); + if (homework != null) + questionRes.Add(homework); //校验结果质量 var checkRes = await VerifySpanQuality(questionRes, taskInfo, captions, sections, Course_Id); await redisManager.AddTaskLog(taskInfo.Id, $"==>课堂内容AI分析结果 得分=>{checkRes.Score}"); await redisManager.AddTaskLog(taskInfo.Id, $"==>改进意见 {checkRes.Suggestion}"); await redisManager.AddTaskLog(taskInfo.Id, $"==>扣分原因 {checkRes.MinusScore}"); + // 质量复检 + if (checkRes != null) + { + var improved = await ImproveSpanBySuggestion(questionRes, taskInfo, captions, sections, 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 (checkRes != null && checkRes.Score >= 85) + if (improvedCheck != null && improvedCheck.Score >= 90) + { + questionRes = improved; + 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, + 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, + }).ToArray(); + await videoTaskStageDB.InsertRangeAsync(tStage); + await videoKonwPointDB.InsertRangeAsync(insertData); + break; + } + } + } + + if (checkRes != null && checkRes.Score >= 90) { //写入知识点 await videoKonwPointDB.DeleteAsync(s => s.VideoTaskId == taskInfo.Id); diff --git a/VideoAnalysisCore/AICore/GPT/Gemini/GeminiGPTClient.cs b/VideoAnalysisCore/AICore/GPT/Gemini/GeminiGPTClient.cs index 1c50699..0bcd8cd 100644 --- a/VideoAnalysisCore/AICore/GPT/Gemini/GeminiGPTClient.cs +++ b/VideoAnalysisCore/AICore/GPT/Gemini/GeminiGPTClient.cs @@ -42,7 +42,7 @@ namespace VideoAnalysisCore.AICore.GPT.ChatGPT /// /// public override async Task ChatAsync(string task, string postMessages, string title, - string model = null, int max_tokens = 16000) + string model = null, int max_tokens = 32_000) { Message[] messageArr = [ new Message(postMessages,"user"), @@ -57,7 +57,8 @@ namespace VideoAnalysisCore.AICore.GPT.ChatGPT max_tokens = max_tokens, stream = true, temperature = 0.2f, - messages = messageArr + messages = messageArr, + max_completion_tokens=16000, }; chatReq.modalities = null; diff --git a/VideoAnalysisCore/Model/VideoKonwPoint.cs b/VideoAnalysisCore/Model/VideoKonwPoint.cs index 0b4a41c..c675e46 100644 --- a/VideoAnalysisCore/Model/VideoKonwPoint.cs +++ b/VideoAnalysisCore/Model/VideoKonwPoint.cs @@ -65,9 +65,14 @@ namespace VideoAnalysisCore.Model /// public string? KnowPointId { get; set; } /// - /// 内容总结 + /// 知识点来源 视频秒 /// - public string? Content { get; set; } + public float KnowSourceTime { get; set; } + /// + /// 内容总结[不写入数据库] + /// + [SugarColumn(IsIgnore = true)] + public virtual string? Content { get; set; } /// /// 课程阶段 /// diff --git a/VideoAnalysisCore/Model/VideoTaskStage.cs b/VideoAnalysisCore/Model/VideoTaskStage.cs new file mode 100644 index 0000000..4bfb48f --- /dev/null +++ b/VideoAnalysisCore/Model/VideoTaskStage.cs @@ -0,0 +1,72 @@ +using SqlSugar; +using System.ComponentModel.DataAnnotations; +using System.Net; +using System.Text.Json; +using UserCenter.Model.Enum; +using VideoAnalysisCore.AICore.GPT.Dto; +using VideoAnalysisCore.AICore.SherpaOnnx; +using VideoAnalysisCore.Model.Enum; +using VideoAnalysisCore.Model.Interface; +using Whisper.net; + +namespace VideoAnalysisCore.Model +{ + /// + /// 视频片段 + /// + [SugarTable("videotaskstage")] + public class VideoTaskStage : IDB + { + /// + /// id + /// + [SugarColumn(IsPrimaryKey = true)] + public long Id { get; set; } + /// + /// 视频任务id + /// + /// + public long VideoTaskId { get; set; } + /// + /// 自定义Id [任务视频自定义id] + /// + /// + [SugarColumn(Length = 500, IsNullable = true)] + public string? TagId { get; set; } + + /// + /// 开始时间 + /// + [SugarColumn( IsNullable = true)] + public float? StartTime { get; set; } + /// + /// 结束时间 + /// + [SugarColumn(IsNullable = true)] + public float? EndTime { get; set; } + /// + /// 持续时间 + /// + [SugarColumn(IsIgnore = true)] + public float? KeepTime => (EndTime ?? 0) - StartTime ?? 0; + /// + /// 主题 + /// + public string? Theme { get; set; } + /// + /// 知识点来源 视频秒 + /// + public float KnowSourceTime { get; set; } + /// + /// 课程阶段 + /// + [SugarColumn(IsIgnore = true)] + public virtual StageEnum? Stage { get; set; } + /// + /// 视频所属云校ID + /// 用户中心的云校id + /// + [SugarColumn(IsNullable = true)] + public long? CloudSchoolId { get; set; } + } +}