本コードをサイトに転載したり、改変して配布することを禁止します。本コードが原因で起こったトラブル等は一切責任を持ちません。必要に応じて使用前にコードの動作確認を行った上、自己責任でお使いください。

本コードの保守メンテナンス等は一切行いませんので、あらかじめご了承ください。本コードは突然非公開にする場合があります。

エラーが出て動作しない人は以下をご確認ください(同じページ内の該当箇所にジャンプ)。 エラーが出た時の対処法

更新履歴

2023/3/6 Ver1.0.0を公開

本コードの使い方

本コードの使い方は以下の動画で解説しています。

https://youtu.be/ZXsu5PCGQRc

1.以下のコードを全てコピーしてGoogle DocsのApps Scriptに貼り付けてください。

//**本コードをサイトに転載したり、改変して配布することを禁止します。
//本コードが原因で起こったトラブル等は一切責任を持ちません。
//必要に応じて使用前にコードの動作確認を行った上、自己責任でお使いください。
//解説ページ:<https://merilinc.notion.site/9356ec5fc73f46188a77b759e8d6710b>
//上記のコメントも削除せずに、そのまま貼り付けてください。**
const OPENAI_API_KEY = "Open AIのAPIキーを設定してください。";
const OPENAI_MODEL = "gpt-3.5-turbo";
const IMAGE_SIZE = '512x512'; // 画像サイズ:512x512、1024x1024
const MAX_INPUT_LENGTH = 2048; // 入力文字数の上限

// メニューを作成する
function onOpen() {
  DocumentApp.getUi().createMenu("ウェブ職TV")
    .addItem("KWからタイトル案を考える", "generateTitles")
    .addItem("KWから記事構成案を考える", "generateIdeas")
    .addItem("構成から下書きを作成する", "blogwriting")
    .addItem("選択部分の詳細を書く", "detailwriting")
    .addItem("選択部分を簡潔に書く", "simplewriting")
    .addItem("選択部分の誤字脱字を修正する", "correctSentence")
    .addItem("選択部分を別の文章に書き直す", "rewrite")
    .addItem("画像を生成する(英語推奨)", "generateImage")
    .addToUi();
}

function generateText(prompt) {
  const requestBody = {
    "model": OPENAI_MODEL,
    "messages": [{"role": "user", "content": prompt}],
    "temperature": 0,
    "max_tokens": MAX_INPUT_LENGTH,
  };
  const requestOptions = {
    "method": "POST",
    "headers": {
      "Content-Type": "application/json",
      "Authorization": "Bearer "+OPENAI_API_KEY
    },
    "payload": JSON.stringify(requestBody),
    "muteHttpExceptions" : true,
  }
  try {
    const response = UrlFetchApp.fetch("<https://api.openai.com/v1/chat/completions>", requestOptions);
    const responseText = response.getContentText();
    const json = JSON.parse(responseText);
    const generatedText = json.choices[0].message.content;
    return generatedText.toString();
  } catch(e) {
    throw new Error(`テキスト生成に失敗しました。`+e);
  }
}

function getSelectedText(colorChange=false) {
  const selection = DocumentApp.getActiveDocument().getSelection();
  let selectedText = '';
  if (!selection) {
    throw new Error('テキストが選択されていません。');
  }
  const elements = selection.getRangeElements();
  for (let i = 0; i < elements.length; i++) {
    const element = elements[i].getElement();
    if (element.editAsText) {
      const text = element.asText().getText();
      const startIndex = elements[i].getStartOffset();
      const endIndex = elements[i].getEndOffsetInclusive();
      selectedText += text.substring(startIndex, endIndex + 1);
      if (colorChange) {
        element.asText().setForegroundColor(startIndex, endIndex, '#D80000');
      }
    }
  }
  if (selectedText.length > MAX_INPUT_LENGTH) {
    throw new Error('選択したテキストが'+MAX_INPUT_LENGTH+'文字を超えています。');
  }
  return selectedText;
}

//選択テキスト直後にテキスト挿入
function insertTextAfterSelection(insertText) {
  const selection = DocumentApp.getActiveDocument().getSelection();
  
  if (selection) {
    const elements = selection.getRangeElements();
    for (let i = 0; i < elements.length; i++) {
      const element = elements[i].getElement();
      const text = element.asText();
      const startIndex = elements[i].getStartOffset();    
      const endIndex = elements[i].getEndOffsetInclusive(); 
      element.asText().setForegroundColor(startIndex, endIndex, '#0711bf');
      text.insertText(endIndex+1, "\\n\\n" + insertText.replace(/^[\\n\\r]+|[\\n\\r]+$/g, ''));
    }
  }
}
function insertImageAfterSelection(image) {
  const selection = DocumentApp.getActiveDocument().getSelection();
  if (!selection) return;

  const elements = selection.getRangeElements();
  const parent = elements[elements.length - 1].getElement().getParent();
  const index = parent.getChildIndex(elements[elements.length - 1].getElement());
  parent.insertInlineImage(index + 1, image);

  return;
}
function inputPrompt() {
  var response = DocumentApp.getUi().prompt(
    'キーワード設定',
    'キーワードを入力してください',
    DocumentApp.getUi().ButtonSet.OK_CANCEL
  );
  if (response.getSelectedButton() == DocumentApp.getUi().Button.OK) {
    if( !response.getResponseText() ){
      throw new Error('キーワードが入力されていません。');
    }
    return response.getResponseText();
  } else {
    return false;
  }
}

