Selaa lähdekoodia

商邀 删除、AI续写、文件下载接口

Lyyyi 1 päivä sitten
vanhempi
commit
53633ebd07

+ 571 - 70
OASystem/OASystem.Api/Controllers/ResourceController.cs

@@ -1,23 +1,23 @@
 using Aspose.Cells;
 using Aspose.Words;
-using Dm.util;
-using EyeSoft.Collections.Generic;
+using Aspose.Words.Tables;
 using EyeSoft.Extensions;
-using Microsoft.IdentityModel.Tokens;
+using NPOI.SS.Formula.Functions;
 using NPOI.SS.UserModel;
 using OASystem.API.OAMethodLib;
 using OASystem.API.OAMethodLib.HunYuanAPI;
 using OASystem.API.OAMethodLib.QiYeWeChatAPI.AppNotice;
 using OASystem.Domain.AesEncryption;
 using OASystem.Domain.Dtos.Groups;
-using OASystem.Domain.Entities.Customer;
 using OASystem.Domain.Entities.Groups;
 using OASystem.Domain.ViewModels.Groups;
 using OASystem.Infrastructure.Repositories.Groups;
+using Quartz.Util;
 using System.ComponentModel.DataAnnotations;
 using System.Data;
 using System.Diagnostics;
 using TencentCloud.Common;
+using static OASystem.API.OAMethodLib.GeneralMethod;
 using static OASystem.API.OAMethodLib.JWTHelper;
 
 namespace OASystem.API.Controllers
@@ -2283,41 +2283,20 @@ Inner Join Sys_Department as d With(Nolock) On u.DepId=d.Id Where m.Id={0} ", _m
         {
             var itemNames = await GeneralMethod.InvitationAIInvName();
 
-            var invDatas = await _sqlSugar.Queryable<Crm_NewClientData>()
-                .Where(x => x.IsDel == 0)
-                .Where(x => !x.Client.Contains("-") && x.Client != null)
-                .Select(x => x.Client)
-                .ToListAsync();
-
-            invDatas = invDatas
-                .Where(c => !string.IsNullOrWhiteSpace(c)) // 确保非空
-                .Select(c => c.Replace(" ", ""))           // 去除所有空格
-                .Distinct()                                // 炼金:去重
-                .ToList();
+            var unitNames = await GeneralMethod.InvitationAIClientName();
 
-            var countries = await _sqlSugar.Queryable<Sys_Countries>()
-                .Where(x => x.IsDel == 0)
-                .Select(x => x.Name_CN)
-                .Distinct()
-                .ToListAsync();
+            var countries = await GeneralMethod.InvitationAICountryName();
 
-            // 解密与去重(使用 HashSet 提升性能)
-            var unitNames = invDatas
-                .Select(item => AesEncryptionHelper.Decrypt(item))
-                .Where(val => !string.IsNullOrEmpty(val))
-                .Distinct()
-                .ToList();
-
-            return Ok(new
+            return Ok(JsonView(true, $"查询成功!", new
             {
                 itemNames,
                 unitNames,
                 countries
-            });
+            }));
         }
 
         /// <summary>
-        /// 商邀资料AI 细聊列表
+        /// 商邀资料AI 资料列表
         /// </summary>
         /// <returns></returns>
         [HttpGet("{name}")]
@@ -2325,20 +2304,30 @@ Inner Join Sys_Department as d With(Nolock) On u.DepId=d.Id Where m.Id={0} ", _m
         public async Task<IActionResult> InvitationAIItemByName(string name)
         {
             // 基础校验
-            if (string.IsNullOrWhiteSpace(name) )
+            if (string.IsNullOrWhiteSpace(name))
                 return Ok(JsonView(false, "请传入有效的名称!"));
 
             var info = await _sqlSugar.Queryable<Res_InvitationAI>()
                 .Where(x => x.IsDel == 0 && x.InvName == name)
                 .FirstAsync();
 
+            if (info == null)
+            {
+                return Ok(JsonView(true, $"暂无数据",new {
+                    Id = 0,
+                    GroupId= 0,
+                    InvName = "",
+                    AiCrawledDetails = new List<InvitationAIInfo>(),
+                }));
+            }
+
             var view = new
             {
                 info.Id,
                 info.InvName,
                 info.GroupId,
                 info.AiCrawledDetails,
-                Entry = info.Entries.FirstOrDefault(),
+                Entry = info.Entries,
             };
             return Ok(JsonView(true, $"查询成功!", view));
         }
