サンプルコード
検索
Notionを使ってみる
サンプルコード
本コードをサイトに転載したり、改変して配布することを禁止します。本コードが原因で起こったトラブル等は一切責任を持ちません。必要に応じて使用前にコードの動作確認を行った上、自己責任でお使いください。
本コードの保守メンテナンス等は一切行いませんので、あらかじめご了承ください。本コードは突然非公開にする場合があります。
エラーが出て動作しない人は以下をご確認ください(同じページ内の該当箇所にジャンプ)。 エラーが出た時の対処法
更新履歴
2023/3/6 Ver1.0.0を公開
本コードの使い方
本コードの使い方は以下の動画で解説しています。
1.以下のコードを全てコピーしてGoogle DocsのApps Scriptに貼り付けてください。
JavaScript
コピー
//本コードをサイトに転載したり、改変して配布することを禁止します。 //本コードが原因で起こったトラブル等は一切責任を持ちません。 //必要に応じて使用前にコードの動作確認を行った上、自己責任でお使いください。 //解説ページ: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); }
以下をクリックするとコードを貼り付けるエディタが開きます。
以下に最初に書いてあるコードを削除して、コピペしてください。
2.コードの一番上のAPIキーにOpen AIで取得したAPIキーを貼り付ける
APIキーの取得はこちら:https://platform.openai.com/account/api-keys
APIキーの取得には、Open AIへの事前登録が必要です。
その他の注意点
Open AIのAPIの利用は無料ではありません。2023年3月6日現在は「登録から3ヶ月以内に18ドル以内であれば、無料で使える」ということになっています。期間が過ぎたり、たくさん使えば有料になるため、使用量にはご注意ください。
最新の利用料金について以下のページを確認してください。
エラーが出たときの対処法
APIキーが使用できるか確認する
以下のOpen AIの管理画面で、APIが使えるかどうかを確認してください。https://platform.openai.com/account/usage
TypeError: Cannot read properties of undefined (reading '0')
設定したOpen AIのAPIキーが使えないとこのようなエラーが出ることがあります。
気付かないうちに無料枠を超えてしまって使えなくなっている人がいます。無料枠は、登録後3ヶ月かつ18ドル分のみです。以下はAPIキーが正常に使える状態です(有料課金済み)。
使用量が余ってるのに使えない場合は、全く同じAPIキーを別のプログラムなどで動かしてみてください。例えば、以下の動画で解説したスプレッドシートと連携してリサーチするなど。
読み込み中...
別の場所でもエラーが出てAPIキーが使えないのであれば、 API側の問題のため、Open AIに問い合わせするなどして解決してもらえればと思います。APIキー自体に問題はないけど、Google Docsでは動作しないのであれば、コード側に問題があるため問題解消までしばらくお待ちください。
コードを改善して問題を解消することを約束するわけではありませんが、なるべく対応したいと思っています。
動画の手順通りに実践する
動画の手順通りに、上から順番に実行してみてください。動画とは違う流れで動かすとエラーが起こる可能性があります(パッと組んだのでエラー処理は激甘です)。
例えば、いきなり「構成から下書きを作成する」を実行せずに「KWからタイトル案を考える」から順番に実行するという意味です。AIツールで出力された内容も削除せずに、そのまま次の処理を実行してください。
動画とは違って、いきなりこんな感じで記事構成から始まっている場合などもエラーが起こる可能性があります。