Public Access
Update meeting summary agent UI
This commit is contained in:
@@ -857,15 +857,13 @@ public sealed class WorkflowRulesEditorTests
|
||||
Assert.Equal("", viewModel.Draft);
|
||||
Assert.Equal(
|
||||
[new WorkflowRulesEditorChatMessage(WorkflowRulesEditorChatRole.User, "Rename ABS Daily")],
|
||||
viewModel.Messages.ToArray());
|
||||
viewModel.Messages.Select(item => item.Message).OfType<WorkflowRulesEditorChatMessage>().ToArray());
|
||||
|
||||
pipeline.Release.SetResult();
|
||||
await sendTask.WaitAsync(TimeSpan.FromSeconds(2));
|
||||
|
||||
Assert.False(viewModel.IsThinking);
|
||||
Assert.Equal(2, viewModel.Messages.Count);
|
||||
Assert.Equal(WorkflowRulesEditorChatRole.Agent, viewModel.Messages[1].Role);
|
||||
Assert.Equal("Updated rules.", viewModel.Messages[1].Content);
|
||||
AssertCompletedActivity(viewModel, ["Thinking..."], "Updated rules.");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -881,20 +879,19 @@ public sealed class WorkflowRulesEditorTests
|
||||
|
||||
Assert.False(viewModel.IsThinking);
|
||||
Assert.Equal("", viewModel.Draft);
|
||||
Assert.Equal(
|
||||
[
|
||||
new WorkflowRulesEditorChatMessage(WorkflowRulesEditorChatRole.User, "check logs"),
|
||||
new WorkflowRulesEditorChatMessage(
|
||||
WorkflowRulesEditorChatRole.Agent,
|
||||
"Settings and logs failed: The request timed out.")
|
||||
],
|
||||
viewModel.Messages.ToArray());
|
||||
Assert.Equal(new WorkflowRulesEditorChatMessage(WorkflowRulesEditorChatRole.User, "check logs"), viewModel.Messages[0].Message);
|
||||
AssertCompletedActivity(
|
||||
viewModel,
|
||||
["Thinking..."],
|
||||
"Meeting Summary Agent failed: The request timed out.");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ViewModelShowsReconnectStatusReportedByPipeline()
|
||||
{
|
||||
var pipeline = new StatusReportingRulesEditorPipeline("Updated rules.");
|
||||
var pipeline = new ReportingRulesEditorPipeline(
|
||||
"Updated rules.",
|
||||
WorkflowRulesEditorActivityUpdate.Status("Reconnecting..."));
|
||||
var viewModel = new WorkflowRulesEditorChatViewModel(pipeline)
|
||||
{
|
||||
Draft = "check logs"
|
||||
@@ -911,7 +908,146 @@ public sealed class WorkflowRulesEditorTests
|
||||
|
||||
Assert.False(viewModel.IsThinking);
|
||||
Assert.Equal("Thinking...", viewModel.ActivityMessage);
|
||||
Assert.Equal("Updated rules.", viewModel.Messages[1].Content);
|
||||
AssertCompletedActivity(viewModel, ["Thinking...", "Reconnecting..."], "Updated rules.");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ViewModelShowsToolCallsAbovePersistentThinkingLine()
|
||||
{
|
||||
var pipeline = new ReportingRulesEditorPipeline(
|
||||
"Updated rules.",
|
||||
WorkflowRulesEditorActivityUpdate.ToolCall("read_logs"));
|
||||
var viewModel = new WorkflowRulesEditorChatViewModel(pipeline)
|
||||
{
|
||||
Draft = "check logs"
|
||||
};
|
||||
|
||||
var sendTask = viewModel.SendAsync();
|
||||
await pipeline.Reported.Task.WaitAsync(TimeSpan.FromSeconds(2));
|
||||
|
||||
Assert.True(viewModel.IsThinking);
|
||||
Assert.Equal("Thinking...", viewModel.ActivityMessage);
|
||||
Assert.Equal(["Called tool: read_logs"], viewModel.ActivityMessages.ToArray());
|
||||
|
||||
pipeline.Release.SetResult();
|
||||
await sendTask.WaitAsync(TimeSpan.FromSeconds(2));
|
||||
|
||||
Assert.False(viewModel.IsThinking);
|
||||
Assert.Empty(viewModel.ActivityMessages);
|
||||
AssertCompletedActivity(viewModel, ["Thinking...", "Called tool: read_logs"], "Updated rules.");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ViewModelShowsVisibleThinkingOutputInActivityExpander()
|
||||
{
|
||||
var pipeline = new ReportingRulesEditorPipeline(
|
||||
"Updated rules.",
|
||||
WorkflowRulesEditorActivityUpdate.Thinking("Checked the recent logs."),
|
||||
WorkflowRulesEditorActivityUpdate.ToolCall("read_logs"),
|
||||
WorkflowRulesEditorActivityUpdate.Thinking("Matched the rule to the user request."));
|
||||
var viewModel = new WorkflowRulesEditorChatViewModel(pipeline)
|
||||
{
|
||||
Draft = "check logs"
|
||||
};
|
||||
|
||||
var sendTask = viewModel.SendAsync();
|
||||
await pipeline.Reported.Task.WaitAsync(TimeSpan.FromSeconds(2));
|
||||
|
||||
Assert.True(viewModel.IsThinking);
|
||||
Assert.Equal("Thinking...", viewModel.ActivityMessage);
|
||||
Assert.Equal(
|
||||
[
|
||||
"Checked the recent logs.",
|
||||
"Called tool: read_logs",
|
||||
"Matched the rule to the user request."
|
||||
],
|
||||
viewModel.ActivityMessages.ToArray());
|
||||
|
||||
pipeline.Release.SetResult();
|
||||
await sendTask.WaitAsync(TimeSpan.FromSeconds(2));
|
||||
|
||||
Assert.False(viewModel.IsThinking);
|
||||
Assert.Empty(viewModel.ActivityMessages);
|
||||
AssertCompletedActivity(
|
||||
viewModel,
|
||||
[
|
||||
"Checked the recent logs.",
|
||||
"Called tool: read_logs",
|
||||
"Matched the rule to the user request."
|
||||
],
|
||||
"Updated rules.");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ViewModelMarshalsBackgroundActivityUpdatesToCapturedContext()
|
||||
{
|
||||
var previousContext = SynchronizationContext.Current;
|
||||
var context = new RecordingSynchronizationContext();
|
||||
var pipeline = new BackgroundReportingRulesEditorPipeline(
|
||||
"Updated rules.",
|
||||
WorkflowRulesEditorActivityUpdate.Thinking("Checked the recent logs."));
|
||||
var viewModel = new WorkflowRulesEditorChatViewModel(pipeline)
|
||||
{
|
||||
Draft = "check logs"
|
||||
};
|
||||
|
||||
SynchronizationContext.SetSynchronizationContext(context);
|
||||
var sendTask = viewModel.SendAsync();
|
||||
SynchronizationContext.SetSynchronizationContext(previousContext);
|
||||
await pipeline.Reported.Task.WaitAsync(TimeSpan.FromSeconds(2));
|
||||
|
||||
Assert.True(viewModel.IsThinking);
|
||||
Assert.Equal(["Checked the recent logs."], viewModel.ActivityMessages.ToArray());
|
||||
Assert.Equal(1, context.SendCount);
|
||||
|
||||
pipeline.Release.SetResult();
|
||||
await sendTask.WaitAsync(TimeSpan.FromSeconds(2));
|
||||
|
||||
AssertCompletedActivity(viewModel, ["Checked the recent logs."], "Updated rules.");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ViewModelKeepsActivityItemsOutOfModelConversation()
|
||||
{
|
||||
var pipeline = new BlockingRulesEditorPipeline("Updated rules.");
|
||||
var viewModel = new WorkflowRulesEditorChatViewModel(pipeline)
|
||||
{
|
||||
Draft = "first"
|
||||
};
|
||||
|
||||
var firstSend = viewModel.SendAsync();
|
||||
await pipeline.Started.Task.WaitAsync(TimeSpan.FromSeconds(2));
|
||||
pipeline.Release.SetResult();
|
||||
await firstSend.WaitAsync(TimeSpan.FromSeconds(2));
|
||||
|
||||
pipeline.Reset("Second response.");
|
||||
viewModel.Draft = "second";
|
||||
var secondSend = viewModel.SendAsync();
|
||||
await pipeline.Started.Task.WaitAsync(TimeSpan.FromSeconds(2));
|
||||
|
||||
Assert.Equal(
|
||||
[
|
||||
new WorkflowRulesEditorChatMessage(WorkflowRulesEditorChatRole.User, "first"),
|
||||
new WorkflowRulesEditorChatMessage(WorkflowRulesEditorChatRole.Agent, "Updated rules.")
|
||||
],
|
||||
pipeline.LastConversation);
|
||||
|
||||
pipeline.Release.SetResult();
|
||||
await secondSend.WaitAsync(TimeSpan.FromSeconds(2));
|
||||
}
|
||||
|
||||
private static void AssertCompletedActivity(
|
||||
WorkflowRulesEditorChatViewModel viewModel,
|
||||
IReadOnlyList<string> expectedActivityLines,
|
||||
string expectedAgentMessage)
|
||||
{
|
||||
Assert.Equal(3, viewModel.Messages.Count);
|
||||
Assert.Equal(WorkflowRulesEditorConversationItemKind.Activity, viewModel.Messages[1].Kind);
|
||||
Assert.StartsWith("Worked for ", viewModel.Messages[1].Content);
|
||||
Assert.Equal(expectedActivityLines, viewModel.Messages[1].ActivityLines);
|
||||
Assert.Equal(WorkflowRulesEditorConversationItemKind.Message, viewModel.Messages[2].Kind);
|
||||
Assert.Equal(WorkflowRulesEditorChatRole.Agent, viewModel.Messages[2].Message?.Role);
|
||||
Assert.Equal(expectedAgentMessage, viewModel.Messages[2].Message?.Content);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
@@ -1196,31 +1332,36 @@ public sealed class WorkflowRulesEditorTests
|
||||
|
||||
private sealed class BlockingRulesEditorPipeline : IWorkflowRulesEditorChatPipeline
|
||||
{
|
||||
private readonly string response;
|
||||
private string response;
|
||||
|
||||
public BlockingRulesEditorPipeline(string response)
|
||||
{
|
||||
this.response = response;
|
||||
}
|
||||
|
||||
public TaskCompletionSource Started { get; } = new(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||
public TaskCompletionSource Started { get; private set; } = new(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||
|
||||
public TaskCompletionSource Release { get; } = new(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||
public TaskCompletionSource Release { get; private set; } = new(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||
|
||||
public IReadOnlyList<WorkflowRulesEditorChatMessage> LastConversation { get; private set; } = [];
|
||||
|
||||
public void Reset(string nextResponse)
|
||||
{
|
||||
response = nextResponse;
|
||||
Started = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||
Release = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||
}
|
||||
|
||||
public async Task<WorkflowRulesEditorChatResult> SendAsync(
|
||||
IReadOnlyList<WorkflowRulesEditorChatMessage> conversation,
|
||||
string userMessage,
|
||||
CancellationToken cancellationToken,
|
||||
Action<string>? statusChanged = null)
|
||||
Action<WorkflowRulesEditorActivityUpdate>? activityChanged = null)
|
||||
{
|
||||
LastConversation = conversation;
|
||||
Started.SetResult();
|
||||
await Release.Task.WaitAsync(cancellationToken);
|
||||
return new WorkflowRulesEditorChatResult(
|
||||
response,
|
||||
conversation
|
||||
.Append(new WorkflowRulesEditorChatMessage(WorkflowRulesEditorChatRole.User, userMessage))
|
||||
.Append(new WorkflowRulesEditorChatMessage(WorkflowRulesEditorChatRole.Agent, response))
|
||||
.ToList());
|
||||
return new WorkflowRulesEditorChatResult(response);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1237,19 +1378,23 @@ public sealed class WorkflowRulesEditorTests
|
||||
IReadOnlyList<WorkflowRulesEditorChatMessage> conversation,
|
||||
string userMessage,
|
||||
CancellationToken cancellationToken,
|
||||
Action<string>? statusChanged = null)
|
||||
Action<WorkflowRulesEditorActivityUpdate>? activityChanged = null)
|
||||
{
|
||||
throw exception;
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class StatusReportingRulesEditorPipeline : IWorkflowRulesEditorChatPipeline
|
||||
private sealed class ReportingRulesEditorPipeline : IWorkflowRulesEditorChatPipeline
|
||||
{
|
||||
private readonly string response;
|
||||
private readonly IReadOnlyList<WorkflowRulesEditorActivityUpdate> updates;
|
||||
|
||||
public StatusReportingRulesEditorPipeline(string response)
|
||||
public ReportingRulesEditorPipeline(
|
||||
string response,
|
||||
params WorkflowRulesEditorActivityUpdate[] updates)
|
||||
{
|
||||
this.response = response;
|
||||
this.updates = updates;
|
||||
}
|
||||
|
||||
public TaskCompletionSource Reported { get; } = new(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||
@@ -1260,17 +1405,57 @@ public sealed class WorkflowRulesEditorTests
|
||||
IReadOnlyList<WorkflowRulesEditorChatMessage> conversation,
|
||||
string userMessage,
|
||||
CancellationToken cancellationToken,
|
||||
Action<string>? statusChanged = null)
|
||||
Action<WorkflowRulesEditorActivityUpdate>? activityChanged = null)
|
||||
{
|
||||
statusChanged?.Invoke("Reconnecting...");
|
||||
foreach (var update in updates)
|
||||
{
|
||||
activityChanged?.Invoke(update);
|
||||
}
|
||||
|
||||
Reported.SetResult();
|
||||
await Release.Task.WaitAsync(cancellationToken);
|
||||
return new WorkflowRulesEditorChatResult(
|
||||
response,
|
||||
conversation
|
||||
.Append(new WorkflowRulesEditorChatMessage(WorkflowRulesEditorChatRole.User, userMessage))
|
||||
.Append(new WorkflowRulesEditorChatMessage(WorkflowRulesEditorChatRole.Agent, response))
|
||||
.ToList());
|
||||
return new WorkflowRulesEditorChatResult(response);
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class BackgroundReportingRulesEditorPipeline : IWorkflowRulesEditorChatPipeline
|
||||
{
|
||||
private readonly string response;
|
||||
private readonly WorkflowRulesEditorActivityUpdate update;
|
||||
|
||||
public BackgroundReportingRulesEditorPipeline(
|
||||
string response,
|
||||
WorkflowRulesEditorActivityUpdate update)
|
||||
{
|
||||
this.response = response;
|
||||
this.update = update;
|
||||
}
|
||||
|
||||
public TaskCompletionSource Reported { get; } = new(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||
|
||||
public TaskCompletionSource Release { get; } = new(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||
|
||||
public async Task<WorkflowRulesEditorChatResult> SendAsync(
|
||||
IReadOnlyList<WorkflowRulesEditorChatMessage> conversation,
|
||||
string userMessage,
|
||||
CancellationToken cancellationToken,
|
||||
Action<WorkflowRulesEditorActivityUpdate>? activityChanged = null)
|
||||
{
|
||||
await Task.Run(() => activityChanged?.Invoke(update), cancellationToken);
|
||||
Reported.SetResult();
|
||||
await Release.Task.WaitAsync(cancellationToken);
|
||||
return new WorkflowRulesEditorChatResult(response);
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class RecordingSynchronizationContext : SynchronizationContext
|
||||
{
|
||||
public int SendCount { get; private set; }
|
||||
|
||||
public override void Send(SendOrPostCallback callback, object? state)
|
||||
{
|
||||
SendCount++;
|
||||
callback(state);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user