MarkdownAIで自分が登場する昔話ジェネレーターを作った！

## はじめに

最近、Markdown AIに興味を持ちました。
個人的にギャル語解説ジェネレーターを作ったのですが、あまり評判ではなかったようで（個人的にはめちゃ気に入ってます）もう少し世の中の為になりそうな楽しいものを考えました。
それが自分自信が昔話の中に登場出来る昔話ジェネレータです。
発想は単純で昔話の世界に自分自身が登場出来たらめちゃくちゃ楽しいだろうな！と思ったことがきっかけです。
あまり時間がないので、今回の目標は以下の通りです。

- 複数の昔話からユーザーが好きなものを選ぶ
- 選択した昔話に、ユーザーが入力した名前や「くん」「ちゃん」といった接尾辞を付けて物語をAIで自動生成
- 簡易読み上げ機能



## 使用技術

- HTML/CSS/JavaScript: 基本的なUIとUXの実装
- Markdown AI: MarkdownベースでWebページを公開し、AIと連携するプラットフォーム


## 機能概要

### 昔話選択

昔話のを選択。
選べる昔話の例：

- 桃太郎
- かぐや姫
- 浦島太郎
- 一寸法師
- 花咲か爺
- （他にも追加可能）

### 名前入力と接尾辞選択

子供が自分の名前を入力し、「くん」「ちゃん」を選択。
これにより、「○○くん」や「○○ちゃん」が物語内に登場。

### AI連携

選択された昔話と入力された名前情報を用いて、AIが新たな昔話を生成。
これはMarkdownAIを使って今回はGPT4oで構築しました。


## サンプルコード

以下は、最終的に調整したサンプルコードの一例です。


```html
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>むかしばなしジェネレーター</title>
    <style>
        body {
            font-family: Arial, sans-serif;
            margin: 0;
            padding: 20px;
            display: flex;
            flex-direction: column;
            align-items: center;
            background-color: #f9f9f9;
        }
        h1 {
            font-size: 2.5rem;
            text-align: center;
            margin-bottom: 20px;
        }
        .input-container {
            margin-top: 20px;
            display: flex;
            flex-direction: column;
            gap: 15px;
            width: 100%;
            max-width: 400px;
        }
        .input-group {
            display: flex;
            flex-direction: column;
        }
        .input-group label {
            margin-bottom: 5px;
            font-weight: bold;
        }
        .input-group input, .input-group select, .input-group button {
            padding: 10px;
            font-size: 16px;
            border: 1px solid #ccc;
            border-radius: 5px;
        }
        .input-group button {
            cursor: pointer;
            background-color: #4CAF50;
            color: white;
            border: none;
            transition: background-color 0.3s;
        }
        .input-group button:hover {
            background-color: #45a049;
        }
        #loadingMessage {
            display: none;
            font-size: 1.2rem;
            color: #555;
            text-align: center;
            margin-top: 10px;
        }
        #answer {
            margin-top: 30px;
            padding: 20px;
            background-color: #fff;
            border: 1px solid #ccc;
            border-radius: 5px;
            width: 100%;
            max-width: 600px;
            text-align: left;
            white-space: pre-wrap;
            box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
        }
        .control-buttons {
            margin-top: 10px;
            display: flex;
            gap: 10px;
        }
        .control-buttons button {
            padding: 10px 15px;
            font-size: 16px;
            border: none;
            color: white;
            border-radius: 5px;
            cursor: pointer;
            transition: background-color 0.3s;
        }
        .control-buttons .play {
            background-color: #007BFF;
        }
        .control-buttons .play:hover {
            background-color: #0056b3;
        }
        .control-buttons .pause {
            background-color: #FFCC00;
        }
        .control-buttons .pause:hover {
            background-color: #d4a600;
        }
        .control-buttons .stop {
            background-color: #FF4136;
        }
        .control-buttons .stop:hover {
            background-color: #d40000;
        }
    </style>
</head>
<body>
    <h1>昔話生成ツール</h1>

    <div class="input-container" id="inputContainer">
        <div class="input-group">
            <label for="storySelect">昔話を選ぶ:</label>
            <select id="storySelect">
                <option value="">-- 選んでね --</option>
                <option value="桃太郎">桃太郎</option>
                <option value="かぐや姫">かぐや姫</option>
                <option value="浦島太郎">浦島太郎</option>
                <option value="一寸法師">一寸法師</option>
                <option value="花咲か爺">花咲か爺</option>
                <option value="鶴の恩返し">鶴の恩返し</option>
                <option value="さるかに合戦">さるかに合戦</option>
                <option value="舌切り雀">舌切り雀</option>
                <option value="こぶとり爺さん">こぶとり爺さん</option>
                <option value="かちかち山">かちかち山</option>
            </select>
        </div>

        <div class="input-group">
            <label for="characterName">お名前を入れてね:</label>
            <input type="text" id="characterName" placeholder="たろう、はなこ など">
        </div>

        <div class="input-group">
            <label for="suffix">どんな呼び方がいい？:</label>
            <select id="suffix">
                <option value="くん">くん (男の子の呼び方)</option>
                <option value="ちゃん">ちゃん (女の子やかわいい呼び方)</option>
            </select>
        </div>

        <div class="input-group">
            <button type="button" id="generateButton">むかしばなし生成</button>
        </div>
        <div id="loadingMessage">むかしばなしを生成中です...</div>
    </div>

    <div id="answer"></div>

    <div class="control-buttons" id="audioControls" style="display:none;">
        <button class="play" id="playButton">再生</button>
        <button class="pause" id="pauseButton">一時停止</button>
        <button class="stop" id="stopButton">停止</button>
    </div>

<script>
    let speechSynthesisUtterance;
    const synth = window.speechSynthesis;

    document.getElementById('generateButton').addEventListener('click', async () => {
        const loadingMessage = document.getElementById('loadingMessage');
        loadingMessage.style.display = 'block';

        const story = document.getElementById('storySelect').value;
        const name = document.getElementById('characterName').value;
        const suffix = document.getElementById('suffix').value;
        const prompt = `昔話を選択します。\n名前を入力します。\nくん・ちゃんを選択します。\nこれを変数にして、昔話の${story}を${name}${suffix}が登場する話として生成してください。`;

        const serverAi = new ServerAI();
        const answer = await serverAi.getAnswerText('63T66ZWxJBCymJBAPjvaiJ', '', prompt);

        document.getElementById('answer').innerText = answer;
        document.getElementById('audioControls').style.display = 'flex';
        loadingMessage.style.display = 'none';
        initializeSpeech(answer);
    });

    function initializeSpeech(text) {
        if (speechSynthesisUtterance) {
            synth.cancel();
        }
        speechSynthesisUtterance = new SpeechSynthesisUtterance(text);
        speechSynthesisUtterance.lang = 'ja-JP';
    }

    document.getElementById('playButton').addEventListener('click', () => {
        if (synth.paused) {
            synth.resume();
        } else {
            synth.speak(speechSynthesisUtterance);
        }
    });

    document.getElementById('pauseButton').addEventListener('click', () => {
        if (synth.speaking) {
            synth.pause();
        }
    });

    document.getElementById('stopButton').addEventListener('click', () => {
        synth.cancel();
    });
</script>
</body>
</html>

```

