AIで自分だけのバッジを集めよう！子供と一緒に楽しめる「Happy Badge」

## はじめに

突然ですが、「[ヘイ！ダギー](https://tv.disney.co.jp/junior/program/20/heyduggee.html)」をご存じでしょうか。
Disney制作の知育アニメなのですが、子供がどハマりするついでに私もハマっています。
非常にコミカルでポップ、軽快な音楽とストーリーで飽きずに観ていられます。

> ※公式サイトより抜粋
犬のダギーのチビッコ園に、個性豊かでゆかいな子どもたちが大集合！
～中略～
みんな、優しく見守っていてくれるダギーが大好き！ 一日の終わりには、頑張ったごほうびとしてダギーからバッジがもらえるんだ。


**「 頑張ったごほうびとしてダギーからバッジがもらえるんだ。」**

ということで、楽しくチャレンジをしていろんなバッジを集めることができるアプリを作ってみました。(Googleアカウントですぐに始められます!)

**Happy Badge**

![mockup.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/649608/e768123c-1c86-d648-9f43-5dfb439f0485.png)

https://happy-badge.reflex.run/

※PWAに対応していますので、「ホーム画面に追加」するとアプリのように利用できおススメです。

ホーム画面への追加方法

https://support.bccks.jp/faq/pwa_2020/



## できること

### コレクション
チャレンジに成功したらバッジをもらえて、コレクションとしてみることができます。
初期登録時には５枚のバッジが用意されており、それぞれにチャレンジの内容が設定されています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/649608/3b9992ce-b7e6-7f9e-b4c6-c23f02a9c048.png)


### つくる

自分でバッジを作ることもできます。
バッジの名前と「できた！」の条件を設定し、バッジのアイコンを決めます。
アイコンはプリセットが300種類以上あり、画像をアップロードしてはめ込むこともできます。
また、バッジの名前と条件からAIに生成してもらうことも可能です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/649608/e3afb2c3-1403-91d1-705b-43c4e3430233.png)



### できた！
チャレンジに成功した（と判断した）ら、「できた！」から認定します。
表示されるバッジをタップしてコレクションに加えましょう。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/649608/0cc4b156-1ac3-453b-2d4d-5ba8ccd998cd.png)

### どんどんチャレンジ
次にチャレンジしてみるバッジを一緒に選んであげましょう。（いつでも変更可能です。）

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/649608/14c7e833-9005-63e7-d8c1-7f142bcc0d99.png)


> 「あれ？おかたづけバッジ持ってたんじゃなかったっけ？？」は２、３回効果があります。ぜひお試しあれ。


## 技術まわりの話

### フレームワーク

Reflex

https://reflex.dev/

PythonだけでWebアプリが作れる系のフレームワークです。
裏ではNext.js＋FastAPIの構成に変換されています。

UIデザインにはChakra-UI、Radix-UI、Tailwind CSSが採用されており、通常のCSSパラメータも指定可能です。

また、npmで公開されている任意のReactコンポーネントを取り込むことができるのも便利です。（今回も全画面のローダーなどを利用しています。）

https://reflex.dev/docs/wrapping-react/overview/