@@ -2351,27 +2340,24 @@ Inner Join Sys_Department as d With(Nolock) On u.DepId=d.Id Where m.Id={0} ", _m
         [ProducesResponseType(typeof(JsonView), StatusCodes.Status200OK)]
         public async Task<IActionResult> InvitationAISearch(InvitationAISearchDto dto)
         {
-            // 基础校验
-            if (string.IsNullOrWhiteSpace(dto.UnitName) || string.IsNullOrWhiteSpace(dto.Country))
-                return Ok(JsonView(false, "请传入有效的单位名称和国家!"));
+            var invAiInfo = await _sqlSugar.Queryable<Res_InvitationAI>().Where(x => x.Id == dto.Id).FirstAsync();
+
+            if (invAiInfo == null || invAiInfo?.Entries == null)
+            {
+                return Ok(JsonView(false, "请先设置关键字信息!"));
+            }
+
+            var entryInfo = invAiInfo.Entries;
 
             // 当前企业领域
             string industryFocus = string.Empty;
 
             // 动态计算 AI 需求缺口
-            int totalTarget = 15;
+            int totalTarget = entryInfo.NeedCount;
 
             // 词条
             string question = string.Empty;
 
-            var groupInfo = await _sqlSugar.Queryable<Grp_DelegationInfo>()
-                .Where(x => x.IsDel == 0 && x.Id == dto.GroupId)
-                .Select(x => new { x.TeamName, x.VisitPurpose })
-                .FirstAsync();
-
-            // 当前企业出访目的
-            string objective = groupInfo?.VisitPurpose ?? "商务考察与合作对接";
-
             string operatorName = await _sqlSugar.Queryable<Sys_Users>().Where(x => x.IsDel == 0 && x.Id == dto.CurrUserId).Select(x => x.CnName).FirstAsync() ?? "-";
 
             var localInvDatas = new List<InvitationAIInfo>(); // 本地数据源(商邀资料)
@@ -2379,14 +2365,15 @@ Inner Join Sys_Department as d With(Nolock) On u.DepId=d.Id Where m.Id={0} ", _m
             #region 本地数据源(商邀资料)
 
             var rawData = await _sqlSugar.Queryable<Res_InvitationOfficialActivityData>()
-                .Where(x => x.IsDel == 0 && x.Country.Equals(dto.Country))
+                .Where(x => x.IsDel == 0 && x.Country.Equals(entryInfo.TargetCountry))
                 .ToListAsync();
 
             // 解密
             localInvDatas = rawData.AsParallel().AsOrdered().Select(item => new InvitationAIInfo
             {
+                Guid = Guid.NewGuid().ToString("N"),
                 Source = 0, // 明确来源标识
-                Region = dto.UnitName,
+                Region = entryInfo.OriginUnit,
                 NameCn = AesEncryptionHelper.Decrypt(item.UnitName),
                 Address = AesEncryptionHelper.Decrypt(item.Address),
                 Scope = AesEncryptionHelper.Decrypt(item.Field),
@@ -2413,12 +2400,11 @@ Inner Join Sys_Department as d With(Nolock) On u.DepId=d.Id Where m.Id={0} ", _m
 - Target Architecture: .NET 6 DTO Compatible Interface
 
 # [INPUT_CONFIG]
-- OriginUnit: {{{dto.UnitName}}} 
-- TargetCountry: {{{dto.Country}}}
-- Objective: {{{objective}}}
+- OriginUnit: {{{entryInfo.OriginUnit}}} 
+- TargetCountry: {{{entryInfo.TargetCountry}}}
+- Objective: {{{entryInfo.Objective}}}
 - DataCount: {{{aiNeedCount}}}
-- OtherConstraints: {{{dto.Remark}}}
-
+- OtherConstraints: {{{entryInfo.OtherConstraints}}}
 # [ROLE_DEFINITION]
 你是一位精通全球跨境经贸、海外园区政策及 {{TargetCountry}} 本地准入法规的【顶级商务咨询顾问】。你擅长通过“产业链对等原则(Chain-Parity Principle)”为跨国企业精准匹配具备落地价值的合作伙伴。
 
@@ -2518,9 +2504,11 @@ Inner Join Sys_Department as d With(Nolock) On u.DepId=d.Id Where m.Id={0} ", _m
 
                         if (hunyuanAIInvDatas != null)
                         {
-                            hunyuanAIInvDatas = hunyuanAIInvDatas.Select(x => {
+                            hunyuanAIInvDatas = hunyuanAIInvDatas.Select(x =>
+                            {
+                                x.Guid = Guid.NewGuid().ToString("N");
                                 x.Source = 1;
-                                x.Region = dto.Country;
+                                x.Region = entryInfo.TargetCountry;
                                 x.Operator = operatorName;
                                 x.OperatedAt = DateTime.Now;
                                 return x;
@@ -2542,14 +2530,48 @@ Inner Join Sys_Department as d With(Nolock) On u.DepId=d.Id Where m.Id={0} ", _m
             // 数据整合 (高效合并)
             var finalResult = localInvDatas.Concat(hunyuanAIInvDatas).ToList();
 
-            // 词条信息
-            var entries = new List<EntryInfo>() { new EntryInfo() {
-                Details = question,
-                Operator = operatorName,
-                OperatedAt = DateTime.Now
-            }  };
+            string invName = $"{entryInfo.OriginUnit}拜访{entryInfo.TargetCountry}";
+
+            invAiInfo.AiCrawledDetails = finalResult;
 
-            string invName = $"{dto.UnitName}拜访{dto.Country}";
+            var update = await _sqlSugar.Updateable(invAiInfo).ExecuteCommandAsync();
+            if (update < 1)
+            {
+                return Ok(JsonView(false, $"数据更新失败!"));
+            }
+
+            #endregion
+
+            return Ok(JsonView(true, $"查询成功!", new
+            {
+                invAiInfo.Id,
+                invAiInfo.InvName,
+                invAiInfo.GroupId,
+                invAiInfo.AiCrawledDetails,
+                Entry = invAiInfo.Entries
+            }));
+        }
+
+        /// <summary>
+        /// 商邀资料AI 设置词条
+        /// </summary>
+        /// <returns></returns>
+        [HttpPost]
+        [ProducesResponseType(typeof(JsonView), StatusCodes.Status200OK)]
+        public async Task<IActionResult> InvitationAISetPrompt(InvitationAISetPromptDto dto)
+        {
+            // 基础校验
+            if (string.IsNullOrWhiteSpace(dto.OriginUnit) || string.IsNullOrWhiteSpace(dto.TargetCountry))
+                return Ok(JsonView(false, "请传入有效的单位名称和国家!"));
+
+            var groupInfo = await _sqlSugar.Queryable<Grp_DelegationInfo>()
+                .Where(x => x.IsDel == 0 && x.Id == dto.GroupId)
+                .Select(x => new { x.TeamName, x.VisitPurpose })
+                .FirstAsync();
+
+            string invName = $"{dto.OriginUnit}拜访{dto.TargetCountry}";
+
+            #region 数据库操作
 
             // 数据库信息获取方式
             // 1.通过团组 GroupId 关联查询 
@@ -2558,6 +2580,21 @@ Inner Join Sys_Department as d With(Nolock) On u.DepId=d.Id Where m.Id={0} ", _m
             // 2.通过InvName 关联查询(考虑到可能存在同名情况,优先使用 GroupId 关联查询,确保数据准确性)
             dataInfo ??= await _sqlSugar.Queryable<Res_InvitationAI>().Where(x => x.IsDel == 0 && x.InvName == invName).FirstAsync();
 
+            #region 词条信息
+
+            string operatorName = await _sqlSugar.Queryable<Sys_Users>().Where(x => x.IsDel == 0 && x.Id == dto.CurrUserId).Select(x => x.CnName).FirstAsync() ?? "-";
+
+            var entryInfo = new EntryInfo()
+            {
+                OriginUnit = dto.OriginUnit,
+                TargetCountry = dto.TargetCountry,
+                Objective = groupInfo?.VisitPurpose ?? "商务考察与合作对接",
+                OtherConstraints = dto.OtherConstraints,
+                Operator = operatorName,
+                OperatedAt = DateTime.Now
+            };
+            #endregion
+
             // 3.如果以上两种方式都没有查询到数据,则说明是新数据,需要添加到数据库
             if (dataInfo == null)
             {
@@ -2566,41 +2603,505 @@ 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,
-                    AiCrawledDetails = finalResult,
-                    Entries = entries,
+                    Entries = entryInfo,
                     CreateUserId = dto.CurrUserId
                 };
 
                 var insert = await _sqlSugar.Insertable(dataInfo).ExecuteCommandAsync();
                 if (insert < 1)
                 {
-                    return Ok(JsonView(false, $"数据新增失败!"));
+                    return Ok(JsonView(false, $"词条信息新增失败!"));
                 }
             }
             else
             {
-                // 3.1 数据存在 则更新数据(覆盖原有的AI爬取详情,保留原有的其他字段不变)
-                dataInfo.AiCrawledDetails = finalResult;
+                // 3.2 数据存在 则更新数据(覆盖原有的AI爬取详情,保留原有的其他字段不变)
+                dataInfo.Entries = entryInfo;
 
                 var update = await _sqlSugar.Updateable(dataInfo).ExecuteCommandAsync();
                 if (update < 1)
                 {
-                    return Ok(JsonView(false, $"数据更新失败!"));
+                    return Ok(JsonView(false, $"词条信息更新失败!"));
                 }
             }
 
             #endregion
 
-            return Ok(JsonView(true, $"查询成功!", new
+            return Ok(JsonView(true, $"设置成功!", new
+            {
+                dataInfo.Id,
+                dataInfo.InvName,
+                dataInfo.GroupId,
+                Entry = dataInfo.Entries,
+            }));
+        }
+
+        /// <summary>
+        /// 商邀资料AI 单条资料删除
+        /// </summary>
+        /// <returns></returns>
+        [HttpPost]
+        [ProducesResponseType(typeof(JsonView), StatusCodes.Status200OK)]
+        public async Task<IActionResult> InvitationAISingleDel(InvitationAISingleDelDto dto)
+        {
+            int id = dto.Id;
+            string guid = dto.Guid;
+            // 基础校验
+            if (id < 1 || string.IsNullOrWhiteSpace(guid))
+                return Ok(JsonView(false, "请传入有效的Id和Guid!"));
+
+            var dataInfo = await _sqlSugar.Queryable<Res_InvitationAI>().Where(x => x.IsDel == 0 && x.Id == id).FirstAsync();
+
+            if (dataInfo == null)
+                return Ok(JsonView(false, "当前数据信息不存在!"));
+
+            var newDataInfos = dataInfo.AiCrawledDetails.Where(x => x.Guid != guid).ToList();
+            dataInfo.AiCrawledDetails = newDataInfos;
+
+            var update = await _sqlSugar.Updateable(dataInfo).ExecuteCommandAsync();
+            if (update < 1)
+            {
+                return Ok(JsonView(false, $"删除失败!"));
+            }
+
+            return Ok(JsonView(true, $"删除成功!", new
             {
                 dataInfo.Id,
                 dataInfo.InvName,
                 dataInfo.GroupId,
-                dataInfo.AiCrawledDetails,
-                Entry = dataInfo.Entries.FirstOrDefault(),
+                dataInfo.AiCrawledDetails
             }));
         }
 
+        /// <summary>
+        /// 商邀资料AI 混元AI查询资料
+        /// </summary>
+        /// <returns></returns>
+        [HttpPost]
+        [ProducesResponseType(typeof(JsonView), StatusCodes.Status200OK)]
+        public async Task<IActionResult> InvitationAICompleteText(InvitationAICompleteTextDto dto)
+        {
+            var invAiInfo = await _sqlSugar.Queryable<Res_InvitationAI>().Where(x => x.Id == dto.Id).FirstAsync();
+
+            if (invAiInfo == null || invAiInfo?.Entries == null)
+            {
+                return Ok(JsonView(false, "请先设置关键字信息!"));
+            }
+
+            var entryInfo = invAiInfo.Entries;
+
+            // 已搜索出的数据(AI 数据源)
+            var hunyuanAIDatas = invAiInfo.AiCrawledDetails.Where(x => x.Source == 1).ToList() ?? new List<InvitationAIInfo>();
+
+            // 当前企业领域
+            string industryFocus = string.Empty;
+
+            // 动态计算 AI 需求缺口
+            int totalTarget = entryInfo.NeedCount;
+
+            // 词条
+            string question = string.Empty;
+
+            string operatorName = await _sqlSugar.Queryable<Sys_Users>().Where(x => x.IsDel == 0 && x.Id == dto.CurrUserId).Select(x => x.CnName).FirstAsync() ?? "-";
+
+            #region 混元AI 获取商邀资料
+
+            var hunyuanAIInvDatas = new List<InvitationAIInfo>();
+
+            int aiNeedCount = Math.Max(0, totalTarget - hunyuanAIDatas.Count);
+
+            // 已存在数据名称整理
+            var existingNames = new HashSet<string>(hunyuanAIDatas.Select(x => x.NameCn));
+            entryInfo.OtherConstraints = $"请基于以下已存在的名称列表进行推荐,避免重复:{string.Join(", ", existingNames)}。{entryInfo.OtherConstraints}";
+
+            if (aiNeedCount > 0)
+            {
+                question = @$"# [SYSTEM_CONTEXT]
+- Role: Senior Business Consultant (Global Supply Chain & Cross-border Trade Expert)
+- Framework: S.P.A.R. (Situation-Problem-Action-Result)
+- Target Architecture: .NET 6 DTO Compatible Interface
+
+# [INPUT_CONFIG]
+- OriginUnit: {{{entryInfo.OriginUnit}}} 
+- TargetCountry: {{{entryInfo.TargetCountry}}}
+- Objective: {{{entryInfo.Objective}}}
+- DataCount: {{{aiNeedCount}}}
+- OtherConstraints: {{{entryInfo.OtherConstraints}}}
+# [ROLE_DEFINITION]
+你是一位精通全球跨境经贸、海外园区政策及 {{TargetCountry}} 本地准入法规的【顶级商务咨询顾问】。你擅长通过“产业链对等原则(Chain-Parity Principle)”为跨国企业精准匹配具备落地价值的合作伙伴。
+
+# [BUSINESS_LOGIC_CoT]
+在生成结果前,请严格执行以下思维链拆解:
+1. **EntityProfiling**: 识别 {{OriginUnit}} 的核心生态位(如:Supply/Demand/Capital/Logistics)。
+2. **ParityMatching**: 检索 {{TargetCountry}} 境内业务闭环对等机构(例如:若 Origin 为分销,则 Target 为源头工厂/种植园)。
+3. **RegulatoryCheck**: 验证目标机构的合规性(如:GACC 备案、SPS 协议、或当地政府特许经营权)。
+4. **DataSynthesis**: 针对无法直接获取的动态(如 PostUrl),优先检索其官网 News 频道或 LinkedIn 企业号。
+
+# [CONSTRAINTS_&_STANDARDS]
+- **NamingConvention**: 所有 JSON Key 必须严格遵循 **PascalCase**(例如:`UnitNameCn` 而非 `unit_name_cn`)。
+- **DataIntegrity**: 
+    - 必须输出精确的 {{DataCount}} 条记录。
+    - 优先级:Core (核心机构) > Backup (关联替代机构)。
+- **ValidationRules**:
+    - `Phone`: 必须包含 {{TargetCountry}} 国际区号(如 +856, +66 等)。
+    - `IntgAdvice`: 必须包含“对等性分析”,解释该机构如何与 {{OriginUnit}} 形成业务闭环(50-100字)。
+    - `Status`: 若字段确实无法获取,统一填充 'N/A',禁止编造。
+- **Safety**: 确保推荐机构不涉及敏感黑名单或已破产企业。
+
+# [INFORMATION_SCHEMA]
+请将结果填充至以下结构的 JSON 数组中:
+- NameCn: 单位名称(中文)
+- NameEn: 单位名称(英文)
+- Address: 详细地理位置(含省市区街道)
+- Scope: 经营范围(需强调其出口配额、生产能力或行业地位)
+- Contact: 联系人姓名及职务
+- Phone: 拨打全号(含区号)
+- Email: 商务联络邮箱
+- SiteUrl: 官方网站或权威社媒主页
+- PostUrl: 近一年内的商务动态/新闻链接
+- RecLevel: 推荐等级(枚举值:Core, Backup)
+- IntgAdvice: 对接深度建议(基于产业链交合、互补逻辑)
+
+# [OUTPUT_PROTOCOL]
+- 仅输出一个标准的 JSON Array 字符串。
+- 严禁任何 Markdown 说明文字、代码块之外的解释或开场白。
+- 确保 JSON 语法在 .NET `JsonSerializer.Deserialize` 下可直接解析。
+
+# [EXECUTION]
+根据以上配置,开始生成。";
+
+                string response = string.Empty;
+                try
+                {
+                    response = await _hunyuanService.ChatCompletionsHunyuan_t1_latestAsync(question);
+                }
+                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}"));
+                }
+
+                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 标识
+                        hunyuanAIInvDatas = JsonConvert.DeserializeObject<List<InvitationAIInfo>>(cleanJson);
+
+                        if (hunyuanAIInvDatas != null)
+                        {
+                            hunyuanAIInvDatas = hunyuanAIInvDatas.Select(x =>
+                            {
+                                x.Guid = Guid.NewGuid().ToString("N");
+                                x.Source = 1;
+                                x.Region = entryInfo.TargetCountry;
+                                x.Operator = operatorName;
+                                x.OperatedAt = DateTime.Now;
+                                return x;
+                            }).ToList();
+                        }
+                    }
+                    catch (JsonException ex)
+                    {
+                        // 记录日志并考虑 fallback 策略
+                        _logger.LogError(ex, "Hunyuan AI 响应解析失败。原始数据:{Response}", response);
+                    }
+                }
+
+                #region 数据库操作
+                invAiInfo.AiCrawledDetails.AddRange(hunyuanAIInvDatas);
+
+                var update = await _sqlSugar.Updateable(invAiInfo).ExecuteCommandAsync();
+                if (update < 1)
+                {
+                    return Ok(JsonView(false, $"AI续写,数据更新失败!"));
+                }
+
+                return Ok(JsonView(true, $"AI续写成功!操作数据{hunyuanAIInvDatas.Count}条"));
+
+                #endregion
+            }
+            #endregion
+
+            return Ok(JsonView(false, $"AI续写失败!"));
+        }
+
+        /// <summary>
+        /// 商邀资料AI 文件生成(基于混元AI已爬取数据进行格式化输出,供用户下载使用)
+        /// </summary>
+        /// <returns></returns>
+        [HttpGet("{id}")]
+        [ProducesResponseType(typeof(JsonView), StatusCodes.Status200OK)]
+        public async Task<IActionResult> InvitationAIFileGenerator(int id)
+        {
+            var invAiInfo = await _sqlSugar.Queryable<Res_InvitationAI>()
+            .Where(x => x.Id == id)
+            .FirstAsync();
+
+            var dataList = invAiInfo?.AiCrawledDetails;
+
+            if (dataList == null)
+                return Ok(JsonView(false, "数据为空"));
+
+            Document doc = new Document();
+            DocumentBuilder builder = new DocumentBuilder(doc);
+
+            // A4页面设置
+            double usableWidth = 495;
+
+            foreach (Section sec in doc.Sections)
+            {
+                sec.PageSetup.PaperSize = Aspose.Words.PaperSize.A4;
+                sec.PageSetup.LeftMargin = 50;
+                sec.PageSetup.RightMargin = 50;
+                sec.PageSetup.TopMargin = 50;
+                sec.PageSetup.BottomMargin = 50;
+            }
+
+            // 卡片背景
+            Color cardBg = Color.FromArgb(252, 252, 252);
+
+            string fontName = "微软雅黑";
+
+            int count = 1;
+
+            foreach (var item in dataList)
+            {
+                // 标题
+                builder.ParagraphFormat.ClearFormatting();
+                builder.Font.ClearFormatting();
+
+                builder.Font.Name = fontName;
+                builder.Font.Size = 16;
+                builder.Font.Bold = true;
+                builder.Font.Color = Color.Black;
+
+                builder.Write($"{count++}. {item.NameCn}");
+                builder.InsertParagraph();
+
+                // 英文名
+                builder.Font.Size = 10;
+                builder.Font.Bold = false;
+                builder.Font.Color = Color.FromArgb(120, 120, 120);
+                builder.Write(item.NameEn ?? "");
+
+                builder.InsertParagraph();
+                builder.InsertParagraph();
+
+                // ====== 卡片表格 ======
+                Aspose.Words.Tables.Table table = builder.StartTable();
+
+                builder.CellFormat.Shading.BackgroundPatternColor = cardBg;
+                builder.CellFormat.VerticalAlignment = CellVerticalAlignment.Center;
+                builder.CellFormat.WrapText = true;
+
+                builder.CellFormat.LeftPadding = 12;
+                builder.CellFormat.RightPadding = 12;
+                builder.CellFormat.TopPadding = 8;
+                builder.CellFormat.BottomPadding = 8;
+
+                double labelW = 120;
+
+                AddSingleRow(builder, fontName, labelW, usableWidth - labelW, "详细地址", item.Address);
+
+                AddSingleRow(builder, fontName, labelW, usableWidth - labelW, "联系人", item.Contact);
+
+                AddSingleRow(builder, fontName, labelW, usableWidth - labelW, "联系电话", item.Phone);
+
+                AddSingleRow(builder, fontName, labelW, usableWidth - labelW, "电子邮箱", item.Email);
+
+                AddSingleLinkRow(builder, fontName, labelW, usableWidth - labelW, "官方网站", item.SiteUrl);
+
+                AddSingleLinkRow(builder, fontName, labelW, usableWidth - labelW, "最新动态", item.PostUrl);
+
+                AddFullWidthRow(builder, fontName, labelW, usableWidth - labelW, "推荐等级", item.RecLevel);
+
+                AddFullWidthRow(builder, fontName, labelW, usableWidth - labelW, "经营范围", item.Scope);
+
+                AddFullWidthRow(builder, fontName, labelW, usableWidth - labelW, "对接建议", item.IntgAdvice);
+
+                // 表格样式
+                table.SetBorders(Aspose.Words.LineStyle.Single, 0.5, Color.FromArgb(230, 230, 230));
+                table.AutoFit(AutoFitBehavior.FixedColumnWidths);
+                table.AllowAutoFit = false;
+                table.PreferredWidth = PreferredWidth.FromPoints(usableWidth);
+
+                builder.EndTable();
+
+                builder.InsertParagraph();
+                builder.InsertParagraph();
+            }
+
+            // 保存逻辑
+            string fileName = $"{invAiInfo.InvName}_Professional.docx";
+            string filePath = Path.Combine(AppSettingsHelper.Get("InvitationAIAssistBasePath"), fileName);
+            doc.Save(filePath, Aspose.Words.SaveFormat.Docx);
+
+            return Ok(JsonView(true, "生成成功", new { Url = $"{AppSettingsHelper.Get("WordBaseUrl")}/{AppSettingsHelper.Get("InvitationAIAssistFtpPath")}/{fileName}" }));
+        }
+        
+        #region 15.12 适配助手方法
+
+        private void AddSingleRow(DocumentBuilder builder, string font, double labelW, double contentW, string label, string value)
+        {
+            // 标签
+            builder.InsertCell();
+            builder.CellFormat.Width = labelW;
+
+            builder.ParagraphFormat.Alignment = ParagraphAlignment.Right;
+
+            builder.Font.Name = font;
+            builder.Font.Size = 9;
+            builder.Font.Bold = false;
+            builder.Font.Color = Color.FromArgb(120, 120, 120);
+
+            builder.Write(label);
+
+            // 内容
+            builder.InsertCell();
+            builder.CellFormat.Width = contentW;
+            builder.CellFormat.HorizontalMerge = CellMerge.First;
+
+            builder.ParagraphFormat.Alignment = ParagraphAlignment.Left;
+
+            builder.Font.Size = 10;
+            builder.Font.Bold = false;
+            builder.Font.Color = Color.FromArgb(60, 60, 60);
+
+            builder.Write(string.IsNullOrEmpty(value) ? "—" : value);
+
+            // 合并剩余列
+            builder.InsertCell();
+            builder.CellFormat.HorizontalMerge = CellMerge.Previous;
+
+            builder.InsertCell();
+            builder.CellFormat.HorizontalMerge = CellMerge.Previous;
+
+            builder.EndRow();
+
+            builder.CellFormat.HorizontalMerge = CellMerge.None;
+        }
+
+        private void AddSingleLinkRow(DocumentBuilder builder, string font, double labelW, double contentW, string label, string url)
+        {
+            builder.InsertCell();
+            builder.CellFormat.Width = labelW;
+
+            builder.ParagraphFormat.Alignment = ParagraphAlignment.Right;
+
+            builder.Font.Size = 9;
+            builder.Font.Bold = false;
+            builder.Font.Color = Color.FromArgb(120, 120, 120);
+
+            builder.Write(label);
+
+            builder.InsertCell();
+            builder.CellFormat.Width = contentW;
+            builder.CellFormat.HorizontalMerge = CellMerge.First;
+
+            builder.ParagraphFormat.Alignment = ParagraphAlignment.Left;
+
+            if (!string.IsNullOrEmpty(url) && url.StartsWith("http"))
+            {
+                builder.Font.Size = 10;
+                builder.Font.Color = Color.FromArgb(0, 102, 204);
+                builder.Font.Underline = Underline.None;
+
+                builder.InsertHyperlink(url, url, false);
+            }
+            else
+            {
+                builder.Font.Color = Color.FromArgb(60, 60, 60);
+                builder.Write("—");
+            }
+
+            builder.InsertCell();
+            builder.CellFormat.HorizontalMerge = CellMerge.Previous;
+
+            builder.InsertCell();
+            builder.CellFormat.HorizontalMerge = CellMerge.Previous;
+
+            builder.EndRow();
+
+            builder.CellFormat.HorizontalMerge = CellMerge.None;
+        }
+
+        private void AddFullWidthRow(DocumentBuilder builder, string font, double lw, double cw, string label, string value)
+        {
+            // 标签
+            builder.InsertCell();
+            builder.CellFormat.Width = lw;
+
+            builder.ParagraphFormat.Alignment = ParagraphAlignment.Right;
+
+            builder.Font.Size = 9;
+            builder.Font.Bold = false;
+            builder.Font.Color = Color.FromArgb(120, 120, 120);
+
+            builder.Write(label);
+
+            // 内容
+            builder.InsertCell();
+            builder.CellFormat.Width = cw;
+            builder.CellFormat.HorizontalMerge = CellMerge.First;
+
+            builder.ParagraphFormat.Alignment = ParagraphAlignment.Left;
+
+            builder.Font.Size = 10;
+            builder.Font.Bold = false;
+            builder.Font.Color = Color.FromArgb(60, 60, 60);
+
+            builder.Write(string.IsNullOrEmpty(value) ? "—" : value);
+
+            builder.InsertCell();
+            builder.CellFormat.HorizontalMerge = CellMerge.Previous;
+
+            builder.InsertCell();
+            builder.CellFormat.HorizontalMerge = CellMerge.Previous;
+
+            builder.EndRow();
+
+            builder.CellFormat.HorizontalMerge = CellMerge.None;
+        }
+
+        #endregion
+
         #endregion
 
         #region 公务出访
@@ -3684,7 +4185,7 @@ Inner Join Sys_Department as d With(Nolock) On u.DepId=d.Id Where m.Id={0} ", _m
                                     b.CreateTime
                                 })
                                 .ToList()
-                                .Where(x => !x.Client.IsNullOrWhiteSpace())
+                                .Where(x => !string.IsNullOrWhiteSpace(x.Client))
                                 .ToList();
 
                 if (Query_DB.Count > 0)

+ 148 - 3
OASystem/OASystem.Api/Controllers/SearchController.cs

@@ -740,13 +740,12 @@ namespace OASystem.API.Controllers
             }
         }
 
