Explorar o código

商邀AI 生成邮件

Lyyyi hai 15 horas
pai
achega
1177ec3b81

+ 34 - 24
OASystem/OASystem.Api/Controllers/GroupsController.cs

@@ -3866,35 +3866,37 @@ FROM
 
                         if (root.TryGetProperty("教育经历", out JsonElement eduInfo))
                         {
-                            if (eduInfo.TryGetProperty("教育经历", out JsonElement educations))
+                            if (eduInfo.ValueKind == JsonValueKind.Array)
                             {
-                                if (educations.ValueKind == JsonValueKind.Array)
+                                foreach (JsonElement edu in eduInfo.EnumerateArray())
                                 {
-                                    foreach (JsonElement edu in educations.EnumerateArray())
-                                    {
-                                        var schoolInfo = new Crm_VisaCustomerSchool();
-                                        if (TryGetNonEmptyStringProperty(edu, "学校名称", out string school_name))
-                                            schoolInfo.School = school_name;
+                                    var schoolInfo = new Crm_VisaCustomerSchool();
+                                    if (TryGetNonEmptyStringProperty(edu, "学校名称", out string school_name))
+                                        schoolInfo.School = school_name;
 
-                                        if (TryGetNonEmptyStringProperty(edu, "学校地址", out string school_address))
-                                            schoolInfo.Address = school_address;
+                                    if (TryGetNonEmptyStringProperty(edu, "学校地址", out string school_address))
+                                        schoolInfo.Address = school_address;
 
-                                        if (TryGetNonEmptyStringProperty(edu, "专业", out string degree))
-                                            schoolInfo.Subject = degree;
+                                    if (TryGetNonEmptyStringProperty(edu, "专业", out string degree))
+                                        schoolInfo.Subject = degree;
 
-                                        if (TryGetNonEmptyStringProperty(edu, "学位", out string major))
-                                            schoolInfo.Education = major;
+                                    if (TryGetNonEmptyStringProperty(edu, "学位", out string major))
+                                        schoolInfo.Education = major;
 
-                                        if (TryGetNonEmptyDateTimeProperty(edu, "入学时间", out DateTime? enrollment_date))
-                                            schoolInfo.StudyStart = enrollment_date;
+                                    if (TryGetNonEmptyDateTimeProperty(edu, "入学时间", out DateTime? enrollment_date))
+                                        schoolInfo.StudyStart = enrollment_date;
 
-                                        if (TryGetNonEmptyDateTimeProperty(edu, "毕业时间", out DateTime? graduation_date))
-                                            schoolInfo.StudyEnd = graduation_date;
+                                    if (TryGetNonEmptyDateTimeProperty(edu, "毕业时间", out DateTime? graduation_date))
+                                        schoolInfo.StudyEnd = graduation_date;
 
-                                        schools.Add(schoolInfo);
-                                    }
+                                    schools.Add(schoolInfo);
                                 }
                             }
+
+                            //if (eduInfo.TryGetProperty("教育经历", out JsonElement educations))
+                            //{
+
+                            //}
                         }
 
                         if (root.TryGetProperty("签证与旅行记录", out JsonElement visaTravelRecs))
@@ -13825,7 +13827,9 @@ FROM
             }
 
 
-            var chatResult = await _doubaoService.CompleteChatAsync(new List<DouBaoChatMessage> { new DouBaoChatMessage { Role = DouBaoRole.user, Content = chat } });
+            // var chatResult = await _doubaoService.CompleteChatAsync(new List<DouBaoChatMessage> { new DouBaoChatMessage { Role = DouBaoRole.user, Content = chat } });
+            var apiResp = await _deepSeekService.ChatAsync(chat);
+            var chatResult = apiResp.Answer;
 
             if (string.IsNullOrEmpty(chatResult))
             {
@@ -13938,13 +13942,18 @@ FROM
                 return Ok(jw);
             }
 
-            var chatResult = await _doubaoService.CompleteChatAsync(new List<DouBaoChatMessage> { new DouBaoChatMessage { Role = DouBaoRole.user, Content = chat } });
+            //var chatResult = await _doubaoService.CompleteChatAsync(new List<DouBaoChatMessage> { new DouBaoChatMessage { Role = DouBaoRole.user, Content = chat } });
+            var apiResp = await _deepSeekService.ChatAsync(chat);
+            var chatResult = apiResp.Answer;
+
             if (string.IsNullOrEmpty(chatResult))
             {
                 jw.Msg = "报批日程安排失败!";
                 return Ok(jw);
             }
 
