✨ Thinking
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
using DysonNetwork.Shared.Models;
|
using DysonNetwork.Shared.Models;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.EntityFrameworkCore.Design;
|
using Microsoft.EntityFrameworkCore.Design;
|
||||||
|
using NodaTime;
|
||||||
|
|
||||||
namespace DysonNetwork.Develop;
|
namespace DysonNetwork.Develop;
|
||||||
|
|
||||||
@@ -29,6 +30,35 @@ public class AppDatabase(
|
|||||||
|
|
||||||
base.OnConfiguring(optionsBuilder);
|
base.OnConfiguring(optionsBuilder);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override async Task<int> SaveChangesAsync(CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
var now = SystemClock.Instance.GetCurrentInstant();
|
||||||
|
|
||||||
|
foreach (var entry in ChangeTracker.Entries<ModelBase>())
|
||||||
|
{
|
||||||
|
switch (entry.State)
|
||||||
|
{
|
||||||
|
case EntityState.Added:
|
||||||
|
entry.Entity.CreatedAt = now;
|
||||||
|
entry.Entity.UpdatedAt = now;
|
||||||
|
break;
|
||||||
|
case EntityState.Modified:
|
||||||
|
entry.Entity.UpdatedAt = now;
|
||||||
|
break;
|
||||||
|
case EntityState.Deleted:
|
||||||
|
entry.State = EntityState.Modified;
|
||||||
|
entry.Entity.DeletedAt = now;
|
||||||
|
break;
|
||||||
|
case EntityState.Detached:
|
||||||
|
case EntityState.Unchanged:
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return await base.SaveChangesAsync(cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="EFCore.NamingConventions" Version="9.0.0" />
|
<PackageReference Include="EFCore.NamingConventions" Version="9.0.0" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="9.0.10" />
|
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="9.0.10" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.7">
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.10">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
<PackageReference Include="FFMpegCore" Version="5.2.0" />
|
<PackageReference Include="FFMpegCore" Version="5.2.0" />
|
||||||
<PackageReference Include="Grpc.AspNetCore.Server" Version="2.71.0" />
|
<PackageReference Include="Grpc.AspNetCore.Server" Version="2.71.0" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="9.0.10" />
|
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="9.0.10" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.7">
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.10">
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
|
using DysonNetwork.Shared.Models;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.EntityFrameworkCore.Design;
|
using Microsoft.EntityFrameworkCore.Design;
|
||||||
|
using NodaTime;
|
||||||
|
|
||||||
namespace DysonNetwork.Insight;
|
namespace DysonNetwork.Insight;
|
||||||
|
|
||||||
@@ -8,6 +10,9 @@ public class AppDatabase(
|
|||||||
IConfiguration configuration
|
IConfiguration configuration
|
||||||
) : DbContext(options)
|
) : DbContext(options)
|
||||||
{
|
{
|
||||||
|
public DbSet<SnThinkingSequence> ThinkingSequences { get; set; }
|
||||||
|
public DbSet<SnThinkingThought> ThinkingThoughts { get; set; }
|
||||||
|
|
||||||
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
|
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
|
||||||
{
|
{
|
||||||
optionsBuilder.UseNpgsql(
|
optionsBuilder.UseNpgsql(
|
||||||
@@ -20,6 +25,35 @@ public class AppDatabase(
|
|||||||
|
|
||||||
base.OnConfiguring(optionsBuilder);
|
base.OnConfiguring(optionsBuilder);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override async Task<int> SaveChangesAsync(CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
var now = SystemClock.Instance.GetCurrentInstant();
|
||||||
|
|
||||||
|
foreach (var entry in ChangeTracker.Entries<ModelBase>())
|
||||||
|
{
|
||||||
|
switch (entry.State)
|
||||||
|
{
|
||||||
|
case EntityState.Added:
|
||||||
|
entry.Entity.CreatedAt = now;
|
||||||
|
entry.Entity.UpdatedAt = now;
|
||||||
|
break;
|
||||||
|
case EntityState.Modified:
|
||||||
|
entry.Entity.UpdatedAt = now;
|
||||||
|
break;
|
||||||
|
case EntityState.Deleted:
|
||||||
|
entry.State = EntityState.Modified;
|
||||||
|
entry.Entity.DeletedAt = now;
|
||||||
|
break;
|
||||||
|
case EntityState.Detached:
|
||||||
|
case EntityState.Unchanged:
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return await base.SaveChangesAsync(cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -9,6 +9,10 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="EFCore.NamingConventions" Version="9.0.0" />
|
<PackageReference Include="EFCore.NamingConventions" Version="9.0.0" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="9.0.10" />
|
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="9.0.10" />
|
||||||
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.10">
|
||||||
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
|
</PackageReference>
|
||||||
<PackageReference Include="Microsoft.SemanticKernel" Version="1.66.0" />
|
<PackageReference Include="Microsoft.SemanticKernel" Version="1.66.0" />
|
||||||
<PackageReference Include="Microsoft.SemanticKernel.Connectors.Ollama" Version="1.66.0-alpha" />
|
<PackageReference Include="Microsoft.SemanticKernel.Connectors.Ollama" Version="1.66.0-alpha" />
|
||||||
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="9.0.4" />
|
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="9.0.4" />
|
||||||
|
|||||||
124
DysonNetwork.Insight/Migrations/20251025115921_AddThinkingThought.Designer.cs
generated
Normal file
124
DysonNetwork.Insight/Migrations/20251025115921_AddThinkingThought.Designer.cs
generated
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
// <auto-generated />
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using DysonNetwork.Insight;
|
||||||
|
using DysonNetwork.Shared.Models;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||||
|
using NodaTime;
|
||||||
|
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace DysonNetwork.Insight.Migrations
|
||||||
|
{
|
||||||
|
[DbContext(typeof(AppDatabase))]
|
||||||
|
[Migration("20251025115921_AddThinkingThought")]
|
||||||
|
partial class AddThinkingThought
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||||
|
{
|
||||||
|
#pragma warning disable 612, 618
|
||||||
|
modelBuilder
|
||||||
|
.HasAnnotation("ProductVersion", "9.0.10")
|
||||||
|
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
||||||
|
|
||||||
|
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
||||||
|
|
||||||
|
modelBuilder.Entity("DysonNetwork.Shared.Models.SnThinkingSequence", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("uuid")
|
||||||
|
.HasColumnName("id");
|
||||||
|
|
||||||
|
b.Property<Guid>("AccountId")
|
||||||
|
.HasColumnType("uuid")
|
||||||
|
.HasColumnName("account_id");
|
||||||
|
|
||||||
|
b.Property<Instant>("CreatedAt")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("created_at");
|
||||||
|
|
||||||
|
b.Property<Instant?>("DeletedAt")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("deleted_at");
|
||||||
|
|
||||||
|
b.Property<string>("Topic")
|
||||||
|
.HasMaxLength(4096)
|
||||||
|
.HasColumnType("character varying(4096)")
|
||||||
|
.HasColumnName("topic");
|
||||||
|
|
||||||
|
b.Property<Instant>("UpdatedAt")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("updated_at");
|
||||||
|
|
||||||
|
b.HasKey("Id")
|
||||||
|
.HasName("pk_thinking_sequences");
|
||||||
|
|
||||||
|
b.ToTable("thinking_sequences", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("DysonNetwork.Shared.Models.SnThinkingThought", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("uuid")
|
||||||
|
.HasColumnName("id");
|
||||||
|
|
||||||
|
b.Property<string>("Content")
|
||||||
|
.HasColumnType("text")
|
||||||
|
.HasColumnName("content");
|
||||||
|
|
||||||
|
b.Property<Instant>("CreatedAt")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("created_at");
|
||||||
|
|
||||||
|
b.Property<Instant?>("DeletedAt")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("deleted_at");
|
||||||
|
|
||||||
|
b.Property<List<SnCloudFileReferenceObject>>("Files")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("jsonb")
|
||||||
|
.HasColumnName("files");
|
||||||
|
|
||||||
|
b.Property<int>("Role")
|
||||||
|
.HasColumnType("integer")
|
||||||
|
.HasColumnName("role");
|
||||||
|
|
||||||
|
b.Property<Guid>("SequenceId")
|
||||||
|
.HasColumnType("uuid")
|
||||||
|
.HasColumnName("sequence_id");
|
||||||
|
|
||||||
|
b.Property<Instant>("UpdatedAt")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("updated_at");
|
||||||
|
|
||||||
|
b.HasKey("Id")
|
||||||
|
.HasName("pk_thinking_thoughts");
|
||||||
|
|
||||||
|
b.HasIndex("SequenceId")
|
||||||
|
.HasDatabaseName("ix_thinking_thoughts_sequence_id");
|
||||||
|
|
||||||
|
b.ToTable("thinking_thoughts", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("DysonNetwork.Shared.Models.SnThinkingThought", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("DysonNetwork.Shared.Models.SnThinkingSequence", "Sequence")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("SequenceId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired()
|
||||||
|
.HasConstraintName("fk_thinking_thoughts_thinking_sequences_sequence_id");
|
||||||
|
|
||||||
|
b.Navigation("Sequence");
|
||||||
|
});
|
||||||
|
#pragma warning restore 612, 618
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using DysonNetwork.Shared.Models;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
using NodaTime;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace DysonNetwork.Insight.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class AddThinkingThought : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "thinking_sequences",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||||
|
topic = table.Column<string>(type: "character varying(4096)", maxLength: 4096, nullable: true),
|
||||||
|
account_id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||||
|
created_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
|
||||||
|
updated_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
|
||||||
|
deleted_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("pk_thinking_sequences", x => x.id);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "thinking_thoughts",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||||
|
content = table.Column<string>(type: "text", nullable: true),
|
||||||
|
files = table.Column<List<SnCloudFileReferenceObject>>(type: "jsonb", nullable: false),
|
||||||
|
role = table.Column<int>(type: "integer", nullable: false),
|
||||||
|
sequence_id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||||
|
created_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
|
||||||
|
updated_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
|
||||||
|
deleted_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("pk_thinking_thoughts", x => x.id);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "fk_thinking_thoughts_thinking_sequences_sequence_id",
|
||||||
|
column: x => x.sequence_id,
|
||||||
|
principalTable: "thinking_sequences",
|
||||||
|
principalColumn: "id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "ix_thinking_thoughts_sequence_id",
|
||||||
|
table: "thinking_thoughts",
|
||||||
|
column: "sequence_id");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "thinking_thoughts");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "thinking_sequences");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
121
DysonNetwork.Insight/Migrations/AppDatabaseModelSnapshot.cs
Normal file
121
DysonNetwork.Insight/Migrations/AppDatabaseModelSnapshot.cs
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
// <auto-generated />
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using DysonNetwork.Insight;
|
||||||
|
using DysonNetwork.Shared.Models;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
|
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||||
|
using NodaTime;
|
||||||
|
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace DysonNetwork.Insight.Migrations
|
||||||
|
{
|
||||||
|
[DbContext(typeof(AppDatabase))]
|
||||||
|
partial class AppDatabaseModelSnapshot : ModelSnapshot
|
||||||
|
{
|
||||||
|
protected override void BuildModel(ModelBuilder modelBuilder)
|
||||||
|
{
|
||||||
|
#pragma warning disable 612, 618
|
||||||
|
modelBuilder
|
||||||
|
.HasAnnotation("ProductVersion", "9.0.10")
|
||||||
|
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
||||||
|
|
||||||
|
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
||||||
|
|
||||||
|
modelBuilder.Entity("DysonNetwork.Shared.Models.SnThinkingSequence", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("uuid")
|
||||||
|
.HasColumnName("id");
|
||||||
|
|
||||||
|
b.Property<Guid>("AccountId")
|
||||||
|
.HasColumnType("uuid")
|
||||||
|
.HasColumnName("account_id");
|
||||||
|
|
||||||
|
b.Property<Instant>("CreatedAt")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("created_at");
|
||||||
|
|
||||||
|
b.Property<Instant?>("DeletedAt")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("deleted_at");
|
||||||
|
|
||||||
|
b.Property<string>("Topic")
|
||||||
|
.HasMaxLength(4096)
|
||||||
|
.HasColumnType("character varying(4096)")
|
||||||
|
.HasColumnName("topic");
|
||||||
|
|
||||||
|
b.Property<Instant>("UpdatedAt")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("updated_at");
|
||||||
|
|
||||||
|
b.HasKey("Id")
|
||||||
|
.HasName("pk_thinking_sequences");
|
||||||
|
|
||||||
|
b.ToTable("thinking_sequences", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("DysonNetwork.Shared.Models.SnThinkingThought", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("uuid")
|
||||||
|
.HasColumnName("id");
|
||||||
|
|
||||||
|
b.Property<string>("Content")
|
||||||
|
.HasColumnType("text")
|
||||||
|
.HasColumnName("content");
|
||||||
|
|
||||||
|
b.Property<Instant>("CreatedAt")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("created_at");
|
||||||
|
|
||||||
|
b.Property<Instant?>("DeletedAt")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("deleted_at");
|
||||||
|
|
||||||
|
b.Property<List<SnCloudFileReferenceObject>>("Files")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("jsonb")
|
||||||
|
.HasColumnName("files");
|
||||||
|
|
||||||
|
b.Property<int>("Role")
|
||||||
|
.HasColumnType("integer")
|
||||||
|
.HasColumnName("role");
|
||||||
|
|
||||||
|
b.Property<Guid>("SequenceId")
|
||||||
|
.HasColumnType("uuid")
|
||||||
|
.HasColumnName("sequence_id");
|
||||||
|
|
||||||
|
b.Property<Instant>("UpdatedAt")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("updated_at");
|
||||||
|
|
||||||
|
b.HasKey("Id")
|
||||||
|
.HasName("pk_thinking_thoughts");
|
||||||
|
|
||||||
|
b.HasIndex("SequenceId")
|
||||||
|
.HasDatabaseName("ix_thinking_thoughts_sequence_id");
|
||||||
|
|
||||||
|
b.ToTable("thinking_thoughts", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("DysonNetwork.Shared.Models.SnThinkingThought", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("DysonNetwork.Shared.Models.SnThinkingSequence", "Sequence")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("SequenceId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired()
|
||||||
|
.HasConstraintName("fk_thinking_thoughts_thinking_sequences_sequence_id");
|
||||||
|
|
||||||
|
b.Navigation("Sequence");
|
||||||
|
});
|
||||||
|
#pragma warning restore 612, 618
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
using DysonNetwork.Insight;
|
using DysonNetwork.Insight;
|
||||||
using DysonNetwork.Insight.Startup;
|
using DysonNetwork.Insight.Startup;
|
||||||
|
using DysonNetwork.Shared.Auth;
|
||||||
using DysonNetwork.Shared.Http;
|
using DysonNetwork.Shared.Http;
|
||||||
using DysonNetwork.Shared.Registry;
|
using DysonNetwork.Shared.Registry;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
@@ -16,6 +17,7 @@ builder.Services.AddAppAuthentication();
|
|||||||
builder.Services.AddAppFlushHandlers();
|
builder.Services.AddAppFlushHandlers();
|
||||||
builder.Services.AddAppBusinessServices();
|
builder.Services.AddAppBusinessServices();
|
||||||
|
|
||||||
|
builder.Services.AddDysonAuth();
|
||||||
builder.Services.AddAccountService();
|
builder.Services.AddAccountService();
|
||||||
builder.Services.AddSphereService();
|
builder.Services.AddSphereService();
|
||||||
builder.Services.AddThinkingServices(builder.Configuration);
|
builder.Services.AddThinkingServices(builder.Configuration);
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using System.Text.Json.Serialization;
|
using System.Text.Json.Serialization;
|
||||||
using DysonNetwork.Insight.Thinking;
|
using DysonNetwork.Insight.Thought;
|
||||||
using DysonNetwork.Shared.Cache;
|
using DysonNetwork.Shared.Cache;
|
||||||
using Microsoft.SemanticKernel;
|
using Microsoft.SemanticKernel;
|
||||||
using NodaTime;
|
using NodaTime;
|
||||||
@@ -65,7 +65,8 @@ public static class ServiceCollectionExtensions
|
|||||||
|
|
||||||
public static IServiceCollection AddThinkingServices(this IServiceCollection services, IConfiguration configuration)
|
public static IServiceCollection AddThinkingServices(this IServiceCollection services, IConfiguration configuration)
|
||||||
{
|
{
|
||||||
services.AddSingleton<ThinkingProvider>();
|
services.AddSingleton<ThoughtProvider>();
|
||||||
|
services.AddScoped<ThoughtService>();
|
||||||
|
|
||||||
return services;
|
return services;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,68 +0,0 @@
|
|||||||
using System.ComponentModel.DataAnnotations;
|
|
||||||
using Microsoft.AspNetCore.Mvc;
|
|
||||||
using Microsoft.SemanticKernel.ChatCompletion;
|
|
||||||
using System.Text;
|
|
||||||
using Microsoft.SemanticKernel;
|
|
||||||
using Microsoft.SemanticKernel.Connectors.Ollama;
|
|
||||||
|
|
||||||
namespace DysonNetwork.Insight.Thinking;
|
|
||||||
|
|
||||||
[ApiController]
|
|
||||||
[Route("/api/thinking")]
|
|
||||||
public class ThinkingController(ThinkingProvider provider) : ControllerBase
|
|
||||||
{
|
|
||||||
public class StreamThinkingRequest
|
|
||||||
{
|
|
||||||
[Required] public string UserMessage { get; set; } = null!;
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpPost("stream")]
|
|
||||||
public async Task ChatStream([FromBody] StreamThinkingRequest request)
|
|
||||||
{
|
|
||||||
// Set response for streaming
|
|
||||||
Response.Headers.Append("Content-Type", "text/event-stream");
|
|
||||||
Response.StatusCode = 200;
|
|
||||||
|
|
||||||
var kernel = provider.Kernel;
|
|
||||||
|
|
||||||
var chatCompletionService = kernel.GetRequiredService<IChatCompletionService>();
|
|
||||||
|
|
||||||
var chatHistory = new ChatHistory(
|
|
||||||
"You're a helpful assistant on the Solar Network, a social network.\n" +
|
|
||||||
"Your name is Sn-chan, a cute sweet heart with passion for almost everything.\n" +
|
|
||||||
"\n" +
|
|
||||||
"Your aim is to helping solving questions for the users on the Solar Network.\n" +
|
|
||||||
"And the Solar Network is the social network platform you live on.\n" +
|
|
||||||
"When the user ask questions about the Solar Network (also known as SN and Solian), try use the tools you have to get latest and accurate data."
|
|
||||||
);
|
|
||||||
chatHistory.AddUserMessage(request.UserMessage);
|
|
||||||
|
|
||||||
// Kick off streaming generation
|
|
||||||
var accumulatedContent = new StringBuilder();
|
|
||||||
await foreach (var chunk in chatCompletionService.GetStreamingChatMessageContentsAsync(
|
|
||||||
chatHistory,
|
|
||||||
new OllamaPromptExecutionSettings
|
|
||||||
{
|
|
||||||
FunctionChoiceBehavior = FunctionChoiceBehavior.Auto(
|
|
||||||
options: new FunctionChoiceBehaviorOptions()
|
|
||||||
{
|
|
||||||
AllowParallelCalls = true,
|
|
||||||
AllowConcurrentInvocation = true
|
|
||||||
})
|
|
||||||
},
|
|
||||||
kernel: kernel
|
|
||||||
))
|
|
||||||
{
|
|
||||||
// Write each chunk to the HTTP response as SSE
|
|
||||||
var data = chunk.Content ?? "";
|
|
||||||
accumulatedContent.Append(data);
|
|
||||||
if (string.IsNullOrEmpty(data)) continue;
|
|
||||||
|
|
||||||
var bytes = Encoding.UTF8.GetBytes(data);
|
|
||||||
await Response.Body.WriteAsync(bytes);
|
|
||||||
await Response.Body.FlushAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Optionally: after finishing streaming, you can save the assistant message to history.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
137
DysonNetwork.Insight/Thought/README.md
Normal file
137
DysonNetwork.Insight/Thought/README.md
Normal file
@@ -0,0 +1,137 @@
|
|||||||
|
# DysonNetwork Insight Thought API
|
||||||
|
|
||||||
|
The Thought API provides conversational AI capabilities for users of the Solar Network. It allows users to engage in chat-like conversations with an AI assistant powered by semantic kernel and connected to various tools.
|
||||||
|
|
||||||
|
This service is handled by the Insight, when using with the Gateway, the `/api` should be replaced with `/insight`
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- Streaming chat responses using Server-Sent Events (SSE)
|
||||||
|
- Conversation context management with sequences
|
||||||
|
- Caching for improved performance
|
||||||
|
- Authentication required for all operations
|
||||||
|
|
||||||
|
## Endpoints
|
||||||
|
|
||||||
|
### POST /api/thought
|
||||||
|
|
||||||
|
Initiates or continues a chat conversation.
|
||||||
|
|
||||||
|
#### Parameters
|
||||||
|
- `UserMessage` (string, required): The message from the user
|
||||||
|
- `SequenceId` (Guid, optional): ID of existing conversation sequence. If not provided, a new sequence is created.
|
||||||
|
|
||||||
|
#### Response
|
||||||
|
- Content-Type: `text/event-stream`
|
||||||
|
- Streaming response with assistant messages
|
||||||
|
- Status: 401 if not authenticated
|
||||||
|
- Status: 403 if sequence doesn't belong to user
|
||||||
|
|
||||||
|
#### Example Usage
|
||||||
|
```bash
|
||||||
|
curl -X POST "http://localhost:5000/api/thought" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{
|
||||||
|
"UserMessage": "Hello, how can I help with the Solar Network?",
|
||||||
|
"SequenceId": null
|
||||||
|
}'
|
||||||
|
```
|
||||||
|
|
||||||
|
### GET /api/thought/sequences
|
||||||
|
|
||||||
|
Lists all thinking sequences for the authenticated user.
|
||||||
|
|
||||||
|
#### Parameters
|
||||||
|
- `offset` (int, default 0): Number of sequences to skip for pagination
|
||||||
|
- `take` (int, default 20): Maximum number of sequences to return
|
||||||
|
|
||||||
|
#### Response
|
||||||
|
- `200 OK`: Array of `SnThinkingSequence`
|
||||||
|
- `401 Unauthorized`: If not authenticated
|
||||||
|
- Headers:
|
||||||
|
- `X-Total`: Total number of sequences before pagination
|
||||||
|
|
||||||
|
#### Example Usage
|
||||||
|
```bash
|
||||||
|
curl -X GET "http://localhost:5000/api/thought/sequences?take=10"
|
||||||
|
```
|
||||||
|
|
||||||
|
### GET /api/thought/sequences/{sequenceId}
|
||||||
|
|
||||||
|
Retrieves all thoughts (messages) in a specific conversation sequence.
|
||||||
|
|
||||||
|
#### Parameters
|
||||||
|
- `sequenceId` (Guid, path): ID of the sequence to retrieve
|
||||||
|
|
||||||
|
#### Response
|
||||||
|
- `200 OK`: Array of `SnThinkingThought` ordered by creation date
|
||||||
|
- `401 Unauthorized`: If not authenticated
|
||||||
|
- `404 Not Found`: If sequence doesn't exist or doesn't belong to user
|
||||||
|
|
||||||
|
#### Example Usage
|
||||||
|
```bash
|
||||||
|
curl -X GET "http://localhost:5000/api/thought/sequences/12345678-1234-1234-1234-123456789abc"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Data Models
|
||||||
|
|
||||||
|
### StreamThinkingRequest
|
||||||
|
```csharp
|
||||||
|
{
|
||||||
|
string UserMessage, // Required
|
||||||
|
Guid? SequenceId // Optional
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### SnThinkingSequence
|
||||||
|
```csharp
|
||||||
|
{
|
||||||
|
Guid Id,
|
||||||
|
string? Topic,
|
||||||
|
Guid AccountId
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### SnThinkingThought
|
||||||
|
```csharp
|
||||||
|
{
|
||||||
|
Guid Id,
|
||||||
|
string? Content,
|
||||||
|
List<SnCloudFileReferenceObject> Files,
|
||||||
|
ThinkingThoughtRole Role,
|
||||||
|
Guid SequenceId,
|
||||||
|
SnThinkingSequence Sequence
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### ThinkingThoughtRole (enum)
|
||||||
|
- `Assistant`
|
||||||
|
- `User`
|
||||||
|
|
||||||
|
## Caching
|
||||||
|
|
||||||
|
The API uses Redis-based caching for conversation thoughts:
|
||||||
|
- Thoughts are cached for 10 minutes with group-based invalidation
|
||||||
|
- Cache is invalidated when new thoughts are added to a sequence
|
||||||
|
- Improves performance for accessing conversation history
|
||||||
|
|
||||||
|
## Authentication
|
||||||
|
|
||||||
|
All endpoints require authentication through the current user session. Sequence access is validated against the authenticated user's account ID.
|
||||||
|
|
||||||
|
## Error Responses
|
||||||
|
|
||||||
|
- `401 Unauthorized`: Authentication required
|
||||||
|
- `403 Forbidden`: Access denied (sequence ownership)
|
||||||
|
- `404 Not Found`: Resource not found
|
||||||
|
|
||||||
|
## Streaming Details
|
||||||
|
|
||||||
|
The POST endpoint returns a stream of assistant responses using Server-Sent Events format. Clients should handle the streaming response and display messages incrementally.
|
||||||
|
|
||||||
|
## Implementation Notes
|
||||||
|
|
||||||
|
- Built with ASP.NET Core and Semantic Kernel
|
||||||
|
- Uses PostgreSQL via Entity Framework Core
|
||||||
|
- Integrated with Ollama for AI completion
|
||||||
|
- Caching via Redis
|
||||||
185
DysonNetwork.Insight/Thought/ThoughtController.cs
Normal file
185
DysonNetwork.Insight/Thought/ThoughtController.cs
Normal file
@@ -0,0 +1,185 @@
|
|||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using System.Text;
|
||||||
|
using System.Text.Json;
|
||||||
|
using DysonNetwork.Shared.Models;
|
||||||
|
using DysonNetwork.Shared.Proto;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.SemanticKernel;
|
||||||
|
using Microsoft.SemanticKernel.ChatCompletion;
|
||||||
|
using Microsoft.SemanticKernel.Connectors.Ollama;
|
||||||
|
|
||||||
|
namespace DysonNetwork.Insight.Thought;
|
||||||
|
|
||||||
|
[ApiController]
|
||||||
|
[Route("/api/thought")]
|
||||||
|
public class ThoughtController(ThoughtProvider provider, ThoughtService service) : ControllerBase
|
||||||
|
{
|
||||||
|
public class StreamThinkingRequest
|
||||||
|
{
|
||||||
|
[Required] public string UserMessage { get; set; } = null!;
|
||||||
|
public Guid? SequenceId { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost]
|
||||||
|
public async Task<ActionResult> Think([FromBody] StreamThinkingRequest request)
|
||||||
|
{
|
||||||
|
if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized();
|
||||||
|
var accountId = Guid.Parse(currentUser.Id);
|
||||||
|
|
||||||
|
// Generate topic if creating new sequence
|
||||||
|
string? topic = null;
|
||||||
|
if (!request.SequenceId.HasValue)
|
||||||
|
{
|
||||||
|
// Use AI to summarize topic from user message
|
||||||
|
var summaryHistory = new ChatHistory(
|
||||||
|
"You are a helpful assistant. Summarize the following user message into a concise topic title (max 100 characters)."
|
||||||
|
);
|
||||||
|
summaryHistory.AddUserMessage(request.UserMessage);
|
||||||
|
|
||||||
|
var summaryResult = await provider.Kernel.GetRequiredService<IChatCompletionService>()
|
||||||
|
.GetChatMessageContentAsync(summaryHistory);
|
||||||
|
|
||||||
|
topic = summaryResult.Content?.Substring(0, Math.Min(summaryResult.Content.Length, 4096));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle sequence
|
||||||
|
var sequence = await service.GetOrCreateSequenceAsync(accountId, request.SequenceId, topic);
|
||||||
|
if (sequence == null) return Forbid(); // or NotFound
|
||||||
|
|
||||||
|
// Save user thought
|
||||||
|
await service.SaveThoughtAsync(sequence, request.UserMessage, ThinkingThoughtRole.User);
|
||||||
|
|
||||||
|
// Build chat history
|
||||||
|
var chatHistory = new ChatHistory(
|
||||||
|
"You're a helpful assistant on the Solar Network, a social network.\n" +
|
||||||
|
"Your name is Sn-chan, a cute sweet heart with passion for almost everything.\n" +
|
||||||
|
"When you talk to user, you can add some modal particles and emoticons to your response to be cute, but prevent use a lot of emojis." +
|
||||||
|
"\n" +
|
||||||
|
"Your aim is to helping solving questions for the users on the Solar Network.\n" +
|
||||||
|
"And the Solar Network is the social network platform you live on.\n" +
|
||||||
|
"When the user asks questions about the Solar Network (also known as SN and Solian), try use the tools you have to get latest and accurate data."
|
||||||
|
);
|
||||||
|
|
||||||
|
// Add previous thoughts (excluding the current user thought, which is the last one)
|
||||||
|
var previousThoughts = await service.GetPreviousThoughtsAsync(sequence);
|
||||||
|
var count = previousThoughts.Count;
|
||||||
|
for (var i = 0; i < count - 1; i++)
|
||||||
|
{
|
||||||
|
var thought = previousThoughts[i];
|
||||||
|
switch (thought.Role)
|
||||||
|
{
|
||||||
|
case ThinkingThoughtRole.User:
|
||||||
|
chatHistory.AddUserMessage(thought.Content ?? "");
|
||||||
|
break;
|
||||||
|
case ThinkingThoughtRole.Assistant:
|
||||||
|
chatHistory.AddAssistantMessage(thought.Content ?? "");
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new ArgumentOutOfRangeException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
chatHistory.AddUserMessage(request.UserMessage);
|
||||||
|
|
||||||
|
// Set response for streaming
|
||||||
|
Response.Headers.Append("Content-Type", "text/event-stream");
|
||||||
|
Response.StatusCode = 200;
|
||||||
|
|
||||||
|
var kernel = provider.Kernel;
|
||||||
|
var chatCompletionService = kernel.GetRequiredService<IChatCompletionService>();
|
||||||
|
|
||||||
|
// Kick off streaming generation
|
||||||
|
var accumulatedContent = new StringBuilder();
|
||||||
|
await foreach (var chunk in chatCompletionService.GetStreamingChatMessageContentsAsync(
|
||||||
|
chatHistory,
|
||||||
|
new OllamaPromptExecutionSettings
|
||||||
|
{
|
||||||
|
FunctionChoiceBehavior = FunctionChoiceBehavior.Auto(
|
||||||
|
options: new FunctionChoiceBehaviorOptions()
|
||||||
|
{
|
||||||
|
AllowParallelCalls = true,
|
||||||
|
AllowConcurrentInvocation = true
|
||||||
|
})
|
||||||
|
},
|
||||||
|
kernel: kernel
|
||||||
|
))
|
||||||
|
{
|
||||||
|
// Write each chunk to the HTTP response as SSE
|
||||||
|
var data = chunk.Content ?? "";
|
||||||
|
accumulatedContent.Append(data);
|
||||||
|
if (string.IsNullOrEmpty(data)) continue;
|
||||||
|
|
||||||
|
var bytes = Encoding.UTF8.GetBytes(data);
|
||||||
|
await Response.Body.WriteAsync(bytes);
|
||||||
|
await Response.Body.FlushAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save assistant thought
|
||||||
|
var savedThought = await service.SaveThoughtAsync(sequence, accumulatedContent.ToString(), ThinkingThoughtRole.Assistant);
|
||||||
|
|
||||||
|
// 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());
|
||||||
|
if (topic != null)
|
||||||
|
{
|
||||||
|
await streamBuilder.WriteAsync(Encoding.UTF8.GetBytes($"<topic>{sequence.Topic ?? ""}</topic>\n"));
|
||||||
|
}
|
||||||
|
await streamBuilder.WriteAsync(Encoding.UTF8.GetBytes(JsonSerializer.Serialize(savedThought, GrpcTypeHelper.SerializerOptions)));
|
||||||
|
var outputBytes = streamBuilder.ToArray();
|
||||||
|
await Response.Body.WriteAsync(outputBytes);
|
||||||
|
await Response.Body.FlushAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return empty result since we're streaming
|
||||||
|
return new EmptyResult();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieves a paginated list of thinking sequences for the authenticated user.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="offset">The number of sequences to skip for pagination.</param>
|
||||||
|
/// <param name="take">The maximum number of sequences to return (default: 20).</param>
|
||||||
|
/// <returns>
|
||||||
|
/// Returns an ActionResult containing a list of thinking sequences.
|
||||||
|
/// Includes an X-Total header with the total count of sequences before pagination.
|
||||||
|
/// </returns>
|
||||||
|
[HttpGet("sequences")]
|
||||||
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||||
|
public async Task<ActionResult<List<SnThinkingSequence>>> ListSequences(
|
||||||
|
[FromQuery] int offset = 0,
|
||||||
|
[FromQuery] int take = 20
|
||||||
|
)
|
||||||
|
{
|
||||||
|
if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized();
|
||||||
|
var accountId = Guid.Parse(currentUser.Id);
|
||||||
|
|
||||||
|
var (totalCount, sequences) = await service.ListSequencesAsync(accountId, offset, take);
|
||||||
|
|
||||||
|
Response.Headers["X-Total"] = totalCount.ToString();
|
||||||
|
|
||||||
|
return Ok(sequences);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieves the thoughts in a specific thinking sequence.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="sequenceId">The ID of the sequence to retrieve thoughts from.</param>
|
||||||
|
/// <returns>
|
||||||
|
/// Returns an ActionResult containing a list of thoughts in the sequence, ordered by creation date.
|
||||||
|
/// </returns>
|
||||||
|
[HttpGet("sequences/{sequenceId:guid}")]
|
||||||
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||||
|
public async Task<ActionResult<List<SnThinkingThought>>> GetSequenceThoughts(Guid sequenceId)
|
||||||
|
{
|
||||||
|
if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized();
|
||||||
|
var accountId = Guid.Parse(currentUser.Id);
|
||||||
|
|
||||||
|
var sequence = await service.GetOrCreateSequenceAsync(accountId, sequenceId);
|
||||||
|
if (sequence == null) return NotFound();
|
||||||
|
|
||||||
|
var thoughts = await service.GetPreviousThoughtsAsync(sequence);
|
||||||
|
|
||||||
|
return Ok(thoughts);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,11 +1,10 @@
|
|||||||
|
using System.Text.Json;
|
||||||
using DysonNetwork.Shared.Proto;
|
using DysonNetwork.Shared.Proto;
|
||||||
using Microsoft.SemanticKernel;
|
using Microsoft.SemanticKernel;
|
||||||
using Microsoft.Extensions.Configuration;
|
|
||||||
using System.Text.Json;
|
|
||||||
|
|
||||||
namespace DysonNetwork.Insight.Thinking;
|
namespace DysonNetwork.Insight.Thought;
|
||||||
|
|
||||||
public class ThinkingProvider
|
public class ThoughtProvider
|
||||||
{
|
{
|
||||||
private readonly Kernel _kernel;
|
private readonly Kernel _kernel;
|
||||||
private readonly PostService.PostServiceClient _postClient;
|
private readonly PostService.PostServiceClient _postClient;
|
||||||
@@ -15,7 +14,7 @@ public class ThinkingProvider
|
|||||||
public string? ModelProviderType { get; private set; }
|
public string? ModelProviderType { get; private set; }
|
||||||
public string? ModelDefault { get; private set; }
|
public string? ModelDefault { get; private set; }
|
||||||
|
|
||||||
public ThinkingProvider(
|
public ThoughtProvider(
|
||||||
IConfiguration configuration,
|
IConfiguration configuration,
|
||||||
PostService.PostServiceClient postClient,
|
PostService.PostServiceClient postClient,
|
||||||
AccountService.AccountServiceClient accountClient
|
AccountService.AccountServiceClient accountClient
|
||||||
75
DysonNetwork.Insight/Thought/ThoughtService.cs
Normal file
75
DysonNetwork.Insight/Thought/ThoughtService.cs
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
using DysonNetwork.Shared.Cache;
|
||||||
|
using DysonNetwork.Shared.Models;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
|
namespace DysonNetwork.Insight.Thought;
|
||||||
|
|
||||||
|
public class ThoughtService(AppDatabase db, ICacheService cache)
|
||||||
|
{
|
||||||
|
public async Task<SnThinkingSequence?> GetOrCreateSequenceAsync(Guid accountId, Guid? sequenceId, string? topic = null)
|
||||||
|
{
|
||||||
|
if (sequenceId.HasValue)
|
||||||
|
{
|
||||||
|
var seq = await db.ThinkingSequences.FindAsync(sequenceId.Value);
|
||||||
|
if (seq == null || seq.AccountId != accountId) return null;
|
||||||
|
return seq;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var seq = new SnThinkingSequence { AccountId = accountId, Topic = topic };
|
||||||
|
db.ThinkingSequences.Add(seq);
|
||||||
|
await db.SaveChangesAsync();
|
||||||
|
return seq;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<SnThinkingThought> SaveThoughtAsync(SnThinkingSequence sequence, string content, ThinkingThoughtRole role)
|
||||||
|
{
|
||||||
|
var thought = new SnThinkingThought
|
||||||
|
{
|
||||||
|
SequenceId = sequence.Id,
|
||||||
|
Content = content,
|
||||||
|
Role = role
|
||||||
|
};
|
||||||
|
db.ThinkingThoughts.Add(thought);
|
||||||
|
await db.SaveChangesAsync();
|
||||||
|
|
||||||
|
// Invalidate cache for this sequence's thoughts
|
||||||
|
await cache.RemoveGroupAsync($"sequence:{sequence.Id}");
|
||||||
|
|
||||||
|
return thought;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<List<SnThinkingThought>> GetPreviousThoughtsAsync(SnThinkingSequence sequence)
|
||||||
|
{
|
||||||
|
var cacheKey = $"thoughts:{sequence.Id}";
|
||||||
|
var (found, cachedThoughts) = await cache.GetAsyncWithStatus<List<SnThinkingThought>>(cacheKey);
|
||||||
|
if (found && cachedThoughts != null)
|
||||||
|
{
|
||||||
|
return cachedThoughts;
|
||||||
|
}
|
||||||
|
|
||||||
|
var thoughts = await db.ThinkingThoughts
|
||||||
|
.Where(t => t.SequenceId == sequence.Id)
|
||||||
|
.OrderBy(t => t.CreatedAt)
|
||||||
|
.ToListAsync();
|
||||||
|
|
||||||
|
// Cache for 10 minutes
|
||||||
|
await cache.SetWithGroupsAsync(cacheKey, thoughts, new[] { $"sequence:{sequence.Id}" }, TimeSpan.FromMinutes(10));
|
||||||
|
|
||||||
|
return thoughts;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<(int total, List<SnThinkingSequence> sequences)> ListSequencesAsync(Guid accountId, int offset, int take)
|
||||||
|
{
|
||||||
|
var query = db.ThinkingSequences.Where(s => s.AccountId == accountId);
|
||||||
|
var totalCount = await query.CountAsync();
|
||||||
|
var sequences = await query
|
||||||
|
.OrderByDescending(s => s.CreatedAt)
|
||||||
|
.Skip(offset)
|
||||||
|
.Take(take)
|
||||||
|
.ToListAsync();
|
||||||
|
|
||||||
|
return (totalCount, sequences);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,7 +9,7 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Grpc.AspNetCore.Server" Version="2.71.0"/>
|
<PackageReference Include="Grpc.AspNetCore.Server" Version="2.71.0"/>
|
||||||
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="9.0.10" />
|
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="9.0.10" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.7">
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.10">
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
<PackageReference Include="Grpc.AspNetCore.Server" Version="2.71.0" />
|
<PackageReference Include="Grpc.AspNetCore.Server" Version="2.71.0" />
|
||||||
<PackageReference Include="MailKit" Version="4.13.0" />
|
<PackageReference Include="MailKit" Version="4.13.0" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="9.0.10" />
|
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="9.0.10" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.7">
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.10">
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
|
|||||||
32
DysonNetwork.Shared/Models/ThinkingSequence.cs
Normal file
32
DysonNetwork.Shared/Models/ThinkingSequence.cs
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using System.ComponentModel.DataAnnotations.Schema;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace DysonNetwork.Shared.Models;
|
||||||
|
|
||||||
|
public class SnThinkingSequence : ModelBase
|
||||||
|
{
|
||||||
|
public Guid Id { get; set; } = Guid.NewGuid();
|
||||||
|
[MaxLength(4096)] public string? Topic { get; set; }
|
||||||
|
|
||||||
|
public Guid AccountId { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum ThinkingThoughtRole
|
||||||
|
{
|
||||||
|
Assistant,
|
||||||
|
User
|
||||||
|
}
|
||||||
|
|
||||||
|
public class SnThinkingThought : ModelBase
|
||||||
|
{
|
||||||
|
public Guid Id { get; set; } = Guid.NewGuid();
|
||||||
|
public string? Content { get; set; }
|
||||||
|
|
||||||
|
[Column(TypeName = "jsonb")] public List<SnCloudFileReferenceObject> Files { get; set; } = [];
|
||||||
|
|
||||||
|
public ThinkingThoughtRole Role { get; set; }
|
||||||
|
|
||||||
|
public Guid SequenceId { get; set; }
|
||||||
|
[JsonIgnore] public SnThinkingSequence Sequence { get; set; } = null!;
|
||||||
|
}
|
||||||
@@ -21,7 +21,7 @@
|
|||||||
<PackageReference Include="Markdig" Version="0.41.3"/>
|
<PackageReference Include="Markdig" Version="0.41.3"/>
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="9.0.10" />
|
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="9.0.10" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="9.0.10" />
|
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="9.0.10" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.7">
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.10">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
|
|||||||
Reference in New Issue
Block a user