-
         /// <summary>
-        /// 商邀AI 关键字输入提示(单字段)
+        /// 商邀AI invName关键字输入提示(单字段)
         /// </summary>
         /// <param name="keyword">关键字</param>
         /// <returns></returns>
-        [HttpGet("ClientKeywordSearch/{keyword}")]
+        [HttpGet("InvAIKeywordSearch/{keyword}")]
         [ProducesResponseType(typeof(JsonView), StatusCodes.Status200OK)]
         public async Task<IActionResult> InvAIKeywordSearch(string keyword)
         {
@@ -799,5 +798,151 @@ namespace OASystem.API.Controllers
                 return Ok(JsonView(true, $"搜索服务暂时不可用!"));
             }
         }
+
+        /// <summary>
+        /// 商邀AI ClientName 关键字输入提示(单字段)
+        /// </summary>
+        /// <param name="keyword">关键字</param>
+        /// <returns></returns>
+        [HttpGet("InvAIClientKeywordSearch/{keyword}")]
+        [ProducesResponseType(typeof(JsonView), StatusCodes.Status200OK)]
+        public async Task<IActionResult> InvAIClientKeywordSearch(string keyword)
+        {
+            try
+            {
+                // 验证请求参数 _invAISearchService
+                if (string.IsNullOrEmpty(keyword))
+                {
+                    return Ok(JsonView(true, $"暂无数据!"));
+                }
+
+                var invNames = await GeneralMethod.InvitationAIClientName();
+
+                var data = invNames.Select(x =>
+                {
+                    return new InvitationAIInvNameView
+                    {
+                        Name = x,
+                        SortTime = DateTime.Now
+                    };
+                }).ToList();
+
+                var searchRequest = new DynamicSearchRequest
+                {
+                    Keyword = keyword,
+                    RequireAllSingleChars = true,
+                    PageIndex = 1,
+                    PageSize = 999999,
+                    FieldWeights = new Dictionary<string, int>
+                    {
+                        { "Name", 10 },
+                    },
+                    OrderBy = "SortTime"
+                };
+
+                // 验证字段配置
+                var validation = _invAISearchService.ValidateFieldConfig(
+                    searchRequest.FieldWeights,
+                    searchRequest.ReturnFields);
+
+                if (!validation.IsValid)
+                {
+                    return Ok(JsonView(true, $"暂无数据!{validation.Message}"));
+                }
+
+                var result = _invAISearchService.SearchDataSource(searchRequest, data);
+
+                if (result.Success)
+                {
+                    var view = result.Items.Select(x => x.Data.Name).ToList();
+
+                    if (view.Count == 0)
+                    {
+                        return Ok(JsonView(true, "暂无数据"));
+                    }
+
+                    return Ok(JsonView(true, result.Message, view, view.Count));
+                }
+
+                return Ok(JsonView(true, "暂无数据"));
+            }
+            catch (Exception ex)
+            {
+                return Ok(JsonView(true, $"搜索服务暂时不可用!"));
+            }
+        }
+
+        /// <summary>
+        /// 商邀AI CountryName 关键字输入提示(单字段)
+        /// </summary>
+        /// <param name="keyword">关键字</param>
+        /// <returns></returns>
+        [HttpGet("InvAICountryKeywordSearch/{keyword}")]
+        [ProducesResponseType(typeof(JsonView), StatusCodes.Status200OK)]
+        public async Task<IActionResult> InvAICountryKeywordSearch(string keyword)
+        {
+            try
+            {
+                // 验证请求参数 _invAISearchService
+                if (string.IsNullOrEmpty(keyword))
+                {
+                    return Ok(JsonView(true, $"暂无数据!"));
+                }
+
+                var countryNames = await GeneralMethod.InvitationAICountryName();
+
+                var data = countryNames.Select(x =>
+                {
+                    return new InvitationAIInvNameView
+                    {
+                        Name = x,
+                        SortTime = DateTime.Now
+                    };
+                }).ToList();
+
+                var searchRequest = new DynamicSearchRequest
+                {
+                    Keyword = keyword,
+                    RequireAllSingleChars = true,
+                    PageIndex = 1,
+                    PageSize = 999999,
+                    FieldWeights = new Dictionary<string, int>
+                    {
+                        { "Name", 10 },
+                    },
+                    OrderBy = "SortTime"
+                };
+
+                // 验证字段配置
+                var validation = _invAISearchService.ValidateFieldConfig(
+                    searchRequest.FieldWeights,
+                    searchRequest.ReturnFields);
+
+                if (!validation.IsValid)
+                {
+                    return Ok(JsonView(true, $"暂无数据!{validation.Message}"));
+                }
+
+                var result = _invAISearchService.SearchDataSource(searchRequest, data);
+
+                if (result.Success)
+                {
+                    var view = result.Items.Select(x => x.Data.Name).ToList();
+
+                    if (view.Count == 0)
+                    {
+                        return Ok(JsonView(true, "暂无数据"));
+                    }
+
+                    return Ok(JsonView(true, result.Message, view, view.Count));
+                }
+
+                return Ok(JsonView(true, "暂无数据"));
+            }
+            catch (Exception ex)
+            {
+                return Ok(JsonView(true, $"搜索服务暂时不可用!"));
+            }
+        }
     }
 }

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

