てけとーぶろぐ。

ソフトウェアの開発と、お絵かきと、雑記と。

Python + Flet + Gemini API でAIチャットアプリを作る

前回はコンソールアプリとしてAIチャットを作りました。

kurima.hatenablog.com

このAIチャットにFletを使ってGUIをつけてみます。

FletはPythonで書けるFlutterと言えば伝わるでしょうか?

自分は何年も前からPythonでデスクトップアプリを組むには何を使ったらいいのかと悩みあれこれ眺めたり少し触ったりしてきました。

やっぱりTkinterなのかなぁなどと思いつつも、整った画面を作るのにノウハウが必要なところに違和感を感じていました。かといって広く使われていないものも使う気がしないといった状態でした。

そんなときにFletを見つけ、やっと腰を据えて使えるものが来たかー?と感じました。

ちなみにStreamlitも、こりゃすごい、と思ったのですけど、自由度が少し足りないんですよね…。そこを犠牲にして思い切り使いやすくしているというものだと思うので仕方ないと思います。

Fletやらの感想はこのくらいにしてアプリを作っていきます。プロジェクトの作成、APIキーの取得、プログラムからのAPIキーの参照については前回と同じなので詳しくは前回をご参照ください

プロジェクトの作成

プロジェクトを作成します。

$ rye init gemini-chat
$ cd gemini-chat
$ rye sync
パッケージのインストール

Gemini API 用の Python SDK が含まれる google-generativeai パッケージ、GUIの作成に使うfletパッケージをインストールします。

$ rye add google-generativeai
$ rye add flet
$ rye sync
Pythonファイルの作成

GUIのチャットアプリを作るわけですけど、ちょうどFletのチュートリアルにチャットアプリがありましたのでこちらをベースとして使わせてもらいたいと思います。

flet.dev

以下のPythonファイルを作成します。 gemini-chat/src/gemini_chat/main.py

そしてまずは以下にあるチュートリアルの最終的なコードをそのままコピーしてmain.pyの内容とします。

github.com

.env ファイルの作成

gemini-chat/.env を以下の内容で作成します。("XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"の部分は取得したAPIキー) 作成の理由は前回を参照してください。

GOOGLE_API_KEY="XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
.gitignore ファイル編集

gemini-chat/.gitignore に以下を追記します。 編集の理由は前回を参照してください。

# env
.env
pyproject.toml ファイルの編集

.envファイルから環境変数に値をセットしてプログラムの実行をするような スクリプトを gemini-chat/pyproject.toml に定義します。 pyproject.tomlに以下を追記します。 Fletを使ったアプリなのでfletコマンドで実行します。

[tool.rye.scripts]
run-main = { cmd = "flet run src/gemini_chat/main.py", env-file = ".env" }
プログラムの実行

以下のコマンドでプログラムを実行します。

$ rye run run-main

プログラムを実行するとFletのチュートリアルのチャットが表示されます。 これに少し手を加えてAIチャットにしましょう。

ユーザー名入力処理の削除

AIチャットでユーザー名の入力を求められるのは煩わしいのでユーザー名入力の処理を削除して固定のユーザー名となるようにしましょう。

ユーザー名入力のダイアログの「Join chat」ボタンをクリックしたときの処理である main() の中の join_chat_click() を削除します。具体的には以下のコードを削除します。

    def join_chat_click(e):
        if not join_user_name.value:
            join_user_name.error_text = "Name cannot be blank!"
            join_user_name.update()
        else:
            page.session.set("user_name", join_user_name.value)
            page.dialog.open = False
            new_message.prefix = ft.Text(f"{join_user_name.value}: ")
            page.pubsub.send_all(
                Message(
                    user_name=join_user_name.value,
                    text=f"{join_user_name.value} has joined the chat.",
                    message_type="login_message",
                )
            )
            page.update()

ユーザー名入力のダイアログの表示の処理を削除します。具体的には main() の中盤にある以下のコードを削除します。

    # A dialog asking for a user display name
    join_user_name = ft.TextField(
        label="Enter your name to join the chat",
        autofocus=True,
        on_submit=join_chat_click,
    )
    page.dialog = ft.AlertDialog(
        open=True,
        modal=True,
        title=ft.Text("Welcome!"),
        content=ft.Column([join_user_name], width=300, height=70, tight=True),
        actions=[ft.ElevatedButton(text="Join chat", on_click=join_chat_click)],
        actions_alignment=ft.MainAxisAlignment.END,
    )

