Преглед изворни кода

商邀AI无团组接口重构与参数校验优化

重构了商邀资料AI-无团组相关接口,新增公务资料新增接口,SSE流式推送AI查询逻辑独立。参数校验更严格,接口参数结构调整,提升前后端对接体验。数据来源表调整,注释与提示信息优化。
Lyyyi пре 1 месец
родитељ
комит
1da94c9d8f

+ 211 - 187
OASystem/OASystem.Api/Controllers/ResourceController.cs

@@ -4908,7 +4908,7 @@ 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> InvitationAI_NoGroupInit()
         {
-            var itemNames = await InvitationAIInvName();
+            var itemNames = await InvitationAI_NoGroupInvName();
             var unitNames = await InvitationAIClientName();
             var countries = await InvitationAICountryName();
             var industryNodes = IndustryTree.Build().Select(x => x.NameCn).ToList();
@@ -5001,156 +5001,56 @@ Inner Join Sys_Department as d With(Nolock) On u.DepId=d.Id Where m.Id={0} ", _m
         }
 
         /// <summary>
-        /// 商邀资料AI-无团组版 混元AI查询资料(SSE流式推送)
+        /// 商邀资料AI-无团组版 新增公务资料
         /// </summary>
+        /// <returns></returns>
         [HttpPost]