@@ -1360,7 +1360,51 @@ namespace OASystem.API.OAMethodLib
                     .ToListAsync();
         }
 
+        /// <summary>
+        /// 商邀AI ClientName 处理
+        /// </summary>
+        /// <returns></returns>
+        public static async Task<List<string>> InvitationAIClientName()
+        {
+            var rawEncryptedDatas = await _sqlSugar.Queryable<Crm_NewClientData>()
+               .Where(x => x.IsDel == 0 && x.Client != null)
+               .Select(x => x.Client)
+               .ToListAsync();
+
+            // 2. 内存精炼:解密 -> 去空格 -> 排除横杠 -> 去重
+            var unitNames = rawEncryptedDatas
+                .Select(encryptedItem =>
+                {
+                    try
+                    {
+                        return AesEncryptionHelper.Decrypt(encryptedItem);
+                    }
+                    catch
+                    {
+                        return null; // 异常解密处理
+                    }
+                })
+                .Where(decrypted => !string.IsNullOrWhiteSpace(decrypted)) // 排除空或纯空格
+                .Select(decrypted => decrypted.Replace(" ", ""))           // 去除所有内部空格
+                .Where(cleaned => !cleaned.Contains("-"))                  // 此时过滤解密后的 "-" 才有效
+                .Distinct()                                                // 最终去重
+                .ToList();
+
+            return unitNames;
+        }
 
