Compare commits

..

4 Commits

Author SHA1 Message Date
小肥羊 4ab527c388 优化任务调度流程 2025-06-18 10:33:36 +08:00
小肥羊 19855abb6f 优化任务调度流程和异常捕获流程 2025-06-17 11:35:06 +08:00
小肥羊 f7c787cdf7 修改 数据结构后调整管理端UI
优化 任务接收辞池[进行中]
2025-06-13 18:23:30 +08:00
小肥羊 f943c4fec4 优化 视频试题AI分析试题识别精准度
修改 蓝鲸字库 请求流程
2025-06-12 16:12:01 +08:00
34 changed files with 434 additions and 1252 deletions

View File

@ -34,6 +34,7 @@ namespace Learn.VideoAnalysis.API
//初始化 插件 //初始化 插件
builder.Configuration.AddAppConfig(args); builder.Configuration.AddAppConfig(args);
builder.Services.AddHttpClient();
builder.Services.AddSqlSugarExpand(); builder.Services.AddSqlSugarExpand();
builder.Services.AddRedisExpand(); builder.Services.AddRedisExpand();
builder.Services.AddCoravel(); builder.Services.AddCoravel();

View File

@ -8,6 +8,7 @@
@using VideoAnalysisCore.Model.Dto @using VideoAnalysisCore.Model.Dto
@using VideoAnalysisCore.Model.Enum; @using VideoAnalysisCore.Model.Enum;
<Table @ref="_table" Loading="tableLoading" TItem="VideoTaskDto" ScrollY="600px" PageSize="10" Total="_total" DataSource="_dataSource" <Table @ref="_table" Loading="tableLoading" TItem="VideoTaskDto" ScrollY="600px" PageSize="10" Total="_total" DataSource="_dataSource"
OnRowClick="(r)=>r.Expanded = !r.Expanded" OnRowClick="(r)=>r.Expanded = !r.Expanded"
@bind-SelectedRows="_selectedRows" OnChange="OnChange" @bind-SelectedRows="_selectedRows" OnChange="OnChange"
@ -20,15 +21,14 @@
<ColumnDefinitions Context="row"> <ColumnDefinitions Context="row">
<Selection /> <Selection />
<PropertyColumn Property="c=>c.Id" Width="110px" Filterable="true" Sortable="true" /> <PropertyColumn Property="c=>c.Id" Width="110px" Filterable="true" Sortable="true" />
<PropertyColumn Property="c=>c.MediaName" Width="200px" />
<PropertyColumn Property="c=>c.TagId" Width="160px" /> <PropertyColumn Property="c=>c.TagId" Width="160px" />
<PropertyColumn Property="c=>c.LastEnum" Width="150px" /> <PropertyColumn Property="c=>c.LastEnum" Width="150px" />
<PropertyColumn Property="c=>c.Subject" Width="100px" /> <PropertyColumn Property="c=>c.Subject" Width="100px" />
<PropertyColumn Property="c=>c.ComeFrom" Width="100px" /> <PropertyColumn Property="c=>c.ComeFrom" Width="100px" />
<PropertyColumn Property="c=>c.MediaUrl" Width="320px" /> <PropertyColumn Property="c=>c.MediaUrl" Width="320px" />
<PropertyColumn Property="c=>c.TotalTokens" Width="100px" />
<PropertyColumn Property="c=>c.CreateTime" /> <PropertyColumn Property="c=>c.CreateTime" />
</ColumnDefinitions> </ColumnDefinitions>
<ExpandTemplate Context="rowData"> <ExpandTemplate Context="rowData">
<Descriptions Title="任务详情" Bordered> <Descriptions Title="任务详情" Bordered>
@ -51,25 +51,25 @@
</Button> </Button>
</DescriptionsItem> </DescriptionsItem>
<DescriptionsItem Title="任务时间轴" Span="5"> <DescriptionsItem Title="任务时间轴" Span="6">
<Steps Current="@((int)rowData.Data.LastEnum)" Status="@rowData.Data.TaskStatus"> <Steps Current="@((int)rowData.Data.LastEnum)" Status="@rowData.Data.TaskStatus">
<Step Title="下载文件" <Step Title="下载文件"
Description="@RowST(rowData,RedisChannelEnum.DownloadFile)" /> Description="@RowST(rowData,RedisChannelEnum.下载文件)" />
<Step Title="分离音频" <Step Title="分离音频"
Description="@RowST(rowData,RedisChannelEnum.SeparateAudio)" /> Description="@RowST(rowData,RedisChannelEnum.分离音频)" />
<Step Title="解析字幕" <Step Title="解析字幕"
Description="@RowST(rowData,RedisChannelEnum.ParsingCaptions)" /> Description="@RowST(rowData,RedisChannelEnum.解析字幕)" />
<Step Title="解析说话人"
Description="@RowST(rowData,RedisChannelEnum.ParsingSpeaker)" />
<Step Title="Chat模型分析" <Step Title="AI模型分析"
Description="@RowST(rowData,RedisChannelEnum.ChatModelAnalysis)" /> Description="@RowST(rowData,RedisChannelEnum.AI模型分析)" />
<Step Title="AI分析试题"
Description="@RowST(rowData,RedisChannelEnum.AI分析试题)" />
<Step Title="结束任务" <Step Title="结束任务"
Description="@RowST(rowData,RedisChannelEnum.EndTask)" /> Description="@RowST(rowData,RedisChannelEnum.结束任务)" />
</Steps> </Steps>
</DescriptionsItem> </DescriptionsItem>
@ -102,4 +102,4 @@
</Select> </Select>
<br /> <br />
<br /> <br />
</Modal> </Modal>

View File

@ -69,9 +69,9 @@ namespace Learn.VideoAnalysis.Components.Pages
/// <param name="query"></param> /// <param name="query"></param>
async void ReStart() async void ReStart()
{ {
await RedisExpand.SetTaskErrorMessage(reStartTask.Id, null); await RedisExpand.ClearTaskError(reStartTask.Id);
_=Task.Run(() => _=Task.Run(() =>
RedisExpand.InsertChannel((RedisChannelEnum)selectEnum, reStartTask.Id) RedisExpand.InsertChannel((RedisChannelEnum)selectEnum, reStartTask.Id)
); );
modalShow = false; modalShow = false;
} }
@ -128,8 +128,8 @@ namespace Learn.VideoAnalysis.Components.Pages
var data = RedisExpand.Redis.HMGet<string>(RedisExpandKey.Task(item.Id), var data = RedisExpand.Redis.HMGet<string>(RedisExpandKey.Task(item.Id),
"Progress", "LastEnum", "StartTime", "ErrorMessage"); "Progress", "LastEnum", "StartTime", "ErrorMessage");
item.Progress = data[0]; item.Progress = data[0];
item.LastEnum = data[1].ToEnum<RedisChannelEnum>() ?? default; item.LastEnum = data[1] == null ?default:data[1].ToEnum<RedisChannelEnum>() ?? default;
item.StartTimeDic = System.Text.Json.JsonSerializer.Deserialize<Dictionary<RedisChannelEnum, DateTime>>(data[2]) ?? null; item.StartTimeDic = data[2]==null?null: System.Text.Json.JsonSerializer.Deserialize<Dictionary<RedisChannelEnum, DateTime>>(data[2]) ?? null;
item.ErrorMessage = data[3]; item.ErrorMessage = data[3];
rowRestartLoading = false; rowRestartLoading = false;
var statusStr = "wait"; var statusStr = "wait";
@ -138,7 +138,7 @@ namespace Learn.VideoAnalysis.Components.Pages
statusStr= "wait"; statusStr= "wait";
else if (!string.IsNullOrEmpty(rowData.Data.ErrorMessage)) else if (!string.IsNullOrEmpty(rowData.Data.ErrorMessage))
statusStr= "error"; statusStr= "error";
else if (dic.ContainsKey(RedisChannelEnum.EndTask)) else if (dic.ContainsKey(RedisChannelEnum.))
statusStr= "finish"; statusStr= "finish";
item.TaskStatus = statusStr; item.TaskStatus = statusStr;
StateHasChanged(); StateHasChanged();

View File

@ -6,8 +6,7 @@
<div id="segmentsContainer" class="sc"> <div id="segmentsContainer" class="sc">
<h2> <h2>
<button class="gudingBtn" onclick="gd(this)">🔒</button> <button class="gudingBtn" onclick="gd(this)">🔒</button></h2>
@nowTask.MediaName</h2>
@for (int i = 0; i < videoKnows.Length; i++) @for (int i = 0; i < videoKnows.Length; i++)
{ {
var item = videoKnows[i]; var item = videoKnows[i];
@ -20,16 +19,19 @@
</div> </div>
<div>概览: @item.Content</div> <div>概览: @item.Content</div>
<br /> <br />
@foreach (var q in item.QuestionArr) @if (item.QuestionArr != null)
{ {
@foreach (var q in item.QuestionArr)
<div class="knowQuestion" onclick="spClickTime(@q.StartTime)"> {
<h3>问题: <span class="kSpan">@q.StartTime 秒</span></h3>
<div class="kSpan">@q.TopicStem</div> <div class="knowQuestion" onclick="spClickTime(@q.StartTime)">
<div >@q.Question</div> <h3>问题: <span class="kSpan">@q.StartTime 秒</span></h3>
<img style="text-align:center" src="@q.ImageUrl" width="320" height="180" /> <div class="kSpan">@q.TopicStem</div>
</div> <div>@q.Question</div>
<br /> <img style="text-align:center" src="@q.PPTImageUrl" width="320" height="180" />
</div>
<br />
}
} }
<br /> <br />
<br /> <br />

View File

@ -95,7 +95,7 @@ namespace Learn.VideoAnalysis.Components.Pages
}).ToArray(); }).ToArray();
videoPath = AppCommon.GetVideoPath(nowTask.Id.ToString()); videoPath = AppCommon.GetVideoPath(nowTask.Id.ToString());
if (nowTask.VideoType == AttachmentsInfoType.Review) if (nowTask.VideoType == AttachmentsInfoType.)
{ {
var questionArr = await videoQuestionDB var questionArr = await videoQuestionDB
.AsQueryable().Where(s => s.VideoTaskId == nowTask.Id) .AsQueryable().Where(s => s.VideoTaskId == nowTask.Id)

View File

@ -4,7 +4,6 @@ using Microsoft.OpenApi.Models;
using VideoAnalysisCore.AICore.SherpaOnnx; using VideoAnalysisCore.AICore.SherpaOnnx;
using Mapster; using Mapster;
using VideoAnalysisCore.AICore.GPT; using VideoAnalysisCore.AICore.GPT;
using VideoAnalysisCore.AICore.GPT.KIMI;
using VideoAnalysisCore.AICore.GPT.ChatGPT; using VideoAnalysisCore.AICore.GPT.ChatGPT;
using Microsoft.Extensions.FileProviders; using Microsoft.Extensions.FileProviders;
using VideoAnalysisCore.AICore.GPT.DeepSeek; using VideoAnalysisCore.AICore.GPT.DeepSeek;

View File

@ -22,6 +22,14 @@
"FFmpeg": { "FFmpeg": {
" TimeSlice": 600 " TimeSlice": 600
}, },
"AliyunOSS": {
"AccessKeyId": "LTAI5tDC6p9h747B7FHbgwkH",
"AccessKeySecret": "vRKgmbp1LB05LaGOjh3ZrZxbHSLYLF",
"BucketDomain": "https://learn-videoanalysis.oss-cn-chengdu.aliyuncs.com",
"Region": "cn-chengdu",
"BucketName": "learn-videoanalysis",
"EndPoint": "oss-cn-chengdu.aliyuncs.com" //
},
"DB": { "DB": {
//"ConnectionString": "AllowLoadLocalInfile=true;Server=10.255.255.3;Port=3306;Database=learn.videoanalysis;User ID=marking;Password=qwe123!@#;CharSet=utf8mb4;pooling=true;SslMode=None", //"ConnectionString": "AllowLoadLocalInfile=true;Server=10.255.255.3;Port=3306;Database=learn.videoanalysis;User ID=marking;Password=qwe123!@#;CharSet=utf8mb4;pooling=true;SslMode=None",
"ConnectionString": "AllowLoadLocalInfile=true;Server=rm-2vc20nd3d11g0oh6g2o.rwlb.cn-chengdu.rds.aliyuncs.com;User ID=marking;Password=poiuytPOIUYT098765)(*&^%;Port=3306;Database=learn.videoanalysis;CharSet=utf8mb4;pooling=true;SslMode=None", "ConnectionString": "AllowLoadLocalInfile=true;Server=rm-2vc20nd3d11g0oh6g2o.rwlb.cn-chengdu.rds.aliyuncs.com;User ID=marking;Password=poiuytPOIUYT098765)(*&^%;Port=3306;Database=learn.videoanalysis;CharSet=utf8mb4;pooling=true;SslMode=None",

View File

@ -68,7 +68,7 @@ namespace VideoAnalysisCore.AICore.FFMPGE
RedisExpand.SetTaskProgress(task, "Frame=>50%"); RedisExpand.SetTaskProgress(task, "Frame=>50%");
Image<Rgb24> prevFrame = null; Image<Rgb24> prevFrame = null;
var keyFrames = new List<int>(); var keyFrames = new List<int>(5);
foreach (var frameFile in frameFiles) foreach (var frameFile in frameFiles)
{ {
using (var currFrame = Image.Load<Rgb24>(frameFile)) using (var currFrame = Image.Load<Rgb24>(frameFile))
@ -171,22 +171,22 @@ namespace VideoAnalysisCore.AICore.FFMPGE
ffmpeg.Error += (sender, e) => ffmpeg.Error += (sender, e) =>
{ {
var ee = new Exception($"音频转码出现异常 \r\n[{e.Input.Name} => {e.Output.Name}]: 错误: {e.Exception.Message}"); var ee = new Exception($"音频转码出现异常 \r\n[{e.Input.Name} => {e.Output.Name}]: 错误: {e.Exception.Message}");
RedisExpand.SetTaskErrorMessage(long.Parse(task), ee);
throw ee; throw ee;
}; };
var conversionOptions = new ConversionOptions var conversionOptions = new ConversionOptions
{ {
ExtraArguments = "-ar 16000 -ac 1" ExtraArguments = "-ar 16000 -ac 1"
//+ (AppCommon.AppSetting.FFmpeg.TimeSlice == 0
//?string.Empty
//: $"-f segment -reset_timestamps 1 -segment_time {AppCommon.AppSetting.FFmpeg.TimeSlice}")
}; };
var res = await ffmpeg.ConvertAsync(inputFile, outputFile, conversionOptions); try
{
await ffmpeg.ConvertAsync(inputFile, outputFile, conversionOptions);
}
catch
{
throw;
}
Console.WriteLine($"{DateTime.Now}=>音频转码完成");
//加入下一队列
RedisExpand.InsertChannel(RedisChannelEnum.ParsingCaptions, task);
} }
} }

View File

@ -16,5 +16,11 @@ namespace VideoAnalysisCore.AICore.GPT
/// <param name="task">任务id</param> /// <param name="task">任务id</param>
/// <returns></returns> /// <returns></returns>
public Task<TaskRes> GetKnow(string task); public Task<TaskRes> GetKnow(string task);
/// <summary>
/// 获取 视频分段内的 试题
/// </summary>
/// <param name="task">任务id</param>
/// <returns></returns>
public Task GetVideoQuestion(string task);
} }
} }

