GPT-4oとPower Appsで食材の写真から料理とレシピを提供するアプリをサクッと作る

# はじめに
昨日、**[Power AppsでGPT-4oを用いた画像から中身を判別するアプリを作成しました。](https://qiita.com/DEmodoriGatsuO/items/4c4734b78973970e0d3d)**
驚きの手軽さで作れることができます。

https://qiita.com/DEmodoriGatsuO/items/4c4734b78973970e0d3d

今回は[作成のもととなった記事](https://qiita.com/sakasegawa/items/45947b0a1c1abb963d0a)に挑戦し、**食材の写真から料理を提案するアプリ**をサクっと作っていきましょう！

https://qiita.com/sakasegawa/items/45947b0a1c1abb963d0a


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2734332/12118cd5-77aa-b478-8a6b-c68acbe4e9c8.png)

# 流れ
昨日作成した`Power Automate フロー`の`プロンプト`を変えれば、ほとんどできてしまいます。
`プロンプト`を変えます。

```json:作成アクション
{
  "model": "gpt-4o",
  "messages": [
    {
      "role": "system",
      "content": "あなたはプロの栄養士です。提供された画像から食材を分析し、作成できる料理を推定してください。"
    },
    {
      "role": "user",
      "content": [
        {
          "type": "image_url",
          "image_url": {
            "url": "@{triggerBody()?['text']}",
            "detail": "high"
          }
        },
        {
          "type": "text",
          "text": "画像にある食材から料理を提案してください。料理の名前、レシピ、カロリー、食材を推定し回答してください。調理にかかる時間も教えてください。回答は日本語でしてください。## JSON Schema {\"type\": \"object\",\"properties\": {\"cooking\": {\"type\":\"string\", \"description\": \"提案される料理名\"}},{ \"recipe\": {\"type\": \"string\", \"description\": \"料理のレシピ\"}},{\"calorie\": {\"type\": \"string\", \"description\": \"カロリー\"}},{\"ingredient\": {\"type\": \"string\", \"description\": \"食材\"}},{\"hour\": {\"type\": \"string\", \"description\": \"調理にかかる時間\"}},\"required\": [\"cooking\",\"recipe\",\"calorie\",\"ingredient\",\"hour\"],}"
        }
      ]
    }
  ],
  "temperature": 1,
  "response_format": {
    "type": "json_object"
  }
}
```

上記の中のコチラ↓

```plaintext:JSONのなかで定義をしているJSON Schema
## JSON Schema {\"type\": \"object\",\"properties\": {\"cooking\": {\"type\":\"string\", \"description\": \"提案される料理名\"}},{ \"recipe\": {\"type\": \"string\", \"description\": \"料理のレシピ\"}},{\"calorie\": {\"type\": \"string\", \"description\": \"カロリー\"}},{\"ingredient\": {\"type\": \"string\", \"description\": \"食材\"}},{\"hour\": {\"type\": \"string\", \"description\": \"調理にかかる時間\"}},\"required\": [\"cooking\",\"recipe\",\"calorie\",\"ingredient\",\"hour\"],}"
```
の箇所です。

`properties`に戻り値を追加します。

| キー | データ型 | 内容 |
|---|---|---|
| cooking | string | 提案される料理名 |
| recipe | string | 料理のレシピ |
| calorie | string | カロリー |
| ingredient | string | 食材 |
| hour | string | 調理にかかる時間 |

`JSON`のなかで`JSONスキーマ`を定義するため、**ダブルクオーテーション` " `** のまえに、 **エスケープ文字` \ `** が入ります。
ひじょーに書きづらいですね…。

**こういうのもGPT-4oにお願いしちゃいましょう！**

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2734332/ac2a2dc8-8bc3-a580-3584-8259287c61c3.png)

**すげー！**

`GPT-4o`に渡す`JSON`の定義は、`作成アクション`で実施します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2734332/de1c0564-7296-513b-f2dc-73cd64128bbd.png)

作成アクションで記載する中でも、JSONの形式が異常であればエラーを表示するため、誤りを見つけやすいです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2734332/18e9ba16-fcab-9809-967f-3f00a545be96.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2734332/e822d22b-cbf9-a4ef-c9fb-c6e6df9081c2.png)


中身の文字列が変わっただけで、やっていることは一緒ですね。