固定のユーザー名を設定する処理を追加します。main() の最後に以下のコードを追加します。

    user_name = "You"
    page.session.set("user_name", user_name)
    new_message.prefix = ft.Text(f"{user_name}: ")
    page.pubsub.send_all(
        Message(
            user_name=user_name,
            text=f"{user_name} has joined the chat.",
            message_type="login_message",
        )
    )
    page.update()
Gemini APIのChatSession作成処理の追加

Gemini APIのChatSessionを作成する処理を追加します。main() の最後に以下のコードを追加します。

エラーメッセージの表示にアラートダイアログを使うようにしただけでChatSessionの作成自体は前回と同じです。

    GOOGLE_API_KEY = os.environ.get("GOOGLE_API_KEY")
    chat_session = None

    if GOOGLE_API_KEY is not None:
        genai.configure(api_key=GOOGLE_API_KEY)

        model = genai.GenerativeModel("gemini-1.5-flash")

        chat_session = model.start_chat(history=[])
    else:

        def handle_close(e):
            print("handle_close()")
            page.dialog.open = False
            page.update()

        page.dialog = ft.AlertDialog(
            open=True,
            modal=True,
            title=ft.Text("エラー"),
            content=ft.Text("環境変数 GOOGLE_API_KEY がセットされていません"),
            actions=[
                ft.TextButton("OK", on_click=handle_close),
            ],
            actions_alignment=ft.MainAxisAlignment.END,
        )

main.py の先頭に import も追加します。

import google.generativeai as genai
import os
AIによる返答処理の追加

AIによる返答処理を追加します。 ユーザーからのメッセージを受けてAIからのメッセージを作成・送信し、AIからのメッセージを受けた場合もユーザーからメッセージを受けた場合と同じように処理します。 そのために main() の中の on_message() を編集します。以下の on_message() を

    def on_message(message: Message):
        if message.message_type == "chat_message":
            m = ChatMessage(message)
        elif message.message_type == "login_message":
            m = ft.Text(message.text, italic=True, color=ft.colors.BLACK45, size=12)
        chat.controls.append(m)
        page.update()

以下のように変更します。

先頭の if に "ai_chat_message" の場合が加わり、末尾にAIからのメッセージ作成・送信が加わっています。

    def on_message(message: Message):
        if (
            message.message_type == "chat_message"
            or message.message_type == "ai_chat_message"
        ):
            m = ChatMessage(message)
        elif message.message_type == "login_message":
            m = ft.Text(message.text, italic=True, color=ft.colors.BLACK45, size=12)

        chat.controls.append(m)
        page.update()

        if message.message_type == "chat_message" and chat_session is not None:
            response = chat_session.send_message(message.text)
            page.pubsub.send_all(
                Message(
                    "AI",
                    response.text,
                    message_type="ai_chat_message",
                )
            )
プログラムの再実行

再度以下のコマンドでプログラムを実行します。

$ rye run run-main

このようにAIとチャットできます。

スクリーンショット

それっぽくなってきました。

最終的な全体のコードを載せておきます。

import flet as ft
import google.generativeai as genai
import os


class Message:
    def __init__(self, user_name: str, text: str, message_type: str):
        self.user_name = user_name
        self.text = text
        self.message_type = message_type


class ChatMessage(ft.Row):
    def __init__(self, message: Message):
        super().__init__()
        self.vertical_alignment = ft.CrossAxisAlignment.START
        self.controls = [
            ft.CircleAvatar(
                content=ft.Text(self.get_initials(message.user_name)),
                color=ft.colors.WHITE,
                bgcolor=self.get_avatar_color(message.user_name),
            ),
            ft.Column(
                [
                    ft.Text(message.user_name, weight="bold"),
                    ft.Text(message.text, selectable=True),
                ],
                tight=True,
                spacing=5,
            ),
        ]

    def get_initials(self, user_name: str):
        if user_name:
            return user_name[:1].capitalize()
        else:
            return "Unknown"  # or any default value you prefer

    def get_avatar_color(self, user_name: str):
        colors_lookup = [
            ft.colors.AMBER,
            ft.colors.BLUE,
            ft.colors.BROWN,
            ft.colors.CYAN,
            ft.colors.GREEN,
            ft.colors.INDIGO,
            ft.colors.LIME,
            ft.colors.ORANGE,
            ft.colors.PINK,
            ft.colors.PURPLE,
            ft.colors.RED,
            ft.colors.TEAL,
            ft.colors.YELLOW,
        ]
        return colors_lookup[hash(user_name) % len(colors_lookup)]