View File

@ -82,9 +82,8 @@ namespace VideoAnalysisCore.AICore.GPT.ChatGPT
.Where(s => s.Course_Id == 27 .Where(s => s.Course_Id == 27
&& s.Depth == 2) && s.Depth == 2)
.Select(s => s.Name).ToArrayAsync(); .Select(s => s.Name).ToArrayAsync();
string title = taskInfo.MediaName;
var fileNameResFormat = "{授课章节: string|null, 授课内容:string}"; var fileNameResFormat = "{授课章节: string|null, 授课内容:string}";
var fileNamePostMessages = title + var fileNamePostMessages =
" 这是一堂课的标题,请你帮我分析一些关于课堂方面的内容." + " 这是一堂课的标题,请你帮我分析一些关于课堂方面的内容." +
$"1.分析出高中{subject}课堂授课的主要章节(例如 章节: 数列),章节范围限定在[{string.Join(',', xkwKnows)}]范围." + $"1.分析出高中{subject}课堂授课的主要章节(例如 章节: 数列),章节范围限定在[{string.Join(',', xkwKnows)}]范围." +
$"2.分析出这堂课的主要授课内容." + $"2.分析出这堂课的主要授课内容." +
@ -184,7 +183,6 @@ namespace VideoAnalysisCore.AICore.GPT.ChatGPT
var gptRes = new TaskRes(captions); var gptRes = new TaskRes(captions);
await RedisExpand.Redis await RedisExpand.Redis
.HMSetAsync(RedisExpandKey.Task(task), "ChatAnalysis", gptRes); .HMSetAsync(RedisExpandKey.Task(task), "ChatAnalysis", gptRes);
RedisExpand.InsertChannel(RedisChannelEnum.EndTask, task);
return gptRes; return gptRes;
} }
public async Task<T> ChatAsync<T>(string task, string postMessages, string postMessages1, string resFormat) public async Task<T> ChatAsync<T>(string task, string postMessages, string postMessages1, string resFormat)
@ -417,9 +415,13 @@ namespace VideoAnalysisCore.AICore.GPT.ChatGPT
await RedisExpand.Redis await RedisExpand.Redis
.HMSetAsync(RedisExpandKey.Task(task), "ChatAnalysis", gptRes); .HMSetAsync(RedisExpandKey.Task(task), "ChatAnalysis", gptRes);
RedisExpand.InsertChannel(RedisChannelEnum.EndTask, task);
return gptRes; return gptRes;
} }
public Task GetVideoQuestion(string task)
{
throw new NotImplementedException();
}
} }
} }

View File

@ -7,7 +7,6 @@ using System.Net.Http;
using Newtonsoft.Json; using Newtonsoft.Json;
using System.Net.Http.Json; using System.Net.Http.Json;
using System.Net; using System.Net;
using VideoAnalysisCore.AICore.GPT.KIMI;
using System.Threading; using System.Threading;
using System; using System;
using System.IO; using System.IO;
@ -121,8 +120,7 @@ namespace VideoAnalysisCore.AICore.GPT.DeepSeek
Console.WriteLine(e.Message); Console.WriteLine(e.Message);
Console.WriteLine(e.StackTrace); Console.WriteLine(e.StackTrace);
Console.WriteLine("=============================================="); Console.WriteLine("==============================================");
Thread.Sleep(1000);
Thread.Sleep(1000);
} }
} }
throw errorMSG.Last(s => s != null); throw errorMSG.Last(s => s != null);

View File