1. `PowerApps (V2) トリガー`で、`画像のbase64文字列`を、`Power Apps`から受け取る
1. `作成アクション`で、`GPT-4o`に渡す値を定義
1. `Azure Key Vault`から`APIキー`を取得
1. `GPT-4o`に`HTTP要求`を送信
1. 結果をPower Appsに返す

さらっと上手くいってしまう・・・。
Power Appsのデザインに時間を投入しましょう。

前回と戻り値が異なるため、`JSON の解析`アクションでスキーマを再定義する必要があります。
今回の例では下記の通りとなります。

```json:Schema
{
    "type": "object",
    "properties": {
        "cooking": {
            "type": "string"
        },
        "recipe": {
            "type": "string"
        },
        "calorie": {
            "type": "string"
        },
        "ingredient": {
            "type": "string"
        },
        "Hour": {
            "type": "string"
        }
    }
}
```

Power Appsに戻す値も、設定しましょう。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2734332/9fc57020-8d57-0613-29d3-82fe91d6b994.png)

```json:Power App またはフローに応答する
{
  "type": "Response",
  "kind": "PowerApp",
  "inputs": {
    "schema": {
      "type": "object",
      "properties": {
        "cooking": {
          "title": "cooking",
          "type": "string",
          "x-ms-content-hint": "TEXT",
          "x-ms-dynamically-added": true
        },
        "recipe": {
          "title": "recipe",
          "type": "string",
          "x-ms-content-hint": "TEXT",
          "x-ms-dynamically-added": true
        },
        "calorie": {
          "title": "calorie",
          "type": "string",
          "x-ms-content-hint": "TEXT",
          "x-ms-dynamically-added": true
        },
        "ingredient": {
          "title": "ingredient",
          "type": "string",
          "x-ms-content-hint": "TEXT",
          "x-ms-dynamically-added": true
        },
        "hour": {
          "title": "hour",
          "type": "string",
          "x-ms-content-hint": "TEXT",
          "x-ms-dynamically-added": true
        }
      },
      "additionalProperties": {}
    },
    "statusCode": 200,
    "body": {
      "cooking": "@body('JSON_の解析')?['cooking']",
      "recipe": "@body('JSON_の解析')?['recipe']",
      "calorie": "@body('JSON_の解析')?['calorie']",
      "ingredient": "@body('JSON_の解析')?['ingredient']",
      "hour": "@body('JSON_の解析')?['Hour']"
    }
  },
  "runAfter": {
    "JSON_の解析": [
      "Succeeded"
    ]
  }
}
```