※こんな感じで使えます（上記リンク先より抜粋）
```python
class ColorPicker(rx.Component):
    library = "react-colorful"
    tag = "HexColorPicker"
    color: rx.Var[str]
    on_change: rx.EventHandler[lambda color: [color]]


color_picker = ColorPicker.create


class ColorPickerState(rx.State):
    color: str = "#db114b"


def index():
    return rx.box(
        rx.vstack(
            rx.heading(
                ColorPickerState.color, color="white"
            ),
            color_picker(
                on_change=ColorPickerState.set_color
            ),
        ),
        background_color=ColorPickerState.color,
        padding="5em",
        border_radius="1em",
    )
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/649608/bb4bd16a-3809-c62d-2778-edf450fe4db6.png)


### データベース
Supabase

https://supabase.com/

いわゆる BaaS (Backend As A Service) ですが、今回は純粋にRDBMSのみを利用しています。WebUIが抜群に使いやすいです。

※ReflexではDB情報を設定することでalembicを使ったマイグレーションや、ORMとしてSQLAlchemyが利用可能です。
以下の記事がわかりやすいです。

https://qiita.com/SFITB/items/b339cc42290a74860af9



### 認証・認可

Google OAuth 2.0

https://developers.google.com/identity/protocols/oauth2?hl=ja

個人情報はできる限り自前で保存したくないのでみんな大好きGoogle先生に頼ります。

### 決済

Stripe

https://stripe.com/jp?utm_campaign=APAC_JP_JA_Search_Brand_Payments-Core_EXA-21278920277&utm_medium=cpc&utm_source=google&ad_content=699150004496&utm_term=stripe%20inc&utm_matchtype=e&utm_adposition=&utm_device=c&gad_source=1&gclid=CjwKCAjw2dG1BhB4EiwA998cqHmJH2IB0riw_FBPV6Dbs_Xspct0evbDJXS1C1AvS3XiLpNRzECpghoCIxwQAvD_BwE

こちらも個人情報を保存したくない＆決済系は自前で実装するのが怖いので、全面的に頼っています。
PaymentLinks→Hooksを使ってAWS LambdaをキックしてSupabaseのクレジット履歴を編集しています。


### 画像生成
OpenAI API Image generation(DALL·E2)

https://platform.openai.com/docs/guides/images

バッジ作成の機能で利用しています。
今回はInpaintingが必要だったのでDALL·E3ではなくDALL·E2です。
StableDiffusionなども試したのですが、マスク画像やパラメータの調整が下手だったのかいい感じのイラストが生成されず。。
一番それっぽく生成してくれたOpenAIを選定しました。

以下のようにマスク画像を用意して渡してあげると機能します。（返ってくる画像データは透過がなくなってしまっているので、ついでに背景を透過しています。）

|base|mask|alpha|
|---|---|---|
|![preset_base.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/649608/9650823c-34e6-26f2-ad85-b94f72b0a400.png)|![preset_base_mask.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/649608/65b499dc-277c-bcd2-a231-f73c5e9b0e99.png)|![preset_base_alpha_mask.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/649608/bc9d324e-525e-0a40-43c0-f47dc8e68f41.png)|



```python
def inpaint(title):

    prompt = f"""
    Cute and pop clipart for kids.
    Generating theme: {title}
    """

    image = Image.open("base.png").convert("RGBA")
    mask = Image.open("base_mask.png").convert("L")

    buffered = io.BytesIO()
    image.save(buffered, format="PNG")
    image_bytes = buffered.getvalue()

    buffered.seek(0)
    mask.save(buffered, format="PNG")
    mask_bytes = buffered.getvalue()

    response = openai.images.edit(
        model="dall-e-2",
        image=image_bytes,
        mask=mask_bytes,
        prompt=prompt,
        n=1,
        size="512x512",
    )

    response = requests.get(response.data[0].url)

    # 背景を透過
    gen_image = Image.open(io.BytesIO(response.content)).convert("RGBA")
    mask = Image.open("alpha.png").convert("L")

    gen_image.putalpha(mask)

    return gen_image
```

ややアメリカナイズされている感は否めませんが↓のように生成されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/649608/c03501b2-34a1-8c41-dc3c-b28e1ef4a45b.png)




## ホスティング
Reflex Hosting Service

https://reflex.dev/docs/hosting/deploy-quick-start/

Reflexが公式でサポートしているホスティングサービスです。

CLIからの操作になりますが、```reflex init```でプロジェクトを作っていれば、```reflex deploy```だけで完了できます。（環境変数の設定など若干めんどくさいですが、shellを作っておけばそこまで面倒ではないかなという印象です。VercelやFly.ioのようにリッチなWebUIはないので好みがわかれるかも。）

※Next.jsとFastAPIとしてビルドされるので、それぞれコンテナに詰めてデプロイすればどこでもホスティングはできるようです。


## おわりに

Reflexの練習がてら子供と一緒に楽しめるWebアプリを作ってみました。
Reflex自体ちょくちょく触ってはいたものの、構想からリリース、記事公開まで```2時間/日×3週間ほど```でできたので本当に使いやすいフレームワークだと思います。
これからもっと広まって欲しい！

※Discord内には日本語チャンネルもありますので是非。

https://discord.gg/DeWEstSxZy