@ -3,7 +3,6 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using VideoAnalysisCore.AICore.GPT.KIMI;
namespace VideoAnalysisCore.AICore.GPT.DeepSeek namespace VideoAnalysisCore.AICore.GPT.DeepSeek
{ {

View File

@ -19,6 +19,7 @@ using FFmpeg.NET.Services;
using Aliyun.OSS; using Aliyun.OSS;
using Yitter.IdGenerator; using Yitter.IdGenerator;
using VideoAnalysisCore.Common.Expand; using VideoAnalysisCore.Common.Expand;
using System.Collections.Generic;
namespace VideoAnalysisCore.AICore.GPT.DeepSeek namespace VideoAnalysisCore.AICore.GPT.DeepSeek
{ {
@ -57,18 +58,9 @@ namespace VideoAnalysisCore.AICore.GPT.DeepSeek
/// 获取内容对应的章节 /// 获取内容对应的章节
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
private async Task<List<VideoKonwPoint>> GetVideoKnow(List<VideoKnowRes> questionRes, VideoTask taskInfo, string sections, int course_Id) private async Task<List<VideoKonwPoint>> GetVideoKnow(List<VideoKnowRes> questionRes, VideoTask taskInfo,
string sections, List<KnowledgeInfo> knowledgeInfos)
{ {
var know = await knowledgeInfoDB.GetFirstAsync(s => s.Course_Id == course_Id && s.Name == sections);
if (know is null)
throw new Exception("未能找到对应知识点=>" + sections);
var subject = taskInfo.Subject.ToString();
var kInfo = await knowledgeInfoDB.GetByIdAsync(know.Parent_Id);
var knowledgeInfos = await knowledgeInfoDB.AsQueryable()
.ToChildListAsync(s => s.Parent_Id, kInfo.Parent_Id == 0 ? kInfo.Id : kInfo.Parent_Id);
var knows = string.Join(',', knowledgeInfos.Select(s => s.Id + "|" + s.Name)); var knows = string.Join(',', knowledgeInfos.Select(s => s.Id + "|" + s.Name));
var knowDic = knowledgeInfos var knowDic = knowledgeInfos
.OrderBy(s => s.Id) .OrderBy(s => s.Id)
@ -79,7 +71,7 @@ namespace VideoAnalysisCore.AICore.GPT.DeepSeek
var thems = JsonSerializer.Serialize(questionRes.Adapt<VideoKnowQueryDto[]>());// string.Join(',', questionRes.Select(s => s.StartTime + "->" + s.Theme)); var thems = JsonSerializer.Serialize(questionRes.Adapt<VideoKnowQueryDto[]>());// string.Join(',', questionRes.Select(s => s.StartTime + "->" + s.Theme));
var checkResFormat1 = """[{"StartTime":开始秒(number),"KnowPoint":知识点名称(string),"KnowPointId":知识点Id(string)}]"""; var checkResFormat1 = """[{"StartTime":开始秒(number),"KnowPoint":知识点名称(string),"KnowPointId":知识点Id(string)}]""";
var knowMessages = var knowMessages =
$"我针对{subject}课堂授课视频分析出了视频的授课阶段片段。" + $"我针对{taskInfo.Subject}课堂授课视频分析出了视频的授课阶段片段。" +
$"现在需要你通过每个片段的内容总结来分配正确的知识点(单个片段允许多个知识点用逗号','分割)。" + $"现在需要你通过每个片段的内容总结来分配正确的知识点(单个片段允许多个知识点用逗号','分割)。" +
$"这是我的分段 {thems}。" + $"这是我的分段 {thems}。" +
$"课堂内容与{sections}章节相关" + $"课堂内容与{sections}章节相关" +
@ -136,7 +128,6 @@ namespace VideoAnalysisCore.AICore.GPT.DeepSeek
&& (s.Depth == 3 && (s.Depth == 3
|| s.Depth == 2)) || s.Depth == 2))
.Select(s => s.Name).ToArrayAsync(); .Select(s => s.Name).ToArrayAsync();
string title = taskInfo.MediaName;
var captionsArr = JsonSerializer.Deserialize<SenseVoiceRes[]>(taskInfo.Captions); var captionsArr = JsonSerializer.Deserialize<SenseVoiceRes[]>(taskInfo.Captions);
var fileNameResFormat = "{授课章节: string|null}"; var fileNameResFormat = "{授课章节: string|null}";
@ -173,7 +164,7 @@ namespace VideoAnalysisCore.AICore.GPT.DeepSeek
{ {
//校验结果质量 //校验结果质量
var thems = JsonSerializer.Serialize(questionRes.Adapt<VideoKnowQueryDto[]>()); var thems = JsonSerializer.Serialize(questionRes.Adapt<VideoKnowQueryDto[]>());
var pptFormat = taskInfo.VideoType==AttachmentsInfoType.PPT var pptFormat = taskInfo.VideoType==AttachmentsInfoType.
? "这堂课是习题课,所讲解内容都是试题。" ? "这堂课是习题课,所讲解内容都是试题。"
: string.Empty; : string.Empty;
var checkResFormat = """{"Score":打分(number),"Evaluation":评价(string)""";//,"Data":优化后的分段(array)}"""; var checkResFormat = """{"Score":打分(number),"Evaluation":评价(string)""";//,"Data":优化后的分段(array)}""";
@ -271,7 +262,7 @@ namespace VideoAnalysisCore.AICore.GPT.DeepSeek
: $"图像视频中授课内容PPT发生了变化的时间节点是{taskInfo.PPTKeyFrame},授课阶段结果可以参考这些时间节点。"; : $"图像视频中授课内容PPT发生了变化的时间节点是{taskInfo.PPTKeyFrame},授课阶段结果可以参考这些时间节点。";
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 exerciseClass = taskInfo?.VideoType == AttachmentsInfoType.Review var exerciseClass = taskInfo?.VideoType == AttachmentsInfoType.
? $"但是本堂课是习题课,所以每个阶段是不同的例题讲解内容。" ? $"但是本堂课是习题课,所以每个阶段是不同的例题讲解内容。"
: string.Empty; : string.Empty;
//$"请注意 本次分析的视频字幕只是其中一部分 不需要分析出所有类型的授课阶段。"; //$"请注意 本次分析的视频字幕只是其中一部分 不需要分析出所有类型的授课阶段。";
@ -290,7 +281,7 @@ namespace VideoAnalysisCore.AICore.GPT.DeepSeek
Console.WriteLine(DateTime.Now + $"=>{taskInfo.Id.ToString()}.开始分析视频内容 {tryCount}"); Console.WriteLine(DateTime.Now + $"=>{taskInfo.Id.ToString()}.开始分析视频内容 {tryCount}");
var resData = await ChatAsync<VideoKnowRes[]>(taskInfo.Id.ToString(), postMessages, "分析字幕"); var resData = await ChatAsync<VideoKnowRes[]>(taskInfo.Id.ToString(), postMessages, "分析字幕");
if (taskInfo?.VideoType == AttachmentsInfoType.Review) if (taskInfo?.VideoType == AttachmentsInfoType.)
foreach (var item in resData) foreach (var item in resData)
item.Stage = StageEnum..ToString(); item.Stage = StageEnum..ToString();
questionRes.AddRange(resData); questionRes.AddRange(resData);
@ -378,22 +369,27 @@ namespace VideoAnalysisCore.AICore.GPT.DeepSeek
/// 提取试题 /// 提取试题
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
private async Task<SenseVoiceRes[]> AnalysisVideoQuestions(VideoTask taskInfo) private async Task<SenseVoiceRes[]> AnalysisVideoQuestions(VideoTask taskInfo, List<KnowledgeInfo> knowledgeInfos)
{ {
Console.WriteLine(DateTime.Now + $"=>{taskInfo.Id} 提取试题"); Console.WriteLine(DateTime.Now + $"=>{taskInfo.Id} 提取试题");
if (taskInfo is null || string.IsNullOrEmpty(taskInfo.PPTKeyFrame)) if (taskInfo is null || string.IsNullOrEmpty(taskInfo.PPTKeyFrame))
return null; return null;
var farmeArr = JsonSerializer.Deserialize<int[]>(taskInfo.PPTKeyFrame); var farmeArr = JsonSerializer.Deserialize<int[]>(taskInfo.PPTKeyFrame);
var knowArrStr = string.Join(',', knowledgeInfos.Select(s => s.Id + "|" + s.Name));
var videoKnowArr = await videoKonwPointDB.GetListAsync(s => s.VideoTaskId == taskInfo.Id); var videoKnowArr = await videoKonwPointDB.GetListAsync(s => s.VideoTaskId == taskInfo.Id);
var videoKnowDic = knowledgeInfos
.GroupBy(s => s.Id)
.ToDictionary(s => s.Key, s => s.First());
var insertData =new List<VideoQuestionOSSDto>(); var insertData =new List<VideoQuestionOSSDto>();
var insertQuestionKonw = new List<VideoQuestionKonw>(); var insertQuestionKonw = new List<VideoQuestionKonw>();
foreach (var item in farmeArr) foreach (var item in farmeArr)
{ {
var knowInfoArr = videoKnowArr var knowInfoArr = videoKnowArr
.Where(s => item+5 >= s.StartTime && item+5 <= s.EndTime) .Where(s => item+20 >= s.StartTime && item < s.EndTime)
.ToArray(); .ToArray();
if (knowInfoArr is null || knowInfoArr.Count() ==0) if (knowInfoArr is null || knowInfoArr.Count() ==0)
return null; continue;
var tryCount = 50; var tryCount = 50;
while (tryCount>1) while (tryCount>1)
{ {
@ -409,20 +405,24 @@ namespace VideoAnalysisCore.AICore.GPT.DeepSeek
break; break;
Console.WriteLine(DateTime.Now + $"=>{taskInfo.Id} 提取{knowInfoArr.First().StartTime}秒试题的试题内容"); Console.WriteLine(DateTime.Now + $"=>{taskInfo.Id} 提取{knowInfoArr.First().StartTime}秒试题的试题内容");
Console.WriteLine( sRes.Result.res.value); Console.WriteLine( sRes.Result.res.value);
var knowArr=JsonSerializer.Serialize(knowInfoArr.Select(s => new { s.KnowPointId, s.KnowPoint })); //var knowArr=JsonSerializer.Serialize(knowInfoArr.Select(s => new { s.KnowPointId, s.KnowPoint }));
var resFormat = """[{"Type":string(试题类型),"TopicStem":string(试题题干),"QuestionArr":[{"Question":string(子问题),"KnowPointId":(string)知识点ID}]}]"""; var resFormat = """[{"Type":string(试题类型),"TopicStem":string(试题题干),"QuestionArr":[{"Question":string(子问题),"KnowPointId":(string)知识点ID}]}]""";
var postMessages = var postMessages =
$"我将提供一段内容是md格式的试题内容字符串。" + $"我将提供一段内容是Markdown格式的试题。" +
$"请提取出其中的试题内容试,题干以及每个试题的问题。并且为每个试题关联上在我限定范围内的知识点(多个则用逗号分割)。" + $"请提取出其中有效的试题内容(包括 题干,公式试题内提出的问题 )。" +
$"知识点格式(json格式)范围[{knowArr}]。" + $"为每个问题关联上限定范围内的知识点(多个则用逗号分割),知识点格式 (知识点Id|知识点名称)知识点范围[{knowArrStr}]。" +
$"排除不是试题内容的文字,优化公式排版并且去除题号,但不要修改latex数学公式。" + $"排除不是试题内容的文字,优化试题排版并且去除题号,尽量保留latex数学公式。" +
$"如果存在多道题,则需要拆分成为多个试题对象!" + $"如果存在多道题,则需要拆分成为多个试题对象!" +
$"试题的类型约束在 填空题/判断题/选择题/解答题 范围内。" + $"试题的类型约束在 填空题/判断题/选择题/解答题/填空题 范围内。" +
$"请检查我提供的字符串内容,如不包含问题试题则返回`[]`" + $"如果是有效试题且题干中存在下划线则试题的题型应该是填空题。" +
$"请检查我提供的字符串内容,如果不能识别知识点则不处理知识点,如不包含问题试题则返回`[]`" +
$"输出内容只返回json格式为({resFormat})" + $"输出内容只返回json格式为({resFormat})" +
$"以下是试题内容" + $"以下是试题内容" +
$"`{sRes.Result.res.value}`"; $"`{sRes.Result.res.value}`";
var resData = await ChatAsync<VideoQuestionOSSDto[]>(taskInfo.Id.ToString(), postMessages, "提取试题", "deepseek-chat"); var resData = await ChatAsync<VideoQuestionOSSDto[]>(taskInfo.Id.ToString(), postMessages, "提取试题", "deepseek-chat");
//var resData = await ChatAsync<VideoQuestionOSSDto[]>(taskInfo.Id.ToString(), postMessages, "提取试题");
if(resData is null || resData.Count()==0)
break;
foreach (var q in resData) foreach (var q in resData)
{ {
var TopicId = YitIdHelper.NextId(); var TopicId = YitIdHelper.NextId();
@ -436,16 +436,22 @@ namespace VideoAnalysisCore.AICore.GPT.DeepSeek
vq.StageId = knowInfoArr.First().StageId; vq.StageId = knowInfoArr.First().StageId;
vq.Question = qt.Question; vq.Question = qt.Question;
vq.TopicId = TopicId; vq.TopicId = TopicId;
vq.Type = q.Type;
if(string.IsNullOrEmpty(qt.KnowPointId))
continue;//重试
insertData.Add(vq); insertData.Add(vq);
foreach (var kid in qt.KnowPointId.Split(",")) foreach (var kid in qt.KnowPointId.Split(","))
{ {
var tryOk = int.TryParse(kid.Split("|").First(),out int kidd);
if(!tryOk || !videoKnowDic.ContainsKey(kidd))
continue;//跳过未识别知识点
insertQuestionKonw.Add(new VideoQuestionKonw() insertQuestionKonw.Add(new VideoQuestionKonw()
{ {
KnowPoint = knowInfoArr.First(s => s.KnowPointId == kid).KnowPoint, KnowPoint = videoKnowDic[kidd].Name,
KnowPointId = kid, KnowPointId = kid,
StageId = q.StageId, StageId = q.StageId,
VideoTaskId = q.VideoTaskId, VideoTaskId = vq.VideoTaskId,
VideoQuestionId = q.Id, VideoQuestionId = vq.Id,
}); });
} }
} }
@ -467,7 +473,6 @@ namespace VideoAnalysisCore.AICore.GPT.DeepSeek
await videoQuestionKonwDB.AsDeleteable() await videoQuestionKonwDB.AsDeleteable()
.Where(s => s.VideoTaskId == taskInfo.Id) .Where(s => s.VideoTaskId == taskInfo.Id)
.ExecuteCommandAsync(); .ExecuteCommandAsync();
await videoQuestionDB.InsertRangeAsync(insertData.Adapt<VideoQuestion[]>()); await videoQuestionDB.InsertRangeAsync(insertData.Adapt<VideoQuestion[]>());
await videoQuestionKonwDB.InsertRangeAsync(insertQuestionKonw); await videoQuestionKonwDB.InsertRangeAsync(insertQuestionKonw);
@ -502,6 +507,14 @@ namespace VideoAnalysisCore.AICore.GPT.DeepSeek
var captionsArr = JsonSerializer.Deserialize<SenseVoiceRes[]>(taskInfo.Captions); var captionsArr = JsonSerializer.Deserialize<SenseVoiceRes[]>(taskInfo.Captions);
//处理视频授课章节 //处理视频授课章节
var sections = await GetSections(taskInfo, Course_Id); var sections = await GetSections(taskInfo, Course_Id);
var know = await knowledgeInfoDB.GetFirstAsync(s => s.Course_Id == Course_Id && s.Name == sections);
if (know is null)
throw new Exception("未能找到对应知识点=>" + sections);
var kInfo = await knowledgeInfoDB.GetByIdAsync(know.Parent_Id);
var knowledgeInfos = await knowledgeInfoDB.AsQueryable()
.ToChildListAsync(s => s.Parent_Id, kInfo.Parent_Id == 0 ? kInfo.Id : kInfo.Parent_Id);
//AI优化字幕 //AI优化字幕
captionsArr = await OptimizeSubtitles(taskInfo, captionsArr, sections); captionsArr = await OptimizeSubtitles(taskInfo, captionsArr, sections);
//合并字幕 //合并字幕
@ -516,7 +529,7 @@ namespace VideoAnalysisCore.AICore.GPT.DeepSeek
if (questionRes.Count == 0) continue; if (questionRes.Count == 0) continue;
//处理分段 知识点 //处理分段 知识点
var insertData = await GetVideoKnow(questionRes, taskInfo, sections, Course_Id); var insertData = await GetVideoKnow(questionRes, taskInfo, sections, knowledgeInfos);
//校验结果质量 //校验结果质量
var checkRes = await VerifySpanQuality(questionRes, taskInfo, captions, sections, Course_Id); var checkRes = await VerifySpanQuality(questionRes, taskInfo, captions, sections, Course_Id);
@ -543,11 +556,50 @@ namespace VideoAnalysisCore.AICore.GPT.DeepSeek
await RedisExpand.Redis await RedisExpand.Redis
.HMSetAsync(RedisExpandKey.Task(task), "VideoKnows", questionRes); .HMSetAsync(RedisExpandKey.Task(task), "VideoKnows", questionRes);
if (taskInfo.VideoType == AttachmentsInfoType.Review)
await AnalysisVideoQuestions(taskInfo);
RedisExpand.InsertChannel(RedisChannelEnum.EndTask, task);
return null; return null;
} }
/// <summary>
/// 获取 视频分段内的 试题
/// </summary>
/// <param name="task">任务id</param>
/// <returns></returns>
public async Task GetVideoQuestion(string task)
{
var taskId = long.Parse(task);
var taskInfo = await videoTaskDB.AsQueryable()
.Where(s => s.Id == taskId)
.FirstAsync();
if (taskInfo.VideoType != AttachmentsInfoType.)
return;
var subject = taskInfo.Subject.ToString();
var Course_Id = 27;
switch (taskInfo.Type)//处理不同任务类型的知识点树
{
case TaskTypeEnum._中职视频分段:
Course_Id = 51;
break;
case TaskTypeEnum._视频分段:
default:
Course_Id = 27;
break;
}
var captionsArr = JsonSerializer.Deserialize<SenseVoiceRes[]>(taskInfo.Captions);
//处理视频授课章节
var sections = await GetSections(taskInfo, Course_Id);
var know = await knowledgeInfoDB.GetFirstAsync(s => s.Course_Id == Course_Id && s.Name == sections);
if (know is null)
throw new Exception("未能找到对应知识点=>" + sections);
var kInfo = await knowledgeInfoDB.GetByIdAsync(know.Parent_Id);
var knowledgeInfos = await knowledgeInfoDB.AsQueryable()
.ToChildListAsync(s => s.Parent_Id, kInfo.Parent_Id == 0 ? kInfo.Id : kInfo.Parent_Id);
//开始分析复习课 试题
await AnalysisVideoQuestions(taskInfo, knowledgeInfos);
return;
}
} }
} }

File diff suppressed because one or more lines are too long

View File

@ -1,356 +0,0 @@
using VideoAnalysisCore.Common;
using System.Net.Http.Headers;
using System.Text;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json.Linq;
using System.Net.Http;
using Newtonsoft.Json;
using System.Net.Http.Json;
using System.Net;
using Azure;
using System.Reflection.PortableExecutable;
using static System.Runtime.InteropServices.JavaScript.JSType;
/// <summary>
/// https://platform.moonshot.cn/docs/api-reference
/// </summary>
namespace VideoAnalysisCore.AICore.GPT.KIMI
{
public class MoonshotClient
{
private readonly ILogger<MoonshotClient> _logger;
private readonly IHttpClientFactory _httpClientFactory;
public MoonshotClient(ILogger<MoonshotClient> logger, IHttpClientFactory httpClientFactory)
{
_logger = logger;
_httpClientFactory = httpClientFactory;
}
/// <summary>
/// list models
/// </summary>
/// <returns></returns>
public async Task<ModelListResp> ListModels()
{
var response = await GetAsync("/v1/models");
return await ParseResp<ModelListResp>(response);
}
/// <summary>
/// Chat
/// </summary>
/// <param name="requestBody"></param>
/// <returns>Return HttpResponseMessage for SSE</returns>
public async Task<ChatRes?> Chat(string requestBody)
{
var chatResp = await PostJsonStreamAsync("/v1/chat/completions", requestBody);
return await chatResp.Content.ReadFromJsonAsync<ChatRes>();
}
/// <summary>
/// ChatSSE[流式传输 更稳定]
/// </summary>
/// <param name="chatReq"></param>
/// <returns>Return HttpResponseMessage for SSE</returns>
public async Task<(Usage u, string res)?> ChatSSE(ChatReq chatReq)
{
chatReq.stream = true;
var requestBody = System.Text.Json.JsonSerializer.Serialize(chatReq);
var chatResp = await PostJsonStreamAsync("/v1/chat/completions", requestBody);
using var stream = await chatResp.Content.ReadAsStreamAsync();
using var reader = new StreamReader(stream, Encoding.UTF8);
string line;
StringBuilder messageBuilder = new StringBuilder();
ChatResSSE lastChat = new ChatResSSE();
while ((line = await reader.ReadLineAsync()) != null)
{
if (line.EndsWith("[DONE]"))
{
// 表示一条消息结束
string message = messageBuilder.ToString();
messageBuilder.Clear();
var u = lastChat?.choices?.FirstOrDefault()?.usage;
if (u == null || string.IsNullOrEmpty(message))
return null;
return (u, message);
}
else if (line.StartsWith("data:"))
{
try
{
var data = System.Text.Json.JsonSerializer.Deserialize<ChatResSSE>(line.Substring("data:".Length).Trim());
lastChat = data;
var str = data?.choices.FirstOrDefault()?.delta.content;
if (!string.IsNullOrEmpty(str))
messageBuilder.Append(str);
}
catch (Exception e)
{
Console.WriteLine("异常 ChatSSE=>");
Console.WriteLine(line);
Console.WriteLine(e.Message);
Console.WriteLine(e.StackTrace);
}
}
}
return null;
}
/// <summary>
/// Chat
/// </summary>
/// <param name="chatReq"></param>
/// <returns>Return HttpResponseMessage for SSE</returns>
public async Task<(Usage u, string res)?> Chat(ChatReq chatReq)
{
var requestBody = System.Text.Json.JsonSerializer.Serialize(chatReq);
var chatResp = await PostJsonStreamAsync("/v1/chat/completions", requestBody);
var res = await chatResp.Content.ReadFromJsonAsync<ChatRes>();
var chatResContent = res?.choices.FirstOrDefault()?.message.content.Trim();
if (res is null || res.error != null)
throw new Exception($"KIMI模型返回异常 Chat 返回参数: " +
$" {System.Text.Json.JsonSerializer.Serialize(res)}");
if (string.IsNullOrEmpty(chatResContent))
return null;
return (res.usage, chatResContent);
}
/// <summary>
/// 计算token长度
/// </summary>
/// <param name="chatReqText">文本</param>
/// <returns></returns>
/// <exception cref="Exception"></exception>
public async Task<int?> GetAsTiMateTokenCount(string chatReqText)
{
var reqObject = new
{
model = "moonshot-v1-128k",
messages = new List<MessagesItem>()
{
new MessagesItem(chatReqText,"system"),
}
};
var response = await PostJsonStreamAsync("/v1/tokenizers/estimate-token-count", JsonConvert.SerializeObject(reqObject));
var responseText = await response.Content.ReadAsStringAsync();
if (response.IsSuccessStatusCode)
{
var responseObj = JToken.Parse(responseText);
return responseObj?["data"]?["total_tokens"]?.ToObject<int>();
}
var error = JsonConvert.DeserializeObject<ErrorResponse>(responseText);
_logger.LogError($"{error?.error?.type}: {error?.error?.message}");
throw new Exception($"{error?.error.type}: {error?.error.message}");
}
/// <summary>
/// Get as timate token count
/// </summary>
/// <param name="chatReq"></param>
/// <returns></returns>
public async Task<int?> GetAsTiMateTokenCount(ChatReq chatReq)
{
var chatReqText = JsonConvert.SerializeObject(chatReq);
return await GetAsTiMateTokenCount(chatReqText);
}
/// <summary>
/// List files
/// </summary>
public virtual async Task<FileListResp> ListFiles()
{
var response = await GetAsync("/v1/files");
return await ParseResp<FileListResp>(response);
}
/// <summary>
/// Upload file
/// </summary>
public virtual async Task<FileItem> UploadFile(string filePath)
{
if (!File.Exists(filePath))
{
throw new FileNotFoundException($"{filePath} not found");
}
var client = _httpClientFactory.CreateClient();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", ApiKey);
var request = new HttpRequestMessage(HttpMethod.Post, $"{Host}/v1/files");
var content = new MultipartFormDataContent
{
{ new StreamContent(File.OpenRead(filePath)), "file", filePath }
};
request.Content = content;
var response = await client.SendAsync(request);
return await ParseResp<FileItem>(response);
}
/// <summary>
/// Upload file stream
/// </summary>
public virtual async Task<FileItem> UploadFileStream(Stream stream, string fileName)
{
var client = _httpClientFactory.CreateClient();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", ApiKey);
var request = new HttpRequestMessage(HttpMethod.Post, $"{Host}/v1/files");
var content = new MultipartFormDataContent
{
{ new StreamContent(stream), "file", fileName }
};
request.Content = content;
var response = await client.SendAsync(request);
return await ParseResp<FileItem>(response);
}
/// <summary>
/// Get file content
/// </summary>
public virtual async Task<FileContent> GetFileContent(string fileId)
{
var response = await GetAsync($"/v1/files/{fileId}/content");
return await ParseResp<FileContent>(response);
}
private async Task<HttpResponseMessage> GetAsync(string path)
{
var client = _httpClientFactory.CreateClient();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", ApiKey);
return await client.GetAsync(Host + path);
}
private async Task<HttpResponseMessage> PostJsonAsync(string path, string json)
{
var client = _httpClientFactory.CreateClient();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", ApiKey);
return await client.PostAsync(Host + path, new StringContent(json, Encoding.UTF8, "application/json"));
}
private async Task<HttpResponseMessage> PostJsonStreamAsync(string path, string json)
{
var uriBuilder = new UriBuilder(Host + path);
var maxRestart = 4;
var errorMSG = new Exception[maxRestart];
for (int i = 0; i < maxRestart; i++)
{
try
{
var client = _httpClientFactory.CreateClient();
client.Timeout = TimeSpan.FromSeconds(Timeout.Infinite);//超时时间20分钟
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", ApiKey);
client.DefaultRequestVersion = HttpVersion.Version20;
client.DefaultVersionPolicy = HttpVersionPolicy.RequestVersionOrLower;
client.DefaultRequestHeaders.ConnectionClose = true;
//var request = ToHttpRequest(path);
//request.Version = HttpVersion.Version20;
//request.VersionPolicy = HttpVersionPolicy.RequestVersionOrLower;
//request.Content = new StringContent(json, Encoding.UTF8, "application/json");
//return await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead);
var content = new StringContent(json, Encoding.UTF8, "application/json");
return await client.PostAsync(uriBuilder.Uri, content);
}
catch (Exception e)
{
errorMSG[i] = e;
Console.WriteLine("====================[请求异常,重试]====================");
Console.WriteLine(uriBuilder.Uri);
Console.WriteLine(e.Message);
Console.WriteLine(e.StackTrace);
Console.WriteLine("==============================================");
}
Thread.Sleep(1000);
}
throw errorMSG.Last(s => s != null);
}
private HttpRequestMessage ToHttpRequest(string path)
{
var request = new HttpRequestMessage();
var uriBuilder = new UriBuilder(Host + path);
request.RequestUri = uriBuilder.Uri;
request.Method = new HttpMethod("POST");
request.Headers.Host = new Uri(Host).Host;
return request;
}
/// <summary>
/// Parse response
/// </summary>
private async Task<T> ParseResp<T>(HttpResponseMessage response)
{
var responseText = await response.Content.ReadAsStringAsync();
if (response.IsSuccessStatusCode)
{
return JsonConvert.DeserializeObject<T>(responseText) ?? default;
}
var error = JsonConvert.DeserializeObject<ErrorResponse>(responseText);
_logger.LogError($"{error?.error.type}: {error?.error.message}");
throw new Exception($"{error?.error.type}: {error?.error.message}");
}
private static string _host = "https://api.moonshot.cn";
public static string Host
{
get
{
if (string.IsNullOrEmpty(_host) && !string.IsNullOrEmpty(AppCommon.Config.ChatGpt.KIMI.Host))
{
_host = AppCommon.Config.ChatGpt.KIMI.Host ?? "";
}
return _host;
}
set
{
_host = value;
}
}
private static string _apiKey = "sk_";
public static string ApiKey
{
get
{
if (string.IsNullOrEmpty(_apiKey) && !string.IsNullOrEmpty(AppCommon.Config.ChatGpt.KIMI.ApiKey))
{
_apiKey = AppCommon.Config.ChatGpt.KIMI.ApiKey ?? "";
}
return _apiKey;
}
set
{
_apiKey = value;
}
}
}
}

View File

@ -1,365 +0,0 @@
namespace VideoAnalysisCore.AICore.GPT.KIMI
{
public class MessagesItem
{
public MessagesItem()
{
}
public MessagesItem(string content, string role = "user", bool partial = false)
{
this.content = content;
this.role = role;
this.partial = partial;
}
/// <summary>
///
/// </summary>
public string role { get; set; }
public bool partial { get; set; } = false;
/// <summary>
///
/// </summary>
public string content { get; set; }
}
/// <summary>
/// chat请求参数
/// </summary>
public class ChatReq
{
/// <summary>
/// 使用的模型
/// <para>例如[ moonshot-v1-8k ]</para>
/// </summary>
public string model { get; set; } = "moonshot-v1-8k";
/// <summary>
/// 消息主体
/// </summary>
public List<MessagesItem> messages { get; set; }
/// <summary>
/// 使用什么采样温度,介于 0 和 1 之间。较高的值(如 0.7)将使输出更加随机,而较低的值(如 0.2)将使其更加集中和确定性
/// <para>默认为 0如果设置值域须为 [0, 1] 我们推荐 0.3,以达到较合适的效果</para>
/// </summary>
public float temperature { get; set; }
/// <summary>
/// 聊天完成时生成的最大 token 数。如果到生成了最大 token 数个结果仍然没有结束finish reason 会是 "length", 否则会是 "stop"
/// <para>这个值建议按需给个合理的值,如果不给的话,我们会给一个不错的整数比如 1024。特别要注意的是这个 max_tokens 是指您期待我们返回的 token 长度,而不是输入 + 输出的总长度。比如对一个 moonshot-v1-8k 模型,它的最大输入 + 输出总长度是 8192当输入 messages 总长度为 4096 的时候,您最多只能设置为 4096否则我们服务会返回不合法的输入参数 invalid_request_error ),并拒绝回答。如果您希望获得“输入的精确 token 数”,可以使用下面的“计算 Token” API 使用我们的计算器获得计数</para>
/// </summary>
public int? max_tokens { get; set; }
/// <summary>
/// 另一种采样方法,即模型考虑概率质量为 top_p 的标记的结果。因此0.1 意味着只考虑概率质量最高的 10% 的标记。一般情况下,我们建议改变这一点或温度,但不建议 同时改变
/// </summary>
public float? top_p { get; set; } = 1.0f;
/// <summary>
/// 为每条输入消息生成多少个结果
/// <para>默认为 1不得大于 5。特别的当 temperature 非常小靠近 0 的时候,我们只能返回 1 个结果,如果这个时候 n 已经设置并且 > 1我们的服务会返回不合法的输入参数(invalid_request_error)</para>
/// </summary>
public int? n { get; set; } = 1;
/// <summary>
/// 存在惩罚,介于-2.0到2.0之间的数字。正值会根据新生成的词汇是否出现在文本中来进行惩罚,增加模型讨论新话题的可能性
/// <para>默认为 0</para>
/// </summary>
public float? presence_penalty { get; set; } = 0;
/// <summary>
/// 频率惩罚,介于-2.0到2.0之间的数字。正值会根据新生成的词汇在文本中现有的频率来进行惩罚,减少模型一字不差重复同样话语的可能性
/// <para>默认为 0</para>
/// </summary>
public float? frequency_penalty { get; set; } = 0;
/// <summary>
/// 停止词,当全匹配这个(组)词后会停止输出,这个(组)词本身不会输出。最多不能超过 5 个字符串,每个字符串不得超过 32 字节
/// <para>默认 null</para>
/// </summary>
public List<string>? stop { get; set; }
/// <summary>
/// 是否流式返回
/// <para>false</para>
/// </summary>
public bool stream { get; set; } = false;
}
public class PermissionItem
{
/// <summary>
///
/// </summary>
public int created { get; set; }
/// <summary>
///
/// </summary>
public string id { get; set; }
/// <summary>
///
/// </summary>
public string @object { get; set; }
/// <summary>
///
/// </summary>
public string allow_create_engine { get; set; }
/// <summary>
///
/// </summary>
public string allow_sampling { get; set; }
/// <summary>
///
/// </summary>
public string allow_logprobs { get; set; }
/// <summary>
///
/// </summary>
public string allow_search_indices { get; set; }
/// <summary>
///
/// </summary>
public string allow_view { get; set; }
/// <summary>
///
/// </summary>
public string allow_fine_tuning { get; set; }
/// <summary>
///
/// </summary>
public string organization { get; set; }
/// <summary>
///
/// </summary>
public string @group { get; set; }
/// <summary>
///
/// </summary>
public string is_blocking { get; set; }
}
public class ChatResError
{
/// <summary>
/// 错误信息
/// </summary>
public string message { get; set; } = string.Empty;
/// <summary>
/// 错误类型
/// </summary>
public string type { get; set; } = string.Empty;
}
public class ChatResSSE
{
public string id { get; set; }
public int created { get; set; }
/// <summary>
/// 模型id
/// </summary>
public string model { get; set; }
/// <summary>
/// 对话
/// </summary>
public ChoiceSSE[] choices { get; set; }
}
public class ChatRes
{
public ChatResError? error { get; set; }
public string id { get; set; }
public int created { get; set; }
/// <summary>
/// 模型id
/// </summary>
public string model { get; set; }
/// <summary>
/// 对话
/// </summary>
public Choice[] choices { get; set; }
/// <summary>
/// token使用情况
/// </summary>
public Usage usage { get; set; }
}
/// <summary>
/// token使用情况
/// </summary>
public class Usage
{
/// <summary>
/// 输入token数量
/// </summary>
public int prompt_tokens { get; set; }
/// <summary>
/// 返回token数量
/// </summary>
public int completion_tokens { get; set; }
/// <summary>
/// 总计token数量
/// </summary>
public int total_tokens { get; set; }
}
public class ChoiceSSE
{
public int index { get; set; }
public Message delta { get; set; }
public string finish_reason { get; set; }
/// <summary>
/// token使用情况
/// </summary>
public Usage usage { get; set; }
}
public class Choice
{
public int index { get; set; }
public Message message { get; set; }
public string finish_reason { get; set; }
}
public class Message
{
public string role { get; set; }
public string content { get; set; }
}
public class ModelInfo
{
/// <summary>
///
/// </summary>
public int created { get; set; }
/// <summary>
///
/// </summary>
public string id { get; set; }
/// <summary>
///
/// </summary>
public string @object { get; set; }
/// <summary>
///
/// </summary>
public string owned_by { get; set; }
/// <summary>
///
/// </summary>
public List<PermissionItem> permission { get; set; }
/// <summary>
///
/// </summary>
public string root { get; set; }
/// <summary>
///
/// </summary>
public string parent { get; set; }
}
public class ModelListResp
{
/// <summary>
///
/// </summary>
public string @object { get; set; }
/// <summary>
///
/// </summary>
public List<ModelInfo> data { get; set; }
}
public class FileListResp
{
/// <summary>
///
/// </summary>
public string @object { get; set; }
/// <summary>
///
/// </summary>
public List<FileItem> data { get; set; }
}
public class FileContent
{
/// <summary>
///
/// </summary>
public string content { get; set; }
/// <summary>
///
/// </summary>
public string file_type { get; set; }
/// <summary>
///
/// </summary>
public string filename { get; set; }
/// <summary>
///
/// </summary>
public string title { get; set; }
/// <summary>
///
/// </summary>
public string type { get; set; }
}
public class FileItem
{
/// <summary>
///
/// </summary>
public string id { get; set; }
/// <summary>
///
/// </summary>
public string @object { get; set; }
/// <summary>
///
/// </summary>
public int bytes { get; set; }
/// <summary>
///
/// </summary>
public int created_at { get; set; }
/// <summary>
///
/// </summary>
public string filename { get; set; }
/// <summary>
///
/// </summary>
public string purpose { get; set; }
/// <summary>
///
/// </summary>
public string status { get; set; }
/// <summary>
///
/// </summary>
public string status_details { get; set; }
}
public class ErrorMsg
{
/// <summary>
///
/// </summary>
public string message { get; set; }
/// <summary>
///
/// </summary>
public string type { get; set; }
}
public class ErrorResponse
{
/// <summary>
///
/// </summary>
public ErrorMsg error { get; set; }
}
}

View File

@ -179,11 +179,8 @@ namespace VideoAnalysisCore.AICore.SherpaOnnx
.Where(it => it.Id == long.Parse(task)) .Where(it => it.Id == long.Parse(task))
.ExecuteCommandAsync(); .ExecuteCommandAsync();
await RedisExpand.Redis.HMSetAsync(RedisExpandKey.Task(task), "Captions", res); await RedisExpand.Redis.HMSetAsync(RedisExpandKey.Task(task), "Captions", res);
//RedisExpand.InsertChannel(Enum.RedisChannelEnum.ParsingSpeaker, task);
//分析完成视频字幕后继续接收任务 //分析完成视频字幕后继续接收任务
RedisExpand.NewTask(); RedisExpand.NewTaskAsync();
RedisExpand.InsertChannel(RedisChannelEnum.ChatModelAnalysis, task);
} }
return res; return res;
} }

View File

@ -84,8 +84,6 @@ namespace VideoAnalysisCore.AICore.SherpaOnnx
.SetColumns(it => it.Speaker == speakerStr) .SetColumns(it => it.Speaker == speakerStr)
.Where(it => it.Id == long.Parse(task)) .Where(it => it.Id == long.Parse(task))
.ExecuteCommandAsync(); .ExecuteCommandAsync();
//加入下一队列
RedisExpand.InsertChannel(RedisChannelEnum.ChatModelAnalysis, task);
} }
} }