+            chatResult = GeneralMethod.ExtractJson(chatResult);
+
             var jsonResult = JsonConvert.DeserializeObject<List<Grp_ApprovalTravelDetails>>(chatResult);
             if (!jsonResult.Any())
             {
@@ -14049,7 +14058,10 @@ FROM
                 return Ok(jw);
             }
 
-            var chatResult = await _doubaoService.CompleteChatAsync(new List<DouBaoChatMessage> { new DouBaoChatMessage { Role = DouBaoRole.user, Content = chat } });
+            // var chatResult = await _doubaoService.CompleteChatAsync(new List<DouBaoChatMessage> { new DouBaoChatMessage { Role = DouBaoRole.user, Content = chat } });
+            var apiResp = await _deepSeekService.ChatAsync(chat);
+            var chatResult = apiResp.Answer;
+
             if (string.IsNullOrEmpty(chatResult))
             {
                 jw.Msg = "行程框架导出失败!";
@@ -32889,7 +32901,6 @@ AirHotelPrice
             return Ok(JsonView(true));
         }
 
-
         /// <summary>
         /// 接团客户名单
         /// json字符串 AddMultiple(添加多个)
@@ -32965,7 +32976,6 @@ AirHotelPrice
             return Ok(JsonView(false));
         }
 
-
         /// <summary>
         /// 接团客户名单
         /// AddMultiple(添加多个)

+ 379 - 11
OASystem/OASystem.Api/Controllers/ResourceController.cs

@@ -6,12 +6,15 @@ using NPOI.SS.Formula.Functions;
 using NPOI.SS.UserModel;
 using OASystem.API.OAMethodLib;
 using OASystem.API.OAMethodLib.HunYuanAPI;
+using OASystem.API.OAMethodLib.QiYeWeChatAPI;
 using OASystem.API.OAMethodLib.QiYeWeChatAPI.AppNotice;
 using OASystem.Domain.AesEncryption;
 using OASystem.Domain.Dtos.Groups;
 using OASystem.Domain.Entities.Groups;
 using OASystem.Domain.ViewModels.Groups;
+using OASystem.Domain.ViewModels.QiYeWeChat;
 using OASystem.Infrastructure.Repositories.Groups;
+using Quartz.Logging;
 using Quartz.Util;
 using System.ComponentModel.DataAnnotations;
 using System.Data;
@@ -19,6 +22,7 @@ using System.Diagnostics;
 using TencentCloud.Common;
 using static OASystem.API.OAMethodLib.GeneralMethod;
 using static OASystem.API.OAMethodLib.JWTHelper;
+using static OpenAI.GPT3.ObjectModels.Models;
 
 namespace OASystem.API.Controllers
 {
@@ -31,9 +35,10 @@ namespace OASystem.API.Controllers
     {
         private readonly IMapper _mapper;
         private readonly ILogger<ResourceController> _logger;
-        private readonly SqlSugarClient _sqlSugar;
         private readonly IConfiguration _config;
         private readonly IHunyuanService _hunyuanService;
+        private readonly IQiYeWeChatApiService _qiYeWeChatApiService;
+        private readonly SqlSugarClient _sqlSugar;
         private readonly CarDataRepository _carDataRep;
         private readonly LocalGuideDataRepository _localGuideDataRep;
         private readonly ThreeCodeRepository _ThreeCodeRep;
@@ -72,6 +77,7 @@ namespace OASystem.API.Controllers
             IConfiguration config,
             ILogger<ResourceController> logger,
             IHunyuanService hunyuanService,
+            IQiYeWeChatApiService qiYeWeChatApiService,
             SqlSugarClient sqlSugar,
             CarDataRepository carDataRep,
             LocalGuideDataRepository localGuideDataRep,
@@ -100,6 +106,7 @@ namespace OASystem.API.Controllers
             _config = config;
             _logger = logger;
             _hunyuanService = hunyuanService;
+            _qiYeWeChatApiService = qiYeWeChatApiService;
             _sqlSugar = sqlSugar;
             _carDataRep = carDataRep;
             _localGuideDataRep = localGuideDataRep;
@@ -2327,7 +2334,7 @@ Inner Join Sys_Department as d With(Nolock) On u.DepId=d.Id Where m.Id={0} ", _m
                 info.InvName,
                 info.GroupId,
                 info.AiCrawledDetails,
-                Entry = info.Entries,
+                Entry = info.EntryInfo,
             };
             return Ok(JsonView(true, $"查询成功!", view));
         }
