From 92cd6b5f7ee9cdd11c78c9648f0606f13bb80bf6 Mon Sep 17 00:00:00 2001 From: LittleSheep Date: Sun, 26 Oct 2025 12:10:10 +0800 Subject: [PATCH] :lipstick: Optimize the AI agent experience --- .../Identity/DeveloperController.cs | 2 +- .../Identity/DeveloperService.cs | 4 +- .../Thought/ThoughtController.cs | 18 +-- .../Thought/ThoughtProvider.cs | 17 +-- DysonNetwork.Shared/Models/Post.cs | 111 +++++++++++++++++- DysonNetwork.Shared/Models/Publisher.cs | 4 +- .../Publisher/PublisherServiceGrpc.cs | 6 +- 7 files changed, 136 insertions(+), 26 deletions(-) diff --git a/DysonNetwork.Develop/Identity/DeveloperController.cs b/DysonNetwork.Develop/Identity/DeveloperController.cs index 39b924a..1f5e6b0 100644 --- a/DysonNetwork.Develop/Identity/DeveloperController.cs +++ b/DysonNetwork.Develop/Identity/DeveloperController.cs @@ -79,7 +79,7 @@ public class DeveloperController( try { var pubResponse = await ps.GetPublisherAsync(new GetPublisherRequest { Name = name }); - pub = SnPublisher.FromProto(pubResponse.Publisher); + pub = SnPublisher.FromProtoValue(pubResponse.Publisher); } catch (RpcException ex) { return NotFound(ex.Status.Detail); diff --git a/DysonNetwork.Develop/Identity/DeveloperService.cs b/DysonNetwork.Develop/Identity/DeveloperService.cs index 5f3fd3b..f74832e 100644 --- a/DysonNetwork.Develop/Identity/DeveloperService.cs +++ b/DysonNetwork.Develop/Identity/DeveloperService.cs @@ -13,7 +13,7 @@ public class DeveloperService( public async Task LoadDeveloperPublisher(SnDeveloper developer) { var pubResponse = await ps.GetPublisherAsync(new GetPublisherRequest { Id = developer.PublisherId.ToString() }); - developer.Publisher = SnPublisher.FromProto(pubResponse.Publisher); + developer.Publisher = SnPublisher.FromProtoValue(pubResponse.Publisher); return developer; } @@ -25,7 +25,7 @@ public class DeveloperService( var pubRequest = new GetPublisherBatchRequest(); pubIds.ForEach(x => pubRequest.Ids.Add(x.ToString())); var pubResponse = await ps.GetPublisherBatchAsync(pubRequest); - var pubs = pubResponse.Publishers.ToDictionary(p => Guid.Parse(p.Id), SnPublisher.FromProto); + var pubs = pubResponse.Publishers.ToDictionary(p => Guid.Parse(p.Id), SnPublisher.FromProtoValue); return enumerable.Select(d => { diff --git a/DysonNetwork.Insight/Thought/ThoughtController.cs b/DysonNetwork.Insight/Thought/ThoughtController.cs index c075bf7..a92eacc 100644 --- a/DysonNetwork.Insight/Thought/ThoughtController.cs +++ b/DysonNetwork.Insight/Thought/ThoughtController.cs @@ -26,13 +26,14 @@ public class ThoughtController(ThoughtProvider provider, ThoughtService service) if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); var accountId = Guid.Parse(currentUser.Id); - // Generate topic if creating new sequence + // Generate a topic if creating a new sequence string? topic = null; if (!request.SequenceId.HasValue) { - // Use AI to summarize topic from user message + // Use AI to summarize a topic from a user message var summaryHistory = new ChatHistory( - "You are a helpful assistant. Summarize the following user message into a concise topic title (max 100 characters). Direct give the topic you summerized, do not add extra preifx / suffix." + "You are a helpful assistant. Summarize the following user message into a concise topic title (max 100 characters).\n" + + "Direct give the topic you summerized, do not add extra prefix / suffix." ); summaryHistory.AddUserMessage(request.UserMessage); @@ -105,7 +106,7 @@ public class ThoughtController(ThoughtProvider provider, ThoughtService service) )) { // Write each chunk to the HTTP response as SSE - var data = chunk.Content ?? ""; + var data = chunk.ToString(); accumulatedContent.Append(data); if (string.IsNullOrEmpty(data)) continue; @@ -121,14 +122,15 @@ public class ThoughtController(ThoughtProvider provider, ThoughtService service) // Write the topic if it was newly set, then the thought object as JSON to the stream using (var streamBuilder = new MemoryStream()) { - await streamBuilder.WriteAsync("\n"u8.ToArray()); + await streamBuilder.WriteAsync("\n\ndata: "u8.ToArray()); if (topic != null) { - await streamBuilder.WriteAsync(Encoding.UTF8.GetBytes($"{sequence.Topic ?? ""}\n")); + var topicJson = JsonSerializer.Serialize(new { type = "topic", data = sequence.Topic ?? "" }); + await streamBuilder.WriteAsync(Encoding.UTF8.GetBytes($"{topicJson}\n\n")); } - await streamBuilder.WriteAsync( - Encoding.UTF8.GetBytes(JsonSerializer.Serialize(savedThought, GrpcTypeHelper.SerializerOptions))); + var thoughtJson = JsonSerializer.Serialize(new { type = "thought", data = savedThought }, GrpcTypeHelper.SerializerOptions); + await streamBuilder.WriteAsync(Encoding.UTF8.GetBytes($"data: {thoughtJson}\n\n")); var outputBytes = streamBuilder.ToArray(); await Response.Body.WriteAsync(outputBytes); await Response.Body.FlushAsync(); diff --git a/DysonNetwork.Insight/Thought/ThoughtProvider.cs b/DysonNetwork.Insight/Thought/ThoughtProvider.cs index 98a6c03..2590887 100644 --- a/DysonNetwork.Insight/Thought/ThoughtProvider.cs +++ b/DysonNetwork.Insight/Thought/ThoughtProvider.cs @@ -1,22 +1,25 @@ using System.ClientModel; using System.Text.Json; +using DysonNetwork.Shared.Models; using DysonNetwork.Shared.Proto; using Microsoft.SemanticKernel; using Microsoft.SemanticKernel.Connectors.Ollama; using Microsoft.SemanticKernel.Connectors.OpenAI; using OpenAI; +using PostPinMode = DysonNetwork.Shared.Proto.PostPinMode; +using PostType = DysonNetwork.Shared.Proto.PostType; namespace DysonNetwork.Insight.Thought; public class ThoughtProvider { - private readonly Kernel _kernel; private readonly PostService.PostServiceClient _postClient; private readonly AccountService.AccountServiceClient _accountClient; - public Kernel Kernel => _kernel; - public string? ModelProviderType { get; private set; } - public string? ModelDefault { get; private set; } + public Kernel Kernel { get; } + + private string? ModelProviderType { get; set; } + private string? ModelDefault { get; set; } public ThoughtProvider( IConfiguration configuration, @@ -27,7 +30,7 @@ public class ThoughtProvider _postClient = postClient; _accountClient = accountClient; - _kernel = InitializeThinkingProvider(configuration); + Kernel = InitializeThinkingProvider(configuration); InitializeHelperFunctions(); } @@ -63,7 +66,7 @@ public class ThoughtProvider private void InitializeHelperFunctions() { // Add Solar Network tools plugin - _kernel.ImportPluginFromFunctions("helper_functions", [ + Kernel.ImportPluginFromFunctions("helper_functions", [ KernelFunctionFactory.CreateFromMethod(async (string userId) => { var request = new GetAccountRequest { Id = userId }; @@ -131,7 +134,7 @@ public class ThoughtProvider if (tags != null) request.Tags.AddRange(tags); if (types != null) request.Types_.AddRange(types.Select(t => (PostType)t)); var response = await _postClient.ListPostsAsync(request); - return JsonSerializer.Serialize(response.Posts, GrpcTypeHelper.SerializerOptions); + return JsonSerializer.Serialize(response.Posts.Select(SnPost.FromProtoValue), GrpcTypeHelper.SerializerOptions); }, "list_posts", "Get posts from the Solar Network with customizable filters.") ]); } diff --git a/DysonNetwork.Shared/Models/Post.cs b/DysonNetwork.Shared/Models/Post.cs index 391ae61..b45313b 100644 --- a/DysonNetwork.Shared/Models/Post.cs +++ b/DysonNetwork.Shared/Models/Post.cs @@ -100,13 +100,13 @@ public class SnPost : ModelBase, IIdentifiedResource, IActivity Upvotes = Upvotes, Downvotes = Downvotes, AwardedScore = (double)AwardedScore, - ReactionsCount = { ReactionsCount ?? new Dictionary() }, + ReactionsCount = { ReactionsCount }, RepliesCount = RepliesCount, ReactionsMade = { ReactionsMade ?? new Dictionary() }, RepliedGone = RepliedGone, ForwardedGone = ForwardedGone, PublisherId = PublisherId.ToString(), - Publisher = Publisher.ToProto(), + Publisher = Publisher.ToProtoValue(), CreatedAt = Timestamp.FromDateTimeOffset(CreatedAt.ToDateTimeOffset()), UpdatedAt = Timestamp.FromDateTimeOffset(UpdatedAt.ToDateTimeOffset()) }; @@ -128,7 +128,7 @@ public class SnPost : ModelBase, IIdentifiedResource, IActivity if (PinMode.HasValue) { - proto.PinMode = (Proto.PostPinMode)((int)PinMode.Value); + proto.PinMode = (Proto.PostPinMode)((int)PinMode.Value + 1); } if (Meta != null) @@ -186,6 +186,95 @@ public class SnPost : ModelBase, IIdentifiedResource, IActivity return proto; } + public static SnPost FromProtoValue(Post proto) + { + var post = new SnPost + { + Id = Guid.Parse(proto.Id), + Title = string.IsNullOrEmpty(proto.Title) ? null : proto.Title, + Description = string.IsNullOrEmpty(proto.Description) ? null : proto.Description, + Slug = string.IsNullOrEmpty(proto.Slug) ? null : proto.Slug, + Visibility = (PostVisibility)((int)proto.Visibility - 1), + Type = (PostType)((int)proto.Type - 1), + ViewsUnique = proto.ViewsUnique, + ViewsTotal = proto.ViewsTotal, + Upvotes = proto.Upvotes, + Downvotes = proto.Downvotes, + AwardedScore = (decimal)proto.AwardedScore, + ReactionsCount = proto.ReactionsCount.ToDictionary(kv => kv.Key, kv => kv.Value), + RepliesCount = proto.RepliesCount, + ReactionsMade = proto.ReactionsMade.ToDictionary(kv => kv.Key, kv => kv.Value), + RepliedGone = proto.RepliedGone, + ForwardedGone = proto.ForwardedGone, + PublisherId = Guid.Parse(proto.PublisherId), + Publisher = SnPublisher.FromProtoValue(proto.Publisher), + CreatedAt = Instant.FromDateTimeOffset(proto.CreatedAt.ToDateTimeOffset()), + UpdatedAt = Instant.FromDateTimeOffset(proto.UpdatedAt.ToDateTimeOffset()) + }; + + if (proto.EditedAt is not null) + post.EditedAt = Instant.FromDateTimeOffset(proto.EditedAt.ToDateTimeOffset()); + + if (proto.PublishedAt is not null) + post.PublishedAt = Instant.FromDateTimeOffset(proto.PublishedAt.ToDateTimeOffset()); + + if (!string.IsNullOrEmpty(proto.Content)) + post.Content = proto.Content; + + if (proto is { HasPinMode: true, PinMode: > 0 }) + post.PinMode = (PostPinMode)(proto.PinMode - 1); + + if (proto.Meta != null) + post.Meta = GrpcTypeHelper.ConvertByteStringToObject>(proto.Meta); + + if (proto.SensitiveMarks != null) + post.SensitiveMarks = + GrpcTypeHelper.ConvertByteStringToObject>(proto.SensitiveMarks); + + if (proto.EmbedView is not null) + post.EmbedView = PostEmbedView.FromProtoValue(proto.EmbedView); + + if (!string.IsNullOrEmpty(proto.RepliedPostId)) + { + post.RepliedPostId = Guid.Parse(proto.RepliedPostId); + if (proto.RepliedPost is not null) + post.RepliedPost = FromProtoValue(proto.RepliedPost); + } + + if (!string.IsNullOrEmpty(proto.ForwardedPostId)) + { + post.ForwardedPostId = Guid.Parse(proto.ForwardedPostId); + if (proto.ForwardedPost is not null) + post.ForwardedPost = FromProtoValue(proto.ForwardedPost); + } + + if (!string.IsNullOrEmpty(proto.RealmId)) + { + post.RealmId = Guid.Parse(proto.RealmId); + if (proto.Realm is not null) + post.Realm = SnRealm.FromProtoValue(proto.Realm); + } + + post.Attachments.AddRange(proto.Attachments.Select(SnCloudFileReferenceObject.FromProtoValue)); + post.Awards.AddRange(proto.Awards.Select(a => new SnPostAward + { + Id = Guid.Parse(a.Id), PostId = Guid.Parse(a.PostId), AccountId = Guid.Parse(a.AccountId), + Amount = (decimal)a.Amount, Attitude = (PostReactionAttitude)((int)a.Attitude - 1), + Message = string.IsNullOrEmpty(a.Message) ? null : a.Message, + CreatedAt = Instant.FromDateTimeOffset(a.CreatedAt.ToDateTimeOffset()), + UpdatedAt = Instant.FromDateTimeOffset(a.UpdatedAt.ToDateTimeOffset()) + })); + post.Reactions.AddRange(proto.Reactions.Select(SnPostReaction.FromProtoValue)); + post.Tags.AddRange(proto.Tags.Select(SnPostTag.FromProtoValue)); + post.Categories.AddRange(proto.Categories.Select(SnPostCategory.FromProtoValue)); + post.FeaturedRecords.AddRange(proto.FeaturedRecords.Select(SnPostFeaturedRecord.FromProtoValue)); + + if (proto.DeletedAt is not null) + post.DeletedAt = Instant.FromDateTimeOffset(proto.DeletedAt.ToDateTimeOffset()); + + return post; + } + public SnActivity ToActivity() { return new SnActivity() @@ -314,8 +403,22 @@ public class SnPostFeaturedRecord : ModelBase { proto.FeaturedAt = Timestamp.FromDateTimeOffset(FeaturedAt.Value.ToDateTimeOffset()); } + return proto; } + + public static SnPostFeaturedRecord FromProtoValue(PostFeaturedRecord proto) + { + return new SnPostFeaturedRecord + { + Id = Guid.Parse(proto.Id), + PostId = Guid.Parse(proto.PostId), + SocialCredits = proto.SocialCredits, + CreatedAt = Instant.FromDateTimeOffset(proto.CreatedAt.ToDateTimeOffset()), + UpdatedAt = Instant.FromDateTimeOffset(proto.UpdatedAt.ToDateTimeOffset()), + FeaturedAt = proto.FeaturedAt != null ? Instant.FromDateTimeOffset(proto.FeaturedAt.ToDateTimeOffset()) : null + }; + } } public enum PostReactionAttitude @@ -352,6 +455,7 @@ public class SnPostReaction : ModelBase { proto.Account = Account.ToProtoValue(); } + return proto; } @@ -422,6 +526,7 @@ public class PostEmbedView { proto.AspectRatio = AspectRatio.Value; } + return proto; } diff --git a/DysonNetwork.Shared/Models/Publisher.cs b/DysonNetwork.Shared/Models/Publisher.cs index 95634d3..8da1571 100644 --- a/DysonNetwork.Shared/Models/Publisher.cs +++ b/DysonNetwork.Shared/Models/Publisher.cs @@ -43,7 +43,7 @@ public class SnPublisher : ModelBase, IIdentifiedResource public string ResourceIdentifier => $"publisher:{Id}"; - public static SnPublisher FromProto(Proto.Publisher proto) + public static SnPublisher FromProtoValue(Proto.Publisher proto) { var publisher = new SnPublisher { @@ -85,7 +85,7 @@ public class SnPublisher : ModelBase, IIdentifiedResource return publisher; } - public Proto.Publisher ToProto() + public Proto.Publisher ToProtoValue() { var p = new Proto.Publisher() { diff --git a/DysonNetwork.Sphere/Publisher/PublisherServiceGrpc.cs b/DysonNetwork.Sphere/Publisher/PublisherServiceGrpc.cs index f489702..cc686b4 100644 --- a/DysonNetwork.Sphere/Publisher/PublisherServiceGrpc.cs +++ b/DysonNetwork.Sphere/Publisher/PublisherServiceGrpc.cs @@ -28,7 +28,7 @@ public class PublisherServiceGrpc(PublisherService service, AppDatabase db) } if (p is null) throw new RpcException(new Status(StatusCode.NotFound, "Publisher not found")); - return new GetPublisherResponse { Publisher = p.ToProto() }; + return new GetPublisherResponse { Publisher = p.ToProtoValue() }; } public override async Task GetPublisherBatch( @@ -43,7 +43,7 @@ public class PublisherServiceGrpc(PublisherService service, AppDatabase db) if (ids.Count == 0) return new ListPublishersResponse(); var list = await db.Publishers.Where(p => ids.Contains(p.Id)).ToListAsync(); var resp = new ListPublishersResponse(); - resp.Publishers.AddRange(list.Select(p => p.ToProto())); + resp.Publishers.AddRange(list.Select(p => p.ToProtoValue())); return resp; } @@ -64,7 +64,7 @@ public class PublisherServiceGrpc(PublisherService service, AppDatabase db) var list = await query.ToListAsync(); var resp = new ListPublishersResponse(); - resp.Publishers.AddRange(list.Select(p => p.ToProto())); + resp.Publishers.AddRange(list.Select(p => p.ToProtoValue())); return resp; }