View File

@ -46,7 +46,6 @@ namespace VideoAnalysisCore.AICore.Whisper
res.Add(new WhisperResDto(segment)); res.Add(new WhisperResDto(segment));
} }
RedisExpand.Redis.HMSet(RedisExpandKey.Task(task), "Captions", res); RedisExpand.Redis.HMSet(RedisExpandKey.Task(task), "Captions", res);
RedisExpand.InsertChannel(RedisChannelEnum.ParsingSpeaker, task);
} }
/// <summary> /// <summary>
/// 检测语言的方法 /// 检测语言的方法

View File

@ -1,5 +1,6 @@
using FreeRedis; using FreeRedis;
using Microsoft.Extensions.DependencyModel; using Microsoft.Extensions.DependencyModel;
using Microsoft.IdentityModel.Tokens;
using SqlSugar; using SqlSugar;
using SqlSugar.IOC; using SqlSugar.IOC;
using System.Collections; using System.Collections;
@ -224,6 +225,24 @@ namespace VideoAnalysisCore.Common
}; };
} }
/// <summary> /// <summary>
/// 获取下一个枚举值
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="current"></param>
/// <returns></returns>
/// <exception cref="ArgumentException"></exception>
public static T? NextEnum<T>(this T current) where T : struct, Enum
{
if (!typeof(T).IsEnum)
throw new ArgumentException("传入类型不是枚举");
T[] values = (T[])Enum.GetValues(typeof(T));
int currentIndex = Array.IndexOf(values, current);
if (currentIndex == values.Length - 1)
return null;
int nextIndex = (currentIndex + 1) % values.Length;
return values[nextIndex];
}
/// <summary>
/// 转化枚举 /// 转化枚举
/// </summary> /// </summary>
/// <param name="value"></param> /// <param name="value"></param>
@ -232,6 +251,8 @@ namespace VideoAnalysisCore.Common
{ {
try try
{ {
if(value is null || string.IsNullOrEmpty(value.ToString()))
return null;
if (Enum.TryParse<T>(value.ToString(), true, out var result) && Enum.IsDefined(typeof(T), result)) if (Enum.TryParse<T>(value.ToString(), true, out var result) && Enum.IsDefined(typeof(T), result))
return result; return result;
return null; return null;

View File

@ -127,7 +127,7 @@ namespace VideoAnalysisCore.Common
//获取资源文件 地址 //获取资源文件 地址
var taskInfo = await videoTaskDB.AsQueryable() var taskInfo = await videoTaskDB.AsQueryable()
.Where(s => s.Id == taskId).FirstAsync(); .Where(s => s.Id == taskId).FirstAsync();
if (taskInfo is null || string.IsNullOrEmpty(taskInfo.MediaName) || taskInfo.MediaName.Contains("教研")) if (taskInfo is null )
throw new Exception($"任务为null/是教研视频/没有视频课程名称"); throw new Exception($"任务为null/是教研视频/没有视频课程名称");
var fileUrl = taskInfo.MediaUrl; var fileUrl = taskInfo.MediaUrl;
if (string.IsNullOrEmpty(fileUrl)) if (string.IsNullOrEmpty(fileUrl))
@ -200,9 +200,9 @@ namespace VideoAnalysisCore.Common
(s, e) => RedisExpand.SetTaskProgress(task, "PPT->" + Math.Round(e.ProgressPercentage, 1) (s, e) => RedisExpand.SetTaskProgress(task, "PPT->" + Math.Round(e.ProgressPercentage, 1)
)); ));
} }
catch (Exception e) catch
{ {
await RedisExpand.SetTaskErrorMessage(taskId, e); throw;
} }
} }
try try
@ -210,13 +210,10 @@ namespace VideoAnalysisCore.Common
await Download(fileUrl, localPath, task + fileExtension, await Download(fileUrl, localPath, task + fileExtension,
(s, e) => RedisExpand.SetTaskProgress(task, Math.Round(e.ProgressPercentage,1) (s, e) => RedisExpand.SetTaskProgress(task, Math.Round(e.ProgressPercentage,1)
)); ));
//加入下一队列
RedisExpand.InsertChannel(RedisChannelEnum.SeparateAudio, task);
} }
catch (Exception e) catch
{ {
await RedisExpand.SetTaskErrorMessage(taskId, e); throw;
} }

