シンプルな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
コメント