-        public async Task InvitationAI_NoGroupSearchStreamProgress([FromBody] InvitationAI_NoGroupSearchDto dto)
+        [ProducesResponseType(typeof(JsonView), StatusCodes.Status200OK)]
+        public async Task<IActionResult> InvitationAI_NoGroupAdd(InvitationAI_NoGroupAddDto dto)
         {
-            HttpContext.InitializeSse();
-
-            try
-            {
-                await HttpContext.SendSseStepAsync(5, "正在加载配置...");
+            // 基础校验
+            if (string.IsNullOrWhiteSpace(dto.InvName))
+                return Ok(JsonView(false, "请传入有效的公务名称!"));
 
-                #region 1. 异步并行化验证 (Performance Boost)
-                var invAiInfo = await _sqlSugar.Queryable<Res_InvitationAI_NoGroup>().Where(x => x.IsDel == 0 && x.Id == dto.Id).FirstAsync();
-                var operatorName = await _sqlSugar.Queryable<Sys_Users>()
-                    .Where(x => x.IsDel == 0 && x.Id == dto.CurrUserId)
-                    .Select(x => x.CnName).FirstAsync();
+            if (dto.CurrUserId <= 0)
+                return Ok(JsonView(false, "用户身份无效"));
 
-                if (invAiInfo?.EntryInfo == null)
-                {
-                    await HttpContext.SendSseStepAsync(-1, "未找到有效的关键字配置。");
-                    return;
-                }
+            var invName = dto.InvName;
 
-                var entryInfo = invAiInfo.EntryInfo;
-                var targetCountrySet = new HashSet<string>(entryInfo.TargetCountry);
-                #endregion
+            #region 数据库操作
 
-                await HttpContext.SendSseStepAsync(20, "正在解析本地典籍 (并行解密)...");
+            // 数据库信息获取方式
+            var invExist = await _sqlSugar.Queryable<Res_InvitationAI_NoGroup>().AnyAsync(x => x.IsDel == 0 && x.InvName == invName);
+            if (invExist)
+                return Ok(JsonView(false, $"公务名称 [{invName}] 已存在,请更换"));
 
-                #region 2. 内存计算优化
-                // 仅查询必要字段,减少 DataReader 压力
-                var rawLocalDatas = await _sqlSugar.Queryable<Res_InvitationOfficialActivityData>()
-                    .Where(x => x.IsDel == 0 && !string.IsNullOrEmpty(x.UnitName))
-                    .Select(x => new { x.Country, x.UnitName, x.Address, x.Field, x.Contact, x.Tel, x.Email })
-                    .ToListAsync();
+            string operatorName = await _sqlSugar.Queryable<Sys_Users>().Where(x => x.IsDel == 0 && x.Id == dto.CurrUserId).Select(x => x.CnName).FirstAsync() ?? "-";
 
-                // PLINQ 核心炼金:利用多核并行解密
-                var allDecrypted = rawLocalDatas.AsParallel().Select(item => new InvitationAI_NoGroupInfo
+            var dataInfo = new Res_InvitationAI_NoGroup()
+            {
+                InvName = invName,
+                EntryInfo = new Entry_NoGroupInfo()
                 {
-                    Guid = Guid.NewGuid().ToString("N"),
-                    Source = 0, // 标识来源:本地
-                    Region = AesEncryptionHelper.Decrypt(item.Country),
-                    NameCn = AesEncryptionHelper.Decrypt(item.UnitName),
-                    Address = AesEncryptionHelper.Decrypt(item.Address),
-                    Scope = AesEncryptionHelper.Decrypt(item.Field),
-                    Contact = AesEncryptionHelper.Decrypt(item.Contact),
-                    Phone = AesEncryptionHelper.Decrypt(item.Tel),
-                    Email = AesEncryptionHelper.Decrypt(item.Email),
-                    OperatedAt = DateTime.Now,
                     Operator = operatorName,
-                }).ToList();
-
-                // 筛选符合国家的本地数据
-                var matchedCountries = allDecrypted.Where(x => targetCountrySet.Contains(x.Region)).ToList();
-
-                #endregion
-
-                #region 3. 混元 AI 协同炼金 (双阶段)
-
-                // --- 阶段 A: 行业匹配分析 ---
-                if (matchedCountries.Any())
-                {
-                    await HttpContext.SendSseStepAsync(40, $"AI 正在执行 {entryInfo.TargetCountry.Count} 国的行业契合度分析...");
-                    string industryQuestion = BuildIndustryPrompt(entryInfo, matchedCountries);
-                    string industryRaw = await _hunyuanService.ChatCompletionsHunyuan_t1_latestAsync(industryQuestion);
-
-                    var industryMatches = CleanAndParseJson<List<IndustryMatchResult>>(industryRaw) ?? new();
-                    var matchedNames = new HashSet<string>(industryMatches.Select(x => x.TargetUnitName));
-                    // 重新过滤:仅保留 AI 认为匹配的本地数据
-                    matchedCountries = matchedCountries.Where(x => !string.IsNullOrEmpty(x.NameCn) && matchedNames.Contains(x.NameCn)).Distinct().ToList();
-                }
-
-                // --- 阶段 B: 差额补全 (Gap Filling) ---
-                var localInvDatas = new List<InvitationAI_NoGroupInfo>();
-                var aiTasks = new List<CountryAIPormptInfo>();
-
-                foreach (var country in entryInfo.TargetCountry)
-                {
-                    var countryData = matchedCountries.Where(x => x.Region == country).Take(entryInfo.NeedCount).ToList();
-                    localInvDatas.AddRange(countryData);
-
-                    int gap = entryInfo.NeedCount - countryData.Count;
-                    if (gap > 0) aiTasks.Add(new() { Country = country, Count = gap });
-                }
-
-                var hunyuanAIInvDatas = new List<InvitationAI_NoGroupInfo>();
-                if (aiTasks.Any())
-                {
-                    // 强制冷却(应对 QPS 限制)
-                    // 混元免费版或低阶版本通常有 1s/1次 的频率限制
-                    await Task.Delay(1000);
-                    // 任务配置计算
-                    var countryTasks = QuotaScheduler.GenerateTasks(aiTasks, entryInfo.Industries, entryInfo.ScaleTypes);
-
-                    var countryTasksGroupBy = countryTasks.GroupBy(x => x.Region).Select(g => new
-                    {
-                        Region = g.Key,
-                        Counrt = g.Count()
-                    }).ToList();
-
-                    await HttpContext.SendSseStepAsync(60, $"AI 正在跨境检索缺失的 {string.Join(", ", countryTasksGroupBy.Select(x => $"{x.Region}({x.Counrt}条)"))} 单位资料...");
-                    string searchQuestion = BuildHunyuanPrompt(aiTasks, countryTasks, entryInfo);
-
-                    _logger.LogInformation(@"公务名称:{InvName};  混元AI查询提示词:{searchQuestion}", invAiInfo.InvName, searchQuestion);
-                    string searchRaw = await _hunyuanService.ChatCompletionsHunyuan_t1_latestAsync(searchQuestion);
-
-                    var aiParsed = CleanAndParseJson<List<InvitationAI_NoGroupInfo>>(searchRaw);
-                    if (aiParsed != null)
-                    {
-                        hunyuanAIInvDatas = aiParsed.Select(x => {
-                            x.Guid = Guid.NewGuid().ToString("N");
-                            x.Source = 1;
-                            x.Operator = operatorName;
-                            x.OperatedAt = DateTime.Now;
-                            return x;
-                        }).ToList();
-                    }
-                }
-                #endregion
-
-                await HttpContext.SendSseStepAsync(90, "正在同步成果至持久化仓库...");
-
-                #region 4. 数据融合与更新
-                var finalResult = localInvDatas.Concat(hunyuanAIInvDatas).ToList();
-
-                // 仅更新指定列,性能更优
-                invAiInfo.AiCrawledDetails = finalResult.OrderByDescending(x => x.OperatedAt).ToList();
-                bool isOk = await _sqlSugar.Updateable(invAiInfo)
-                    .UpdateColumns(x => x.AiCrawledDetails)
-                    .ExecuteCommandHasChangeAsync();
-
-                if (!isOk) await HttpContext.SendSseStepAsync(-1, $"数据持久化失败。");
-                #endregion
+                    OperatedAt = DateTime.Now
+                },
+                CreateUserId = dto.CurrUserId
+            };
 
-                // 5. 终焉推送
-                await HttpContext.SendSseStepAsync(100, "操作成功!资料已全部就绪。", new
-                {
-                    invAiInfo.Id,
-                    AiCrawledDetails = finalResult.OrderByDescending(x => x.Source).ThenBy(x => x.Region).ToList()
-                });
-            }
-            catch (Exception ex)
+            var insert = await _sqlSugar.Insertable(dataInfo).ExecuteReturnIdentityAsync();
+            if (insert < 1)
             {
-                _logger.LogError(ex, "SSE 管道熔断");
-                await HttpContext.SendSseStepAsync(-1, $"SSE 错误:{ex.InnerException.Message}({ex.Message})");
+                return Ok(JsonView(false, $"词条信息新增失败!"));
             }