View File

@ -88,10 +88,14 @@ namespace VideoAnalysisCore.Common.Expand
{ {
try try
{ {
var path = item.VideoTaskId.ToString() + "/" + Path.GetFileName(item.FilePath); var isDebug = false;
#if DEBUG
isDebug = true;
#endif
var path = (isDebug ?"debug/":string.Empty) + item.VideoTaskId.ToString() + "/" + Path.GetFileName(item.FilePath);
if (cached.Contains(item.FilePath)) if (cached.Contains(item.FilePath))
{ {
item.ImageUrl = AppCommon.Config.AliyunOSS.BucketDomain + "/" + path; item.PPTImageUrl = AppCommon.Config.AliyunOSS.BucketDomain + "/" + path;
continue; continue;
} }
using var file = File.OpenRead(item.FilePath); using var file = File.OpenRead(item.FilePath);
@ -100,7 +104,7 @@ namespace VideoAnalysisCore.Common.Expand
AppCommon.Config.AliyunOSS.BucketName, AppCommon.Config.AliyunOSS.BucketName,
path, path,
file); file);
item.ImageUrl = AppCommon.Config.AliyunOSS.BucketDomain + "/" + path; item.PPTImageUrl = AppCommon.Config.AliyunOSS.BucketDomain + "/" + path;
cached.Add(item.FilePath); cached.Add(item.FilePath);
continue; continue;
} }