def main(page: ft.Page):
    page.horizontal_alignment = ft.CrossAxisAlignment.STRETCH
    page.title = "Flet Chat"

    def send_message_click(e):
        if new_message.value != "":
            page.pubsub.send_all(
                Message(
                    page.session.get("user_name"),
                    new_message.value,
                    message_type="chat_message",
                )
            )
            new_message.value = ""
            new_message.focus()
            page.update()

    def on_message(message: Message):
        if (
            message.message_type == "chat_message"
            or message.message_type == "ai_chat_message"
        ):
            m = ChatMessage(message)
        elif message.message_type == "login_message":
            m = ft.Text(message.text, italic=True, color=ft.colors.BLACK45, size=12)

        chat.controls.append(m)
        page.update()

        if message.message_type == "chat_message" and chat_session is not None:
            response = chat_session.send_message(message.text)
            page.pubsub.send_all(
                Message(
                    "AI",
                    response.text,
                    message_type="ai_chat_message",
                )
            )

    page.pubsub.subscribe(on_message)

    # Chat messages
    chat = ft.ListView(
        expand=True,
        spacing=10,
        auto_scroll=True,
    )

    # A new message entry form
    new_message = ft.TextField(
        hint_text="Write a message...",
        autofocus=True,
        shift_enter=True,
        min_lines=1,
        max_lines=5,
        filled=True,
        expand=True,
        on_submit=send_message_click,
    )

    # Add everything to the page
    page.add(
        ft.Container(
            content=chat,
            border=ft.border.all(1, ft.colors.OUTLINE),
            border_radius=5,
            padding=10,
            expand=True,
        ),
        ft.Row(
            [
                new_message,
                ft.IconButton(
                    icon=ft.icons.SEND_ROUNDED,
                    tooltip="Send message",
                    on_click=send_message_click,
                ),
            ]
        ),
    )

    user_name = "You"
    page.session.set("user_name", user_name)
    new_message.prefix = ft.Text(f"{user_name}: ")
    page.pubsub.send_all(
        Message(
            user_name=user_name,
            text=f"{user_name} has joined the chat.",
            message_type="login_message",
        )
    )
    page.update()

    GOOGLE_API_KEY = os.environ.get("GOOGLE_API_KEY")
    chat_session = None

    if GOOGLE_API_KEY is not None:
        genai.configure(api_key=GOOGLE_API_KEY)

        model = genai.GenerativeModel("gemini-1.5-flash")

        chat_session = model.start_chat(history=[])
    else:

        def handle_close(e):
            print("handle_close()")
            page.dialog.open = False
            page.update()

        page.dialog = ft.AlertDialog(
            open=True,
            modal=True,
            title=ft.Text("エラー"),
            content=ft.Text("環境変数 GOOGLE_API_KEY がセットされていません"),
            actions=[
                ft.TextButton("OK", on_click=handle_close),
            ],
            actions_alignment=ft.MainAxisAlignment.END,
        )


ft.app(target=main)

Python + Rye + Gemini API でAIチャットを作る

ChatGPTだとかCopilotだとか、AIチャットというのでしょうか。 最近ちょこちょこ使っています。

便利でありがたいのですけど、質問の一部だけが毎回異なるような定型的な質問が簡単にできると更に便利だなと思っています。

例えば「英単語のXXXとXXXの違いと使い分けを教えてください」という質問はXXXの部分を変えて何度もしていたりします。

そういうのはGhatGPTのAPIを使えばできるよ、という記事は何度も見かけたのですけど APIの利用料など気にせずに、できれば無料で行いたい…。 何かないかなと探してみるとGoogleのGemini APIが通常の使用では制限を気にせず無料で使えそうでした。

ということでGemini APIを使ってまずは試しに単純なチャットを作ってみました。 いくらか使い慣れているPythonで作ります。

Gemini APIの使い方に関しては以下を参考にしています。

