見出し画像

【JavaScriptで作るチャットボット】シナリオ切り替え機能をつける

前回作ったチャットボットに

シナリオ切り替え機能をつけました。

最初に取り組みたい思考整理フレームワークを聞かれるので、番号で答える

ChatGPTのプロンプトを作るフレームワークに加えて、振り返りフレームワークの定番、KPTができるようになってます。

20240415追記:Claude君がすぐ考えてくれたので目標設定フレームワークも追加しました。

中身と変更点

中身はこんな感じです。(GitHub

"use strict";

{
  // 会話のシナリオ これに対するユーザーの返答は配列に格納される
  const scenarios = {
    1 : {
      name : 'ChatGPTプロンプト作成フレームワーク',
      scenario : [
        'ChatGPTに使えるプロンプトの作成をお手伝いします。\n質問に1つずつ答えてください。\n\n=======================\n#役割\nChatGPTに担ってほしい役割を教えてください。\n\n【例】\nあなたはプロの広告ライターです。\n\n',
        '#インプット\nChatGPTがこれからあなたに与えられるタスクに取り組むにあたって、知っておくべき前提情報を教えてください。\n\n【例】\n・売りたい商品\n思考整理チャットボットくん\n\n・商品の特徴\n思考整理のためのフレームワークに従って、ユーザーに質問をするチャットボットです。\n対話形式で1つずつ質問がされるので、ユーザーは全体の整合性にこだわり過ぎずに、フレームワークに従って思考をアウトプットすることに集中できます。\n\n・制約\n商品紹介文は、フォーマルな文体で書いてください。\n商品紹介文は300文字以内にしてください。\n\n',
        '#命令\nChatGPTにやってほしいタスクを教えてください。\n\n【例】\n(#インプット)の情報と制約に従って、商品紹介文を書いてください。\n\n',
        '#アウトプット\nChatGPTに出力してほしい回答の形式を教えてください。\n\n【例】\nプレーンテキストで出力してください。\n',
      ],
      output : [
        'お疲れ様でした。以下があなたのプロンプトです。\nコピペしてChatGPTの指示にお使いください。\n\n=======================\n#役割\n',
        '\n#インプット\n',
        '\n#命令\n',
        '\n#アウトプット\n',
      ]
    },
    2 : {
      name : 'KPT振り返りフレームワーク',
      scenario : [
        '仕事やプロジェクトが完了したら、KPT(=Keep, Problem, Try)フレームワークに基づいて、振り返りをしましょう。\n今回完了した仕事やプロジェクトに関して、質問に1つずつ答えてください。\n\n=======================\n#Keep\nよかったこと、次回も継続してやりたいことを教えてください。',
        '#Problem\nよくなかったこと、次回は改善すべきことを教えてください。',
        '#Try\n次回に挑戦すること、具体的な改善策を教えてください。'
      ],
      output : [
        'お疲れ様でした。\n以下がKPTフレームワークに基づいて書き出された振り返りです。\n\n=======================\n#Keep\n',
        '\n#Problem\n',
        '\n#Try\n'
      ],
    }
  };

  // シナリオ選び用スクリプト
  const introScenario = [
    'あなたの思考整理を手伝うチャットボットです。\n取り組みたいフレームワークを選んで、番号を教えてください。\n(JavaScriptを用いて利用者側で処理を行っているため、入力内容は開発者からは見えません。ご安心ください。)\n\n'
  ];
  const introScenarioError = '正しい番号が入力されませんでした。';

  const form = document.querySelector('form'); //form selector
  const chatArea = document.querySelector('ul'); //ul selector

  // Ctrl + Enterでフォーム送信 .submit()はsubmitイベントをトリガーしないのでボタンをクリックしたことにする
  form.addEventListener('keydown', function(e) {
    if (e.ctrlKey) {
      if (e.key === 'Enter') {
        e.preventDefault();
        form.querySelector('button').click();
      }
    }
  });

  // ユーザーの入力をpromiseで取得する
  function getUserInput() {
    return new Promise((resolve) => {
      form.addEventListener('submit', (e) => {
        e.preventDefault();
        if (form.querySelector('.textBox').value !== '') {
          const data = form.querySelector('.textBox');
          resolve(data.value);
        }
      });
    });
  }

  // チャットボックスに入力内容を表示
  function renderChat(chat, classname) {
    let chatLi = document.createElement('li'); //li要素を作成
    let chatDiv = document.createElement('div'); //div要素を作成

    chatDiv.textContent = chat; //chatの内容をdiv要素の値に

    chatLi.appendChild(chatDiv); //div要素をli要素の子要素に

    chatLi.classList.add(classname); //classnameをクラス名としてli要素に指定

    chatArea.appendChild(chatLi); //chatAreaに子要素としてli要素を追加

    chatLi.scrollIntoView({ behavior: 'smooth' });//追加したli要素が見えるところまでスクロール
  }
  
  // 会話シナリオ(botScript)の要素を1つ表示した後、以降はユーザーからの入力があるごとに要素を表示
  // ユーザーからの返答はresultに格納 最後にユーザーの返答を整形して表示するためのシナリオ(botOutput)を使ってユーザーの入力内容を整形して表示
  async function scenario(botScript, botOutput, result) {
    for (let i = 0; i < botScript.length; i++) {
      setTimeout(function() {
        renderChat(botScript[i], 'chatBubble');
      }, 1000);
      const userInput = await getUserInput();
      renderChat(userInput, 'userBubble');
      result.push(userInput);
      form.querySelector('.textBox').value = '';
    }
    setTimeout(function() { //1つの吹き出しで表示したいので文字列を結合していく
      let finalOutput = '';
      for (let i = 0; i < botOutput.length; i++) {
        finalOutput = finalOutput + botOutput[i];
        finalOutput = finalOutput + result[i] + '\n';
      }
      renderChat(finalOutput, 'chatBubble');
    }, 1000);  
  }

  // シナリオを選ばせて実行する関数
  async function chooseScenario(scenarioDict, botScript) {
    // シナリオのリストを作成
    let showScenario = '';
    Object.keys(scenarioDict).forEach ((key) => {
      showScenario += `${key} : ${scenarios[key]['name']}\n`;
    });
    // シナリオリストを表示
    setTimeout(function() {
      let introBubble = '';
      for (let i = 0; i < botScript.length; i++) {
        introBubble = botScript[i];
      }
      renderChat(introBubble + showScenario, 'chatBubble');
    }, 1000);
    // ユーザー入力の取得
    const userInput = await getUserInput();
    renderChat(userInput, 'userBubble');
    form.querySelector('.textBox').value = '';
    // ユーザー入力のチェック
    let userInputValid = 0;
    Object.keys(scenarioDict).forEach ((key) => {
      if (userInput === key) {
        userInputValid++;
      }
    });
    // シナリオの実行
    if (userInputValid !== 0) {
      const result = [];
      await scenario(scenarioDict[userInput]['scenario'], scenarioDict[userInput]['output'], result);
      setTimeout(function() { // シナリオの実行後、シナリオ選択に戻る
        chooseScenario(scenarioDict, botScript);
      }, 2000);
    } else {
      setTimeout(function() {
        renderChat(introScenarioError, 'chatBubble'); //不正な入力があった場合はシナリオ選択に戻る
        chooseScenario(scenarioDict, botScript);
      }, 1000);
    }
    
  }

  // 関数の実行
  chooseScenario(scenarios, introScenario);

}


シナリオ1つを1つの定数に格納していたのを、オブジェクトでまとめて格納

const シナリオの名前 = [シナリオの中身]、としていたものを、
const scenarios = {
  1 : {
   name = シナリオ名前,
   scenario = [質問のシナリオ],
   output = [アウトプットのシナリオ]
  },
  2 ~中略~

としました。

scenariosに数字をキーとする入れ子のオブジェクトとしてシナリオを格納して、シナリオの名前もnameというキーに保持しておくことで、

最初にシナリオの番号と名前が表示され、ユーザーが番号でシナリオを選べるようになっています。こうすることでシナリオを追加し放題になりました。

次はローディングアニメーションあたりに挑戦してみたいですね。

この記事が参加している募集

つくってみた

この記事が気に入ったらサポートをしてみませんか?