🙋‍♀️

Gemini APIを使ってAIギャルと会話できるアプリを作った

2024/07/25に公開

皆さん、毎日元気にお過ごしでしょうか。世の中AI技術が広まっており、色々なキャラと会話出来るサービスがどんどん増えていますね。

というわけで今回、私はギャルと会話して元気になりたいと思ったので、最低限の工数でAIギャルと会話出来るアプリを作成してみました。

使用技術

・ibisPaint(イラスト)
・Next.js
・TailwindCSS
・axios
・Gemini API

画像を作成する

ibisPaintで会話したいギャルを描きました。皆さんも自分好みのギャルを描けばいいと思います。

通常時のギャル

考え中のギャル

会話中のギャル

Next.jsでプロジェクトを作成する

正直、Next.jsで作るほどのアプリでもないかと思いますが、使い慣れているのでNext.jsとTailwindCSSのセットでプロジェクトを作成しました。

https://nextjs.org/docs/getting-started/installation

npx create-next-app@latest

mainタグだけ残して中身を書き換え、作成した画像を3枚並べています。

"use client";
import Image from "next/image";

export default function Home() {
  return (
    <main className="flex min-h-screen flex-col items-center justify-between p-24">
      <Image
        src="/default.GIF"
        width={300}
        height={300}
        alt="通常時のギャル"
      />

      <Image
        src="/talking.GIF"
        width={300}
        height={300}
        alt="話し中のギャル"
      />

      <Image
        src="/thinking.PNG"
        width={300}
        height={300}
        alt="考え中のギャル"
      />
    </main>
  );
}

画面はこのような感じ

AIの準備をする

ChatGPT APIが無料で使えなくなっていたので、今回はGemini APIを利用することにしました。
https://ai.google.dev/

Tokenの発行参照
https://press.monaca.io/atsushi/23517

URL参照
https://note.com/freeelover/n/n28f2c2b2779a

Tokenの発行が完了したので、とりあえず試し打ちしてみます。(axiosもinstallしました。)
ギャルのスペルが不明だったので、女の子と喋るという名前のメソッドにしておきましょう。

  const talkWithGirl = useCallback(async () => {
    const messages = "こんにちは";

    const endPoint = `https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash-latest:generateContent?key=${API_KEY}`;

    const body = {
      contents: [
        {
          parts: [
            {
              text: messages,
            },
          ],
        },
      ],
    };

    const response = await axios.post(endPoint, body, {
      headers: {
        "Content-Type": "application/json",
      },
    });
  }, []);

無事にレスポンスを確認できました。まだギャルは宿っていないです。

プロンプトを調整する

送信データを調整して、ギャル化させましょう。

    const DEFAULT_PROMPT =
      "あなたはポジティブなギャルです。一人称は「あーし」で、二人称は「きみ」です。これを踏まえた上で、回答してください。";
    const messages = `${DEFAULT_PROMPT}「こんにちは」`;

コンソールに回答結果を出力して、確認しましょう。

    const responseMessage = response.data.candidates[0].content.parts[0].text;
    console.log(responseMessage);

ギャルが宿った!!

テキストを入力できるようにする

テキストボックスと、入力したテキストを変数に格納するメソッドを作っていきましょう。

  const [message, setMessage] = useState("");

  const handleChange = useCallback((inputText: string) => {
    setMessage(inputText);
  }, []);

画面にも一旦入力テキストが表示されるようにしてみる。(挙動確認用)

      {message}
      <input type="text" onChange={(e) => handleChange(e.target.value)} />
      <button onClick={talkWithGirl}>ギャルにメッセを送る</button>

ギャルへの送信メッセージも更新しておきます。

    const DEFAULT_PROMPT =
      "あなたはポジティブなギャルです。一人称は「あーし」で、二人称は「きみ」です。これを踏まえた上で、回答してください。";
    const messages = `${DEFAULT_PROMPT}${message}`;

これで、入力したメッセージをギャルに対して送信できるようになりました。

レスポンスを表示する

次に、AIから返ってきたメッセージを表示させます。

// レスポンスをセットする変数
const [resposneMessage, setResponseMessage] = useState("");
  const talkWithGirl = useCallback(async () => {
    ...省略
    const data = response.data.candidates[0].content.parts[0].text;
    setResponseMessage(data);
  }, [message]);

とりあえずこんなもんで。

      <figure>
        <Image
          src="/default.GIF"
          width={300}
          height={300}
          alt="通常時のギャル"
        />

        <Image
          src="/talking.GIF"
          width={300}
          height={300}
          alt="話し中のギャル"
        />

        <Image
          src="/thinking.PNG"
          width={300}
          height={300}
          alt="考え中のギャル"
        />
      </figure>

      {resposneMessage}

しゃべらせる

