153 lines
5.3 KiB
C#
153 lines
5.3 KiB
C#
using DysonNetwork.Shared.Cache;
|
|
using DysonNetwork.Shared.Models;
|
|
using DysonNetwork.Shared.Proto;
|
|
using Microsoft.EntityFrameworkCore;
|
|
using PaymentService = DysonNetwork.Shared.Proto.PaymentService;
|
|
using TransactionType = DysonNetwork.Shared.Proto.TransactionType;
|
|
using WalletService = DysonNetwork.Shared.Proto.WalletService;
|
|
|
|
namespace DysonNetwork.Insight.Thought;
|
|
|
|
public class ThoughtService(AppDatabase db, ICacheService cache, PaymentService.PaymentServiceClient paymentService, WalletService.WalletServiceClient walletService)
|
|
{
|
|
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,
|
|
List<SnThinkingChunk>? chunks = null,
|
|
string? model = null
|
|
)
|
|
{
|
|
// Approximate token count (1 token ≈ 4 characters for GPT-like models)
|
|
var tokenCount = content?.Length / 4 ?? 0;
|
|
|
|
var thought = new SnThinkingThought
|
|
{
|
|
SequenceId = sequence.Id,
|
|
Content = content,
|
|
Role = role,
|
|
TokenCount = tokenCount,
|
|
ModelName = model,
|
|
Chunks = chunks ?? new List<SnThinkingChunk>()
|
|
};
|
|
db.ThinkingThoughts.Add(thought);
|
|
|
|
// Update sequence total tokens only for assistant responses
|
|
if (role == ThinkingThoughtRole.Assistant)
|
|
sequence.TotalToken += tokenCount;
|
|
|
|
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)
|
|
.OrderByDescending(t => t.CreatedAt)
|
|
.ToListAsync();
|
|
|
|
// Cache for 10 minutes
|
|
await cache.SetWithGroupsAsync(cacheKey, thoughts, [$"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);
|
|
}
|
|
|
|
public async Task SettleThoughtBills(ILogger logger)
|
|
{
|
|
var sequences = await db.ThinkingSequences
|
|
.Where(s => s.PaidToken < s.TotalToken)
|
|
.ToListAsync();
|
|
|
|
if (sequences.Count == 0)
|
|
{
|
|
logger.LogInformation("No unpaid sequences found.");
|
|
return;
|
|
}
|
|
|
|
// Group by account
|
|
var groupedByAccount = sequences.GroupBy(s => s.AccountId);
|
|
|
|
foreach (var accountGroup in groupedByAccount)
|
|
{
|
|
var accountId = accountGroup.Key;
|
|
var totalUnpaidTokens = accountGroup.Sum(s => s.TotalToken - s.PaidToken);
|
|
var cost = (long)Math.Ceiling(totalUnpaidTokens / 1000.0);
|
|
|
|
if (cost == 0) continue;
|
|
|
|
try
|
|
{
|
|
var walletResponse = await walletService.GetWalletAsync(new GetWalletRequest { AccountId = accountId.ToString() });
|
|
var walletId = Guid.Parse(walletResponse.Id);
|
|
|
|
var date = DateTime.Now.ToString("yyyy-MM-dd");
|
|
await paymentService.CreateTransactionAsync(new CreateTransactionRequest
|
|
{
|
|
PayerWalletId = walletId.ToString(),
|
|
PayeeWalletId = null,
|
|
Currency = WalletCurrency.SourcePoint,
|
|
Amount = cost.ToString(),
|
|
Remarks = $"Wage for SN-chan on {date}",
|
|
Type = TransactionType.System
|
|
});
|
|
|
|
// Mark all sequences for this account as paid
|
|
foreach (var sequence in accountGroup)
|
|
sequence.PaidToken = sequence.TotalToken;
|
|
|
|
logger.LogInformation("Billed {cost} points for account {accountId}", cost, accountId);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
logger.LogError(ex, "Error billing for account {accountId}", accountId);
|
|
}
|
|
}
|
|
|
|
await db.SaveChangesAsync();
|
|
}
|
|
}
|