シンプルなAIアプリ4

シンプルなAIアプリ3の続きです。

ブラウザでAIに送信するプロンプトを編集し、AIが返した結果を表示できるようになったので、UIを調整します。

UIの調整

index.htmlを更新する

ブラウザに表示する画面を検討しました。

そして、次のようなイメージにすることを決定しました。

AIアプリ
 +-------------------------------------------------+
 |AIの回答                | AIに送るプロンプト:    |
 |div                     |  +------------------+  |
 |(id="ai-result")        |  | textarea         |  |
 |                        |  | (id="ai-prompt") |  |
 |                        |  |                  |  |
 |                        |  |                  |  |
 |                        |  |                  |  |
 |                        |  +------------------+  |
 |                        |                 button |
 |                        |      (id="send-button")|
 +-------------------------------------------------+

上のイメージに従って、index.html を更新します。

<body>
    <h1>AIアプリ</h1>
    <div id="content">
        <div id="left-panel">
            <h2>AIの回答</h2>
            <div id="ai-result"></div>
        </div>
        <div id="right-panel">
            <label for="ai-prompt">AIに送るプロンプト:</label>
            <textarea id="ai-prompt"></textarea>
            <button id="send-button">送信</button>
        </div>
    </div>
</body>

styles.cssの作成

せっかくAIに質問できるプログラムを作成したので、CSS作成をAIに手伝ってもらいましょう。

以下が、AIに送信するプロンプトです。

CSSファイルの作成を手伝ってください。

(実際のプロンプトでは、ここにindex.htmlを添付します。)

上のHTMLに対して、次の条件をすべて満たすCSSを作って欲しいです。

* ai-resultの縦スクロールを有効にしてください。それ以外はすべてスクロールを無効化してください。
* htmlのサイズを100vwと100vhにしてください。
* bodyの幅は80%、高さは95%にしてください。
(など…、他にも細かい指定を記載しましたが、省略します)

以下は、簡単なイメージ図です。

(実際のプロンプトでは、ここにイメージ図を添付します。)

というプロンプトを入力して、AIにCSSを作ってもらいました。

ただし、1回で完全なCSSは作成できません。AIが生成したCSSをひな型にして自分で調整するか、細かな指定を追加して何度も生成すると良いでしょう。

以下が、調整や繰り返しによって完成した styles.css です。

html {
    height: 100vh;
    width: 100vw;
    overflow: hidden;
    /* html全体でのスクロールを無効化 */
}

body {
    width: 80%;
    height: 95%;
    margin: auto;
    display: flex;
    flex-direction: column;
    /* body内を縦に配置 */
    overflow: hidden;
    /* body全体でのスクロールを無効化 */
}

#content {
    display: flex;
    flex-direction: row;
    /* content内を横に配置 */
    flex: 1;
    /* contentの高さを残りの高さに合わせる */
    overflow: hidden;
    /* content全体でのスクロールを無効化 */
}

#left-panel {
    width: 66.66%;
    /* 2:1の比率で66.66% */
    height: 100%;
    display: flex;
    flex-direction: column;
    overflow: hidden;
    /* left-panel全体でのスクロールを無効化 */
}

#ai-result {
    width: 100%;
    flex: 1;
    /* ai-resultの高さを残りの高さに合わせる */
    overflow-y: auto;
    /* ai-resultのみ縦スクロールを有効化 */
}

#right-panel {
    width: 33.33%;
    /* 2:1の比率で33.33% */
    height: 100%;
    display: flex;
    flex-direction: column;
    overflow: hidden;
    /* right-panel全体でのスクロールを無効化 */
}

#ai-prompt {
    width: 100%;
    height: 100%;
    /* ai-promptの高さを残りの高さに合わせる */
    resize: none;
    /* サイズ変更不可 */
    flex: 1;
    box-sizing: border-box;
}

#send-button {
    /* ボタンの高さをコンテンツに合わせて調整 */
    height: fit-content;
    padding: 8px 16px;
    background-color: #09F;
    color: #FFF;
    border-radius: 4px;
    border: none;
    margin: 4px 4px 4px auto;

    &:hover {
        background-color: #06A;
    }
}

/* その他のスタイル(任意)*/
body,
h1,
h2,
label,
button {
    font-family: sans-serif;
}

