AIに思い出を絵本にしてもらうアプリの技術的な解説

# はじめに
前回AIに日記から絵本を作成したもらうアプリの記事を投稿しました。

https://qiita.com/shinry/items/279c34d30ebe7283554a

この記事の技術的な部分について、もう少し触れておきたいなと思ったので記事にしたいと思います。

# アプリの簡単な説明
前回の記事にも書いていますが、軽くアプリの説明をしたいと思います。

皆さんの中には毎日今日の出来事を日記に書き留めて、記録しておく人がいると思います。
この日は友達と旅行に行ったな、この日は家族でおいしい料理を食べに行ったなと。

**こういった貴重な体験は残していくだけだともったいないです。**

ただ、記録しておくだけでは思い出風化させてしまいます。

**このアプリがあれば、日記を書き、あの瞬間の思い出を絵本にし、文字と絵で情景を思い返すことができます。**

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1328154/65255b65-26b1-b310-4826-41fb5dfd1822.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1328154/ed18662f-b78d-f77e-779f-7ce4c3e4afba.png)

# 使用AI技術
Gemini（無料API）とAzureOpenAI（有料）を使用しています。
文章生成はGemini、画像生成はAzureOpenAIのdall-e3です。

# 文章の作成部分の処理
文章の作成処理はこのアプリにとって重要な部分です。
日記の文章があるのですが、単純にその文章だけだと絵本は作れません。
絵本にはページがあって、そのページごとに文章があります。

ページごとの文章は、日記の内容だけだと不足していたり、文字数が多かったり、1ページごとに表示するには区切りが悪かったりとします。

そのため、絵本の文章作成は生成AIに任せています。
イメージは以下のような感じです。
```js
// 元の文章
["今日はとても良い天気で、公園で散歩をしていたら美しい花が咲いているのを見つけました。その花を見ながら、穏やかな時間を過ごしました。"]

// AIに分割してもらった文章
[
  "今日はとても良い天気でした。",
  "公園で散歩をしていたら、美しい花を見つけました。",
  "その花を見ながら、ゆったりとした気分になりました。",
  "穏やかな時間を過ごして幸せでした。"
]
```

Gemini API処理部分は以下の通りです。
プロンプトと一緒になげて、ほしい文章を作成してもらっています。
VITE_GEMINI_KEYはGeminiのキーをenvに定義しています。
```ts
import { GoogleGenerativeAI } from "@google/generative-ai";

// 環境変数の読み込み
const API_KEY: string =  process.env.VITE_GEMINI_KEY as string;

// インスタンスの作成
const genAI = new GoogleGenerativeAI(API_KEY);

// Geminiモデルを使用してテキストを生成するための関数
export async function startGemini(prompt:string) {
  // gemini-pro モデルを使用
  const model = genAI.getGenerativeModel({ model: "gemini-pro"});

  // プロンプトに基づいてテキストを生成
  const result = await model.generateContent(prompt);

  // 生成されたテキストを取得
  const response = await result.response;

  // テキストを抽出
  const text = response.text();

  // テキストが配列形式のJSON文字列として返されることを期待
  try {
    const contentArray = JSON.parse(text); // JSONとしてパースして配列に変換
    return contentArray; // 配列を返す
  } catch (error) {
    console.error("JSONの解析に失敗しました:", error);
    return []; // 失敗した場合は空の配列を返す
  }
}

export const createPrompt = (
  name: string,
  title: string | undefined,
  content: string | undefined
) => {
  return `
    タイトル: ${title ?? "タイトルなし"}
    内容: ${content ?? "内容なし"}
    主人公: ${name}

    以上を基に、次の形式の配列で返してください。絵本のようにやさしく親しみやすい文章で返してください。
    [
      "全体の概要",
      "段落1",
      "段落2",
      "段落3",
      ...
    ]
    必ず配列形式で返してください。各段落は文章として、概要は1行で簡潔にまとめてください。
  `;
};
```


# 画像生成処理のAPI
画像生成処理のAPIの処理です。
今回の画像生成はAzureOpenAIで行っています。VITE_AZURE_OPENAI_ENDPOINT、VITE_AZURE_OPENAI_API_KEYはそれぞれAzureOpenAIのエンドポイントとAPIキーをenvに定義しています。
generateImage関数のmessageにプロンプトを含めて、Geminiに作成してもらった配列形式の文章をなげています。

イメージとしてはその配列をループで回して、ページ毎の絵本の画像を作成してもらっています。

```ts
import 'openai/shims/web';
import { AzureOpenAI } from "openai";

// Azure OpenAIのエンドポイントとAPIキーを設定
const endpoint = process.env.VITE_AZURE_OPENAI_ENDPOINT!.replace(/\/$/, "");
const apiKey = process.env.VITE_AZURE_OPENAI_API_KEY;

// 必要なAzure OpenAIのデプロイ名とAPIバージョン
const apiVersion = "2024-02-01";
const deploymentName = "dall-e-3";

// AzureOpenAIクライアントを取得
function getClient(): AzureOpenAI {
  return new AzureOpenAI({
    dangerouslyAllowBrowser: true,
    endpoint,
    apiKey,
    apiVersion,
    deployment: deploymentName,
  });
}

// 画像生成関数（Base64形式）
export const generateImage = async (message: string, numberOfImages: number = 1) => {
  try {
    const client = getClient();

    const results = await client.images.generate({
      prompt: message,
      size: "1024x1024",
      n: numberOfImages,
      model: "",
      response_format: "b64_json", // Base64形式で取得
    });

    // Base64形式の画像データを取得して返す
    const base64Images = results.data.map((image) => image.b64_json);
    return base64Images;
  } catch (error) {
    console.error("画像生成に失敗しました:", error);
    throw error;
  }
};
```

# 絵本作成処理のコスト
Geminiは無料で使用しているのでコストはかかっていません。

AzureOpenAIは最初に200ドルの無料クレジットが付いてきます。（30日間のみ利用可能）
200ドルあれば十分使えます。

そのため、開発にはほぼお金はかかっていません。

ただし、30日間200ドルの期限が切れた後は、画像生成の枚数に応じて使用コストが多少発生しました。（数百円ぐらい）

画像生成は結構お金がかかるので運用するとしたらしっかり考えないといけないです。

AzureOpenAI料金参考
（今回dall-e3を使いましたが、dall-e2を使えばもうすこし安くなりそう）

https://www.goatman.co.jp/media/azure-openai-service-pricing/#%E7%94%BB%E5%83%8F%E7%94%9F%E6%88%90%E3%83%A2%E3%83%87%E3%83%AB_DALL-E_%E6%96%99%E9%87%91%E4%BD%93%E7%B3%BB

# おわりに
初個人開発で悩む部分もたくさんありました。しかし、得るものはたくさんありました。（知識、経験、自信）

この記事がどなたかの役に立てば幸いです。

# 参考記事
https://qiita.com/yu-tsubo2/items/28e7128be9062defd8b7#libgeneratekotowazatextts