9
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Microsoft Power AppsAdvent Calendar 2024

Day 16

Power Appsでモダンなチャットアプリを作ってみる (1)

Last updated at Posted at 2024-12-15

はじめに

Power Apps Advent Calendar 2024 12月16日分。今回はPower AppsJSONファイルをデータソースにしたモダンなチャットアプリを作っていきたいと思います。

デザインは下記のようなPower Apps キャンバス アプリです。

image.png

画面デザインがそれらしいですね。HTML 文字列を活用しています。
またHTML 文字列の格納先は、高さが伸縮可能なギャラリー コントロールです。

階層構造
Chat Screen (画面)
└── conMainScreen (垂直コンテナー)
    └── conChatLayout (水平コンテナー)
        ├── conChatListSidebar (垂直コンテナー)
        │   ├── conUserProfileHeader (水平コンテナー)
        │   │   ├── imgUserProfile (画像)
        │   │   └── lblUserName (ラベル)
        │   ├── conNewChatHeader (水平コンテナー)
        │   │   ├── icoNewChat (アイコン)
        │   │   └── lblicoNewChat (ラベル)
        │   └── galChatList (Gallery)
        │       ├── lblCreatedTime (ラベル)
        │       ├── txtAvatar (テキスト入力)
        │       └── lblChatTitle (Label)
        └── conChatMain (垂直コンテナー)
            ├── conChatHeader (水平コンテナー)
            │   ├── icoChatHeader (画像)
            │   └── lblChatHeader (ラベル)
            ├── galMessages (高さが伸縮可能なギャラリー)
            │   └── htmlMessageContent (Html 文字列)
            └── conMessageInput (水平コンテナー)
                ├── inpMessageText (テキスト入力)
                └── btnSendMessage (ボタン)

モダン コントロールは、ボタン コントロールのみ利用しています。

今回のポイントはJSONファイルのコンテンツの書き込み、読み込みによるデータのやり取りになります。
Power Automateをつかった手法です。

上記を活用します。順番に見ていきます。

レイアウト作成案のコツ

最近の私のレイアウト案の進め方ですが、AIファーストで案出しを進めています。
CopilotChatGPTClaudeどれも非常に優秀です。
その中でもClaudeArtifacts機能があり、視覚的にアプリのレイアウトを提案してくれます。

レイアウトに自信がない方や、どのような形で作成するか迷った際には、こういった進め方もあるんだな、くらいに見ていただければ幸いです。

JSONファイル活用におけるデータ戦略

今回はチャットの履歴をJSONファイルそのものに格納します。内容は都度Power Automateで読み込む進め方です。

図解すると下記のようになります

あらためて必要な項目に目を向けてみます。
下記のような項目を挙げて進めてみることにします。

  • MessageID: 参照用に設けるGUID列
  • LastMessageText: 最後に投稿されたメッセージ
  • LastMessageTime: 最後に投稿された日時
  • LastSender: 最後に投稿したユーザー名
  • MessageCount: メッセージ数
  • CreatedBy: チャットを作成したユーザー名
  • CreatedTime: 作成日時
  • UpdatedTime: 更新日時
  • Subject: 会話タイトル
    • MessageIndex: メッセージの順番
    • SenderMailAddress: 送信元のメールアドレス
    • SenderName: 送信元のユーザー
    • TimeStamp: 送信日時

上記のうち、第一階層であるLastMessageText ~ Subjectまでは、SharePoint ドキュメント ライブラリの機能を活用し、メタデータとして列を設けます。

image.png

CreatedTimeUpdatedTimeはシステム列と内容がかぶりますが、メタデータとして今回は追加します。

列名 データ型
MessageID 1 行テキスト
LastMessageText 1 行テキスト
LastMessageTime 日付と時刻
LastSender 1 行テキスト
MessageCount 数値
CreatedBy 1 行テキスト
CreatedTime 日付と時刻
UpdatedTime 日付と時刻
Subject 1 行テキスト

そして、このメタデータが付記されたJSONファイルに、下記の値を追加していきます。

読み取り、書き込み対象のJSON ファイル
{
  "Log": [
    {
      "MessageIndex": "メッセージの順番",
      "SenderMailAddress": "送信元のメールアドレス",
      "SenderName": "送信元のユーザー",
      "TimeStamp": "送信日時"
    },
    {
      "MessageIndex": "メッセージの順番",
      "SenderMailAddress": "送信元のメールアドレス",
      "SenderName": "送信元のユーザー",
      "TimeStamp": "送信日時"
    } // 末尾に値を追加していくイメージ
  ]
}