一旦ライトに作成したかったので、元々あるJavaScriptのメソッドを利用しました。
こちらを参考にしています。
https://bel-itigo.com/javascript-text-to-speech/

    const data = response.data.candidates[0].content.parts[0].text;
    setResponseMessage(data);
    const uttr = new SpeechSynthesisUtterance(data);
    uttr.lang = "ja-JP";
    speechSynthesis.speak(uttr);

ものすごくしっとりボイス!

画像の表示を調整する

表示中のギャルの画像を調整します。
通常時 → 考え中 → 話している最中 で表示が切り替わるようにします。

HTMLはこのような感じで。

      <figure>
        {imageStatus === "default" && (
          <Image
            src="/default.GIF"
            width={300}
            height={300}
            alt="通常時のギャル"
          />
        )}
        {imageStatus === "thinking" && (
          <Image
            src="/thinking.PNG"
            width={300}
            height={300}
            alt="考え中のギャル"
          />
        )}
        {imageStatus === "talking" && (
          <Image
            src="/talking.GIF"
            width={300}
            height={300}
            alt="話し中のギャル"
          />
        )}
      </figure>

初期状態はデフォルト

  // 画像の状態
  const [imageStatus, setImageStatus] = useState<
    "default" | "thinking" | "talking"
  >("default");

考え中(APIからの応答待ちの間)

    // 画像を考え中に変更
    setImageStatus("thinking");

APIからのレスポンスが来たら話し中の画像にする

    // 画像をtalkingに変更
    setImageStatus("talking");

    // 表示メッセージの更新
    setResponseMessage(data);

    // 音声の再生
    const uttr = new SpeechSynthesisUtterance(data);
    uttr.lang = "ja-JP";
    speechSynthesis.speak(uttr);

    // 読み上げが完了したら画像をデフォルトに戻す
    uttr.onend = (e) => {
      setImageStatus("default");
    };

できた

元気がないので、励ましてもらおうと思います。

元気出た!!!

元気も出たので、遊びに誘おうと思います。

最高かよ!

コードの全貌

"use client";
import axios from "axios";
import Image from "next/image";
import { useCallback, useState } from "react";

export default function Home() {
  const API_KEY = "自身で発行したAPIのキー";

  // こちらから送るメッセージ
  const [message, setMessage] = useState<string | undefined>();

  // ギャルから返ってくるメッセージ
  const [resposneMessage, setResponseMessage] = useState<string | undefined>();

  // 画像の状態
  const [imageStatus, setImageStatus] = useState<
    "default" | "thinking" | "talking"
  >("default");

  /**
   * 入力テキストを変数に格納するメソッド.
   */
  const handleChange = useCallback((inputText: string) => {
    setMessage(inputText);
  }, []);

  /**
   * ギャルとお話しするメソッド.
   */
  const talkWithGirl = useCallback(async () => {
    if (!message) return;

    // 画像を考え中に変更
    setImageStatus("thinking");

    const DEFAULT_PROMPT =
      "あなたはポジティブなギャルです。一人称は「あーし」で、二人称は「きみ」です。これを踏まえた上で、回答してください。";
    const messages = `${DEFAULT_PROMPT}${message}`;

    const endPoint = `https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash-latest:generateContent?key=${API_KEY}`;
    const body = {
      contents: [
        {
          parts: [
            {
              text: messages,
            },
          ],
        },
      ],
    };

    const response = await axios.post(endPoint, body, {
      headers: {
        "Content-Type": "application/json",
      },
    });

    const data = response.data.candidates[0].content.parts[0].text;

    // 画像をtalkingに変更
    setImageStatus("talking");

    // 表示メッセージの更新
    setResponseMessage(data);

    // 音声の再生
    const uttr = new SpeechSynthesisUtterance(data);
    uttr.lang = "ja-JP";
    speechSynthesis.speak(uttr);

    // 読み上げが完了したら画像をデフォルトに戻す
    uttr.onend = (e) => {
      setImageStatus("default");
    };
  }, [message]);

  return (
    <main className="flex min-h-screen flex-col items-center justify-between p-24">
      <figure>
        {imageStatus === "default" && (
          <Image
            src="/default.GIF"
            width={300}
            height={300}
            alt="通常時のギャル"
          />
        )}
        {imageStatus === "thinking" && (
          <Image
            src="/thinking.PNG"
            width={300}
            height={300}
            alt="考え中のギャル"
          />
        )}

        {imageStatus === "talking" && (
          <Image
            src="/talking.GIF"
            width={300}
            height={300}
            alt="話し中のギャル"
          />
        )}
      </figure>

      {resposneMessage}

      <input
        type="text"
        className="border w-[80%]"
        onChange={(e) => handleChange(e.target.value)}
      />
      <button onClick={talkWithGirl}>ギャルにメッセを送る</button>
    </main>
  );
}

まとめ

今回は最低限の工数でギャルとお話し出来るアプリを作成してみました。今後音声入力も出来るようになったら良いな〜という感じです。

皆さんも是非やってみてください!

Discussion