ai.google.dev

Ryeのインストール

セットアップを簡単にするためPythonのパッケージマネージャーにはRyeを使います。

Ryeをインストールしていない場合は以下の「Installation Instructions」に従ってインストールします。

rye.astral.sh

プロジェクトの作成

プロジェクトを作成します。

$ rye init gemini-tutorial
$ cd gemini-tutorial
$ rye sync
パッケージのインストール

Gemini API 用の Python SDK が含まれる google-generativeai パッケージをインストールします。

$ rye add google-generativeai
$ rye sync
Pythonファイルの作成

gemini-tutorial/src/gemini_tutorial/main.py として以下の内容でPythonファイルを作成します。

import google.generativeai as genai
import os

GOOGLE_API_KEY = os.environ.get("GOOGLE_API_KEY")

if GOOGLE_API_KEY is None:
    print("環境変数 GOOGLE_API_KEY がセットされていません")
    exit()

genai.configure(api_key=GOOGLE_API_KEY)

model = genai.GenerativeModel("gemini-1.5-flash")

chat = model.start_chat(history=[])

print("メッセージを入力してください(「exit」で終了します)")
print("")

while True:
    print("あなた:")
    message = input()
    print("")
    if message == "exit":
        print("終了します")
        break
    response = chat.send_message(message)
    print("AI:")
    print(response.text)
APIキーの取得

以下のサイトの「APIキーを取得する」ボタンをクリックしてAPIの使用に必要なAPIキーを取得します。

ai.google.dev

取得したら以下のようにして「GOOGLE_API_KEY」という環境変数に取得したAPIキーをセットします。 ("XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"の部分は取得したAPIキー) Pythonプログラムからこの環境変数を参照します。

$ export GOOGLE_API_KEY="XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"

セキュリティを考えてこうしているだけで プログラムを公開しないというのであればPythonプログラム内で

GOOGLE_API_KEY = os.environ.get("GOOGLE_API_KEY")

の代わりに

GOOGLE_API_KEY = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"

としても構いません。

プログラムの実行
$ rye run python src/gemini_tutorial/main.py
プログラムの実行結果
$ rye run python src/gemini_tutorial/main.py
メッセージを入力してください(「exit」で終了します)

あなた:
こんにちは!

AI:
こんにちは! 何かご用でしょうか?

あなた:
あなたは誰ですか?100文字程度で教えてください。

AI:
私は、Googleによって訓練された、大規模言語モデルです。 私は、テキストを理解し、生成することが得意です。 質問に答えたり、物語を書いたり、コードを生成したりできます。

あなた:
exit

終了します

たったこれだけのコードでできてしまうなんて驚きです。

.env ファイルの作成

上記のようにAPIキーを環境変数にセットしているとターミナルを立ち上げるたびに環境変数にセットしなければなりません。 それは面倒なのでプログラム実行時にファイルからAPIキーを読み込むようにしてみましょう。 gemini-tutorial/.env を以下の内容で作成します。("XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"の部分は取得したAPIキー)

GOOGLE_API_KEY="XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
.gitignore ファイル編集

.envファイルにはAPIキーが書かれているわけですから ここでもセキュリティを考えて.envファイルをリポジトリーの管理対象外としたいのであれば gemini-tutorial/.gitignore に以下を追記します。

# env
.env
pyproject.toml ファイルの編集

.envファイルから環境変数に値をセットしてプログラムの実行をするような スクリプトを gemini-tutorial/pyproject.toml に定義します。 pyproject.tomlに以下を追記します。

[tool.rye.scripts]
run-main = { cmd = "python src/gemini_tutorial/main.py", env-file = ".env" }
スクリプトを使ってのプログラムの実行

そしてプログラムの実行を以下のコマンドで行うようにします。

$ rye run run-main

Ubuntuでネットワークの不調の原因を探る

Ubuntuを快適に使っていたのですが 近頃Webブラウジングで固まったり、Web会議で映像や音声が途切れたりといった ネットワークの不調があって困っていました。

結果的にwifiの電波状況の問題で wifiルーターを再起動してオートチャネルセレクトを強制的に行わせ 動作中にも定期的にオートチャネルセレクトがなされるようにルーターの設定を変更して 改善しました。 →2024/03/27追記: 改善したと思いきや時間帯などによってはdropが10%を超えるので、さらにバンドステアリング機能もオフにしたところ、今度こそ改善したっぽいです。不調になったら接続先のSSIDの切り替えで周波数が切り替えられるのでこちらのほうが良さそうです。