@@ -2342,12 +2349,12 @@ Inner Join Sys_Department as d With(Nolock) On u.DepId=d.Id Where m.Id={0} ", _m
         {
             var invAiInfo = await _sqlSugar.Queryable<Res_InvitationAI>().Where(x => x.Id == dto.Id).FirstAsync();
 
-            if (invAiInfo == null || invAiInfo?.Entries == null)
+            if (invAiInfo == null || invAiInfo?.EntryInfo == null)
             {
                 return Ok(JsonView(false, "请先设置关键字信息!"));
             }
 
-            var entryInfo = invAiInfo.Entries;
+            var entryInfo = invAiInfo.EntryInfo;
 
             // 当前企业领域
             string industryFocus = string.Empty;
@@ -2548,7 +2555,7 @@ Inner Join Sys_Department as d With(Nolock) On u.DepId=d.Id Where m.Id={0} ", _m
                 invAiInfo.InvName,
                 invAiInfo.GroupId,
                 invAiInfo.AiCrawledDetails,
-                Entry = invAiInfo.Entries
+                Entry = invAiInfo.EntryInfo
             }));
         }
 
@@ -2603,7 +2610,7 @@ Inner Join Sys_Department as d With(Nolock) On u.DepId=d.Id Where m.Id={0} ", _m
                 {
                     InvName = groupInfo?.TeamName ?? invName,  //默认团组名称
                     GroupId = dto.GroupId,
-                    Entries = entryInfo,
+                    EntryInfo = entryInfo,
                     CreateUserId = dto.CurrUserId
                 };
 
@@ -2616,7 +2623,7 @@ Inner Join Sys_Department as d With(Nolock) On u.DepId=d.Id Where m.Id={0} ", _m
             else
             {
                 // 3.2 数据存在 则更新数据(覆盖原有的AI爬取详情,保留原有的其他字段不变)
-                dataInfo.Entries = entryInfo;
+                dataInfo.EntryInfo = entryInfo;
 
                 var update = await _sqlSugar.Updateable(dataInfo).ExecuteCommandAsync();
                 if (update < 1)
@@ -2632,12 +2639,54 @@ Inner Join Sys_Department as d With(Nolock) On u.DepId=d.Id Where m.Id={0} ", _m
                 dataInfo.Id,
                 dataInfo.InvName,
                 dataInfo.GroupId,
-                Entry = dataInfo.Entries,
+                Entry = dataInfo.EntryInfo,
             }));
         }
 
         /// <summary>
-        /// 商邀资料AI 单条资料删除
+        /// 商邀资料AI 保存
+        /// </summary>
+        /// <returns></returns>
+        [HttpPost]
+        [ProducesResponseType(typeof(JsonView), StatusCodes.Status200OK)]
+        public async Task<IActionResult> InvitationAISave(InvitationAISaveDto dto)
+        {
+            if (dto.Id < 1) return Ok(JsonView(false, "请选择保存的团组"));
+            if (dto.CurrUserId < 1) return Ok(JsonView(false, "请传入用户Id"));
+            if (dto.AiCrawledDetail == null) return Ok(JsonView(false, "请选择保存的邀请方详情"));
+
+            var invAiInfo = await _sqlSugar.Queryable<Res_InvitationAI>()
+            .Where(x => x.Id == dto.Id)
+            .FirstAsync();
+
+            if (invAiInfo == null) return Ok(JsonView(false, "数据信息为空"));
+
+            var dataList = invAiInfo?.AiCrawledDetails;
+
+            if (dataList == null) return Ok(JsonView(false, "邀请方数据信息为空"));
+
+            var datas = dataList.Where(x => x.Guid != dto.AiCrawledDetail.Guid).ToList();
+
+            var editInfo = dto.AiCrawledDetail;
+
+            var opUserName = await _sqlSugar.Queryable<Sys_Users>().Where(x => x.IsDel == 0 && x.Id == dto.CurrUserId).Select(x => x.CnName).FirstAsync() ?? "-";
+            editInfo.Operator = opUserName;
+            editInfo.OperatedAt = DateTime.Now;
+
+            datas.Add(editInfo);
+            invAiInfo.AiCrawledDetails = datas;
+
+            var editUpd = await _sqlSugar.Updateable(invAiInfo).UpdateColumns(x => x.AiCrawledDetails).ExecuteCommandAsync();
+            if (editUpd < 1)
+            {
+                return Ok(JsonView(true, "保存失败"));
+            }
+
+            return Ok(JsonView(true, "保存成功"));
+        }
+
+        /// <summary>
+        /// 商邀资料AI 单条删除
         /// </summary>
         /// <returns></returns>
         [HttpPost]
