Markdown AIで「パパ、これどういうこと？」を解決してみた


## 1. はじめに

　ゆるゆるOL、文系出身、大学生の時にPythonの授業についていけず挫折した経験があります。半年ほど前の定期異動で情シスにやってきたのですが、「IPアドレス」レベルの言葉の定義がわからなかった状態でした。 

生成AIについても、「えーわかんない、ChatGPTに聞いてみたら(笑)」みたいなやりとりでしか意識しない生活でした。上司が生成AIに詳しいので、同じレベルで話がしたい！と思い勉強して、11月に生成AIパスポートを取得。ChatGPTには課金もして日々ダル絡みしています。

　こんなよわよわな者がQiitaで記事を書くなんて…ROM専で十分や…と思っていたところ、先日、[Qiita Woman Summit supported by パナソニック コネクト](https://increments.connpass.com/event/339944/?utm_campaign=&utm_source=notifications&utm_medium=email&utm_content=title_link)というイベントに参加し、アウトプットの大切さを知ったので、キャンペーンに参加しつつ初投稿してみます。お手柔らかにお願いします.

**「その知識、間違いだよ！」などのご指摘、大歓迎です。**

コメント欄までお待ちしております.



## 2. よわよわでも大丈夫！Markdown AIを触ってみよう

- **基本機能と特徴**  
  [Markdown AIとは何か？](https://qiita.com/mdown_ai_jpn/items/58c58dfd16954c19aeaf)より、

  > Markdown AIの特徴として、  
  > 
  > - 使用料が無料  
  > - Markdown記法でサイトが作れる  
  > - AI実装が簡単にできる  
  > - AIを独自にカスタマイズできる  
  > - 面倒なサーバー設定が全く要らずにボタンひとつでサイトを公開  
  > 
  > が強みとなっています.

  とのことです。  
  なお私は、本キャンペーン[Markdown AIチャレンジ！効率化と個性を活かした活用術！](https://qiita.com/official-events/52830497d1d63301da95)に出会うまで、「Markdown AI」はおろか「マークダウン記法」を知りませんでした。というか今もわかっていないです。  
  個人的には  
  **「無料で」「サーバー設定不要で」「全世界に公開可能」**  
  というところが魅力的だと感じています.

- **利用手順**  
  グーグルアカウントさえ持っていれば誰でも始められます。面倒な初期設定も不要です。  
  こちらの記事が非常に勉強になったので、私が改めてまとめ直す必要はないかと思っています。  

 https://qiita.com/pdc-ujoy/items/81009936ddf843665e40

---

## 3. MarkdownAIで作成したサイトの概要

- **コンテンツの概要**  
  今回作ったのは、「子供向けニュース・論文変換サイト」です。  
  ニュースや論文の難しい内容を、子供向けに変換してくれます。このとき、対象の子供の年齢層と母国語を選ぶことができます。お子さんがご自身で使うというよりは、親御さんが使って、出力された内容をお子さんが読んだり、親御さんがお子さんに説明したりするという使い方を想定しています。  
  例えば、下記のようなサイトも存在するのですが、  

https://www3.nhk.or.jp/news/easy/
  
  そもそものニュースの内容があまり難しくない且つ大きなニュースだけが選ばれているな、と感じています。  
  「子供向けニュース・論文変換サイト」は、生成AIの活用によって、どんな内容でもお子さん向けに説明しやすくしてくれるのが強みです.

- **狙いや目的**  
  最初は、さまざまなモデルを作って遊んでいたのですが、  
  - できるだけ多くの人に使ってもらえて（せっかく全世界に公開できるので)
  - 役に立って（面白い！よりも便利！を重視したかったので）  
  - 自分の本業にも生かせそう（コンテンツ発信をしている会社なので）  
  というところを考えていった結果、たどり着いたのが「子供向けニュース・論文変換」です。  
  ひとまず触ってみたい！という方は、[こちら](https://mdown.ai/content/d39ffc88-99b3-4f15-814e-32abe9189dc9)をご覧ください.



## 4. 制作プロセスの詳細

- **AIモデルの作成**  
  まずは、メインで稼働するモデルを作成しました。  
  初期のプロンプトがこちら:

  ```plaintext
  Role: あなたはニュース記事を子供向けに書き換える編集者です。  
  Request: 以下のニュース記事を、指定された子どもの年齢層および母国語に合わせて、分かりやすく、かつ不適切な内容には保護者の確認を促す文言を付け加えて書き換えてください。  
  Rule: 出力はMarkdown形式で、文章の内容はシンプルで理解しやすいものにしてください。  
  Rule: 子どもの年齢層および母国語の情報は、後から渡されるパラメータに基づいて対応してください。  
  Reason: 入力されたニュース記事の内容を、そのまま掲載すると子どもにとって難しい表現や不適切な表現がある場合があるため。  
  Recommend: 不適切な表現（例：暴力、性的な表現、ギャンブル、ドラッグ等）が含まれる場合、保護者に確認を促すメッセージを追加してください.
  ```

  初っ端は、ニュースの変換しか考えていなかったのですが、ニュースだけでなく論文など学術書も変換できたら面白いなと思い、修正していきました。  
  それ以外にも、指示通りに出力されないなどのエラーが何度も発生したため、修正を重ね、現段階で動いているプロンプトがこちらです.

  ```plaintext
  Role:
  あなたはニュース記事や学術的な文章、その他の情報記事を子供向けに書き換える専門の編集者です。常に原文の事実や情報を正確に保持しながら、指定された子供が理解しやすい文章に変換してください.

  Request:
  以下の文章を、指定された子どもの年齢層と母国語に合わせ、内容がシンプルかつ明瞭になるよう書き換えてください. なお、文章のジャンルはニュース記事に限らず、学術論文やその他の情報記事でも構いません. また、文章が長い場合でも、可能な限り全文の内容を変換対象としてください.
  ただし、文章内に不適切な内容（例：暴力、性的な表現、ギャンブル、ドラッグ等）が含まれている場合にのみ、文章の冒頭に「保護者の確認が必要です」という警告文を追加してください. 不適切な内容が存在しない場合は、警告文は出力しないでください.

  変換ルール（年齢層別）:

  **3歳から5歳向け（幼児）:**
  - 文章は必ずすべてひらがな・カタカナのみで記述すること.  
    例:  
    - 「福岡県」→「ふくおかけん」  
    - 「行橋市」→「ゆくはしし」  
    - 「事故」→「じこ」  
    - 「軽乗用車」→「けいじょうようしゃ」
  - すべての専門用語や一般用語も、ひらがな・カタカナのみで表記すること.

  **6歳から8歳向け（小学校低学年）:**
  - 文章中は適切な漢字を使用する. 固有名詞や重要な語句（例：福岡県、行橋市、事故、軽自動車、車など）は漢字で記述する.
  - 各漢字または漢字を含む語句の直後に、必ず（）内に正しい読み仮名（ひらがな）を添えること.  
    例:  
    - 「福岡県」→「福岡県（ふくおかけん）」  
    - 「行橋市」→「行橋市（ゆくはしし）」  
    - 「事故」→「事故（じこ）」  
    - 「軽自動車」→「軽自動車（けいじどうしゃ）」  
    - 「車」→「車（くるま）」

  **9歳から12歳向け（小学校高学年）:**
  - 標準的な文章として記述すること.  
    必要以上にひらがなを多用せず、読みやすい自然な文章を心がけること.  
    （※必要に応じて固有名詞や重要語句にふりがなを付けることも可能ですが、むやみにふりがなを多用しないこと.)

  **出力形式:**
  - 出力形式については、特定の形式（Markdown等）に固執せず、指定された年齢層に最も適した形式で表現すること.

  **全体の注意事項:**
  - 文章はシンプルで明確にし、子供が容易に理解できるようにすること.
  - 入力文章に含まれる事実や情報は正確に保持し、誤解を招く変換や事実の歪曲を行わないこと.
  - 入力文章がニュース記事以外のジャンル（学術論文など）であっても、元の内容の正確さを保ちつつ、指定された子ども向けの言葉に変換すること.
  - 入力文章が長文の場合でも、全文を変換対象とし、可能な限り全体の内容を反映させること.

  **Recommend:**
  - 不適切な表現（例：暴力、性的な表現、ギャンブル、ドラッグ等）が含まれている場合にのみ、文章の冒頭に「保護者の確認が必要です」という警告メッセージを追加すること.
  - 不適切な表現が全く含まれていない場合は、警告文は一切追加しないこと.
  - 事実や情報を捻じ曲げたり、曖昧な情報に基づいた変換は絶対に行わないこと.
  ```

よく見ると言葉が強いな。笑  
恐らくこれでもまだ指示通りに動かないところはあると思うので、さらに修正していく必要があると考えています.

- **大枠の構築**  
  当初考えていた大枠は、  
  - 元の文章の入力欄  
  - 年齢層・母国語の選択欄  
  - 「変換する」というボタン  
  - 変換された記事の出力欄  
  
  といった、いたってシンプルなものでした.  
  そこから、  
  
  - 変換を待つ時間が暇なので、暇つぶしできる機能 → アンケート  
  - 入力した記事を一括削除できるボタン  
  が欲しいと思い、入れ込みました。それでもまだまだシンプルですが…


  ![スクリーンショット (87).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3819454/082f6018-1ed2-444d-a519-c6d8841e1f41.png)


　![スクリーンショット (88).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3819454/921f78c8-c974-4ab1-b9b0-bfa2fdb0254f.png)



  また、本当は、文章そのものをコピペで入力するのではなく、ニュース記事のURLや論文のPDFを読ませる機能を実装したかったのですが、力及ばず、実現できませんでした.  
  
  そして、記事変換の暇つぶしとして追加したアンケート機能ですが、現段階で集計する仕組みはできていません。本当に暇をつぶしてもらっているだけです。ここは今後変えていくので、今回は見逃してください.


- **完成版コード**  
  公開しているサイトのコードがこちらです.  

 ```html
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>子供向けニュース・論文変換サイト</title>
<link href="https://fonts.googleapis.com/css2?family=Luckiest+Guy&display=swap" rel="stylesheet">
<style>
  /* 全体のレイアウトと落ち着いた背景 */
  body {
    font-family: "Comic Sans MS", cursive, sans-serif;
    padding: 20px;
    background: linear-gradient(to right, #e0eafc, #cfdef3);
    color: #333;
  }
  /* タイトルのスタイル：フォントはLuckiest Guy、サイズは48px、色は黒 */
  h1 {
    font-size: 48px;
    text-align: center;
    color: #000;
    font-family: 'Luckiest Guy', cursive;
    margin-bottom: 30px;
  }
  /* 入力コンテナのスタイル */
  .input-container {
    position: relative;
    margin-bottom: 20px;
  }
  /* テキストエリアのスタイル（枠線は黒、下部余白追加） */
  textarea {
    width: 100%;
    height: 150px;
    border: 2px solid #000;
    border-radius: 10px;
    padding: 10px;
    font-size: 16px;
    padding-bottom: 40px; /* クリアボタン分の余白 */
  }
  /* クリアボタンのスタイル：入力欄の右下に配置 */
  .clear-btn {
    position: absolute;
    bottom: 10px;
    right: 10px;
    padding: 8px;
    font-size: 14px;
    background-color: #3366cc;
    color: white;
    border: none;
    border-radius: 5px;
    cursor: pointer;
  }
  .clear-btn:hover {
    background-color: #224488;
  }
  /* セレクトボックスとその他のボタンのスタイル */
  select, button {
    padding: 8px;
    margin: 8px 0;
    font-size: 16px;
    border-radius: 5px;
  }
  button {
    background-color: #3366cc; /* 濃い青色 */
    border: none;
    color: white;
    cursor: pointer;
  }
  button:hover {
    background-color: #224488;
  }
  /* 出力エリアのスタイル（文字サイズ大） */
  #output {
    background: #fff3e0;
    border: 2px dashed #ff9900;
    padding: 20px;
    margin-top: 20px;
    font-size: 24px;
    color: #333;
    border-radius: 10px;
  }
  /* アンケートエリアのスタイル */
  #survey {
    background: #e6ffe6;
    border: 2px solid #009900;
    padding: 20px;
    margin-top: 20px;
    font-size: 18px;
    color: #333;
    border-radius: 10px;
  }
  /* モーダルウィンドウのスタイル */
  .modal {
    display: none;
    position: fixed;
    z-index: 100;
    left: 0;
    top: 0;
    width: 100%;
    height: 100%;
    overflow: auto;
    background-color: rgba(0,0,0,0.5);
  }
  .modal-content {
    background-color: #fff;
    margin: 15% auto;
    padding: 20px;
    border: 1px solid #888;
    width: 80%;
    max-width: 400px;
    text-align: center;
    border-radius: 10px;
  }
  /* ローディング状態のスタイル */
  .is-loading {
    opacity: 0.5;
  }
</style>
</head>
<body>

<h1>子供向けニュース・論文変換サイト</h1>

<label for="newsInput">変換したい文章を入力してください</label><br>
<div class="input-container">
  <textarea id="newsInput"></textarea>
  <button id="clearButton" class="clear-btn">クリア</button>
</div>

<label for="ageGroup">子どもの年齢層</label>
<select id="ageGroup">
  <option value="幼児">幼児 (3-5歳)</option>
  <option value="小学校低学年">小学校低学年 (6-8歳)</option>
  <option value="小学校高学年">小学校高学年 (9-12歳)</option>
</select><br>

<label for="language">子どもの母国語:</label>
<select id="language">
  <option value="日本語">日本語</option>
  <option value="韓国語">韓国語</option>
  <option value="中国語">中国語</option>
  <option value="英語">英語</option>
</select><br>

<button id="rewriteButton">変換する</button>

<!-- アンケートエリアを記事変換結果より上に配置 -->
<div id="survey" style="display: none;"></div>
<div id="output" style="display: none;"></div>

<!-- 保護者確認用モーダル -->
<div id="guardianModal" class="modal">
  <div class="modal-content">
    <p>子どもに不適切な内容が含まれている可能性があります。<br>
       続行しますか？</p>
    <button id="confirmButton">はい、続行する</button>
    <button id="cancelButton">キャンセル</button>
  </div>
</div>

<script>
  // アンケートデータ（10種類）
  const surveys = [
    {
      title: "【1. お子様の学習習慣について】",
      question: "お待ちの間、アンケートのご協力をお願いいたします。\n「お子様の学習習慣形成において、最も効果的だとお考えの取り組みは何ですか？」",
      options: [
        "規則正しい生活リズムの確立",
        "興味を引く教材の利用",
        "家族との対話の充実",
        "無回答"
      ]
    },
    {
      title: "【2. お子様とのコミュニケーションについて】",
      question: "お待ちの間、アンケートのご協力をお願いいたします。\n「日常のコミュニケーションの中で、最も大切にされていることは何ですか？」",
      options: [
        "心温まる会話の時間",
        "意思疎通を深めるアクティビティ",
        "共通の趣味や関心の共有",
        "無回答"
      ]
    },
    {
      title: "【3. お子様の日常生活について】",
      question: "お待ちの間、アンケートのご協力をお願いいたします。\n「お子様の日常生活で、最も重視される要素は何だとお考えですか？」",
      options: [
        "規則正しい生活リズムの確立",
        "自由な発想と遊びの時間の充実",
        "バランスのとれた食事と健康管理",
        "無回答"
      ]
    },
    {
      title: "【4. 学習環境の充実】",
      question: "お待ちの間、アンケートのご協力をお願いいたします。\n「お子様の学習環境について、どの観点を最も大切にされていますか？」",
      options: [
        "家庭での積極的な学習サポート",
        "自発的な興味と好奇心の育成",
        "学校や地域の教育資源の活用",
        "無回答"
      ]
    },
    {
      title: "【5. テクノロジーの活用】",
      question: "お待ちの間、アンケートのご協力をお願いいたします。\n「お子様の学びにおいて、テクノロジーの導入についてどのようにお考えですか？」",
      options: [
        "積極的に取り入れるべき",
        "適度なバランスで活用する",
        "伝統的な学びの方法を尊重する",
        "無回答"
      ]
    },
    {
      title: "【6. 家庭と仕事の両立】",
      question: "お待ちの間、アンケートのご協力をお願いいたします。\n「ご家庭での子育てとお仕事の両立において、最も注力されている点はどれですか？」",
      options: [
        "家族との時間を最優先する",
        "仕事と家庭のバランスを保つ",
        "柔軟な働き方の導入を試みる",
        "無回答"
      ]
    },
    {
      title: "【7. 地域コミュニティとの連携】",
      question: "お待ちの間、アンケートのご協力をお願いいたします。\n「子育てにおける地域コミュニティの役割について、どのようにお感じになられますか？」",
      options: [
        "地域の支援が子育ての大きな助けとなる",
        "近隣との交流が子どもの成長を促す",
        "基本的には家庭内で完結させる",
        "無回答"
      ]
    },
    {
      title: "【8. 将来への展望】",
      question: "お待ちの間、アンケートのご協力をお願いいたします。\n「お子様の将来について、最も感じることは何ですか？」",
      options: [
        "明るい未来への期待を持っている",
        "変化に柔軟に対応する力の育成が必要",
        "現代社会の不透明さに対する不安がある",
        "無回答"
      ]
    },
    {
      title: "【9. 子育てのアプローチ】",
      question: "お待ちの間、アンケートのご協力をお願いいたします。\n「ご自身の子育てスタイルとして、最も当てはまるものはどれですか？」",
      options: [
        "厳しさと温かさを併せ持つバランス型",
        "自由と自立を重んじる育て方",
        "状況に応じた柔軟な対応を心がける",
        "無回答"
      ]
    },
    {
      title: "【10. お子様の将来への期待について】",
      question: "お待ちの間、アンケートのご協力をお願いいたします。\n「お子様の未来に向け、最も重視される点はどれだとお感じになりますか？」",
      options: [
        "豊かな人間関係の構築",
        "多様な経験の積み重ね",
        "心身の健やかな成長",
        "無回答"
      ]
    }
  ];


  // 不適切なキーワードのリスト
  const inappropriateKeywords = ["暴力", "性的", "ギャンブル", "ドラッグ"];

  // 入力された記事原文に不適切なキーワードが含まれるかチェック
  function checkInappropriate(text) {
    for (let keyword of inappropriateKeywords) {
      if (text.includes(keyword)) {
        return true;
      }
    }
    return false;
  }

  // モーダル表示／非表示用の関数
  const guardianModal = document.getElementById('guardianModal');
  const confirmButton = document.getElementById('confirmButton');
  const cancelButton = document.getElementById('cancelButton');

  function showModal() {
    guardianModal.style.display = 'block';
  }
  function hideModal() {
    guardianModal.style.display = 'none';
  }

  // モーダル内ボタンのイベント
  confirmButton.addEventListener('click', () => {
    hideModal();
    processRewrite(true);
  });
  cancelButton.addEventListener('click', () => {
    hideModal();
    alert("保護者の確認が必要なため、変換を中止しました。");
  });

  // 変換ボタンのイベントリスナー
  document.getElementById('rewriteButton').addEventListener('click', () => {
    // 前の変換結果とアンケート結果をクリアする
    const outputDiv = document.getElementById('output');
    outputDiv.innerText = "";
    outputDiv.style.display = 'none';

    const surveyDiv = document.getElementById('survey');
    surveyDiv.innerHTML = "";
    surveyDiv.style.display = 'none';

    const newsText = document.getElementById('newsInput').value;
    if (!newsText.trim()) {
      alert("テキストを入力してください。");
      return;
    }
    // 不適切な内容のチェック
    if (checkInappropriate(newsText)) {
      showModal();
    } else {
      processRewrite(false);
    }
  });

  // クリアボタンのイベントリスナー（入力テキストを一括削除）
  document.getElementById('clearButton').addEventListener('click', () => {
    document.getElementById('newsInput').value = "";
  });

  // 記事変換とアンケート表示処理
  function processRewrite(guardianConfirmed) {
    const button = document.getElementById('rewriteButton');
    button.classList.add('is-loading');
    button.disabled = true;

    const newsText = document.getElementById('newsInput').value;
    const ageGroup = document.getElementById('ageGroup').value;
    const language = document.getElementById('language').value;

    // 変換リクエスト用のデータを作成
    const conversionData = {
      newsText: newsText,
      ageGroup: ageGroup,
      language: language,
      guardianConfirmed: guardianConfirmed
    };

    // アンケートエリアをすぐに表示（10種類のアンケートの中からランダムに1つ選び、その内容を表示し、選択肢ボタンのイベントを設定する）
    const surveyDiv = document.getElementById('survey');
    surveyDiv.style.display = 'block';
    generateRandomSurvey(surveyDiv);

    const serverAi = new ServerAI();
    // AIモデルID "hXBJYigMSDrdV9M8FBmYYa" を使用して記事変換を実行する
    serverAi.getAnswerText("hXBJYigMSDrdV9M8FBmYYa", "", conversionData, { stream: false })
      .then(conversionResult => {
         const outputDiv = document.getElementById('output');
         outputDiv.style.display = 'block';
         outputDiv.innerText = conversionResult;
      })
      .catch(error => {
         alert("記事の変換中にエラーが発生しました。");
         console.error(error);
      })
      .finally(() => {
         button.classList.remove('is-loading');
         button.disabled = false;
      });
  }

  // 10種類のアンケートの中からランダムに1つ選び、その内容を表示し、選択肢ボタンにイベントを設定する
  function generateRandomSurvey(surveyDiv) {
    const randomIndex = Math.floor(Math.random() * surveys.length);
    const selectedSurvey = surveys[randomIndex];
    let surveyHTML = `<h2>${selectedSurvey.title}</h2>
                      <p>${selectedSurvey.question}</p>
                      <div>`;
    selectedSurvey.options.forEach(option => {
      surveyHTML += `<button class="survey-option" data-option="${option}">${option}</button> `;
    });
    surveyHTML += `</div>`;
    surveyDiv.innerHTML = surveyHTML;

    // 各選択肢ボタンにクリックイベントを設定
    const optionButtons = surveyDiv.querySelectorAll(".survey-option");
    optionButtons.forEach(button => {
      button.addEventListener("click", () => {
        surveyDiv.innerHTML = `<p>ご回答いただきありがとうございます。お答えいただいた内容は当サイトの運営に役立てます。<br>
        記事変換終了までもう少々お待ちください。</p>`;
      });
    });
  }
</script>

</body>
</html>

```



## 5. 総括と今後の意気込み

　今回はほとんどを生成AIに頼って作りました。逆に言えば、MarkdownAIと生成AIがあれば、コードが一切書けない初心者でもこのレベルのものは作れる、というわけです。いい時代。新しき良き時代ですね.  
  
とはいえ、「サーバーの設定？ちょろいね」「自分でサイト運営してるやで」という方からすると、MarkdownAIでは叶わないことが多くてもどかしく感じられるかもしれません.
 
今後は、もともとやりたかった「お子さんがご自身で変換したい文章を入力する」という形式のサイトも作りたいと考えています。また、今回で使い方をかなり覚えたので、もっと自分の趣味や勉強に活かせるサイトも作ってみたいです.  
余談ですが、私自身、活字を読むのが得意ではないので、これからは生成AIを使って論文や技術書を読みやすい文章に書き換えてもらえばいいのか！という発見があったのも収穫の一つです.

:::note info
お願い  
機能として実装したものの、実は私、英語も中国語も韓国語もわからないです.  
そのため、日本語の出力のためにはプロンプトを何度も修正しましたが、このモデルの日本語以外の出力の精度は未知数です. ここから直していくことになりますが、かなり時間がかかると思います.  
是非、英語・中国語・韓国語に精通している方がいらっしゃったら、一緒にプロンプトの修正をしていただけますと助かります…！！
:::


## 6. 参考資料・リンク集

- **公式サイト・ドキュメント**  
  - [Markdown AI公式サイト](https://mdown.ai/)  
  - [Markdown AIの詳細説明ページ](https://mdown.ai/content?id=335b81b2-bcb0-4dbb-83d0-531aa132181f)

- **参考記事**  
  本文中にも記載しましたが、使い方に関して、とても勉強になりました。ありがとうございました！！
  
  [Markdown AIで試験問題出題サイトを作ってみた！初心者でもわかる使い方も説明しています](https://qiita.com/pdc-ujoy/items/81009936ddf843665e40)