View File

@ -1,9 +1,11 @@
using FreeRedis; using FreeRedis;
using FreeRedis.Internal; using FreeRedis.Internal;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using NetTaste;
using Newtonsoft.Json.Schema; using Newtonsoft.Json.Schema;
using SqlSugar.IOC; using SqlSugar.IOC;
using System; using System;
using System.Security.Cryptography;
using System.Text.Json; using System.Text.Json;
using System.Threading.Channels; using System.Threading.Channels;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -20,6 +22,7 @@ using VideoAnalysisCore.AICore.Whisper;
using VideoAnalysisCore.Model; using VideoAnalysisCore.Model;
using VideoAnalysisCore.Model.Dto; using VideoAnalysisCore.Model.Dto;
using VideoAnalysisCore.Model.Enum; using VideoAnalysisCore.Model.Enum;
using static System.Runtime.InteropServices.JavaScript.JSType;
namespace VideoAnalysisCore.Common namespace VideoAnalysisCore.Common
{ {
@ -76,11 +79,12 @@ namespace VideoAnalysisCore.Common
/// </summary> /// </summary>
public static class RedisExpand public static class RedisExpand
{ {
/// <summary> /// <summary>
/// redis 连接 /// redis 连接
/// </summary> /// </summary>
public static RedisClient Redis = new RedisClient(AppCommon.Config.Redis.ConnectionString); public static RedisClient Redis = new RedisClient(AppCommon.Config.Redis.ConnectionString);
public static Dictionary<RedisChannelEnum, Action<string>> SubscribeList = new Dictionary<RedisChannelEnum, Action<string>>(); public static Dictionary<RedisChannelEnum, Func<string,Task>> SubscribeList = new Dictionary<RedisChannelEnum, Func<string, Task>>();
/// <summary> /// <summary>
/// 队列池 /// 队列池
/// </summary> /// </summary>
@ -148,7 +152,7 @@ namespace VideoAnalysisCore.Common
/// </summary> /// </summary>
/// <param name="enum">枚举</param> /// <param name="enum">枚举</param>
/// <param name="taskId">任务id</param> /// <param name="taskId">任务id</param>
public static void InsertChannel(RedisChannelEnum @enum, object taskId) public static async Task InsertChannel(RedisChannelEnum @enum, object taskId)
{ {
if (taskId is null) throw new Exception("taskId为空"); if (taskId is null) throw new Exception("taskId为空");
if (Redis is null) throw new Exception("redis未初始化"); if (Redis is null) throw new Exception("redis未初始化");
@ -156,17 +160,31 @@ namespace VideoAnalysisCore.Common
var startTime = Redis.HMGet<Dictionary<RedisChannelEnum, DateTime>>(RedisExpandKey.Task(taskId), "StartTime").FirstOrDefault(); var startTime = Redis.HMGet<Dictionary<RedisChannelEnum, DateTime>>(RedisExpandKey.Task(taskId), "StartTime").FirstOrDefault();
if (startTime is null) if (startTime is null)
startTime = new Dictionary<RedisChannelEnum, DateTime>(); startTime = new Dictionary<RedisChannelEnum, DateTime>();
if (!startTime.ContainsKey(@enum))
startTime.Add(@enum, DateTime.Now);
else
startTime[@enum] = DateTime.Now;
Redis.HMSet(RedisExpandKey.Task(taskId), "StartTime", startTime);
if (!SubscribeList.ContainsKey(@enum)) if (!SubscribeList.ContainsKey(@enum))
throw new Exception(@enum + " 未实现"); throw new Exception(@enum + " 未实现");
var tId = taskId.ToString();
try
{
while (true)
{
if (!startTime.ContainsKey(@enum))
startTime.Add(@enum, DateTime.Now);
else
startTime[@enum] = DateTime.Now;
SubscribeList[@enum].Invoke(taskId.ToString()); Redis.HMSet(RedisExpandKey.Task(taskId), "StartTime", startTime);
await SubscribeList[@enum](tId);
var e = @enum.NextEnum();
if (e is null)
break;
@enum = e.Value;
}
}
catch (Exception ex)
{
await SetTaskErrorMessage((long)taskId, ex);
}
} }
public static async Task TaskEnd(string task) public static async Task TaskEnd(string task)
@ -176,7 +194,8 @@ namespace VideoAnalysisCore.Common
// .HMGetAsync<TaskRes>(RedisExpandKey.Task(task), "ChatAnalysis")).FirstOrDefault(); // .HMGetAsync<TaskRes>(RedisExpandKey.Task(task), "ChatAnalysis")).FirstOrDefault();
//if (gptRes is null) //if (gptRes is null)
// throw new Exception("未能读取到GPT处理结果"); // throw new Exception("未能读取到GPT处理结果");
//删除任务执行状态
await Redis.HDelAsync(RedisExpandKey.IDTask,task);
var taskData = await DbScoped.Sugar.Queryable<VideoTask>() var taskData = await DbScoped.Sugar.Queryable<VideoTask>()
.FirstAsync(s => s.Id == tId); .FirstAsync(s => s.Id == tId);
if (taskData.Captions == "[]") if (taskData.Captions == "[]")
@ -189,7 +208,7 @@ namespace VideoAnalysisCore.Common
//taskData.ChatAnalysis = JsonSerializer.Serialize(gptRes); //taskData.ChatAnalysis = JsonSerializer.Serialize(gptRes);
taskData.ChatAnalysisScore =0; taskData.ChatAnalysisScore =0;
taskData.ErrorMessage = string.Empty; taskData.ErrorMessage = string.Empty;
taskData.LastEnum = RedisChannelEnum.EndTask; taskData.LastEnum = RedisChannelEnum.;
taskData.EndTime = DateTime.Now; taskData.EndTime = DateTime.Now;
await DbScoped.Sugar.Updateable(taskData) await DbScoped.Sugar.Updateable(taskData)
.UpdateColumns(it => new .UpdateColumns(it => new
@ -214,30 +233,24 @@ namespace VideoAnalysisCore.Common
{ {
if (Redis is null) throw new Exception("redis未初始化"); if (Redis is null) throw new Exception("redis未初始化");
SubscribeList.Add(RedisChannelEnum.DownloadFile, SubscribeList.Add(RedisChannelEnum.,
(Action<string>)((msg) => { async (msg) => await TouchChannel(RedisChannelEnum., msg,
TouchChannel(RedisChannelEnum.DownloadFile, msg, (task) =>
(Func<string, Task>)((task) =>
{ {
using var scope = AppCommon.Services?.CreateScope(); using var scope = AppCommon.Services?.CreateScope();
if (scope is null || ServiceProviderServiceExtensions.GetService<DownloadFile>(scope.ServiceProvider) is null) if (scope is null || scope.ServiceProvider.GetService<DownloadFile>() is null)
throw new Exception("DownloadFile 未注入"); throw new Exception("DownloadFile 未注入");
else else
return (Task)(scope.ServiceProvider.GetService<DownloadFile>()?.RunTask(task) ?? Task.CompletedTask); return scope.ServiceProvider.GetService<DownloadFile>()?.RunTask(task) ?? Task.CompletedTask;
})); }));
})); SubscribeList.Add(RedisChannelEnum.,
async (msg) => await TouchChannel(RedisChannelEnum., msg, FFMPGEHandle.RunAsync));
SubscribeList.Add(RedisChannelEnum.SeparateAudio, SubscribeList.Add(RedisChannelEnum.,
(msg) => { TouchChannel(RedisChannelEnum.SeparateAudio, msg, FFMPGEHandle.RunAsync); }); async (msg) => await TouchChannel(RedisChannelEnum., msg, SenseVoice.RunTask));
//SubscribeList.Add(RedisChannelEnum.解析说话人,
SubscribeList.Add(RedisChannelEnum.ParsingCaptions, // async (msg) => await TouchChannel(RedisChannelEnum.解析说话人, msg, Speaker.Run));
(msg) => { TouchChannel(RedisChannelEnum.ParsingCaptions, msg, SenseVoice.RunTask); }); SubscribeList.Add(RedisChannelEnum.AI模型分析,
SubscribeList.Add(RedisChannelEnum.ParsingSpeaker, async (msg) => await TouchChannel(RedisChannelEnum.AI模型分析, msg,
(msg) => { TouchChannel(RedisChannelEnum.ParsingSpeaker, msg, Speaker.Run); });
SubscribeList.Add(RedisChannelEnum.ChatModelAnalysis,
(msg) =>
{
TouchChannel(RedisChannelEnum.ChatModelAnalysis, msg,
(task) => (task) =>
{ {
using var scope = AppCommon.Services?.CreateScope(); using var scope = AppCommon.Services?.CreateScope();
@ -245,63 +258,75 @@ namespace VideoAnalysisCore.Common
throw new Exception("IBserGPT 未注入"); throw new Exception("IBserGPT 未注入");
else else
return scope.ServiceProvider.GetService<IBserGPT>()?.GetKnow(task) ?? Task.CompletedTask; return scope.ServiceProvider.GetService<IBserGPT>()?.GetKnow(task) ?? Task.CompletedTask;
}); }));
}); SubscribeList.Add(RedisChannelEnum.AI分析试题,
SubscribeList.Add(RedisChannelEnum.EndTask, async (msg) => await TouchChannel(RedisChannelEnum.AI分析试题, msg,
(msg) => { TouchChannel(RedisChannelEnum.EndTask, msg, TaskEnd); }); (task) =>
{
using var scope = AppCommon.Services?.CreateScope();
if (scope is null || scope.ServiceProvider.GetService<IBserGPT>() is null)
throw new Exception("IBserGPT 未注入");
else
return scope.ServiceProvider.GetService<IBserGPT>()?.GetVideoQuestion(task) ?? Task.CompletedTask;
}));
SubscribeList.Add(RedisChannelEnum.,
async (msg) => await TouchChannel(RedisChannelEnum., msg, TaskEnd));
await ReceivingTaskAsync(); ReceivingTaskAsync();
} }
/// <summary> /// <summary>
/// 重新执行新任务 /// 重新执行新任务
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
public static void NewTask() public static async void NewTaskAsync()
{ {
Task.Run(async () => await Redis.DelAsync(RedisExpandKey.IDTask);
{ ReceivingTaskAsync();
await Redis.DelAsync(RedisExpandKey.IDTask);
await ReceivingTaskAsync();
});
} }
/// <summary> /// <summary>
/// 重新接收新任务 /// 重新接收新任务
/// </summary> /// </summary>
public static async Task ReceivingTaskAsync() public static void ReceivingTaskAsync()
{ {
if (AppCommon.Config.TaskSetting.IS_Server) if (AppCommon.Config.TaskSetting.IS_Server)
{ {
Console.WriteLine($"{DateTime.Now} =>服务端不接收任务"); Console.WriteLine($"{DateTime.Now} =>服务端不接收任务");
return; return;
} }
var oldTask = await Redis.GetAsync(RedisExpandKey.IDTask); Task.Run(async () =>
if (!string.IsNullOrEmpty(oldTask))
{ {
var lastEnum = (await Redis.HMGetAsync<RedisChannelEnum>(RedisExpandKey.Task(oldTask), "LastEnum")).FirstOrDefault(); var oldTask = await Redis.GetAsync(RedisExpandKey.IDTask);
await SetTaskErrorMessage(long.Parse(oldTask), null); if (!string.IsNullOrEmpty(oldTask))
InsertChannel(lastEnum, oldTask); {
return; Console.WriteLine(DateTime.Now.ToString("HH:mm:ss") + "-------------> 接收重试任务 " + oldTask);
} await ClearTaskError(long.Parse(oldTask));
if (Subscribe?.IsUnsubscribed == false)//排除重试机制后 多次接收任务导致内存泄露 var lastEnum = (await Redis.HMGetAsync<RedisChannelEnum>(RedisExpandKey.Task(oldTask), "LastEnum")).FirstOrDefault();
return; await InsertChannel(lastEnum, oldTask);
Subscribe = Redis.SubscribeList(RedisExpandKey.ChannelKey, (taskId) => return;
{ }
if (taskId is null) return; if (Subscribe?.IsUnsubscribed == false)//排除重试机制后 多次接收任务导致内存泄露
Subscribe?.Dispose(); return;
//存储当前机器的任务 Subscribe = Redis.SubscribeList(RedisExpandKey.ChannelKey, async (taskId) =>
Redis.Set(RedisExpandKey.IDTask, taskId); {
Console.WriteLine(DateTime.Now.ToString("HH:mm:ss") + "-------------> 接收到任务 " + taskId); if (taskId is null) return;
InsertChannel(RedisChannelEnum.DownloadFile, taskId); Subscribe?.Dispose();//取消接收任务监听
//存储当前机器的任务
Redis.Set(RedisExpandKey.IDTask, taskId);
Console.WriteLine(DateTime.Now.ToString("HH:mm:ss") + "-------------> 接收到任务 " + taskId);
await InsertChannel(RedisChannelEnum., taskId);
});
}); });
} }
/// <summary> /// <summary>
/// 写入任务异常 /// 写入任务异常
/// </summary> /// </summary>
/// <param name="taskID"></param> /// <param name="taskID"></param>
/// <param name="errorMessage"></param> /// <param name="ex"></param>
/// <returns></returns> /// <returns></returns>
public static async Task<bool> SetTaskErrorMessage(long taskID, Exception? ex) public static async Task<bool> SetTaskErrorMessage(long taskID, Exception? ex)
{ {
@ -315,16 +340,29 @@ namespace VideoAnalysisCore.Common
Console.WriteLine(ex.StackTrace); Console.WriteLine(ex.StackTrace);
Console.WriteLine("=============================================="); Console.WriteLine("==============================================");
//清除失败任务 重新接收任务 //清除失败任务 重新接收任务
await Redis.DelAsync(RedisExpandKey.IDTask); NewTaskAsync();
await ReceivingTaskAsync();
} }
return await SetTaskError(taskID, error);
}
/// <summary>
/// 清除 任务的错误信息
/// </summary>
/// <param name="taskID"></param>
/// <returns></returns>
public static async Task<bool> ClearTaskError(long taskID) =>await SetTaskError(taskID, string.Empty);
/// <summary>
/// 修改任务的错误信息
/// </summary>
/// <param name="taskID"></param>
/// <param name="error"></param>
/// <returns></returns>
public static async Task<bool> SetTaskError(long taskID, string? error)
{
Redis.HMSet(RedisExpandKey.Task(taskID), "ErrorMessage", error); Redis.HMSet(RedisExpandKey.Task(taskID), "ErrorMessage", error);
return await DbScoped.Sugar.Updateable<VideoTask>() return await DbScoped.Sugar.Updateable<VideoTask>()
.SetColumns(it => it.ErrorMessage == error)//SetColumns是可以叠加的 写2个就2个字段赋值 .SetColumns(it => it.ErrorMessage == error)//SetColumns是可以叠加的 写2个就2个字段赋值
.Where(it => it.Id == taskID) .Where(it => it.Id == taskID)
.ExecuteCommandAsync() == 1; .ExecuteCommandAsync() == 1;
} }
/// <summary> /// <summary>
@ -333,14 +371,14 @@ namespace VideoAnalysisCore.Common
/// <param name="key"></param> /// <param name="key"></param>
/// <param name="taskId"></param> /// <param name="taskId"></param>
/// <param name="action"></param> /// <param name="action"></param>
public static async void TouchChannel(RedisChannelEnum key, string taskId, Func<string, Task> action = null) public static async Task TouchChannel(RedisChannelEnum key, string taskId, Func<string, Task> action = null)
{ {
if (taskId is null) return; if (taskId is null) return;
var tID = long.Parse(taskId); var tID = long.Parse(taskId);
if (action is not null) if (action is not null)
{ {
var errArr = new Exception[3]; var tryCount = 1;
for (int i = 0; i < 3; i++) for (int i = 0; i < tryCount; i++)
{ {
Console.WriteLine(DateTime.Now.ToString("HH:mm:ss") + "-> 开始执行 " + key + " " + taskId); Console.WriteLine(DateTime.Now.ToString("HH:mm:ss") + "-> 开始执行 " + key + " " + taskId);
try try
@ -360,16 +398,16 @@ namespace VideoAnalysisCore.Common
} }
catch (Exception ex) catch (Exception ex)
{ {
errArr[i] = ex;
Console.WriteLine("====================[出现异常]===================="); Console.WriteLine("====================[出现异常]====================");
Console.WriteLine(ex.Message); Console.WriteLine(ex.Message);
Console.WriteLine(ex.StackTrace); Console.WriteLine(ex.StackTrace);
Console.WriteLine("=============================================="); Console.WriteLine("==============================================");
Thread.Sleep(1000); Thread.Sleep(1000);
Console.WriteLine(DateTime.Now.ToString("HH:mm:ss") + "-> 稍后后重试." + key + " " + taskId); Console.WriteLine(DateTime.Now.ToString("HH:mm:ss") + "-> 稍后后重试." + key + " " + taskId);
if (i+1== tryCount)
throw;
} }
} }
await SetTaskErrorMessage(tID, errArr.First());
} }
else else
{ {

View File

@ -146,26 +146,12 @@ namespace VideoAnalysisCore.Controllers
task.Subject = subject; task.Subject = subject;
await videoTaskDB.UpdateAsync(task); await videoTaskDB.UpdateAsync(task);
} }
//重新开始执行GPT分析 //todo重新开始执行GPT分析
RedisExpand.InsertChannel(RedisChannelEnum.ChatModelAnalysis
, task.Id);
return Ok(); return Ok();
} }
/// <summary>
/// 插入队列
/// </summary>
/// <param name="enum"></param>
/// <param name="msg"></param>
/// <returns></returns>
[HttpPost(Name = "TestInsertChannel")]
public IActionResult TestInsertChannel(int @enum = 1, string msg = "1")
{
RedisExpand.InsertChannel(@enum.ToEnum<RedisChannelEnum>().Value
, msg);
return Ok();
}
/// <summary> /// <summary>
/// ÊÓÆµ´¦Àí /// ÊÓÆµ´¦Àí
@ -190,7 +176,6 @@ namespace VideoAnalysisCore.Controllers
Subject = req.Subject, Subject = req.Subject,
Tag = req.Tag, Tag = req.Tag,
TagId = req.TagId, TagId = req.TagId,
MediaName = req.Name,
PPTVideoCode = req.PPTVideoCode, PPTVideoCode = req.PPTVideoCode,
VideoType=req.VideoType VideoType=req.VideoType
}; };