-            finally
+
+            #endregion
+
+            return Ok(JsonView(true, $"新增成功!", new
             {
-                await HttpContext.FinalizeSseAsync();
-            }
+                id = insert,
+                invName = dataInfo.InvName,
+                entry = dataInfo.EntryInfo,
+            }));
         }
 
         /// <summary>
@@ -5161,29 +5061,25 @@ 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> InvitationAI_NoGroupSetPrompt(InvitationAI_NoGroupSetPromptDto dto)
         {
-            // 基础校验
-            if (string.IsNullOrWhiteSpace(dto.InvName) || string.IsNullOrWhiteSpace(dto.Objective) || string.IsNullOrWhiteSpace(dto.OriginUnit) ||
-                dto.TargetCountry == null || dto.TargetCountry.Count == 0 || dto.Industries == null || dto.Industries.Count == 0 ||
-                dto.ScaleTypes == null || dto.ScaleTypes.Count == 0
-                )
-                return Ok(JsonView(false, "请传入有效的公务名称、单位名称、国家、出访目的、行业和规模类型!"));
-
-            var invName = dto.InvName;
-
-            #region 数据库操作
+            if (dto.Id < 1) return Ok(JsonView(false, "当前公务ID无效"));
+            if (string.IsNullOrWhiteSpace(dto.Objective)) return Ok(JsonView(false, "请输入出访目的"));
+            if (string.IsNullOrWhiteSpace(dto.OriginUnit)) return Ok(JsonView(false, "请输入单位名称"));
+            if (dto.TargetCountry == null || !dto.TargetCountry.Any()) return Ok(JsonView(false, "请选择目标国家"));
+            if (dto.Industries == null || !dto.Industries.Any()) return Ok(JsonView(false, "请选择所属行业"));
+            if (dto.ScaleTypes == null || !dto.ScaleTypes.Any()) return Ok(JsonView(false, "请选择规模类型"));
+            if (string.IsNullOrWhiteSpace(dto.VisitDate)) return Ok(JsonView(false, "请输入出访时间"));
 
             // 数据库信息获取方式
-            var dataInfo = await _sqlSugar.Queryable<Res_InvitationAI_NoGroup>().Where(x => x.IsDel == 0 && x.InvName.Equals(invName)).FirstAsync();
-
-            #region 词条信息
+            var dataInfo = await _sqlSugar.Queryable<Res_InvitationAI_NoGroup>().Where(x => x.IsDel == 0 && x.Id == dto.Id).FirstAsync();
+            if (dataInfo == null)
+                return Ok(JsonView(false, "请先新增公务信息"));
 
             string operatorName = await _sqlSugar.Queryable<Sys_Users>().Where(x => x.IsDel == 0 && x.Id == dto.CurrUserId).Select(x => x.CnName).FirstAsync() ?? "-";
-
-            var entryInfo = new Entry_NoGroupInfo()
+            dataInfo.EntryInfo = new Entry_NoGroupInfo()
             {
-                OriginUnit = dto.OriginUnit,
+                OriginUnit = dto.OriginUnit?.Trim(),
                 TargetCountry = dto.TargetCountry,
-                Objective = dto.Objective,
+                Objective = dto.Objective?.Trim(),
                 Industries = dto.Industries,
                 ScaleTypes = dto.ScaleTypes,
                 IsBackground = dto.IsBackground,
@@ -5191,53 +5087,28 @@ Inner Join Sys_Department as d With(Nolock) On u.DepId=d.Id Where m.Id={0} ", _m
                 Operator = operatorName,
                 OperatedAt = DateTime.Now
             };
-            #endregion
-
-            if (dataInfo == null)
-            {
-                dataInfo = new Res_InvitationAI_NoGroup()
-                {
-                    InvName = invName,
-                    EntryInfo = entryInfo,
-                    CreateUserId = dto.CurrUserId
-                };
 
-                var insert = await _sqlSugar.Insertable(dataInfo).ExecuteReturnIdentityAsync();
-                if (insert < 1)
-                {
-                    return Ok(JsonView(false, $"词条信息新增失败!"));
-                }
-                dataInfo.Id = insert;
-            }
-            else
+            var updateResult = await _sqlSugar.Updateable(dataInfo)
+                .UpdateColumns(x => new { x.EntryInfo })
+                .ExecuteCommandHasChangeAsync();
+            if (!updateResult)
             {
-                dataInfo.EntryInfo = 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
             {
-                dataInfo.Id,
-                dataInfo.InvName,
-                Entry = dataInfo.EntryInfo,
+                id = dataInfo.Id,
+                invName = dataInfo.InvName,
+                entry = dataInfo.EntryInfo
             }));
         }
 
         /// <summary>