+        /// <summary>
+        /// 商邀AI CountryName 处理
+        /// </summary>
+        /// <returns></returns>
+        public static async Task<List<string>> InvitationAICountryName()
+        {
+            return await _sqlSugar.Queryable<Sys_Countries>()
+                .Where(x => x.IsDel == 0)
+                .Select(x => x.Name_CN)
+                .Distinct()
+                .ToListAsync();
+        }
 
         #endregion
 

+ 6 - 2
OASystem/OASystem.Api/appsettings.json

@@ -156,7 +156,7 @@
   "ExcelTempPath": "D:/FTP/File/OA2023/Office/Excel/Template/",
   "GrpListFileBasePath": "D:/FTP/File/OA2023/Office/GrpFile/GroupList/",
   "GrpListFileFtpPath": "Office/GrpFile/GroupList/",
-  //D:\FTP\File\OA2023\Office\GrpFile
+  // D:\FTP\File\OA2023\Office\GrpFile
   "VisaProgressImageBaseUrl": "http://132.232.92.186:24/",
   "VisaProgressImageBasePath": "D:/FTP/File/OA2023/Image/Visa/",
   "VisaProgressImageFtpPath": "Image/Visa/",
@@ -167,9 +167,13 @@
   "WageSheetExcelFptPath": "D:/FTP/File/OA2023/Office/WageSheetFile/",
   "WageSheetTaxExcelBaseUrl": "http://132.232.92.186:24/",
   "WageSheetTaxExcelFptPath": "D:/FTP/File/OA2023/Office/Excel/WageSheetTaxFile/",
