Public Access
180 lines
5.7 KiB
C#
180 lines
5.7 KiB
C#
using System.Collections.ObjectModel;
|
|
using System.Diagnostics;
|
|
|
|
namespace MeetingAssistant.Workflow;
|
|
|
|
public sealed class WorkflowRulesEditorChatViewModel
|
|
{
|
|
private const string ThinkingPlaceholder = "Thinking...";
|
|
private readonly IWorkflowRulesEditorChatPipeline pipeline;
|
|
|
|
public WorkflowRulesEditorChatViewModel(IWorkflowRulesEditorChatPipeline pipeline)
|
|
{
|
|
this.pipeline = pipeline;
|
|
}
|
|
|
|
public ObservableCollection<WorkflowRulesEditorConversationItem> Messages { get; } = [];
|
|
|
|
public ObservableCollection<string> ActivityMessages { get; } = [];
|
|
|
|
public string Draft { get; set; } = "";
|
|
|
|
public bool IsThinking { get; private set; }
|
|
|
|
public string ActivityMessage { get; private set; } = ThinkingPlaceholder;
|
|
|
|
public event EventHandler? Changed;
|
|
|
|
public async Task SendAsync(CancellationToken cancellationToken = default)
|
|
{
|
|
var prompt = Draft.Trim();
|
|
if (string.IsNullOrWhiteSpace(prompt) || IsThinking)
|
|
{
|
|
return;
|
|
}
|
|
|
|
Draft = "";
|
|
var startedAt = Stopwatch.GetTimestamp();
|
|
var activityLines = new List<string>();
|
|
var hasVisibleThinking = false;
|
|
var activityContext = SynchronizationContext.Current;
|
|
var priorConversation = Messages
|
|
.Select(item => item.Message)
|
|
.OfType<WorkflowRulesEditorChatMessage>()
|
|
.ToList();
|
|
Messages.Add(WorkflowRulesEditorConversationItem.ChatMessage(
|
|
new WorkflowRulesEditorChatMessage(WorkflowRulesEditorChatRole.User, prompt)));
|
|
ActivityMessages.Clear();
|
|
IsThinking = true;
|
|
ActivityMessage = ThinkingPlaceholder;
|
|
OnChanged();
|
|
|
|
try
|
|
{
|
|
var result = await pipeline.SendAsync(
|
|
priorConversation,
|
|
prompt,
|
|
cancellationToken,
|
|
update => hasVisibleThinking |= ApplyActivityUpdate(activityContext, update, activityLines));
|
|
AddCompletedActivity(startedAt, activityLines, hasVisibleThinking);
|
|
Messages.Add(WorkflowRulesEditorConversationItem.ChatMessage(
|
|
new WorkflowRulesEditorChatMessage(
|
|
WorkflowRulesEditorChatRole.Agent,
|
|
string.IsNullOrWhiteSpace(result.Response)
|
|
? "(No response text returned.)"
|
|
: result.Response.Trim())));
|
|
}
|
|
catch (Exception exception) when (exception is not OperationCanceledException || !cancellationToken.IsCancellationRequested)
|
|
{
|
|
AddCompletedActivity(startedAt, activityLines, hasVisibleThinking);
|
|
Messages.Add(WorkflowRulesEditorConversationItem.ChatMessage(
|
|
new WorkflowRulesEditorChatMessage(
|
|
WorkflowRulesEditorChatRole.Agent,
|
|
$"Meeting Summary Agent failed: {exception.Message}")));
|
|
}
|
|
finally
|
|
{
|
|
IsThinking = false;
|
|
ActivityMessages.Clear();
|
|
ActivityMessage = ThinkingPlaceholder;
|
|
OnChanged();
|
|
}
|
|
}
|
|
|
|
private bool ApplyActivityUpdate(
|
|
SynchronizationContext? context,
|
|
WorkflowRulesEditorActivityUpdate update,
|
|
List<string> activityLines)
|
|
{
|
|
if (context is null || SynchronizationContext.Current == context)
|
|
{
|
|
return ApplyActivityUpdate(update, activityLines);
|
|
}
|
|
|
|
var hasVisibleThinking = false;
|
|
Exception? exception = null;
|
|
context.Send(
|
|
_ =>
|
|
{
|
|
try
|
|
{
|
|
hasVisibleThinking = ApplyActivityUpdate(update, activityLines);
|
|
}
|
|
catch (Exception caught)
|
|
{
|
|
exception = caught;
|
|
}
|
|
},
|
|
null);
|
|
|
|
if (exception is not null)
|
|
{
|
|
throw exception;
|
|
}
|
|
|
|
return hasVisibleThinking;
|
|
}
|
|
|
|
private bool ApplyActivityUpdate(
|
|
WorkflowRulesEditorActivityUpdate update,
|
|
List<string> activityLines)
|
|
{
|
|
if (!IsThinking || string.IsNullOrWhiteSpace(update.Text))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
var hasVisibleThinking = false;
|
|
if (update.Kind == WorkflowRulesEditorActivityKind.ToolCall)
|
|
{
|
|
var line = $"Called tool: {update.Text.Trim()}";
|
|
ActivityMessages.Add(line);
|
|
activityLines.Add(line);
|
|
}
|
|
else if (update.Kind == WorkflowRulesEditorActivityKind.Thinking)
|
|
{
|
|
var line = update.Text.Trim();
|
|
ActivityMessages.Add(line);
|
|
activityLines.Add(line);
|
|
hasVisibleThinking = true;
|
|
}
|
|
else
|
|
{
|
|
var line = update.Text.Trim();
|
|
ActivityMessage = line;
|
|
activityLines.Add(line);
|
|
}
|
|
|
|
OnChanged();
|
|
return hasVisibleThinking;
|
|
}
|
|
|
|
private void AddCompletedActivity(
|
|
long startedAt,
|
|
IReadOnlyList<string> activityLines,
|
|
bool hasVisibleThinking)
|
|
{
|
|
var completedActivityLines = hasVisibleThinking
|
|
? activityLines.ToArray()
|
|
: new[] { ThinkingPlaceholder }.Concat(activityLines).ToArray();
|
|
Messages.Add(WorkflowRulesEditorConversationItem.Activity(
|
|
$"Worked for {FormatDuration(Stopwatch.GetElapsedTime(startedAt))}",
|
|
completedActivityLines));
|
|
}
|
|
|
|
private static string FormatDuration(TimeSpan duration)
|
|
{
|
|
if (duration.TotalMinutes >= 1)
|
|
{
|
|
return $"{(int)duration.TotalMinutes}m {duration.Seconds}s";
|
|
}
|
|
|
|
return $"{Math.Max(0, (int)Math.Round(duration.TotalSeconds))}s";
|
|
}
|
|
|
|
private void OnChanged()
|
|
{
|
|
Changed?.Invoke(this, EventArgs.Empty);
|
|
}
|
|
}
|