View File

@ -46,18 +46,24 @@ namespace VideoAnalysisCore.Controllers.Dto
} }
public class NodePackageReq public class NodePackageReq
{ {
/// <summary>
/// 素材Id 需要返回给我的参数
/// </summary>
public long MaterialId { get; set; }
/// <summary> /// <summary>
/// 录播结构目录节点编号 /// 文件Id 需要返回给我的参数
/// </summary> /// </summary>
[Required(ErrorMessage = "目录节点编号是必填项")] public long AttachmentId { get; set; }
public long NodeId { get; set; }
/// <summary> /// <summary>
/// 科目类型 /// 需要返回给我的参数
/// </summary> /// </summary>
[Required(ErrorMessage = "科目类型是必填项")] public string VideoCode { get; set; }
public SubjectEnum SubjectType { get; set; } /// <summary>
/// 视频地址
/// </summary>
public string VideoUrl { get; set; }
/// <summary> /// <summary>
/// 任务类型 /// 任务类型
@ -66,10 +72,39 @@ namespace VideoAnalysisCore.Controllers.Dto
public TaskTypeEnum TaskType { get; set; } public TaskTypeEnum TaskType { get; set; }
/// <summary> /// <summary>
/// 视频列表 /// 课程类型 0=新课 3=复习 4= 活动 5=班会 7=其他资料
/// </summary> /// </summary>
[Required(ErrorMessage = "文件数量是必填项")] public AttachmentsInfoType CourseType { get; set; }
public List<StructurePageContentAnalyzeItem> AnalyzeItems { get; set; } /// <summary>
/// 视频类型 PPT课件 = 1, 摄像头 = 2
/// </summary>
public VideoType VideoType { get; set; }
/// <summary>
/// 分析完成后的回调地址
/// </summary>
public string CallBackUrl { get; set; }
/// <summary>
/// 请求区域 回调的时候 需要把这个添加到Headers里面 Key=Area value=Area的值
/// </summary>
public string Area { get; set; }
/// <summary>
/// 请求区域 回调的时候 需要把这个添加到Headers里面 Key=HostIP value=HostIP的值
/// </summary>
public string HostIP { get; set; }
/// <summary>
/// 科目类型
/// </summary>
[Required(ErrorMessage = "科目类型是必填项")]
public SubjectEnum SubjectId { get; set; }
/// <summary>
/// 内容的Id 当内容id相同的时候 则表示是一组数据
/// <para>用于查找视频下对应的PPT资源</para>
/// </summary>
public long ContentId { get; set; }
} }
/// <summary> /// <summary>
@ -103,11 +138,6 @@ namespace VideoAnalysisCore.Controllers.Dto
[Required(ErrorMessage = "资源URL是必填项")] [Required(ErrorMessage = "资源URL是必填项")]
public string MediaUrl { get; set; } = string.Empty; public string MediaUrl { get; set; } = string.Empty;
/// <summary> /// <summary>
/// 资源名称
/// </summary>
[Required(ErrorMessage = "资源名称是必要的")]
public string Name { get; set; } = string.Empty;
/// <summary>
/// ApiKey /// ApiKey
/// </summary> /// </summary>
[Required(ErrorMessage = "接口Token是必填项")] [Required(ErrorMessage = "接口Token是必填项")]

View File

@ -45,79 +45,53 @@ namespace VideoAnalysisCore.Controllers
} }
/// <summary>
/// 蓝鲸智库_添加文件节点监控
/// </summary>
/// <param name="req">请求体</param>
/// <returns></returns>
[HttpPost(Name = "NodeSubscription")]
[NonAction, Obsolete]
public async Task<IActionResult> NodeSubscription(NodeMonitoringReq req)
{
if (req is null || req.NodeId == 0)
return BadRequest("无效的提交数据");
if (nodesubscriptionDB.IsAny(s => s.NodeId == req.NodeId))
return BadRequest("重复添加了节点监控任务" + req.NodeId);
var res = await nodesubscriptionDB.InsertReturnEntityAsync(new NodeSubscription()
{
NodeId = req.NodeId,
TaskType = req.Type ?? default,
Subject = req.Subject ?? default,
});
return Ok(res);
}
/// <summary> /// <summary>
/// 蓝鲸智库_文件包订阅 /// 蓝鲸智库_文件包订阅
/// </summary> /// </summary>
/// <param name="req">请求体</param> /// <param name="reqArr">请求体</param>
/// <returns></returns> /// <returns></returns>
[HttpPost(Name = "NodePackage")] [HttpPost(Name = "NodePackage")]
public async Task<IActionResult> NodePackage(NodePackageReq req) public async Task<IActionResult> NodePackage(NodePackageReq[] reqArr)
{ {
Console.WriteLine($"{DateTime.Now} 文件包订阅请求 req=" + JsonSerializer.Serialize(req)); Console.WriteLine($"{DateTime.Now} 文件包订阅请求 req=" + JsonSerializer.Serialize(reqArr));
if (req.AnalyzeItems is null || req.AnalyzeItems.Count() == 0) if (reqArr is null || reqArr.Count() == 0)
return BadRequest("无效视频列表数据"); return BadRequest("无效视频列表数据");
var videos = new List<VideoTask>(req.AnalyzeItems.Count); var videos = new List<VideoTask>(reqArr.Count());
var nodePackages = new List<NodePackageInfo>(req.AnalyzeItems.Count); var nodePackages = new List<NodePackageInfo>(reqArr.Count());
var videoIdArr = videoTaskDB.AsQueryable().Select(v => v.TagId).Distinct().ToArray(); var videoIdArr = videoTaskDB.AsQueryable().Select(v => v.TagId).Distinct().ToArray();
foreach (var s in req.AnalyzeItems) foreach (var sGroup in reqArr.GroupBy(s=>s.ContentId))
{ {
var s= sGroup.First(s=>s.VideoType==VideoType.);
var sPPT= sGroup.FirstOrDefault(s=>s.VideoType==VideoType.PPT课件);
var np = new NodePackageInfo() var np = new NodePackageInfo()
{ {
VideoCode = s.VideoCode, VideoCode = s.VideoCode,
AttachmentsInfoType = s.AttachmentsInfoType,
MaterialId = s.MaterialId, MaterialId = s.MaterialId,
StructurePageContentId = s.StructurePageContentId, AttachmentId = s.AttachmentId,
VideoName = s.VideoName, TaskType = s.TaskType,
NodeId = req.NodeId, SubjectType = s.SubjectId,
TaskType = req.TaskType,
SubjectType = req.SubjectType,
VideoUrl =s.VideoUrl, VideoUrl =s.VideoUrl,
CourseType = s.CourseType,
CallBackUrl=s.CallBackUrl, CallBackUrl=s.CallBackUrl,
Area = s.Area,
HostIP = s.HostIP,
}; };
nodePackages.Add(np); nodePackages.Add(np);
if (s.AttachmentsInfoType == AttachmentsInfoType.PPT)
continue;
if (videoIdArr.Contains(s.VideoCode)) if (videoIdArr.Contains(s.VideoCode))
continue; continue;
var pptInfo = req.AnalyzeItems //获取ppt videoCode var pptCode = sPPT!=null ? sPPT.VideoCode : string.Empty;
.FirstOrDefault(x => x.AttachmentsInfoType == AttachmentsInfoType.PPT
&& s.StructurePageContentId == x.StructurePageContentId);
var pptCode = pptInfo?.VideoCode ?? (pptInfo?.VideoUrl??string.Empty);
videos.Add(new VideoTask() videos.Add(new VideoTask()
{ {
Id = YitIdHelper.NextId(), Id = YitIdHelper.NextId(),
ComeFrom = "127.0.0.1", ComeFrom = GetClientIpAddress(),
ApiToken = "", ApiToken = "",
Type = req.TaskType, Type = s.TaskType,
Subject = req.SubjectType, Subject = s.SubjectId,
TagId = s.VideoCode, TagId = s.VideoCode,
MediaUrl =s.VideoUrl, MediaUrl =s.VideoUrl,
MediaName = s.VideoName,
PPTVideoCode = pptCode, PPTVideoCode = pptCode,
VideoType =s.AttachmentsInfoType VideoType =s.CourseType
}); });
} }
await nodePackageInfoDB.InsertRangeAsync(nodePackages); await nodePackageInfoDB.InsertRangeAsync(nodePackages);
@ -129,6 +103,16 @@ namespace VideoAnalysisCore.Controllers
return Ok(); return Ok();
} }
private string GetClientIpAddress()
{
// 检查 X-Forwarded-For 请求头
if (HttpContext.Request.Headers.ContainsKey("X-Forwarded-For")
&& !string.IsNullOrEmpty(HttpContext.Request.Headers["X-Forwarded-For"]))
return HttpContext.Request.Headers["X-Forwarded-For"].ToString();
if (HttpContext.Connection.RemoteIpAddress != null)
return HttpContext.Connection.RemoteIpAddress.ToString();
throw new Exception("未能获取到客户端ip地址");
}
/// <summary> /// <summary>
/// 获取任务类型 /// 获取任务类型
/// </summary> /// </summary>

