From 59c8171ce82b25aded5f7ca98656937da06d6181 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, 9 Jan 2025 11:53:48 +0800 Subject: [PATCH] =?UTF-8?q?=E5=90=8C=E6=AD=A5=E4=BB=A3=E7=A0=81=20?= =?UTF-8?q?=E5=BC=95=E5=85=A5=20=E7=9F=A5=E8=AF=86=E7=82=B9=E6=95=B0?= =?UTF-8?q?=E6=8D=AE=E5=BA=93=20=E9=87=8D=E5=86=99=20AI=E5=88=86=E6=9E=90?= =?UTF-8?q?=E8=A7=86=E9=A2=91=E7=89=87=E6=AE=B5=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Components/Layouts/BasicLayout.razor.cs | 7 + .../Components/Pages/VideoTaskPage.razor | 4 + .../Components/Pages/VideoTaskPage.razor.cs | 5 + .../Components/Pages/VideoTaskShow.razor | 80 ++++++++ .../Components/Pages/VideoTaskShow.razor.cs | 72 ++++++++ .../Components/Pages/VideoTaskShow.razor.css | 59 ++++++ VideoAnalysis/Controllers/ApiController.cs | 4 +- VideoAnalysis/Controllers/Dto/ApiDto.cs | 6 + VideoAnalysis/Program.cs | 11 +- VideoAnalysis/appsettings.json | 9 +- VideoAnalysisCore/AICore/GPT/BserGPT.cs | 1 + .../AICore/GPT/ChatGPT/Chat_GPT.cs | 171 +++++++++++++++++- .../AICore/GPT/Dto/QuestionRes.cs | 22 +++ VideoAnalysisCore/AICore/GPT/KIMI/KIMI_GPT.cs | 2 +- .../AICore/SherpaOnnx/SenseVoice.cs | 3 +- VideoAnalysisCore/Common/AppCommon.cs | 76 +++++--- VideoAnalysisCore/Common/RedisExpand.cs | 3 +- VideoAnalysisCore/Common/Repository.cs | 6 +- VideoAnalysisCore/Common/SqlSugarExpand.cs | 7 + VideoAnalysisCore/Interface/IDB.cs | 15 ++ VideoAnalysisCore/Interface/IKnowsDB.cs | 15 ++ .../Model/CourseGradingCriteria.cs | 3 +- VideoAnalysisCore/Model/Dto/VideoTaskDto.cs | 5 + VideoAnalysisCore/Model/KnowledgeInfo.cs | 45 +++++ VideoAnalysisCore/Model/VideoTask.cs | 7 +- 25 files changed, 603 insertions(+), 35 deletions(-) create mode 100644 VideoAnalysis/Components/Pages/VideoTaskShow.razor create mode 100644 VideoAnalysis/Components/Pages/VideoTaskShow.razor.cs create mode 100644 VideoAnalysis/Components/Pages/VideoTaskShow.razor.css create mode 100644 VideoAnalysisCore/Interface/IDB.cs create mode 100644 VideoAnalysisCore/Interface/IKnowsDB.cs create mode 100644 VideoAnalysisCore/Model/KnowledgeInfo.cs diff --git a/VideoAnalysis/Components/Layouts/BasicLayout.razor.cs b/VideoAnalysis/Components/Layouts/BasicLayout.razor.cs index 47de42b..a53b2c2 100644 --- a/VideoAnalysis/Components/Layouts/BasicLayout.razor.cs +++ b/VideoAnalysis/Components/Layouts/BasicLayout.razor.cs @@ -56,6 +56,13 @@ namespace VideoAnalysisRazor.Layouts Name = "登录页", Key = "Login", HideInMenu = true, + }, + new MenuDataItem + { + Path = "/VideoTaskShow", + Name = "视频任务预览", + Key = "VideoTaskShow", + HideInMenu = true, } ]; } diff --git a/VideoAnalysis/Components/Pages/VideoTaskPage.razor b/VideoAnalysis/Components/Pages/VideoTaskPage.razor index 0376d28..02f68a9 100644 --- a/VideoAnalysis/Components/Pages/VideoTaskPage.razor +++ b/VideoAnalysis/Components/Pages/VideoTaskPage.razor @@ -45,6 +45,10 @@ + + diff --git a/VideoAnalysis/Components/Pages/VideoTaskPage.razor.cs b/VideoAnalysis/Components/Pages/VideoTaskPage.razor.cs index 1b0f224..30732a8 100644 --- a/VideoAnalysis/Components/Pages/VideoTaskPage.razor.cs +++ b/VideoAnalysis/Components/Pages/VideoTaskPage.razor.cs @@ -20,6 +20,7 @@ namespace Learn.VideoAnalysis.Components.Pages [Inject] private ConfirmService ComfirmService { get; set; } = default!; [Inject] private Repository taskDB { get; set; } = default!; + [Inject] private NavigationManager NavigationManager { get; set; } = default!; [Inject] private INotificationService _notice { get; set; } = default!; @@ -56,6 +57,10 @@ namespace Learn.VideoAnalysis.Components.Pages reStartTask = query; modalShow = true; } + void PreviewTask(VideoTaskDto task) + { + NavigationManager.NavigateTo("/VideoTaskShow/"+task.Id); + } /// /// 重试 /// diff --git a/VideoAnalysis/Components/Pages/VideoTaskShow.razor b/VideoAnalysis/Components/Pages/VideoTaskShow.razor new file mode 100644 index 0000000..fd8cd11 --- /dev/null +++ b/VideoAnalysis/Components/Pages/VideoTaskShow.razor @@ -0,0 +1,80 @@ +@page "/VideoTaskShow/{tid}" +
+ + + +
+ +
+ +
+
+ +
+ + + + +@code { + +} diff --git a/VideoAnalysis/Components/Pages/VideoTaskShow.razor.cs b/VideoAnalysis/Components/Pages/VideoTaskShow.razor.cs new file mode 100644 index 0000000..87a9449 --- /dev/null +++ b/VideoAnalysis/Components/Pages/VideoTaskShow.razor.cs @@ -0,0 +1,72 @@ +using AntDesign; +using AntDesign.TableModels; +using FFmpeg.NET.Services; +using Learn.VideoAnalysis.Controllers.Dto; +using Microsoft.AspNetCore.Components; +using Microsoft.AspNetCore.Components.Web; +using Microsoft.AspNetCore.DataProtection.KeyManagement; +using SqlSugar; +using System.Linq.Expressions; +using System.Threading.Tasks; +using VideoAnalysisCore.AICore.GPT.Dto; +using VideoAnalysisCore.AICore.SherpaOnnx; +using VideoAnalysisCore.Common; +using VideoAnalysisCore.Enum; +using VideoAnalysisCore.Model; +using VideoAnalysisCore.Model.Dto; + +namespace Learn.VideoAnalysis.Components.Pages +{ + public partial class VideoTaskShow : ComponentBase + { + + [Inject] private ConfirmService ComfirmService { get; set; } = default!; + [Inject] private IHttpContextAccessor HttpContext { get; set; } = default!; + [Inject] private Repository taskDB { get; set; } = default!; + + private VideoTask nowTask { get; set; } = default!; + private string videoPath { get; set; } = default!; + + /// + /// 字幕 + /// + private SenseVoiceRes[] captionsArr { get; set; } = default!; + /// + /// 分段 + /// + private VideoKnowRes[] videoKnows { get; set; } = default!; + /// + /// 在渲染页面之后 + /// + /// + /// + protected override async Task OnAfterRenderAsync(bool firstRender) + { + + + } + /// + /// 初始化 + /// + protected override async void OnInitialized() + { + var routeData = HttpContext.HttpContext.GetRouteData(); + if (routeData is null) + return; + long taskId = (long)routeData.Values["id"]; + nowTask = await taskDB.GetFirstAsync(s => s.Id == taskId); + if(nowTask is null) + return; + captionsArr = RedisExpand.Redis.HMGet(RedisExpandKey.Task(taskId), "Captions").FirstOrDefault(); + videoKnows = RedisExpand.Redis.HMGet(RedisExpandKey.Task(taskId), "VideoKnows").FirstOrDefault(); + videoPath = AppCommon.GetVideoPath(nowTask.Id.ToString()); + } + + private async Task Comfirm(string message) + { + return await ComfirmService.Show(message, "提示", ConfirmButtons.YesNo, ConfirmIcon.Warning) == ConfirmResult.Yes; + } + + } + +} diff --git a/VideoAnalysis/Components/Pages/VideoTaskShow.razor.css b/VideoAnalysis/Components/Pages/VideoTaskShow.razor.css new file mode 100644 index 0000000..9ca6b8f --- /dev/null +++ b/VideoAnalysis/Components/Pages/VideoTaskShow.razor.css @@ -0,0 +1,59 @@ + +* { + padding: 0; + margin: 0; +} + +#video-container { + position: relative; + width: 1600px; + height: 850px; + float: left; +} + +video { + width: 100%; + height: 100%; +} + +.subtitles { + position: absolute; + bottom: -40px; + width: 100%; + text-align: center; + color: white; + background-color: rgba(0, 0, 0, 0.7); + font-size: 18px; +} + +#segmentsContainer { + display: flex; + flex-direction: column; + width: 265px; + height: 850px; + gap: 10px; + overflow: hidden; + overflow-y: scroll; + float: left; + flex-wrap: nowrap; + padding: 10px; + align-content: flex-start; + justify-content: flex-start; + align-items: center; +} + + #segmentsContainer button { + width: 100%; + height: 60px; + font-size: 1.3rem; + text-align: left; + cursor: pointer; + color: rgb(103, 194, 58); + background-color: rgb(240, 249, 235); + border: 1px solid rgb(179, 225, 157); + } + + #segmentsContainer button:hover { + background-color: rgb(248, 230, 191) !important; + border: 1px solid rgb(206, 187, 81); + } diff --git a/VideoAnalysis/Controllers/ApiController.cs b/VideoAnalysis/Controllers/ApiController.cs index ca4dcae..24ac22e 100644 --- a/VideoAnalysis/Controllers/ApiController.cs +++ b/VideoAnalysis/Controllers/ApiController.cs @@ -83,8 +83,7 @@ namespace Learn.VideoAnalysis.Controllers public async Task Test(long taskId) { //¿ʼִGPT - RedisExpand.InsertChannel(RedisChannelEnum.ChatModelAnalysis - , taskId); + chatGPT.GetKnow(taskId.ToString()); return Ok(); } @@ -206,6 +205,7 @@ namespace Learn.VideoAnalysis.Controllers Subject = req.Subject, Tag = req.Tag, TagId = req.TagId, + MediaName = req.MediaName }; // task.Id = await videoTaskDB.InsertReturnBigIdentityAsync(task); diff --git a/VideoAnalysis/Controllers/Dto/ApiDto.cs b/VideoAnalysis/Controllers/Dto/ApiDto.cs index 11d5104..361207b 100644 --- a/VideoAnalysis/Controllers/Dto/ApiDto.cs +++ b/VideoAnalysis/Controllers/Dto/ApiDto.cs @@ -18,6 +18,12 @@ namespace Learn.VideoAnalysis.Controllers.Dto [Url(ErrorMessage = "请输入有效的 URL")] public string MediaUrl { get; set; } = string.Empty; /// + /// 资源名称 + /// + [Required(ErrorMessage = "资源名称是必要的")] + [Url(ErrorMessage = "请输入有效的 资源名称")] + public string MediaName { get; set; } = string.Empty; + /// /// ApiKey /// [Required(ErrorMessage = "接口Token是必填项")] diff --git a/VideoAnalysis/Program.cs b/VideoAnalysis/Program.cs index 5efc6a4..f8e077b 100644 --- a/VideoAnalysis/Program.cs +++ b/VideoAnalysis/Program.cs @@ -7,6 +7,7 @@ using Mapster; using VideoAnalysisCore.AICore.GPT; using VideoAnalysisCore.AICore.GPT.KIMI; using VideoAnalysisCore.AICore.GPT.ChatGPT; +using Microsoft.Extensions.FileProviders; @@ -101,7 +102,15 @@ namespace Learn.VideoAnalysis //} - app.UseStaticFiles(); + app.UseStaticFiles(new StaticFileOptions + { + FileProvider = new PhysicalFileProvider(AppCommon.TaskCachedFile), + RequestPath = "/video", + //OnPrepareResponse = ctx => // + //{ + // ctx.Context.Response.Headers[Microsoft.Net.Http.Headers.HeaderNames.CacheControl] = "public,max - age = 31536000"; + //} + }); app.UseAntiforgery(); app.MapRazorComponents() diff --git a/VideoAnalysis/appsettings.json b/VideoAnalysis/appsettings.json index 453bffd..aaabb52 100644 --- a/VideoAnalysis/appsettings.json +++ b/VideoAnalysis/appsettings.json @@ -27,14 +27,19 @@ "ApiKey": "sk-8BvvhESZIkgUbiaaJhglPxFa4o2X9H3xEv9lXELrWWwGxHWY" }, "ChatGpt": { - "Host": "https://api.oaibest.com/", + "Host": "https://api.g4f.icu/", + //"Host": "https://api.oaibest.com/", "ApiKey": "sk-D15tBln31N7dI9Fi7lds7OySFv5tOEK7DMNsG5rY2E6DCr4s" } }, "DB": { "ConnectionString": "AllowLoadLocalInfile=true;Server=192.168.2.9;User ID=root;Password=qwe123!@#;Port=3306;Database=learn.videoanalysis;CharSet=utf8mb4;pooling=true;SslMode=None", "SqlType": "MySql", - "UpdateTable": true, + "UpdateTable": true + }, + "KnowsDB": { + "ConnectionString": "Server=47.109.35.116;Database=ResourceBank;UID=live;Password=Woshiren^&*();MultipleActiveResultSets=true;Encrypt=True;TrustServerCertificate=True;", + "SqlType": "SqlServer" } } } diff --git a/VideoAnalysisCore/AICore/GPT/BserGPT.cs b/VideoAnalysisCore/AICore/GPT/BserGPT.cs index eaa9368..4cc4911 100644 --- a/VideoAnalysisCore/AICore/GPT/BserGPT.cs +++ b/VideoAnalysisCore/AICore/GPT/BserGPT.cs @@ -16,5 +16,6 @@ namespace VideoAnalysisCore.AICore.GPT /// 任务id /// public Task CallGPT(string task); + public Task GetKnow(string task); } } diff --git a/VideoAnalysisCore/AICore/GPT/ChatGPT/Chat_GPT.cs b/VideoAnalysisCore/AICore/GPT/ChatGPT/Chat_GPT.cs index 7a0bf79..5edd72d 100644 --- a/VideoAnalysisCore/AICore/GPT/ChatGPT/Chat_GPT.cs +++ b/VideoAnalysisCore/AICore/GPT/ChatGPT/Chat_GPT.cs @@ -8,6 +8,7 @@ using System.Reflection; using VideoAnalysisCore.Model.Dto; using VideoAnalysisCore.AICore.GPT.Dto; using VideoAnalysisCore.AICore.GPT; +using System.Threading.Tasks; namespace VideoAnalysisCore.AICore.GPT.ChatGPT { @@ -19,16 +20,184 @@ namespace VideoAnalysisCore.AICore.GPT.ChatGPT private readonly ChatGPTClient chatClient; private readonly Repository criteriaDB; private readonly Repository videoTaskDB; + private readonly Repository knowledgeInfoDB; /// /// 初始化 /// /// /// - public Chat_GPT(ChatGPTClient moonshotClient, Repository criteria, Repository videoTaskDB) + public Chat_GPT(ChatGPTClient moonshotClient, Repository criteria, Repository videoTaskDB, Repository knowledgeInfoDB) { this.chatClient = moonshotClient; criteriaDB = criteria; this.videoTaskDB = videoTaskDB; + this.knowledgeInfoDB = knowledgeInfoDB; + } + + private static List MergeRes(VideoKnowRes[] timeBases) + { + if (timeBases == null || timeBases.Count() == 0) + { + return new List(); + } + var mergedList = new List(); + // 初始化合并段 + var current = timeBases.First(); + foreach (var next in timeBases) + { + // 如果类型相同,则扩展时间段 + if (current.主题 == next.主题) + { + current.结束秒 = Math.Max(current.结束秒.Value, next.结束秒.Value); + current.内容总结 += next.内容总结; + } + else + { + // 类型不同,将当前时间段加入结果列表,并开始新时间段 + mergedList.Add(current); + current = next; + } + } + // 添加最后的时间段 + mergedList.Add(current); + return mergedList; + } + + + /// + /// 获取知识点 + /// + /// 任务id + /// + public async Task GetKnow(string task) + { + var taskId = long.Parse(task); + var taskInfo = await videoTaskDB.AsQueryable() + .Where(s => s.Id == taskId) + .FirstAsync(); + var subject = "数学"; + var xkwKnows = await knowledgeInfoDB.AsQueryable() + .Where(s => s.Course_Id == 27 + && s.Depth == 2) + .Select(s => s.Name).ToArrayAsync(); + string title = "周三(1.3)《第八章——统计与概率:超几何分布的极限为二项分布》"; + var fileNameResFormat = "{授课章节: string|null, 授课内容:string}"; + var fileNamePostMessages = title + + " 这是一堂课的标题,请你帮我分析一些关于课堂方面的内容." + + $"1.分析出高中{subject}课堂授课的主要章节(例如 章节: 数列),章节范围限定在[{xkwKnows}]范围." + + $"2.分析出这堂课的主要授课内容." + + $"输出格式 json字符串 对象格式{fileNameResFormat}"; + var fileNameInfoRes = await ChatAsync(task, fileNamePostMessages, fileNameResFormat); + + var captions = ExpandFunction.GetSpeakerCaptions(task); + var criteriaBuilder = new StringBuilder(); + + var resFormat = """[{"StartTime":开始秒(number),"EndTime":结束秒(number),"Section":章节(string),"Theme":主题(string),"Content":内容总结(string)}]"""; + var know = await knowledgeInfoDB.GetFirstAsync(s => s.Name == fileNameInfoRes.授课章节); + var knowledgeInfos = await knowledgeInfoDB.AsQueryable().ToChildListAsync(s => s.Parent_Id, know.Id); + var knows = "数列的概念,数列的定义,项的表示,数列的表示方法,通项公式,递推公式,图像表示,数列的类型,等差数列,等比数列,其他特殊数列,数列的性质,单调性,有限性,数列的求和,等差数列求和公式,等比数列求和公式,数列极限,递推关系"; + knows = string.Join(',', knowledgeInfos.Select(s => s.Name)); + var postMessages = + $"你的任务是分析视频字幕内容并提取出中国高考考试试题方法点,然后根据步骤分析出内容片段" + + $"按以下步骤完成:" + + $"1.识别方法点:提取字幕内容中与{subject}考试相关的方法点。" + + $"2.分类方法点:按学科方法点,细化到具体章节与主题(例如“章节:数列 主题:数列的基本概念”)。" + + $"3.分析总结:基于提取出的方法点名称来匹配我提供的方法点名称" + + $"提供的方法点名称(基本概念,课堂练习,{knows},例题讲解)。" + + $"4.关联合并相似的知识点来合并为内容片段。" + + $"内容片段使用关联知识点中的最小(开始秒)和(最大)结束秒,主题为关联知识点的主题分析,内容总结为关联知识点的内容总结分析。" + + $"延长内容片段时间区间来获取更加详细的上下文。" + + $"输入:包含时间戳的视频字幕文本。" + + $"以下是包含时间的视频字幕文本。" + + $"字幕格式(说话人:开始秒:结束秒:内容|下一段字幕).字幕列表 {captions.Captions}" + + $"输出格式({resFormat})"; + //var postMessages = + // $"你是一名专业的教育视频内容分析助手,主要任务是分析视频字幕内容,精准提取出与中国高考数学考试相关的试题方法点。" + + // $"按以下步骤完成:" + + // $"1.准确识别方法点:从字幕中提取与{subject}考试紧密相关的方法点,尤其关注与给定方法点类别相关的内容" + + // $"2.深入分析总结:依据给定的方法点类别进行约束,确定提取出的方法点所属类别。" + + // $"给定方法点包括(基本概念,课堂练习,例题讲解,解题技巧,{gjz})。" + + // $"3.细致分类方法点:按照学科方法点进行分类,具体细化到特定章节与主题,格式为 “章节:具体章节名称 主题:具体主题名称" + + // $"4.合理合并相似方法点为内容片段,确保内容的连贯性和逻辑性" + + // $"5.关联每个内容片段的方法点所有的时间并结构化输出。" + + // $"尽可能延内容片段时间区间,以获取详细的方法点上下文。" + + // $"输入:包含时间戳的视频字幕文本。输出格式:开始秒,结束秒,主题,内容总结" + + // $"以下是包含时间的视频字幕文本。" + + // $"字幕格式(说话人:开始秒:结束秒:内容|下一段字幕).字幕列表 {captions.Captions}" + + // $"返回固定的JSON格式({resFormat})"; + + var questionRes =await ChatAsync(task, postMessages, resFormat); + await RedisExpand.Redis + .HMSetAsync(RedisExpandKey.Task(task), "VideoKnows", questionRes); + + var postMessages1 = + $"你的任务是分析json内容并合并含义相似的主题为新的主题" + + $"按以下步骤完成:" + + $"1.合理合并主题字段重复相似的对象为新的json对象,确保内容的连贯性和逻辑性。" + + $"2.合并对象属性持续时间低于60秒的对象" + + $"3.结构化输出。" + + $"输入:json对象 包含总结开始秒,结束秒,持续时间,主题,章节,内容总结" + + $"以下是包含json内容的文本。" + + $" {JsonSerializer.Serialize(questionRes)}" + + $"返回固定的JSON格式({resFormat})"; + + + var questionRes1 = await ChatAsync(task, postMessages1, resFormat); + //questionRes1 = MergeRes(questionRes1).ToArray(); + + var gptRes = new TaskRes(captions); + await RedisExpand.Redis + .HMSetAsync(RedisExpandKey.Task(task), "ChatAnalysis", gptRes); + RedisExpand.InsertChannel(RedisChannelEnum.EndTask, task); + return gptRes; + } + public async Task ChatAsync(string task,string postMessages,string resFormat) + { + var maxTokens = 4000; + var chatRep = new ChatRequest + { + max_tokens = maxTokens, + temperature = 0.3f, + messages = [ + new Message(postMessages,"system"), + new Message(resFormat,"assistant"), + ] + }; + + RedisExpand.SetTaskGPTReqCached(task, chatRep); + var chatResp = await chatClient.Chat(chatRep); + var chatResContent = chatResp?.res; + if (string.IsNullOrEmpty(chatResContent)) + throw new Exception("GPT返回message无效结果"); + if (chatResp != null) + RedisExpand.SetTaskGPTCached(task, new object[] { chatResp.Value.res, chatResp.Value.u }); + + chatResContent = chatResContent?.Replace("字幕内容", "课堂情况"); + chatResContent = chatResContent?.Replace("\n", ""); + chatResContent = chatResContent?.Replace("```json", ""); + chatResContent = chatResContent?.Replace("```", ""); + chatResContent = chatResContent?.Replace("}{", "},{"); + chatResContent = chatResContent?.Replace("}|{", "},{"); + chatResContent = chatResContent?.Trim(); + var startsStr = typeof(T).IsArray? "[" :"{"; + var endStr = typeof(T).IsArray? "]" : "}"; + if (!chatResContent.StartsWith(startsStr)) + chatResContent = startsStr + chatResContent; + if (!chatResContent.EndsWith(endStr)) + chatResContent = chatResContent + endStr; + var questionRes = JsonSerializer.Deserialize(chatResContent); + if (questionRes is null) + throw new Exception("ChatGPT返回无效结果"); + //var totalTokens = chatResp?.u.total_tokens ?? 0; + //if (totalTokens > 1) + //{ + // var tid = long.Parse(task); + // await videoTaskDB.AsUpdateable() + // .SetColumns(it => it.TotalTokens == totalTokens)//SetColumns是可以叠加的 写2个就2个字段赋值 + // .Where(it => it.Id == tid) + // .ExecuteCommandAsync(); + //} + return questionRes; } /// /// 访问GPT diff --git a/VideoAnalysisCore/AICore/GPT/Dto/QuestionRes.cs b/VideoAnalysisCore/AICore/GPT/Dto/QuestionRes.cs index 30d7007..d760934 100644 --- a/VideoAnalysisCore/AICore/GPT/Dto/QuestionRes.cs +++ b/VideoAnalysisCore/AICore/GPT/Dto/QuestionRes.cs @@ -10,6 +10,28 @@ using System.Threading.Tasks; namespace VideoAnalysisCore.AICore.GPT.Dto { + public class VideoKnowRes + { + /// + /// 问题解释 + /// + public float? 开始秒 { get; set; } + public float? 结束秒 { get; set; } + public float? 持续时间 => (结束秒 ?? 0) - 开始秒??0; + public string? 主题 { get; set; } + public string? 章节 { get; set; } + public string? 内容总结 { get; set; } + + } + public class FileNameInfo + { + /// + /// 问题解释 + /// + public string? 授课章节 { get; set; } + public string? 授课内容 { get; set; } + + } public class QuestionRes { /// diff --git a/VideoAnalysisCore/AICore/GPT/KIMI/KIMI_GPT.cs b/VideoAnalysisCore/AICore/GPT/KIMI/KIMI_GPT.cs index 36a2dd5..0e34f5d 100644 --- a/VideoAnalysisCore/AICore/GPT/KIMI/KIMI_GPT.cs +++ b/VideoAnalysisCore/AICore/GPT/KIMI/KIMI_GPT.cs @@ -23,7 +23,7 @@ namespace VideoAnalysisCore.AICore.GPT.KIMI /// /// kimi 文本模型 /// - public class KIMI_GPT : IBserGPT + public class KIMI_GPT // : IBserGPT { private readonly MoonshotClient moonshotClient; private readonly Repository criteriaDB; diff --git a/VideoAnalysisCore/AICore/SherpaOnnx/SenseVoice.cs b/VideoAnalysisCore/AICore/SherpaOnnx/SenseVoice.cs index f296b83..0c4dfef 100644 --- a/VideoAnalysisCore/AICore/SherpaOnnx/SenseVoice.cs +++ b/VideoAnalysisCore/AICore/SherpaOnnx/SenseVoice.cs @@ -233,7 +233,8 @@ namespace VideoAnalysisCore.AICore.SherpaOnnx } await RedisExpand.Redis.HMSetAsync(RedisExpandKey.Task(task), "Captions", res); - RedisExpand.InsertChannel(Enum.RedisChannelEnum.ParsingSpeaker, task); + //RedisExpand.InsertChannel(Enum.RedisChannelEnum.ParsingSpeaker, task); + RedisExpand.InsertChannel(Enum.RedisChannelEnum.ChatModelAnalysis, task); } } diff --git a/VideoAnalysisCore/Common/AppCommon.cs b/VideoAnalysisCore/Common/AppCommon.cs index 1c5330b..9f9c210 100644 --- a/VideoAnalysisCore/Common/AppCommon.cs +++ b/VideoAnalysisCore/Common/AppCommon.cs @@ -4,12 +4,16 @@ using FreeRedis; using Microsoft.Extensions.DependencyModel; using SqlSugar; using SqlSugar.IOC; +using System.Collections.Generic; +using System.Linq; using System.Reflection; using System.Runtime.Loader; using System.Text; using System.Threading.Tasks; +using UserCenter.Model.Interface; using VideoAnalysisCore.AICore.SherpaOnnx; using VideoAnalysisCore.Enum; +using VideoAnalysisCore.Interface; using VideoAnalysisCore.Model.Dto; using Whisper.net; @@ -29,22 +33,26 @@ namespace VideoAnalysisCore.Common /// 主库数据库表类型 /// public static readonly IEnumerable DbMatserType; + public static readonly IEnumerable KnowsType; static AppCommon() { try { - Assemblies = ExpandFunction.GetAssemblies(); var assembliesType = Assemblies.Where(s => s.FullName.Contains("VideoAnalysis")).SelectMany(s => s.ExportedTypes .Where(u => !u.IsInterface && !u.IsAbstract && u.IsClass && u.IsDefined(typeof(SugarTable), false))); - DbMatserType = assembliesType; + DbMatserType = assembliesType + .Where(u => u.GetInterfaces().Contains(typeof(IDB))); + KnowsType = assembliesType + .Where(u =>u.GetInterfaces().Contains(typeof(IKnowsDB))); } catch { + throw; } - //.Where(u => !u.IsDefined(typeof(SplitTableAttribute), false)) - //.Where(u => !typeof(Model.DataCenterYH.IDataCenterYHModel).IsAssignableFrom(u)) - //.Where(u => !u.IsSubclassOf(typeof(YQ_BaseEntity))); + //.Where(u => !u.IsDefined(typeof(SplitTableAttribute), false)) + //.Where(u => !typeof(Model.DataCenterYH.IDataCenterYHModel).IsAssignableFrom(u)) + //.Where(u => !u.IsSubclassOf(typeof(YQ_BaseEntity))); } /// @@ -66,6 +74,13 @@ namespace VideoAnalysisCore.Common public static string AIModelFile = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "AICore", "_Static"); + /// + /// 获取视频路径 + /// + /// + /// + public static string GetVideoPath(string tid) => + Path.Combine(TaskCachedFile, tid, tid + ".mp4"); } /// @@ -140,35 +155,46 @@ namespace VideoAnalysisCore.Common { var captionsArr = RedisExpand.Redis.HMGet(RedisExpandKey.Task(task), "Captions").FirstOrDefault(); var speakerArr = RedisExpand.Redis.HMGet(RedisExpandKey.Task(task), "Speaker").FirstOrDefault(); - if (captionsArr is null || captionsArr.Length == 0 - || speakerArr is null || speakerArr.Length == 0) + if (captionsArr is null || captionsArr.Length == 0) + //|| speakerArr is null || speakerArr.Length == 0) throw new Exception("音频解析数据异常"); // 教师说话人Id - var techerId = speakerArr.GroupBy(s=>s.SpeakerIndex).Select(s => (s.Key,s.Sum(x=>x.Total))) + var techerId = speakerArr is null || !speakerArr.Any() + ? 0 + :speakerArr.GroupBy(s=>s.SpeakerIndex).Select(s => (s.Key,s.Sum(x=>x.Total))) .OrderByDescending(s=>s.Item2).First().Key; var teacherSpeaking = 0f; var studentSpeaking = 0f; var results = new Dictionary>(); - foreach (var segment in captionsArr) + if (speakerArr is null || speakerArr.Count() == 0) { - var spList = new List(); - foreach (var speakerRes in speakerArr) + var ss = new List { 1 }; + results = captionsArr.ToDictionary(s => s, s=> ss); + } + else + { + foreach (var segment in captionsArr) { - if (speakerRes.Start > segment.End) - break; - if (segment.Start <= speakerRes.End - && segment.End >= speakerRes.Start) + var spList = new List(); + foreach (var speakerRes in speakerArr) { - if (speakerRes.SpeakerIndex == techerId) - teacherSpeaking += speakerRes.Total; - else - studentSpeaking += speakerRes.Total; - spList.Add(speakerRes.SpeakerIndex); + if (speakerRes.Start > segment.End) + break; + if (segment.Start <= speakerRes.End + && segment.End >= speakerRes.Start) + { + if (speakerRes.SpeakerIndex == techerId) + teacherSpeaking += speakerRes.Total; + else + studentSpeaking += speakerRes.Total; + spList.Add(speakerRes.SpeakerIndex); + } } + var sp = spList.Distinct().ToList(); + if (sp.Count > 0) + results.Add(segment, sp); } - var sp = spList.Distinct().ToList(); - if(sp.Count>0) - results.Add(segment, sp); + } //拼接 提示词字幕源 var stringBuilder = new StringBuilder(); @@ -352,6 +378,10 @@ namespace VideoAnalysisCore.Common /// 数据库配置 /// public DBConfig DB { get; set; } = new DBConfig(); + /// + /// 知识点数据库 + /// + public DBConfig KnowsDB { get; set; } = new DBConfig(); } } diff --git a/VideoAnalysisCore/Common/RedisExpand.cs b/VideoAnalysisCore/Common/RedisExpand.cs index 4dcc4d9..bd6f154 100644 --- a/VideoAnalysisCore/Common/RedisExpand.cs +++ b/VideoAnalysisCore/Common/RedisExpand.cs @@ -213,7 +213,7 @@ namespace VideoAnalysisCore.Common if (scope is null || scope.ServiceProvider.GetService() is null) throw new Exception("IBserGPT 未注入"); else - return scope.ServiceProvider.GetService()?.CallGPT(task) ?? Task.CompletedTask; + return scope.ServiceProvider.GetService()?.GetKnow(task) ?? Task.CompletedTask; }); }); SubscribeList.Add(RedisChannelEnum.EndTask, @@ -277,6 +277,7 @@ namespace VideoAnalysisCore.Common .Where(it => it.Id == taskID) .ExecuteCommandAsync() == 1; } + /// /// 触发 /// diff --git a/VideoAnalysisCore/Common/Repository.cs b/VideoAnalysisCore/Common/Repository.cs index d1afcce..235516c 100644 --- a/VideoAnalysisCore/Common/Repository.cs +++ b/VideoAnalysisCore/Common/Repository.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; +using VideoAnalysisCore.Interface; using VideoAnalysisCore.Model; namespace VideoAnalysisCore.Common @@ -13,7 +14,10 @@ namespace VideoAnalysisCore.Common { public Repository() { - base.Context = DbScoped.SugarScope; + if(typeof(T).GetInterfaces().Contains(typeof(IKnowsDB))) + base.Context = DbScoped.SugarScope.GetConnection(1001); + else + base.Context = DbScoped.SugarScope; } } diff --git a/VideoAnalysisCore/Common/SqlSugarExpand.cs b/VideoAnalysisCore/Common/SqlSugarExpand.cs index 52d959a..a1493f3 100644 --- a/VideoAnalysisCore/Common/SqlSugarExpand.cs +++ b/VideoAnalysisCore/Common/SqlSugarExpand.cs @@ -27,6 +27,13 @@ namespace VideoAnalysisCore.Common ConnectionString = AppCommon.Config.DB.ConnectionString, DbType =AppCommon.Config.DB.SqlType, IsAutoCloseConnection = true//自动释放 + }, + new IocConfig() + { + ConfigId =1001, + ConnectionString = AppCommon.Config.KnowsDB.ConnectionString, + DbType =AppCommon.Config.KnowsDB.SqlType, + IsAutoCloseConnection = true//自动释放 }, }; services.AddSingleton(typeof(Repository<>)); diff --git a/VideoAnalysisCore/Interface/IDB.cs b/VideoAnalysisCore/Interface/IDB.cs new file mode 100644 index 0000000..9bb9511 --- /dev/null +++ b/VideoAnalysisCore/Interface/IDB.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace VideoAnalysisCore.Interface +{ + /// + /// 表属于IDB + /// + interface IDB + { + } +} diff --git a/VideoAnalysisCore/Interface/IKnowsDB.cs b/VideoAnalysisCore/Interface/IKnowsDB.cs new file mode 100644 index 0000000..4aa5abe --- /dev/null +++ b/VideoAnalysisCore/Interface/IKnowsDB.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace VideoAnalysisCore.Interface +{ + /// + /// 表属于KnowsDB + /// + interface IKnowsDB + { + } +} diff --git a/VideoAnalysisCore/Model/CourseGradingCriteria.cs b/VideoAnalysisCore/Model/CourseGradingCriteria.cs index d2e5323..1cfd29d 100644 --- a/VideoAnalysisCore/Model/CourseGradingCriteria.cs +++ b/VideoAnalysisCore/Model/CourseGradingCriteria.cs @@ -6,6 +6,7 @@ using System.Net; using UserCenter.Model.Enum; using VideoAnalysisCore.AICore.SherpaOnnx; using VideoAnalysisCore.Enum; +using VideoAnalysisCore.Interface; using Whisper.net; namespace VideoAnalysisCore.Model @@ -14,7 +15,7 @@ namespace VideoAnalysisCore.Model /// 课堂评分标准 /// [SugarTable("coursegradingcriteria")] - public class CourseGradingCriteria + public class CourseGradingCriteria: IDB { /// /// Id diff --git a/VideoAnalysisCore/Model/Dto/VideoTaskDto.cs b/VideoAnalysisCore/Model/Dto/VideoTaskDto.cs index 4b9fa01..574a58e 100644 --- a/VideoAnalysisCore/Model/Dto/VideoTaskDto.cs +++ b/VideoAnalysisCore/Model/Dto/VideoTaskDto.cs @@ -57,6 +57,11 @@ namespace VideoAnalysisCore.Model.Dto [DisplayName("媒体路径")] public string MediaUrl { get; set; } = string.Empty; /// + /// 媒体名称 + /// + [DisplayName("媒体名称")] + public string MediaName { get; set; } = string.Empty; + /// /// 自定义ID /// [DisplayName("自定义ID")] diff --git a/VideoAnalysisCore/Model/KnowledgeInfo.cs b/VideoAnalysisCore/Model/KnowledgeInfo.cs new file mode 100644 index 0000000..8eea235 --- /dev/null +++ b/VideoAnalysisCore/Model/KnowledgeInfo.cs @@ -0,0 +1,45 @@ +using SqlSugar; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using VideoAnalysisCore.Interface; + +namespace VideoAnalysisCore.Model.Dto +{ + [SugarTable("knowledgeinfo")] + public class KnowledgeInfo: IKnowsDB + { + [SugarColumn(IsPrimaryKey = true, ColumnDescription = "Id 主键", ColumnName = "id")] + public long Id { get; set; } + [SugarColumn(ColumnDescription = "自己数据库Id", ColumnName = "thisid")] + public long ThisId { get; set; } + [SugarColumn(InsertServerTime = true, ColumnDescription = "创建时间", ColumnName = "createtime")] + public DateTime CreateTime { get; set; } + [SugarColumn(UpdateServerTime = true, InsertServerTime = true, IsNullable = true, ColumnDescription = "更新时间", ColumnName = "updatetime")] + public DateTime UpdateTime { get; set; } + [SugarColumn(ColumnDescription = "是否删除 true=删除", DefaultValue = "false", ColumnName = "deletestate")] + public bool DeleteState { get; set; } + [SugarColumn(ColumnDescription = "排序", ColumnName = "ordinal", DefaultValue = "0")] + public int Ordinal { get; set; } + + [SugarColumn(ColumnDataType = "nvarchar(200)", ColumnDescription = "知识点名称", ColumnName = "name", IsNullable = true)] + public string Name { get; set; } + [SugarColumn(ColumnDescription = "节点深度,一级节点的深度为1,二级节点的深度为2,以此类推。", ColumnName = "depth", IsNullable = true)] + public long Depth { get; set; } + [SugarColumn(ColumnDescription = "适用于精简版", ColumnName = "for_lite", IsNullable = true)] + public bool For_Lite { get; set; } + [SugarColumn(ColumnDescription = "父节点ID", ColumnName = "parent_id", IsNullable = true)] + public long Parent_Id { get; set; } + [SugarColumn(ColumnDescription = "root节点的ID", ColumnName = "root_id", IsNullable = true)] + public long Root_Id { get; set; } + [SugarColumn(ColumnDataType = "nvarchar(100)", ColumnDescription = "节点类型,可用值:NODE、KNOWLEDGE_POINT、TESTING_POINT,分别代表普通节点、知识点、考点。", ColumnName = "type", IsNullable = true)] + public string Type { get; set; } + [SugarColumn(ColumnDescription = "课程ID", ColumnName = "course_id", IsNullable = true)] + public long Course_Id { get; set; } + [SugarColumn(InsertServerTime = true, ColumnDescription = "学科网创建时间", ColumnName = "create_time", IsNullable = true)] + public DateTime Create_Time { get; set; } + } + +} diff --git a/VideoAnalysisCore/Model/VideoTask.cs b/VideoAnalysisCore/Model/VideoTask.cs index ae9f489..f7afd95 100644 --- a/VideoAnalysisCore/Model/VideoTask.cs +++ b/VideoAnalysisCore/Model/VideoTask.cs @@ -6,6 +6,7 @@ using UserCenter.Model.Enum; using VideoAnalysisCore.AICore.GPT.Dto; using VideoAnalysisCore.AICore.SherpaOnnx; using VideoAnalysisCore.Enum; +using VideoAnalysisCore.Interface; using Whisper.net; namespace VideoAnalysisCore.Model @@ -14,7 +15,7 @@ namespace VideoAnalysisCore.Model /// 视频任务模型 /// [SugarTable("videotask")] - public class VideoTask + public class VideoTask: IDB { /// /// 任务id @@ -27,6 +28,10 @@ namespace VideoAnalysisCore.Model /// public string MediaUrl { get; set; } = string.Empty; /// + /// 媒体文件名称 + /// + public string MediaName { get; set; } = string.Empty; + /// /// 下载后本地媒体目录 /// public string LocalMediaPath { get; set; } = string.Empty;