// 選択したテキストをもとにタイトル案を考える
function generateTitles() {
  const inputKeyword = inputPrompt();
  if( inputKeyword ){
    const prompt = "キーワード「" + inputKeyword + "」でGoogle検索したユーザーのクリック率が高くなる記事タイトル案を10個考えて。タイトルには具体的な数字、記事を読むメリットなどを含めること。";
    DocumentApp.getActiveDocument().getBody().appendParagraph("キーワード:"+inputKeyword+""+generateText(prompt)+"\\n\\n");
  }
}
// 選択したテキストをもとに記事構成案を考える
function generateIdeas() {
  const inputKeyword = inputPrompt();
  if( inputKeyword ){
    const prompt = "キーワード「" + inputKeyword + "」でGoogle検索したユーザーが知りたいことを深掘りして、再検索キーワードの検索意図も含めて、ユーザーの全ての検索意図を満たす専門的な記事構成を考えて。H2見出し、H3見出しをそれぞれ分けて考えること。再検索キーワードは回答に書かないでください。";
    DocumentApp.getActiveDocument().getBody().appendParagraph("キーワード:"+inputKeyword+"\\n"+generateText(prompt)+"\\n\\n");
  }
}
// 選択したテキストをもとにブログ記事を書く
function blogwriting() {
  const prompt = getSelectedText() + "\\n\\n上記の構成をもとに本文を書いて。H2見出しは##、H3見出しは###を文頭につけること";
  DocumentApp.getActiveDocument().getBody().appendParagraph(generateText(prompt));
}
// 選択したテキストをもとに詳細を書く
function detailwriting() {
  const prompt = getSelectedText() + "\\n\\nこの文章をもとに、さらに詳しく専門的な文章を書いて。必要があればH4見出しを追加してもOK。H4見出しは####を文頭につけること。「そして」「また」のような順接や並列の接続詞はなるべく使わないこと。文章の前後の改行を含めないこと。";
  insertTextAfterSelection(generateText(prompt));
  getSelectedText(true);
}
// 選択したテキストを簡潔に書く
function simplewriting() {
  const prompt = getSelectedText() + "\\n\\nこの文章を短く簡潔にまとめ直して。";
  insertTextAfterSelection(generateText(prompt));
  getSelectedText(true);
}
// 選択したテキストを修正して
function correctSentence() {
  const prompt = getSelectedText() + "\\n\\nこの文章の誤字脱字やおかしなところを修正して。";
  insertTextAfterSelection(generateText(prompt));
  getSelectedText(true);
}
// 選択したテキストを書き直して。
function rewrite() {
  const prompt = getSelectedText() + "\\n\\nこの文章を別の文章に書き直して。";
  insertTextAfterSelection(generateText(prompt));
  getSelectedText(true);
}
// 選択したテキストをもとに画像を生成する
function generateImage() {
  const apiUrl = '<https://api.openai.com/v1/images/generations>';
  const prompt = "「"+getSelectedText()+"」を英語に翻訳してから実行して。No text should be placed within the image.";
  let headers = {
    'Authorization':'Bearer '+ OPENAI_API_KEY,
    'Content-type': 'application/json',
    'X-Slack-No-Retry': 1
  };
  let options = {
  'muteHttpExceptions' : true,
  'headers': headers,
  'method': 'POST',
  'payload': JSON.stringify({
  'n': 1,
  'size' : IMAGE_SIZE,
  'prompt': prompt})
  };
  const response = JSON.parse(UrlFetchApp.fetch(apiUrl, options).getContentText());
  const image = UrlFetchApp.fetch(response.data[0].url).getAs('image/png');
  insertImageAfterSelection(image);
}

以下をクリックするとコードを貼り付けるエディタが開きます。

Untitled.png

以下に最初に書いてあるコードを削除して、コピペしてください。

Untitled.png

2.コードの一番上のAPIキーにOpen AIで取得したAPIキーを貼り付ける

APIキーの取得はこちら:https://platform.openai.com/account/api-keys