-        /// 商邀资料AI-无团组版 设置复选框选中 批量
+        /// 商邀资料AI-无团组版 设置复选框选中 批量(支持传入空数组进行重置)
         /// </summary>
         /// <param name="dto"></param>
         /// <returns></returns>
-        /// <summary>
-        /// 商邀资料AI-无团组版 设置复选框选中 批量 (支持传入空数组进行重置)
-        /// </summary>
         [HttpPost]
         public async Task<IActionResult> InvitationAI_NoGroupSetChecked([FromBody] InvitationAI_NoGroupSetCheckedDto dto)
         {
@@ -5452,6 +5323,159 @@ Inner Join Sys_Department as d With(Nolock) On u.DepId=d.Id Where m.Id={0} ", _m
                 : JsonView(false, $"操作失败:{result.ErrorMessage}"));
         }
 
+        /// <summary>
+        /// 商邀资料AI-无团组版 混元AI查询资料(SSE流式推送)
+        /// </summary>
+        [HttpPost]
+        public async Task InvitationAI_NoGroupSearchStreamProgress([FromBody] InvitationAI_NoGroupSearchDto dto)
+        {
+            HttpContext.InitializeSse();
+
+            try
+            {
+                await HttpContext.SendSseStepAsync(5, "正在加载配置...");
+
+                #region 1. 异步并行化验证 (Performance Boost)
+                var invAiInfo = await _sqlSugar.Queryable<Res_InvitationAI_NoGroup>().Where(x => x.IsDel == 0 && x.Id == dto.Id).FirstAsync();
+                var operatorName = await _sqlSugar.Queryable<Sys_Users>()
+                    .Where(x => x.IsDel == 0 && x.Id == dto.CurrUserId)
+                    .Select(x => x.CnName).FirstAsync();
+
+                if (invAiInfo?.EntryInfo == null)
+                {
+                    await HttpContext.SendSseStepAsync(-1, "未找到有效的关键字配置。");
+                    return;
+                }
+
+                var entryInfo = invAiInfo.EntryInfo;
+                var targetCountrySet = new HashSet<string>(entryInfo.TargetCountry);
+                #endregion
+
+                await HttpContext.SendSseStepAsync(20, "正在解析本地典籍 (并行解密)...");
+
+                #region 2. 内存计算优化
+                // 仅查询必要字段,减少 DataReader 压力
+                var rawLocalDatas = await _sqlSugar.Queryable<Res_InvitationOfficialActivityData>()
+                    .Where(x => x.IsDel == 0 && !string.IsNullOrEmpty(x.UnitName))
+                    .Select(x => new { x.Country, x.UnitName, x.Address, x.Field, x.Contact, x.Tel, x.Email })
+                    .ToListAsync();
+
+                // PLINQ 核心炼金:利用多核并行解密
+                var allDecrypted = rawLocalDatas.AsParallel().Select(item => new InvitationAI_NoGroupInfo
+                {
+                    Guid = Guid.NewGuid().ToString("N"),
+                    Source = 0, // 标识来源:本地
+                    Region = AesEncryptionHelper.Decrypt(item.Country),
+                    NameCn = AesEncryptionHelper.Decrypt(item.UnitName),
+                    Address = AesEncryptionHelper.Decrypt(item.Address),
+                    Scope = AesEncryptionHelper.Decrypt(item.Field),
+                    Contact = AesEncryptionHelper.Decrypt(item.Contact),
+                    Phone = AesEncryptionHelper.Decrypt(item.Tel),
+                    Email = AesEncryptionHelper.Decrypt(item.Email),
+                    OperatedAt = DateTime.Now,
+                    Operator = operatorName,
+                }).ToList();
+
+                // 筛选符合国家的本地数据
+                var matchedCountries = allDecrypted.Where(x => targetCountrySet.Contains(x.Region)).ToList();
+
+                #endregion
+
+                #region 3. 混元 AI 协同炼金 (双阶段)
+
+                // --- 阶段 A: 行业匹配分析 ---
+                if (matchedCountries.Any())
+                {
+                    await HttpContext.SendSseStepAsync(40, $"AI 正在执行 {entryInfo.TargetCountry.Count} 国的行业契合度分析...");
+                    string industryQuestion = BuildIndustryPrompt(entryInfo, matchedCountries);
+                    string industryRaw = await _hunyuanService.ChatCompletionsHunyuan_t1_latestAsync(industryQuestion);
+
+                    var industryMatches = CleanAndParseJson<List<IndustryMatchResult>>(industryRaw) ?? new();
+                    var matchedNames = new HashSet<string>(industryMatches.Select(x => x.TargetUnitName));
+                    // 重新过滤:仅保留 AI 认为匹配的本地数据
+                    matchedCountries = matchedCountries.Where(x => !string.IsNullOrEmpty(x.NameCn) && matchedNames.Contains(x.NameCn)).Distinct().ToList();
+                }
+
+                // --- 阶段 B: 差额补全 (Gap Filling) ---
+                var localInvDatas = new List<InvitationAI_NoGroupInfo>();
+                var aiTasks = new List<CountryAIPormptInfo>();
+
+                foreach (var country in entryInfo.TargetCountry)
+                {
+                    var countryData = matchedCountries.Where(x => x.Region == country).Take(entryInfo.NeedCount).ToList();
+                    localInvDatas.AddRange(countryData);
+
+                    int gap = entryInfo.NeedCount - countryData.Count;
+                    if (gap > 0) aiTasks.Add(new() { Country = country, Count = gap });
+                }
+
+                var hunyuanAIInvDatas = new List<InvitationAI_NoGroupInfo>();
+                if (aiTasks.Any())
+                {
+                    // 强制冷却(应对 QPS 限制)
+                    // 混元免费版或低阶版本通常有 1s/1次 的频率限制
+                    await Task.Delay(1000);
+                    // 任务配置计算
+                    var countryTasks = QuotaScheduler.GenerateTasks(aiTasks, entryInfo.Industries, entryInfo.ScaleTypes);
+
+                    var countryTasksGroupBy = countryTasks.GroupBy(x => x.Region).Select(g => new
+                    {
+                        Region = g.Key,
+                        Counrt = g.Count()
+                    }).ToList();
+
+                    await HttpContext.SendSseStepAsync(60, $"AI 正在跨境检索缺失的 {string.Join(", ", countryTasksGroupBy.Select(x => $"{x.Region}({x.Counrt}条)"))} 单位资料...");
+                    string searchQuestion = BuildHunyuanPrompt(aiTasks, countryTasks, entryInfo);
+
+                    _logger.LogInformation(@"公务名称:{InvName};  混元AI查询提示词:{searchQuestion}", invAiInfo.InvName, searchQuestion);
+                    string searchRaw = await _hunyuanService.ChatCompletionsHunyuan_t1_latestAsync(searchQuestion);
+
+                    var aiParsed = CleanAndParseJson<List<InvitationAI_NoGroupInfo>>(searchRaw);
+                    if (aiParsed != null)
+                    {
+                        hunyuanAIInvDatas = aiParsed.Select(x => {
+                            x.Guid = Guid.NewGuid().ToString("N");
+                            x.Source = 1;
+                            x.Operator = operatorName;
+                            x.OperatedAt = DateTime.Now;
+                            return x;
+                        }).ToList();
+                    }
+                }
+                #endregion
+
+                await HttpContext.SendSseStepAsync(90, "正在同步成果至持久化仓库...");
+
+                #region 4. 数据融合与更新
+                var finalResult = localInvDatas.Concat(hunyuanAIInvDatas).ToList();
+
+                // 仅更新指定列,性能更优
+                invAiInfo.AiCrawledDetails = finalResult.OrderByDescending(x => x.OperatedAt).ToList();
+                bool isOk = await _sqlSugar.Updateable(invAiInfo)
+                    .UpdateColumns(x => x.AiCrawledDetails)
+                    .ExecuteCommandHasChangeAsync();
+
+                if (!isOk) await HttpContext.SendSseStepAsync(-1, $"数据持久化失败。");
+                #endregion
+
+                // 5. 终焉推送
+                await HttpContext.SendSseStepAsync(100, "操作成功!资料已全部就绪。", new
+                {
+                    invAiInfo.Id,
+                    AiCrawledDetails = finalResult.OrderByDescending(x => x.Source).ThenBy(x => x.Region).ToList()
+                });
+            }
+            catch (Exception ex)
+            {
+                _logger.LogError(ex, "SSE 管道熔断");
+                await HttpContext.SendSseStepAsync(-1, $"SSE 错误:{ex.InnerException.Message}({ex.Message})");
+            }
+            finally
+            {
+                await HttpContext.FinalizeSseAsync();
+            }
+        }
+
         /// <summary>
         ///  商邀资料AI-无团组版  混元AI续写(SSE流式推送)
         /// </summary>