# Power Apps
**完成系は[Twitter](https://x.com/DemodoriGatsuo/status/1791821900295070107)で紹介させていただきました。**

<blockquote class="twitter-tweet" data-media-max-width="560"><p lang="ja" dir="ltr">デザイン整えられた！<br>GPT-4oを使って、食材の写真から、料理名・カロリー・食材・かかる時間を提案してもらうアプリ！<br>低コストでこれが実現できるのは震えます！！<a href="https://twitter.com/hashtag/PowerApps?src=hash&amp;ref_src=twsrc%5Etfw">#PowerApps</a> <a href="https://twitter.com/hashtag/OpenAI?src=hash&amp;ref_src=twsrc%5Etfw">#OpenAI</a> <a href="https://twitter.com/hashtag/ChatGPT?src=hash&amp;ref_src=twsrc%5Etfw">#ChatGPT</a> <a href="https://twitter.com/hashtag/GPT4o?src=hash&amp;ref_src=twsrc%5Etfw">#GPT4o</a> <a href="https://t.co/aVgy0qAr7t">pic.twitter.com/aVgy0qAr7t</a></p>&mdash; 出戻りガツオ🐟 Microsoft MVP (@DemodoriGatsuo) <a href="https://twitter.com/DemodoriGatsuo/status/1791821900295070107?ref_src=twsrc%5Etfw">May 18, 2024</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

レイアウトの着想は、**Power Apps loverの師匠`Reza Dorrani`氏の[YouTube](https://www.youtube.com/watch?v=1o2L0DADzKQ)で紹介されたアプリをオマージュしています。**

<iframe width="560" height="315" src="https://www.youtube.com/embed/1o2L0DADzKQ?si=B33-yb4cGbh4bRXJ" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>

## コントロールのツリー図

```plaintext:コントロールのツリー図
Main
├─ grpComplete // 完了画面
│  ├─ btnComplete　// ボタンで完了画面を閉じる
│  ├─ txtComplete
│  └─ backgroundComplete
├─ grpProgress // ロード画面
│  ├─ txtProgress
│  ├─ imgProgress
│  ├─ Progress
│  └─ backgroundProgress
└─ ScreenContainer
   ├─ HeaderContainer
   │  ├─ imgTop
   │  ├─ lblTitle
   │  ├─ Avatar
   │  └─ lblMyname
   ├─ BottomContainer
   │  └─  SidebarContainer
   │  └─ galMode // 写真のアップロードかカメラか選択
   │  │  ├─  shpSelected
   │  │  ├─ icoMenu
   │  │  ├─ lblMode
   │  │  └─ shpSelectedBackground
   │  ├─ conBadge
   │  │  └─ Badge
   │  ├─ InfoContainer
   │  │  ├─ InfoButton
   │  │  └─ lblInfo
   │  ├─ conPicture // galModeに応じて表示、非表示
   │  │  ├─ ImageContainer
   │  │  │  ├─ AddPicture
   │  │  │  └─ Image
   │  │  └─ ButtonPicture
   │  └─ conCamera // galModeに応じて表示、非表示
   │     ├─ CameraContainer
   │     └─ ButtonCamera
   └─ MainContainer
      ├─ conHeaderMain
      │  ├─ LogoImage
      │  ├─ lblHeaderMain
      │  ├─ lblCooking　// 料理名を表示するテキストラベル
      │  └─ barHeaderMain
      ├─ conMainRecipe
      │  └─ txtRecipe // レシピを表示するテキスト入力
      ├─ conCalorie
      │  ├─ imgCalorie
      │  ├─ keyCalorie
      │  └─ txtCalorie // カロリーを表示するテキストラベル
      ├─ conIngredient
      │  ├─ imgIngredient
      │  ├─ keyIngredient
      │  └─ txtIngredient // 食材を表示するテキストラベル
      └─ conHour
         ├─ imgHour
         ├─ keyHour
         └─ txtHour // 時間を表示するテキストラベル
```

モダンコントロールで`SharePoint`を選択。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2734332/446bddb8-a0b0-86ad-5501-e434276fd7f4.png)

これをもとに、コントロールのカラーに統一感を持たせます。
[色の濃淡は`App.Theme.Colors`で表現しましょう](https://learn.microsoft.com/ja-jp/power-apps/maker/canvas-apps/controls/modern-controls/modern-theming#use-themes-with-power-fx)

https://learn.microsoft.com/ja-jp/power-apps/maker/canvas-apps/controls/modern-controls/modern-theming#use-themes-with-power-fx


[モダン コントロール](https://learn.microsoft.com/ja-jp/power-apps/maker/canvas-apps/controls/modern-controls/modern-controls-reference)のほとんどが、このテーマに沿って色の表現するため、非常に便利です。

https://learn.microsoft.com/ja-jp/power-apps/maker/canvas-apps/controls/modern-controls/modern-controls-reference

今回はヘッダーを自作します。`アバター`が追加されましたね。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2734332/3e9a458c-f213-527d-167b-108c6fcd4019.png)


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2734332/3e71e7e0-52dc-bc95-c681-6df8c348219c.png)

`アイコン`は下記のサイトから使わせていただいています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2734332/ee4dcceb-4354-a4cd-4d14-839f35b079b7.png)

https://icooon-mono.com/

https://icons8.jp/icons

ドーナツ🍩のアイコンは、`PowerPoint`にてストック画像の色を変えて、挿入しています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2734332/2cbf54e9-2f92-7b45-0328-ac3bbf2151d5.png)


`画像の追加`と`カメラ`からも画像認識をすることを見据えてコントロールを用意します。
メニューの切り替えは、ギャラリーで実施します。

- 画像の追加 モード
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2734332/91d38b1c-daf4-998f-562d-b85cdd02d744.png)


- カメラ モード
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2734332/44672cec-8fd4-952a-0d01-320d9c1f5661.png)

`写真を直接追加する方法`と`カメラから写真を送る方法`で、コンテナー単位でコントロールを分けます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2734332/a50e11c1-18bf-d3ea-19d5-ad1575b0407c.png)