-  //收款账单 - 文件路径配置
+  // 收款账单 - 文件路径配置
   "ReceivablesUploadFileBasePath": "D:/FTP/File/OA2023/Office/Word/ForeignReceivables/UploadFile",
   "ReceivablesUploadFileFtpPath": "Office/Word/ForeignReceivables/UploadFile",
+  // 商邀资料 AI 文件路径配置
+  "InvitationAIAssistBasePath": "D:/FTP/File/OA2023/Office/Word/InvitationAIAssist/",
+  "InvitationAIAssistFtpPath": "Office/Word/InvitationAIAssist/",
+
   "CTableCorrelationPageDatas": [
     {
       "CTableId": 76, //CtableId 酒店预订

+ 24 - 4
OASystem/OASystem.Domain/Dtos/Resource/InvitationAI.cs

@@ -7,24 +7,44 @@
     /// <summary>
     /// 商邀资料AI 混元AI查询资料 Dto
     /// </summary>
-    public class InvitationAISearchDto
+    public class InvitationAISetPromptDto
     {
         public int GroupId { get; set; }
 
         /// <summary>
         /// 出访单位
         /// </summary>
-        public string UnitName { get; set; }
+        public string OriginUnit { get; set; }
         /// <summary>
         /// 出访国家
         /// </summary>
-        public string Country { get; set; }
+        public string TargetCountry { get; set; }
         /// <summary>
         /// 备注信息
         /// </summary>
-        public string Remark { get; set; }
+        public string OtherConstraints { get; set; }
+
+        public int CurrUserId { get; set; }
+    }
+
+    public class InvitationAISearchDto
+    {
+        public int Id { get; set; }
 
         public int CurrUserId { get; set; }
     }
 
+    public class InvitationAICompleteTextDto : InvitationAISearchDto
+    { }
+
+
+
+    public class InvitationAISingleDelDto
+        {
+        public int Id { get; set; }
+
+        public string Guid { get; set; }
+
+    }
+
 }

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

@@ -35,7 +35,7 @@ namespace OASystem.Domain.Entities.Resource
         /// 词条信息
         /// </summary>
         [SugarColumn(ColumnName = "Entries", ColumnDescription = "词条信息", IsJson = true, IsNullable = true, ColumnDataType = "varchar(max)")]
-        public List<EntryInfo> Entries { get; set; } = new List<EntryInfo>();
+        public EntryInfo Entries { get; set; } = new EntryInfo();
 
     }
 
@@ -44,6 +44,11 @@ namespace OASystem.Domain.Entities.Resource
     /// </summary>
     public class InvitationAIInfo
     {
+        /// <summary>
+        /// Guid
+        /// </summary>
+        public string  Guid { get; set; }
+
         /// <summary>
         /// 数据来源
         /// 0-本地数据;1-第三方AI接口数据
@@ -120,9 +125,25 @@ namespace OASystem.Domain.Entities.Resource
     public class EntryInfo
     {
         /// <summary>
-        /// 词条信息
+        /// 出访单位
+        /// </summary>
+        public string OriginUnit { get; set; }
+        /// <summary>
+        /// 拜访国家
+        /// </summary>
+        public string TargetCountry { get; set; }
+        /// <summary>
+        /// 出访目的
+        /// </summary>
+        public string Objective { get; set; }
+        /// <summary>
+        /// 数据条数
+        /// </summary>
+        public int NeedCount { get; set; } = 20;
+        /// <summary>
+        /// 其他规则
         /// </summary>
-        public string Details { get; set; }
+        public string OtherConstraints { get; set; }
         /// <summary>
         /// 操作时间
         /// </summary>