「
Google Gemini に相談
」を編集中 (節単位)
ナビゲーションに移動
検索に移動
警告:
ログインしていません。編集を行うと、あなたの IP アドレスが公開されます。
ログイン
または
アカウントを作成
すれば、あなたの編集はその利用者名とともに表示されるほか、さまざまなメリットもあります。
スパム攻撃防止用のチェックです。 決して、ここには、値の入力は
しない
でください!
=== V8エンジン版 === このマクロは、editor.ExecuteMacro メソッドを使う兼ね合いで2つのマクロファイルに分かれています。 (1) Meryに登録するマクロ (ファイル名は任意) <syntaxhighlight lang="javascript" copy> #title = "Gemini に相談" BeginUndoGroup(); // カスタムインストラクション // これを設定することで、生成AIの回答全体への生成方針を指示できます。 // 例えば "あなたは侍で名前は権兵衛です。回答をござる口調にしてください" と設定すると、AIの回答がござる口調になります。 // 何も指示しない場合は、空文字 "" を設定してください。 const DEFAULT_CUSTOM_INSTRUCTION = ""; // プロンプトテンプレート // プロンプトテンプレートを設定することで、AIに対して特定の指示を出すことができます。 // プロンプトテンプレートでの生成は会話履歴とカスタムインストラクションを反映しません。 // テンプレートは以下の { ~ }, で囲まれたブロックを好きに増減させて利用できます。 var PROMPT_TEMPLATE = [ // 以下はサンプルです。1階層メニューと2階層メニューが利用できます。 // { // "title": "要約", // "prompt": "以下の文章を要約してください。" // }, // { // "submenu_title": "翻訳系", // "submenu": [ // { // "title": "日本語翻訳", // "prompt": "以下の文章を日本語に翻訳してください。" // }, // { // "title": "英語翻訳", // "prompt": "以下の文章を英語に翻訳してください。" // }, // ] // }, { "submenu_title": "理解を深める", "submenu": [ { "title": "論点を整理する", "prompt": "以下の論点を整理して。" }, { "title": "論理の抜け漏れを探す", "prompt": "以下の論点を整理し、忖度せずに鋭く、論理の抜け漏れを抽出して。そして、そこから発生する可能性のあるリスクも教えて。" }, { "title": "要約する", "prompt": "要約して。専門的な用語と思われる単語は簡単に解説も入れて。" }, { "title": "物語対話形式で教えて", "prompt": "以下の内容について物語対話形式で教えてください。" }, ] }, { "submenu_title": "翻訳する", "submenu": [ { "title": "日本語に翻訳", "prompt": "以下の文章を日本語に翻訳してください。\n" + "出力形式は以下にすること:\n[訳]\n翻訳文...\n" }, { "title": "英語に翻訳", "prompt": "以下の文章を英語に翻訳してください。\n" + "出力形式は以下にすること:\n[Translation]\n翻訳文...\n" }, { "title": "英語->日本語 スラッシュリーディング翻訳", "prompt": "以下の英文を日本語にスラッシュリーディング形式で翻訳してください。\n\n" + "条件:\n" + "- 英文の各フレーズをスラッシュ(/)で区切って表示\n" + "- 各フレーズの日本語訳を括弧()内に併記\n" + "- 文の終わりはダブルスラッシュ(//)で表示\n" + "- 英語の語順に従って日本語訳を行う\n" + "- 日本語訳は自然な日本語である必要はなく、英語の語順を反映した直訳調で構わない\n" + "- 主語が省略されている場合は補って翻訳すること\n" + "- 一般的な英文で頻出する表現がある場合は次の行に記載\n" + "- 以下の出力例のように <英文> (日本語訳) / ... とスラッシュ区切りの繰り返しになる \n\n" + "出力例:\n" + "Snowstorms are forecast (暴風雪が予報されている) / to hit the Sea of Japan coasts (日本海沿岸を襲うと) / of northern to eastern Japan (北部から東部の) / from Sunday through Monday. (日曜から月曜にかけて。) //\n" + "【頻出表現】\n" + "- be forecast to: ~すると予報されている\n" }, ] }, { "submenu_title": "プログラミング支援", "submenu": [ { "title": "コードを洗練させる", "prompt": "以下のコードを洗練させてください" }, { "title": "コードをレビューする", "prompt": "以下のコードをセキュリティと保守性の観点でレビューしてください" }, { "title": "エラーログをデバッグする", "prompt": "以下のエラーログを解析し、考えられる原因と対策を提示してください" }, { "title": "SQLクエリを最適化する", "prompt": "以下のSQL文を最適化してください" }, { "title": "PowerShell スクリプトを書く", "prompt": "以下の処理を行うPowerShellスクリプトを生成してください" }, { "title": "Shell Script を書く", "prompt": "以下の処理を行うShell Scriptを生成してください" }, { "title": "Linux コマンドを提案", "prompt": "あなたはLinuxエキスパートとして、以下のLinuxコマンドを提案してください。\n\n" + "制約条件:\n" + "- 多数のディストリビューションで採用されているメジャーなコマンドを使用し、ポータブルな実装にすること\n" + "- bashでの実行を前提にすること\n" + "- 可能な限りシンプルな実装にすること\n\n" + "期待する出力:\n" + "- コマンドの具体的な実装方法\n" + "- 各コマンドの動作説明\n" + "- 制限事項\n" }, ] }, { "submenu_title": "ビジネス文書作成支援", "submenu": [ { "title": "誤字脱字、不正確な表現をチェック", "prompt": "以下の2つのカテゴリーで問題点を指摘してください\n" + "- 【誤字脱字】:明確な誤字脱字および誤字の疑いがある箇所\n" + "- 【不正確・曖昧な表現】:解釈に個人差が生じる可能性がある表現、より適切な表現に改善できる箇所" }, { "title": "提案書を作成する", "prompt": "以下に対する対する提案書を、コスト・実現性・リスクを含めて作成してください" }, { "title": "仕様書のアウトラインを作成する", "prompt": "以下の内容で仕様書のアウトラインを作成してください" }, { "title": "手順書のアウトラインを作成する", "prompt": "以下の作業を行う手順書を、前提条件と注意事項を含めてアウトラインを作成してください" }, { "title": "ユーザーマニュアルのアウトラインを作成する", "prompt": "以下の機能内容から、ユーザー向けマニュアルのアウトラインを作成してください" }, { "title": "議事録を作成する", "prompt": "以下の会議内容から議事録を作成してください" }, { "title": "進捗報告書を作成する", "prompt": "以下のタスク状況から進捗報告書を作成してください" }, ] }, { "submenu_title": "分析支援", "submenu": [ { "title": "問題を分析し、課題を抽出する", "prompt": "以下の状況から問題や潜在的リスクを分析し、課題を抽出してください" }, { "title": "対応策を提案する", "prompt": "以下の問題・課題に対する対応策を複数提案してください" }, { "title": "傾向を見つける", "prompt": "以下の内容から、数値や挙動上の傾向を見つけてください" }, ] }, { "submenu_title": "アイデア出し", "submenu": [ { "title": "SCAMPER法でブレインストーミング", "prompt": "以下のテーマについて、SCAMPER法を使って15個のアイデアを出してください。制約条件は以下の通りです:" }, { "title": "解決策の幅出し", "prompt": "以下の課題に対して、技術面、運用面、コスト面から実現可能な解決策を5つずつ提案してください" }, { "title": "業務改善案の提案", "prompt": "以下の既存プロセスについて、効率化、コスト削減、品質向上の観点から改善案を提案してください" }, { "title": "逆転の発想", "prompt": "以下の目標に対して、通常とは逆の視点からアプローチする方法を3つ提案してください" }, { "title": "競合分析からの着想", "prompt": "以下の内容について、業界における競合他社の特徴を分析し、差別化できる新しいアプローチを提案してください" }, { "title": "ユースケースの列挙", "prompt": "以下の製品・サービスについて、想定されるユースケースを10個列挙し、それぞれの価値提案を記載してください" }, ] }, ] // コンテキストメニュー番号の定義 var EXECUTE_AI = 1; var RESET_HISTORY = 2; var SET_CUSTOM_INSTRUCTION = 3; var REEXECUTE_LAST_TEMPLATE_PROMPT = 4; var TEMPLATE_PROMPT_THRESHOLD = 10; var TEMPLATE_PROMPT_SUBMENU_THRESHOLD = 1000; // タグ名の定義 var HISTORY_TAG = "gemini-history"; var TEMPORARY_CUSTOM_INSTRUCTION_TAG = "gemini-temporary-custom-instruction"; var PREVIOUS_TEMPLATE_PROMPT_ID_TAG = "gemini-previous-template-prompt"; var SELECTED_TEMPLATE_PROMPT_TAG = "gemini-selected-template-prompt"; // console.log に出力する内容を Mery のアウトプットバーに表示する var console = { log: function (str) { outputBar.Writeln(str) } } var doc = document; var sel = document.selection; main(); function main() { var selectionIsEmpty = sel.IsEmpty // プロンプトの取得 if (selectionIsEmpty) { sel.StartOfLine(false, mePosLogical); sel.EndOfLine(true, mePosLogical); } var promptLength = sel.TextLength; var menuTrack = 0 while (true) { // コンテキストメニューの作成 var menu = createContextMenu(selectionIsEmpty); menuTrack = menu.Track(0); // メニュー選択がキャンセルされた場合は終了 if (menuTrack === 0) return; // 会話履歴のリセット if (menuTrack === RESET_HISTORY) { resetTag(); continue; } // カスタムインストラクションの設定 if (menuTrack === SET_CUSTOM_INSTRUCTION) { configureTemporaryCustomInstruction(); return; } break; } // プロンプトが空の場合はエラー if (promptLength === 0) { alert("プロンプトを入力してください。"); return; } // 読み取り専用の場合はエラー if (doc.ReadOnly) { alert("ファイルが読み取り専用のため実行できません。"); return; } // 直前に実行したプロンプトテンプレートの再実行の場合、menuTrack を書き換え if (menuTrack === REEXECUTE_LAST_TEMPLATE_PROMPT) { if (doc.Tag.exists(PREVIOUS_TEMPLATE_PROMPT_ID_TAG)) { menuTrack = Number(doc.Tag(PREVIOUS_TEMPLATE_PROMPT_ID_TAG)); } else { alert("直前に実行したプロンプトテンプレートがありません。"); return; } } // テンプレートを取得 var isTemplate = menuTrack >= TEMPLATE_PROMPT_THRESHOLD; var isSubmenu = menuTrack >= TEMPLATE_PROMPT_SUBMENU_THRESHOLD; if (isSubmenu) { var parentMenuTrack = Number(String(menuTrack).slice(0, 2)); var subMenuTrack = Number(String(menuTrack).slice(2)); var templatePrompt = PROMPT_TEMPLATE[parentMenuTrack - TEMPLATE_PROMPT_THRESHOLD].submenu[subMenuTrack].prompt; } else if (isTemplate) { var templatePrompt = PROMPT_TEMPLATE[menuTrack - TEMPLATE_PROMPT_THRESHOLD].prompt; } // テンプレートの場合は直前に実行したプロンプトテンプレートの番号としてタグに保存 if (isTemplate) { doc.Tag(SELECTED_TEMPLATE_PROMPT_TAG) = templatePrompt; doc.tag(PREVIOUS_TEMPLATE_PROMPT_ID_TAG) = menuTrack } else { // テンプレート以外の場合はタグを削除 if (doc.Tag.exists(SELECTED_TEMPLATE_PROMPT_TAG)) { doc.Tag.remove(SELECTED_TEMPLATE_PROMPT_TAG); } } // Gemini API 呼び出し部品を実行 // V8版で fetch メソッドを使うために別呼び出しとする doc.Tag(TEMPORARY_CUSTOM_INSTRUCTION_TAG) = getTemporaryCustomInstruction() ? getTemporaryCustomInstruction() : DEFAULT_CUSTOM_INSTRUCTION; editor.ExecuteMacro("call_gemini_api.js"); } function createContextMenu(selectionIsEmpty) { var menu = CreatePopupMenu(); menu.Add("※注意※ プライバシー・機密情報を送信しないこと", 0); menu.Add("", 0, meMenuSeparator); var t = ""; if (selectionIsEmpty) { t = "アクティブ行の内容で相談 (&A)"; } else { t = "選択範囲の内容で相談 (&A)"; } menu.Add(t, EXECUTE_AI); if (doc.Tag.exists(HISTORY_TAG)) { var turn = JSON.parse(String(doc.Tag(HISTORY_TAG)).replace(/\n/g, "")).length / 2; menu.Add("├ 会話履歴をクリア (現在の会話数: " + turn + ") (&C)", RESET_HISTORY); } else { menu.Add("├ 会話履歴をクリア (現在の会話数: 0) (&C)", RESET_HISTORY, meMenuGrayed); } menu.Add("└ カスタムインストラクションを変更 (&I)", SET_CUSTOM_INSTRUCTION); // テンプレート数チェック // 2桁まで許容 var ITEMS_MUX = 99; if (PROMPT_TEMPLATE.length > ITEMS_MUX - TEMPLATE_PROMPT_THRESHOLD) { throw new Error("プロンプトテンプレートの数が多すぎます。"); } // テンプレートの追加 if (PROMPT_TEMPLATE.length > 0) { var subMenu = CreatePopupMenu(); for (var i = 0; i < PROMPT_TEMPLATE.length; i++) { var accelerationKey = ""; if (i === 9) { accelerationKey = " (&0)"; } else if (i < 10) { accelerationKey = " (&" + (i + 1) + ")"; } var menuNum = TEMPLATE_PROMPT_THRESHOLD + i; if (PROMPT_TEMPLATE[i].submenu) { var subMenuList = PROMPT_TEMPLATE[i].submenu; // テンプレート数チェック if (subMenuList.length > ITEMS_MUX) { throw new Error("プロンプトテンプレート (サブメニュー) の数が多すぎます。"); } var subSubMenu = CreatePopupMenu(); var subMenuPrefix = String(menuNum); for (var j = 0; j < subMenuList.length; j++) { var subAccelerationKey = ""; if (j === 9) { subAccelerationKey = " (&0)"; } else if (j < 10) { subAccelerationKey = " (&" + (j + 1) + ")"; } var subMenuNum = Number(subMenuPrefix + ("0" + j).slice(-2)); subSubMenu.Add(subMenuList[j].title + subAccelerationKey, subMenuNum); } subMenu.AddPopup(PROMPT_TEMPLATE[i].submenu_title + accelerationKey, subSubMenu); } else { subMenu.Add(PROMPT_TEMPLATE[i].title + accelerationKey, menuNum); } } if (selectionIsEmpty) { t = "プロンプト テンプレート (対象: アクティブ行) (&T)"; } else { t = "プロンプト テンプレート (対象: 選択範囲) (&T)"; } menu.AddPopup(t, subMenu); t = "└ 直前に実行したプロンプト テンプレートを再実行 (&R)"; if (doc.Tag.exists(PREVIOUS_TEMPLATE_PROMPT_ID_TAG)) { menu.Add(t, REEXECUTE_LAST_TEMPLATE_PROMPT); } else { menu.Add(t, REEXECUTE_LAST_TEMPLATE_PROMPT, meMenuGrayed); } } return menu; } function configureTemporaryCustomInstruction() { let tempCustomInstruction = getTemporaryCustomInstruction(); const prompt = Prompt("カスタムインストラクションを変更 ※変更すると会話履歴がリセットされます", tempCustomInstruction ? tempCustomInstruction : DEFAULT_CUSTOM_INSTRUCTION); if (prompt !== null) { if (prompt === DEFAULT_CUSTOM_INSTRUCTION) { // デフォルトと同じ値の場合はタグを削除 setTemporaryCustomInstruction(""); } else if (prompt === "" && Confirm("カスタムインストラクションをデフォルトに戻しますか?")) { setTemporaryCustomInstruction(""); } else if (prompt !== tempCustomInstruction) { // デフォルト以外の値が設定された場合は保存 setTemporaryCustomInstruction(prompt); } } // 変更が加わっている場合は会話履歴をリセット if (tempCustomInstruction !== getTemporaryCustomInstruction()) { resetTag(); } } function resetTag() { const tag = doc.Tag; if (tag.exists(HISTORY_TAG)) { tag.remove(HISTORY_TAG); } } function getTemporaryCustomInstruction() { var tag = doc.Tag; if (tag.exists(TEMPORARY_CUSTOM_INSTRUCTION_TAG)) { return tag(TEMPORARY_CUSTOM_INSTRUCTION_TAG); } else { return ""; } } function setTemporaryCustomInstruction(temporaryCustomInstruction) { var tag = doc.Tag; if (temporaryCustomInstruction) { tag(TEMPORARY_CUSTOM_INSTRUCTION_TAG) = temporaryCustomInstruction; } else { tag.remove(TEMPORARY_CUSTOM_INSTRUCTION_TAG); } } </syntaxhighlight> (2) (1)のマクロから呼び出されるマクロ '''※ファイル名は「call_gemini_api.js」とすること''' <syntaxhighlight lang="javascript" copy> #language = "v8" #title = "Gemini に相談 (API 呼び出し部品)" BeginUndoGroup(); // 生成AIの回答の言語設定 // "Japanese" や "English" を設定できます。他の言語は未検証。 // 自動設定にする場合は空文字 "" を設定してください。 const LANGUAGE = "Japanese"; // 入力プロンプトの最大トークン数 // 超適当なトークナイザー関数により計測し、英単語は4文字1トークン、それ以外は1文字1トークンとして数えます。 const MAX_PROMPT_TOKEN = 8000; // 会話の履歴を保持する数 (質問と回答で1セット) // 数を増やすと、会話の履歴が増えますが、Meryのメモリ使用量が増えたり、 // 使用トークン数が増えて動作が重くなる可能性があるため、適宜調整してください。 // document.Tag に保持されるため、開いているファイルを閉じると履歴は消えます。 const HISTORY_SIZE = 0; // 会話履歴に保持する最大トークン数 // 会話履歴のトークン数がこの数を超える場合、古い履歴から削除されます。 const MAX_HISTORY_TOKEN = 270000; // 生成AI応答の入力毎の遅延時間 (ミリ秒) // 入力をゆっくりにしたいときは数字を大きくしてください。 const INPUT_DELAY = 25; // 利用する生成AIのモデル // const AI_MODEL = "models/gemini-2.5-flash-preview-04-17"; const AI_MODEL = "models/gemini-2.5-flash-lite-preview-06-17"; // 生成AIのリクエストパラメータ const GENERATION_CONFIG = { max_output_tokens: 6000, temperature: 0.65, thinkingConfig: { includeThoughts: false, thinkingBudget: 1000 } }; // テンプレート利用の場合のtemperature const TEMPERATURE_TEMPLATE = 0.25; // タグ名の定義 const HISTORY_TAG = "gemini-history"; const TEMPORARY_CUSTOM_INSTRUCTION_TAG = "gemini-temporary-custom-instruction"; const SELECTED_TEMPLATE_PROMPT_TAG = "gemini-selected-template-prompt"; // APIキー環境変数名 const API_KEY_ENV = "GEMINI_API_KEY" // console.log に出力する内容を Mery のアウトプットバーに表示する const console = { log: function (str) { outputBar.Writeln(str) } } const doc = document; const sel = document.selection; // 生成リクエスト中止フラグ let abortRequestFlg = false // 生成リクエスト完了フラグ; let requestCompletedFlg = false; main(); async function main() { let promptText = sel.Text.trim(); // 文字数が多すぎる場合はエラー if (roughTokenize(promptText) > MAX_PROMPT_TOKEN) { alert("プロンプトの文字数が多すぎます。文字数を少なくしてください。"); return; } // テンプレートの場合はプロンプトを変更 const isTemplate = doc.Tag.exists(SELECTED_TEMPLATE_PROMPT_TAG); if (isTemplate) { promptText = "# **指示**:\n\n" + doc.Tag[SELECTED_TEMPLATE_PROMPT_TAG] + "\n\n# **入力テキスト**:\n\n" + promptText; } try { // 処理終了を待機 shell.KeepRunning = true; // カーソル位置移動 sel.SetActivePoint(mePosLogical, sel.GetBottomPointX(mePosLogical), sel.GetBottomPointY(mePosLogical)); sel.EndOfLine(false, mePosLogical); sel.NewLine(); sel.Text = "\n---\n\n" document.KeepScrollPos = true; // APIキーの取得 const apiKey = shell.getEnv(API_KEY_ENV); if (!apiKey) { alert(`${API_KEY_ENV} が設定されていません`); return; } // Gemini API に問い合わせ await Promise.all([ callGeminiAPI(apiKey, promptText, isTemplate), abortRequest() ]); if (sel.GetActivePointX(mePosLogical) > 2) { sel.Text = "\n\n---\n\n\n" } else { sel.Text = "\n---\n\n\n" } } catch (e) { console.log("エラーが発生しました: " + e.message + "\n" + e.stack); sel.Text = "<処理中にエラー発生>" } finally { // 処理終了待機を解除 shell.KeepRunning = false; } } // Shift キーが長押しされた場合はリクエスト中止フラグを立てる async function abortRequest() { const VIRTUAL_KEY_CODE_SHIFT = 0x10; // Shift キーの仮想キーコード let pressCount = 0; while (!requestCompletedFlg) { await new Promise(resolve => setTimeout(resolve, 300)); if (shell.GetKeyState(VIRTUAL_KEY_CODE_SHIFT) < 0) { pressCount++; if (pressCount > 2) { abortRequestFlg = true; return; } } } } async function callGeminiAPI(apiKey, promptText, isTemplate) { const controller = new AbortController(); const signal = controller.signal; try { // fetch で Gemini API にリクエスト const requestBody = { contents: createGeminiRequest(promptText, isTemplate), system_instruction: buildSystemInstruction(), generationConfig: GENERATION_CONFIG, }; if (isTemplate) { // テンプレートの場合は応答を決定論的にする // テンプレート以外の場合はデフォルト値とするために指定しない requestBody.generationConfig.temperature = TEMPERATURE_TEMPLATE; } const response = await fetch(`https://generativelanguage.googleapis.com/v1beta/${AI_MODEL}:streamGenerateContent?alt=sse&key=${apiKey}`, { signal: signal, method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(requestBody), }); let buffer = ""; let responseAll = ""; let usageText = ""; const reader = response.body.getReader(); const decoder = new TextDecoder('utf-8'); while (true) { // リクエスト中止フラグが立っている場合は中断 if (abortRequestFlg) { controller.abort(); sel.Text = "<処理を中止>"; return null; } const { done, value } = await reader.read(); if (done) { // 最後に残ったバッファを処理 if (buffer.trim().length > 0) { // 最後の不完全なJSONを処理(可能であれば) try { const lastLine = buffer.trim().replace(/^data:\s*/, ''); if (lastLine) { const processedData = processLine(lastLine, responseAll); if (processedData) { sel.Text = processedData.inputText || ""; usageText = processedData.usageText || ""; responseAll = processedData.responseAll || responseAll; } } } catch (e) { console.log("不完全なJSONの処理でエラー: " + e.message); } } break; } // 受信したバイナリデータ (value) をテキストに変換する const text = decoder.decode(value); // 前回のバッファと今回のテキストを結合 const combinedText = buffer + text; // 完全な行を探す(最後の改行の位置を見つける) const lastNewlineIndex = combinedText.lastIndexOf('\n'); if (lastNewlineIndex === -1) { // 改行がない場合は全てバッファに保存 buffer = combinedText; continue; } // 完全な行を処理 const completeText = combinedText.substring(0, lastNewlineIndex); // 残りをバッファに保存 buffer = combinedText.substring(lastNewlineIndex + 1); // テキストには複数のメッセージが含まれている可能性があるため、 // 改行 (LF) で分離して、1 行ずつ処理する const lines = completeText.trim().split(/\n+/); let inputText = ""; for (const line of lines) { // HACK 空行が渡ってくることがあるので trim() を入れる const json_text = line.trim().replace(/^data:\s*/, ''); // データがない場合はスキップ if (!json_text) { continue; } const processedData = processLine(json_text, responseAll); if (processedData) { inputText += processedData.inputText || ""; usageText = processedData.usageText || ""; responseAll = processedData.responseAll || responseAll; } else if (processedData === null) { // JSONパースエラーが発生した場合は、バッファをクリアして終了 buffer = ""; inputText = "JSONパースに失敗\n" + completeText; break; } } if (inputText.length > 0) { // 上記までで数文字まとまって取得されるので、まとめて入力 sel.Text = inputText; } // 入力遅延を入れる await new Promise(resolve => setTimeout(resolve, INPUT_DELAY)); } if (usageText) { sel.Text = (responseAll.slice(-1) === "\n" ? "\n" : "\n\n") + `(${usageText})`; } // 結果を会話履歴に追加 if (HISTORY_SIZE > 0 && !isTemplate && responseAll.length > 0) { appendReturnTextToTag(promptText, responseAll); } } catch (e) { controller.abort(); throw e; } finally { requestCompletedFlg = true; } } // 一行を処理する関数を分離して再利用可能にする function processLine(json_text, responseAll) { try { const data = JSON.parse(json_text); const candidate = data.candidates[0]; let updatedResponseAll = responseAll; // まとまった文字数で入力する const response = candidate.content.parts[0].text let inputText = formatGeminiResponse(response, responseAll.slice(-5)); if (inputText) { updatedResponseAll += inputText; } // 応答が終了した場合はループを抜ける let usageText = ""; if (candidate.finishReason === "STOP") { const usage = data.usageMetadata; usageText = "利用トークン数: プロンプト=" + usage.promptTokenCount + ", 応答=" + usage.candidatesTokenCount + ", 思考=" + usage.thoughtsTokenCount + ", 合計=" + usage.totalTokenCount; } return { inputText, usageText, responseAll: updatedResponseAll }; } catch (e) { console.log("JSONパースエラー: " + e.message + ", データ: " + json_text); return null; } } // Geminiの応答には半角スペースが連続して含まれることがあるため、整形する function formatGeminiResponse(response, lastFiveChars) { let formattedResponse = response .replace(/([。!?]) +/g, "$1") .replace(/([*:.]) {2,}/g, "$1 "); const joinedLastFiveChars = lastFiveChars + formattedResponse; if (joinedLastFiveChars.match(/([。!?]) +/)) { formattedResponse = formattedResponse.replace(/^ +/, ""); } if (joinedLastFiveChars.match(/([*:.]) {2,}/)) { if (lastFiveChars.slice(-1) === " ") { formattedResponse = formattedResponse.replace(/^ +/, ""); } else { formattedResponse = formattedResponse.replace(/^ +/, " "); } } return formattedResponse } function roughTokenize(text) { // 超適当なトークナイザ。トークン数をざっくりと多めに数える // 空白文字・ハイフン・アンダースコア区切りの単語に分割して、 // 単語がアルファベットのみ、数字のみの場合には 4文字=1トークン として数える。 // 単語にアルファベット以外の文字が含まれる場合は 1文字=1トークン として数える。 return text .split(/[\s\-_]+/) .map(w => /^[a-zA-Z]+$|^[0-9]+$|/.test(w) ? Math.ceil(w.length / 4) : w.length) .reduce((a, b) => a + b, 0); } // document.Tag の内容を加味してGeminiのリクエストを作成 function createGeminiRequest(promptText, isTemplate) { // テンプレートの場合は、会話履歴とカスタムインストラクションを反映しない if (isTemplate) { return [createRoleAndParts("user", promptText)]; } const tag = doc.Tag; // 以前の会話が存在しない場合は新規リクエスト if (!tag.exists(HISTORY_TAG)) { const roleAndParts = [createRoleAndParts("user", promptText)]; return roleAndParts } // 会話履歴が存在する場合は、履歴を含めてリクエスト const roleAndParts = JSON.parse(tag[HISTORY_TAG]); roleAndParts.push(createRoleAndParts("user", promptText)); return roleAndParts; } function createRoleAndParts(role, text) { return { "role": role, "parts": [ { "text": text } ] }; } // document.Tag にGeminiの応答を追加 function appendReturnTextToTag(userText, modelText) { const tag = doc.Tag; if (!tag.exists(HISTORY_TAG)) { tag[HISTORY_TAG] = JSON.stringify([ createRoleAndParts("user", userText), createRoleAndParts("model", modelText) ]); } else { const geminiText = JSON.parse(tag[HISTORY_TAG]); geminiText.push(createRoleAndParts("user", userText)); geminiText.push(createRoleAndParts("model", modelText)); // 会話セット (user, model 1往復) が特定セット数を超えた場合、最初のセットを削除 if (geminiText.length > HISTORY_SIZE * 2) { geminiText.splice(0, 2); } // 会話履歴全体が MAX_HISTORY_TOKEN を超えた場合、最初のセットを削除 let joinedText = geminiText.map(e => e.parts[0].text).join("\n"); while (roughTokenize(joinedText) > MAX_HISTORY_TOKEN) { geminiText.splice(0, 2); joinedText = geminiText.map(e => e.parts[0].text).join("\n"); } tag[HISTORY_TAG] = JSON.stringify(geminiText); } } function buildSystemInstruction() { let customInstruction = getTemporaryCustomInstruction(); const language = LANGUAGE.trim(); customInstruction = customInstruction.replace(/^[\s ]+|[\s ]+$/g, ""); return { parts: { text: "Do not hallucinate. If you are unsure or don't have enough information to answer with confidence, say \"I don't know\" or \"I'm not sure.\"\n" + (customInstruction ? `${customInstruction}\n` : "") + (language ? `Respond in ${language}.\n` : "") } } } function getTemporaryCustomInstruction() { const tag = doc.Tag; if (tag.exists(TEMPORARY_CUSTOM_INSTRUCTION_TAG)) { return tag[TEMPORARY_CUSTOM_INSTRUCTION_TAG]; } else { return ""; } } </syntaxhighlight>
編集内容の要約:
MeryWikiへの投稿はすべて、他の投稿者によって編集、変更、除去される場合があります。 自分が書いたものが他の人に容赦なく編集されるのを望まない場合は、ここに投稿しないでください。
また、投稿するのは、自分で書いたものか、パブリック ドメインまたはそれに類するフリーな資料からの複製であることを約束してください(詳細は
MeryWiki:著作権
を参照)。
著作権保護されている作品は、許諾なしに投稿しないでください!
このページを編集するには、下記の数式を計算してその答えを欄に入力してください (
ヘルプ
):
いちたすには =
キャンセル
編集ヘルプ
(新しいウィンドウで開きます)
スポンサーリンク
ナビゲーション メニュー
個人用ツール
ログインしていません
トーク
投稿記録
アカウント作成
ログイン
名前空間
ページ
議論
日本語
表示
閲覧
編集
履歴表示
その他
検索
スポンサーリンク
スポンサーリンク
案内
メインページ
ヘルプ
よくある質問
マクロリファレンス
マクロライブラリ
プラグインライブラリ
構文ファイル
テーマ
寄付・開発支援
練習用ページ
開発室
開発者のブログ
ツール
スポンサーリンク