├── img ├── MCP.png ├── 创建博客.png ├── 博客列表.png ├── 博客详情.png ├── 工作流.png └── 首页.png ├── BlogAgent ├── wwwroot │ ├── favicon.ico │ ├── appsettings.json │ ├── data │ │ └── menu.json │ ├── pro_icon.svg │ ├── css │ │ ├── theme.default.css │ │ └── site.css │ ├── js │ │ ├── screenfull.min.js │ │ └── main.js │ ├── assets │ │ └── logo.svg │ └── index.html ├── Components │ ├── GlobalHeader │ │ ├── RightContent.razor │ │ └── RightContent.razor.cs │ └── Markdown.razor ├── Models │ ├── ChartDataItem.cs │ ├── ActivityGroup.cs │ ├── ActivityUser.cs │ ├── OfflineDataItem.cs │ ├── ActivityProject.cs │ ├── BasicProfileDataType.cs │ ├── OfflineChartDataItem.cs │ ├── RadarDataItem.cs │ ├── FormItemLayout.cs │ ├── NoticeItem.cs │ ├── AdvancedProfileData.cs │ ├── SearchDataItem.cs │ ├── BasicGood.cs │ ├── BasicProgress.cs │ ├── AdvancedOperation.cs │ ├── ActivitiesType.cs │ ├── ListFormModel.cs │ ├── NoticeType.cs │ ├── LoginParamsType.cs │ ├── ChartData.cs │ ├── LayoutModel.cs │ ├── CurrentUser.cs │ ├── ListItemDataType.cs │ └── FormModel.cs ├── _Imports.razor ├── App.razor ├── BlogAgent.csproj ├── Layouts │ ├── UserLayout.razor │ ├── BasicLayout.razor │ └── UserLayout.razor.css ├── appsettings.json ├── Properties │ └── launchSettings.json ├── Pages │ ├── _Host.cshtml │ ├── Index.razor │ └── Blog │ │ ├── List.razor │ │ ├── Detail.razor │ │ └── Create.razor ├── .gitattributes ├── Program.cs └── .gitignore ├── BlogAgent.Domain ├── Repositories │ ├── Base │ │ ├── CreateEntity │ │ │ ├── IEntityService.cs │ │ │ └── EntityService.cs │ │ ├── Setting │ │ │ ├── ISettingsRepository.cs │ │ │ ├── Settings.cs │ │ │ └── SettingsRepository.cs │ │ ├── SqlSugarHelper.cs │ │ └── IRepository.cs │ ├── BlogContentRepository.cs │ ├── AgentExecutionRepository.cs │ ├── BlogTaskRepository.cs │ ├── McpServerConfigRepository.cs │ └── ReviewResultRepository.cs ├── Common │ ├── Options │ │ ├── DBConnectionOption.cs │ │ └── OpenAIOption.cs │ ├── Map │ │ ├── AutoMapProfile.cs │ │ ├── MapperRegister.cs │ │ └── MapperExtend.cs │ ├── Extensions │ │ ├── ServiceDescriptionAttribute.cs │ │ ├── InitExtensions.cs │ │ └── ServiceCollectionExtensions.cs │ └── Excel │ │ └── ExeclPropertyAttribute.cs ├── Utils │ ├── RepoUtils │ │ ├── ObjectExtensions.cs │ │ ├── AppException.cs │ │ └── RepoFiles.cs │ ├── LongToDateTimeConverter.cs │ ├── OpenAIHttpClientHandler.cs │ └── ConvertUtils.cs ├── Domain │ ├── Dto │ │ ├── WritingRequirements.cs │ │ ├── ResearchResultDto.cs │ │ ├── DraftContentDto.cs │ │ ├── WorkflowResult.cs │ │ ├── CreateBlogRequest.cs │ │ ├── BlogTaskDto.cs │ │ ├── ReviewResultDto.cs │ │ ├── WorkflowProgressDto.cs │ │ └── McpServerConfigDto.cs │ ├── Model │ │ ├── PageList.cs │ │ ├── AgentExecution.cs │ │ ├── BlogTask.cs │ │ ├── BlogContent.cs │ │ ├── ReviewResult.cs │ │ ├── McpServerConfig.cs │ │ └── AgentOutputs.cs │ └── Enum │ │ ├── AgentType.cs │ │ └── AgentTaskStatus.cs ├── Services │ ├── Agents │ │ ├── Base │ │ │ └── IAgentService.cs │ │ ├── WriterAgent.cs │ │ ├── ReviewerAgent.cs │ │ └── ResearcherAgent.cs │ └── WebContentService.cs └── BlogAgent.Domain.csproj ├── Directory.Build.props ├── .cursor └── rules │ └── antd.mdc ├── BlogAgent.sln ├── .gitattributes └── .gitignore /img/MCP.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuzeyu91/BlogAgent/HEAD/img/MCP.png -------------------------------------------------------------------------------- /img/创建博客.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuzeyu91/BlogAgent/HEAD/img/创建博客.png -------------------------------------------------------------------------------- /img/博客列表.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuzeyu91/BlogAgent/HEAD/img/博客列表.png -------------------------------------------------------------------------------- /img/博客详情.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuzeyu91/BlogAgent/HEAD/img/博客详情.png -------------------------------------------------------------------------------- /img/工作流.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuzeyu91/BlogAgent/HEAD/img/工作流.png -------------------------------------------------------------------------------- /img/首页.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuzeyu91/BlogAgent/HEAD/img/首页.png -------------------------------------------------------------------------------- /BlogAgent/wwwroot/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuzeyu91/BlogAgent/HEAD/BlogAgent/wwwroot/favicon.ico -------------------------------------------------------------------------------- /BlogAgent/Components/GlobalHeader/RightContent.razor: -------------------------------------------------------------------------------- 1 | @namespace BlogAgent.Components 2 | @inherits AntDomComponentBase 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /BlogAgent/Models/ChartDataItem.cs: -------------------------------------------------------------------------------- 1 | namespace BlogAgent.Models 2 | { 3 | public class ChartDataItem 4 | { 5 | public string X { get; set; } 6 | public int Y { get; set; } 7 | } 8 | } -------------------------------------------------------------------------------- /BlogAgent/Models/ActivityGroup.cs: -------------------------------------------------------------------------------- 1 | namespace BlogAgent.Models 2 | { 3 | public class ActivityGroup 4 | { 5 | public string Name { get; set; } 6 | public string Link { get; set; } 7 | } 8 | } -------------------------------------------------------------------------------- /BlogAgent/Models/ActivityUser.cs: -------------------------------------------------------------------------------- 1 | namespace BlogAgent.Models 2 | { 3 | public class ActivityUser 4 | { 5 | public string Name { get; set; } 6 | public string Avatar { get; set; } 7 | } 8 | } -------------------------------------------------------------------------------- /BlogAgent/Models/OfflineDataItem.cs: -------------------------------------------------------------------------------- 1 | namespace BlogAgent.Models 2 | { 3 | public class OfflineDataItem 4 | { 5 | public string Name { get; set; } 6 | public float Cvr { get; set; } 7 | } 8 | } -------------------------------------------------------------------------------- /BlogAgent/Models/ActivityProject.cs: -------------------------------------------------------------------------------- 1 | namespace BlogAgent.Models 2 | { 3 | public class ActivityProject 4 | { 5 | public string Name { get; set; } 6 | public string Link { get; set; } 7 | } 8 | } -------------------------------------------------------------------------------- /BlogAgent.Domain/Repositories/Base/CreateEntity/IEntityService.cs: -------------------------------------------------------------------------------- 1 | namespace BlogAgent.Domain.Repositories 2 | { 3 | public interface IEntityService 4 | { 5 | bool CreateEntity(string entityName, string filePath); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /BlogAgent/Models/BasicProfileDataType.cs: -------------------------------------------------------------------------------- 1 | namespace BlogAgent.Models 2 | { 3 | public class BasicProfileDataType 4 | { 5 | public BasicGood[] BasicGoods { get; set; } 6 | public BasicProgress[] BasicProgress { get; set; } 7 | } 8 | } -------------------------------------------------------------------------------- /BlogAgent/Models/OfflineChartDataItem.cs: -------------------------------------------------------------------------------- 1 | namespace BlogAgent.Models 2 | { 3 | public class OfflineChartDataItem 4 | { 5 | public long X { get; set; } 6 | public int Y1 { get; set; } 7 | public int Y2 { get; set; } 8 | } 9 | } -------------------------------------------------------------------------------- /BlogAgent/Models/RadarDataItem.cs: -------------------------------------------------------------------------------- 1 | namespace BlogAgent.Models 2 | { 3 | public class RadarDataItem 4 | { 5 | public string Name { get; set; } 6 | public string Label { get; set; } 7 | public int Value { get; set; } 8 | } 9 | } -------------------------------------------------------------------------------- /BlogAgent/Models/FormItemLayout.cs: -------------------------------------------------------------------------------- 1 | using AntDesign; 2 | 3 | namespace BlogAgent.Models 4 | { 5 | public class FormItemLayout 6 | { 7 | public ColLayoutParam LabelCol { get; set; } 8 | public ColLayoutParam WrapperCol { get; set; } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /BlogAgent/Models/NoticeItem.cs: -------------------------------------------------------------------------------- 1 | using AntDesign.ProLayout; 2 | 3 | namespace BlogAgent.Models 4 | { 5 | public class NoticeItem : NoticeIconData 6 | { 7 | public string Id { get; set; } 8 | public string Type { get; set; } 9 | public string Status { get; set; } 10 | } 11 | } -------------------------------------------------------------------------------- /Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 0.1.0 5 | 1.0.0-preview.251016.1 6 | 9.0.5 7 | 8 | 9 | -------------------------------------------------------------------------------- /BlogAgent.Domain/Repositories/Base/Setting/ISettingsRepository.cs: -------------------------------------------------------------------------------- 1 | using BlogAgent.Domain.Repositories.Base; 2 | using System.Collections.Generic; 3 | using System.Threading.Tasks; 4 | 5 | 6 | namespace BlogAgent.Repositories.Demo 7 | { 8 | public interface ISettingsRepository : IRepository 9 | { 10 | 11 | } 12 | } -------------------------------------------------------------------------------- /BlogAgent/Models/AdvancedProfileData.cs: -------------------------------------------------------------------------------- 1 | namespace BlogAgent.Models 2 | { 3 | public class AdvancedProfileData 4 | { 5 | public AdvancedOperation[] AdvancedOperation1 { get; set; } 6 | public AdvancedOperation[] AdvancedOperation2 { get; set; } 7 | public AdvancedOperation[] AdvancedOperation3 { get; set; } 8 | } 9 | } -------------------------------------------------------------------------------- /BlogAgent/Models/SearchDataItem.cs: -------------------------------------------------------------------------------- 1 | namespace BlogAgent.Models 2 | { 3 | public class SearchDataItem 4 | { 5 | public int Index { get; set; } 6 | public string Keywod { get; set; } 7 | public int Count { get; set; } 8 | public int Range { get; set; } 9 | public int Status { get; set; } 10 | } 11 | } -------------------------------------------------------------------------------- /BlogAgent/Models/BasicGood.cs: -------------------------------------------------------------------------------- 1 | namespace BlogAgent.Models 2 | { 3 | public class BasicGood 4 | { 5 | public string Id { get; set; } 6 | public string Name { get; set; } 7 | public string Barcode { get; set; } 8 | public string Price { get; set; } 9 | public string Num { get; set; } 10 | public string Amount { get; set; } 11 | } 12 | } -------------------------------------------------------------------------------- /BlogAgent/_Imports.razor: -------------------------------------------------------------------------------- 1 | @using AntDesign 2 | @using AntDesign.ProLayout 3 | @using System.Net.Http 4 | @using System.Net.Http.Json 5 | @using Microsoft.AspNetCore.Components.Forms 6 | @using Microsoft.AspNetCore.Components.Routing 7 | @using Microsoft.AspNetCore.Components.Web 8 | @using Microsoft.JSInterop 9 | @using BlogAgent 10 | @using BlogAgent.Models 11 | @using BlogAgent.Components 12 | -------------------------------------------------------------------------------- /BlogAgent/Models/BasicProgress.cs: -------------------------------------------------------------------------------- 1 | namespace BlogAgent.Models 2 | { 3 | public class BasicProgress 4 | { 5 | public string Key { get; set; } 6 | public string Time { get; set; } 7 | public string Rate { get; set; } 8 | public string Status { get; set; } 9 | public string Operator { get; set; } 10 | public string Cost { get; set; } 11 | } 12 | } -------------------------------------------------------------------------------- /BlogAgent/Models/AdvancedOperation.cs: -------------------------------------------------------------------------------- 1 | namespace BlogAgent.Models 2 | { 3 | public class AdvancedOperation 4 | { 5 | public string Key { get; set; } 6 | public string Type { get; set; } 7 | public string Name { get; set; } 8 | public string Status { get; set; } 9 | public string UpdatedAt { get; set; } 10 | public string Memo { get; set; } 11 | } 12 | } -------------------------------------------------------------------------------- /BlogAgent/Models/ActivitiesType.cs: -------------------------------------------------------------------------------- 1 | namespace BlogAgent.Models 2 | { 3 | public class ActivitiesType 4 | { 5 | public string Id { get; set; } 6 | public DateTime UpdatedAt { get; set; } 7 | public ActivityUser User { get; set; } 8 | public ActivityGroup Group { get; set; } 9 | public ActivityProject Project { get; set; } 10 | public string Template { get; set; } 11 | } 12 | } -------------------------------------------------------------------------------- /BlogAgent.Domain/Common/Options/DBConnectionOption.cs: -------------------------------------------------------------------------------- 1 | namespace BlogAgent.Domain.Common.Options 2 | { 3 | public class DBConnectionOption 4 | { 5 | 6 | /// 7 | /// sqlite连接字符串 8 | /// 9 | public static string DbType { get; set; } 10 | /// 11 | /// pg链接字符串 12 | /// 13 | public static string ConnectionStrings { get; set; } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /BlogAgent.Domain/Common/Map/AutoMapProfile.cs: -------------------------------------------------------------------------------- 1 | using AutoMapper; 2 | 3 | namespace BlogAgent.Domain.Common.Map 4 | { 5 | public class AutoMapProfile : Profile 6 | { 7 | public AutoMapProfile() 8 | { 9 | 10 | //映射时忽略null值映射,适用于MapTo场景 11 | //CreateMap().ForAllMembers(opt => opt.Condition((src, dest, sourceMember) => sourceMember != null)); 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /BlogAgent/Models/ListFormModel.cs: -------------------------------------------------------------------------------- 1 | namespace BlogAgent.Models 2 | { 3 | public class ListFormModel 4 | { 5 | public string Owner { get; set; } = "wzj"; 6 | 7 | public string ActiveUser { get; set; } 8 | 9 | public string Satisfaction { get; set; } 10 | } 11 | 12 | public class BasicListFormModel 13 | { 14 | public string Status { get; set; } = "all"; 15 | public string SearchKeyword { get; set; } 16 | } 17 | } -------------------------------------------------------------------------------- /BlogAgent.Domain/Utils/RepoUtils/ObjectExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json; 2 | 3 | namespace BlogAgent.Domain.Utils 4 | { 5 | internal static class ObjectExtensions 6 | { 7 | private static readonly JsonSerializerOptions s_jsonOptionsCache = new() { WriteIndented = true }; 8 | 9 | public static string AsJson(this object obj) 10 | { 11 | return JsonSerializer.Serialize(obj, s_jsonOptionsCache); 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /BlogAgent.Domain/Utils/RepoUtils/AppException.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft. All rights reserved. 2 | 3 | namespace BlogAgent.Domain.Utils; 4 | 5 | internal class AppException : Exception 6 | { 7 | public AppException() : base() 8 | { 9 | } 10 | 11 | public AppException(string message) : base(message) 12 | { 13 | } 14 | 15 | public AppException(string message, Exception innerException) : base(message, innerException) 16 | { 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /BlogAgent/Models/NoticeType.cs: -------------------------------------------------------------------------------- 1 | namespace BlogAgent.Models 2 | { 3 | public class NoticeType 4 | { 5 | public string Id { get; set; } 6 | public string Title { get; set; } 7 | public string Logo { get; set; } 8 | public string Description { get; set; } 9 | public string UpdatedAt { get; set; } 10 | public string Member { get; set; } 11 | public string Href { get; set; } 12 | public string MemberLink { get; set; } 13 | } 14 | } -------------------------------------------------------------------------------- /BlogAgent/wwwroot/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "ProSettings": { 3 | "NavTheme": "dark", 4 | "Layout": "side", 5 | "ContentWidth": "Fluid", 6 | "FixedHeader": false, 7 | "FixSiderbar": true, 8 | "Title": "Ant Design Pro", 9 | "PrimaryColor": "daybreak", 10 | "ColorWeak": false, 11 | "SplitMenus": false, 12 | "HeaderRender": true, 13 | "FooterRender": true, 14 | "MenuRender": true, 15 | "MenuHeaderRender": true, 16 | "HeaderHeight": 48 17 | } 18 | } -------------------------------------------------------------------------------- /BlogAgent/App.razor: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |

Sorry, there's nothing at this address.

10 |
11 |
12 |
13 | -------------------------------------------------------------------------------- /BlogAgent/Models/LoginParamsType.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | 3 | namespace BlogAgent.Models 4 | { 5 | public class LoginParamsType 6 | { 7 | [Required] public string UserName { get; set; } 8 | 9 | [Required] public string Password { get; set; } 10 | 11 | public string Mobile { get; set; } 12 | 13 | public string Captcha { get; set; } 14 | 15 | public string LoginType { get; set; } 16 | 17 | public bool AutoLogin { get; set; } 18 | } 19 | } -------------------------------------------------------------------------------- /BlogAgent.Domain/Common/Options/OpenAIOption.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace BlogAgent.Domain.Common.Options 8 | { 9 | public class OpenAIOption 10 | { 11 | public static string EndPoint { get; set; } 12 | 13 | public static string Key { get; set; } 14 | 15 | public static string ChatModel { get; set; } 16 | 17 | public static string EmbeddingModel { get; set; } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /BlogAgent/wwwroot/data/menu.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "path": "/", 4 | "name": "首页", 5 | "key": "home", 6 | "icon": "home" 7 | }, 8 | { 9 | "path": "/blog/list", 10 | "name": "博客列表", 11 | "key": "blog-list", 12 | "icon": "unordered-list" 13 | }, 14 | { 15 | "path": "/blog/create", 16 | "name": "创建博客", 17 | "key": "blog-create", 18 | "icon": "plus-circle" 19 | }, 20 | { 21 | "path": "/mcp/config", 22 | "name": "MCP配置", 23 | "key": "mcp-config", 24 | "icon": "setting" 25 | } 26 | ] -------------------------------------------------------------------------------- /BlogAgent.Domain/Common/Extensions/ServiceDescriptionAttribute.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | 3 | namespace BlogAgent.Domain.Common.Extensions 4 | { 5 | public class ServiceDescriptionAttribute : Attribute 6 | { 7 | public ServiceDescriptionAttribute(Type serviceType, ServiceLifetime lifetime) 8 | { 9 | ServiceType = serviceType; 10 | Lifetime = lifetime; 11 | } 12 | 13 | public Type ServiceType { get; set; } 14 | 15 | public ServiceLifetime Lifetime { get; set; } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /BlogAgent.Domain/Repositories/Base/Setting/Settings.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using SqlSugar; 4 | 5 | namespace BlogAgent.Repositories.Demo 6 | { 7 | /// 8 | /// Setting 9 | /// 10 | [SugarTable("Settings")] 11 | public class Settings 12 | { 13 | /// 14 | /// 主键ID 15 | /// 16 | [SugarColumn(IsPrimaryKey = true)] 17 | public string Id { get; set; } = Guid.NewGuid().ToString(); 18 | 19 | public string Name { get; set; } 20 | public string Value { get; set; } 21 | } 22 | } -------------------------------------------------------------------------------- /BlogAgent.Domain/Domain/Dto/WritingRequirements.cs: -------------------------------------------------------------------------------- 1 | namespace BlogAgent.Domain.Domain.Dto 2 | { 3 | /// 4 | /// 写作要求DTO 5 | /// 6 | public class WritingRequirements 7 | { 8 | /// 9 | /// 目标字数 10 | /// 11 | public int? TargetWordCount { get; set; } 12 | 13 | /// 14 | /// 写作风格 15 | /// 16 | public string? Style { get; set; } 17 | 18 | /// 19 | /// 目标读者 20 | /// 21 | public string? TargetAudience { get; set; } 22 | } 23 | } 24 | 25 | -------------------------------------------------------------------------------- /BlogAgent.Domain/Domain/Model/PageList.cs: -------------------------------------------------------------------------------- 1 | namespace BlogAgent.Domain.Model 2 | { 3 | /// 4 | /// 返回分页结果 5 | /// 6 | public class PageList 7 | { 8 | //查询结果 9 | public List List { get; set; } 10 | /// 11 | /// 当前页,从1开始 12 | /// 13 | public int PageIndex { get; set; } 14 | /// 15 | /// 每页数量 16 | /// 17 | public int PageSize { get; set; } 18 | /// 19 | /// 总数 20 | /// 21 | public int TotalCount { get; set; } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /BlogAgent.Domain/Domain/Enum/AgentType.cs: -------------------------------------------------------------------------------- 1 | namespace BlogAgent.Domain.Domain.Enum 2 | { 3 | /// 4 | /// Agent类型枚举 5 | /// 6 | public enum AgentType 7 | { 8 | /// 9 | /// 资料收集专家 10 | /// 11 | Researcher = 1, 12 | 13 | /// 14 | /// 博客撰写专家 15 | /// 16 | Writer = 2, 17 | 18 | /// 19 | /// 质量审查专家 20 | /// 21 | Reviewer = 3, 22 | 23 | /// 24 | /// 编辑优化专家 25 | /// 26 | Editor = 4 27 | } 28 | } 29 | 30 | -------------------------------------------------------------------------------- /BlogAgent.Domain/Domain/Dto/ResearchResultDto.cs: -------------------------------------------------------------------------------- 1 | namespace BlogAgent.Domain.Domain.Dto 2 | { 3 | /// 4 | /// 资料收集结果DTO 5 | /// 6 | public class ResearchResultDto 7 | { 8 | /// 9 | /// 资料摘要(Markdown格式) 10 | /// 11 | public string Summary { get; set; } = string.Empty; 12 | 13 | /// 14 | /// 提取的关键点列表 15 | /// 16 | public List KeyPoints { get; set; } = new(); 17 | 18 | /// 19 | /// 生成时间 20 | /// 21 | public DateTime Timestamp { get; set; } 22 | } 23 | } 24 | 25 | -------------------------------------------------------------------------------- /BlogAgent.Domain/Repositories/Base/Setting/SettingsRepository.cs: -------------------------------------------------------------------------------- 1 | using BlogAgent.Domain.Common.Extensions; 2 | using Microsoft.Extensions.DependencyInjection; 3 | using Microsoft.Extensions.Logging; 4 | using SqlSugar; 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Threading.Tasks; 8 | using BlogAgent.Domain.Repositories.Base; 9 | 10 | 11 | namespace BlogAgent.Repositories.Demo 12 | { 13 | /// 14 | /// 聊天消息仓储实现 15 | /// 16 | [ServiceDescription(typeof(ISettingsRepository), ServiceLifetime.Scoped)] 17 | public class SettingsRepository : Repository, ISettingsRepository 18 | { 19 | 20 | } 21 | } -------------------------------------------------------------------------------- /BlogAgent/wwwroot/pro_icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /BlogAgent.Domain/Common/Excel/ExeclPropertyAttribute.cs: -------------------------------------------------------------------------------- 1 | using NPOI.SS.UserModel; 2 | 3 | namespace BlogAgent.Domain.Common.Excel 4 | { 5 | public class ExeclPropertyAttribute : Attribute 6 | { 7 | public ExeclPropertyAttribute() 8 | { 9 | 10 | } 11 | public ExeclPropertyAttribute(string displayName, int order, CellType cellType = CellType.String) 12 | { 13 | DisplayName = displayName; 14 | Order = order; 15 | CellType = cellType; 16 | } 17 | 18 | public string DisplayName { get; set; } 19 | 20 | public int Order { get; set; } 21 | 22 | public CellType CellType { get; set; } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /BlogAgent/Models/ChartData.cs: -------------------------------------------------------------------------------- 1 | namespace BlogAgent.Models 2 | { 3 | public class ChartData 4 | { 5 | public ChartDataItem[] VisitData { get; set; } 6 | public ChartDataItem[] VisitData2 { get; set; } 7 | public ChartDataItem[] SalesData { get; set; } 8 | public SearchDataItem[] SearchData { get; set; } 9 | public OfflineDataItem[] OfflineData { get; set; } 10 | public OfflineChartDataItem[] OfflineChartData { get; set; } 11 | public ChartDataItem[] SalesTypeData { get; set; } 12 | public ChartDataItem[] SalesTypeDataOnline { get; set; } 13 | public ChartDataItem[] SalesTypeDataOffline { get; set; } 14 | public RadarDataItem[] RadarData { get; set; } 15 | } 16 | } -------------------------------------------------------------------------------- /BlogAgent.Domain/Domain/Dto/DraftContentDto.cs: -------------------------------------------------------------------------------- 1 | namespace BlogAgent.Domain.Domain.Dto 2 | { 3 | /// 4 | /// 博客初稿DTO 5 | /// 6 | public class DraftContentDto 7 | { 8 | /// 9 | /// 博客标题 10 | /// 11 | public string Title { get; set; } = string.Empty; 12 | 13 | /// 14 | /// 博客内容(Markdown格式) 15 | /// 16 | public string Content { get; set; } = string.Empty; 17 | 18 | /// 19 | /// 字数统计 20 | /// 21 | public int WordCount { get; set; } 22 | 23 | /// 24 | /// 生成时间 25 | /// 26 | public DateTime GeneratedAt { get; set; } 27 | } 28 | } 29 | 30 | -------------------------------------------------------------------------------- /BlogAgent.Domain/Services/Agents/Base/IAgentService.cs: -------------------------------------------------------------------------------- 1 | using BlogAgent.Domain.Domain.Enum; 2 | 3 | namespace BlogAgent.Domain.Services.Agents.Base 4 | { 5 | /// 6 | /// Agent服务接口 7 | /// 8 | public interface IAgentService 9 | { 10 | /// 11 | /// Agent名称 12 | /// 13 | string AgentName { get; } 14 | 15 | /// 16 | /// Agent类型 17 | /// 18 | AgentType AgentType { get; } 19 | 20 | /// 21 | /// 执行Agent任务 22 | /// 23 | /// 输入内容 24 | /// 任务ID(用于记录执行日志) 25 | /// 输出内容 26 | Task ExecuteAsync(string input, int taskId); 27 | } 28 | } 29 | 30 | -------------------------------------------------------------------------------- /BlogAgent/BlogAgent.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net9.0 5 | enable 6 | enable 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /BlogAgent.Domain/Domain/Dto/WorkflowResult.cs: -------------------------------------------------------------------------------- 1 | namespace BlogAgent.Domain.Domain.Dto 2 | { 3 | /// 4 | /// 工作流执行结果DTO 5 | /// 6 | public class WorkflowResult 7 | { 8 | /// 9 | /// 是否成功 10 | /// 11 | public bool Success { get; set; } 12 | 13 | /// 14 | /// 当前阶段 15 | /// 16 | public string Stage { get; set; } = string.Empty; 17 | 18 | /// 19 | /// 输出数据 20 | /// 21 | public object? Output { get; set; } 22 | 23 | /// 24 | /// 消息 25 | /// 26 | public string Message { get; set; } = string.Empty; 27 | 28 | /// 29 | /// 错误详情 30 | /// 31 | public string? ErrorDetail { get; set; } 32 | } 33 | } 34 | 35 | -------------------------------------------------------------------------------- /BlogAgent.Domain/Common/Map/MapperRegister.cs: -------------------------------------------------------------------------------- 1 | using AutoMapper; 2 | using Microsoft.Extensions.DependencyInjection; 3 | 4 | namespace BlogAgent.Domain.Common.Map 5 | { 6 | public static class MapperRegister 7 | { 8 | public static void AddMapper(this IServiceCollection services) 9 | { 10 | var config = new MapperConfiguration(cfg => 11 | { 12 | cfg.CreateMissingTypeMaps = true; 13 | cfg.ValidateInlineMaps = false; 14 | cfg.AddProfile(); 15 | }); 16 | 17 | IMapper mapper = config.CreateMapper(); 18 | 19 | //启动实体映射 20 | Mapper.Initialize(cfg => 21 | { 22 | cfg.CreateMissingTypeMaps = true; 23 | cfg.ValidateInlineMaps = false; 24 | cfg.AddProfile(); 25 | }); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /BlogAgent/Layouts/UserLayout.razor: -------------------------------------------------------------------------------- 1 | @namespace BlogAgent 2 | @using OneOf 3 | @inherits LayoutComponentBase 4 | 5 |
6 |
7 | 8 |
9 |
10 |
11 | 17 |
BlogAgent
18 |
19 | @Body 20 |
21 | 22 |
23 | 24 | @code 25 | { 26 | public LinkItem[] Links { get; set; } = 27 | { 28 | // new LinkItem 29 | // { 30 | // Key = "BlogAgent", 31 | // Title = "BlogAgent", 32 | // Href = "", 33 | // BlankTarget = true, 34 | // } 35 | }; 36 | } 37 | -------------------------------------------------------------------------------- /BlogAgent.Domain/Utils/LongToDateTimeConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Buffers.Text; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Text.Json; 7 | using System.Text.Json.Serialization; 8 | using System.Threading.Tasks; 9 | 10 | namespace BlogAgent.Domain.Utils 11 | { 12 | public class LongToDateTimeConverter : JsonConverter 13 | { 14 | public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) 15 | { 16 | if (Utf8Parser.TryParse(reader.ValueSpan, out long value, out _)) 17 | return DateTime.UnixEpoch.AddMilliseconds(value); 18 | 19 | throw new FormatException(); 20 | } 21 | 22 | public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options) 23 | { 24 | writer.WriteStringValue( 25 | JsonEncodedText.Encode(((long)(value - DateTime.UnixEpoch).TotalMilliseconds).ToString())); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /BlogAgent/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.AspNetCore": "Warning" 6 | } 7 | }, 8 | "AllowedHosts": "*", 9 | "urls": "http://*:5000", 10 | "ProSettings": { 11 | "NavTheme": "light", 12 | "Layout": "side", 13 | "ContentWidth": "Fluid", 14 | "FixedHeader": false, 15 | "FixSiderbar": true, 16 | "Title": "BlogAgent", 17 | "PrimaryColor": "daybreak", 18 | "ColorWeak": false, 19 | "SplitMenus": false, 20 | "HeaderRender": true, 21 | "FooterRender": true, 22 | "MenuRender": true, 23 | "MenuHeaderRender": true, 24 | "HeaderHeight": 48 25 | }, 26 | "OpenAI": { 27 | "Key": "你的秘钥,注册antskapi享受2折优惠", 28 | "EndPoint": "https://api.antsk.cn/v1", 29 | "ChatModel": "gpt-41", 30 | "EmbeddingModel": "text-embedding-ada-002" 31 | }, 32 | "DBConnection": { 33 | "DbType": "Sqlite", 34 | "ConnectionStrings": "Data Source=BlogAgent.db", 35 | "VectorConnection": "BlogAgentMem.db", 36 | "VectorSize": 1536 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /BlogAgent/Components/Markdown.razor: -------------------------------------------------------------------------------- 1 | @namespace BlogAgent.Components 2 | @using Markdig 3 | 4 |
5 | @if (!string.IsNullOrWhiteSpace(Value)) 6 | { 7 | @((MarkupString)_htmlContent) 8 | } 9 | else if (!string.IsNullOrWhiteSpace(Placeholder)) 10 | { 11 | @Placeholder 12 | } 13 |
14 | 15 | @code { 16 | [Parameter] public string? Value { get; set; } 17 | [Parameter] public string? CssClass { get; set; } 18 | [Parameter] public string? Placeholder { get; set; } 19 | [Parameter(CaptureUnmatchedValues = true)] public Dictionary? AdditionalAttributes { get; set; } 20 | 21 | private string _htmlContent = string.Empty; 22 | private static readonly MarkdownPipeline Pipeline = new MarkdownPipelineBuilder() 23 | .UseAdvancedExtensions() 24 | .Build(); 25 | 26 | protected override void OnParametersSet() 27 | { 28 | var content = Value ?? string.Empty; 29 | _htmlContent = Markdig.Markdown.ToHtml(content, Pipeline); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /BlogAgent.Domain/Domain/Dto/CreateBlogRequest.cs: -------------------------------------------------------------------------------- 1 | namespace BlogAgent.Domain.Domain.Dto 2 | { 3 | /// 4 | /// 创建博客请求DTO 5 | /// 6 | public class CreateBlogRequest 7 | { 8 | /// 9 | /// 工作流模式: auto-全自动, manual-分步模式 10 | /// 11 | public string WorkflowMode { get; set; } = "auto"; 12 | 13 | /// 14 | /// 博客主题 15 | /// 16 | public string Topic { get; set; } = string.Empty; 17 | 18 | /// 19 | /// 参考资料内容 20 | /// 21 | public string? ReferenceContent { get; set; } 22 | 23 | /// 24 | /// 参考链接 25 | /// 26 | public string? ReferenceUrls { get; set; } 27 | 28 | /// 29 | /// 目标字数 30 | /// 31 | public int? TargetWordCount { get; set; } 32 | 33 | /// 34 | /// 写作风格 35 | /// 36 | public string? Style { get; set; } 37 | 38 | /// 39 | /// 目标读者 40 | /// 41 | public string? TargetAudience { get; set; } 42 | } 43 | } 44 | 45 | -------------------------------------------------------------------------------- /BlogAgent.Domain/Domain/Enum/AgentTaskStatus.cs: -------------------------------------------------------------------------------- 1 | namespace BlogAgent.Domain.Domain.Enum 2 | { 3 | /// 4 | /// 博客任务状态枚举 5 | /// 6 | public enum AgentTaskStatus 7 | { 8 | /// 9 | /// 已创建 10 | /// 11 | Created = 0, 12 | 13 | /// 14 | /// 资料收集中 15 | /// 16 | Researching = 1, 17 | 18 | /// 19 | /// 资料收集完成 20 | /// 21 | ResearchCompleted = 2, 22 | 23 | /// 24 | /// 撰写中 25 | /// 26 | Writing = 3, 27 | 28 | /// 29 | /// 撰写完成 30 | /// 31 | WritingCompleted = 4, 32 | 33 | /// 34 | /// 审查中 35 | /// 36 | Reviewing = 5, 37 | 38 | /// 39 | /// 审查完成 40 | /// 41 | ReviewCompleted = 6, 42 | 43 | /// 44 | /// 已发布 45 | /// 46 | Published = 7, 47 | 48 | /// 49 | /// 失败 50 | /// 51 | Failed = 99 52 | } 53 | } 54 | 55 | -------------------------------------------------------------------------------- /BlogAgent/Layouts/BasicLayout.razor: -------------------------------------------------------------------------------- 1 | @namespace BlogAgent 2 | @inherits LayoutComponentBase 3 | 4 | 7 | 8 | 9 | 10 | 11 | @Body 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | @code 20 | { 21 | private MenuDataItem[] _menuData = { }; 22 | 23 | [Inject] public HttpClient HttpClient { get; set; } 24 | 25 | protected override async Task OnInitializedAsync() 26 | { 27 | await base.OnInitializedAsync(); 28 | _menuData = await HttpClient.GetFromJsonAsync("data/menu.json"); 29 | } 30 | 31 | 32 | public LinkItem[] Links { get; set; } = 33 | { 34 | // new LinkItem 35 | // { 36 | // Key = "BlogAgent", 37 | // Title = "BlogAgent", 38 | // Href = "", 39 | // BlankTarget = true, 40 | // } 41 | }; 42 | } -------------------------------------------------------------------------------- /BlogAgent/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/launchsettings.json", 3 | "iisSettings": { 4 | "windowsAuthentication": false, 5 | "anonymousAuthentication": true, 6 | "iisExpress": { 7 | "applicationUrl": "http://localhost:6970", 8 | "sslPort": 44300 9 | } 10 | }, 11 | "profiles": { 12 | "http": { 13 | "commandName": "Project", 14 | "dotnetRunMessages": true, 15 | "launchBrowser": true, 16 | "launchUrl": "swagger", 17 | "applicationUrl": "http://localhost:5254", 18 | "environmentVariables": { 19 | "ASPNETCORE_ENVIRONMENT": "Development" 20 | } 21 | }, 22 | "https": { 23 | "commandName": "Project", 24 | "dotnetRunMessages": true, 25 | "launchBrowser": true, 26 | "launchUrl": "swagger", 27 | "applicationUrl": "https://localhost:7044;http://localhost:5254", 28 | "environmentVariables": { 29 | "ASPNETCORE_ENVIRONMENT": "Development" 30 | } 31 | }, 32 | "IIS Express": { 33 | "commandName": "IISExpress", 34 | "launchBrowser": true, 35 | "launchUrl": "swagger", 36 | "environmentVariables": { 37 | "ASPNETCORE_ENVIRONMENT": "Development" 38 | } 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /BlogAgent.Domain/Domain/Dto/BlogTaskDto.cs: -------------------------------------------------------------------------------- 1 | using BlogAgent.Domain.Domain.Enum; 2 | using AgentTaskStatus = BlogAgent.Domain.Domain.Enum.AgentTaskStatus; 3 | 4 | namespace BlogAgent.Domain.Domain.Dto 5 | { 6 | /// 7 | /// 博客任务DTO 8 | /// 9 | public class BlogTaskDto 10 | { 11 | public int Id { get; set; } 12 | public string Topic { get; set; } = string.Empty; 13 | public AgentTaskStatus Status { get; set; } 14 | public string? CurrentStage { get; set; } 15 | public DateTime CreatedAt { get; set; } 16 | public DateTime? UpdatedAt { get; set; } 17 | 18 | /// 19 | /// 状态显示文本 20 | /// 21 | public string StatusText => Status switch 22 | { 23 | AgentTaskStatus.Created => "已创建", 24 | AgentTaskStatus.Researching => "资料收集中", 25 | AgentTaskStatus.ResearchCompleted => "资料收集完成", 26 | AgentTaskStatus.Writing => "撰写中", 27 | AgentTaskStatus.WritingCompleted => "撰写完成", 28 | AgentTaskStatus.Reviewing => "审查中", 29 | AgentTaskStatus.ReviewCompleted => "审查完成", 30 | AgentTaskStatus.Published => "已发布", 31 | AgentTaskStatus.Failed => "失败", 32 | _ => "未知状态" 33 | }; 34 | } 35 | } 36 | 37 | -------------------------------------------------------------------------------- /BlogAgent/Models/LayoutModel.cs: -------------------------------------------------------------------------------- 1 | using AntDesign; 2 | 3 | namespace BlogAgent.Models 4 | { 5 | public class LayoutModel 6 | { 7 | public static FormItemLayout _formItemLayout = new FormItemLayout 8 | { 9 | LabelCol = new ColLayoutParam 10 | { 11 | Xs = new EmbeddedProperty { Span = 24 }, 12 | Sm = new EmbeddedProperty { Span = 7 }, 13 | }, 14 | 15 | WrapperCol = new ColLayoutParam 16 | { 17 | Xs = new EmbeddedProperty { Span = 24 }, 18 | Sm = new EmbeddedProperty { Span = 12 }, 19 | Md = new EmbeddedProperty { Span = 10 }, 20 | } 21 | }; 22 | 23 | public static FormItemLayout _submitFormLayout = new FormItemLayout 24 | { 25 | WrapperCol = new ColLayoutParam 26 | { 27 | Xs = new EmbeddedProperty { Span = 24, Offset = 0 }, 28 | Sm = new EmbeddedProperty { Span = 10, Offset = 7 }, 29 | } 30 | }; 31 | 32 | public static ListGridType _listGridType = new ListGridType 33 | { 34 | Gutter = 16, 35 | Xs = 1, 36 | Sm = 2, 37 | Md = 3, 38 | Lg = 3, 39 | Xl = 4, 40 | Xxl = 4 41 | }; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /BlogAgent/Models/CurrentUser.cs: -------------------------------------------------------------------------------- 1 | namespace BlogAgent.Models 2 | { 3 | public class TagType 4 | { 5 | public string Key { get; set; } 6 | public string Label { get; set; } 7 | } 8 | 9 | public class GeographicType 10 | { 11 | public TagType Province { get; set; } 12 | public TagType City { get; set; } 13 | } 14 | 15 | public class CurrentUser 16 | { 17 | public string Name { get; set; } 18 | public string Avatar { get; set; } 19 | public string Userid { get; set; } 20 | public NoticeType[] Notice { get; set; } = { }; 21 | public string Email { get; set; } 22 | public string Signature { get; set; } 23 | public string Title { get; set; } 24 | public string Group { get; set; } 25 | public TagType[] Tags { get; set; } = { }; 26 | public int NotifyCount { get; set; } 27 | public int UnreadCount { get; set; } 28 | public string Country { get; set; } 29 | public GeographicType Geographic { get; set; } 30 | public string Address { get; set; } 31 | public string Phone { get; set; } 32 | } 33 | 34 | public class UserLiteItem 35 | { 36 | public string Avater { get; set; } 37 | public string Title { get; set; } 38 | public string Description { get; set; } 39 | } 40 | } -------------------------------------------------------------------------------- /BlogAgent.Domain/Domain/Dto/ReviewResultDto.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace BlogAgent.Domain.Domain.Dto 4 | { 5 | /// 6 | /// 审查结果DTO 7 | /// 8 | public class ReviewResultDto 9 | { 10 | [JsonPropertyName("overallScore")] 11 | public int OverallScore { get; set; } 12 | 13 | [JsonPropertyName("accuracy")] 14 | public DimensionScore Accuracy { get; set; } = new(); 15 | 16 | [JsonPropertyName("logic")] 17 | public DimensionScore Logic { get; set; } = new(); 18 | 19 | [JsonPropertyName("originality")] 20 | public DimensionScore Originality { get; set; } = new(); 21 | 22 | [JsonPropertyName("formatting")] 23 | public DimensionScore Formatting { get; set; } = new(); 24 | 25 | [JsonPropertyName("recommendation")] 26 | public string Recommendation { get; set; } = string.Empty; 27 | 28 | [JsonPropertyName("summary")] 29 | public string Summary { get; set; } = string.Empty; 30 | } 31 | 32 | /// 33 | /// 维度评分 34 | /// 35 | public class DimensionScore 36 | { 37 | [JsonPropertyName("score")] 38 | public int Score { get; set; } 39 | 40 | [JsonPropertyName("issues")] 41 | public List Issues { get; set; } = new(); 42 | } 43 | } 44 | 45 | -------------------------------------------------------------------------------- /BlogAgent/Pages/_Host.cshtml: -------------------------------------------------------------------------------- 1 | @page "/" 2 | @namespace BlogAgent.Pages 3 | @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers 4 | @{ 5 | Layout = null; 6 | } 7 | 8 | 9 | 10 | 11 | 12 | 13 | BlogAgent 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /BlogAgent/Models/ListItemDataType.cs: -------------------------------------------------------------------------------- 1 | 2 | using BlogAgent.Domain.Utils; 3 | using System.Text.Json.Serialization; 4 | 5 | namespace BlogAgent.Models 6 | { 7 | public class Member 8 | { 9 | public string Avatar { get; set; } 10 | public string Name { get; set; } 11 | public string Id { get; set; } 12 | } 13 | 14 | public class ListItemDataType 15 | { 16 | public string Id { get; set; } 17 | public string Owner { get; set; } 18 | public string Title { get; set; } 19 | public string Avatar { get; set; } 20 | public string Cover { get; set; } 21 | public string Status { get; set; } 22 | public int Percent { get; set; } 23 | public string Logo { get; set; } 24 | public string Href { get; set; } 25 | public string Body { get; set; } 26 | public string SubDescription { get; set; } 27 | public string Description { get; set; } 28 | public int ActiveUser { get; set; } 29 | public int NewUser { get; set; } 30 | public int Star { get; set; } 31 | public int Like { get; set; } 32 | public int Message { get; set; } 33 | public string Content { get; set; } 34 | public Member[] Members { get; set; } 35 | 36 | [JsonConverter(typeof(LongToDateTimeConverter))] 37 | public DateTime UpdatedAt { get; set; } 38 | 39 | [JsonConverter(typeof(LongToDateTimeConverter))] 40 | public DateTime CreatedAt { get; set; } 41 | } 42 | } -------------------------------------------------------------------------------- /BlogAgent.Domain/Common/Map/MapperExtend.cs: -------------------------------------------------------------------------------- 1 | using AutoMapper; 2 | 3 | namespace BlogAgent.Domain.Common.Map 4 | { 5 | public static class MapperExtend 6 | { 7 | /// 8 | /// Entity集合转DTO集合 9 | /// 10 | /// 11 | /// 12 | /// 13 | public static List ToDTOList(this object value) 14 | { 15 | if (value == null) 16 | return new List(); 17 | 18 | return Mapper.Map>(value); 19 | } 20 | /// 21 | /// Entity转DTO 22 | /// 23 | /// 24 | /// 25 | /// 26 | public static T ToDTO(this object value) 27 | { 28 | if (value == null) 29 | return default(T); 30 | 31 | return Mapper.Map(value); 32 | } 33 | 34 | /// 35 | /// 给已有对象map,适合update场景,如需过滤空值需要在AutoMapProfile 设置 36 | /// 37 | /// 38 | /// 39 | /// 40 | /// 41 | public static T MapTo(this object self, T result) 42 | { 43 | if (self == null) 44 | return default(T); 45 | return (T)Mapper.Map(self, result, self.GetType(), typeof(T)); 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /BlogAgent.Domain/Repositories/Base/CreateEntity/EntityService.cs: -------------------------------------------------------------------------------- 1 | using BlogAgent.Domain.Common.Extensions; 2 | using BlogAgent.Domain.Common.Options; 3 | 4 | using Microsoft.Extensions.DependencyInjection; 5 | using SqlSugar; 6 | using System.Data; 7 | 8 | namespace BlogAgent.Domain.Repositories 9 | { 10 | [ServiceDescription(typeof(IEntityService), ServiceLifetime.Scoped)] 11 | public class EntityService : IEntityService 12 | { 13 | public SqlSugarClient db = new SqlSugarClient(new ConnectionConfig() 14 | { 15 | ConnectionString = DBConnectionOption.ConnectionStrings, 16 | DbType = (SqlSugar.DbType)Enum.Parse(typeof(SqlSugar.DbType), DBConnectionOption.DbType), 17 | InitKeyType = InitKeyType.Attribute,//从特性读取主键和自增列信息 18 | IsAutoCloseConnection = true,//开启自动释放模式和EF原理一样我就不多解释了 19 | }); 20 | /// 21 | /// 生成实体类 22 | /// 23 | /// 24 | /// 25 | /// 26 | public bool CreateEntity(string entityName, string filePath) 27 | { 28 | try 29 | { 30 | db.DbFirst.IsCreateAttribute().Where(entityName).SettingClassTemplate(old => 31 | { 32 | return old.Replace("{Namespace}", "BlogAgent.Domain.Repositories");//修改Namespace命名空间 33 | }).CreateClassFile(filePath); 34 | return true; 35 | } 36 | catch (Exception) 37 | { 38 | return false; 39 | } 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /BlogAgent/Models/FormModel.cs: -------------------------------------------------------------------------------- 1 | namespace BlogAgent.Models 2 | { 3 | public class StepFormModel 4 | { 5 | public string ReceiverAccountType { get; set; } = "ant-design@alipay.com"; 6 | public string ReceiverAccount { get; set; } = "test@example.com"; 7 | public string ReceiverName { get; set; } = "Alex"; 8 | public string PayAccount { get; set; } 9 | public string Password { get; set; } = "500"; 10 | public string Amount { get; set; } = "12345678"; 11 | } 12 | 13 | public class AdvancedFormModel 14 | { 15 | public string Name { get; set; } 16 | public string Url { get; set; } 17 | public string Owner { get; set; } 18 | public string Approver { get; set; } 19 | public DateTime?[] DateRange { get; set; } 20 | public string Type { get; set; } 21 | public string Name2 { get; set; } 22 | public string Url2 { get; set; } 23 | public string Owner2 { get; set; } 24 | public string Approver2 { get; set; } 25 | public DateTime? DateRange2 { get; set; } 26 | public string Type2 { get; set; } 27 | } 28 | 29 | public class BasicFormModel 30 | { 31 | public string Title { get; set; } 32 | public string Client { get; set; } 33 | public string Invites { get; set; } 34 | public int Disclosure { get; set; } 35 | public int Weight { get; set; } 36 | public string Standard { get; set; } 37 | public string Goal { get; set; } 38 | public DateTime?[] DateRange { get; set; } 39 | } 40 | 41 | public class Owner 42 | { 43 | public string Id { get; set; } 44 | public string Name { get; set; } 45 | } 46 | } -------------------------------------------------------------------------------- /BlogAgent.Domain/Repositories/Base/SqlSugarHelper.cs: -------------------------------------------------------------------------------- 1 | using BlogAgent.Domain; 2 | using BlogAgent.Domain.Common.Options; 3 | using SqlSugar; 4 | using System.Reflection; 5 | 6 | namespace BlogAgent.Domain.Repositories.Base 7 | { 8 | public class SqlSugarHelper() 9 | { 10 | 11 | /// 12 | /// sqlserver连接 13 | /// 14 | public static SqlSugarScope SqlScope() 15 | { 16 | 17 | string DBType = DBConnectionOption.DbType; 18 | string ConnectionString = DBConnectionOption.ConnectionStrings; 19 | 20 | var config = new ConnectionConfig() 21 | { 22 | ConnectionString = ConnectionString, 23 | InitKeyType = InitKeyType.Attribute,//从特性读取主键和自增列信息 24 | IsAutoCloseConnection = true, 25 | ConfigureExternalServices = new ConfigureExternalServices 26 | { 27 | //注意: 这儿AOP设置不能少 28 | EntityService = (c, p) => 29 | { 30 | /***高版C#写法***/ 31 | //支持string?和string 32 | if (p.IsPrimarykey == false && new NullabilityInfoContext() 33 | .Create(c).WriteState is NullabilityState.Nullable) 34 | { 35 | p.IsNullable = true; 36 | } 37 | } 38 | } 39 | }; 40 | DbType dbType = (DbType)Enum.Parse(typeof(DbType), DBType); 41 | config.DbType = dbType; 42 | var scope = new SqlSugarScope(config, Db => 43 | { 44 | 45 | }); 46 | return scope; 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /BlogAgent.Domain/Utils/RepoUtils/RepoFiles.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft. All rights reserved. 2 | 3 | using System.Reflection; 4 | 5 | namespace BlogAgent.Domain.Utils; 6 | 7 | internal static class RepoFiles 8 | { 9 | /// 10 | /// Scan the local folders from the repo, looking for "samples/plugins" folder. 11 | /// 12 | /// The full path to samples/plugins 13 | public static string SamplePluginsPath() 14 | { 15 | string baseDirectory = AppDomain.CurrentDomain.BaseDirectory; 16 | string folderName = "plugins"; 17 | 18 | string FindPluginsDirectory(string startDir, string targetFolder) 19 | { 20 | string currDir = Path.GetFullPath(startDir); 21 | const int maxAttempts = 10; 22 | 23 | for (int i = 0; i < maxAttempts; i++) 24 | { 25 | string potentialPath = Path.Combine(currDir, targetFolder); 26 | if (Directory.Exists(potentialPath)) 27 | { 28 | return potentialPath; 29 | } 30 | 31 | currDir = Path.GetFullPath(Path.Combine(currDir, "..")); 32 | } 33 | 34 | return null; // Not found after max attempts. 35 | } 36 | 37 | // Check in the BaseDirectory and its parent directories 38 | string path = FindPluginsDirectory(baseDirectory, folderName) 39 | ?? FindPluginsDirectory(baseDirectory + Path.DirectorySeparatorChar + folderName, folderName); 40 | 41 | if (string.IsNullOrEmpty(path)) 42 | { 43 | throw new AppException("Plugins directory not found. The app needs the plugins from the repo to work."); 44 | } 45 | return path; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /BlogAgent.Domain/Common/Extensions/InitExtensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Builder; 2 | using Microsoft.Extensions.DependencyInjection; 3 | using SqlSugar; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Reflection; 8 | using System.Text; 9 | using System.Threading.Tasks; 10 | using BlogAgent.Repositories.Demo; 11 | 12 | namespace BlogAgent.Domain.Common.Extensions 13 | { 14 | public static class InitExtensions 15 | { 16 | 17 | /// 18 | /// 根据程序集中的实体类创建数据库表 19 | /// 20 | /// 21 | public static void CodeFirst(this WebApplication app) 22 | { 23 | using (var scope = app.Services.CreateScope()) 24 | { 25 | // 获取仓储服务 26 | var _repository = scope.ServiceProvider.GetRequiredService(); 27 | // 创建数据库(如果不存在) 28 | _repository.GetDB().DbMaintenance.CreateDatabase(); 29 | 30 | // 扫描 BlogAgent.Domain 程序集中的所有实体类 31 | var domainAssembly = typeof(InitExtensions).Assembly; 32 | var entityTypes = domainAssembly.GetTypes() 33 | .Where(type => TypeIsEntity(type)); 34 | 35 | // 为每个找到的类型初始化数据库表 36 | foreach (var type in entityTypes) 37 | { 38 | _repository.GetDB().CodeFirst.InitTables(type); 39 | } 40 | } 41 | } 42 | 43 | static bool TypeIsEntity(Type type) 44 | { 45 | // 检查类型是否具有SugarTable特性 46 | return type.GetCustomAttributes(typeof(SugarTable), inherit: false).Length > 0; 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /BlogAgent/Layouts/UserLayout.razor.css: -------------------------------------------------------------------------------- 1 | /* stylelint-disable at-rule-empty-line-before,at-rule-name-space-after,at-rule-no-unknown */ 2 | /* stylelint-disable no-duplicate-selectors */ 3 | /* stylelint-disable */ 4 | /* stylelint-disable declaration-bang-space-before,no-duplicate-selectors,string-no-newline */ 5 | .container__b__0 { 6 | display: flex; 7 | flex-direction: column; 8 | height: 100vh; 9 | overflow: auto; 10 | background: #f0f2f5; 11 | } 12 | .container__b__0 .lang { 13 | width: 100%; 14 | height: 40px; 15 | line-height: 44px; 16 | text-align: right; 17 | } 18 | .container__b__0 .lang :global(.ant-dropdown-trigger) { 19 | margin-right: 24px; 20 | } 21 | .container__b__0 .content { 22 | flex: 1; 23 | padding: 32px 0; 24 | } 25 | .container__b__0 .top { 26 | text-align: center; 27 | } 28 | .container__b__0 .header { 29 | height: 44px; 30 | line-height: 44px; 31 | } 32 | .container__b__0 .header a { 33 | text-decoration: none; 34 | } 35 | .container__b__0 .logo { 36 | height: 44px; 37 | margin-right: 16px; 38 | vertical-align: top; 39 | } 40 | .container__b__0 .title { 41 | position: relative; 42 | top: 2px; 43 | color: rgba(0, 0, 0, 0.85); 44 | font-weight: 600; 45 | font-size: 33px; 46 | font-family: Avenir, 'Helvetica Neue', Arial, Helvetica, sans-serif; 47 | } 48 | .container__b__0 .desc { 49 | margin-top: 12px; 50 | margin-bottom: 40px; 51 | color: rgba(0, 0, 0, 0.45); 52 | font-size: 14px; 53 | } 54 | @media (min-width: 768px) { 55 | .container__b__0 { 56 | background-image: url('https://gw.alipayobjects.com/zos/rmsportal/TVYTbAXWheQpRcWDaDMu.svg'); 57 | background-repeat: no-repeat; 58 | background-position: center 110px; 59 | background-size: 100%; 60 | } 61 | .container__b__0 .content { 62 | padding: 32px 0 24px; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /.cursor/rules/antd.mdc: -------------------------------------------------------------------------------- 1 | --- 2 | description: 你是一位精通Blazor和Ant Design Blazor组件库的资深开发专家 3 | globs: *.razor 4 | alwaysApply: false 5 | --- 6 | 你是一位精通Blazor和Ant Design Blazor组件库的资深开发专家 7 | 8 | 9 | - 深入理解Blazor框架的核心概念和最佳实践 10 | - 熟练掌握Ant Design Blazor组件库的全部功能和使用方法 11 | - 具备丰富的Blazor界面开发和优化经验 12 | 13 | 14 | 根据用户提供的需求,优化使用Ant Design Blazor组件库构建的界面 15 | 16 | 17 | 1. 仔细聆听用户描述的具体需求 18 | 2. 分析当前界面实现,识别可优化的方面 19 | 3. 提供清晰、具体的优化建议,包括但不限于: 20 | - 组件选择和配置调整 21 | - 布局结构优化 22 | - 性能提升方案 23 | - 用户体验改进 24 | 4. 解释每项优化建议的理由和预期效果 25 | 5. 如有必要,提供优化后的代码示例 26 | 27 | 28 | 29 | - 使用专业且易懂的语言 30 | - 按优先级或逻辑顺序组织建议 31 | - 为复杂的优化提供分步骤说明 32 | 33 | 34 | 35 | - 确保建议符合Blazor和Ant Design Blazor的最佳实践 36 | - 考虑界面的可维护性、可扩展性和重用性 37 | - 在保持功能完整的同时,追求简洁高效的实现 38 | 39 | 40 | 41 | AntSK-Business/ 42 | ├── src/ 43 | │ ├── AntSK/ 44 | │ │ ├── Components/ 45 | │ │ ├── Controllers/ 46 | │ │ ├── Domain/ 47 | │ │ ├── Layouts/ 48 | │ │ ├── Models/ 49 | │ │ ├── Pages/ 50 | │ │ ├── Services/ 51 | │ │ ├── wwwroot/ 52 | │ │ ├── Program.cs 53 | │ │ └── appsettings.json 54 | │ ├── AntSK.AppHost/ 55 | │ ├── AntSK.BlazorDiagram/ 56 | │ ├── AntSK.Domain/ 57 | │ │ ├── Common/ 58 | │ │ ├── Domain/ 59 | │ │ ├── Options/ 60 | │ │ │ ├── GraphDBConnectionOption.cs 61 | │ │ │ ├── GraphOpenAIOption.cs 62 | │ │ │ ├── GraphSearchOption.cs 63 | │ │ │ └── GraphSysOption.cs 64 | │ │ ├── Repositories/ 65 | │ │ └── Utils/ 66 | │ ├── AntSK.Evaluation/ 67 | │ ├── AntSK.LLM/ 68 | │ ├── AntSK.LLamaFactory/ 69 | │ ├── AntSK.OCR/ 70 | │ ├── AntSK.OfflineAuthForm/ 71 | │ └── AntSK.ServiceDefaults/ 72 | 73 | 74 | -------------------------------------------------------------------------------- /BlogAgent.Domain/Domain/Dto/WorkflowProgressDto.cs: -------------------------------------------------------------------------------- 1 | namespace BlogAgent.Domain.Domain.Dto 2 | { 3 | /// 4 | /// 工作流进度信息 DTO 5 | /// 6 | public class WorkflowProgressDto 7 | { 8 | /// 9 | /// 任务ID 10 | /// 11 | public int TaskId { get; set; } 12 | 13 | /// 14 | /// 当前步骤 (0=研究, 1=撰写, 2=审查, 3=完成) 15 | /// 16 | public int CurrentStep { get; set; } 17 | 18 | /// 19 | /// 步骤名称 20 | /// 21 | public string StepName { get; set; } = string.Empty; 22 | 23 | /// 24 | /// 执行状态 25 | /// 26 | public string Status { get; set; } = string.Empty; 27 | 28 | /// 29 | /// 执行消息 30 | /// 31 | public string Message { get; set; } = string.Empty; 32 | 33 | /// 34 | /// 当前阶段输出内容(部分) 35 | /// 36 | public string? CurrentOutput { get; set; } 37 | 38 | /// 39 | /// 是否完成 40 | /// 41 | public bool IsCompleted { get; set; } 42 | 43 | /// 44 | /// 是否成功 45 | /// 46 | public bool IsSuccess { get; set; } 47 | 48 | /// 49 | /// 错误信息 50 | /// 51 | public string? ErrorMessage { get; set; } 52 | 53 | /// 54 | /// 研究结果(步骤1完成后) 55 | /// 56 | public ResearchResultDto? ResearchResult { get; set; } 57 | 58 | /// 59 | /// 草稿内容(步骤2完成后) 60 | /// 61 | public DraftContentDto? DraftContent { get; set; } 62 | 63 | /// 64 | /// 审查结果(步骤3完成后) 65 | /// 66 | public ReviewResultDto? ReviewResult { get; set; } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /BlogAgent.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.13.35913.81 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BlogAgent", "BlogAgent\BlogAgent.csproj", "{72444DA3-844C-D70C-C984-822C6BA6E821}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BlogAgent.Domain", "BlogAgent.Domain\BlogAgent.Domain.csproj", "{E95DA37E-8AA9-9899-B64C-920F1BCE156E}" 9 | EndProject 10 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "解决方案项", "解决方案项", "{9FA3D6BD-1EC1-3BA5-80CB-CE02773A58D5}" 11 | ProjectSection(SolutionItems) = preProject 12 | Directory.Build.props = Directory.Build.props 13 | EndProjectSection 14 | EndProject 15 | Global 16 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 17 | Debug|Any CPU = Debug|Any CPU 18 | Release|Any CPU = Release|Any CPU 19 | EndGlobalSection 20 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 21 | {72444DA3-844C-D70C-C984-822C6BA6E821}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 22 | {72444DA3-844C-D70C-C984-822C6BA6E821}.Debug|Any CPU.Build.0 = Debug|Any CPU 23 | {72444DA3-844C-D70C-C984-822C6BA6E821}.Release|Any CPU.ActiveCfg = Release|Any CPU 24 | {72444DA3-844C-D70C-C984-822C6BA6E821}.Release|Any CPU.Build.0 = Release|Any CPU 25 | {E95DA37E-8AA9-9899-B64C-920F1BCE156E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 26 | {E95DA37E-8AA9-9899-B64C-920F1BCE156E}.Debug|Any CPU.Build.0 = Debug|Any CPU 27 | {E95DA37E-8AA9-9899-B64C-920F1BCE156E}.Release|Any CPU.ActiveCfg = Release|Any CPU 28 | {E95DA37E-8AA9-9899-B64C-920F1BCE156E}.Release|Any CPU.Build.0 = Release|Any CPU 29 | EndGlobalSection 30 | GlobalSection(SolutionProperties) = preSolution 31 | HideSolutionNode = FALSE 32 | EndGlobalSection 33 | GlobalSection(ExtensibilityGlobals) = postSolution 34 | SolutionGuid = {3DC3878E-160A-4BB8-9865-BF2433C9BFAF} 35 | EndGlobalSection 36 | EndGlobal 37 | -------------------------------------------------------------------------------- /BlogAgent.Domain/Repositories/BlogContentRepository.cs: -------------------------------------------------------------------------------- 1 | using BlogAgent.Domain.Common.Extensions; 2 | using BlogAgent.Domain.Domain.Model; 3 | using BlogAgent.Domain.Repositories.Base; 4 | using Microsoft.Extensions.DependencyInjection; 5 | using SqlSugar; 6 | 7 | namespace BlogAgent.Domain.Repositories 8 | { 9 | /// 10 | /// 博客内容仓储 11 | /// 12 | [ServiceDescription(typeof(BlogContentRepository), ServiceLifetime.Scoped)] 13 | public class BlogContentRepository : Repository 14 | { 15 | public BlogContentRepository(ISqlSugarClient db) : base(db) 16 | { 17 | } 18 | 19 | /// 20 | /// 根据任务ID获取内容 21 | /// 22 | public async Task GetByTaskIdAsync(int taskId) 23 | { 24 | return await GetDB().Queryable() 25 | .Where(c => c.TaskId == taskId) 26 | .OrderByDescending(c => c.CreatedAt) 27 | .FirstAsync(); 28 | } 29 | 30 | /// 31 | /// 更新发布状态 32 | /// 33 | public async Task UpdatePublishStatusAsync(int contentId, bool isPublished) 34 | { 35 | return await GetDB().Updateable() 36 | .SetColumns(c => new BlogContent 37 | { 38 | IsPublished = isPublished, 39 | PublishedAt = isPublished ? DateTime.Now : null, 40 | UpdatedAt = DateTime.Now 41 | }) 42 | .Where(c => c.Id == contentId) 43 | .ExecuteCommandAsync() > 0; 44 | } 45 | 46 | /// 47 | /// 获取已发布的内容列表 48 | /// 49 | public async Task> GetPublishedContentsAsync(int page = 1, int pageSize = 20) 50 | { 51 | return await GetDB().Queryable() 52 | .Where(c => c.IsPublished) 53 | .OrderByDescending(c => c.PublishedAt) 54 | .ToPageListAsync(page, pageSize); 55 | } 56 | } 57 | } 58 | 59 | -------------------------------------------------------------------------------- /BlogAgent/wwwroot/css/theme.default.css: -------------------------------------------------------------------------------- 1 | 2 | /*@font-face { 3 | font-family: 'IBM Plex Mono'; 4 | font-style: normal; 5 | font-weight: 500; 6 | font-display: swap; 7 | src: url(https://fonts.gstatic.com/s/ibmplexmono/v19/-F6qfjptAgt5VM-kVkqdyU8n3twJwl1FgtIU.woff2) format('woff2'); 8 | unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F; 9 | }*/ 10 | 11 | /*@font-face { 12 | font-family: ibmplexmono; 13 | src: url(fonts/ibmplexmono.woff2); 14 | display: swap; 15 | }*/ 16 | 17 | @font-face { 18 | font-family: CascadiaCode; 19 | src: url(fonts/CascadiaCode.ttf); 20 | display: swap; 21 | } 22 | 23 | 24 | .bc-terminal-theme { 25 | font-family: CascadiaCode, monospace; 26 | font-style: normal; 27 | font-weight: 500; 28 | background-color: rgb(29, 42, 53); 29 | color: rgb(203, 213, 225); 30 | } 31 | 32 | .bc-terminal-theme > .bc-paragraph { 33 | } 34 | 35 | .bc-terminal-theme .bc-terminal-input { 36 | color: rgb(203, 213, 225); 37 | caret-color: rgb(5, 206, 145); 38 | font-family: inherit; 39 | font-size: 100%; 40 | /*line-height: 1.15;*/ 41 | padding: 0; 42 | } 43 | 44 | 45 | .bc-terminal-theme > .bc-paragraph-command > .bc-prompt > .bc-prompt-name { 46 | color: rgb(255, 157, 0); 47 | } 48 | 49 | .bc-terminal-theme > .bc-paragraph-command > .bc-prompt > .bc-prompt-spr1 { 50 | } 51 | 52 | .bc-terminal-theme > .bc-paragraph-command > .bc-prompt > .bc-prompt-host { 53 | color: rgb(5, 206, 145); 54 | } 55 | 56 | .bc-terminal-theme > .bc-paragraph-command > .bc-prompt > .bc-prompt-spr2 { 57 | } 58 | 59 | .bc-terminal-theme > .bc-paragraph-command > .bc-prompt > .bc-prompt-path { 60 | } 61 | 62 | .bc-terminal-theme > .bc-paragraph-command > .bc-prompt > .bc-prompt-pmt { 63 | } 64 | 65 | 66 | 67 | 68 | 69 | .bc-terminal-theme > .bc-paragraph-response > .bc-command-content { 70 | } 71 | 72 | 73 | .spin-container { 74 | display: flex; 75 | justify-content: center; 76 | align-items: center; 77 | min-height: 300px; 78 | } -------------------------------------------------------------------------------- /BlogAgent.Domain/Repositories/AgentExecutionRepository.cs: -------------------------------------------------------------------------------- 1 | using BlogAgent.Domain.Common.Extensions; 2 | using BlogAgent.Domain.Domain.Enum; 3 | using BlogAgent.Domain.Domain.Model; 4 | using BlogAgent.Domain.Repositories.Base; 5 | using Microsoft.Extensions.DependencyInjection; 6 | using SqlSugar; 7 | 8 | namespace BlogAgent.Domain.Repositories 9 | { 10 | /// 11 | /// Agent执行记录仓储 12 | /// 13 | [ServiceDescription(typeof(AgentExecutionRepository), ServiceLifetime.Scoped)] 14 | public class AgentExecutionRepository : Repository 15 | { 16 | public AgentExecutionRepository(ISqlSugarClient db) : base(db) 17 | { 18 | } 19 | 20 | /// 21 | /// 根据任务ID获取执行记录 22 | /// 23 | public async Task> GetByTaskIdAsync(int taskId) 24 | { 25 | return await GetDB().Queryable() 26 | .Where(e => e.TaskId == taskId) 27 | .OrderBy(e => e.StartTime) 28 | .ToListAsync(); 29 | } 30 | 31 | /// 32 | /// 根据任务ID和Agent类型获取最新的执行记录 33 | /// 34 | public async Task GetLatestByTaskAndAgentAsync(int taskId, AgentType agentType) 35 | { 36 | return await GetDB().Queryable() 37 | .Where(e => e.TaskId == taskId && e.AgentType == agentType) 38 | .OrderByDescending(e => e.StartTime) 39 | .FirstAsync(); 40 | } 41 | 42 | /// 43 | /// 获取执行统计信息 44 | /// 45 | public async Task> GetExecutionStatisticsAsync() 46 | { 47 | var list = await GetDB().Queryable() 48 | .Where(e => e.Success) 49 | .GroupBy(e => e.AgentType) 50 | .Select(g => new { AgentType = g.AgentType, Count = SqlFunc.AggregateCount(g.Id) }) 51 | .ToListAsync(); 52 | 53 | return list.ToDictionary(x => x.AgentType, x => x.Count); 54 | } 55 | } 56 | } 57 | 58 | -------------------------------------------------------------------------------- /BlogAgent/wwwroot/js/screenfull.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * screenfull 3 | * v5.1.0 - 2020-12-24 4 | * (c) Sindre Sorhus; MIT License 5 | */ 6 | 7 | !function(){"use strict";var c="undefined"!=typeof window&&void 0!==window.document?window.document:{},e="undefined"!=typeof module&&module.exports,s=function(){for(var e,n=[["requestFullscreen","exitFullscreen","fullscreenElement","fullscreenEnabled","fullscreenchange","fullscreenerror"],["webkitRequestFullscreen","webkitExitFullscreen","webkitFullscreenElement","webkitFullscreenEnabled","webkitfullscreenchange","webkitfullscreenerror"],["webkitRequestFullScreen","webkitCancelFullScreen","webkitCurrentFullScreenElement","webkitCancelFullScreen","webkitfullscreenchange","webkitfullscreenerror"],["mozRequestFullScreen","mozCancelFullScreen","mozFullScreenElement","mozFullScreenEnabled","mozfullscreenchange","mozfullscreenerror"],["msRequestFullscreen","msExitFullscreen","msFullscreenElement","msFullscreenEnabled","MSFullscreenChange","MSFullscreenError"]],l=0,r=n.length,t={};l 2 | 3 | 4 | net9.0 5 | enable 6 | enable 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /BlogAgent.Domain/Domain/Model/AgentExecution.cs: -------------------------------------------------------------------------------- 1 | using SqlSugar; 2 | using BlogAgent.Domain.Domain.Enum; 3 | 4 | namespace BlogAgent.Domain.Domain.Model 5 | { 6 | /// 7 | /// Agent执行记录实体 8 | /// 9 | [SugarTable("agent_execution")] 10 | public class AgentExecution 11 | { 12 | /// 13 | /// 主键ID 14 | /// 15 | [SugarColumn(IsPrimaryKey = true, IsIdentity = true)] 16 | public int Id { get; set; } 17 | 18 | /// 19 | /// 关联的任务ID 20 | /// 21 | [SugarColumn(IsNullable = false)] 22 | public int TaskId { get; set; } /// 23 | /// Agent类型 24 | /// 25 | [SugarColumn(IsNullable = false)] 26 | public AgentType AgentType { get; set; } 27 | 28 | /// 29 | /// 输入内容 30 | /// 31 | [SugarColumn(ColumnDataType = "text", IsNullable = true)] 32 | public string? Input { get; set; } 33 | 34 | /// 35 | /// 输出内容 36 | /// 37 | [SugarColumn(ColumnDataType = "text", IsNullable = true)] 38 | public string? Output { get; set; } 39 | 40 | /// 41 | /// 是否成功 42 | /// 43 | [SugarColumn(IsNullable = false)] 44 | public bool Success { get; set; } 45 | 46 | /// 47 | /// 错误消息 48 | /// 49 | [SugarColumn(Length = 2000, IsNullable = true)] 50 | public string? ErrorMessage { get; set; } 51 | 52 | /// 53 | /// Token使用量 54 | /// 55 | [SugarColumn(IsNullable = true)] 56 | public int? TokensUsed { get; set; } 57 | 58 | /// 59 | /// 执行耗时(毫秒) 60 | /// 61 | [SugarColumn(IsNullable = true)] 62 | public long? ExecutionTimeMs { get; set; } 63 | 64 | /// 65 | /// 开始时间 66 | /// 67 | [SugarColumn(IsNullable = false)] 68 | public DateTime StartTime { get; set; } = DateTime.Now; 69 | 70 | /// 71 | /// 结束时间 72 | /// 73 | [SugarColumn(IsNullable = true)] 74 | public DateTime? EndTime { get; set; } 75 | } 76 | } 77 | 78 | -------------------------------------------------------------------------------- /BlogAgent/wwwroot/js/main.js: -------------------------------------------------------------------------------- 1 | // 数据库聊天页面相关功能 2 | window.databaseChatFunctions = { 3 | // 滚动聊天容器到底部 4 | scrollChatToBottom: function (element) { 5 | if (!element) return; 6 | 7 | try { 8 | if (typeof element.scrollTo === 'function') { 9 | element.scrollTo({ 10 | top: element.scrollHeight, 11 | behavior: 'smooth' 12 | }); 13 | } else { 14 | element.scrollTop = element.scrollHeight; 15 | } 16 | } catch (e) { 17 | console.error('滚动失败:', e); 18 | } 19 | }, 20 | 21 | // 复制文本到剪贴板 22 | copyToClipboard: function (text) { 23 | if (!text) return Promise.reject('无文本可复制'); 24 | 25 | return navigator.clipboard.writeText(text) 26 | .then(() => true) 27 | .catch(err => { 28 | console.error('复制失败:', err); 29 | // 回退方案 30 | try { 31 | const textArea = document.createElement('textarea'); 32 | textArea.value = text; 33 | document.body.appendChild(textArea); 34 | textArea.focus(); 35 | textArea.select(); 36 | const successful = document.execCommand('copy'); 37 | document.body.removeChild(textArea); 38 | return successful; 39 | } catch (e) { 40 | console.error('回退复制失败:', e); 41 | return false; 42 | } 43 | }); 44 | }, 45 | 46 | // 确认函数已加载 47 | isLoaded: function() { 48 | return true; 49 | } 50 | }; 51 | 52 | // 确保函数已准备就绪 53 | console.log("数据库聊天功能已加载"); 54 | 55 | // 在Blazor启动后执行 56 | document.addEventListener('DOMContentLoaded', function() { 57 | // 防止功能未加载情况下的错误 58 | if (!window.databaseChatFunctions) { 59 | console.error("警告:数据库聊天功能未正确加载,请检查main.js文件"); 60 | // 提供基本功能以防止页面崩溃 61 | window.databaseChatFunctions = { 62 | scrollChatToBottom: function() { console.warn("滚动功能未加载"); }, 63 | copyToClipboard: function() { console.warn("复制功能未加载"); return Promise.resolve(false); }, 64 | isLoaded: function() { return false; } 65 | }; 66 | } 67 | }); 68 | -------------------------------------------------------------------------------- /BlogAgent.Domain/Common/Extensions/ServiceCollectionExtensions.cs: -------------------------------------------------------------------------------- 1 | using BlogAgent.Domain.Common.Options; 2 | using BlogAgent.Domain.Utils; 3 | using BlogAgent.Repositories.Demo; 4 | using Microsoft.Extensions.DependencyInjection; 5 | using SqlSugar; 6 | using System.Reflection; 7 | using System; 8 | 9 | namespace BlogAgent.Domain.Common.Extensions 10 | { 11 | public static class ServiceCollectionExtensions 12 | { 13 | /// 14 | /// 从程序集中加载类型并添加到容器中 15 | /// 16 | /// 容器 17 | /// 程序集集合 18 | /// 19 | public static IServiceCollection AddServicesFromAssemblies(this IServiceCollection services, params string[] assemblies) 20 | { 21 | Type attributeType = typeof(ServiceDescriptionAttribute); 22 | //var refAssembyNames = Assembly.GetExecutingAssembly().GetReferencedAssemblies(); 23 | foreach (var item in assemblies) 24 | { 25 | Assembly assembly = Assembly.Load(item); 26 | 27 | var types = assembly.GetTypes(); 28 | 29 | foreach (var classType in types) 30 | { 31 | if (!classType.IsAbstract && classType.IsClass && classType.IsDefined(attributeType, false)) 32 | { 33 | ServiceDescriptionAttribute serviceAttribute = (classType.GetCustomAttribute(attributeType) as ServiceDescriptionAttribute); 34 | switch (serviceAttribute.Lifetime) 35 | { 36 | case ServiceLifetime.Scoped: 37 | services.AddScoped(serviceAttribute.ServiceType, classType); 38 | break; 39 | 40 | case ServiceLifetime.Singleton: 41 | services.AddSingleton(serviceAttribute.ServiceType, classType); 42 | break; 43 | 44 | case ServiceLifetime.Transient: 45 | services.AddTransient(serviceAttribute.ServiceType, classType); 46 | break; 47 | } 48 | } 49 | } 50 | } 51 | return services; 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /BlogAgent.Domain/Domain/Model/BlogTask.cs: -------------------------------------------------------------------------------- 1 | using SqlSugar; 2 | using BlogAgent.Domain.Domain.Enum; 3 | 4 | namespace BlogAgent.Domain.Domain.Model 5 | { 6 | /// 7 | /// 博客任务实体 8 | /// 9 | [SugarTable("blog_task")] 10 | public class BlogTask 11 | { 12 | /// 13 | /// 主键ID 14 | /// 15 | [SugarColumn(IsPrimaryKey = true, IsIdentity = true)] 16 | public int Id { get; set; } /// 17 | /// 博客主题 18 | /// 19 | [SugarColumn(Length = 500, IsNullable = false)] 20 | public string Topic { get; set; } = string.Empty; 21 | 22 | /// 23 | /// 参考资料内容 24 | /// 25 | [SugarColumn(ColumnDataType = "text", IsNullable = true)] 26 | public string? ReferenceContent { get; set; } 27 | 28 | /// 29 | /// 参考链接(多个用换行分隔) 30 | /// 31 | [SugarColumn(ColumnDataType = "text", IsNullable = true)] 32 | public string? ReferenceUrls { get; set; } 33 | 34 | /// 35 | /// 任务状态 36 | /// 37 | [SugarColumn(IsNullable = false)] 38 | public Domain.Enum.AgentTaskStatus Status { get; set; } = Domain.Enum.AgentTaskStatus.Created; 39 | 40 | /// 41 | /// 当前阶段 42 | /// 43 | [SugarColumn(Length = 50, IsNullable = true)] 44 | public string? CurrentStage { get; set; } 45 | 46 | /// 47 | /// 目标字数 48 | /// 49 | [SugarColumn(IsNullable = true)] 50 | public int? TargetWordCount { get; set; } 51 | 52 | /// 53 | /// 写作风格 54 | /// 55 | [SugarColumn(Length = 200, IsNullable = true)] 56 | public string? Style { get; set; } 57 | 58 | /// 59 | /// 目标读者 60 | /// 61 | [SugarColumn(Length = 200, IsNullable = true)] 62 | public string? TargetAudience { get; set; } 63 | 64 | /// 65 | /// 创建时间 66 | /// 67 | [SugarColumn(IsNullable = false)] 68 | public DateTime CreatedAt { get; set; } = DateTime.Now; 69 | 70 | /// 71 | /// 更新时间 72 | /// 73 | [SugarColumn(IsNullable = true)] 74 | public DateTime? UpdatedAt { get; set; } 75 | } 76 | } 77 | 78 | -------------------------------------------------------------------------------- /BlogAgent.Domain/Repositories/BlogTaskRepository.cs: -------------------------------------------------------------------------------- 1 | using BlogAgent.Domain.Common.Extensions; 2 | using BlogAgent.Domain.Domain.Model; 3 | using BlogAgent.Domain.Repositories.Base; 4 | using Microsoft.Extensions.DependencyInjection; 5 | using SqlSugar; 6 | using TaskStatus = BlogAgent.Domain.Domain.Enum.AgentTaskStatus; 7 | 8 | namespace BlogAgent.Domain.Repositories 9 | { 10 | /// 11 | /// 博客任务仓储 12 | /// 13 | [ServiceDescription(typeof(BlogTaskRepository), ServiceLifetime.Scoped)] 14 | public class BlogTaskRepository : Repository 15 | { 16 | public BlogTaskRepository(ISqlSugarClient db) : base(db) 17 | { 18 | } 19 | 20 | /// 21 | /// 根据状态查询任务列表 22 | /// 23 | public async Task> GetByStatusAsync(Domain.Enum.AgentTaskStatus status) 24 | { 25 | return await GetDB().Queryable() 26 | .Where(t => t.Status == status) 27 | .OrderByDescending(t => t.CreatedAt) 28 | .ToListAsync(); 29 | } 30 | 31 | /// 32 | /// 获取最近的任务列表 33 | /// 34 | public async Task> GetRecentTasksAsync(int count = 20) 35 | { 36 | return await GetDB().Queryable() 37 | .OrderByDescending(t => t.CreatedAt) 38 | .Take(count) 39 | .ToListAsync(); 40 | } 41 | 42 | /// 43 | /// 更新任务状态 44 | /// 45 | public async Task UpdateStatusAsync(int taskId, Domain.Enum.AgentTaskStatus status, string? currentStage = null) 46 | { 47 | var updateColumns = new List { nameof(BlogTask.Status), nameof(BlogTask.UpdatedAt) }; 48 | var task = new BlogTask 49 | { 50 | Id = taskId, 51 | Status = status, 52 | UpdatedAt = DateTime.Now 53 | }; 54 | 55 | if (!string.IsNullOrEmpty(currentStage)) 56 | { 57 | task.CurrentStage = currentStage; 58 | updateColumns.Add(nameof(BlogTask.CurrentStage)); 59 | } 60 | 61 | return await GetDB().Updateable(task) 62 | .UpdateColumns(updateColumns.ToArray()) 63 | .ExecuteCommandAsync() > 0; 64 | } 65 | } 66 | } 67 | 68 | -------------------------------------------------------------------------------- /BlogAgent.Domain/Domain/Model/BlogContent.cs: -------------------------------------------------------------------------------- 1 | using SqlSugar; 2 | 3 | namespace BlogAgent.Domain.Domain.Model 4 | { 5 | /// 6 | /// 博客内容实体 7 | /// 8 | [SugarTable("blog_content")] 9 | public class BlogContent 10 | { 11 | /// 12 | /// 主键ID 13 | /// 14 | [SugarColumn(IsPrimaryKey = true, IsIdentity = true)] 15 | public int Id { get; set; } 16 | 17 | /// 18 | /// 关联的任务ID 19 | /// 20 | [SugarColumn(IsNullable = false)] 21 | public int TaskId { get; set; } /// 22 | /// 博客标题 23 | /// 24 | [SugarColumn(Length = 500, IsNullable = false)] 25 | public string Title { get; set; } = string.Empty; 26 | 27 | /// 28 | /// 博客正文(Markdown格式) 29 | /// 30 | [SugarColumn(ColumnDataType = "text", IsNullable = false)] 31 | public string Content { get; set; } = string.Empty; 32 | 33 | /// 34 | /// 摘要 35 | /// 36 | [SugarColumn(Length = 1000, IsNullable = true)] 37 | public string? Summary { get; set; } 38 | 39 | /// 40 | /// 标签(逗号分隔) 41 | /// 42 | [SugarColumn(Length = 500, IsNullable = true)] 43 | public string? Tags { get; set; } 44 | 45 | /// 46 | /// 字数统计 47 | /// 48 | [SugarColumn(IsNullable = false)] 49 | public int WordCount { get; set; } 50 | 51 | /// 52 | /// 资料摘要(ResearcherAgent的输出) 53 | /// 54 | [SugarColumn(ColumnDataType = "text", IsNullable = true)] 55 | public string? ResearchSummary { get; set; } 56 | 57 | /// 58 | /// 是否已发布 59 | /// 60 | [SugarColumn(IsNullable = false)] 61 | public bool IsPublished { get; set; } = false; 62 | 63 | /// 64 | /// 发布时间 65 | /// 66 | [SugarColumn(IsNullable = true)] 67 | public DateTime? PublishedAt { get; set; } 68 | 69 | /// 70 | /// 创建时间 71 | /// 72 | [SugarColumn(IsNullable = false)] 73 | public DateTime CreatedAt { get; set; } = DateTime.Now; 74 | 75 | /// 76 | /// 更新时间 77 | /// 78 | [SugarColumn(IsNullable = true)] 79 | public DateTime? UpdatedAt { get; set; } 80 | } 81 | } 82 | 83 | -------------------------------------------------------------------------------- /BlogAgent.Domain/Repositories/McpServerConfigRepository.cs: -------------------------------------------------------------------------------- 1 | using BlogAgent.Domain.Common.Extensions; 2 | using BlogAgent.Domain.Domain.Model; 3 | using BlogAgent.Domain.Repositories.Base; 4 | using Microsoft.Extensions.DependencyInjection; 5 | using SqlSugar; 6 | using System.Collections.Generic; 7 | using System.Threading.Tasks; 8 | 9 | namespace BlogAgent.Domain.Repositories 10 | { 11 | /// 12 | /// MCP服务器配置仓储 13 | /// 14 | [ServiceDescription(typeof(McpServerConfigRepository), ServiceLifetime.Scoped)] 15 | public class McpServerConfigRepository : Repository 16 | { 17 | public McpServerConfigRepository(ISqlSugarClient db) : base(db) 18 | { 19 | } 20 | 21 | /// 22 | /// 获取所有启用的MCP服务器配置 23 | /// 24 | public async Task> GetEnabledConfigsAsync() 25 | { 26 | return await GetDB().Queryable() 27 | .Where(c => c.IsEnabled) 28 | .OrderBy(c => c.Name) 29 | .ToListAsync(); 30 | } 31 | 32 | /// 33 | /// 根据名称查询配置 34 | /// 35 | public async Task GetByNameAsync(string name) 36 | { 37 | return await GetDB().Queryable() 38 | .Where(c => c.Name == name) 39 | .FirstAsync(); 40 | } 41 | 42 | /// 43 | /// 检查名称是否已存在 44 | /// 45 | public async Task ExistsNameAsync(string name, int? excludeId = null) 46 | { 47 | var query = GetDB().Queryable() 48 | .Where(c => c.Name == name); 49 | 50 | if (excludeId.HasValue) 51 | { 52 | query = query.Where(c => c.Id != excludeId.Value); 53 | } 54 | 55 | return await query.AnyAsync(); 56 | } 57 | 58 | /// 59 | /// 切换启用状态 60 | /// 61 | public async Task ToggleEnabledAsync(int id) 62 | { 63 | var config = await GetByIdAsync(id); 64 | if (config == null) return false; 65 | 66 | config.IsEnabled = !config.IsEnabled; 67 | config.UpdatedAt = System.DateTime.Now; 68 | 69 | return await GetDB().Updateable(config) 70 | .UpdateColumns(c => new { c.IsEnabled, c.UpdatedAt }) 71 | .ExecuteCommandAsync() > 0; 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /BlogAgent.Domain/Domain/Dto/McpServerConfigDto.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace BlogAgent.Domain.Domain.Dto 5 | { 6 | /// 7 | /// MCP服务器配置DTO 8 | /// 9 | public class McpServerConfigDto 10 | { 11 | public int Id { get; set; } 12 | public string Name { get; set; } = string.Empty; 13 | public string? Description { get; set; } 14 | public string TransportType { get; set; } = "stdio"; 15 | public string? Command { get; set; } 16 | public List? Arguments { get; set; } 17 | public string? ServerUrl { get; set; } 18 | public bool RequiresAuth { get; set; } = false; 19 | public string? OAuthClientId { get; set; } 20 | public string? OAuthRedirectUri { get; set; } 21 | public bool IsEnabled { get; set; } = true; 22 | public Dictionary? EnvironmentVariables { get; set; } 23 | public string? Remarks { get; set; } 24 | public DateTime CreatedAt { get; set; } 25 | public DateTime UpdatedAt { get; set; } 26 | } 27 | 28 | /// 29 | /// 创建/更新MCP服务器配置请求 30 | /// 31 | public class SaveMcpServerConfigRequest 32 | { 33 | public int? Id { get; set; } 34 | public string Name { get; set; } = string.Empty; 35 | public string? Description { get; set; } 36 | public string TransportType { get; set; } = "stdio"; 37 | public string? Command { get; set; } 38 | public List? Arguments { get; set; } 39 | public string? ServerUrl { get; set; } 40 | public bool RequiresAuth { get; set; } = false; 41 | public string? OAuthClientId { get; set; } 42 | public string? OAuthRedirectUri { get; set; } 43 | public bool IsEnabled { get; set; } = true; 44 | public Dictionary? EnvironmentVariables { get; set; } 45 | public string? Remarks { get; set; } 46 | } 47 | 48 | /// 49 | /// MCP工具信息 50 | /// 51 | public class McpToolInfo 52 | { 53 | public string Name { get; set; } = string.Empty; 54 | public string? Description { get; set; } 55 | public string ServerName { get; set; } = string.Empty; 56 | public int ServerId { get; set; } 57 | } 58 | 59 | /// 60 | /// MCP服务器测试结果 61 | /// 62 | public class McpServerTestResult 63 | { 64 | public bool Success { get; set; } 65 | public string Message { get; set; } = string.Empty; 66 | public List Tools { get; set; } = new(); 67 | public string? Error { get; set; } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /BlogAgent/.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /BlogAgent.Domain/Repositories/ReviewResultRepository.cs: -------------------------------------------------------------------------------- 1 | using BlogAgent.Domain.Common.Extensions; 2 | using BlogAgent.Domain.Domain.Model; 3 | using BlogAgent.Domain.Repositories.Base; 4 | using Microsoft.Extensions.DependencyInjection; 5 | using SqlSugar; 6 | 7 | namespace BlogAgent.Domain.Repositories 8 | { 9 | /// 10 | /// 审查结果仓储 11 | /// 12 | [ServiceDescription(typeof(ReviewResultRepository), ServiceLifetime.Scoped)] 13 | public class ReviewResultRepository : Repository 14 | { 15 | public ReviewResultRepository(ISqlSugarClient db) : base(db) 16 | { 17 | } 18 | 19 | /// 20 | /// 根据任务ID获取审查结果 21 | /// 22 | public async Task GetByTaskIdAsync(int taskId) 23 | { 24 | return await GetDB().Queryable() 25 | .Where(r => r.TaskId == taskId) 26 | .OrderByDescending(r => r.CreatedAt) 27 | .FirstAsync(); 28 | } 29 | 30 | /// 31 | /// 根据内容ID获取审查结果 32 | /// 33 | public async Task GetByContentIdAsync(int contentId) 34 | { 35 | return await GetDB().Queryable() 36 | .Where(r => r.ContentId == contentId) 37 | .OrderByDescending(r => r.CreatedAt) 38 | .FirstAsync(); 39 | } 40 | 41 | /// 42 | /// 获取平均评分统计 43 | /// 44 | public async Task> GetAverageScoresAsync() 45 | { 46 | var result = await GetDB().Queryable() 47 | .Select(r => new 48 | { 49 | r.OverallScore, 50 | r.AccuracyScore, 51 | r.LogicScore, 52 | r.OriginalityScore, 53 | r.FormattingScore 54 | }) 55 | .ToListAsync(); 56 | 57 | if (!result.Any()) 58 | return new Dictionary(); 59 | 60 | return new Dictionary 61 | { 62 | ["Overall"] = result.Average(r => r.OverallScore), 63 | ["Accuracy"] = result.Average(r => r.AccuracyScore), 64 | ["Logic"] = result.Average(r => r.LogicScore), 65 | ["Originality"] = result.Average(r => r.OriginalityScore), 66 | ["Formatting"] = result.Average(r => r.FormattingScore) 67 | }; 68 | } 69 | 70 | /// 71 | /// 获取通过率统计 72 | /// 73 | public async Task GetPassRateAsync() 74 | { 75 | var total = await GetDB().Queryable().CountAsync(); 76 | if (total == 0) return 0; 77 | 78 | var passedCount = await GetDB().Queryable() 79 | .Where(r => r.OverallScore >= 80) 80 | .CountAsync(); 81 | 82 | return (double)passedCount / total * 100; 83 | } 84 | } 85 | } 86 | 87 | -------------------------------------------------------------------------------- /BlogAgent.Domain/Domain/Model/ReviewResult.cs: -------------------------------------------------------------------------------- 1 | using SqlSugar; 2 | 3 | namespace BlogAgent.Domain.Domain.Model 4 | { 5 | /// 6 | /// 审查结果实体 7 | /// 8 | [SugarTable("review_result")] 9 | public class ReviewResult 10 | { 11 | /// 12 | /// 主键ID 13 | /// 14 | [SugarColumn(IsPrimaryKey = true, IsIdentity = true)] 15 | public int Id { get; set; } 16 | 17 | /// 18 | /// 关联的任务ID 19 | /// 20 | [SugarColumn(IsNullable = false)] 21 | public int TaskId { get; set; } 22 | 23 | /// 24 | /// 关联的内容ID 25 | /// 26 | [SugarColumn(IsNullable = false)] 27 | public int ContentId { get; set; } /// 28 | /// 综合评分(0-100) 29 | /// 30 | [SugarColumn(IsNullable = false)] 31 | public int OverallScore { get; set; } 32 | 33 | /// 34 | /// 准确性评分(0-40) 35 | /// 36 | [SugarColumn(IsNullable = false)] 37 | public int AccuracyScore { get; set; } 38 | 39 | /// 40 | /// 准确性问题列表(JSON格式) 41 | /// 42 | [SugarColumn(ColumnDataType = "text", IsNullable = true)] 43 | public string? AccuracyIssues { get; set; } 44 | 45 | /// 46 | /// 逻辑性评分(0-30) 47 | /// 48 | [SugarColumn(IsNullable = false)] 49 | public int LogicScore { get; set; } 50 | 51 | /// 52 | /// 逻辑性问题列表(JSON格式) 53 | /// 54 | [SugarColumn(ColumnDataType = "text", IsNullable = true)] 55 | public string? LogicIssues { get; set; } 56 | 57 | /// 58 | /// 原创性评分(0-20) 59 | /// 60 | [SugarColumn(IsNullable = false)] 61 | public int OriginalityScore { get; set; } 62 | 63 | /// 64 | /// 原创性问题列表(JSON格式) 65 | /// 66 | [SugarColumn(ColumnDataType = "text", IsNullable = true)] 67 | public string? OriginalityIssues { get; set; } 68 | 69 | /// 70 | /// 规范性评分(0-10) 71 | /// 72 | [SugarColumn(IsNullable = false)] 73 | public int FormattingScore { get; set; } 74 | 75 | /// 76 | /// 规范性问题列表(JSON格式) 77 | /// 78 | [SugarColumn(ColumnDataType = "text", IsNullable = true)] 79 | public string? FormattingIssues { get; set; } 80 | 81 | /// 82 | /// 审查建议(通过/需修改/不通过) 83 | /// 84 | [SugarColumn(Length = 50, IsNullable = false)] 85 | public string Recommendation { get; set; } = string.Empty; 86 | 87 | /// 88 | /// 审查总结 89 | /// 90 | [SugarColumn(ColumnDataType = "text", IsNullable = true)] 91 | public string? Summary { get; set; } 92 | 93 | /// 94 | /// 创建时间 95 | /// 96 | [SugarColumn(IsNullable = false)] 97 | public DateTime CreatedAt { get; set; } = DateTime.Now; 98 | } 99 | } 100 | 101 | -------------------------------------------------------------------------------- /BlogAgent/Program.cs: -------------------------------------------------------------------------------- 1 | using AntDesign.ProLayout; 2 | using Microsoft.AspNetCore.Components; 3 | using BlogAgent.Domain.Common.Extensions; 4 | using BlogAgent.Domain.Common.Options; 5 | using BlogAgent.Domain.Repositories.Base; 6 | using Serilog; 7 | using SqlSugar; 8 | using Log = Serilog.Log; 9 | 10 | var builder = WebApplication.CreateBuilder(args); 11 | 12 | // Add services to the container. 13 | builder.Services.AddRazorPages(); 14 | builder.Services.AddServerSideBlazor(); 15 | builder.Services.AddAntDesign(); 16 | builder.Services.AddScoped(sp => new HttpClient 17 | { 18 | BaseAddress = new Uri(sp.GetService()!.BaseUri) 19 | }); 20 | builder.Services.Configure(builder.Configuration.GetSection("ProSettings")); 21 | 22 | // 添加内存缓存服务 23 | builder.Services.AddMemoryCache(); 24 | 25 | // 添加 HttpClient 工厂(用于 Web 内容抓取) 26 | builder.Services.AddHttpClient("WebContentFetcher", client => 27 | { 28 | client.Timeout = TimeSpan.FromSeconds(30); 29 | client.DefaultRequestHeaders.Add("User-Agent", "BlogAgent/1.0 (Content Fetcher)"); 30 | }); 31 | 32 | builder.Services.AddControllers(); 33 | // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle 34 | builder.Services.AddEndpointsApiExplorer(); 35 | builder.Services.AddSwaggerGen(); 36 | 37 | // 配置数据库连接选项 38 | builder.Configuration.GetSection("DBConnection").Get(); 39 | builder.Configuration.GetSection("OpenAI").Get(); 40 | 41 | // 注册 SqlSugar 42 | builder.Services.AddScoped(sp => SqlSugarHelper.SqlScope()); 43 | 44 | builder.Services.AddServicesFromAssemblies("BlogAgent", "BlogAgent.Domain"); 45 | 46 | 47 | // 添加响应压缩服务 48 | builder.Services.AddResponseCompression(options => 49 | { 50 | options.EnableForHttps = true; 51 | options.Providers.Add(); 52 | options.Providers.Add(); 53 | options.MimeTypes = Microsoft.AspNetCore.ResponseCompression.ResponseCompressionDefaults.MimeTypes.Concat( 54 | new[] { "application/javascript", "text/css", "text/javascript", "application/json", "text/html" }); 55 | }); 56 | 57 | // 添加 CORS 服务 58 | builder.Services.AddCors(options => 59 | { 60 | options.AddPolicy("Any", builder => 61 | { 62 | builder.AllowAnyOrigin() 63 | .AllowAnyMethod() 64 | .AllowAnyHeader(); 65 | }); 66 | }); 67 | 68 | // 配置 Serilog 静态日志器 69 | Log.Logger = new LoggerConfiguration() 70 | .ReadFrom.Configuration(builder.Configuration) 71 | .WriteTo.Console() 72 | .CreateLogger(); 73 | 74 | // 将 Serilog 集成到 ASP.NET Core 日志系统 75 | builder.Logging.ClearProviders(); 76 | builder.Logging.AddSerilog(Log.Logger); 77 | 78 | 79 | 80 | var app = builder.Build(); 81 | 82 | // Configure the HTTP request pipeline. 83 | if (app.Environment.IsDevelopment()) 84 | { 85 | app.UseSwagger(); 86 | app.UseSwaggerUI(); 87 | } 88 | app.UseStaticFiles(); 89 | 90 | app.UseRouting(); 91 | 92 | app.MapBlazorHub(); 93 | 94 | app.MapFallbackToPage("/_Host"); 95 | 96 | app.UseAuthorization(); 97 | 98 | app.MapControllers(); 99 | 100 | app.CodeFirst(); 101 | 102 | app.Run(); 103 | -------------------------------------------------------------------------------- /BlogAgent.Domain/Services/Agents/WriterAgent.cs: -------------------------------------------------------------------------------- 1 | using BlogAgent.Domain.Services.Agents.Base; 2 | using BlogAgent.Domain.Common.Extensions; 3 | using BlogAgent.Domain.Domain.Dto; 4 | using BlogAgent.Domain.Domain.Enum; 5 | using BlogAgent.Domain.Repositories; 6 | using Microsoft.Extensions.Logging; 7 | 8 | namespace BlogAgent.Domain.Services.Agents 9 | { 10 | /// 11 | /// 博客撰写专家Agent 12 | /// 13 | [ServiceDescription(typeof(WriterAgent), Microsoft.Extensions.DependencyInjection.ServiceLifetime.Scoped)] 14 | public class WriterAgent : BaseAgentService 15 | { 16 | public override string AgentName => "博客撰写专家"; 17 | 18 | public override AgentType AgentType => AgentType.Writer; 19 | 20 | protected override string Instructions => @"你是一位资深技术博客作家,擅长将技术内容转化为通俗易懂的文章。 21 | 22 | **任务:** 23 | 1. 基于提供的资料摘要撰写技术博客 24 | 2. 确保逻辑清晰、层次分明 25 | 3. 适当添加代码示例和实战案例 26 | 4. 语言专业但通俗易懂 27 | 28 | **文章结构(严格按照以下格式):** 29 | 30 | # 标题 31 | > 引言(一段吸引读者的导语,阐明文章价值) 32 | 33 | ## 一、背景介绍 34 | [技术背景、为什么需要这个技术、解决什么问题] 35 | 36 | ## 二、核心概念 37 | [关键概念详细解释,必要时配图说明] 38 | 39 | ## 三、实战应用 40 | [代码示例和使用场景] 41 | 42 | ### 3.1 场景一 43 | ```csharp 44 | // 代码示例 45 | ``` 46 | 47 | ### 3.2 场景二 48 | ```csharp 49 | // 代码示例 50 | ``` 51 | 52 | ## 四、最佳实践 53 | [经验总结、注意事项、常见陷阱] 54 | 55 | ## 五、总结 56 | [全文总结、技术展望、延伸阅读建议] 57 | 58 | **质量标准:** 59 | - 字数不少于1500字(根据用户要求调整) 60 | - 代码示例使用```代码块,标注语言类型 61 | - 避免空洞的套话和无意义的形容词 62 | - 逻辑流畅,前后呼应 63 | - 专业术语首次出现时给予解释 64 | - 适当使用列表、表格等提升可读性"; 65 | 66 | protected override int MaxTokens => 6000; // 增加Token限制以支持长文 67 | 68 | public WriterAgent( 69 | ILogger logger, 70 | AgentExecutionRepository executionRepository, 71 | McpConfigService mcpConfigService) 72 | : base(logger, executionRepository, mcpConfigService) 73 | { 74 | } 75 | 76 | /// 77 | /// 执行博客撰写任务 78 | /// 79 | /// 博客主题 80 | /// 资料摘要 81 | /// 写作要求 82 | /// 任务ID 83 | /// 博客初稿 84 | public async Task WriteAsync( 85 | string topic, 86 | string researchSummary, 87 | WritingRequirements? requirements, 88 | int taskId) 89 | { 90 | var requirementsText = requirements != null 91 | ? $@" 92 | **写作要求:** 93 | - 目标字数: {requirements.TargetWordCount ?? 1500}字 94 | - 写作风格: {requirements.Style ?? "专业易懂"} 95 | - 目标读者: {requirements.TargetAudience ?? "中级开发者"}" 96 | : ""; 97 | 98 | var input = $@"**主题:** {topic} 99 | 100 | **资料摘要:** 101 | {researchSummary} 102 | {requirementsText} 103 | 104 | 请基于以上资料撰写一篇高质量的技术博客,严格按照规定的结构和格式输出。"; 105 | 106 | var output = await ExecuteAsync(input, taskId); 107 | 108 | return new DraftContentDto 109 | { 110 | Title = ExtractTitle(output), 111 | Content = output, 112 | WordCount = CountWords(output), 113 | GeneratedAt = DateTime.Now 114 | }; 115 | } 116 | } 117 | } 118 | 119 | -------------------------------------------------------------------------------- /BlogAgent.Domain/Domain/Model/McpServerConfig.cs: -------------------------------------------------------------------------------- 1 | using SqlSugar; 2 | using System; 3 | 4 | namespace BlogAgent.Domain.Domain.Model 5 | { 6 | /// 7 | /// MCP服务器配置模型 8 | /// 9 | [SugarTable("mcp_server_configs")] 10 | public class McpServerConfig 11 | { 12 | /// 13 | /// 主键ID 14 | /// 15 | [SugarColumn(IsPrimaryKey = true, IsIdentity = true)] 16 | public int Id { get; set; } 17 | 18 | /// 19 | /// 配置名称 20 | /// 21 | [SugarColumn(Length = 100, IsNullable = false)] 22 | public string Name { get; set; } = string.Empty; 23 | 24 | /// 25 | /// 描述 26 | /// 27 | [SugarColumn(Length = 500, IsNullable = true)] 28 | public string? Description { get; set; } 29 | 30 | /// 31 | /// 传输类型: stdio | http 32 | /// 33 | [SugarColumn(Length = 20, IsNullable = false)] 34 | public string TransportType { get; set; } = "stdio"; 35 | 36 | /// 37 | /// Stdio模式: 命令 (如: npx, node, python) 38 | /// 39 | [SugarColumn(Length = 200, IsNullable = true)] 40 | public string? Command { get; set; } 41 | 42 | /// 43 | /// Stdio模式: 命令参数 (JSON数组格式, 如: ["-y", "@modelcontextprotocol/server-github"]) 44 | /// 45 | [SugarColumn(ColumnDataType = "text", IsNullable = true)] 46 | public string? Arguments { get; set; } 47 | 48 | /// 49 | /// Http模式: 服务器URL 50 | /// 51 | [SugarColumn(Length = 500, IsNullable = true)] 52 | public string? ServerUrl { get; set; } 53 | 54 | /// 55 | /// 是否需要OAuth认证 56 | /// 57 | [SugarColumn(IsNullable = false)] 58 | public bool RequiresAuth { get; set; } = false; 59 | 60 | /// 61 | /// OAuth客户端ID 62 | /// 63 | [SugarColumn(Length = 200, IsNullable = true)] 64 | public string? OAuthClientId { get; set; } 65 | 66 | /// 67 | /// OAuth重定向URI 68 | /// 69 | [SugarColumn(Length = 500, IsNullable = true)] 70 | public string? OAuthRedirectUri { get; set; } 71 | 72 | /// 73 | /// 是否启用 74 | /// 75 | [SugarColumn(IsNullable = false)] 76 | public bool IsEnabled { get; set; } = true; 77 | 78 | /// 79 | /// 创建时间 80 | /// 81 | [SugarColumn(IsNullable = false)] 82 | public DateTime CreatedAt { get; set; } = DateTime.Now; 83 | 84 | /// 85 | /// 更新时间 86 | /// 87 | [SugarColumn(IsNullable = false)] 88 | public DateTime UpdatedAt { get; set; } = DateTime.Now; 89 | 90 | /// 91 | /// 环境变量配置 (JSON格式) 92 | /// 93 | [SugarColumn(ColumnDataType = "text", IsNullable = true)] 94 | public string? EnvironmentVariables { get; set; } 95 | 96 | /// 97 | /// 备注 98 | /// 99 | [SugarColumn(Length = 1000, IsNullable = true)] 100 | public string? Remarks { get; set; } 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /BlogAgent.Domain/Utils/OpenAIHttpClientHandler.cs: -------------------------------------------------------------------------------- 1 | using BlogAgent.Domain.Common.Options; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Text.RegularExpressions; 7 | using System.Threading.Tasks; 8 | 9 | namespace BlogAgent.Domain.Utils 10 | { 11 | internal class OpenAIHttpClientHandler : HttpClientHandler 12 | { 13 | 14 | protected override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) 15 | { 16 | UriBuilder uriBuilder; 17 | Regex regex = new Regex(@"(https?)://([^/:]+)(:\d+)?/(.*)"); 18 | Match match = regex.Match(OpenAIOption.EndPoint); 19 | 20 | var mediaType = request.Content.Headers.ContentType.MediaType; 21 | string requestBody = (await request.Content.ReadAsStringAsync()).Unescape(); 22 | var uncaseBody = new StringContent(requestBody, Encoding.UTF8, mediaType); 23 | request.Content = uncaseBody; 24 | 25 | if (Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") == "Development" && request.Content != null) 26 | { 27 | //便于调试查看请求prompt 28 | Console.WriteLine(requestBody); 29 | } 30 | if (match.Success) 31 | { 32 | string xieyi = match.Groups[1].Value; 33 | string host = match.Groups[2].Value; 34 | string port = match.Groups[3].Value; // 可选的端口号 35 | string route = match.Groups[4].Value; 36 | // 如果port不为空,它将包含冒号,所以你可能需要去除它 37 | port = string.IsNullOrEmpty(port) ? port : port.Substring(1); 38 | // 拼接host和端口号 39 | var hostnew = string.IsNullOrEmpty(port) ? host : $"{host}:{port}"; 40 | 41 | switch (request.RequestUri.LocalPath) 42 | { 43 | case "/v1/chat/completions": 44 | //替换代理 45 | uriBuilder = new UriBuilder(request.RequestUri) 46 | { 47 | // 这里是你要修改的 URL 48 | Scheme = $"{xieyi}://{hostnew}/", 49 | Host = host, 50 | Path = route + "v1/chat/completions", 51 | }; 52 | if (port.ConvertToInt32() != 0) 53 | { 54 | uriBuilder.Port = port.ConvertToInt32(); 55 | } 56 | 57 | request.RequestUri = uriBuilder.Uri; 58 | 59 | break; 60 | case "/v1/embeddings": 61 | uriBuilder = new UriBuilder(request.RequestUri) 62 | { 63 | // 这里是你要修改的 URL 64 | Scheme = $"{xieyi}://{host}/", 65 | Host = host, 66 | Path = route + "v1/embeddings", 67 | }; 68 | if (port.ConvertToInt32() != 0) 69 | { 70 | uriBuilder.Port = port.ConvertToInt32(); 71 | } 72 | request.RequestUri = uriBuilder.Uri; 73 | break; 74 | } 75 | } 76 | 77 | // 接着,调用基类的 SendAsync 方法将你的修改后的请求发出去 78 | HttpResponseMessage response = await base.SendAsync(request, cancellationToken); 79 | 80 | return response; 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /BlogAgent/wwwroot/assets/logo.svg: -------------------------------------------------------------------------------- 1 | Group 28 Copy 5Created with Sketch. -------------------------------------------------------------------------------- /BlogAgent.Domain/Repositories/Base/IRepository.cs: -------------------------------------------------------------------------------- 1 | 2 | using BlogAgent.Domain.Model; 3 | using SqlSugar; 4 | using System.Linq.Expressions; 5 | 6 | namespace BlogAgent.Domain.Repositories.Base 7 | { 8 | public interface IRepository 9 | { 10 | SqlSugarScope GetDB(); 11 | List GetList(); 12 | Task> GetListAsync(); 13 | List GetList(Expression> whereExpression); 14 | Task> GetListAsync(Expression> whereExpression); 15 | int Count(Expression> whereExpression); 16 | Task CountAsync(Expression> whereExpression); 17 | PageList GetPageList(Expression> whereExpression, PageModel page); 18 | PageList

GetPageList

(Expression> whereExpression, PageModel page); 19 | Task> GetPageListAsync(Expression> whereExpression, PageModel page); 20 | Task> GetPageListAsync

(Expression> whereExpression, PageModel page); 21 | PageList GetPageList(Expression> whereExpression, PageModel page, Expression> orderByExpression = null, OrderByType orderByType = OrderByType.Asc); 22 | Task> GetPageListAsync(Expression> whereExpression, PageModel page, Expression> orderByExpression = null, OrderByType orderByType = OrderByType.Asc); 23 | PageList

GetPageList

(Expression> whereExpression, PageModel page, Expression> orderByExpression = null, OrderByType orderByType = OrderByType.Asc); 24 | Task> GetPageListAsync

(Expression> whereExpression, PageModel page, Expression> orderByExpression = null, OrderByType orderByType = OrderByType.Asc); 25 | PageList GetPageList(List conditionalList, PageModel page); 26 | Task> GetPageListAsync(List conditionalList, PageModel page); 27 | PageList GetPageList(List conditionalList, PageModel page, Expression> orderByExpression = null, OrderByType orderByType = OrderByType.Asc); 28 | Task> GetPageListAsync(List conditionalList, PageModel page, Expression> orderByExpression = null, OrderByType orderByType = OrderByType.Asc); 29 | T GetById(dynamic id); 30 | Task GetByIdAsync(dynamic id); 31 | T GetSingle(Expression> whereExpression); 32 | Task GetSingleAsync(Expression> whereExpression); 33 | T GetFirst(Expression> whereExpression); 34 | Task GetFirstAsync(Expression> whereExpression); 35 | bool Insert(T obj); 36 | Task InsertAsync(T obj); 37 | bool InsertRange(List objs); 38 | Task InsertRangeAsync(List objs); 39 | int InsertReturnIdentity(T obj); 40 | Task InsertReturnIdentityAsync(T obj); 41 | long InsertReturnBigIdentity(T obj); 42 | Task InsertReturnBigIdentityAsync(T obj); 43 | bool DeleteByIds(dynamic[] ids); 44 | Task DeleteByIdsAsync(dynamic[] ids); 45 | bool Delete(dynamic id); 46 | Task DeleteAsync(dynamic id); 47 | bool Delete(T obj); 48 | Task DeleteAsync(T obj); 49 | bool Delete(Expression> whereExpression); 50 | Task DeleteAsync(Expression> whereExpression); 51 | bool Update(T obj); 52 | Task UpdateAsync(T obj); 53 | bool UpdateRange(List objs); 54 | bool InsertOrUpdate(T obj); 55 | Task InsertOrUpdateAsync(T obj); 56 | Task UpdateRangeAsync(List objs); 57 | bool IsAny(Expression> whereExpression); 58 | Task IsAnyAsync(Expression> whereExpression); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /BlogAgent.Domain/Domain/Model/AgentOutputs.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace BlogAgent.Domain.Domain.Model 4 | { 5 | ///

6 | /// 资料收集结果的结构化输出 7 | /// 8 | public class ResearchOutput 9 | { 10 | [JsonPropertyName("topic_analysis")] 11 | public string TopicAnalysis { get; set; } = string.Empty; 12 | 13 | [JsonPropertyName("key_points")] 14 | public List KeyPoints { get; set; } = new(); 15 | 16 | [JsonPropertyName("technical_details")] 17 | public List TechnicalDetails { get; set; } = new(); 18 | 19 | [JsonPropertyName("code_examples")] 20 | public List CodeExamples { get; set; } = new(); 21 | 22 | [JsonPropertyName("references")] 23 | public List References { get; set; } = new(); 24 | } 25 | 26 | public class KeyPoint 27 | { 28 | [JsonPropertyName("importance")] 29 | public int Importance { get; set; } // 1-3, 3最高 30 | 31 | [JsonPropertyName("content")] 32 | public string Content { get; set; } = string.Empty; 33 | } 34 | 35 | public class TechnicalDetail 36 | { 37 | [JsonPropertyName("title")] 38 | public string Title { get; set; } = string.Empty; 39 | 40 | [JsonPropertyName("description")] 41 | public string Description { get; set; } = string.Empty; 42 | } 43 | 44 | public class CodeExample 45 | { 46 | [JsonPropertyName("language")] 47 | public string Language { get; set; } = string.Empty; 48 | 49 | [JsonPropertyName("code")] 50 | public string Code { get; set; } = string.Empty; 51 | 52 | [JsonPropertyName("description")] 53 | public string Description { get; set; } = string.Empty; 54 | } 55 | 56 | /// 57 | /// 博客初稿的结构化输出 58 | /// 59 | public class DraftOutput 60 | { 61 | [JsonPropertyName("title")] 62 | public string Title { get; set; } = string.Empty; 63 | 64 | [JsonPropertyName("introduction")] 65 | public string Introduction { get; set; } = string.Empty; 66 | 67 | [JsonPropertyName("sections")] 68 | public List Sections { get; set; } = new(); 69 | 70 | [JsonPropertyName("conclusion")] 71 | public string Conclusion { get; set; } = string.Empty; 72 | 73 | [JsonPropertyName("word_count")] 74 | public int WordCount { get; set; } 75 | } 76 | 77 | public class ContentSection 78 | { 79 | [JsonPropertyName("heading")] 80 | public string Heading { get; set; } = string.Empty; 81 | 82 | [JsonPropertyName("content")] 83 | public string Content { get; set; } = string.Empty; 84 | 85 | [JsonPropertyName("subsections")] 86 | public List? Subsections { get; set; } 87 | } 88 | 89 | /// 90 | /// 审查结果的结构化输出 91 | /// 92 | public class ReviewOutput 93 | { 94 | [JsonPropertyName("overall_score")] 95 | public int OverallScore { get; set; } 96 | 97 | [JsonPropertyName("accuracy")] 98 | public ScoreDetail Accuracy { get; set; } = new(); 99 | 100 | [JsonPropertyName("logic")] 101 | public ScoreDetail Logic { get; set; } = new(); 102 | 103 | [JsonPropertyName("originality")] 104 | public ScoreDetail Originality { get; set; } = new(); 105 | 106 | [JsonPropertyName("formatting")] 107 | public ScoreDetail Formatting { get; set; } = new(); 108 | 109 | [JsonPropertyName("recommendation")] 110 | public string Recommendation { get; set; } = string.Empty; // "通过", "需修改", "不通过" 111 | 112 | [JsonPropertyName("summary")] 113 | public string Summary { get; set; } = string.Empty; 114 | } 115 | 116 | public class ScoreDetail 117 | { 118 | [JsonPropertyName("score")] 119 | public int Score { get; set; } 120 | 121 | [JsonPropertyName("issues")] 122 | public List Issues { get; set; } = new(); 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /BlogAgent.Domain/Services/Agents/ReviewerAgent.cs: -------------------------------------------------------------------------------- 1 | using BlogAgent.Domain.Services.Agents.Base; 2 | using BlogAgent.Domain.Common.Extensions; 3 | using BlogAgent.Domain.Domain.Dto; 4 | using BlogAgent.Domain.Domain.Enum; 5 | using BlogAgent.Domain.Repositories; 6 | using Microsoft.Extensions.Logging; 7 | 8 | namespace BlogAgent.Domain.Services.Agents 9 | { 10 | /// 11 | /// 质量审查专家Agent 12 | /// 13 | [ServiceDescription(typeof(ReviewerAgent), Microsoft.Extensions.DependencyInjection.ServiceLifetime.Scoped)] 14 | public class ReviewerAgent : BaseAgentService 15 | { 16 | public override string AgentName => "质量审查专家"; 17 | 18 | public override AgentType AgentType => AgentType.Reviewer; 19 | 20 | protected override string Instructions => @"你是一位严格的技术内容审查专家,负责对博客文章进行全面质量评估。 21 | 22 | **审查标准:** 23 | 24 | 1. **准确性(40分)**: 25 | - 技术概念定义是否准确 26 | - 代码示例是否正确可运行 27 | - 引用数据是否真实可靠 28 | - 是否包含过时或错误信息 29 | 30 | 2. **逻辑性(30分)**: 31 | - 文章结构是否清晰、层次分明 32 | - 论证是否充分、有理有据 33 | - 段落衔接是否自然流畅 34 | - 是否存在跳跃式思维或逻辑漏洞 35 | 36 | 3. **原创性(20分)**: 37 | - 是否有独特见解和深度分析 38 | - 是否是简单资料堆砌 39 | - 案例是否具有实战价值 40 | - 是否避免空洞套话 41 | 42 | 4. **规范性(10分)**: 43 | - Markdown格式是否规范 44 | - 代码块是否正确标注语言 45 | - 中英文标点是否符合规范 46 | - 专业术语使用是否统一 47 | 48 | **输出格式(严格按照JSON格式,不要添加任何其他文字):** 49 | 50 | ```json 51 | { 52 | ""overallScore"": 85, 53 | ""accuracy"": { 54 | ""score"": 38, 55 | ""issues"": [""问题描述1"", ""问题描述2""] 56 | }, 57 | ""logic"": { 58 | ""score"": 25, 59 | ""issues"": [""问题描述1""] 60 | }, 61 | ""originality"": { 62 | ""score"": 18, 63 | ""issues"": [] 64 | }, 65 | ""formatting"": { 66 | ""score"": 9, 67 | ""issues"": [""问题描述1""] 68 | }, 69 | ""recommendation"": ""通过"", 70 | ""summary"": ""总体评价和具体修改建议"" 71 | } 72 | ``` 73 | 74 | **评分规则:** 75 | - 总分 ≥ 80分: recommendation为""通过"" 76 | - 70 ≤ 总分 < 80: recommendation为""需修改"" 77 | - 总分 < 70: recommendation为""不通过"" 78 | 79 | **注意事项:** 80 | - 评分要客观公正,不要过于严苛或宽松 81 | - issues数组中的每个问题要具体明确,指出位置 82 | - summary要给出可操作的修改建议 83 | - 必须严格按照JSON格式输出,不要有多余文字"; 84 | 85 | protected override float Temperature => 0.3f; // 降低温度以提高输出稳定性 86 | 87 | public ReviewerAgent( 88 | ILogger logger, 89 | AgentExecutionRepository executionRepository, 90 | McpConfigService mcpConfigService) 91 | : base(logger, executionRepository, mcpConfigService) 92 | { 93 | } 94 | 95 | /// 96 | /// 执行质量审查任务 97 | /// 98 | /// 博客标题 99 | /// 博客内容 100 | /// 任务ID 101 | /// 审查结果 102 | public async Task ReviewAsync(string title, string content, int taskId) 103 | { 104 | var input = $@"请审查以下博客文章: 105 | 106 | **标题:** {title} 107 | 108 | **内容:** 109 | {content} 110 | 111 | 请严格按照JSON格式输出审查结果,不要添加任何解释性文字。"; 112 | 113 | var output = await ExecuteAsync(input, taskId); 114 | 115 | // 解析JSON响应 116 | var result = ParseJsonResponse(output); 117 | 118 | if (result == null) 119 | { 120 | _logger.LogWarning($"[{AgentName}] JSON解析失败,返回默认审查结果"); 121 | 122 | // 返回默认的低分结果 123 | return new ReviewResultDto 124 | { 125 | OverallScore = 50, 126 | Accuracy = new DimensionScore { Score = 20, Issues = new List { "AI响应格式错误,无法准确评估" } }, 127 | Logic = new DimensionScore { Score = 15, Issues = new List() }, 128 | Originality = new DimensionScore { Score = 10, Issues = new List() }, 129 | Formatting = new DimensionScore { Score = 5, Issues = new List() }, 130 | Recommendation = "需修改", 131 | Summary = "审查结果解析失败,建议人工检查文章质量" 132 | }; 133 | } 134 | 135 | return result; 136 | } 137 | } 138 | } 139 | 140 | -------------------------------------------------------------------------------- /BlogAgent/Components/GlobalHeader/RightContent.razor.cs: -------------------------------------------------------------------------------- 1 | using AntDesign; 2 | using AntDesign.ProLayout; 3 | using Microsoft.AspNetCore.Components; 4 | using BlogAgent.Models; 5 | 6 | namespace BlogAgent.Components 7 | { 8 | public partial class RightContent 9 | { 10 | private CurrentUser _currentUser = new CurrentUser(); 11 | private NoticeIconData[] _notifications = { }; 12 | private NoticeIconData[] _messages = { }; 13 | private NoticeIconData[] _events = { }; 14 | private int _count = 0; 15 | 16 | private List> DefaultOptions { get; set; } = new List> 17 | { 18 | new AutoCompleteDataItem 19 | { 20 | Label = "umi ui", 21 | Value = "umi ui" 22 | }, 23 | new AutoCompleteDataItem 24 | { 25 | Label = "Pro Table", 26 | Value = "Pro Table" 27 | }, 28 | new AutoCompleteDataItem 29 | { 30 | Label = "Pro Layout", 31 | Value = "Pro Layout" 32 | } 33 | }; 34 | 35 | public AvatarMenuItem[] AvatarMenuItems { get; set; } = new AvatarMenuItem[] 36 | { 37 | new() { Key = "center", IconType = "user", Option = "个人中心"}, 38 | new() { Key = "setting", IconType = "setting", Option = "个人设置"}, 39 | new() { IsDivider = true }, 40 | new() { Key = "logout", IconType = "logout", Option = "退出登录"} 41 | }; 42 | 43 | [Inject] protected NavigationManager NavigationManager { get; set; } 44 | 45 | [Inject] protected MessageService MessageService { get; set; } 46 | 47 | protected override async Task OnInitializedAsync() 48 | { 49 | await base.OnInitializedAsync(); 50 | SetClassMap(); 51 | 52 | } 53 | 54 | /// 55 | /// 设置组件的CSS类映射 56 | /// 57 | protected void SetClassMap() 58 | { 59 | ClassMapper 60 | .Clear() 61 | .Add("right"); 62 | } 63 | 64 | /// 65 | /// 处理用户菜单项选择事件 66 | /// 67 | /// 选中的菜单项 68 | public void HandleSelectUser(MenuItem item) 69 | { 70 | switch (item.Key) 71 | { 72 | case "center": 73 | NavigationManager.NavigateTo("/account/center"); 74 | break; 75 | case "setting": 76 | NavigationManager.NavigateTo("/account/settings"); 77 | break; 78 | case "logout": 79 | NavigationManager.NavigateTo("/user/login"); 80 | break; 81 | } 82 | } 83 | 84 | /// 85 | /// 处理语言选择菜单项事件 86 | /// 87 | /// 选中的语言菜单项 88 | public void HandleSelectLang(MenuItem item) 89 | { 90 | } 91 | 92 | /// 93 | /// 处理清空通知、消息或事件列表的事件 94 | /// 95 | /// 要清空的项目类型(notification/message/event) 96 | /// 异步任务 97 | public async Task HandleClear(string key) 98 | { 99 | switch (key) 100 | { 101 | case "notification": 102 | _notifications = new NoticeIconData[] { }; 103 | break; 104 | case "message": 105 | _messages = new NoticeIconData[] { }; 106 | break; 107 | case "event": 108 | _events = new NoticeIconData[] { }; 109 | break; 110 | } 111 | MessageService.Success($"清空了{key}"); 112 | } 113 | 114 | /// 115 | /// 处理查看更多的事件 116 | /// 117 | /// 要查看更多的项目类型 118 | /// 异步任务 119 | public async Task HandleViewMore(string key) 120 | { 121 | MessageService.Info("Click on view more"); 122 | } 123 | } 124 | } -------------------------------------------------------------------------------- /BlogAgent/wwwroot/css/site.css: -------------------------------------------------------------------------------- 1 | /* stylelint-disable at-rule-empty-line-before,at-rule-name-space-after,at-rule-no-unknown */ 2 | /* stylelint-disable no-duplicate-selectors */ 3 | /* stylelint-disable */ 4 | /* stylelint-disable declaration-bang-space-before,no-duplicate-selectors,string-no-newline */ 5 | html, 6 | body, 7 | #root, 8 | #app, 9 | app { 10 | height: 100%; 11 | } 12 | .colorWeak { 13 | filter: invert(80%); 14 | } 15 | .ant-layout { 16 | min-height: 100vh; 17 | } 18 | canvas { 19 | display: block; 20 | } 21 | body { 22 | text-rendering: optimizeLegibility; 23 | -webkit-font-smoothing: antialiased; 24 | -moz-osx-font-smoothing: grayscale; 25 | } 26 | ul, 27 | ol { 28 | list-style: none; 29 | } 30 | .action { 31 | cursor: pointer; 32 | } 33 | @media (max-width: 480px) { 34 | .ant-table { 35 | width: 100%; 36 | overflow-x: auto; 37 | } 38 | .ant-table-thead > tr > th, 39 | .ant-table-tbody > tr > th, 40 | .ant-table-thead > tr > td, 41 | .ant-table-tbody > tr > td { 42 | white-space: pre; 43 | } 44 | .ant-table-thead > tr > th > span, 45 | .ant-table-tbody > tr > th > span, 46 | .ant-table-thead > tr > td > span, 47 | .ant-table-tbody > tr > td > span { 48 | display: block; 49 | } 50 | } 51 | @media screen and (-ms-high-contrast: active), (-ms-high-contrast: none) { 52 | body .ant-design-pro > .ant-layout { 53 | min-height: 100vh; 54 | } 55 | } 56 | 57 | 58 | 59 | ::-webkit-scrollbar { 60 | width: 1px; /* ���ù������Ŀ��� */ 61 | height: 8px; /* ����ˮƽ�������ĸ߶� */ 62 | } 63 | 64 | ::-webkit-scrollbar-track { 65 | background: #f5f5f5; /* ���ù���������ı���ɫ */ 66 | } 67 | 68 | ::-webkit-scrollbar-thumb { 69 | background: #c1c1c1; /* ���ù�����Ĵָ�ı���ɫ�����϶��IJ��֣� */ 70 | border-radius: 8px; /* ���ù�����Ĵָ��Բ�� */ 71 | } 72 | 73 | ::-webkit-scrollbar-thumb:hover { 74 | background: #555; /* �����ͣʱ������Ĵָ�ı���ɫ */ 75 | } 76 | 77 | /* ������Firefox */ 78 | * { 79 | scrollbar-width: thin; /* ���ù�����Ϊϸ�� */ 80 | scrollbar-color: #ccc #f1f1f1; /* ���ù�����Ĵָ�͹������ɫ */ 81 | } 82 | 83 | /* HTML预览独立工具栏样式 */ 84 | .html-preview-toolbar { 85 | position: absolute; 86 | top: 30px; 87 | right: 8px; 88 | z-index: 15; 89 | display: flex; 90 | align-items: center; 91 | gap: 4px; 92 | padding: 4px; 93 | background: rgba(255, 255, 255, 0.95); 94 | border-radius: 6px; 95 | box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15); 96 | backdrop-filter: blur(4px); 97 | border: 1px solid rgba(127, 127, 255, 0.2); 98 | } 99 | 100 | /* HTML预览按钮样式 */ 101 | .html-preview-btn { 102 | background: linear-gradient(135deg, #7F7FFF 0%, #9F9FFF 100%); 103 | color: white; 104 | border: none; 105 | border-radius: 4px; 106 | padding: 6px 12px; 107 | font-size: 12px; 108 | font-weight: 500; 109 | cursor: pointer; 110 | transition: all 0.3s ease; 111 | outline: none; 112 | box-shadow: 0 1px 3px rgba(127, 127, 255, 0.3); 113 | } 114 | 115 | .html-preview-btn:hover { 116 | background: linear-gradient(135deg, #6F6FFF 0%, #8F8FFF 100%); 117 | transform: translateY(-1px); 118 | box-shadow: 0 3px 12px rgba(127, 127, 255, 0.4); 119 | } 120 | 121 | .html-preview-btn:active { 122 | transform: translateY(0); 123 | box-shadow: 0 1px 4px rgba(127, 127, 255, 0.3); 124 | } 125 | 126 | .html-preview-btn:focus { 127 | box-shadow: 0 0 0 3px rgba(127, 127, 255, 0.3); 128 | } 129 | 130 | /* 代码块容器样式优化 */ 131 | pre[class*="language-"] { 132 | position: relative; 133 | } 134 | 135 | /* 原始toolbar样式保持不变 */ 136 | pre[class*="language-"] .toolbar { 137 | position: absolute; 138 | top: 8px; 139 | right: 8px; 140 | z-index: 10; 141 | display: flex; 142 | align-items: center; 143 | gap: 4px; 144 | padding: 4px; 145 | background: rgba(255, 255, 255, 0.9); 146 | border-radius: 4px; 147 | box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); 148 | } 149 | 150 | /* 响应式设计 */ 151 | @media (max-width: 768px) { 152 | .html-preview-btn { 153 | padding: 4px 8px; 154 | font-size: 11px; 155 | } 156 | 157 | .html-preview-toolbar { 158 | gap: 2px; 159 | padding: 2px; 160 | } 161 | 162 | /* 移动端时调整位置 */ 163 | .html-preview-toolbar { 164 | top: 30px; 165 | } 166 | } 167 | 168 | @media (max-width: 480px) { 169 | .html-preview-toolbar { 170 | position: static; 171 | margin-top: 8px; 172 | margin-bottom: 8px; 173 | justify-content: center; 174 | background: rgba(255, 255, 255, 0.98); 175 | } 176 | 177 | pre[class*="language-"] .toolbar ~ .html-preview-toolbar { 178 | top: auto; 179 | } 180 | } -------------------------------------------------------------------------------- /BlogAgent/Pages/Index.razor: -------------------------------------------------------------------------------- 1 | @namespace BlogAgent.Pages 2 | @page "/" 3 | @using BlogAgent.Domain.Services 4 | @using BlogAgent.Domain.Repositories 5 | @using BlogAgent.Domain.Domain.Enum 6 | @inject BlogService BlogService 7 | @inject ReviewResultRepository ReviewResultRepository 8 | @inject NavigationManager NavigationManager 9 | @implements IDisposable 10 | 11 | 12 | 13 | 基于Microsoft Agent Framework的多Agent协作博客生成系统 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 资料收集Agent 46 |

智能提取和整理参考资料,生成结构化摘要

47 |
48 | 49 | 博客撰写Agent 50 |

基于资料生成高质量技术博客,支持自定义风格

51 |
52 | 53 | 质量审查Agent 54 |

多维度评估文章质量,提供详细改进建议

55 |
56 | 57 | 工作流编排 58 |

半自动化流程,每阶段用户确认后继续

59 |
60 |
61 |
62 |
63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 78 | 79 | 80 | 83 | 84 | 85 | 86 | 87 |
88 | 89 | @code { 90 | private int totalTasks = 0; 91 | private int publishedCount = 0; 92 | private int averageScore = 0; 93 | private double passRate = 0; 94 | private bool _disposed = false; 95 | 96 | protected override async Task OnInitializedAsync() 97 | { 98 | try 99 | { 100 | var tasks = await BlogService.GetTaskListAsync(1000); 101 | 102 | if (_disposed) return; 103 | 104 | totalTasks = tasks.Count; 105 | publishedCount = tasks.Count(t => t.Status == AgentTaskStatus.Published); 106 | 107 | var scores = await ReviewResultRepository.GetAverageScoresAsync(); 108 | 109 | if (_disposed) return; 110 | 111 | if (scores.ContainsKey("Overall")) 112 | { 113 | averageScore = (int)scores["Overall"]; 114 | } 115 | 116 | passRate = await ReviewResultRepository.GetPassRateAsync(); 117 | passRate = Math.Round(passRate, 1); 118 | 119 | await SafeStateHasChangedAsync(); 120 | } 121 | catch 122 | { 123 | // 忽略错误,使用默认值 124 | } 125 | } 126 | 127 | private void NavigateToCreate() 128 | { 129 | NavigationManager.NavigateTo("/blog/create"); 130 | } 131 | 132 | private void NavigateToList() 133 | { 134 | NavigationManager.NavigateTo("/blog/list"); 135 | } 136 | 137 | private async Task SafeStateHasChangedAsync() 138 | { 139 | if (!_disposed) 140 | { 141 | try 142 | { 143 | await InvokeAsync(StateHasChanged); 144 | } 145 | catch (ObjectDisposedException) 146 | { 147 | // 组件已被销毁,忽略此异常 148 | } 149 | } 150 | } 151 | 152 | public void Dispose() 153 | { 154 | _disposed = true; 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /BlogAgent.Domain/Services/WebContentService.cs: -------------------------------------------------------------------------------- 1 | using BlogAgent.Domain.Common.Extensions; 2 | using Microsoft.Extensions.Logging; 3 | using System.Net; 4 | using System.Text; 5 | using System.Text.RegularExpressions; 6 | 7 | namespace BlogAgent.Domain.Services 8 | { 9 | /// 10 | /// Web 内容抓取服务 11 | /// 12 | [ServiceDescription(typeof(WebContentService), Microsoft.Extensions.DependencyInjection.ServiceLifetime.Scoped)] 13 | public class WebContentService 14 | { 15 | private readonly ILogger _logger; 16 | private readonly HttpClient _httpClient; 17 | 18 | public WebContentService(ILogger logger, IHttpClientFactory httpClientFactory) 19 | { 20 | _logger = logger; 21 | _httpClient = httpClientFactory.CreateClient("WebContentFetcher"); 22 | _httpClient.Timeout = TimeSpan.FromSeconds(30); 23 | _httpClient.DefaultRequestHeaders.Add("User-Agent", "BlogAgent/1.0 (Content Fetcher)"); 24 | } 25 | 26 | /// 27 | /// 抓取 URL 内容 28 | /// 29 | /// 目标 URL 30 | /// 清洗后的文本内容 31 | public async Task FetchUrlContentAsync(string url) 32 | { 33 | try 34 | { 35 | _logger.LogInformation($"开始抓取 URL: {url}"); 36 | 37 | var response = await _httpClient.GetAsync(url); 38 | response.EnsureSuccessStatusCode(); 39 | 40 | var content = await response.Content.ReadAsStringAsync(); 41 | var contentType = response.Content.Headers.ContentType?.MediaType ?? ""; 42 | 43 | // 根据内容类型处理 44 | string textContent; 45 | if (contentType.Contains("text/html")) 46 | { 47 | textContent = ExtractTextFromHtml(content); 48 | } 49 | else if (contentType.Contains("text/plain")) 50 | { 51 | textContent = content; 52 | } 53 | else if (contentType.Contains("application/json")) 54 | { 55 | textContent = content; 56 | } 57 | else 58 | { 59 | textContent = content; // 其他类型直接返回 60 | } 61 | 62 | _logger.LogInformation($"URL 抓取成功: {url}, 内容长度: {textContent.Length}"); 63 | return textContent; 64 | } 65 | catch (HttpRequestException ex) 66 | { 67 | _logger.LogError(ex, $"HTTP 请求失败: {url}"); 68 | return $"[无法访问 URL: {url}]\n错误: {ex.Message}"; 69 | } 70 | catch (TaskCanceledException ex) 71 | { 72 | _logger.LogError(ex, $"请求超时: {url}"); 73 | return $"[URL 访问超时: {url}]"; 74 | } 75 | catch (Exception ex) 76 | { 77 | _logger.LogError(ex, $"抓取 URL 失败: {url}"); 78 | return $"[URL 抓取失败: {url}]\n错误: {ex.Message}"; 79 | } 80 | } 81 | 82 | /// 83 | /// 批量抓取 URL 内容 84 | /// 85 | /// URL 列表 86 | /// 格式化的内容集合 87 | public async Task FetchMultipleUrlsAsync(IEnumerable urls) 88 | { 89 | var fetchedContents = new List(); 90 | 91 | foreach (var url in urls) 92 | { 93 | var trimmedUrl = url.Trim(); 94 | if (string.IsNullOrWhiteSpace(trimmedUrl)) 95 | continue; 96 | 97 | var content = await FetchUrlContentAsync(trimmedUrl); 98 | var formattedContent = $@" 99 | ================================================================================ 100 | 📄 来源: {trimmedUrl} 101 | ================================================================================ 102 | 103 | {content} 104 | 105 | "; 106 | fetchedContents.Add(formattedContent); 107 | } 108 | 109 | if (fetchedContents.Count == 0) 110 | { 111 | return "无可用的参考资料"; 112 | } 113 | 114 | return string.Join("\n", fetchedContents); 115 | } 116 | 117 | /// 118 | /// 从 HTML 中提取纯文本内容 119 | /// 120 | private string ExtractTextFromHtml(string html) 121 | { 122 | if (string.IsNullOrWhiteSpace(html)) 123 | return string.Empty; 124 | 125 | try 126 | { 127 | // 移除 script 和 style 标签 128 | html = Regex.Replace(html, @"]*?>.*?", "", RegexOptions.Singleline | RegexOptions.IgnoreCase); 129 | html = Regex.Replace(html, @"]*?>.*?", "", RegexOptions.Singleline | RegexOptions.IgnoreCase); 130 | 131 | // 保留代码块(pre, code)的换行 132 | html = Regex.Replace(html, @"]*?>(.*?)", "\n```\n$1\n```\n", RegexOptions.Singleline | RegexOptions.IgnoreCase); 133 | html = Regex.Replace(html, @"]*?>(.*?)", "`$1`", RegexOptions.Singleline | RegexOptions.IgnoreCase); 134 | 135 | // 将常见块级元素转换为换行 136 | html = Regex.Replace(html, @"<(p|div|br|h[1-6]|li|tr)[^>]*?>", "\n", RegexOptions.IgnoreCase); 137 | html = Regex.Replace(html, @"", "\n", RegexOptions.IgnoreCase); 138 | 139 | // 移除所有其他 HTML 标签 140 | html = Regex.Replace(html, @"<[^>]+>", ""); 141 | 142 | // 解码 HTML 实体 143 | html = WebUtility.HtmlDecode(html); 144 | 145 | // 清理多余空白 146 | html = Regex.Replace(html, @"[ \t]+", " "); // 多个空格/制表符 -> 单个空格 147 | html = Regex.Replace(html, @"\n\s*\n\s*\n+", "\n\n"); // 多个换行 -> 双换行 148 | html = html.Trim(); 149 | 150 | // 限制内容长度(避免过长) 151 | const int MaxLength = 50000; 152 | if (html.Length > MaxLength) 153 | { 154 | html = html.Substring(0, MaxLength) + "\n\n[内容过长,已截断...]"; 155 | } 156 | 157 | return html; 158 | } 159 | catch (Exception ex) 160 | { 161 | _logger.LogError(ex, "HTML 文本提取失败"); 162 | return html; // 失败时返回原始内容 163 | } 164 | } 165 | 166 | /// 167 | /// 验证 URL 格式 168 | /// 169 | public static bool IsValidUrl(string url) 170 | { 171 | if (string.IsNullOrWhiteSpace(url)) 172 | return false; 173 | 174 | return Uri.TryCreate(url, UriKind.Absolute, out var uriResult) 175 | && (uriResult.Scheme == Uri.UriSchemeHttp || uriResult.Scheme == Uri.UriSchemeHttps); 176 | } 177 | } 178 | } 179 | -------------------------------------------------------------------------------- /BlogAgent/Pages/Blog/List.razor: -------------------------------------------------------------------------------- 1 | @page "/blog/list" 2 | @using BlogAgent.Domain.Domain.Dto 3 | @using AgentTaskStatus = BlogAgent.Domain.Domain.Enum.AgentTaskStatus 4 | @using BlogAgent.Domain.Services 5 | @inject BlogService BlogService 6 | @inject NavigationManager Navigation 7 | @inject IMessageService Message 8 | @inject ModalService ModalService 9 | @implements IDisposable 10 | 11 | 12 | 13 | 14 | 15 | 18 | 19 | 20 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 34 | 35 | 36 | 37 | @{ 38 | var (color, text) = GetStatusDisplay(context.Status); 39 | } 40 | @text 41 | 42 | 43 | @if (!string.IsNullOrEmpty(context.CurrentStage)) 44 | { 45 | @GetStageText(context.CurrentStage) 46 | } 47 | else 48 | { 49 | - 50 | } 51 | 52 | 53 | @context.CreatedAt.ToString("yyyy-MM-dd HH:mm:ss") 54 | 55 | 56 | 57 | 58 | 61 | 62 | @if (context.Status != AgentTaskStatus.Published) 63 | { 64 | 65 | 68 | 69 | } 70 | 71 | 75 | 78 | 79 | 80 | 81 | 82 |
83 |
84 | 85 | @code { 86 | private List tasks = new(); 87 | private bool loading = false; 88 | private bool _disposed = false; 89 | 90 | protected override async Task OnInitializedAsync() 91 | { 92 | await LoadData(); 93 | } 94 | 95 | private async Task LoadData() 96 | { 97 | loading = true; 98 | await SafeStateHasChangedAsync(); 99 | 100 | try 101 | { 102 | tasks = await BlogService.GetTaskListAsync(50); 103 | 104 | if (_disposed) return; 105 | } 106 | catch (Exception ex) 107 | { 108 | if (!_disposed) 109 | { 110 | await InvokeAsync(() => Message.Error($"加载数据失败: {ex.Message}")); 111 | } 112 | } 113 | finally 114 | { 115 | if (!_disposed) 116 | { 117 | loading = false; 118 | await SafeStateHasChangedAsync(); 119 | } 120 | } 121 | } 122 | 123 | private async Task DeleteTask(int taskId) 124 | { 125 | try 126 | { 127 | var success = await BlogService.DeleteTaskAsync(taskId); 128 | 129 | if (_disposed) return; 130 | 131 | if (success) 132 | { 133 | await InvokeAsync(() => Message.Success("删除成功")); 134 | await LoadData(); 135 | } 136 | else 137 | { 138 | await InvokeAsync(() => Message.Error("删除失败")); 139 | } 140 | } 141 | catch (Exception ex) 142 | { 143 | if (!_disposed) 144 | { 145 | await InvokeAsync(() => Message.Error($"删除失败: {ex.Message}")); 146 | } 147 | } 148 | } 149 | 150 | private async Task SafeStateHasChangedAsync() 151 | { 152 | if (!_disposed) 153 | { 154 | try 155 | { 156 | await InvokeAsync(StateHasChanged); 157 | } 158 | catch (ObjectDisposedException) 159 | { 160 | // 组件已被销毁,忽略此异常 161 | } 162 | } 163 | } 164 | 165 | public void Dispose() 166 | { 167 | _disposed = true; 168 | } 169 | 170 | private (string color, string text) GetStatusDisplay(AgentTaskStatus status) 171 | { 172 | return status switch 173 | { 174 | AgentTaskStatus.Created => ("default", "已创建"), 175 | AgentTaskStatus.Researching => ("processing", "资料收集中"), 176 | AgentTaskStatus.ResearchCompleted => ("cyan", "资料收集完成"), 177 | AgentTaskStatus.Writing => ("processing", "撰写中"), 178 | AgentTaskStatus.WritingCompleted => ("blue", "撰写完成"), 179 | AgentTaskStatus.Reviewing => ("processing", "审查中"), 180 | AgentTaskStatus.ReviewCompleted => ("orange", "审查完成"), 181 | AgentTaskStatus.Published => ("success", "已发布"), 182 | AgentTaskStatus.Failed => ("error", "失败"), 183 | _ => ("default", "未知") 184 | }; 185 | } 186 | 187 | private string GetStageText(string stage) 188 | { 189 | return stage switch 190 | { 191 | "created" => "已创建", 192 | "research" => "资料收集", 193 | "research_completed" => "资料收集完成", 194 | "write" => "博客撰写", 195 | "write_completed" => "撰写完成", 196 | "review" => "质量审查", 197 | "review_completed" => "审查完成", 198 | "published" => "已发布", 199 | _ => stage 200 | }; 201 | } 202 | } 203 | 204 | -------------------------------------------------------------------------------- /BlogAgent/Pages/Blog/Detail.razor: -------------------------------------------------------------------------------- 1 | @page "/blog/detail/{TaskId:int}" 2 | @using BlogAgent.Domain.Domain.Model 3 | @using BlogAgent.Domain.Domain.Dto 4 | @using BlogAgent.Domain.Services 5 | @inject BlogService BlogService 6 | @inject NavigationManager Navigation 7 | @inject IMessageService Message 8 | 9 | 10 | 11 | 12 | @if (content != null && !content.IsPublished) 13 | { 14 | 15 | 18 | 19 | } 20 | 21 | 24 | 25 | @if (content != null) 26 | { 27 | 28 | 31 | 32 | } 33 | 34 | 35 | 36 | 37 | @if (loading) 38 | { 39 | 40 |
41 | 42 |
43 |
44 | } 45 | else if (content != null) 46 | { 47 | 48 | 49 | 50 | @content.Title 51 | 52 | 53 | @if (content.IsPublished) 54 | { 55 | 已发布 56 | } 57 | else 58 | { 59 | 草稿 60 | } 61 | 62 | 63 | @content.WordCount 字 64 | 65 | 66 | @content.CreatedAt.ToString("yyyy-MM-dd HH:mm:ss") 67 | 68 | 69 | @(content.PublishedAt?.ToString("yyyy-MM-dd HH:mm:ss") ?? "-") 70 | 71 | 72 | 73 | @if (reviewResult != null) 74 | { 75 | 76 | 77 | 80 | 81 | @reviewResult.Accuracy.Score / 40 82 | @reviewResult.Logic.Score / 30 83 | @reviewResult.Originality.Score / 20 84 | @reviewResult.Formatting.Score / 10 85 | 86 | 87 | @reviewResult.Recommendation 88 | 89 | 90 | @if (!string.IsNullOrEmpty(reviewResult.Summary)) 91 | { 92 | 93 | @reviewResult.Summary 94 | 95 | } 96 | 97 | } 98 | 99 | 100 | 博客内容 101 | 102 | 103 | 104 | 105 | 106 | 107 | } 108 | else 109 | { 110 | 111 | 112 | 115 | 116 | 117 | } 118 | 119 | @code { 120 | [Parameter] public int TaskId { get; set; } 121 | 122 | private bool loading = false; 123 | private bool publishing = false; 124 | private bool exporting = false; 125 | private BlogContent? content; 126 | private ReviewResultDto? reviewResult; 127 | 128 | protected override async Task OnInitializedAsync() 129 | { 130 | await LoadData(); 131 | } 132 | 133 | private async Task LoadData() 134 | { 135 | loading = true; 136 | try 137 | { 138 | content = await BlogService.GetContentAsync(TaskId); 139 | 140 | if (content != null) 141 | { 142 | reviewResult = await BlogService.GetReviewResultAsync(TaskId); 143 | } 144 | } 145 | catch (Exception ex) 146 | { 147 | Message.Error($"加载数据失败: {ex.Message}"); 148 | } 149 | finally 150 | { 151 | loading = false; 152 | } 153 | } 154 | 155 | private async Task PublishBlog() 156 | { 157 | publishing = true; 158 | try 159 | { 160 | var success = await BlogService.PublishBlogAsync(TaskId); 161 | 162 | if (success) 163 | { 164 | Message.Success("发布成功!"); 165 | await LoadData(); 166 | } 167 | else 168 | { 169 | Message.Error("发布失败"); 170 | } 171 | } 172 | catch (Exception ex) 173 | { 174 | Message.Error($"发布失败: {ex.Message}"); 175 | } 176 | finally 177 | { 178 | publishing = false; 179 | } 180 | } 181 | 182 | private async Task ExportMarkdown() 183 | { 184 | exporting = true; 185 | try 186 | { 187 | // 使用应用根目录的exports文件夹 188 | var exportPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "exports"); 189 | var filePath = await BlogService.ExportToMarkdownAsync(TaskId, exportPath); 190 | 191 | Message.Success($"导出成功: {Path.GetFileName(filePath)}"); 192 | } 193 | catch (Exception ex) 194 | { 195 | Message.Error($"导出失败: {ex.Message}"); 196 | } 197 | finally 198 | { 199 | exporting = false; 200 | } 201 | } 202 | } 203 | 204 | -------------------------------------------------------------------------------- /BlogAgent.Domain/Services/Agents/ResearcherAgent.cs: -------------------------------------------------------------------------------- 1 | using BlogAgent.Domain.Services.Agents.Base; 2 | using BlogAgent.Domain.Common.Extensions; 3 | using BlogAgent.Domain.Domain.Dto; 4 | using BlogAgent.Domain.Domain.Enum; 5 | using BlogAgent.Domain.Domain.Model; 6 | using BlogAgent.Domain.Repositories; 7 | using Microsoft.Extensions.Logging; 8 | using Microsoft.Extensions.AI; 9 | using System.Text.Json; 10 | using System.ComponentModel; 11 | 12 | namespace BlogAgent.Domain.Services.Agents 13 | { 14 | /// 15 | /// 资料收集专家Agent - 使用 Agent Framework 16 | /// 17 | [ServiceDescription(typeof(ResearcherAgent), Microsoft.Extensions.DependencyInjection.ServiceLifetime.Scoped)] 18 | public class ResearcherAgent : BaseAgentService 19 | { 20 | public override string AgentName => "资料收集专家"; 21 | 22 | public override AgentType AgentType => AgentType.Researcher; 23 | 24 | protected override string Instructions => @"你是一位专业的技术资料收集专家。 25 | 26 | **任务:** 27 | 1. 仔细阅读用户提供的主题和参考资料 28 | 2. 提取关键信息点(技术概念、代码示例、最佳实践、应用场景等) 29 | 3. 整理成结构化的JSON格式输出 30 | 31 | **输出要求:** 32 | - topic_analysis: 对主题的理解和定位,包括技术背景、适用场景、目标读者 33 | - key_points: 核心要点列表,每个要点包含重要程度(1-3,3最高)和内容 34 | - technical_details: 技术细节列表,每个包含标题和详细说明 35 | - code_examples: 代码示例列表(如果有),包含语言、代码和描述 36 | - references: 参考来源列表 37 | 38 | **质量要求:** 39 | - 信息准确,不添加未提供的内容 40 | - 结构清晰,层次分明 41 | - 提炼核心概念,避免冗余 42 | - 如果资料不足,明确指出缺失的部分"; 43 | 44 | // 使用结构化输出 45 | protected override ChatResponseFormat? ResponseFormat => 46 | ChatResponseFormat.ForJsonSchema(schemaName: "ResearchOutput"); 47 | 48 | // 添加工具函数 49 | protected override IEnumerable? Tools => new[] 50 | { 51 | Microsoft.Extensions.AI.AIFunctionFactory.Create(CountWordsInText), 52 | Microsoft.Extensions.AI.AIFunctionFactory.Create(ExtractCodeBlocks) 53 | }; 54 | 55 | public ResearcherAgent( 56 | ILogger logger, 57 | AgentExecutionRepository executionRepository, 58 | McpConfigService mcpConfigService) 59 | : base(logger, executionRepository, mcpConfigService) 60 | { 61 | } 62 | 63 | /// 64 | /// 执行资料收集任务 65 | /// 66 | /// 博客主题 67 | /// 参考资料 68 | /// 任务ID 69 | /// 资料收集结果 70 | public async Task ResearchAsync(string topic, string referenceContent, int taskId) 71 | { 72 | var input = $@"**主题:** {topic} 73 | 74 | **参考资料:** 75 | {referenceContent} 76 | 77 | 请按照指示整理资料,输出结构化的JSON格式数据。"; 78 | 79 | var output = await ExecuteAsync(input, taskId); 80 | 81 | // 解析结构化输出 82 | var researchOutput = JsonSerializer.Deserialize(output); 83 | 84 | if (researchOutput == null) 85 | { 86 | throw new InvalidOperationException("无法解析研究结果"); 87 | } 88 | 89 | // 转换为 Markdown 格式(保持向后兼容) 90 | var markdown = ConvertToMarkdown(researchOutput); 91 | 92 | return new ResearchResultDto 93 | { 94 | Summary = markdown, 95 | KeyPoints = researchOutput.KeyPoints.Select(kp => kp.Content).ToList(), 96 | Timestamp = DateTime.Now 97 | }; 98 | } 99 | 100 | /// 101 | /// 将结构化输出转换为 Markdown 格式 102 | /// 103 | private string ConvertToMarkdown(ResearchOutput output) 104 | { 105 | var markdown = new System.Text.StringBuilder(); 106 | 107 | markdown.AppendLine("## 主题分析"); 108 | markdown.AppendLine(output.TopicAnalysis); 109 | markdown.AppendLine(); 110 | 111 | markdown.AppendLine("## 核心要点"); 112 | foreach (var point in output.KeyPoints.OrderByDescending(p => p.Importance)) 113 | { 114 | var stars = new string('⭐', point.Importance); 115 | markdown.AppendLine($"{stars} {point.Content}"); 116 | } 117 | markdown.AppendLine(); 118 | 119 | markdown.AppendLine("## 技术细节"); 120 | foreach (var detail in output.TechnicalDetails) 121 | { 122 | markdown.AppendLine($"### {detail.Title}"); 123 | markdown.AppendLine(detail.Description); 124 | markdown.AppendLine(); 125 | } 126 | 127 | if (output.CodeExamples.Any()) 128 | { 129 | markdown.AppendLine("## 代码示例"); 130 | foreach (var example in output.CodeExamples) 131 | { 132 | markdown.AppendLine($"```{example.Language}"); 133 | markdown.AppendLine(example.Code); 134 | markdown.AppendLine("```"); 135 | markdown.AppendLine(example.Description); 136 | markdown.AppendLine(); 137 | } 138 | } 139 | 140 | markdown.AppendLine("## 参考来源"); 141 | foreach (var reference in output.References) 142 | { 143 | markdown.AppendLine($"- {reference}"); 144 | } 145 | 146 | return markdown.ToString(); 147 | } 148 | 149 | // ============ Agent Tools ============ 150 | 151 | /// 152 | /// 工具函数: 统计文本字数 153 | /// 154 | [Description("统计给定文本的字数,包括中文字符和英文单词")] 155 | private static int CountWordsInText([Description("要统计的文本内容")] string text) 156 | { 157 | if (string.IsNullOrWhiteSpace(text)) 158 | return 0; 159 | 160 | int chineseChars = text.Count(c => c >= 0x4E00 && c <= 0x9FA5); 161 | int englishWords = text.Split(new[] { ' ', '\n', '\r' }, StringSplitOptions.RemoveEmptyEntries) 162 | .Count(word => word.Any(c => (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'))); 163 | 164 | return chineseChars + englishWords; 165 | } 166 | 167 | /// 168 | /// 工具函数: 提取代码块 169 | /// 170 | [Description("从Markdown文本中提取所有代码块")] 171 | private static string ExtractCodeBlocks([Description("Markdown格式的文本")] string markdown) 172 | { 173 | var codeBlocks = new List(); 174 | var lines = markdown.Split('\n'); 175 | bool inCodeBlock = false; 176 | var currentBlock = new System.Text.StringBuilder(); 177 | 178 | foreach (var line in lines) 179 | { 180 | if (line.Trim().StartsWith("```")) 181 | { 182 | if (inCodeBlock) 183 | { 184 | codeBlocks.Add(currentBlock.ToString()); 185 | currentBlock.Clear(); 186 | inCodeBlock = false; 187 | } 188 | else 189 | { 190 | inCodeBlock = true; 191 | } 192 | } 193 | else if (inCodeBlock) 194 | { 195 | currentBlock.AppendLine(line); 196 | } 197 | } 198 | 199 | return string.Join("\n---\n", codeBlocks); 200 | } 201 | } 202 | } 203 | 204 | 205 | -------------------------------------------------------------------------------- /BlogAgent.Domain/Utils/ConvertUtils.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | 3 | namespace BlogAgent.Domain 4 | { 5 | /// 6 | /// 转换工具类 7 | /// 8 | public static class ConvertUtils 9 | { 10 | /// 11 | /// 判断是否为空,为空返回true 12 | /// 13 | /// 14 | /// 15 | public static bool IsNull(this object data) 16 | { 17 | //如果为null 18 | if (data == null) 19 | { 20 | return true; 21 | } 22 | 23 | //如果为"" 24 | if (data.GetType() == typeof(String)) 25 | { 26 | if (string.IsNullOrEmpty(data.ToString().Trim())) 27 | { 28 | return true; 29 | } 30 | } 31 | return false; 32 | } 33 | 34 | /// 35 | /// 判断是否为空,为空返回true 36 | /// 37 | /// 38 | /// 39 | public static bool IsNotNull(this object data) 40 | { 41 | //如果为null 42 | if (data == null) 43 | { 44 | return false; 45 | } 46 | 47 | //如果为"" 48 | if (data.GetType() == typeof(String)) 49 | { 50 | if (string.IsNullOrEmpty(data.ToString().Trim())) 51 | { 52 | return false; 53 | } 54 | } 55 | return true; 56 | } 57 | 58 | /// 59 | /// 判断是否为空,为空返回true 60 | /// 61 | /// 62 | /// 63 | public static bool IsNull(string data) 64 | { 65 | //如果为null 66 | if (data == null) 67 | { 68 | return true; 69 | } 70 | 71 | //如果为"" 72 | if (data.GetType() == typeof(String)) 73 | { 74 | if (string.IsNullOrEmpty(data.ToString().Trim())) 75 | { 76 | return true; 77 | } 78 | } 79 | return false; 80 | } 81 | 82 | /// 83 | /// 将obj类型转换为string 84 | /// 85 | /// 86 | /// 87 | public static string ConvertToString(this object s) 88 | { 89 | if (s == null) 90 | { 91 | return ""; 92 | } 93 | else 94 | { 95 | return Convert.ToString(s); 96 | } 97 | } 98 | 99 | /// 100 | /// object 转int32 101 | /// 102 | /// 103 | /// 104 | public static Int32 ConvertToInt32(this object s) 105 | { 106 | int i = 0; 107 | if (s == null) 108 | { 109 | return 0; 110 | } 111 | else 112 | { 113 | int.TryParse(s.ToString(), out i); 114 | } 115 | return i; 116 | } 117 | 118 | /// 119 | /// object 转int32 120 | /// 121 | /// 122 | /// 123 | public static Int64 ConvertToInt64(this object s) 124 | { 125 | long i = 0; 126 | if (s == null) 127 | { 128 | return 0; 129 | } 130 | else 131 | { 132 | long.TryParse(s.ToString(), out i); 133 | } 134 | return i; 135 | } 136 | 137 | /// 138 | /// 将字符串转double 139 | /// 140 | /// 141 | /// 142 | public static double ConvertToDouble(this object s) 143 | { 144 | double i = 0; 145 | if (s == null) 146 | { 147 | return 0; 148 | } 149 | else 150 | { 151 | double.TryParse(s.ToString(), out i); 152 | } 153 | return i; 154 | } 155 | 156 | /// 157 | /// 转换为datetime类型 158 | /// 159 | /// 160 | /// 161 | public static DateTime ConvertToDateTime(this string s) 162 | { 163 | DateTime dt = new DateTime(); 164 | if (s == null || s == "") 165 | { 166 | return DateTime.Now; 167 | } 168 | DateTime.TryParse(s, out dt); 169 | return dt; 170 | } 171 | 172 | /// 173 | /// 转换为datetime类型的格式字符串 174 | /// 175 | /// 要转换的对象 176 | /// 格式化字符串 177 | /// 178 | public static string ConvertToDateTime(this string s, string y) 179 | { 180 | DateTime dt = new DateTime(); 181 | DateTime.TryParse(s, out dt); 182 | return dt.ToString(y); 183 | } 184 | 185 | 186 | /// 187 | /// 将字符串转换成decimal 188 | /// 189 | /// 190 | /// 191 | public static decimal ConvertToDecimal(this object s) 192 | { 193 | decimal d = 0; 194 | if (s == null || s == "") 195 | { 196 | return 0; 197 | } 198 | 199 | Decimal.TryParse(s.ToString(), out d); 200 | 201 | return d; 202 | 203 | } 204 | /// 205 | /// decimal保留2位小数 206 | /// 207 | public static decimal DecimalFraction(this decimal num) 208 | { 209 | return Convert.ToDecimal(num.ToString("f2")); 210 | } 211 | 212 | 213 | /// 214 | /// 替换html种的特殊字符 215 | /// 216 | /// 217 | /// 218 | public static string ReplaceHtml(this string s) 219 | { 220 | return s.Replace("<", "<").Replace(">", ">").Replace("&", "&").Replace(""", "\""); 221 | } 222 | 223 | /// 224 | /// 流转byte 225 | /// 226 | /// 227 | /// 228 | public static byte[] StreamToByte(this Stream stream) 229 | { 230 | byte[] bytes = new byte[stream.Length]; 231 | stream.Read(bytes, 0, bytes.Length); 232 | // 设置当前流的位置为流的开始 233 | stream.Seek(0, SeekOrigin.Begin); 234 | return bytes; 235 | } 236 | 237 | /// 238 | /// \uxxxx转中文,保留换行符号 239 | /// 240 | /// 241 | /// 242 | public static string Unescape(this string value) 243 | { 244 | if (value.IsNull()) 245 | { 246 | return ""; 247 | } 248 | 249 | try 250 | { 251 | Formatting formatting = Formatting.None; 252 | 253 | object jsonObj = JsonConvert.DeserializeObject(value); 254 | string unescapeValue = JsonConvert.SerializeObject(jsonObj, formatting); 255 | return unescapeValue; 256 | } 257 | catch (Exception ex) 258 | { 259 | Console.WriteLine(ex.ToString()); 260 | return ""; 261 | } 262 | } 263 | 264 | } 265 | } 266 | -------------------------------------------------------------------------------- /BlogAgent/.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.rsuser 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | 13 | # User-specific files (MonoDevelop/Xamarin Studio) 14 | *.userprefs 15 | 16 | # Mono auto generated files 17 | mono_crash.* 18 | 19 | # Build results 20 | [Dd]ebug/ 21 | [Dd]ebugPublic/ 22 | [Rr]elease/ 23 | [Rr]eleases/ 24 | x64/ 25 | x86/ 26 | [Ww][Ii][Nn]32/ 27 | [Aa][Rr][Mm]/ 28 | [Aa][Rr][Mm]64/ 29 | bld/ 30 | [Bb]in/ 31 | [Oo]bj/ 32 | [Oo]ut/ 33 | [Ll]og/ 34 | [Ll]ogs/ 35 | 36 | # Visual Studio 2015/2017 cache/options directory 37 | .vs/ 38 | # Uncomment if you have tasks that create the project's static files in wwwroot 39 | #wwwroot/ 40 | 41 | # Visual Studio 2017 auto generated files 42 | Generated\ Files/ 43 | 44 | # MSTest test Results 45 | [Tt]est[Rr]esult*/ 46 | [Bb]uild[Ll]og.* 47 | 48 | # NUnit 49 | *.VisualState.xml 50 | TestResult.xml 51 | nunit-*.xml 52 | 53 | # Build Results of an ATL Project 54 | [Dd]ebugPS/ 55 | [Rr]eleasePS/ 56 | dlldata.c 57 | 58 | # Benchmark Results 59 | BenchmarkDotNet.Artifacts/ 60 | 61 | # .NET Core 62 | project.lock.json 63 | project.fragment.lock.json 64 | artifacts/ 65 | 66 | # ASP.NET Scaffolding 67 | ScaffoldingReadMe.txt 68 | 69 | # StyleCop 70 | StyleCopReport.xml 71 | 72 | # Files built by Visual Studio 73 | *_i.c 74 | *_p.c 75 | *_h.h 76 | *.ilk 77 | *.meta 78 | *.obj 79 | *.iobj 80 | *.pch 81 | *.pdb 82 | *.ipdb 83 | *.pgc 84 | *.pgd 85 | *.rsp 86 | *.sbr 87 | *.tlb 88 | *.tli 89 | *.tlh 90 | *.tmp 91 | *.tmp_proj 92 | *_wpftmp.csproj 93 | *.log 94 | *.vspscc 95 | *.vssscc 96 | .builds 97 | *.pidb 98 | *.svclog 99 | *.scc 100 | 101 | # Chutzpah Test files 102 | _Chutzpah* 103 | 104 | # Visual C++ cache files 105 | ipch/ 106 | *.aps 107 | *.ncb 108 | *.opendb 109 | *.opensdf 110 | *.sdf 111 | *.cachefile 112 | *.VC.db 113 | *.VC.VC.opendb 114 | 115 | # Visual Studio profiler 116 | *.psess 117 | *.vsp 118 | *.vspx 119 | *.sap 120 | 121 | # Visual Studio Trace Files 122 | *.e2e 123 | 124 | # TFS 2012 Local Workspace 125 | $tf/ 126 | 127 | # Guidance Automation Toolkit 128 | *.gpState 129 | 130 | # ReSharper is a .NET coding add-in 131 | _ReSharper*/ 132 | *.[Rr]e[Ss]harper 133 | *.DotSettings.user 134 | 135 | # TeamCity is a build add-in 136 | _TeamCity* 137 | 138 | # DotCover is a Code Coverage Tool 139 | *.dotCover 140 | 141 | # AxoCover is a Code Coverage Tool 142 | .axoCover/* 143 | !.axoCover/settings.json 144 | 145 | # Coverlet is a free, cross platform Code Coverage Tool 146 | coverage*.json 147 | coverage*.xml 148 | coverage*.info 149 | 150 | # Visual Studio code coverage results 151 | *.coverage 152 | *.coveragexml 153 | 154 | # NCrunch 155 | _NCrunch_* 156 | .*crunch*.local.xml 157 | nCrunchTemp_* 158 | 159 | # MightyMoose 160 | *.mm.* 161 | AutoTest.Net/ 162 | 163 | # Web workbench (sass) 164 | .sass-cache/ 165 | 166 | # Installshield output folder 167 | [Ee]xpress/ 168 | 169 | # DocProject is a documentation generator add-in 170 | DocProject/buildhelp/ 171 | DocProject/Help/*.HxT 172 | DocProject/Help/*.HxC 173 | DocProject/Help/*.hhc 174 | DocProject/Help/*.hhk 175 | DocProject/Help/*.hhp 176 | DocProject/Help/Html2 177 | DocProject/Help/html 178 | 179 | # Click-Once directory 180 | publish/ 181 | 182 | # Publish Web Output 183 | *.[Pp]ublish.xml 184 | *.azurePubxml 185 | # Note: Comment the next line if you want to checkin your web deploy settings, 186 | # but database connection strings (with potential passwords) will be unencrypted 187 | *.pubxml 188 | *.publishproj 189 | 190 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 191 | # checkin your Azure Web App publish settings, but sensitive information contained 192 | # in these scripts will be unencrypted 193 | PublishScripts/ 194 | 195 | # NuGet Packages 196 | *.nupkg 197 | # NuGet Symbol Packages 198 | *.snupkg 199 | # The packages folder can be ignored because of Package Restore 200 | **/[Pp]ackages/* 201 | # except build/, which is used as an MSBuild target. 202 | !**/[Pp]ackages/build/ 203 | # Uncomment if necessary however generally it will be regenerated when needed 204 | #!**/[Pp]ackages/repositories.config 205 | # NuGet v3's project.json files produces more ignorable files 206 | *.nuget.props 207 | *.nuget.targets 208 | 209 | # Microsoft Azure Build Output 210 | csx/ 211 | *.build.csdef 212 | 213 | # Microsoft Azure Emulator 214 | ecf/ 215 | rcf/ 216 | 217 | # Windows Store app package directories and files 218 | AppPackages/ 219 | BundleArtifacts/ 220 | Package.StoreAssociation.xml 221 | _pkginfo.txt 222 | *.appx 223 | *.appxbundle 224 | *.appxupload 225 | 226 | # Visual Studio cache files 227 | # files ending in .cache can be ignored 228 | *.[Cc]ache 229 | # but keep track of directories ending in .cache 230 | !?*.[Cc]ache/ 231 | 232 | # Others 233 | ClientBin/ 234 | ~$* 235 | *~ 236 | *.dbmdl 237 | *.dbproj.schemaview 238 | *.jfm 239 | *.pfx 240 | *.publishsettings 241 | orleans.codegen.cs 242 | 243 | # Including strong name files can present a security risk 244 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 245 | #*.snk 246 | 247 | # Since there are multiple workflows, uncomment next line to ignore bower_components 248 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 249 | #bower_components/ 250 | 251 | # RIA/Silverlight projects 252 | Generated_Code/ 253 | 254 | # Backup & report files from converting an old project file 255 | # to a newer Visual Studio version. Backup files are not needed, 256 | # because we have git ;-) 257 | _UpgradeReport_Files/ 258 | Backup*/ 259 | UpgradeLog*.XML 260 | UpgradeLog*.htm 261 | ServiceFabricBackup/ 262 | *.rptproj.bak 263 | 264 | # SQL Server files 265 | *.mdf 266 | *.ldf 267 | *.ndf 268 | 269 | # Business Intelligence projects 270 | *.rdl.data 271 | *.bim.layout 272 | *.bim_*.settings 273 | *.rptproj.rsuser 274 | *- [Bb]ackup.rdl 275 | *- [Bb]ackup ([0-9]).rdl 276 | *- [Bb]ackup ([0-9][0-9]).rdl 277 | 278 | # Microsoft Fakes 279 | FakesAssemblies/ 280 | 281 | # GhostDoc plugin setting file 282 | *.GhostDoc.xml 283 | 284 | # Node.js Tools for Visual Studio 285 | .ntvs_analysis.dat 286 | node_modules/ 287 | 288 | # Visual Studio 6 build log 289 | *.plg 290 | 291 | # Visual Studio 6 workspace options file 292 | *.opt 293 | 294 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 295 | *.vbw 296 | 297 | # Visual Studio LightSwitch build output 298 | **/*.HTMLClient/GeneratedArtifacts 299 | **/*.DesktopClient/GeneratedArtifacts 300 | **/*.DesktopClient/ModelManifest.xml 301 | **/*.Server/GeneratedArtifacts 302 | **/*.Server/ModelManifest.xml 303 | _Pvt_Extensions 304 | 305 | # Paket dependency manager 306 | .paket/paket.exe 307 | paket-files/ 308 | 309 | # FAKE - F# Make 310 | .fake/ 311 | 312 | # CodeRush personal settings 313 | .cr/personal 314 | 315 | # Python Tools for Visual Studio (PTVS) 316 | __pycache__/ 317 | *.pyc 318 | 319 | # Cake - Uncomment if you are using it 320 | # tools/** 321 | # !tools/packages.config 322 | 323 | # Tabs Studio 324 | *.tss 325 | 326 | # Telerik's JustMock configuration file 327 | *.jmconfig 328 | 329 | # BizTalk build output 330 | *.btp.cs 331 | *.btm.cs 332 | *.odx.cs 333 | *.xsd.cs 334 | 335 | # OpenCover UI analysis results 336 | OpenCover/ 337 | 338 | # Azure Stream Analytics local run output 339 | ASALocalRun/ 340 | 341 | # MSBuild Binary and Structured Log 342 | *.binlog 343 | 344 | # NVidia Nsight GPU debugger configuration file 345 | *.nvuser 346 | 347 | # MFractors (Xamarin productivity tool) working folder 348 | .mfractor/ 349 | 350 | # Local History for Visual Studio 351 | .localhistory/ 352 | 353 | # BeatPulse healthcheck temp database 354 | healthchecksdb 355 | 356 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 357 | MigrationBackup/ 358 | 359 | # Ionide (cross platform F# VS Code tools) working folder 360 | .ionide/ 361 | 362 | # Fody - auto-generated XML schema 363 | FodyWeavers.xsd -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.rsuser 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | 13 | # User-specific files (MonoDevelop/Xamarin Studio) 14 | *.userprefs 15 | 16 | # Mono auto generated files 17 | mono_crash.* 18 | 19 | # Build results 20 | [Dd]ebug/ 21 | [Dd]ebugPublic/ 22 | [Rr]elease/ 23 | [Rr]eleases/ 24 | x64/ 25 | x86/ 26 | [Ww][Ii][Nn]32/ 27 | [Aa][Rr][Mm]/ 28 | [Aa][Rr][Mm]64/ 29 | bld/ 30 | [Bb]in/ 31 | [Oo]bj/ 32 | [Oo]ut/ 33 | [Ll]og/ 34 | [Ll]ogs/ 35 | 36 | # Visual Studio 2015/2017 cache/options directory 37 | .vs/ 38 | # Uncomment if you have tasks that create the project's static files in wwwroot 39 | #wwwroot/ 40 | 41 | # Visual Studio 2017 auto generated files 42 | Generated\ Files/ 43 | 44 | # MSTest test Results 45 | [Tt]est[Rr]esult*/ 46 | [Bb]uild[Ll]og.* 47 | 48 | # NUnit 49 | *.VisualState.xml 50 | TestResult.xml 51 | nunit-*.xml 52 | 53 | # Build Results of an ATL Project 54 | [Dd]ebugPS/ 55 | [Rr]eleasePS/ 56 | dlldata.c 57 | 58 | # Benchmark Results 59 | BenchmarkDotNet.Artifacts/ 60 | 61 | # .NET Core 62 | project.lock.json 63 | project.fragment.lock.json 64 | artifacts/ 65 | 66 | # ASP.NET Scaffolding 67 | ScaffoldingReadMe.txt 68 | 69 | # StyleCop 70 | StyleCopReport.xml 71 | 72 | # Files built by Visual Studio 73 | *_i.c 74 | *_p.c 75 | *_h.h 76 | *.ilk 77 | *.meta 78 | *.obj 79 | *.iobj 80 | *.pch 81 | *.pdb 82 | *.ipdb 83 | *.pgc 84 | *.pgd 85 | *.rsp 86 | *.sbr 87 | *.tlb 88 | *.tli 89 | *.tlh 90 | *.tmp 91 | *.tmp_proj 92 | *_wpftmp.csproj 93 | *.log 94 | *.vspscc 95 | *.vssscc 96 | .builds 97 | *.pidb 98 | *.svclog 99 | *.scc 100 | 101 | # Chutzpah Test files 102 | _Chutzpah* 103 | 104 | # Visual C++ cache files 105 | ipch/ 106 | *.aps 107 | *.ncb 108 | *.opendb 109 | *.opensdf 110 | *.sdf 111 | *.cachefile 112 | *.VC.db 113 | *.VC.VC.opendb 114 | 115 | # Visual Studio profiler 116 | *.psess 117 | *.vsp 118 | *.vspx 119 | *.sap 120 | 121 | # Visual Studio Trace Files 122 | *.e2e 123 | 124 | # TFS 2012 Local Workspace 125 | $tf/ 126 | 127 | # Guidance Automation Toolkit 128 | *.gpState 129 | 130 | # ReSharper is a .NET coding add-in 131 | _ReSharper*/ 132 | *.[Rr]e[Ss]harper 133 | *.DotSettings.user 134 | 135 | # TeamCity is a build add-in 136 | _TeamCity* 137 | 138 | # DotCover is a Code Coverage Tool 139 | *.dotCover 140 | 141 | # AxoCover is a Code Coverage Tool 142 | .axoCover/* 143 | !.axoCover/settings.json 144 | 145 | # Coverlet is a free, cross platform Code Coverage Tool 146 | coverage*.json 147 | coverage*.xml 148 | coverage*.info 149 | 150 | # Visual Studio code coverage results 151 | *.coverage 152 | *.coveragexml 153 | 154 | # NCrunch 155 | _NCrunch_* 156 | .*crunch*.local.xml 157 | nCrunchTemp_* 158 | 159 | # MightyMoose 160 | *.mm.* 161 | AutoTest.Net/ 162 | 163 | # Web workbench (sass) 164 | .sass-cache/ 165 | 166 | # Installshield output folder 167 | [Ee]xpress/ 168 | 169 | # DocProject is a documentation generator add-in 170 | DocProject/buildhelp/ 171 | DocProject/Help/*.HxT 172 | DocProject/Help/*.HxC 173 | DocProject/Help/*.hhc 174 | DocProject/Help/*.hhk 175 | DocProject/Help/*.hhp 176 | DocProject/Help/Html2 177 | DocProject/Help/html 178 | 179 | # Click-Once directory 180 | publish/ 181 | 182 | # Publish Web Output 183 | *.[Pp]ublish.xml 184 | *.azurePubxml 185 | # Note: Comment the next line if you want to checkin your web deploy settings, 186 | # but database connection strings (with potential passwords) will be unencrypted 187 | *.pubxml 188 | *.publishproj 189 | 190 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 191 | # checkin your Azure Web App publish settings, but sensitive information contained 192 | # in these scripts will be unencrypted 193 | PublishScripts/ 194 | 195 | # NuGet Packages 196 | *.nupkg 197 | # NuGet Symbol Packages 198 | *.snupkg 199 | # The packages folder can be ignored because of Package Restore 200 | **/[Pp]ackages/* 201 | # except build/, which is used as an MSBuild target. 202 | !**/[Pp]ackages/build/ 203 | # Uncomment if necessary however generally it will be regenerated when needed 204 | #!**/[Pp]ackages/repositories.config 205 | # NuGet v3's project.json files produces more ignorable files 206 | *.nuget.props 207 | *.nuget.targets 208 | 209 | # Microsoft Azure Build Output 210 | csx/ 211 | *.build.csdef 212 | 213 | # Microsoft Azure Emulator 214 | ecf/ 215 | rcf/ 216 | 217 | # Windows Store app package directories and files 218 | AppPackages/ 219 | BundleArtifacts/ 220 | Package.StoreAssociation.xml 221 | _pkginfo.txt 222 | *.appx 223 | *.appxbundle 224 | *.appxupload 225 | 226 | # Visual Studio cache files 227 | # files ending in .cache can be ignored 228 | *.[Cc]ache 229 | # but keep track of directories ending in .cache 230 | !?*.[Cc]ache/ 231 | 232 | # Others 233 | ClientBin/ 234 | ~$* 235 | *~ 236 | *.dbmdl 237 | *.dbproj.schemaview 238 | *.jfm 239 | *.pfx 240 | *.publishsettings 241 | orleans.codegen.cs 242 | 243 | # Including strong name files can present a security risk 244 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 245 | #*.snk 246 | 247 | # Since there are multiple workflows, uncomment next line to ignore bower_components 248 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 249 | #bower_components/ 250 | 251 | # RIA/Silverlight projects 252 | Generated_Code/ 253 | 254 | # Backup & report files from converting an old project file 255 | # to a newer Visual Studio version. Backup files are not needed, 256 | # because we have git ;-) 257 | _UpgradeReport_Files/ 258 | Backup*/ 259 | UpgradeLog*.XML 260 | UpgradeLog*.htm 261 | ServiceFabricBackup/ 262 | *.rptproj.bak 263 | 264 | # SQL Server files 265 | *.mdf 266 | *.ldf 267 | *.ndf 268 | 269 | # Business Intelligence projects 270 | *.rdl.data 271 | *.bim.layout 272 | *.bim_*.settings 273 | *.rptproj.rsuser 274 | *- [Bb]ackup.rdl 275 | *- [Bb]ackup ([0-9]).rdl 276 | *- [Bb]ackup ([0-9][0-9]).rdl 277 | 278 | # Microsoft Fakes 279 | FakesAssemblies/ 280 | 281 | # GhostDoc plugin setting file 282 | *.GhostDoc.xml 283 | 284 | # Node.js Tools for Visual Studio 285 | .ntvs_analysis.dat 286 | node_modules/ 287 | 288 | # Visual Studio 6 build log 289 | *.plg 290 | 291 | # Visual Studio 6 workspace options file 292 | *.opt 293 | 294 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 295 | *.vbw 296 | 297 | # Visual Studio LightSwitch build output 298 | **/*.HTMLClient/GeneratedArtifacts 299 | **/*.DesktopClient/GeneratedArtifacts 300 | **/*.DesktopClient/ModelManifest.xml 301 | **/*.Server/GeneratedArtifacts 302 | **/*.Server/ModelManifest.xml 303 | _Pvt_Extensions 304 | 305 | # Paket dependency manager 306 | .paket/paket.exe 307 | paket-files/ 308 | 309 | # FAKE - F# Make 310 | .fake/ 311 | 312 | # CodeRush personal settings 313 | .cr/personal 314 | 315 | # Python Tools for Visual Studio (PTVS) 316 | __pycache__/ 317 | *.pyc 318 | 319 | # Cake - Uncomment if you are using it 320 | # tools/** 321 | # !tools/packages.config 322 | 323 | # Tabs Studio 324 | *.tss 325 | 326 | # Telerik's JustMock configuration file 327 | *.jmconfig 328 | 329 | # BizTalk build output 330 | *.btp.cs 331 | *.btm.cs 332 | *.odx.cs 333 | *.xsd.cs 334 | 335 | # OpenCover UI analysis results 336 | OpenCover/ 337 | 338 | # Azure Stream Analytics local run output 339 | ASALocalRun/ 340 | 341 | # MSBuild Binary and Structured Log 342 | *.binlog 343 | 344 | # NVidia Nsight GPU debugger configuration file 345 | *.nvuser 346 | 347 | # MFractors (Xamarin productivity tool) working folder 348 | .mfractor/ 349 | 350 | # Local History for Visual Studio 351 | .localhistory/ 352 | 353 | # BeatPulse healthcheck temp database 354 | healthchecksdb 355 | 356 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 357 | MigrationBackup/ 358 | 359 | # Ionide (cross platform F# VS Code tools) working folder 360 | .ionide/ 361 | 362 | # Fody - auto-generated XML schema 363 | FodyWeavers.xsd 364 | /BlogAgent/appsettings.Development.json 365 | *.db 366 | /dotnet 367 | /agent-framework 368 | /docs 369 | -------------------------------------------------------------------------------- /BlogAgent/wwwroot/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | Ant Design Pro Blazor 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 178 |
186 | logo 187 |
188 |
189 | 190 | 191 | 192 |
193 |
194 |
195 | 197 | Text2Sql.Net.Web
198 |
199 |
200 |
201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | -------------------------------------------------------------------------------- /BlogAgent/Pages/Blog/Create.razor: -------------------------------------------------------------------------------- 1 | @page "/blog/create" 2 | @using BlogAgent.Domain.Domain.Dto 3 | @using BlogAgent.Domain.Services 4 | @inject BlogService BlogService 5 | @inject NavigationManager Navigation 6 | @inject IMessageService Message 7 | @implements IDisposable 8 | 9 | 10 | 11 | 通过AI智能体协作创建高质量技术博客 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 全自动模式 25 |
26 | AI 自动完成资料收集→博客撰写→质量审查,无需人工干预 27 |
28 |
29 | 30 | 分步模式 31 |
32 | 每个阶段手动触发,可以随时查看和调整中间结果 33 |
34 |
35 |
36 |
37 | 38 | 39 | 40 | 直接输入文本 41 | 上传文档 42 | 提供参考链接 43 | 44 | 45 | 46 | @if (inputMode == "text") 47 | { 48 | 49 |