using CloudBuilder.AI.Data; using CloudBuilder.AI.Entity; using CloudBuilder.AI.SearchCriteria; using CloudBuilder.AI.Service; using CloudBuilder.Core.DatabaseAccessor.Entity; using CloudBuilder.Core.DependencyInjection.Task; using CloudBuilder.Core.Service; using CloudBuilder.Security.Entity; using CloudBuilder.Topshelf.Python; using CloudBuilder.Topshelf.Task.Novel.Process; using Microsoft.Extensions.DependencyInjection; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using static System.Net.Mime.MediaTypeNames; namespace CloudBuilder.Topshelf.Task.Novel { //task:SpeakerTask param_name:param_value public class SpeakerTask : IScheduleTask { private readonly IApplicationService service; public SpeakerTask(IApplicationService service) { this.service = service; } public void Run(Dictionary bodyDict) { AiSentenceSearchCriteria c = new AiSentenceSearchCriteria(); c.limit = 999999; List filters = new List(); SearchFilter filter = new SearchFilter(); filter.Column = AiSentenceViewEntity.DB_CHAPTER_GUID; filter.Type = SearchFilterTypePolicy.TEXT; filter.Operator = SearchFilterOperatorPolicy.CONTAIN; filter.Value = "cf34136e-d30a-4901-9109-626e412eaf57"; filters.Add(filter); c.Filter = filters.ToArray(); AiSentenceViewResultData data = service.ServiceProvider.GetService().FindBySearchCriteria(c); if (data == null || data.Datas == null || data.Datas.Count() == 0) return; AiSentenceViewEntity[] sentenceViews = data.Datas.OrderBy(x => x.ParagraphIndex).ThenBy(x => x.DialogueIndc).ToArray(); if (sentenceViews == null || sentenceViews.Length == 0) return; PythonCommand python = new PythonCommand(service); Hanlp2 hanlp = new Hanlp2(python); List all = GetNamedSummary(hanlp, data.Datas, 0, data.Datas.Length); string text = string.Empty; //@"乌云忽然凝聚变形,瞬间化为了一个身高寸许的黑绿婴儿出来,并口吐人言的说道。"; //text = "艳美少妇打了韩立"; //text = "银月嫣然一笑后,敛冲元婴一拜的说道。这元婴看起来除了比韩立主元婴小了一些外,模样神态全都一般无二的样子。"; //List sentiments = hanlp.GetSentiment(text); SpeakerFeatureLearner speakerFeatureLearner = new SpeakerFeatureLearner(); BuildPersonNameRules(all.ToArray(), speakerFeatureLearner); AiSentenceViewEntity[] sentenceTemps; int paragraphIndex = 1; foreach (var sentence in sentenceViews) { sentence.CreatedBy = sentence.UpdatedBy = string.Empty; if (string.IsNullOrEmpty(sentence.Content)) continue; text = sentence.Content; List ners = hanlp.GetHanlpNer(text); List> srls = hanlp.GetHanlpSrl(text); List cons = hanlp.GetHanlpCon(text); speakerFeatureLearner.AutoLearnFromText("", ners, srls, cons); //如果不存在人名 if (ners == null || !ners.Where(x => x.Type.Contains("PERSON")).Any()) { if (sentence.DialogueIndc == YesNoPolicy.NO) { continue; } foreach (var p in sentenceViews.Where(x => x.ParagraphIndex == sentence.ParagraphIndex).ToArray()) { if (string.IsNullOrEmpty(p.PersonName)) continue; sentence.PersonName = p.PersonName; } } string[] persons = ners.Where(x => x.Type.Contains("PERSON")).Select(x => x.Text).Distinct().ToArray(); if (persons == null && persons.Count() == 0) continue; if (persons != null && persons.Count() == 1) { sentence.PersonName = speakerFeatureLearner.MatchSpeaker(persons.FirstOrDefault()); continue; } //如果是两个,取ARG0 sentence.PersonName = speakerFeatureLearner.MatchSpeaker(persons.FirstOrDefault()); } List indexRange = GetNamedSummaryBefore(hanlp, data.Datas, 8); //3字词根据次数划分到2字词内 python.Dispose(); } public List GetNamedSummaryBefore(Hanlp2 hanlp, AiSentenceViewEntity[] datas, int paragraphIndex) { return GetNamedSummary(hanlp, datas, paragraphIndex - 3 > 0 ? paragraphIndex - 3 : 0, paragraphIndex); } public List GetNamedSummaryAfter(Hanlp2 hanlp, AiSentenceViewEntity[] datas, int paragraphIndex) { return GetNamedSummary(hanlp, datas, paragraphIndex, paragraphIndex + 3); } public List GetNamedSummary(Hanlp2 hanlp, AiSentenceViewEntity[] datas, int startIndex, int endIndex) { List nameds = new List(); NamedSummary summary; List temps; AiSentenceViewEntity sentence; for (int i = 0; i < datas.Length; i++) { sentence = datas[i]; if (sentence.ParagraphIndex < startIndex) continue; if (sentence.ParagraphIndex > endIndex) continue; temps = hanlp.GetHanlpNer(sentence.Content); if (temps == null || temps.Count() == 0) continue; foreach (var tp in temps) { if (tp.Text.Length == 1) continue; if (tp.Type != "PERSON") continue; if (nameds.Select(x => x.Person).Contains(tp.Text)) { summary = nameds.Where(x => x.Person == tp.Text).FirstOrDefault(); summary.Count++; continue; } summary = new NamedSummary(); summary.Person = tp.Text; summary.Count = 1; nameds.Add(summary); } } return nameds; } /// /// 处理 NamedSummary 数组:按包含关系分组,以次数多的为主名称调用 AddRule /// /// 人名统计数组 public void BuildPersonNameRules(NamedSummary[] namedSummaries, SpeakerFeatureLearner speakerFeatureLearner) { // 空值校验 if (namedSummaries == null || !namedSummaries.Any()) { Console.WriteLine("NamedSummary 数组为空,无需处理"); return; } // 1. 提取去重的人名-次数字典(确保每个人名只统计一次总次数) var nameCountDict = namedSummaries .Where(ns => !string.IsNullOrWhiteSpace(ns.Person)) .GroupBy(ns => ns.Person.Trim()) .ToDictionary(g => g.Key, g => g.Sum(ns => ns.Count)); if (nameCountDict.Count < 2) { Console.WriteLine("有效人名数量不足,无需建立映射"); return; } // 2. 标记已处理的人名,避免重复配对 var processedNames = new HashSet(); // 3. 遍历查找包含关系的人名对,按次数确定主从 foreach (var (name1, count1) in nameCountDict) { if (processedNames.Contains(name1)) continue; // 查找与当前人名存在包含关系的其他人名 var matchedNames = nameCountDict .Where(kv => !processedNames.Contains(kv.Key) && kv.Key != name1 && (kv.Key.Contains(name1) || name1.Contains(kv.Key))) // 互相包含都算匹配 .ToList(); if (matchedNames.Count == 0) { processedNames.Add(name1); continue; // 无匹配的包含关系人名,跳过 } // 4. 合并当前人名和匹配人名,按次数选主名称 foreach (var (name2, count2) in matchedNames) { // 确定主/次要名称:次数多的是主,次数相同则选长度长的(可选规则) string mainName = count1 > count2 ? name1 : (count2 > count1 ? name2 : (name1.Length > name2.Length ? name1 : name2)); string minorName = mainName == name1 ? name2 : name1; // 5. 调用 AddRule(次要名称, 主名称) speakerFeatureLearner.AddRule(mainName, minorName); Console.WriteLine($"已调用 AddRule:AddRule(\"{mainName}\", \"{minorName}\")"); // 标记为已处理,避免重复配对 processedNames.Add(name1); processedNames.Add(name2); } } } public class NamedSummary { public int Count { get; set; } public string Person { get; set; } public decimal MinConfidence { get; set; } } } }