h1 {
    text-align: center;
    margin-bottom: 1em;
}

#left-panel h2 {
    margin-bottom: 0.5em;
}

label {
    margin-bottom: 0.5em;
}

実行結果

さっそく使ってみましょう。CSSを変えただけなので、ブラウザの画面を更新するだけです。

Markdownへの対応

テストのためにいろいろなプロンプトを入力してみると、次のようなフォーマットでAIが回答してくることが多いことが分かりました。

「赤い」といえば、色々なものが思い浮かびます。いくつか例を挙げると:

* **色:** これは最も直接的な答えですね。トマト、リンゴ、血、バラ、夕焼けなど、赤い色のもの全てです。
* **感情・状態:** 怒り、興奮、危険、熱、炎症など、赤い色に関連付けられる感情や状態を表す言葉も考えられます。
(以下省略)

これは、Markdown記法 というフォーマットです。(※ 私はまったく知らなかったのですが、今回、AIに質問することで知りました。)

Gemini の文章生成は、 Markdown記法を生成しやすいようです。

そこで、AIから返ってきたMarkdown記法の結果を、適切に処理して、画面表示してみましょう。

script.tsの修正

ブラウザへの表示部分なので、src/client/script.ts を改良します。

以下が、改良した src/client/script.ts です。変更されていないGetPrompt()関数のコードは省略しています。

import { marked } from 'https://cdn.jsdelivr.net/npm/marked/lib/marked.esm.js';
import { IClientToServer, IServerToClient } from "../common/interface.ts";

// 送信ボタンのクリックイベントを登録する
const sendButton = document.getElementById("send-button");
if (sendButton instanceof HTMLButtonElement) {
    sendButton.addEventListener("click", async () => {
        try {
            // id="ai-prompt"のtextareaからプロンプトを得る。
            const prompt = GetPrompt();

            // プロンプトが空なら、それを表示して終了する。
            if (!prompt) {
                UpdateAiResult("プロンプトがありません");
                return;
            }

            // プロンプトをサーバーの"ai-api"に送信する。
            const sendData: IClientToServer = { prompt: prompt };
            const response = await fetch("ai-api", {
                method: "POST",
                body: JSON.stringify(sendData)
            });

            // サーバーから結果が返ってきたら、それを表示する。
            const json = await response.json() as IServerToClient;
            UpdateAiResult(marked.parse(json.result), 'HTML');
        } catch (error) {
            // エラーがあった場合は、それを表示する。
            console.error('Error:', error);
            UpdateAiResult(`エラーが発生しました: ${(error as Error).message}`);
        }
    });
}

/**
 * id="ai-result"の内部をresultに変更する。
 * @param result テキストまたはHTML
 * @param type resultをテキストとして扱うか、HTMLとして扱うか
 */
function UpdateAiResult(result: string, type: 'text' | 'HTML' = 'text') {
    const aiResult = document.getElementById("ai-result");
    if (aiResult) {
        if (type === 'text') {
            aiResult.innerText = result;
        }
        else {
            aiResult.innerHTML = result;
        }
    }
}

import で Markdown 記法を HTML に変換するライブラリをインポートしています。

marked.parse()関数で AIの返したテキストを処理することで、Markdown 記法が HTML になります。

UpdateAiResult()関数は、従来通りテキストを受け取るケースと、Markdownで処理されたHTMLを受け取るケースに対応する修正を行いました。

なお、importで変換ライブラリをインポートするために、index.htmlのscript.jsを読み込むところで、以下のようにtype="module"を追加する必要があります。

<script src="script.js" type="module"></script>

実行結果

Markdown記法の、**色:** のような表記がHTMLの <strong>色:</strong> に変換され、適切に表示されるようになりました。

今回までの作業状況

以下に最新のディレクトリ構成と状況を記載します。

/project-root
|-- deno.json
|-- /public
|   |-- index.html(Update:UI変更とMarkdown対応)
|   |-- styles.css(New)
|   |-- script.js(Update:script.tsから生成される)
|-- /src
|   |-- /common
|   |   |-- interface.ts
|   |-- /client
|   |   |-- script.ts(Update:Markdown対応)
|   |-- /server
|       |-- main.ts

コメント