+ 2 - 3
OASystem/OASystem.Api/OAMethodLib/GeneralMethod.cs

@@ -1373,8 +1373,7 @@ namespace OASystem.API.OAMethodLib
         /// <returns></returns>
         public static async Task<List<InvitationAIInvNameView>> InvitationAI_NoGroupInvName()
         {
-            // 将去重后的结果包装,再进行全局
-            return await _sqlSugar.Queryable<Res_InvitationAI>()
+            return await _sqlSugar.Queryable<Res_InvitationAI_NoGroup>()
                     .Where(x => x.IsDel == 0 && !string.IsNullOrEmpty(x.InvName))
                     .Select(x => new InvitationAIInvNameView { Id = x.Id, Name = x.InvName, Source = 2, SortTime = x.CreateTime })
                     .OrderByDescending(it => it.Source)
@@ -1393,7 +1392,7 @@ namespace OASystem.API.OAMethodLib
                .Select(x => x.Client)
                .ToListAsync();
 
-            // 2. 内存精炼:解密 -> 去空格 -> 排除横杠 -> 去重
+            // 解密 -> 去空格 -> 排除横杠 -> 去重
             var unitNames = rawEncryptedDatas
                 .Select(encryptedItem =>
                 {

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

@@ -155,12 +155,58 @@ namespace OASystem.Domain.Dtos.Resource
     {
     }
 
-    public class InvitationAI_NoGroupSetPromptDto : InvitationAISetPromptDto
+    public class InvitationAI_NoGroupAddDto
     {
+        public string InvName { get; set; }
+
+        public int CurrUserId { get; set; }
+    }
+
+    public class InvitationAI_NoGroupSetPromptDto 
+    {
+        public int Id { get; set; }
+
+        /// <summary>
+        /// 出访单位
+        /// </summary>
+        public string OriginUnit { get; set; }
+        /// <summary>
+        /// 出访国家
+        /// </summary>
+        public List<string> TargetCountry { get; set; }
+
         /// <summary>
         /// 出访时间(eg:2026-04-16)
         /// </summary>
         public string VisitDate { get; set; }
+
+        /// <summary>
+        /// 出访目的
+        /// </summary>
+        public string Objective { get; set; }
+
+        /// <summary>
+        /// 行业信息
+        /// 信息技术、金融与财会、工业制造、医疗保健、政府与公共服务、消费与贸易
+        /// </summary>
+        public List<string> Industries { get; set; } = new List<string>();
+
+        /// <summary>
+        /// 规模类型
+        /// </summary>
+        public List<string> ScaleTypes { get; set; } = new List<string>();
+
+        /// <summary>
+        /// 是否需要华人单位背景
+        /// </summary>
+        public bool IsBackground { get; set; } = false;
+
+        /// <summary>
+        /// 备注信息
+        /// </summary>
+        public string OtherConstraints { get; set; }
+
+        public int CurrUserId { get; set; }
     }
 
     public class InvitationAI_NoGroupSetCheckedDto : InvitationAISetCheckedDto