@@ -2683,12 +2732,12 @@ Inner Join Sys_Department as d With(Nolock) On u.DepId=d.Id Where m.Id={0} ", _m
         {
             var invAiInfo = await _sqlSugar.Queryable<Res_InvitationAI>().Where(x => x.Id == dto.Id).FirstAsync();
 
-            if (invAiInfo == null || invAiInfo?.Entries == null)
+            if (invAiInfo == null || invAiInfo?.EntryInfo == null)
             {
                 return Ok(JsonView(false, "请先设置关键字信息!"));
             }
 
-            var entryInfo = invAiInfo.Entries;
+            var entryInfo = invAiInfo.EntryInfo;
 
             // 已搜索出的数据(AI 数据源)
             var hunyuanAIDatas = invAiInfo.AiCrawledDetails.Where(x => x.Source == 1).ToList() ?? new List<InvitationAIInfo>();
@@ -3102,6 +3151,325 @@ Inner Join Sys_Department as d With(Nolock) On u.DepId=d.Id Where m.Id={0} ", _m
 
         #endregion
 
+        /// <summary>
+        /// 商邀资料AI 生成邮件
+        /// </summary>
+        /// <returns></returns>
+        [HttpPost]
+        [ProducesResponseType(typeof(JsonView), StatusCodes.Status200OK)]
+        public async Task<IActionResult> InvitationAIGenerateEmail(InvitationAIGenerateEmailDto dto)
+        {
+            if (dto.Id < 1) return Ok(JsonView(false, "请选择保存的团组"));
+            if (dto.CurrUserId < 1) return Ok(JsonView(false, "请传入用户Id"));
+            if (dto.Guids == null || !dto.Guids.Any()) return Ok(JsonView(false, "请传入需要生成邮件的单位列表"));
+
+            var invAiInfo = await _sqlSugar.Queryable<Res_InvitationAI>()
+                .Where(x => x.Id == dto.Id)
+                .FirstAsync();
+
+            if (invAiInfo == null) return Ok(JsonView(false, "数据信息为空"));
+
+            var dataList = invAiInfo?.AiCrawledDetails;
+
+            if (dataList == null) return Ok(JsonView(false, "邀请方数据信息为空"));
+
+            var clientInfoSources = dataList.Where(x => dto.Guids.Contains(x.Guid)).ToList();
+
+            var clientInfos = clientInfoSources.Select(x => new AICreateEmailInfo()
+            {
+                Guid = x.Guid,
+                NameCn = x.NameCn,
+                Scope = x.Scope,
+                Subject = "",
+                Content = ""
+            }).ToList();
+
+            if (clientInfos == null || !clientInfos.Any()) return Ok(JsonView(false, "邀请单位信息为空"));
+
+            var groupInfo = await _sqlSugar.Queryable<Grp_DelegationInfo>()
+                .Where(x => x.IsDel == 0 && x.Id == invAiInfo.GroupId)
+                .Select(x => new { x.TeamName, x.VisitPurpose,x.VisitDate})
+                .FirstAsync();
+            string operatorName = await _sqlSugar.Queryable<Sys_Users>().Where(x => x.IsDel == 0 && x.Id == dto.CurrUserId).Select(x => x.CnName).FirstAsync() ?? "-";
+            string pormpt = $@"
+# Role
+你是一位精通国际政企关系的【首席联络官】。你具备极强的行业分析能力,能通过 [SourceEntity] 的名称自动检索并推导其行政职能与行业地位,并以此撰写具有战略高度、语调优雅的正式商务邮件。
+
+# Intelligence Task: Source Profiling
+在生成邮件前,请先执行以下逻辑:
+1. **职能推导**:基于 [SourceEntity] 的名称,自动识别其在所属领域的具体行政职能与政策影响力。
+2. **战略对齐**:将其职能与全球大趋势(如:Sustainable Urbanization, Digital Transformation, Carbon Neutrality)挂钩,作为邮件第二段的叙事背景。
+
+# Reference Model (Few-Shot Example)
+在生成时,请严格参考以下范例的【语气】、【五段式结构】和【外交辞令】:
+- Subject: Official Study Visit Inquiry – [Delegation Name] ([Date])
+- Para 1: ""On behalf of [SourceEntity], I am writing to formally propose...""
+- Para 2: ""As a pivotal megacity [AI根据Source地位补全]... we recognize [Target]’s leadership in...""
+- Para 3: ""We are particularly keen to explore... [基于Target经营范围推导的3个技术点]...""
+- Para 4: ""We propose a 2–3 day visit... during the week of [Logistics]...""
+- Para 5: ""Enclosed please find the [PascalCase_File]...""
+
+# Task
+根据 [TargetList] 中每个单位的【经营范围】,为 [SourceEntity] 生成独立的英文访问请求邮件。
+
+# Inputs
+- [SourceEntity]: [{invAiInfo.EntryInfo?.OriginUnit ?? ""}]
+- [VisitPurpose]: [{groupInfo?.VisitPurpose}]
+- [TargetList]: [{JsonConvert.SerializeObject(clientInfos)}]
+- [Logistics]: [{groupInfo?.VisitDate.ToString("yyyy-MM-dd")}]
+
+# Execution Logic (Chain of Thought)
+1. **Scope-to-Focus Analysis**: 
+   - 深入分析每个 Target 的【经营范围】。
+   - 自动推导 3 个与 [VisitPurpose] 高度对齐的专业考察点(如:Policy Frameworks, Technical Standards, Operational Case Studies)。
+2. **Modular Drafting**: 
+   - 必须严格遵循参考范例的 5 段式逻辑。
+   - 针对不同职能属性(监管/技术/运营)动态微调邮件主题(Subject)。
+3. **No Personnel Reference**: 严禁提及“名单、人数、成员、审核中”等任何具体人员信息,保持机构对等对话的高度。
+
+# Constraints & Standards
+- **Tone**: Formal, Strategic, and Executive (庄重、具战略高度、执行力强)。
+- **Naming Protocol**: 附件引用统一使用 `JointVisitAgenda`, `StrategicInquiryBrief` (PascalCase)。
+- **Output Format**: 仅输出一个合规的 JSON 数组,不包含任何解释性文字。
+- **Field Mapping**:
+  - `Guid`: 原样保留。
+  - `NameCn`: 原样保留。
+  - `Scope`: 原样保留。
+  - `Subject`: 动态生成的英文主题。
+  - `Content`: 包含 Markdown 格式符号(`###`, `**`, `*`, `---`)的英文正文。
+
+# JSON Structure Template
+[
+  {{
+    ""Guid"": ""ID"",
+    ""NameCn"": ""Entity Name"",
+    ""Scope"": ""Scope Description"",
+    ""Subject"": ""Dynamic English Subject"",
+    ""Content"": ""### Subject\n\n**Dear...**\n\nBody content with *Markdown*...\n\n--- \n\nClosing.""
+  }}
+]";
+
+            string response = string.Empty;
+            try
+            {
+                response = await _hunyuanService.ChatCompletionsHunyuan_t1_latestAsync(pormpt);
+            }
+            catch (HttpRequestException ex)
+            {
+                _logger.LogError(ex, "网络连接失败,无法调用混元API");
+                return Ok(JsonView(false, $"网络连接异常,请检查网络后重试!Ex:{ex.Message}"));
+            }
+            catch (TaskCanceledException ex)
+            {
+                _logger.LogError(ex, "请求超时");
+                return Ok(JsonView(false, $"AI 响应超时,请稍后再试!Ex:{ex.Message}"));
+            }
+            catch (TencentCloudSDKException ex)
+            {
+                // 记录完整日志
+                _logger.LogError(ex, "腾讯云API调用失败 | 错误码: {ErrorCode} | 请求ID: {RequestId}", ex.ErrorCode, ex.RequestId);
+
+                // 根据错误码做差异化处理
+                if (ex.ErrorCode != null && ex.ErrorCode == "AuthFailure.SignatureExpire")
+                {
+                    return Ok(JsonView(false, $"系统时间异常,请同步时间后重试!Ex:{ex.Message}"));
+                }
+                else if (ex.ErrorCode != null && ex.ErrorCode.StartsWith("Unauthorized"))
+                {
+                    return Ok(JsonView(false, $"当前账号无权限访问该服务!Ex:{ex.Message}"));
+                }
+                else
+                {
+                    return Ok(JsonView(false, $"腾讯云服务调用失败,请稍后重试!Ex:{ex.Message}"));
+                }
+            }
+            catch (Exception ex)
+            {
+                _logger.LogError(ex, "未知错误,调用混元API失败");
+                return Ok(JsonView(false, $"未知错误,调用混元API失败!Ex:{ex.Message}"));
+            }
+
+            var hunyuanAIEmailDatas = new List<AICreateEmailInfo>();
+
+            if (!string.IsNullOrWhiteSpace(response))
+            {
+                // 预处理:过滤 AI 可能返回的 Markdown 标记
+                string cleanJson = response.Trim();
+                if (cleanJson.StartsWith("```json"))
+                    cleanJson = cleanJson.Substring(7, cleanJson.Length - 10).Trim();
+                else if (cleanJson.StartsWith("```"))
+                    cleanJson = cleanJson.Substring(3, cleanJson.Length - 6).Trim();
+
+                try
+                {
+                    // 解析并注入 Source 标识
+                    hunyuanAIEmailDatas = JsonConvert.DeserializeObject<List<AICreateEmailInfo>>(cleanJson);
+                    
+                }
+                catch (JsonException ex)
+                {
+                    // 记录日志并考虑 fallback 策略
+                    _logger.LogError(ex, "Hunyuan AI 响应解析失败。原始数据:{Response}", response);
+                }
+            }
+
+            if (hunyuanAIEmailDatas == null || !hunyuanAIEmailDatas.Any())
+            {
+                return Ok(JsonView(false, "Hunyuan AI 生成的邮件为空"));
+            }
+
+            // 数据邮件存储在数据库
+            StringBuilder msgSb = new StringBuilder();
+
+            foreach (var client in clientInfoSources)
+            {
+                var aiEmailInfo = hunyuanAIEmailDatas.Where(x => x.Guid == client.Guid).FirstOrDefault();
+                if (aiEmailInfo == null)
+                {
+                    msgSb.AppendLine($"{client.NameCn}: 邮件AI生成失败!");
+                    continue;
+                }
+                client.EmailInfo.Status = 2; // AI已生成
+                client.EmailInfo.EmailTitle = aiEmailInfo.Subject;
+                client.EmailInfo.EmailContent = aiEmailInfo.Content;
+                client.EmailInfo.Operator = operatorName;
+                client.EmailInfo.OperatedAt = DateTime.Now;    
+                msgSb.AppendLine($"{client.NameCn}: 邮件AI生成成功!");
+            }
+
+            // 先删除原有数据中对应的单位数据
+            invAiInfo.AiCrawledDetails = invAiInfo.AiCrawledDetails.Where(x => !dto.Guids.Contains(x.Guid)).ToList();
+            // 再添加AI生成的邮件数据
+            invAiInfo.AiCrawledDetails.AddRange(clientInfoSources);
+
+            var update = await _sqlSugar.Updateable(invAiInfo).UpdateColumns(x => x.AiCrawledDetails).ExecuteCommandAsync();
+            if (update < 1)
+            {
+                return Ok(JsonView(false, $"邮件AI生成,数据更新失败!"));
+            }
+
+            return Ok(JsonView(true, msgSb.ToString()));
+        }
+
+        /// <summary>
+        /// 商邀资料AI 邮件保存
+        /// </summary>
+        /// <returns></returns>
+        [HttpPost]
+        [ProducesResponseType(typeof(JsonView), StatusCodes.Status200OK)]
+        public async Task<IActionResult> InvitationAIEmailSave(InvitationAIEmailSaveDto dto)
+        {
+            if (dto.Id < 1) return Ok(JsonView(false, "请选择保存的团组"));
+            if (dto.CurrUserId < 1) return Ok(JsonView(false, "请传入用户Id"));
+            if (string.IsNullOrEmpty(dto.Guid)) return Ok(JsonView(false, "请传入Guid"));
+            if (string.IsNullOrEmpty(dto.EmailTitle)) return Ok(JsonView(false, "请传入邮件标题"));
+            if (string.IsNullOrEmpty(dto.EmailContent)) return Ok(JsonView(false, "请传入邮件内容"));
+
+            var invAiInfo = await _sqlSugar.Queryable<Res_InvitationAI>()
+                .Where(x => x.Id == dto.Id)
+                .FirstAsync();
+
+            if (invAiInfo == null) return Ok(JsonView(false, "数据信息为空"));
+
+            var dataList = invAiInfo?.AiCrawledDetails;
+
+            if (dataList == null) return Ok(JsonView(false, "邀请方数据信息为空"));
+
+            var editInfo = dataList.Where(x => x.Guid == dto.Guid).FirstOrDefault();
+
+            if (editInfo == null) return Ok(JsonView(false, "邀请方信息为空"));
+
+            var opUserName = await _sqlSugar.Queryable<Sys_Users>().Where(x => x.IsDel == 0 && x.Id == dto.CurrUserId).Select(x => x.CnName).FirstAsync() ?? "-";
+
+            editInfo.EmailInfo.EmailTitle = dto.EmailTitle;
+            editInfo.EmailInfo.EmailContent = dto.EmailContent;
+            editInfo.EmailInfo.Operator = opUserName;
+            editInfo.EmailInfo.OperatedAt = DateTime.Now;
+
+            var datas = dataList.Where(x => x.Guid != dto.Guid).ToList();
+            datas.Add(editInfo);
+            invAiInfo.AiCrawledDetails = datas;
+
+            var editUpd = await _sqlSugar.Updateable(invAiInfo).UpdateColumns(x => x.AiCrawledDetails).ExecuteCommandAsync();
+            if (editUpd < 1)
+            {
+                return Ok(JsonView(true, "邮件失败"));
+            }
+
+            return Ok(JsonView(true, "邮件成功"));
+        }
+
+        /// <summary>
+        /// 商邀资料AI 发送邮件(默认当前用户企业微信邮件)
+        /// </summary>
+        /// <returns></returns>
+        [HttpPost]
+        [ProducesResponseType(typeof(JsonView), StatusCodes.Status200OK)]
+        public async Task<IActionResult> InvitationAISeedEmail(InvitationAISeedEmailDto dto)
+        {
+            if (dto.Id < 1) return Ok(JsonView(false, "请选择发送邮件的团组"));
+            if (dto.CurrUserId < 1) return Ok(JsonView(false, "请传入用户Id"));
+            if (dto.Guids == null || dto.Guids.Count < 1) return Ok(JsonView(false, "请选择发送邮件的单位"));
+
+            var invAiInfo = await _sqlSugar.Queryable<Res_InvitationAI>()
+            .Where(x => x.Id == dto.Id)
+            .FirstAsync();
+
+            if (invAiInfo == null) return Ok(JsonView(false, "数据信息为空"));
+
+            var dataList = invAiInfo?.AiCrawledDetails;
+
+            if (dataList == null) return Ok(JsonView(false, "邀请方数据信息为空"));
+
+            var seedInvInfos = dataList.Where(x => dto.Guids.Contains(x.Guid)).ToList();
+
+            if (seedInvInfos == null) Ok(JsonView(false, "发送邮件的邀请方信息为空"));
+
+            var userInfo = await _sqlSugar.Queryable<Sys_Users>().Where(x => x.IsDel == 0 && x.Id == dto.CurrUserId).Select(x => new { x.Email, x.CnName }).FirstAsync();
+
+            if (string.IsNullOrEmpty(userInfo.Email)) Ok(JsonView(false, "当前账号未配置邮箱,请先配置邮箱!"));
+
+            var seedEmails = new List<EmailInfo>();
+            #region 发送邮件
+
+            foreach (var item in seedInvInfos)
+            {
+                int seedStatus = 4;
+
+
+                try
+                {
+                    var req = new EmailRequestDto()
+                    {
+                        ToEmails = new List<string> { "johnny.yang@pan-american-intl.com" },
+                        CcEmails = new List<string> { userInfo.Email },
+                        BccEmails = new List<string> { userInfo.Email },
+                        Subject = "测试邮件 - 来自企业微信API",
+                        Body = "这是一封通过企业微信API发送的测试邮件,包含附件。",
+                        Files = Array.Empty<IFormFile>()
+                    };
+                    var response = await _qiYeWeChatApiService.EmailSendAsync(req);
+                    if (response.errcode == 0)
+                    {
+                        seedStatus = 3;
+                    }
+
+                }
+                catch (Exception ex)
+                {
+                    _logger.LogError(ex, "商邀AI调用企业微信邮件API失败。");
+                }
+
+                seedEmails.Add(new() { EmailContent = "邮件内容", Status = seedStatus, OperatedAt = DateTime.Now, Operator = userInfo.CnName });
+            }
+
+            #endregion
+
+
+
+            return Ok(JsonView(true, "发送成功"));
+        }
+
         #endregion
 
         #region 公务出访

+ 32 - 0
OASystem/OASystem.Api/OAMethodLib/GeneralMethod.cs

@@ -6983,6 +6983,38 @@ namespace OASystem.API.OAMethodLib
 
 
         #endregion
+
+        #region 商邀 AI 辅助方法
+
+        /// <summary>
+        /// AI对话结果中提取JSON
+        /// </summary>
+        /// <param name="input"></param>
+        /// <returns></returns>
+        public static string ExtractJson(string input)
+        {
+            // 1) 优先匹配 Markdown 代码块: ```json ... ``` 或 ``` ... ```
+            var m = Regex.Match(
+                input,
+                @"```(?:json)?\s*([\s\S]*?)\s*```",
+                RegexOptions.IgnoreCase);
+            if (m.Success)
+                return m.Groups[1].Value.Trim();
+            // 2) 兜底:从文本中截取第一个 JSON 数组/对象
+            int arrStart = input.IndexOf('[');
+            int arrEnd = input.LastIndexOf(']');
+            if (arrStart >= 0 && arrEnd > arrStart)
+                return input.Substring(arrStart, arrEnd - arrStart + 1).Trim();
+            int objStart = input.IndexOf('{');
+            int objEnd = input.LastIndexOf('}');
+            if (objStart >= 0 && objEnd > objStart)
+                return input.Substring(objStart, objEnd - objStart + 1).Trim();
+            // 3) 实在提取不到,按原文返回,让反序列化抛错
+            return input.Trim();
+        }
+
+        #endregion
+
     }
 }
 