View File

@ -8,7 +8,9 @@ using System.Net;
using System.Net.Http.Json; using System.Net.Http.Json;
using System.Reflection; using System.Reflection;
using System.Security.Cryptography; using System.Security.Cryptography;
using System.Security.Policy;
using System.Text; using System.Text;
using System.Text.Json;
using System.Threading.Tasks; using System.Threading.Tasks;
using UserCenter.Model.Enum; using UserCenter.Model.Enum;
using VideoAnalysisCore.Common; using VideoAnalysisCore.Common;
@ -31,6 +33,7 @@ namespace VideoAnalysisCore.Job
private readonly Repository<NodePackageInfo> nodePackageInfoDB; private readonly Repository<NodePackageInfo> nodePackageInfoDB;
private readonly Repository<Attachments> attachmentsDB; private readonly Repository<Attachments> attachmentsDB;
private readonly Repository<VideoTask> videoTaskDB; private readonly Repository<VideoTask> videoTaskDB;
private readonly IHttpClientFactory _clientFactory;
public NodePackageJob(Repository<Attachments> videoTaskDB, public NodePackageJob(Repository<Attachments> videoTaskDB,
Repository<NodePackageInfo> nodePackageInfoDB, Repository<VideoTask> videotaskDB) Repository<NodePackageInfo> nodePackageInfoDB, Repository<VideoTask> videotaskDB)
{ {
@ -65,9 +68,7 @@ namespace VideoAnalysisCore.Job
Console.WriteLine($"{DateTime.Now} 执行=>文件包任务 任务回调 数量{postData.Count} ..."); Console.WriteLine($"{DateTime.Now} 执行=>文件包任务 任务回调 数量{postData.Count} ...");
if (postData.Count() == 0) if (postData.Count() == 0)
return; return;
//var responseMessage = await new HttpClient() foreach (var item in taskArr)
// .PostAsJsonAsync(AppCommon.Config.Subsystem.蓝鲸智库.APIUrl + "/api/callback/platform/videosAnalyze", postData);
foreach (var item in taskArr)
{ {
HttpResponseMessage responseMessage = null; HttpResponseMessage responseMessage = null;
try try
@ -75,8 +76,14 @@ namespace VideoAnalysisCore.Job
var postUrl = !string.IsNullOrWhiteSpace(item.CallBackUrl) var postUrl = !string.IsNullOrWhiteSpace(item.CallBackUrl)
? item.CallBackUrl ? item.CallBackUrl
: AppCommon.Config.Subsystem..APIUrl + "/api/callback/platform/videosAnalyze"; : AppCommon.Config.Subsystem..APIUrl + "/api/callback/platform/videosAnalyze";
responseMessage = await new HttpClient()
.PostAsJsonAsync(postUrl, postData); var apiClent = _clientFactory.CreateClient();
var request = new HttpRequestMessage(HttpMethod.Post, postUrl);
request.Headers.Add("Area", item.Area); // 直接添加到本次请求头
request.Headers.Add("HostIP", item.HostIP); // 直接添加到本次请求头
request.Content = new StringContent(JsonSerializer.Serialize(postData), Encoding.UTF8, "application/json");
responseMessage = await apiClent.SendAsync(request);
if (responseMessage.IsSuccessStatusCode) if (responseMessage.IsSuccessStatusCode)
{ {
var res = await responseMessage.Content.ReadAsStringAsync(); var res = await responseMessage.Content.ReadAsStringAsync();

View File

@ -92,7 +92,6 @@ namespace VideoAnalysisCore.Job
Tag = item.NodeId.ToString(), Tag = item.NodeId.ToString(),
TagId = s.VideoCode, TagId = s.VideoCode,
MediaUrl = string.Empty, MediaUrl = string.Empty,
MediaName = s.Name
}); });
} }
var maxId = data.Max(s => s.Id); var maxId = data.Max(s => s.Id);

View File

@ -41,7 +41,7 @@ namespace VideoAnalysisCore.Job
// 查询 2 天前任务执行完成的记录 // 查询 2 天前任务执行完成的记录
var completedTasks = videotaskDB.AsQueryable() var completedTasks = videotaskDB.AsQueryable()
.Where(t => .Where(t =>
t.LastEnum == Model.Enum.RedisChannelEnum.EndTask t.LastEnum == Model.Enum.RedisChannelEnum.
&& t.EndTime < twoDaysAgo && t.EndTime < twoDaysAgo
&& t.EndTime > endDaysAgo) && t.EndTime > endDaysAgo)
.ToList(); .ToList();

View File

@ -7,21 +7,22 @@ using System.Threading.Tasks;
namespace VideoAnalysisCore.Model.Enum namespace VideoAnalysisCore.Model.Enum
{ {
public enum VideoType
{
PPT课件 = 1,
= 2
}
public enum AttachmentsInfoType public enum AttachmentsInfoType
{ {
[Description("常规课程")] = 0,
None = 0, [Description("复习/习题课")]
[Description("教研")] = 3,
TeachingResearch = 1,
[Description("PPT")]
PPT = 2,
[Description("复习")]
Review = 3,
[Description("活动")] [Description("活动")]
Activities = 4, = 4,
[Description("班会")] [Description("班会")]
Meeting = 5, = 5,
[Description("行为分析")] [Description("其他资料")]
Behavior = 6, = 7
} }
} }

View File

@ -5,33 +5,37 @@
/// </summary> /// </summary>
public enum RedisChannelEnum public enum RedisChannelEnum
{ {
/// <summary>
/// 等待中
/// </summary>
= 0,
/// <summary> /// <summary>
/// 下载文件 /// 下载文件
/// </summary> /// </summary>
DownloadFile, = 5,
/// <summary> /// <summary>
/// 分离音频 /// 分离音频
/// </summary> /// </summary>
SeparateAudio, = 10,
/// <summary> /// <summary>
/// 解析字幕 /// 解析字幕
/// </summary> /// </summary>
ParsingCaptions, = 20,
/// <summary> ///// <summary>
/// 解析说话人 ///// 解析说话人
/// </summary> ///// </summary>
ParsingSpeaker, //解析说话人 = 30,
/// <summary> /// <summary>
/// Chat模型分析 /// Chat模型分析
/// </summary> /// </summary>
ChatModelAnalysis, AI模型分析 = 40,
///// <summary> /// <summary>
///// 回调三方系统 /// 分析试题
///// </summary> /// </summary>
//CallBackSystem, AI分析试题 = 50,
/// <summary> /// <summary>
/// 结束任务 /// 结束任务
/// </summary> /// </summary>
EndTask, = 60,
} }
} }

View File

@ -30,18 +30,9 @@ namespace VideoAnalysisCore.Model
[SugarColumn(Length = 32)] [SugarColumn(Length = 32)]
public string VideoCode { get; set; } public string VideoCode { get; set; }
/// <summary> /// <summary>
/// 视频文件名称 /// 文件Id 需要返回给我的参数
/// </summary> /// </summary>
[SugarColumn(Length = 32)] public long AttachmentId { get; set; }
public string VideoName { get; set; }
/// <summary>
/// 录播结构目录节点编号
/// </summary>
public long NodeId { get; set; }
/// <summary>
/// 录播内容编号
/// </summary>
public long StructurePageContentId { get; set; }
/// <summary> /// <summary>
/// 素材ID /// 素材ID
/// </summary> /// </summary>
@ -49,7 +40,7 @@ namespace VideoAnalysisCore.Model
/// <summary> /// <summary>
/// 内容类型 /// 内容类型
/// </summary> /// </summary>
public AttachmentsInfoType AttachmentsInfoType { get; set; } public AttachmentsInfoType CourseType { get; set; }
/// <summary> /// <summary>
/// 科目类型 /// 科目类型
@ -80,8 +71,17 @@ namespace VideoAnalysisCore.Model
/// <summary> /// <summary>
/// 通知回调地址 /// 通知回调地址
/// </summary> /// </summary>
[SugarColumn(Length = 500)] [SugarColumn(Length = 500)]
public string CallBackUrl { get; set; } public string CallBackUrl { get; set; }
/// <summary>
/// 请求区域
/// <para>回调添加到Headers</para>
/// </summary>
public string Area { get; set; }
/// <summary>
/// 请求区域
/// <para>回调添加到Headers</para>
/// </summary>
public string HostIP { get; set; }
} }
} }

View File

@ -39,10 +39,10 @@ namespace VideoAnalysisCore.Model
public long TopicId { get; set; } public long TopicId { get; set; }
/// <summary> /// <summary>
/// 地址 /// PPT切图地址
/// </summary> /// </summary>
[SugarColumn(Length =100)] [SugarColumn(Length =100)]
public string? ImageUrl { get; set; } public string? PPTImageUrl { get; set; }
/// <summary> /// <summary>
/// 题干 /// 题干
/// </summary> /// </summary>
@ -53,6 +53,12 @@ namespace VideoAnalysisCore.Model
/// </summary> /// </summary>
public string? Question { get; set; } public string? Question { get; set; }
/// <summary> /// <summary>
/// 试题类型
/// <para>[填空题/判断题/选择题/解答题]</para>
/// </summary>
[SugarColumn(Length = 10,IsNullable =true)]
public string? Type { get; set; }
/// <summary>
/// 试题开始时间 /// 试题开始时间
/// </summary> /// </summary>
public int StartTime { get; set; } public int StartTime { get; set; }

View File

@ -28,10 +28,6 @@ namespace VideoAnalysisCore.Model
/// </summary> /// </summary>
public string MediaUrl { get; set; } = string.Empty; public string MediaUrl { get; set; } = string.Empty;
/// <summary> /// <summary>
/// 媒体文件名称
/// </summary>
public string MediaName { get; set; } = string.Empty;
/// <summary>
/// 下载后本地媒体目录 /// 下载后本地媒体目录
/// </summary> /// </summary>
public string LocalMediaPath { get; set; } = string.Empty; public string LocalMediaPath { get; set; } = string.Empty;