海外のMVPのYouTubeを見ているとParseJSON 関数を使った大規模データ対応を見かけます。
それであれば、書き込み先そのものがJSONでいいのでは🧐という思想に基づいて作成しています。

アプリの機能

それではPower Appsの解説にうつります。このアプリのサイドパネルには、以下の項目が表示しています。

  • 自分自身の名前
  • チャットの履歴
  • チャット相手との詳細なやり取り

複数のチャットの履歴は、SharePointドキュメント ライブラリをデータソースにします。
ここのJSON ファイルが表示されるイメージです。

image.png

このギャラリー コントロールがクリックされた際に、JSONファイル コンテンツを読み込む処理を走らせます。

OnSelect
// ローカル変数に一意の値であるMessageIDを保持する
UpdateContext({locMessageID: ThisItem.MessageID});

// JSONファイルコンテンツをパースしてテーブルに変換する
ClearCollect(
    colChat,
    ForAll(
        Table(ParseJSON('get-json-content'.Run(locMessageID).response).Log) As Result,
        {
            MessageIndex: Value(Result.Value.MessageIndex),
            MessageText: Text(Result.Value.MessageText),
            SenderName: Text(Result.Value.SenderName),
            TimeStamp: DateTimeValue(Result.Value.TimeStamp)
        }
    )
);

チャットの内容

メッセージボックス、つまりチャット内容のやり取りについては、以下の関数を使用して、HTML文字列で表現します。

HtmlText
With(
    {
        avatarStyle: "width: 32px; height: 32px; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 14px;",
        messageWrapperBase: "max-width: 70%; margin-bottom: 16px; display: flex; gap: 8px;",
        messageBubbleBase: "padding: 8px 12px; border-radius: 8px; box-shadow: 0 1px 2px rgba(0,0,0,0.1);",
        nameStyle: "font-size: 12px; color: #666666; margin-bottom: 4px;",
        timeStyle: "font-size: 11px; color: #666666; text-align: right;"
    },
    Switch(
        Mod(ThisItem.MessageIndex, 2),
        0,
        $"<div style='{messageWrapperBase} margin-right: auto;'>
            <div style='{avatarStyle} background-color: #e0e0e0;'>
                {Left(ThisItem.SenderName, 2)}
            </div>
            <div>
                <div style='{nameStyle}'>{ThisItem.SenderName}</div>
                <div style='{messageBubbleBase} background-color: #ffffff;'>
                    <div style='margin-bottom: 4px;'>{ThisItem.MessageText}</div>
                    <div style='{timeStyle}'>{ThisItem.TimeStamp}</div>
                </div>
            </div>
        </div>",
        1,
        $"<div style='{messageWrapperBase} margin-left: auto; flex-direction: row-reverse;'>
            <div style='{avatarStyle} background-color: #128C7E; color: white;'>Me</div>
            <div>
                <div style='{nameStyle} text-align: right;'>自分</div>
                <div style='{messageBubbleBase} background-color: #dcf8c6;'>
                    <div style='margin-bottom: 4px;'>{ThisItem.MessageText}</div>
                    <div style='{timeStyle}'>{ThisItem.TimeStamp}</div>
                </div>
            </div>
        </div>"
    )
)

登場する関数は、下記の三つです。

これらを組み合わせ、HTML文字列を用いた高さの自動調整機能や伸縮可能なギャラリーを活用することで、動的なデザインを実現されます。

まったくローコードではないですね。AIの力を借りないと作成できません。

なぜHTML文字列を採用しているのか、理由はコントロール数が抑えられることにあります。
Power Apps キャンバスアプリに設定できるコントロール数には限りがあります。
デザインにこだわればこだわるほど、コントロール数は増加傾向にあり、自分のやりたいことと離れる可能性があります。

AI黎明期の今、Copilot in Edgeでも上記のような関数は提案してくださいます。
ぜひ活用を検討してみてください。

チャットの投稿パネル

チャットの投稿用にテキスト入力送信用のボタンを設けます。

image.png

こちらに後述するPower Automateのフローを追加します。