+ 4 - 0
OASystem/OASystem.Api/appsettings.json

@@ -468,6 +468,10 @@
         {
           "Id": 396,
           "Name": "李瑛"
+        },
+        {
+          "Id": 397,
+          "Name": "资怡"
         }
       ]
     }

+ 39 - 1
OASystem/OASystem.Domain/Dtos/Resource/InvitationAI.cs

@@ -1,4 +1,6 @@
-namespace OASystem.Domain.Dtos.Resource
+using OASystem.Domain.Entities.Resource;
+
+namespace OASystem.Domain.Dtos.Resource
 {
     public class InvitationAI
     {
@@ -37,6 +39,42 @@
     public class InvitationAICompleteTextDto : InvitationAISearchDto
     { }
 
+    public class InvitationAISeedEmailDto: InvitationAISearchDto
+    {
+        public List<string> Guids { get; set; } 
+    }
+
+    public class InvitationAISaveDto : InvitationAISearchDto
+    {
+        public InvitationAIInfo AiCrawledDetail { get; set; }
+    }
+
+    public class InvitationAIEmailSaveDto : InvitationAISearchDto 
+    {
+        /// <summary>
+        /// Guid
+        /// </summary>
+        public string Guid { get; set; }
+
+        /// <summary>
+        /// 邮件标题
+        /// </summary>
+        public string EmailTitle { get; set; }
+
+        /// <summary>
+        /// 邮箱内容
+        /// </summary>
+        public string EmailContent { get; set; }
+    }
+
+
+    public class InvitationAIGenerateEmailDto: InvitationAISearchDto
+    {
+        /// <summary>
+        /// Guid
+        /// </summary>
+        public List<string> Guids { get; set; }
+    }
 
 
     public class InvitationAISingleDelDto

+ 49 - 3
OASystem/OASystem.Domain/Entities/Resource/Res_InvitationAI.cs

@@ -35,8 +35,7 @@ namespace OASystem.Domain.Entities.Resource
         /// 词条信息
         /// </summary>
         [SugarColumn(ColumnName = "Entries", ColumnDescription = "词条信息", IsJson = true, IsNullable = true, ColumnDataType = "varchar(max)")]
-        public EntryInfo Entries { get; set; } = new EntryInfo();
-
+        public EntryInfo EntryInfo { get; set; } = new EntryInfo();
     }
 
     /// <summary>
@@ -47,7 +46,7 @@ namespace OASystem.Domain.Entities.Resource
         /// <summary>
         /// Guid
         /// </summary>
-        public string  Guid { get; set; }
+        public string Guid { get; set; }
 
         /// <summary>
         /// 数据来源
@@ -107,6 +106,11 @@ namespace OASystem.Domain.Entities.Resource
         /// </summary>
         public string IntgAdvice { get; set; }
 
+        /// <summary>
+        /// 邮件信息
+        /// </summary>
+        public EmailInfo EmailInfo { get; set; } = new EmailInfo();
+
         /// <summary>
         /// 备注
         /// </summary>
@@ -153,4 +157,46 @@ namespace OASystem.Domain.Entities.Resource
         /// </summary>
         public string Operator { get; set; }
     }
+
+    public class EmailInfo
+    {
+        /// <summary>
+        /// 邮件标题
+        /// </summary>
+        public string EmailTitle { get; set; }
+
+        /// <summary>
+        /// 邮箱内容
+        /// </summary>
+        public string EmailContent { get; set; }
+
+        /// <summary>
+        /// 发送状态
+        /// 1.未开始
+        /// 2.AI生成成功
+        /// 3.发送完成
+        /// 4.发送失败
+        /// </summary>
+        public int Status { get; set; } = 1;
+
+        /// <summary>
+        /// 操作时间
+        /// </summary>
+        public DateTime OperatedAt { get; set; }
+        /// <summary>
+        /// 操作人
+        /// </summary>
+        public string Operator { get; set; }
+    }
+
+
+    public class AICreateEmailInfo
+    {
+
+        public string Guid { get; set; }
+        public string NameCn { get; set; }
+        public string Scope { get; set; }
+        public string Subject { get; set; }
+        public string Content { get; set; }
+    }
 }