feat: extract global AI service and persist sales-product publish AI reports
Move provider/chat APIs from /xhs/ai to /ai, rename t_ai_provider, and require adopted AI analysis on price publish. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
d30375ba49
commit
592054f062
9
db.sql
9
db.sql
@ -29,6 +29,7 @@ DROP TABLE IF EXISTS `role_data_scope`;
|
||||
DROP TABLE IF EXISTS `role_org_perm`;
|
||||
DROP TABLE IF EXISTS `user_org_perm`;
|
||||
DROP TABLE IF EXISTS `t_xhs_media`;
|
||||
DROP TABLE IF EXISTS `t_ai_provider`;
|
||||
DROP TABLE IF EXISTS `t_xhs_ai_provider`;
|
||||
DROP TABLE IF EXISTS `t_xhs_human_config`;
|
||||
DROP TABLE IF EXISTS `t_xhs_skill`;
|
||||
@ -1733,7 +1734,7 @@ CREATE TABLE `t_xhs_human_config` (
|
||||
UNIQUE KEY `uk_xhs_human_customer` (`CustomerId`)
|
||||
) COMMENT='小红书拟人行为与防封配置表';
|
||||
|
||||
CREATE TABLE `t_xhs_ai_provider` (
|
||||
CREATE TABLE `t_ai_provider` (
|
||||
`Id` INT NOT NULL AUTO_INCREMENT COMMENT '主键',
|
||||
`CustomerId` INT NOT NULL COMMENT '客户ID;0 表示全局',
|
||||
`ProviderKey` VARCHAR(32) NOT NULL COMMENT 'ai_chatgpt / ai_zhipu 等',
|
||||
@ -1751,9 +1752,9 @@ CREATE TABLE `t_xhs_ai_provider` (
|
||||
`UpdateUserId` INT NULL,
|
||||
`UpdateTime` DATETIME NULL,
|
||||
PRIMARY KEY (`Id`),
|
||||
UNIQUE KEY `uk_xhs_ai_provider` (`CustomerId`, `ProviderKey`),
|
||||
KEY `idx_xhs_ai_customer` (`CustomerId`, `Enabled`)
|
||||
) COMMENT='小红书 AI 提供商配置表';
|
||||
UNIQUE KEY `uk_ai_provider` (`CustomerId`, `ProviderKey`),
|
||||
KEY `idx_ai_customer` (`CustomerId`, `Enabled`)
|
||||
) COMMENT='全局 AI 提供商配置表(CustomerId=0 表示平台级)';
|
||||
|
||||
CREATE TABLE `t_xhs_media` (
|
||||
`Id` INT NOT NULL AUTO_INCREMENT COMMENT '主键',
|
||||
|
||||
16
sql/20260801_global_ai_provider.sql
Normal file
16
sql/20260801_global_ai_provider.sql
Normal file
@ -0,0 +1,16 @@
|
||||
-- 全局 AI 提供商:从小红书域表迁出(未上线环境可直接改名)
|
||||
-- 若表尚不存在则跳过;若已是 t_ai_provider 则无需执行。
|
||||
|
||||
RENAME TABLE `t_xhs_ai_provider` TO `t_ai_provider`;
|
||||
|
||||
ALTER TABLE `t_ai_provider`
|
||||
COMMENT = '全局 AI 提供商配置表(CustomerId=0 表示平台级)';
|
||||
|
||||
-- 索引名顺带规范化(可选,失败可忽略)
|
||||
ALTER TABLE `t_ai_provider`
|
||||
DROP INDEX `uk_xhs_ai_provider`,
|
||||
ADD UNIQUE KEY `uk_ai_provider` (`CustomerId`, `ProviderKey`);
|
||||
|
||||
ALTER TABLE `t_ai_provider`
|
||||
DROP INDEX `idx_xhs_ai_customer`,
|
||||
ADD KEY `idx_ai_customer` (`CustomerId`, `Enabled`);
|
||||
3
sql/20260801_sales_product_publish_ai_report.sql
Normal file
3
sql/20260801_sales_product_publish_ai_report.sql
Normal file
@ -0,0 +1,3 @@
|
||||
-- 销售产品价格发布记录:保存 AI 风险分析报告
|
||||
ALTER TABLE `t_sales_product_publish_record`
|
||||
ADD COLUMN `AiAnalysisReport` MEDIUMTEXT NULL COMMENT '价格发布AI风险分析报告' AFTER `Description`;
|
||||
72
src/Oms.Application/Dtos/Ai/AiDtos.cs
Normal file
72
src/Oms.Application/Dtos/Ai/AiDtos.cs
Normal file
@ -0,0 +1,72 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Oms.Application.Dtos.Ai;
|
||||
|
||||
public class AiProviderOutput
|
||||
{
|
||||
public int Id { get; set; }
|
||||
|
||||
public string ProviderKey { get; set; } = string.Empty;
|
||||
|
||||
public string ProviderType { get; set; } = string.Empty;
|
||||
|
||||
public string DisplayName { get; set; } = string.Empty;
|
||||
|
||||
public string BaseUrl { get; set; } = string.Empty;
|
||||
|
||||
public string? ApiKey { get; set; }
|
||||
|
||||
public string? ApiSecret { get; set; }
|
||||
|
||||
public bool IsDefault { get; set; }
|
||||
|
||||
public bool Enabled { get; set; }
|
||||
}
|
||||
|
||||
public class AiProviderSaveInput
|
||||
{
|
||||
[Required]
|
||||
public string ProviderKey { get; set; } = string.Empty;
|
||||
|
||||
[Required]
|
||||
public string ProviderType { get; set; } = string.Empty;
|
||||
|
||||
[Required]
|
||||
public string DisplayName { get; set; } = string.Empty;
|
||||
|
||||
[Required]
|
||||
public string BaseUrl { get; set; } = string.Empty;
|
||||
|
||||
public string? ApiKey { get; set; }
|
||||
|
||||
public string? ApiSecret { get; set; }
|
||||
|
||||
public bool IsDefault { get; set; }
|
||||
|
||||
public bool Enabled { get; set; } = true;
|
||||
}
|
||||
|
||||
public class AiChatInput
|
||||
{
|
||||
[Required]
|
||||
public string ProviderKey { get; set; } = string.Empty;
|
||||
|
||||
public string? Model { get; set; }
|
||||
|
||||
[Required]
|
||||
public List<AiChatMessageInput> Messages { get; set; } = [];
|
||||
|
||||
public string Scene { get; set; } = "assistant";
|
||||
}
|
||||
|
||||
public class AiChatMessageInput
|
||||
{
|
||||
public string Role { get; set; } = "user";
|
||||
|
||||
public string Content { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
public class AiChatOutput
|
||||
{
|
||||
public string Content { get; set; } = string.Empty;
|
||||
}
|
||||
@ -15,6 +15,11 @@ public class SalesProductPriceSaveAllInput
|
||||
/// </summary>
|
||||
public bool Publish { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 价格发布 AI 风险分析报告(Publish=true 时必填)。
|
||||
/// </summary>
|
||||
public string? AiAnalysisReport { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 价格项集合。
|
||||
/// </summary>
|
||||
|
||||
@ -19,4 +19,9 @@ public class SalesProductPublishInput
|
||||
/// 描述。
|
||||
/// </summary>
|
||||
public string? Description { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 价格发布 AI 风险分析报告(operatorType=price 时必填)。
|
||||
/// </summary>
|
||||
public string? AiAnalysisReport { get; set; }
|
||||
}
|
||||
|
||||
@ -45,6 +45,11 @@ public class SalesProductPublishRecordOutput
|
||||
/// </summary>
|
||||
public string? Description { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 价格发布 AI 风险分析报告。
|
||||
/// </summary>
|
||||
public string? AiAnalysisReport { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 内容。
|
||||
/// </summary>
|
||||
|
||||
@ -320,75 +320,6 @@ public class XhsSkillSaveInput
|
||||
public object? Recipe { get; set; }
|
||||
}
|
||||
|
||||
public class XhsAiProviderOutput
|
||||
{
|
||||
public int Id { get; set; }
|
||||
|
||||
public string ProviderKey { get; set; } = string.Empty;
|
||||
|
||||
public string ProviderType { get; set; } = string.Empty;
|
||||
|
||||
public string DisplayName { get; set; } = string.Empty;
|
||||
|
||||
public string BaseUrl { get; set; } = string.Empty;
|
||||
|
||||
public string? ApiKey { get; set; }
|
||||
|
||||
public string? ApiSecret { get; set; }
|
||||
|
||||
public bool IsDefault { get; set; }
|
||||
|
||||
public bool Enabled { get; set; }
|
||||
}
|
||||
|
||||
public class XhsAiProviderSaveInput
|
||||
{
|
||||
[Required]
|
||||
public string ProviderKey { get; set; } = string.Empty;
|
||||
|
||||
[Required]
|
||||
public string ProviderType { get; set; } = string.Empty;
|
||||
|
||||
[Required]
|
||||
public string DisplayName { get; set; } = string.Empty;
|
||||
|
||||
[Required]
|
||||
public string BaseUrl { get; set; } = string.Empty;
|
||||
|
||||
public string? ApiKey { get; set; }
|
||||
|
||||
public string? ApiSecret { get; set; }
|
||||
|
||||
public bool IsDefault { get; set; }
|
||||
|
||||
public bool Enabled { get; set; } = true;
|
||||
}
|
||||
|
||||
public class XhsAiChatInput
|
||||
{
|
||||
[Required]
|
||||
public string ProviderKey { get; set; } = string.Empty;
|
||||
|
||||
public string? Model { get; set; }
|
||||
|
||||
[Required]
|
||||
public List<XhsAiChatMessageInput> Messages { get; set; } = [];
|
||||
|
||||
public string Scene { get; set; } = "assistant";
|
||||
}
|
||||
|
||||
public class XhsAiChatMessageInput
|
||||
{
|
||||
public string Role { get; set; } = "user";
|
||||
|
||||
public string Content { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
public class XhsAiChatOutput
|
||||
{
|
||||
public string Content { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
public class XhsAnalysisStartInput
|
||||
{
|
||||
[Required]
|
||||
|
||||
@ -55,7 +55,7 @@ public class OmsApplicationModule : FakeModule
|
||||
context.Services.AddOmsItfxIntegration(configuration);
|
||||
context.Services.AddOmsPlatformShopIntegration(configuration);
|
||||
context.Services.AddOmsOrderBatchMessaging(configuration);
|
||||
context.Services.AddXhsLlmClient();
|
||||
context.Services.AddAiLlmClient();
|
||||
context.Services.AddHostedService<XhsScheduledPublishWorker>();
|
||||
context.Services.AddHostedService<MarketingBackgroundWorker>();
|
||||
}
|
||||
|
||||
@ -1,48 +1,51 @@
|
||||
using Fake.Application;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Oms.Application.Dtos.Ai;
|
||||
using Oms.Application.Dtos.Shared;
|
||||
using Oms.Application.Dtos.Xhs;
|
||||
using Oms.Application.Shared;
|
||||
using Oms.Domain.AiAggregate.Entities;
|
||||
using Oms.Domain.AiAggregate.Repositories;
|
||||
using Oms.Domain.CustomersAggregate.Repositories;
|
||||
using Oms.Domain.XhsAggregate.Entities;
|
||||
using Oms.Domain.XhsAggregate.Repositories;
|
||||
|
||||
namespace Oms.Application.Services.Xhs;
|
||||
namespace Oms.Application.Services.Ai;
|
||||
|
||||
[ApiExplorerSettings(GroupName = "OMS-Xhs")]
|
||||
public class XhsAiService : ApplicationService
|
||||
/// <summary>
|
||||
/// 全局 AI 提供商与对话服务(系统级,非业务域专属)。
|
||||
/// </summary>
|
||||
[ApiExplorerSettings(GroupName = "OMS-Ai")]
|
||||
public class AiService : ApplicationService
|
||||
{
|
||||
private const string SecretMask = "******";
|
||||
|
||||
private IXhsAiProviderRepository AiProviderRepository => LazyServiceProvider.GetRequiredService<IXhsAiProviderRepository>();
|
||||
private IAiProviderRepository AiProviderRepository => LazyServiceProvider.GetRequiredService<IAiProviderRepository>();
|
||||
private ICustomerRepository CustomerRepository => LazyServiceProvider.GetRequiredService<ICustomerRepository>();
|
||||
|
||||
/// <summary>
|
||||
/// 查询AI服务商列表
|
||||
/// 查询 AI 服务商列表。
|
||||
/// </summary>
|
||||
[HttpPost("/xhs/ai/provider/list")]
|
||||
public async Task<OmsItemsResult<XhsAiProviderOutput>> ListProvidersAsync()
|
||||
[HttpPost("/ai/provider/list")]
|
||||
public async Task<OmsItemsResult<AiProviderOutput>> ListProvidersAsync()
|
||||
{
|
||||
var customerId = await OmsSessionHelper.ResolveXhsCustomerIdAsync(CustomerRepository, CurrentUser);
|
||||
var items = await AiProviderRepository.GetListAsync(x => x.CustomerId == customerId || x.CustomerId == 0);
|
||||
return new OmsItemsResult<XhsAiProviderOutput>
|
||||
return new OmsItemsResult<AiProviderOutput>
|
||||
{
|
||||
Items = items.Select(x => MapProvider(x, maskSecret: true)).ToList()
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 保存AI服务商配置
|
||||
/// 保存 AI 服务商配置。
|
||||
/// </summary>
|
||||
[HttpPost("/xhs/ai/provider/save")]
|
||||
public async Task<XhsAiProviderOutput> SaveProviderAsync(XhsAiProviderSaveInput input)
|
||||
[HttpPost("/ai/provider/save")]
|
||||
public async Task<AiProviderOutput> SaveProviderAsync(AiProviderSaveInput input)
|
||||
{
|
||||
var customerId = await OmsSessionHelper.ResolveXhsCustomerIdAsync(CustomerRepository, CurrentUser);
|
||||
var providerKey = input.ProviderKey.Trim();
|
||||
var entity = await AiProviderRepository.GetByProviderKeyAsync(customerId, providerKey);
|
||||
if (entity == null)
|
||||
{
|
||||
entity = new XhsAiProvider { CustomerId = customerId, ProviderKey = providerKey };
|
||||
entity = new AiProvider { CustomerId = customerId, ProviderKey = providerKey };
|
||||
}
|
||||
|
||||
entity.ProviderType = input.ProviderType.Trim();
|
||||
@ -85,22 +88,22 @@ public class XhsAiService : ApplicationService
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// AI对话
|
||||
/// AI 对话。
|
||||
/// </summary>
|
||||
[HttpPost("/xhs/ai/chat")]
|
||||
public async Task<XhsAiChatOutput> ChatAsync(XhsAiChatInput input)
|
||||
[HttpPost("/ai/chat")]
|
||||
public async Task<AiChatOutput> ChatAsync(AiChatInput input)
|
||||
{
|
||||
var customerId = await OmsSessionHelper.ResolveXhsCustomerIdAsync(CustomerRepository, CurrentUser);
|
||||
var provider = await ResolveProviderAsync(customerId, input.ProviderKey.Trim());
|
||||
var llm = LazyServiceProvider.GetRequiredService<XhsLlmClient>();
|
||||
var llm = LazyServiceProvider.GetRequiredService<AiLlmClient>();
|
||||
var content = await llm.ChatCompletionAsync(
|
||||
provider,
|
||||
input.Messages.Select(x => (x.Role, x.Content)).ToList(),
|
||||
input.Model);
|
||||
return new XhsAiChatOutput { Content = content };
|
||||
return new AiChatOutput { Content = content };
|
||||
}
|
||||
|
||||
internal async Task<XhsAiProvider> ResolveProviderAsync(int customerId, string providerKey)
|
||||
internal async Task<AiProvider> ResolveProviderAsync(int customerId, string providerKey)
|
||||
{
|
||||
var provider = await AiProviderRepository.GetByProviderKeyAsync(customerId, providerKey);
|
||||
if (provider == null || !provider.Enabled)
|
||||
@ -116,7 +119,7 @@ public class XhsAiService : ApplicationService
|
||||
return provider;
|
||||
}
|
||||
|
||||
private static XhsAiProviderOutput MapProvider(XhsAiProvider entity, bool maskSecret) => new()
|
||||
private static AiProviderOutput MapProvider(AiProvider entity, bool maskSecret) => new()
|
||||
{
|
||||
Id = entity.Id,
|
||||
ProviderKey = entity.ProviderKey,
|
||||
@ -153,6 +153,7 @@ public partial class SalesProductService
|
||||
PublishUser = entity.PublishUser,
|
||||
PublishTime = entity.PublishTime,
|
||||
Description = entity.Description,
|
||||
AiAnalysisReport = entity.AiAnalysisReport,
|
||||
Content = operatorType == PriceOperatorType
|
||||
? OmsDocHelper.ParseSalesProductPricePublishContentForApi(entity.Content)
|
||||
: OmsDocHelper.ToSnapshotApiContent(OmsDocHelper.DeserializeRulesSnapshot(entity.Content)),
|
||||
@ -227,7 +228,11 @@ public partial class SalesProductService
|
||||
/// <summary>
|
||||
/// 为当前配置生成发布快照。
|
||||
/// </summary>
|
||||
private async Task CreatePublishRecordAsync(int salesProductId, string operatorType, string? description)
|
||||
private async Task CreatePublishRecordAsync(
|
||||
int salesProductId,
|
||||
string operatorType,
|
||||
string? description,
|
||||
string? aiAnalysisReport = null)
|
||||
{
|
||||
object snapshot = operatorType == PriceOperatorType
|
||||
? await BuildPricePublishSnapshotAsync(salesProductId)
|
||||
@ -243,6 +248,7 @@ public partial class SalesProductService
|
||||
PublishUser = string.IsNullOrWhiteSpace(CurrentUser.UserName) ? CurrentUser.GetId<int>().ToString() : CurrentUser.UserName!,
|
||||
PublishTime = DateTime.Now,
|
||||
Description = description,
|
||||
AiAnalysisReport = string.IsNullOrWhiteSpace(aiAnalysisReport) ? null : aiAnalysisReport.Trim(),
|
||||
Content = OmsDocHelper.SerializeSnapshotJson(snapshot)
|
||||
});
|
||||
}
|
||||
|
||||
@ -262,7 +262,16 @@ public partial class SalesProductService : ApplicationService
|
||||
await SalesProductRepository.ReplacePricesAsync(input.SalesProductId, prices, factors, details);
|
||||
if (input.Publish)
|
||||
{
|
||||
await CreatePublishRecordAsync(input.SalesProductId, PriceOperatorType, "保存并发布价格");
|
||||
if (string.IsNullOrWhiteSpace(input.AiAnalysisReport))
|
||||
{
|
||||
throw new Fake.Application.BusinessException("价格发布前须完成 AI 风险分析并保存报告");
|
||||
}
|
||||
|
||||
await CreatePublishRecordAsync(
|
||||
input.SalesProductId,
|
||||
PriceOperatorType,
|
||||
"保存并发布价格",
|
||||
input.AiAnalysisReport);
|
||||
}
|
||||
}
|
||||
|
||||
@ -349,7 +358,17 @@ public partial class SalesProductService : ApplicationService
|
||||
[HttpPost("/channel/sales-product/publish")]
|
||||
public async Task PublishAsync(SalesProductPublishInput input)
|
||||
{
|
||||
await CreatePublishRecordAsync(input.SalesProductId, NormalizeOperatorType(input.OperatorType), input.Description);
|
||||
var operatorType = NormalizeOperatorType(input.OperatorType);
|
||||
if (operatorType == PriceOperatorType && string.IsNullOrWhiteSpace(input.AiAnalysisReport))
|
||||
{
|
||||
throw new Fake.Application.BusinessException("价格发布前须完成 AI 风险分析并保存报告");
|
||||
}
|
||||
|
||||
await CreatePublishRecordAsync(
|
||||
input.SalesProductId,
|
||||
operatorType,
|
||||
input.Description,
|
||||
input.AiAnalysisReport);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@ -7,10 +7,10 @@ using Oms.Application.Dtos.Mcp;
|
||||
using Oms.Application.Dtos.Shared;
|
||||
using Oms.Application.Mcp;
|
||||
using Oms.Application.Shared;
|
||||
using Oms.Domain.AiAggregate.Repositories;
|
||||
using Oms.Domain.CustomersAggregate.Repositories;
|
||||
using Oms.Domain.McpAggregate.Entities;
|
||||
using Oms.Domain.McpAggregate.Repositories;
|
||||
using Oms.Domain.XhsAggregate.Repositories;
|
||||
|
||||
namespace Oms.Application.Services.Mcp;
|
||||
|
||||
@ -19,9 +19,9 @@ public class McpToolService : ApplicationService
|
||||
{
|
||||
private IMcpToolRepository McpToolRepository => LazyServiceProvider.GetRequiredService<IMcpToolRepository>();
|
||||
private IMcpToolExecutor ToolExecutor => LazyServiceProvider.GetRequiredService<IMcpToolExecutor>();
|
||||
private IXhsAiProviderRepository AiProviderRepository => LazyServiceProvider.GetRequiredService<IXhsAiProviderRepository>();
|
||||
private IAiProviderRepository AiProviderRepository => LazyServiceProvider.GetRequiredService<IAiProviderRepository>();
|
||||
private ICustomerRepository CustomerRepository => LazyServiceProvider.GetRequiredService<ICustomerRepository>();
|
||||
private XhsLlmClient LlmClient => LazyServiceProvider.GetRequiredService<XhsLlmClient>();
|
||||
private AiLlmClient LlmClient => LazyServiceProvider.GetRequiredService<AiLlmClient>();
|
||||
|
||||
/// <summary>列出 MCP 工具(内置目录 + 库表覆盖)</summary>
|
||||
[HttpPost("/mcp/tool/list")]
|
||||
@ -307,7 +307,7 @@ public class McpToolService : ApplicationService
|
||||
return enabled;
|
||||
}
|
||||
|
||||
private async Task<Oms.Domain.XhsAggregate.Entities.XhsAiProvider> ResolveProviderAsync(
|
||||
private async Task<Oms.Domain.AiAggregate.Entities.AiProvider> ResolveProviderAsync(
|
||||
int customerId,
|
||||
string providerKey)
|
||||
{
|
||||
|
||||
@ -4,8 +4,8 @@ using Fake.Application;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Oms.Application.Dtos.Orders;
|
||||
using Oms.Application.Shared;
|
||||
using Oms.Domain.XhsAggregate.Entities;
|
||||
using Oms.Domain.XhsAggregate.Repositories;
|
||||
using Oms.Domain.AiAggregate.Entities;
|
||||
using Oms.Domain.AiAggregate.Repositories;
|
||||
|
||||
namespace Oms.Application.Services.Orders;
|
||||
|
||||
@ -20,10 +20,10 @@ public partial class OrderService
|
||||
"地址1",
|
||||
];
|
||||
|
||||
private IXhsAiProviderRepository AiProviderRepository =>
|
||||
LazyServiceProvider.GetRequiredService<IXhsAiProviderRepository>();
|
||||
private IAiProviderRepository AiProviderRepository =>
|
||||
LazyServiceProvider.GetRequiredService<IAiProviderRepository>();
|
||||
|
||||
private XhsLlmClient LlmClient => LazyServiceProvider.GetRequiredService<XhsLlmClient>();
|
||||
private AiLlmClient LlmClient => LazyServiceProvider.GetRequiredService<AiLlmClient>();
|
||||
|
||||
/// <summary>
|
||||
/// AI 解析粘贴的收件地址文本,并校验是否完整。
|
||||
@ -122,7 +122,7 @@ public partial class OrderService
|
||||
};
|
||||
}
|
||||
|
||||
private async Task<XhsAiProvider> ResolveAiProviderAsync(int customerId, string? providerKey)
|
||||
private async Task<AiProvider> ResolveAiProviderAsync(int customerId, string? providerKey)
|
||||
{
|
||||
var key = (providerKey ?? string.Empty).Trim();
|
||||
if (!string.IsNullOrWhiteSpace(key))
|
||||
|
||||
@ -2,6 +2,7 @@ using Fake.Application;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Oms.Application.Dtos.Shared;
|
||||
using Oms.Application.Dtos.Xhs;
|
||||
using Oms.Application.Services.Ai;
|
||||
using Oms.Application.Shared;
|
||||
using Oms.Domain.CustomersAggregate.Repositories;
|
||||
using Oms.Domain.XhsAggregate.Consts;
|
||||
@ -110,9 +111,9 @@ public class XhsAnalysisService : ApplicationService
|
||||
throw new BusinessException("请先完成笔记采集");
|
||||
}
|
||||
|
||||
var aiService = LazyServiceProvider.GetRequiredService<XhsAiService>();
|
||||
var aiService = LazyServiceProvider.GetRequiredService<AiService>();
|
||||
var provider = await aiService.ResolveProviderAsync(customerId, input.ProviderKey.Trim());
|
||||
var llm = LazyServiceProvider.GetRequiredService<XhsLlmClient>();
|
||||
var llm = LazyServiceProvider.GetRequiredService<AiLlmClient>();
|
||||
|
||||
var skillHint = string.Empty;
|
||||
if (input.SkillId is > 0)
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
using Fake.Application;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Oms.Application.Dtos.Xhs;
|
||||
using Oms.Application.Services.Ai;
|
||||
using Oms.Application.Shared;
|
||||
using Oms.Domain.CustomersAggregate.Repositories;
|
||||
using Oms.Domain.XhsAggregate.Consts;
|
||||
@ -66,9 +67,9 @@ public class XhsCreationService : ApplicationService
|
||||
public async Task<XhsCreationGenerateOutput> GenerateAsync(XhsCreationGenerateInput input)
|
||||
{
|
||||
var customerId = await OmsSessionHelper.ResolveXhsCustomerIdAsync(CustomerRepository, CurrentUser);
|
||||
var aiService = LazyServiceProvider.GetRequiredService<XhsAiService>();
|
||||
var aiService = LazyServiceProvider.GetRequiredService<AiService>();
|
||||
var provider = await aiService.ResolveProviderAsync(customerId, input.ProviderKey.Trim());
|
||||
var llm = LazyServiceProvider.GetRequiredService<XhsLlmClient>();
|
||||
var llm = LazyServiceProvider.GetRequiredService<AiLlmClient>();
|
||||
|
||||
var skillHint = string.Empty;
|
||||
if (input.SkillId is > 0)
|
||||
|
||||
@ -1,11 +1,11 @@
|
||||
using System.Net.Http.Headers;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Nodes;
|
||||
using Fake.Application;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Oms.Application.Mcp;
|
||||
using Oms.Domain.XhsAggregate.Entities;
|
||||
using Oms.Domain.AiAggregate.Entities;
|
||||
|
||||
namespace Oms.Application.Shared;
|
||||
|
||||
@ -40,21 +40,21 @@ public class LlmChatResult
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// OpenAI 兼容 Chat Completions 客户端(智谱 / DeepSeek / 豆包等)。
|
||||
/// OpenAI 兼容 Chat Completions 客户端(通义 / DeepSeek / 豆包等)。
|
||||
/// </summary>
|
||||
public class XhsLlmClient
|
||||
public class AiLlmClient
|
||||
{
|
||||
public const string HttpClientName = nameof(XhsLlmClient);
|
||||
public const string HttpClientName = nameof(AiLlmClient);
|
||||
|
||||
private readonly IHttpClientFactory _httpClientFactory;
|
||||
|
||||
public XhsLlmClient(IHttpClientFactory httpClientFactory)
|
||||
public AiLlmClient(IHttpClientFactory httpClientFactory)
|
||||
{
|
||||
_httpClientFactory = httpClientFactory;
|
||||
}
|
||||
|
||||
public async Task<string> ChatCompletionAsync(
|
||||
XhsAiProvider provider,
|
||||
AiProvider provider,
|
||||
IReadOnlyList<(string Role, string Content)> messages,
|
||||
string? modelOverride = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
@ -69,7 +69,7 @@ public class XhsLlmClient
|
||||
}
|
||||
|
||||
public async Task<LlmChatResult> ChatCompletionDetailedAsync(
|
||||
XhsAiProvider provider,
|
||||
AiProvider provider,
|
||||
IReadOnlyList<LlmChatMessage> messages,
|
||||
IReadOnlyList<LlmToolDefinition>? tools = null,
|
||||
string? modelOverride = null,
|
||||
@ -258,11 +258,11 @@ public class XhsLlmClient
|
||||
}
|
||||
}
|
||||
|
||||
public static class XhsLlmServiceExtensions
|
||||
public static class AiLlmServiceExtensions
|
||||
{
|
||||
public static IServiceCollection AddXhsLlmClient(this IServiceCollection services)
|
||||
public static IServiceCollection AddAiLlmClient(this IServiceCollection services)
|
||||
{
|
||||
services.AddHttpClient(XhsLlmClient.HttpClientName, client =>
|
||||
services.AddHttpClient(AiLlmClient.HttpClientName, client =>
|
||||
{
|
||||
client.Timeout = TimeSpan.FromMinutes(3);
|
||||
});
|
||||
@ -270,7 +270,8 @@ public static class XhsLlmServiceExtensions
|
||||
{
|
||||
client.Timeout = TimeSpan.FromSeconds(45);
|
||||
});
|
||||
services.AddTransient<XhsLlmClient>();
|
||||
services.AddTransient<AiLlmClient>();
|
||||
return services;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,10 +1,13 @@
|
||||
using Oms.Domain.Shared;
|
||||
using SqlSugar;
|
||||
|
||||
namespace Oms.Domain.XhsAggregate.Entities;
|
||||
namespace Oms.Domain.AiAggregate.Entities;
|
||||
|
||||
[SugarTable("t_xhs_ai_provider")]
|
||||
public class XhsAiProvider : OmsAuditedAggregateRoot
|
||||
/// <summary>
|
||||
/// 全局 AI 提供商配置(CustomerId=0 表示平台级)。
|
||||
/// </summary>
|
||||
[SugarTable("t_ai_provider")]
|
||||
public class AiProvider : OmsAuditedAggregateRoot
|
||||
{
|
||||
public int CustomerId { get; set; }
|
||||
|
||||
@ -0,0 +1,9 @@
|
||||
using Fake.Domain.Repositories;
|
||||
using Oms.Domain.AiAggregate.Entities;
|
||||
|
||||
namespace Oms.Domain.AiAggregate.Repositories;
|
||||
|
||||
public interface IAiProviderRepository : IRepository<AiProvider>
|
||||
{
|
||||
Task<AiProvider?> GetByProviderKeyAsync(int customerId, string providerKey, CancellationToken cancellationToken = default);
|
||||
}
|
||||
@ -21,6 +21,12 @@ public class SalesProductPublishRecord : OmsAuditedEntity
|
||||
|
||||
public string? Description { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 价格发布 AI 风险分析报告(operatorType=price 时必填)。
|
||||
/// </summary>
|
||||
[SugarColumn(ColumnDataType = "mediumtext", IsNullable = true)]
|
||||
public string? AiAnalysisReport { get; set; }
|
||||
|
||||
[SugarColumn(ColumnDataType = "json", IsNullable = true)]
|
||||
public string? Content { get; set; }
|
||||
}
|
||||
|
||||
@ -51,9 +51,4 @@ public interface IXhsHumanConfigRepository : IRepository<XhsHumanConfig>
|
||||
Task<XhsHumanConfig?> GetByCustomerIdAsync(int customerId, CancellationToken cancellationToken = default);
|
||||
}
|
||||
|
||||
public interface IXhsAiProviderRepository : IRepository<XhsAiProvider>
|
||||
{
|
||||
Task<XhsAiProvider?> GetByProviderKeyAsync(int customerId, string providerKey, CancellationToken cancellationToken = default);
|
||||
}
|
||||
|
||||
public interface IXhsMediaRepository : IRepository<XhsMedia>;
|
||||
|
||||
16
src/Oms.SqlSugarCore/Ai/AiProviderRepository.cs
Normal file
16
src/Oms.SqlSugarCore/Ai/AiProviderRepository.cs
Normal file
@ -0,0 +1,16 @@
|
||||
using Fake.DependencyInjection;
|
||||
using Oms.Domain.AiAggregate.Entities;
|
||||
using Oms.Domain.AiAggregate.Repositories;
|
||||
using Oms.SqlSugarCore.Repositories;
|
||||
|
||||
namespace Oms.SqlSugarCore.Ai;
|
||||
|
||||
public class AiProviderRepository
|
||||
: OmsSqlSugarRepository<AiProvider>, IAiProviderRepository, IScopedDependency
|
||||
{
|
||||
public Task<AiProvider?> GetByProviderKeyAsync(
|
||||
int customerId,
|
||||
string providerKey,
|
||||
CancellationToken cancellationToken = default) =>
|
||||
FirstOrDefaultAsync(x => x.CustomerId == customerId && x.ProviderKey == providerKey, cancellationToken);
|
||||
}
|
||||
@ -162,14 +162,4 @@ public class XhsHumanConfigRepository
|
||||
FirstOrDefaultAsync(x => x.CustomerId == customerId, cancellationToken);
|
||||
}
|
||||
|
||||
public class XhsAiProviderRepository
|
||||
: OmsSqlSugarRepository<XhsAiProvider>, IXhsAiProviderRepository, IScopedDependency
|
||||
{
|
||||
public Task<XhsAiProvider?> GetByProviderKeyAsync(
|
||||
int customerId,
|
||||
string providerKey,
|
||||
CancellationToken cancellationToken = default) =>
|
||||
FirstOrDefaultAsync(x => x.CustomerId == customerId && x.ProviderKey == providerKey, cancellationToken);
|
||||
}
|
||||
|
||||
public class XhsMediaRepository : OmsSqlSugarRepository<XhsMedia>, IXhsMediaRepository, IScopedDependency;
|
||||
|
||||
@ -22,8 +22,8 @@ public class XhsRolePermissionSeedContributor(RoleService roleService) : IDataSe
|
||||
"Xhs.Skills.Save",
|
||||
"Xhs.Skills.Run",
|
||||
"Xhs.HumanSettings.Save",
|
||||
"Xhs.Ai.Use",
|
||||
"Xhs.Ai.Save"
|
||||
"System.Ai.Use",
|
||||
"System.Ai.Save"
|
||||
];
|
||||
|
||||
public async Task SeedAsync()
|
||||
|
||||
Loading…
Reference in New Issue
Block a user