`ギャラリー`の`Selectedの値`と`Visible`を連動させ、切り替えを実現します。

```conPicture.Visible
(galMode.Selected.Mode="Attachment")
```

```conCamera.Visible
(galMode.Selected.Mode="Camera")
```

`GPT-4o`から
1. `料理名`
1. `レシピ`
1. `カロリー`
1. `食材`
1. `かかる時間`

合計5つの値を受け取るため、受け皿を加えます。
Power Automateの戻り値を受け取り、それぞれのプロパティで値を設定。

```plaintext:ツリー図
   └─ MainContainer
      ├─ conHeaderMain
      │  ├─ LogoImage
      │  ├─ lblHeaderMain
      │  ├─ lblCooking　// 料理名を表示するテキストラベル
      │  └─ barHeaderMain
      ├─ conMainRecipe
      │  └─ txtRecipe // レシピを表示するテキスト入力
      ├─ conCalorie
      │  ├─ imgCalorie
      │  ├─ keyCalorie
      │  └─ txtCalorie // カロリーを表示するテキストラベル
      ├─ conIngredient
      │  ├─ imgIngredient
      │  ├─ keyIngredient
      │  └─ txtIngredient // 食材を表示するテキストラベル
      └─ conHour
         ├─ imgHour
         ├─ keyHour
         └─ txtHour // 時間を表示するテキストラベル
```

あとはPower Automate実行中の待機画面です。
GPT-4oの強みで全然時間がかからないのですが、昨日と全く一緒では芸がないので、`ロード画面`と`完了画面`を入れます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2734332/381161d9-bd53-93fb-f365-3853e408f4cf.png)

- `ロード画面` 四角形に、テキストラベルとプログレスバーを入れる
- `完了画面` 四角形に、テキストラベルとボタンを入れる

```plaintext:ツリー図
Main
├─ grpComplete // 完了画面
│  ├─ btnComplete　// ボタンで完了画面を閉じる
│  ├─ txtComplete
│  └─ backgroundComplete
├─ grpProgress // ロード画面
│  ├─ txtProgress
│  ├─ imgProgress
│  ├─ Progress
│  └─ backgroundProgress
```

それぞれ作成します。

画面の真ん中にコントロールが配置されるようにするには、`X`、`Y`のプロパティを下記のように設定します。

```plaintext:X
(Parent.Width - Self.Width) / 2
```

```plaintext:Y
(Parent.Height - Self.Height) / 2
```

:::note info
ギャラリーの中のコントロールの場合は、
- (Parent.TemplateWidth - Self.Width) / 2
- (Parent.TemplateHeight - Self.Height) / 2

上記で定義できます。
:::

しょっちゅう使いまわすので、覚えておいて損はないです。

これで、`GPT-4o`を呼び出すボタンコントロールに下記の式を設定すれば

```plaintext:OnSelect
/*
* ロード画面を表示する
*/
Set(varVisible1,true);
/*
* Power AutomateでGPT-4oを呼び出す
*/
UpdateContext({Response:'GPT-4o-Image-observe'.Run(Substitute(JSON(Image.Image,JSONFormat.IncludeBinaryData),"""",""))});
/*
* 完了画面を表示する
*/
Set(varVisible2,true);
```

**ロード画面のグループ、完了画面のグループが順番に表示され、映えます！**

![タイトルなし 4.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2734332/42d75a42-4d32-a7bb-583e-240482aeb614.gif)


**Power Automateの昨日と異なるポイントが、ほとんどないためPower Appsの説明に重きを置きました。**

ChatGPT用のカスタムコネクタを作ると、Power Automateを経由せずにできるので、さらに便利ですね。
[カスタムコネクタの作り方は、Microsoftの吉野様のQiitaの記事が大変わかりやすいです。](https://qiita.com/Satoshi_Yoshino/items/e2a47a3aaedef7ea5079#%E3%81%BE%E3%81%A8%E3%82%81
)

https://qiita.com/Satoshi_Yoshino/items/e2a47a3aaedef7ea5079#%E3%81%BE%E3%81%A8%E3%82%81

# おわりに
強力な機能が、こんなに簡単に使えて本当にいいのかと震えます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2734332/b258b203-7175-6ca7-5ad2-d55e9e292ef2.png)

**今度は何を作ろう・・・。**
**ワクワクが止まりませんね！**

皆様、良いPower lifeを！