OnSelect
// コレクションのアイテムの数で、処理を分ける
If(
    CountRows(colChat) = 0,
    // 初回投稿
    ClearCollect(
        colChat,
        {
            MessageIndex: 1,
            SenderName: Office365ユーザー.MyProfileV2().displayName,
            MessageText: inpMessageText.Text,
            TimeStamp: Now()
        }
    );
    Collect(
        colChat,
        {
            MessageIndex: (CountRows(colChat) + 1),
            SenderName: "AI Builder",
            MessageText: 'AI Reply'.Predict(inpMessageText.Text).Text,
            TimeStamp: Now()
        }
    );
    // AI Builderのカスタムプロンプトでタイトルを作成する
    UpdateContext({locAiResponse: 'Custom-prompt-response'.Predict(inpMessageText.Text).StructuredOutput});
    ,
    // 初回投稿ではないときは、自分の投稿とAI Replyの値を設定する
    Collect(
        colChat,
        {
            MessageIndex: (CountRows(colChat) + 1),
            SenderName: Office365ユーザー.MyProfileV2().displayName,
            MessageText: inpMessageText.Text,
            TimeStamp: Now()
        }
    );
    Collect(
        colChat,
        {
            MessageIndex: (CountRows(colChat) + 1),
            SenderName: "AI Builder",
            MessageText: 'AI Reply'.Predict(inpMessageText.Text).Text,
            TimeStamp: Now()
        }
    );
    
);
// 初回の投稿と最後の投稿をWith関数に格納
With(
    {
        FirstPost: First(colChat),
        LastPost: Last(colChat)
    },
    // JSON文字列用にオブジェクトを設定
    UpdateContext(
        {
            spMetaData: {
                MessageID: Coalesce(
                    locMessageID,
                    Text(GUID())
                ),
                LastMessageText: LastPost.MessageText,
                LastMessageTime: LastPost.TimeStamp,
                LastSender: LastPost.SenderName,
                MessageCount: CountRows(colChat),
                CreateBy: FirstPost.SenderName,
                CreatedTime: FirstPost.TimeStamp,
                UpdatedTime: LastPost.TimeStamp,
                Subject: Coalesce(
                    locAiResponse.response,
                    galChatList.Selected.Subject
                )
            }
        }
    )
);
// Power Automateを実施、初回データ作成時用にMessageIdを戻す
UpdateContext(
    {
        locMessageID: 'post-json-chat'.Run(
            JSON(spMetaData),
            JSON(
                {Log: Table(colChat)},
                JSONFormat.Compact
            )
        ).messageid
    }
);
// テキスト入力をクリアする
Reset(inpMessageText);

// Filter関数で引数に設定しているギャラリーコントロール 最新化用の値
UpdateContext({locFilter: false});
UpdateContext({locFilter: true});

後述しますが、JSON ファイルの中身は、Power Appsから都度設定し、Power Automateに送る流れです。

JSON 関数を使って、チャットのやり取りを構造化された文字列に変換しましょう。

JSON ファイルにユーザーを表すプロパティを用意しています。ユーザー列に該当するSenderMailAddressSenderNameを文字列に設定しているのは、プロフィールの画像データなど不要な列を含めてしまうことを避けるためです。

AI Builderの活用

この関数の中でAI Builderを利用しています。
まずチャットの返信はAIReplyで実施しています。

GPTによる返答の恩恵が受けられます。
単純に応答を返すだけの関数ですね。

またチャットの要約、タイトルを設定するためにカスタム プロンプトを利用します。

AI ハブから簡単にJSONで構造化された応答を返すことが可能です。

image.png

プロンプト ビルダーを使用して JSON で GPT の出力形式を定義します。

プロンプト
下記はチャットを開始するときの文言です。内容から会話の内容を決定し、20文字ほどの一文に要約してください。また要約した内容からチャットのアイコンを作成します。16進法で文字色と背景色を提案してください。
 - 文章は一文でまとめてください
 - 体言止めを用いてください
 - 句読点は出力に含めないでください

### チャット
{引数のチャット} 

image.png

引数は右上の画面から設定します。
テキストだけではなく、画像データソースの利用も可能です。

出力はJSONを指定して

image.png

下記のように加工しやすい形に変換します。

image.png

戻り値が構造的になるため、非常に便利です。
こちらの詳細はMicrosoft learnに譲らせてください。

ここからPower Automateの解説といきたいところですが・・・

ここまでで結構長くなってしまいました。

Power Automateは次回投稿に内容を譲ることにします。
お楽しみに!

9
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?