運悪く不調を感じはじめた時期がUbuntuのバージョンを変えたタイミングだったことや、 WindowsAndroidだと不調を感じることがなかったことから、 Ubuntuの何かが原因だと思ってしまい解決まで遠回りをしてしまいました。

解決にあたっては以下の記事がとても役に立ちました。

gihyo.jp

ここで紹介されているwavemonを以下のコマンドでインストールして

$ sudo apt-get install wavemon

以下のコマンドで起動。

$ wavemon

起動しておいて、不調を感じたときに見てみるとStaticsのdropが10%を超えていました。 普段でも数%以上。 一方で、そういえば不調を感じないなと思った別の場所、つまり別のwifi環境で見てみるとdropは0.X%でした。 このことからwifiの電波状況だと判断しました。

Fire HD 10 タブレット(≒Androidタブレット)をPC風に使う(ソフトウェア編)

当初この記事を書こうと考えたときは以下の動画にあるような Linuxをインストールしてあれこれというのを紹介しようと思っていました。

【改造不要】Fireタブレットでデスクトップが使いたい!2/2 Termux編【Amazon Fire HD 10 Plus (2021)】 - YouTube

しかし実際にやってみて自分としてはAndroidへのLinuxのインストールは実用的ではないと感じました。 それで結局、人に勧められると思ったPC風に使う方法としては以下の2つとなりました。

それぞれについて説明します。

PCにリモートデスクトップ接続して使う

タイトルの通りです。接続先のPCはWindows, Mac, Linux どれでも。 Fire HD 10 タブレット にインストールするリモートデスクトップクライアントアプリとしては探した中では以下のいずれかが良さそうです。

使い方のポイントとしては設定で解像度を指定してやることです。 Fire HD 10 タブレット だと 1920x1200 が画面の解像度なので この縦横比になるように、かつ操作しやすい表示になるように、例えばこの75%とかの解像度を指定してやります。

Fire HD 10 タブレットの場合、Google Playがインストールされていないのでアプリがインストールしづらいですね…。 Google Play をインストールしたり、APKダウンロードサイトからダウンロードしてきたりもできそうではありますけど あまり紹介されていないやり方としては aRDP, bVNC についてはオープンソースなので、ソースコードからビルドしてAPKを作ってインストールするという手があります。

以下のサイトの Building に従って、あっさりビルドできました。

github.com

それから出先から接続するのであればPCに何らかの方法でインターネット経由で接続できるようにしなければなりません。 ngrokを使ったり、VPNを経由するのがいいと思います。

ngrok.com

VPN接続にはVPNクライアントが必要ですがOpen SSTP Clientなんかがおすすめです。 こちらもオープンソースで、こちらについては自分でビルドせずともGitHubからAPKがダウンロード可能です。

github.com

PC風にWebブラウザーを使う

Fire HD 10 タブレット に接続したキーボードとマウスを使ってPC風に操作できるWebブラウザーアプリを使ってWebブラウジングします。

タブバーを表示できるなど、PC風なUIになるWebブラウザーアプリを選ぶことになります。 以下をお勧めします。

いずれも公式サイトでAPKを配布しています。

Vivaldi Browser

以下のサイトの「Alternative download stores.」をクリックして表示される「Download the app (apk):」からダウンロードします。

vivaldi.com

Kiwi Browser 以下のサイトの「Download now (.APK)」からダウンロードします。

kiwibrowser.com

Kiwi Browser についてはChrome拡張が使える点も素晴らしいです。

Fire HD 10 タブレット(≒Androidタブレット)をPC風に使う(ハードウェア編)

自分は「タブレットは不要、結局PCが必要になるんだよね。」という意見なのですけど 先日安さと性能に惹かれてFire HD 10 タブレットを買ってしまいました。

せっかくなのでPC風に使えるのか試してみました。

結論を先に言ってしまうと、使うだけならこんなことをせずにLIFEBOOKなどの軽量ノートPCを買った方がいいです。 ただ用途によっては値段の割に使えますし、何よりどうすれば使いやすくなるかなとあれこれ考えて試すことが楽しいです。 そしてその中で気づきなどもあります。

