diff --git a/source/server/Karamem0.Commistant.Bot/Bots/ActivityBot.cs b/source/server/Karamem0.Commistant.Bot/Bots/ActivityBot.cs index f355dd6..22f3e0c 100644 --- a/source/server/Karamem0.Commistant.Bot/Bots/ActivityBot.cs +++ b/source/server/Karamem0.Commistant.Bot/Bots/ActivityBot.cs @@ -104,27 +104,15 @@ protected override async Task OnMessageActivityAsync(ITurnContext>(async () => + var arguments = await this.openAIService.GetArgumentsAsync(command, cancellationToken: cancellationToken); + var result = arguments?.Type switch { - var message = await this.openAIService.ChatCompletionAsync(command, cancellationToken: cancellationToken); - if (message.FinishReason == ChatFinishReason.ToolCalls) - { - var arguments = message.ToolCalls.Select(item => item.FunctionArguments).First(); - var content = JsonSerializer.Deserialize(arguments?.ToString()); - return content?.Type switch - { - "会議開始後" => await dc.BeginDialogAsync(nameof(StartMeetingDialog), content, cancellationToken: cancellationToken), - "会議終了前" => await dc.BeginDialogAsync(nameof(EndMeetingDialog), content, cancellationToken: cancellationToken), - "会議中" => await dc.BeginDialogAsync(nameof(InMeetingDialog), content, cancellationToken: cancellationToken), - "初期化" => await dc.BeginDialogAsync(nameof(ResetDialog), cancellationToken: cancellationToken), - _ => null, - }; - } - else - { - return null; - } - })(); + "会議開始後" => await dc.BeginDialogAsync(nameof(StartMeetingDialog), arguments, cancellationToken: cancellationToken), + "会議終了前" => await dc.BeginDialogAsync(nameof(EndMeetingDialog), arguments, cancellationToken: cancellationToken), + "会議中" => await dc.BeginDialogAsync(nameof(InMeetingDialog), arguments, cancellationToken: cancellationToken), + "初期化" => await dc.BeginDialogAsync(nameof(ResetDialog), cancellationToken: cancellationToken), + _ => null, + }; if (result is null) { _ = await turnContext.SendActivityAsync( diff --git a/source/server/Karamem0.Commistant.Bot/Dialogs/EndMeetingDialog.cs b/source/server/Karamem0.Commistant.Bot/Dialogs/EndMeetingDialog.cs index c339b25..7c1c592 100644 --- a/source/server/Karamem0.Commistant.Bot/Dialogs/EndMeetingDialog.cs +++ b/source/server/Karamem0.Commistant.Bot/Dialogs/EndMeetingDialog.cs @@ -105,6 +105,7 @@ private async Task BeforeConfirmAsync(WaterfallStepContext ste new AdaptiveTextInput() { Id = "Message", + IsMultiline = true, Label = "メッセージ", Placeholder = "会議後に表示されるメッセージ", Style = AdaptiveTextInputStyle.Text, @@ -172,18 +173,12 @@ private async Task AfterConrifmAsync(WaterfallStepContext step property.EndMeetingMessage = value.Value("Message", null); property.EndMeetingUrl = value.Value("Url", null); this.logger.SettingsUpdated(stepContext.Context.Activity); - _ = await stepContext.Context.SendActivityAsync( - "設定を変更しました。", - cancellationToken: cancellationToken - ); + _ = await stepContext.Context.SendSettingsUpdatedAsync(cancellationToken); } if (value.Value("Button") == "Cancel") { this.logger.SettingsCancelled(stepContext.Context.Activity); - _ = await stepContext.Context.SendActivityAsync( - "キャンセルしました。設定は変更されていません。", - cancellationToken: cancellationToken - ); + _ = await stepContext.Context.SendSettingsCancelledAsync(cancellationToken); } if (stepContext.Context.Activity.ReplyToId is not null) { @@ -191,46 +186,116 @@ private async Task AfterConrifmAsync(WaterfallStepContext step { Body = [ - new AdaptiveFactSet() + new AdaptiveColumnSet() { - Facts = + Columns = [ - new() + new AdaptiveColumn() { - Title = "スケジュール", - Value = new Func(() => - property.EndMeetingSchedule switch + Items = + [ + new AdaptiveTextBlock() { - -1 => "なし", - 0 => "予定時刻", - _ => $"{property.EndMeetingSchedule} 分後" + Text = "スケジュール", + Weight = AdaptiveTextWeight.Bolder } - )() + ], + Width = "90px" }, - new() + new AdaptiveColumn() { - Title = "メッセージ", - Value = $"{property.EndMeetingMessage}" + Items = + [ + new AdaptiveTextBlock() + { + Text = property.EndMeetingSchedule switch + { + -1 => "なし", + 0 => "予定時刻", + _ => $"{property.EndMeetingSchedule} 分前" + } + } + ], + Width = "stretch" + } + ], + }, + new AdaptiveColumnSet() + { + Columns = + [ + new AdaptiveColumn() + { + Items = + [ + new AdaptiveTextBlock() + { + Text = "メッセージ", + Weight = AdaptiveTextWeight.Bolder + } + ], + Width = "90px" }, - new() + new AdaptiveColumn() { - Title = "URL", - Value = $"{property.EndMeetingUrl}" + Items = + [ + new AdaptiveTextBlock() + { + Text = $"{property.EndMeetingMessage}", + Wrap = true + } + ], + Width = "stretch" + } + ] + }, + new AdaptiveColumnSet() + { + Columns = + [ + new AdaptiveColumn() + { + Items = + [ + new AdaptiveTextBlock() + { + Text = "URL", + Weight = AdaptiveTextWeight.Bolder + } + ], + Width = "90px" + }, + new AdaptiveColumn() + { + Items = + [ + new AdaptiveTextBlock() + { + Text = $"{property.EndMeetingUrl}" + } + ], + Width = "stretch" } ] } ] }; - if (property.EndMeetingUrl is not null) + if (Uri.TryCreate(property.EndMeetingUrl, UriKind.Absolute, out var url)) { - var bytes = await this.qrCodeService.CreateAsync(property.EndMeetingUrl, cancellationToken); + var bytes = await this.qrCodeService.CreateAsync(url.ToString(), cancellationToken); var base64 = Convert.ToBase64String(bytes); card.Body.Add(new AdaptiveImage() { - AltText = property.EndMeetingUrl, - Size = AdaptiveImageSize.Stretch, + AltText = url.ToString(), + Size = AdaptiveImageSize.Large, Url = new Uri($"data:image/png;base64,{base64}") }); + card.Actions.Add(new AdaptiveOpenUrlAction() + { + Title = "URL を開く", + Url = url, + }); } var activity = MessageFactory.Attachment(new Attachment() { @@ -238,10 +303,7 @@ private async Task AfterConrifmAsync(WaterfallStepContext step Content = JsonConvert.DeserializeObject(card.ToJson()) }); activity.Id = stepContext.Context.Activity.ReplyToId; - _ = await stepContext.Context.UpdateActivityAsync( - activity, - cancellationToken: cancellationToken - ); + _ = await stepContext.Context.UpdateActivityAsync(activity, cancellationToken); } return await stepContext.EndDialogAsync(cancellationToken: cancellationToken); } diff --git a/source/server/Karamem0.Commistant.Bot/Dialogs/InMeetingDialog.cs b/source/server/Karamem0.Commistant.Bot/Dialogs/InMeetingDialog.cs index de5e7ae..eb167d2 100644 --- a/source/server/Karamem0.Commistant.Bot/Dialogs/InMeetingDialog.cs +++ b/source/server/Karamem0.Commistant.Bot/Dialogs/InMeetingDialog.cs @@ -110,6 +110,7 @@ private async Task BeforeConfirmAsync(WaterfallStepContext ste new AdaptiveTextInput() { Id = "Message", + IsMultiline = true, Label = "メッセージ", Placeholder = "会議中に表示されるメッセージ", Style = AdaptiveTextInputStyle.Text, @@ -177,10 +178,12 @@ private async Task AfterConrifmAsync(WaterfallStepContext step property.InMeetingMessage = value.Value("Message", null); property.InMeetingUrl = value.Value("Url", null); this.logger.SettingsUpdated(stepContext.Context.Activity); - _ = await stepContext.Context.SendActivityAsync( - "設定を変更しました。", - cancellationToken: cancellationToken - ); + _ = await stepContext.Context.SendSettingsUpdatedAsync(cancellationToken); + } + if (value.Value("Button") == "Cancel") + { + this.logger.SettingsCancelled(stepContext.Context.Activity); + _ = await stepContext.Context.SendSettingsCancelledAsync(cancellationToken); } if (stepContext.Context.Activity.ReplyToId is not null) { @@ -188,45 +191,116 @@ private async Task AfterConrifmAsync(WaterfallStepContext step { Body = [ - new AdaptiveFactSet() + new AdaptiveColumnSet() { - Facts = + Columns = [ - new() + new AdaptiveColumn() { - Title = "スケジュール", - Value = new Func(() => - property.InMeetingSchedule switch + Items = + [ + new AdaptiveTextBlock() { - -1 => "なし", - _ => $"{property.InMeetingSchedule} 分" + Text = "スケジュール", + Weight = AdaptiveTextWeight.Bolder } - )() + ], + Width = "90px" }, - new() + new AdaptiveColumn() { - Title = "メッセージ", - Value = $"{property.InMeetingMessage}" + Items = + [ + new AdaptiveTextBlock() + { + Text = property.InMeetingSchedule switch + { + -1 => "なし", + 0 => "予定時刻", + _ => $"{property.InMeetingSchedule} 分ごと" + } + } + ], + Width = "stretch" + } + ], + }, + new AdaptiveColumnSet() + { + Columns = + [ + new AdaptiveColumn() + { + Items = + [ + new AdaptiveTextBlock() + { + Text = "メッセージ", + Weight = AdaptiveTextWeight.Bolder + } + ], + Width = "90px" }, - new() + new AdaptiveColumn() { - Title = "URL", - Value = $"{property.InMeetingUrl}" + Items = + [ + new AdaptiveTextBlock() + { + Text = $"{property.InMeetingMessage}", + Wrap = true + } + ], + Width = "stretch" + } + ] + }, + new AdaptiveColumnSet() + { + Columns = + [ + new AdaptiveColumn() + { + Items = + [ + new AdaptiveTextBlock() + { + Text = "URL", + Weight = AdaptiveTextWeight.Bolder + } + ], + Width = "90px" + }, + new AdaptiveColumn() + { + Items = + [ + new AdaptiveTextBlock() + { + Text = $"{property.InMeetingUrl}" + } + ], + Width = "stretch" } ] } ] }; - if (property.InMeetingUrl is not null) + if (Uri.TryCreate(property.InMeetingUrl, UriKind.Absolute, out var url)) { - var bytes = await this.qrCodeService.CreateAsync(property.InMeetingUrl, cancellationToken); + var bytes = await this.qrCodeService.CreateAsync(url.ToString(), cancellationToken); var base64 = Convert.ToBase64String(bytes); card.Body.Add(new AdaptiveImage() { - AltText = property.InMeetingUrl, - Size = AdaptiveImageSize.Stretch, + AltText = url.ToString(), + Size = AdaptiveImageSize.Large, Url = new Uri($"data:image/png;base64,{base64}") }); + card.Actions.Add(new AdaptiveOpenUrlAction() + { + Title = "URL を開く", + Url = url, + }); } var activity = MessageFactory.Attachment(new Attachment() { @@ -234,10 +308,7 @@ private async Task AfterConrifmAsync(WaterfallStepContext step Content = JsonConvert.DeserializeObject(card.ToJson()) }); activity.Id = stepContext.Context.Activity.ReplyToId; - _ = await stepContext.Context.UpdateActivityAsync( - activity, - cancellationToken: cancellationToken - ); + _ = await stepContext.Context.UpdateActivityAsync(activity, cancellationToken); } return await stepContext.EndDialogAsync(cancellationToken: cancellationToken); } diff --git a/source/server/Karamem0.Commistant.Bot/Dialogs/ResetDialog.cs b/source/server/Karamem0.Commistant.Bot/Dialogs/ResetDialog.cs index 3d2191d..33a53b5 100644 --- a/source/server/Karamem0.Commistant.Bot/Dialogs/ResetDialog.cs +++ b/source/server/Karamem0.Commistant.Bot/Dialogs/ResetDialog.cs @@ -7,6 +7,7 @@ // using AdaptiveCards; +using Karamem0.Commistant.Extensions; using Karamem0.Commistant.Logging; using Karamem0.Commistant.Models; using Karamem0.Commistant.Validators; @@ -106,20 +107,15 @@ private async Task AfterConrifmAsync(WaterfallStepContext step } if (value.Value("Button") == "Yes") { - this.logger.SettingsReseted(stepContext.Context.Activity); var accessor = this.conversationState.CreateProperty(nameof(ConversationProperty)); await accessor.SetAsync(stepContext.Context, new(), cancellationToken); - _ = await stepContext.Context.SendActivityAsync( - "設定を初期化しました。", - cancellationToken: cancellationToken - ); + this.logger.SettingsReseted(stepContext.Context.Activity); + _ = await stepContext.Context.SendSettingsResetedAsync(cancellationToken); } if (value.Value("Button") == "No") { this.logger.SettingsCancelled(stepContext.Context.Activity); - _ = await stepContext.Context.SendActivityAsync( - "キャンセルしました。設定は変更されていません。", - cancellationToken: cancellationToken); + _ = await stepContext.Context.SendSettingsCancelledAsync(cancellationToken); } if (stepContext.Context.Activity.ReplyToId is not null) { @@ -134,14 +130,12 @@ private async Task AfterConrifmAsync(WaterfallStepContext step new() { Title = "応答", - Value = new Func(() => - value.Value("Button") switch - { - "Yes" => "はい", - "No" => "いいえ", - _ => "" - } - )() + Value = value.Value("Button") switch + { + "Yes" => "はい", + "No" => "いいえ", + _ => "" + } } ] } @@ -153,10 +147,7 @@ private async Task AfterConrifmAsync(WaterfallStepContext step Content = JsonConvert.DeserializeObject(card.ToJson()) }); activity.Id = stepContext.Context.Activity.ReplyToId; - _ = await stepContext.Context.UpdateActivityAsync( - activity, - cancellationToken: cancellationToken - ); + _ = await stepContext.Context.UpdateActivityAsync(activity, cancellationToken); } return await stepContext.EndDialogAsync(cancellationToken: cancellationToken); } diff --git a/source/server/Karamem0.Commistant.Bot/Dialogs/StartMeetingDialog.cs b/source/server/Karamem0.Commistant.Bot/Dialogs/StartMeetingDialog.cs index 5594a77..dcd1f4c 100644 --- a/source/server/Karamem0.Commistant.Bot/Dialogs/StartMeetingDialog.cs +++ b/source/server/Karamem0.Commistant.Bot/Dialogs/StartMeetingDialog.cs @@ -105,6 +105,7 @@ private async Task BeforeConfirmAsync(WaterfallStepContext ste new AdaptiveTextInput() { Id = "Message", + IsMultiline = true, Label = "メッセージ", Placeholder = "開始後に表示されるメッセージ", Style = AdaptiveTextInputStyle.Text, @@ -172,18 +173,12 @@ private async Task AfterConrifmAsync(WaterfallStepContext step property.StartMeetingMessage = value.Value("Message", null); property.StartMeetingUrl = value.Value("Url", null); this.logger.SettingsUpdated(stepContext.Context.Activity); - _ = await stepContext.Context.SendActivityAsync( - "設定を変更しました。", - cancellationToken: cancellationToken - ); + _ = await stepContext.Context.SendSettingsUpdatedAsync(cancellationToken); } if (value.Value("Button") == "Cancel") { this.logger.SettingsCancelled(stepContext.Context.Activity); - _ = await stepContext.Context.SendActivityAsync( - "キャンセルしました。設定は変更されていません。", - cancellationToken: cancellationToken - ); + _ = await stepContext.Context.SendSettingsCancelledAsync(cancellationToken); } if (stepContext.Context.Activity.ReplyToId is not null) { @@ -191,46 +186,116 @@ private async Task AfterConrifmAsync(WaterfallStepContext step { Body = [ - new AdaptiveFactSet() + new AdaptiveColumnSet() { - Facts = + Columns = [ - new() + new AdaptiveColumn() { - Title = "スケジュール", - Value = new Func(() => - property.StartMeetingSchedule switch + Items = + [ + new AdaptiveTextBlock() { - -1 => "なし", - 0 => "予定時刻", - _ => $"{property.StartMeetingSchedule} 分後" + Text = "スケジュール", + Weight = AdaptiveTextWeight.Bolder } - )() + ], + Width = "90px" }, - new() + new AdaptiveColumn() { - Title = "メッセージ", - Value = $"{property.StartMeetingMessage}" + Items = + [ + new AdaptiveTextBlock() + { + Text = property.StartMeetingSchedule switch + { + -1 => "なし", + 0 => "予定時刻", + _ => $"{property.StartMeetingSchedule} 分後" + } + } + ], + Width = "stretch" + } + ], + }, + new AdaptiveColumnSet() + { + Columns = + [ + new AdaptiveColumn() + { + Items = + [ + new AdaptiveTextBlock() + { + Text = "メッセージ", + Weight = AdaptiveTextWeight.Bolder + } + ], + Width = "90px" }, - new() + new AdaptiveColumn() { - Title = "URL", - Value = $"{property.StartMeetingUrl}" + Items = + [ + new AdaptiveTextBlock() + { + Text = $"{property.StartMeetingMessage}", + Wrap = true + } + ], + Width = "stretch" + } + ] + }, + new AdaptiveColumnSet() + { + Columns = + [ + new AdaptiveColumn() + { + Items = + [ + new AdaptiveTextBlock() + { + Text = "URL", + Weight = AdaptiveTextWeight.Bolder + } + ], + Width = "90px" + }, + new AdaptiveColumn() + { + Items = + [ + new AdaptiveTextBlock() + { + Text = $"{property.StartMeetingUrl}" + } + ], + Width = "stretch" } ] } ] }; - if (property.StartMeetingUrl is not null) + if (Uri.TryCreate(property.StartMeetingUrl, UriKind.Absolute, out var url)) { - var bytes = await this.qrCodeService.CreateAsync(property.StartMeetingUrl, cancellationToken); + var bytes = await this.qrCodeService.CreateAsync(url.ToString(), cancellationToken); var base64 = Convert.ToBase64String(bytes); card.Body.Add(new AdaptiveImage() { - AltText = property.StartMeetingUrl, - Size = AdaptiveImageSize.Stretch, + AltText = url.ToString(), + Size = AdaptiveImageSize.Large, Url = new Uri($"data:image/png;base64,{base64}") }); + card.Actions.Add(new AdaptiveOpenUrlAction() + { + Title = "URL を開く", + Url = url, + }); } var activity = MessageFactory.Attachment(new Attachment() { @@ -238,10 +303,7 @@ private async Task AfterConrifmAsync(WaterfallStepContext step Content = JsonConvert.DeserializeObject(card.ToJson()) }); activity.Id = stepContext.Context.Activity.ReplyToId; - _ = await stepContext.Context.UpdateActivityAsync( - activity, - cancellationToken: cancellationToken - ); + _ = await stepContext.Context.UpdateActivityAsync(activity, cancellationToken); } return await stepContext.EndDialogAsync(cancellationToken: cancellationToken); } diff --git a/source/server/Karamem0.Commistant.Bot/Extensions/TurnContextExtensions.cs b/source/server/Karamem0.Commistant.Bot/Extensions/TurnContextExtensions.cs new file mode 100644 index 0000000..459ba79 --- /dev/null +++ b/source/server/Karamem0.Commistant.Bot/Extensions/TurnContextExtensions.cs @@ -0,0 +1,50 @@ +// +// Copyright (c) 2022-2024 karamem0 +// +// This software is released under the MIT License. +// +// https://github.com/karamem0/commistant/blob/main/LICENSE +// + +using AdaptiveCards; +using Microsoft.Bot.Builder; +using Microsoft.Bot.Builder.Dialogs; +using Microsoft.Bot.Schema; +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace Karamem0.Commistant.Extensions; + +public static class TurnContextExtensions +{ + + public static async Task SendSettingsCancelledAsync(this ITurnContext target, CancellationToken cancellationToken = default) + { + return await target.SendActivityAsync( + "キャンセルしました。設定は変更されていません。", + cancellationToken: cancellationToken + ); + } + + public static async Task SendSettingsResetedAsync(this ITurnContext target, CancellationToken cancellationToken = default) + { + return await target.SendActivityAsync( + "設定を初期化しました。", + cancellationToken: cancellationToken + ); + } + + public static async Task SendSettingsUpdatedAsync(this ITurnContext target, CancellationToken cancellationToken = default) + { + return await target.SendActivityAsync( + "設定を変更しました。", + cancellationToken: cancellationToken + ); + } + +} diff --git a/source/server/Karamem0.Commistant.Bot/Mappings/AutoMapperProfile.cs b/source/server/Karamem0.Commistant.Bot/Mappings/AutoMapperProfile.cs index c28b6ad..80bb00a 100644 --- a/source/server/Karamem0.Commistant.Bot/Mappings/AutoMapperProfile.cs +++ b/source/server/Karamem0.Commistant.Bot/Mappings/AutoMapperProfile.cs @@ -33,19 +33,19 @@ public AutoMapperProfile() o => { o.Condition(s => s.Type == "会議開始後"); - o.MapFrom((s, _) => + o.MapFrom((s, d) => { if (s.Value is null) { - return -1; + return d.StartMeetingSchedule; } if (s.Value.Enabled is false) { - return -1; + return d.StartMeetingSchedule; } if (Array.TrueForAll(StartMeetingSchedules, _ => _ != s.Value.Schedule)) { - return -1; + return d.StartMeetingSchedule; } return s.Value.Schedule; }); @@ -56,19 +56,19 @@ public AutoMapperProfile() o => { o.Condition(s => s.Type == "会議開始後"); - o.MapFrom((s, _) => + o.MapFrom((s, d) => { if (s.Value is null) { - return null; + return d.StartMeetingMessage; } if (s.Value.Enabled is false) { - return null; + return d.StartMeetingMessage; } if (string.IsNullOrEmpty(s.Value.Message)) { - return null; + return d.StartMeetingMessage; } return s.Value.Message; }); @@ -79,19 +79,19 @@ public AutoMapperProfile() o => { o.Condition(s => s.Type == "会議開始後"); - o.MapFrom((s, _) => + o.MapFrom((s, d) => { if (s.Value is null) { - return null; + return d.StartMeetingUrl; } if (s.Value.Enabled is false) { - return null; + return d.StartMeetingUrl; } if (string.IsNullOrEmpty(s.Value.Url)) { - return null; + return d.StartMeetingUrl; } return s.Value.Url; }); @@ -102,19 +102,19 @@ public AutoMapperProfile() o => { o.Condition(s => s.Type == "会議終了前"); - o.MapFrom((s, _) => + o.MapFrom((s, d) => { if (s.Value is null) { - return -1; + return d.EndMeetingSchedule; } if (s.Value.Enabled is false) { - return -1; + return d.EndMeetingSchedule; } if (Array.TrueForAll(EndMeetingSchedules, _ => _ != s.Value.Schedule)) { - return -1; + return d.EndMeetingSchedule; } return s.Value.Schedule; }); @@ -125,19 +125,19 @@ public AutoMapperProfile() o => { o.Condition(s => s.Type == "会議終了前"); - o.MapFrom((s, _) => + o.MapFrom((s, d) => { if (s.Value is null) { - return null; + return d.EndMeetingMessage; } if (s.Value.Enabled is false) { - return null; + return d.EndMeetingMessage; } if (string.IsNullOrEmpty(s.Value.Message)) { - return null; + return d.EndMeetingMessage; } return s.Value.Message; }); @@ -148,19 +148,19 @@ public AutoMapperProfile() o => { o.Condition(s => s.Type == "会議終了前"); - o.MapFrom((s, _) => + o.MapFrom((s, d) => { if (s.Value is null) { - return null; + return d.EndMeetingUrl; } if (s.Value.Enabled is false) { - return null; + return d.EndMeetingUrl; } if (string.IsNullOrEmpty(s.Value.Url)) { - return null; + return d.EndMeetingUrl; } return s.Value.Url; }); @@ -171,19 +171,19 @@ public AutoMapperProfile() o => { o.Condition(s => s.Type == "会議中"); - o.MapFrom((s, _) => + o.MapFrom((s, d) => { if (s.Value is null) { - return -1; + return d.InMeetingSchedule; } if (s.Value.Enabled is false) { - return -1; + return d.InMeetingSchedule; } if (Array.TrueForAll(InMeetingSchedules, _ => _ != s.Value.Schedule)) { - return -1; + return d.InMeetingSchedule; } return s.Value.Schedule; }); @@ -194,19 +194,19 @@ public AutoMapperProfile() o => { o.Condition(s => s.Type == "会議中"); - o.MapFrom((s, _) => + o.MapFrom((s, d) => { if (s.Value is null) { - return null; + return d.InMeetingMessage; } if (s.Value.Enabled is false) { - return null; + return d.InMeetingMessage; } if (string.IsNullOrEmpty(s.Value.Message)) { - return null; + return d.InMeetingMessage; } return s.Value.Message; }); @@ -217,19 +217,19 @@ public AutoMapperProfile() o => { o.Condition(s => s.Type == "会議中"); - o.MapFrom((s, _) => + o.MapFrom((s, d) => { if (s.Value is null) { - return null; + return d.InMeetingUrl; } if (s.Value.Enabled is false) { - return null; + return d.InMeetingUrl; } if (string.IsNullOrEmpty(s.Value.Url)) { - return null; + return d.InMeetingUrl; } return s.Value.Url; }); diff --git a/source/server/Karamem0.Commistant.Common/Extensions/BlobClientExtension.cs b/source/server/Karamem0.Commistant.Common/Extensions/BlobClientExtension.cs index d6e2d46..5919a4f 100644 --- a/source/server/Karamem0.Commistant.Common/Extensions/BlobClientExtension.cs +++ b/source/server/Karamem0.Commistant.Common/Extensions/BlobClientExtension.cs @@ -37,9 +37,9 @@ public static async Task> GetObjectAsync(this BlobClient targe public static async Task SetObjectAsync(this BlobClient target, BlobContent value) { - using var stream = new MemoryStream(Encoding.UTF8.GetBytes(JsonSerializer.Serialize(value.Data))); + var content = BinaryData.FromString(JsonSerializer.Serialize(value.Data)); _ = await target.UploadAsync( - stream, + content, new BlobUploadOptions() { Conditions = new BlobRequestConditions() diff --git a/source/server/Karamem0.Commistant.Common/Serialization/JsonSerializer.cs b/source/server/Karamem0.Commistant.Common/Serialization/JsonSerializer.cs index 9c6f4ca..cbbb636 100644 --- a/source/server/Karamem0.Commistant.Common/Serialization/JsonSerializer.cs +++ b/source/server/Karamem0.Commistant.Common/Serialization/JsonSerializer.cs @@ -25,19 +25,13 @@ public static class JsonSerializer public static T? Deserialize(string? value) { - if (value is null) - { - throw new ArgumentNullException(nameof(value)); - } + _ = value ?? throw new ArgumentNullException(nameof(value)); return JsonConvert.DeserializeObject(value, settings); } public static string Serialize(T? value) { - if (value is null) - { - throw new ArgumentNullException(nameof(value)); - } + _ = value ?? throw new ArgumentNullException(nameof(value)); return JsonConvert.SerializeObject(value, settings); } diff --git a/source/server/Karamem0.Commistant.Common/Services/OpenAIService.cs b/source/server/Karamem0.Commistant.Common/Services/OpenAIService.cs index b0d91be..2d61619 100644 --- a/source/server/Karamem0.Commistant.Common/Services/OpenAIService.cs +++ b/source/server/Karamem0.Commistant.Common/Services/OpenAIService.cs @@ -7,7 +7,9 @@ // using Azure.AI.OpenAI; +using Karamem0.Commistant.Models; using Karamem0.Commistant.Resources; +using Newtonsoft.Json; using OpenAI; using OpenAI.Chat; using System; @@ -22,7 +24,7 @@ namespace Karamem0.Commistant.Services; public interface IOpenAIService { - Task ChatCompletionAsync(string text, CancellationToken cancellationToken = default); + Task GetArgumentsAsync(string text, CancellationToken cancellationToken = default); } @@ -33,7 +35,7 @@ public class OpenAIService(OpenAIClient openAIClient, string openAIModelName) : private readonly string openAIModelName = openAIModelName; - public async Task ChatCompletionAsync(string text, CancellationToken cancellationToken = default) + public async Task GetArgumentsAsync(string text, CancellationToken cancellationToken = default) { var chatCompletionsOptions = new ChatCompletionOptions() { @@ -69,7 +71,16 @@ public async Task ChatCompletionAsync(string text, CancellationT chatCompletionsOptions, cancellationToken ); - return chatCompletion.Value; + if (chatCompletion.Value.FinishReason == ChatFinishReason.ToolCalls) + { + return JsonConvert.DeserializeObject( + chatCompletion.Value.ToolCalls + .Select(item => item.FunctionArguments) + .First() + .ToString() + ); + } + return null; } } diff --git a/source/server/Karamem0.Commistant.Functions/Commands/EndMeetingCommand.cs b/source/server/Karamem0.Commistant.Functions/Commands/EndMeetingCommand.cs index 6d00d89..3d17624 100644 --- a/source/server/Karamem0.Commistant.Functions/Commands/EndMeetingCommand.cs +++ b/source/server/Karamem0.Commistant.Functions/Commands/EndMeetingCommand.cs @@ -48,7 +48,7 @@ public override async Task ExecuteAsync( CancellationToken cancellationToken = default ) { - if (property.InMeeting is not true) + if (property.InMeeting is false) { return; } @@ -79,7 +79,7 @@ public override async Task ExecuteAsync( { this.logger.EndMeetingMessageNotifying(reference, property); var card = new AdaptiveCard("1.3"); - if (string.IsNullOrEmpty(property.EndMeetingMessage) is not true) + if (string.IsNullOrEmpty(property.EndMeetingMessage) is false) { card.Body.Add(new AdaptiveTextBlock() { @@ -87,16 +87,21 @@ public override async Task ExecuteAsync( Wrap = true }); } - if (string.IsNullOrEmpty(property.EndMeetingUrl) is not true) + if (Uri.TryCreate(property.EndMeetingUrl, UriKind.Absolute, out var url)) { - var bytes = await this.qrCodeService.CreateAsync(property.EndMeetingUrl, cancellationToken); + var bytes = await this.qrCodeService.CreateAsync(url.ToString(), cancellationToken); var base64 = Convert.ToBase64String(bytes); card.Body.Add(new AdaptiveImage() { - AltText = property.InMeetingUrl, + AltText = url.ToString(), Size = AdaptiveImageSize.Stretch, Url = new Uri($"data:image/png;base64,{base64}") }); + card.Actions.Add(new AdaptiveOpenUrlAction() + { + Title = "URL を開く", + Url = url, + }); } var activity = MessageFactory.Attachment(new Attachment() { @@ -109,7 +114,7 @@ public override async Task ExecuteAsync( _ = await this.connectorClientService.SendActivityAsync( new Uri(reference.ServiceUrl), (Activity)activity, - cancellationToken: cancellationToken + cancellationToken ); } finally diff --git a/source/server/Karamem0.Commistant.Functions/Commands/InMeetingCommand.cs b/source/server/Karamem0.Commistant.Functions/Commands/InMeetingCommand.cs index c5cf7cf..3e22f6d 100644 --- a/source/server/Karamem0.Commistant.Functions/Commands/InMeetingCommand.cs +++ b/source/server/Karamem0.Commistant.Functions/Commands/InMeetingCommand.cs @@ -48,7 +48,7 @@ public override async Task ExecuteAsync( CancellationToken cancellationToken = default ) { - if (property.InMeeting is not true) + if (property.InMeeting is false) { return; } @@ -75,7 +75,7 @@ public override async Task ExecuteAsync( { this.logger.InMeetingMessageNotifying(reference, property); var card = new AdaptiveCard("1.3"); - if (string.IsNullOrEmpty(property.InMeetingMessage) is not true) + if (string.IsNullOrEmpty(property.InMeetingMessage) is false) { card.Body.Add(new AdaptiveTextBlock() { @@ -83,16 +83,21 @@ public override async Task ExecuteAsync( Wrap = true }); } - if (string.IsNullOrEmpty(property.InMeetingUrl) is not true) + if (Uri.TryCreate(property.InMeetingUrl, UriKind.Absolute, out var url)) { - var bytes = await this.qrCodeService.CreateAsync(property.InMeetingUrl); + var bytes = await this.qrCodeService.CreateAsync(url.ToString(), cancellationToken); var base64 = Convert.ToBase64String(bytes); card.Body.Add(new AdaptiveImage() { - AltText = property.InMeetingUrl, + AltText = url.ToString(), Size = AdaptiveImageSize.Stretch, Url = new Uri($"data:image/png;base64,{base64}") }); + card.Actions.Add(new AdaptiveOpenUrlAction() + { + Title = "URL を開く", + Url = url, + }); } var activity = MessageFactory.Attachment(new Attachment() { @@ -105,7 +110,7 @@ public override async Task ExecuteAsync( _ = await this.connectorClientService.SendActivityAsync( new Uri(reference.ServiceUrl), (Activity)activity, - cancellationToken: cancellationToken + cancellationToken ); } finally diff --git a/source/server/Karamem0.Commistant.Functions/Commands/StartMeetingCommand.cs b/source/server/Karamem0.Commistant.Functions/Commands/StartMeetingCommand.cs index b946203..8ddaf2b 100644 --- a/source/server/Karamem0.Commistant.Functions/Commands/StartMeetingCommand.cs +++ b/source/server/Karamem0.Commistant.Functions/Commands/StartMeetingCommand.cs @@ -48,7 +48,7 @@ public override async Task ExecuteAsync( CancellationToken cancellationToken = default ) { - if (property.InMeeting is not true) + if (property.InMeeting is false) { return; } @@ -79,7 +79,7 @@ public override async Task ExecuteAsync( { this.logger.StartMeetingMessageNotifying(reference, property); var card = new AdaptiveCard("1.3"); - if (string.IsNullOrEmpty(property.StartMeetingMessage) is not true) + if (string.IsNullOrEmpty(property.StartMeetingMessage) is false) { card.Body.Add(new AdaptiveTextBlock() { @@ -87,16 +87,21 @@ public override async Task ExecuteAsync( Wrap = true }); } - if (string.IsNullOrEmpty(property.StartMeetingUrl) is not true) + if (Uri.TryCreate(property.StartMeetingUrl, UriKind.Absolute, out var url)) { - var bytes = await this.qrCodeService.CreateAsync(property.StartMeetingUrl); + var bytes = await this.qrCodeService.CreateAsync(url.ToString(), cancellationToken); var base64 = Convert.ToBase64String(bytes); card.Body.Add(new AdaptiveImage() { - AltText = property.InMeetingUrl, + AltText = url.ToString(), Size = AdaptiveImageSize.Stretch, Url = new Uri($"data:image/png;base64,{base64}") }); + card.Actions.Add(new AdaptiveOpenUrlAction() + { + Title = "URL を開く", + Url = url, + }); } var activity = MessageFactory.Attachment(new Attachment() { @@ -109,7 +114,7 @@ public override async Task ExecuteAsync( _ = await this.connectorClientService.SendActivityAsync( new Uri(reference.ServiceUrl), (Activity)activity, - cancellationToken: cancellationToken + cancellationToken ); } finally