実際のサイトは以下です。
是非試してみてください。
https://mdown.ai/content/777bb03a-ffa6-4880-a11d-94aaf93a295c

![スクリーンショット 2024-12-23 21.04.57.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2841127/b5876d6b-a01c-b73b-ff88-b47580c80b96.png)
![スクリーンショット 2024-12-23 21.05.11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2841127/73c69be0-2364-7d89-830e-0a61cb367b6c.png)

使い方は簡単で昔話を選んで、好きな名前を入れ生成ボタンを押すだけです。
生成後は下段に再生ボタンが表示されるので押すと読み上げしてくれます。


## 難点

CSSやJavascriptの仕様がイマイチ分からなかったので、何が使えて何が使えないのかが判別できなかったです。
またAI側もナレッジの使用などが出来ないようで、出来たらもっと面白い昔話が生成出来るかなと思いました。
生成に結構時間がかかります。30秒くらい。もう少し早くなると良いかな。

## 将来的に

実はこのアイデア結構良いなと思っていて、絵本風のデバイスとして作り上げたいと考えています。
Open AIのAvvancedVoiceAPI使えば読み上げもかなり自然になると思いますし、子供と対話しながら物語が進んでいくような展開も楽しそうかなと発想やアイデアがかなり広がりました。


## まとめ

Markdown AI自体ここ１ヶ月で知って触ってみたのですが、かなり面白いサービスだと思いました。
特にこういった技術は触ってみる事と自身のアイデアをどれだけ溶け込ませる事が出来るかが重要です。
私の場合はまだまだ技術的にも発想力も乏しいのでもっと精進せねばと思いました。
それと同時にかなり楽しく作る事が出来たので、これからもMarkdownAIは触っていきたいと思います！