今回気づいたことの中で特に共有しておきたいと思ったことはオープンソースAndroidアプリって結構あるんだなってことです。

Termux, aRDP, bVNC, XServer XSDL と今回主に使ったアプリはいずれもオープンソースでした。

前置きはこの辺にして早速やっていきます。まずはハードウェア編です。

ハードウェアって何のこと?かと思いますので説明しますと、PC風に使うためにFire HD 10に追加で必要なもののことです。キーボード、マウスなどです。

追加で必要なものを選ぶ際には主に重量・携帯性、使いやすさを重視しています。 特に重量・携帯性はタブレットをPC風に使う上でノートPCといい勝負ができる数少ない点だと思います。

それであれこれ考えて以下に落ち着きました。

折りたたみスティック型スタンド

重量、開きやすさ、角度調整の自由さを考えるとこういうスタンドだと思います。

item.rakuten.co.jp

TPUケース

ごく普通のTPUケースです。いろいろなところで似たようなものが売られていると思います。 TPUケースをつけて、TPUケースに上記のスタンドを貼り付けています。 傷を気にしない人はTPUケースは使わずタブレットに直接スタンドを貼ってしまうと更に軽くなるかと思います。

ガラスフィルム

これまたごく普通のガラスフィルムです。傷を気にしない人は不要かと…。

Bluetoothキーボード

キーボードの接続方法はつなぎやすさや持ち運びの妨げにならないことを考えるとBluetoothではないでしょうか。 そして、重量、英語配列、Ctrlキーの位置、それなりのうちやすさといったところでこれに落ち着きました。 いまのところ問題なしですけど耐久性があるのか不明です。

Bluetoothマウス

薄さ、重量、使いやすさでこれに落ち着きました。鉄板のロジクール

Reader CaptureでKindle for PCの電子書籍をPDFにする手順の変更

電子書籍をPDFファイル/ZIPファイルに変換できるWindows用アプリReader CaptureKindle for PCの電子書籍をPDFにする手順に変更があったのでお知らせします。

どうも最近Kindle for PCは、書籍によって、フルスクリーンにしても画面の上辺・下辺に細い枠線が表示されるようになったようです。

この場合にReader Captureでうまくキャプチャーするには、固定トリミングを今までの左右に加えて、上下に 上: 1、下: 1 と設定する必要あります。

最新の Reader Capture 1.15.0.0 ではこれをデフォルトの設定としました。

Reader CaptureでKindle for PCの電子書籍をPDFにする手順の全体は Reader Capture - 電子書籍をPDFファイル/ZIPファイルに変換! をご参照ください。

細かいことを言うと、上辺・下辺に細い枠線が表示されない場合は、固定トリミング 上: 1、下: 1 となっていると1ピクセル切れてしまうことになるんですよね。 気になる方は「範囲選択機能」で細い枠線があるのかチェックするといいかもしれません。

格安HDMIキャプチャーボードでスマホをラズパイのディスプレイにする

ラズパイ(Raspberry Pi 2 Model B)にUbuntuをインストールして家庭内サーバーにしようとしました。

以下の記事の手順に従えばラズパイにディスプレイもキーボードもつなぐことなくSSHログインしてセットアップができるはずなのですが、なんだかんだ、はじめだけちょっとディスプレイをつないで確認しながらやりたくなりました。

zenn.dev

かといってこれのために5千円以上かけてディスプレイを買うのも…となっていたところに使えそうなものを見つけました。 それがこの格安のHDMIキャプチャーボードです。

これにUSB 変換アダプタとHDMIケーブルをつないで、Androidスマホとラズパイをつなぎます。

スマホで以下のアプリを起動しておけばスマホが簡易ディスプレイになります。

play.google.com

ただし画面の表示の様子はこんなですので長時間の作業には向きません。

スクリーンショット

このHDMIキャプチャーボード、えらい安いですし、大丈夫なのかと疑ってしまいますが 多少遅延もあったり、Motion JPEGでのノイズがあったりなので値段相応なのかもしれません。

多くのHDMIキャプチャーボードの使い方には耐えないように思いますけど、今回の使い方はちょうどではないでしょうか。

スマホでなくタブレットだったら画面の大きさにも満足ですかねー。