journal/Journal.Core/Services/Conversations/ConversationService.cs
Jacob Schmidt 192e6e3891 feat: add AI coaching, conversation persistence, and LLamaSharp integration
- Add Journal.AI project with LLamaSharp-based AI service (Phi-3 model)
- Implement coach sessions (daily check-in, evening review, weekly review)
- Add conversation CRUD with SQLCipher persistence
- AI chat with full conversation history for context-aware replies
- Frontend: CoachPanel, AI stores, conversation stores, side panel UI
- Conversation list with create, rename, and delete support
- Fix Phi-3 output quality (system prompt leaking, token cleanup, JSON filtering)
- Fix CREATEDRAFT kind override in coach sessions

Co-Authored-By: Oz <oz-agent@warp.dev>
2026-03-01 16:07:59 -06:00

69 lines
2.2 KiB
C#

using System.ComponentModel.DataAnnotations;
using Journal.Core.Dtos;
using Journal.Core.Models;
using Journal.Core.Repositories;
namespace Journal.Core.Services.Conversations;
public class ConversationService(IConversationRepository repo) : IConversationService
{
private readonly IConversationRepository _repo = repo ?? throw new ArgumentNullException(nameof(repo));
private static ConversationDto MapSummary(Conversation c) => new(c.Id, c.Title, c.CreatedAt, c.UpdatedAt);
private static ConversationMessageDto MapMessage(ConversationMessage m) => new(m.Id, m.Role, m.Text, m.CreatedAt);
public List<ConversationDto> GetAll()
{
var items = _repo.GetAll();
return [.. items.Select(MapSummary)];
}
public ConversationDetailDto? GetById(Guid id)
{
var c = _repo.GetById(id);
if (c is null) return null;
var messages = _repo.GetMessages(id);
return new ConversationDetailDto(
c.Id, c.Title,
[.. messages.Select(MapMessage)],
c.CreatedAt, c.UpdatedAt);
}
public ConversationDto Create(CreateConversationDto dto)
{
ArgumentNullException.ThrowIfNull(dto);
var ctx = new ValidationContext(dto);
Validator.ValidateObject(dto, ctx, validateAllProperties: true);
var conversation = new Conversation(dto.Title);
_repo.Add(conversation);
return MapSummary(conversation);
}
public bool Update(Guid id, UpdateConversationDto dto)
{
ArgumentNullException.ThrowIfNull(dto);
if (dto.Title is not null && string.IsNullOrWhiteSpace(dto.Title))
throw new ValidationException("Title cannot be empty");
return _repo.Update(id, dto.Title?.Trim());
}
public bool Remove(Guid id) => _repo.Remove(id);
public ConversationMessageDto AddMessage(Guid conversationId, string role, string text)
{
var message = new ConversationMessage(conversationId, role, text);
_repo.AddMessage(message);
return MapMessage(message);
}
public List<ConversationMessageDto> GetMessages(Guid conversationId)
{
var messages = _repo.GetMessages(conversationId);
return [.. messages.Select(MapMessage)];
}
}