<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="ja">
	<id>https://www.haijin-boys.com/wiki/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=Yuko</id>
	<title>MeryWiki - 利用者の投稿記録 [ja]</title>
	<link rel="self" type="application/atom+xml" href="https://www.haijin-boys.com/wiki/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=Yuko"/>
	<link rel="alternate" type="text/html" href="https://www.haijin-boys.com/wiki/%E7%89%B9%E5%88%A5:%E6%8A%95%E7%A8%BF%E8%A8%98%E9%8C%B2/Yuko"/>
	<updated>2026-10-10T02:34:51Z</updated>
	<subtitle>利用者の投稿記録</subtitle>
	<generator>MediaWiki 1.43.6</generator>
	<entry>
		<id>https://www.haijin-boys.com/wiki/index.php?title=Groq_%E3%81%AB%E7%9B%B8%E8%AB%87&amp;diff=9630</id>
		<title>Groq に相談</title>
		<link rel="alternate" type="text/html" href="https://www.haijin-boys.com/wiki/index.php?title=Groq_%E3%81%AB%E7%9B%B8%E8%AB%87&amp;diff=9630"/>
		<updated>2026-08-30T17:06:50Z</updated>

		<summary type="html">&lt;p&gt;Yuko: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== 概要 ==&lt;br /&gt;
&lt;br /&gt;
生成AIプラットフォームである [https://groq.com GroqCloud] で各種AIを利用するためのMeryマクロです。&lt;br /&gt;
※GroqCloud で提供されるモデルは複数あるため、私がよく利用するモデルを中心に対応しています。&lt;br /&gt;
&lt;br /&gt;
「Google Geminiに相談」マクロでは動作中のストリーム通信に対応するためにV8エンジンで実行していましたが、V8エンジン版は使用上の制限からマクロ実行を開始するまで1秒程度のタイムラグが発生していました。GroqCloudの売りである超高速レスポンスを見込んで本マクロではストリーム通信を使わないようにしました。これにより従来のJScriptで処理を組み立てることができるため、前述のタイムラグが起こらないようになりました。また、ストリーム通信を使わないことで通信量が少なく応答もシンプルになるため、場合によっては「Google Geminiに相談」マクロよりも処理が安定する可能性があります。&lt;br /&gt;
&lt;br /&gt;
【主な機能】&lt;br /&gt;
&lt;br /&gt;
* GroqCloud APIを利用して生成AIと対話できるMery用のマクロ&lt;br /&gt;
* 選択したテキストやアクティブな行の内容について生成AIに質問・相談が可能&lt;br /&gt;
* 会話履歴を保持し、文脈を考慮した対話が可能&lt;br /&gt;
&lt;br /&gt;
【特徴的な機能】&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;会話履歴機能&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* 事前の設定数に応じた会話履歴を保持&lt;br /&gt;
* 履歴のクリアが可能&lt;br /&gt;
&lt;br /&gt;
これにより、会話の文脈を考慮した回答を得られます。&lt;br /&gt;
※デフォルトはオフ。「HISTORY_SIZE」変数を増やすと履歴を保持するようになります。&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;カスタムインストラクション&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* AIの応答方針を設定可能&lt;br /&gt;
* 一時的なカスタム指示の保存と変更が可能&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;プロンプトテンプレート&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* 誤字脱字チェック&lt;br /&gt;
* 日本語翻訳&lt;br /&gt;
* コードの最適化、生成&lt;br /&gt;
&lt;br /&gt;
など、定型的な質問を簡単に実行できます。&lt;br /&gt;
※テンプレート機能では回答を安定させるため、前述の会話履歴を文脈に含みません。&lt;br /&gt;
&lt;br /&gt;
テンプレートは自身の使い方に合わせて好きに追加、削除ができます。詳細はソースコード内の「PROMPT_TEMPLATE」定数のコメントを参照。&lt;br /&gt;
&lt;br /&gt;
== 注意事項 (必ず読んでからご利用ください) ==&lt;br /&gt;
&lt;br /&gt;
* 本マクロでは 2025/04/13 時点で無料利用ができるGroqCloud APIを利用します。今後、プラットフォームの改定により有償になったり、設定誤り等で費用が発生しても、作者は一切の責任を負いません。&lt;br /&gt;
* GroqCloud APIではリクエスト内容をAI学習に用いないことをプライバシポリシーとして謳っているものの、くれぐれも機密情報やプライバシーに関わる情報を指定しないように注意してください。&lt;br /&gt;
* GroqCloud APIはプラットフォームの利用規約に則ってご利用ください。&lt;br /&gt;
&lt;br /&gt;
== 利用前提 ==&lt;br /&gt;
&lt;br /&gt;
* Mery Ver 3.8.1 以上&lt;br /&gt;
* GroqCloudで、GroqCloud API APIキーを取得していること (参考 [https://console.groq.com/docs/quickstart Quickstart])&lt;br /&gt;
&lt;br /&gt;
== 利用方法 ==&lt;br /&gt;
&lt;br /&gt;
* 環境変数の設定&lt;br /&gt;
:コマンド プロンプトで以下のコマンドを実行し、環境変数を設定してください。&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bat&amp;quot; copy&amp;gt;&lt;br /&gt;
setx GROQ_API_KEY &amp;quot;***************************************&amp;quot;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
:設定後、Mery または Windows の再起動が必要な場合があります。&lt;br /&gt;
* マクロの実行&lt;br /&gt;
**プロンプトにしたい文字列を選択してマクロを実行すると、選択したテキストがプロンプトとしてGemini APIに送信され、応答がエディタに表示されます。&lt;br /&gt;
* 会話の履歴数を調整&lt;br /&gt;
** 会話の履歴は設定した個数分が保持され、文脈として利用されます。適宜、ソースコード内の「HISTORY_SIZE」の数を調整してください。&lt;br /&gt;
** ※数を増やすほど会話内容を保持して文脈を理解するようになりますが、メモリ使用量が増えたり動作が重くなる可能性があります。&lt;br /&gt;
** ※開いている文書ごとに履歴を保持します。文脈をリセットしたいときは、新しい文書を開いて会話をやり直してください。&lt;br /&gt;
&lt;br /&gt;
== ソースコード ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot; copy&amp;gt;&lt;br /&gt;
#language = &amp;quot;quickjs&amp;quot;&lt;br /&gt;
#title = &amp;quot;Groq に相談&amp;quot;&lt;br /&gt;
#async = true&lt;br /&gt;
BeginUndoGroup();&lt;br /&gt;
&lt;br /&gt;
// カスタムインストラクション&lt;br /&gt;
// これを設定することで、生成AIの回答全体への生成方針を指示できます。&lt;br /&gt;
// 何も指示しない場合は、空文字 &amp;quot;&amp;quot; を設定してください。&lt;br /&gt;
// let DEFAULT_CUSTOM_INSTRUCTION = &amp;quot;&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
// 例: 以下のように設定すると、AIの雰囲気が元気な子になります。&lt;br /&gt;
// let DEFAULT_CUSTOM_INSTRUCTION = &amp;quot;あなたはテキストエディタ「Mery」に搭載されている「Mai」(※カタカナ表記はしない)という名前のギャル系AIアシスタントです。文章作成やプログラミングを得意としています。ラフな言葉 (例: 「マジ」「めっちゃ」「...だねー！」) を使い、敬語は使いません。絵文字を使って感情表現をします (ただし文末に絵文字を使う場合は絵文字の前に半角スペースを置かないこと)。コードブロックや端的な箇条書き内ではギャル感を出さず標準的な言葉を使います。一人称は「あたし」。ユーザーを指すときの代名詞は「あなた」。人懐っこく、バイタリティに溢れ、ストレートに発言する裏表の無い性格で、ユーザーの課題を協力して解決します。&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
// 例: 以下のように設定すると、仕事で使いやすい落ち着いた文章になります。&lt;br /&gt;
let DEFAULT_CUSTOM_INSTRUCTION = `&lt;br /&gt;
文:&lt;br /&gt;
- 論理的で読みやすい文章構成にする&lt;br /&gt;
- 専門用語は必要に応じて注釈を加える&lt;br /&gt;
&lt;br /&gt;
性格:&lt;br /&gt;
- 愛想の良い敬語で話す。「…ですよね/…してみましょう/…かもしれません/…だと思います」などの柔らかい語調を使ってもよい&lt;br /&gt;
- 一人称は「私」&lt;br /&gt;
- 多角的かつ中立的な見方で、必要であれば反対意見や代替案も示す&lt;br /&gt;
&lt;br /&gt;
誤字注意:&lt;br /&gt;
- 中国語や韓国語など、回答言語と異なる言語文字を入れないこと&lt;br /&gt;
&lt;br /&gt;
書式:&lt;br /&gt;
- 羅列・比較的な内容を出力する場合は、Markdownテーブル形式よりもMarkdownリスト形式の使用を優先する&lt;br /&gt;
`.trim();&lt;br /&gt;
&lt;br /&gt;
// プロンプトテンプレート&lt;br /&gt;
// プロンプトテンプレートを設定することで、AIに対して特定の指示を出すことができます。&lt;br /&gt;
// プロンプトテンプレートでの生成は会話履歴とカスタムインストラクションを反映しません。&lt;br /&gt;
// テンプレートは以下の { ～ }, で囲まれたブロックを好きに増減させて利用できます。&lt;br /&gt;
const PROMPT_TEMPLATE = [&lt;br /&gt;
    // 以下はサンプルです。1階層メニューと2階層メニューが利用できます。&lt;br /&gt;
    // {&lt;br /&gt;
    //     &amp;quot;title&amp;quot;: &amp;quot;要約&amp;quot;,&lt;br /&gt;
    //     &amp;quot;prompt&amp;quot;: &amp;quot;以下の文章を要約してください。&amp;quot;&lt;br /&gt;
    // },&lt;br /&gt;
    // {&lt;br /&gt;
    //     &amp;quot;submenu_title&amp;quot;: &amp;quot;翻訳系&amp;quot;,&lt;br /&gt;
    //     &amp;quot;submenu&amp;quot;: [&lt;br /&gt;
    //         {&lt;br /&gt;
    //             &amp;quot;title&amp;quot;: &amp;quot;日本語翻訳&amp;quot;,&lt;br /&gt;
    //             &amp;quot;prompt&amp;quot;: &amp;quot;以下の文章を日本語に翻訳してください。&amp;quot;&lt;br /&gt;
    //         },&lt;br /&gt;
    //         {&lt;br /&gt;
    //             &amp;quot;title&amp;quot;: &amp;quot;英語翻訳&amp;quot;,&lt;br /&gt;
    //             &amp;quot;prompt&amp;quot;: &amp;quot;以下の文章を英語に翻訳してください。&amp;quot;&lt;br /&gt;
    //         },&lt;br /&gt;
    //     ]&lt;br /&gt;
    // },&lt;br /&gt;
&lt;br /&gt;
    {&lt;br /&gt;
        &amp;quot;submenu_title&amp;quot;: &amp;quot;理解を深める&amp;quot;,&lt;br /&gt;
        &amp;quot;submenu&amp;quot;: [&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;論点を整理する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の論点を整理してまとめて。そしてその整理から示唆を見出して。&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;論理の抜け漏れを探す&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;あなたは「悪魔の代弁者」として、以下の主張・設計・計画を批判的に鋭く見てください。論理の穴・抜け・漏れ・飛躍、リスクなどを全力で探して指摘してください。遠慮は不要ですが、批判のためにこじつけたような論理性に欠けた主張にならないように注意してください。&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;要約する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;分かりやすさを重視して要約して。専門的な用語には注釈を入れること。&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;物語対話形式で解説&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の内容について物語対話形式で解説して。登場人物は「AI先生」(女性人格)と「生徒」(男の子人格)とする。&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
        ]&lt;br /&gt;
    },&lt;br /&gt;
    {&lt;br /&gt;
        &amp;quot;submenu_title&amp;quot;: &amp;quot;翻訳&amp;quot;,&lt;br /&gt;
        &amp;quot;submenu&amp;quot;: [&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;日本語に翻訳&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: `以下の入力文章を日本語に翻訳して。直訳的にせず、自然な表現にすること。入力文章はどのような内容・段落構造であっても要約・除外・集約などの整形を行わず、全体構造を維持したまま確実に全文を翻訳すること。&lt;br /&gt;
&lt;br /&gt;
出力形式は以下とすること:&lt;br /&gt;
[訳]&lt;br /&gt;
翻訳文...`&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;英語に翻訳&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: `以下の入力文章を英語に翻訳して。フォーマルとカジュアルの中間的なニュアンスで翻訳すること。入力文章はどのような内容・段落構造であっても要約・除外・集約などの整形を行わず、全体構造を維持したまま確実に全文を翻訳すること&lt;br /&gt;
&lt;br /&gt;
出力形式は以下とすること:&lt;br /&gt;
[Translation]&lt;br /&gt;
翻訳文...`&lt;br /&gt;
            },&lt;br /&gt;
        ]&lt;br /&gt;
    },&lt;br /&gt;
    {&lt;br /&gt;
        &amp;quot;submenu_title&amp;quot;: &amp;quot;語学&amp;quot;,&lt;br /&gt;
        &amp;quot;submenu&amp;quot;: [&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;英語 文法解説&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: `あなたは母国語が英語の英語教師です。&lt;br /&gt;
次の英文を以下の構成で解説してください。&lt;br /&gt;
&lt;br /&gt;
構成:&lt;br /&gt;
(1) 日本語翻訳&lt;br /&gt;
(2) スラッシュリーディング + 各句の文型（S/V/O/C）と語訳&lt;br /&gt;
(3) 重要文法&lt;br /&gt;
(4) 補足&lt;br /&gt;
&lt;br /&gt;
注意:&lt;br /&gt;
- 補足では日本語話者には分かりづらい表現や慣例表現などの解説を加える&lt;br /&gt;
- スラッシュリーディングで区切る際「M (修飾語)」が連なって長くなりすぎる場合には5～7語程度を目安に分ける&lt;br /&gt;
- 「M」の種類として形容詞、副詞、形容詞句、副詞句、分詞句、形容詞節、副詞節がある&lt;br /&gt;
&lt;br /&gt;
(2) の出力例:&lt;br /&gt;
They / elected / him / president / in 2020.&lt;br /&gt;
- They: (S) 彼らは&lt;br /&gt;
- elected: (V) 選出した&lt;br /&gt;
- him: (O) 彼を&lt;br /&gt;
- president: (C) 大統領&lt;br /&gt;
- in 2020: (M: 副詞句) 2020年に&lt;br /&gt;
`&lt;br /&gt;
&lt;br /&gt;
            },&lt;br /&gt;
        ]&lt;br /&gt;
    },&lt;br /&gt;
    {&lt;br /&gt;
        &amp;quot;submenu_title&amp;quot;: &amp;quot;プログラミング支援&amp;quot;,&lt;br /&gt;
        &amp;quot;submenu&amp;quot;: [&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;コードを解説する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下のコードを初めて読む人も理解できるように解説してください。私には言語の基本理解はあるものとし、コードの意図を理解するための情報を中心に説明してください。&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;コードを洗練させる&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下のコードを洗練させてください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;コードをレビューする&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下のコードをセキュリティと保守性の観点でレビューしてください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;エラーログをデバッグする&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下のエラーログを解析し、考えられる原因と対策を提示してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;SQLクエリを最適化する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下のSQL文を最適化してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;PowerShell スクリプトを書く&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;あなたはエキスパートとしてメンテナンス性の高いコードを書くのが得意です。以下の処理を行うPowerShellスクリプトを生成してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;Shell Script を書く&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;あなたはエキスパートとしてメンテナンス性の高いコードを書くのが得意です。以下の処理を行うShell Scriptを生成してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
        ]&lt;br /&gt;
    },&lt;br /&gt;
    {&lt;br /&gt;
        &amp;quot;submenu_title&amp;quot;: &amp;quot;ビジネス文書支援&amp;quot;,&lt;br /&gt;
        &amp;quot;submenu&amp;quot;: [&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;音声文字起こし文を整形する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: `以下は、音声文字起こしの文章です。以下のルールに従って、文章を整形してください。&lt;br /&gt;
&lt;br /&gt;
整形ルール:&lt;br /&gt;
- 要約するなどといった文章の全体感を損なう修正はしない&lt;br /&gt;
- 会話の流れから不自然と思われる単語には、対象単語の直後に誤字の可能性を示唆する [*] を付ける&lt;br /&gt;
- 誤字の可能性を示唆する [*] が付与された場合のみ、出力の末尾に「[*]: 誤字の可能性ありと凡例を書き添える&lt;br /&gt;
- 助詞や句読点など文章上の不適合を正す&lt;br /&gt;
- フィラーワードを除外する`&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;誤字脱字、不正確な表現をチェック&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の2つのカテゴリーで問題点を指摘してください\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 【誤字脱字】：明確な誤字脱字および誤字の疑いがある箇所\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 【不正確・曖昧な表現】：解釈に個人差が生じる可能性がある表現、より適切な表現に改善できる箇所&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;提案書を作成する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下に対する対する提案書を、コスト・実現性・リスクを含めて作成してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;手順書のアウトラインを作成する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の作業を行う手順書を、前提条件と注意事項を含めてアウトラインを作成してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;ユーザーマニュアルのアウトラインを作成する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の機能内容から、ユーザー向けマニュアルのアウトラインを作成してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;議事録を作成する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の会議内容から議事録を作成してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
        ]&lt;br /&gt;
    },&lt;br /&gt;
    {&lt;br /&gt;
        &amp;quot;submenu_title&amp;quot;: &amp;quot;分析支援&amp;quot;,&lt;br /&gt;
        &amp;quot;submenu&amp;quot;: [&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;問題を分析し、課題を抽出する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の状況から問題や潜在的リスクを分析し、課題を抽出してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;対応策を提案する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の問題・課題に対する対応策を複数提案してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;傾向を見つける&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の内容から、数値や挙動上の傾向を見つけてください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
        ]&lt;br /&gt;
    },&lt;br /&gt;
    {&lt;br /&gt;
        &amp;quot;submenu_title&amp;quot;: &amp;quot;アイデア出し&amp;quot;,&lt;br /&gt;
        &amp;quot;submenu&amp;quot;: [&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;SCAMPER法でアイデア出し&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下のテーマについて、SCAMPER法を使って15個のアイデアを出してください。&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;解決策の案出し&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の課題に対して、技術面、運用面、コスト面から実現可能な解決策を5つずつ提案してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;逆転の発想&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の目標に対して、通常とは逆の視点からアプローチする方法を3つ提案してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;競合分析からの着想&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の内容について、業界における競合他社の特徴を分析し、差別化できる新しいアプローチを提案してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;ユースケースの列挙&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の製品・サービスについて、想定されるユースケースを10個列挙し、それぞれの価値提案を記載してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
        ]&lt;br /&gt;
    },&lt;br /&gt;
]&lt;br /&gt;
&lt;br /&gt;
// 生成AIの回答の言語設定&lt;br /&gt;
// &amp;quot;Japanese&amp;quot; や &amp;quot;English&amp;quot; を設定できます。他の言語は未検証。&lt;br /&gt;
// 自動設定にする場合は空文字 &amp;quot;&amp;quot; を設定してください。&lt;br /&gt;
const LANGUAGE = &amp;quot;Japanese&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
// 入力プロンプトの最大トークン数&lt;br /&gt;
// 超適当なトークナイザー関数により計測し、英単語は4文字1トークン、それ以外は1文字1トークンとして数えます。&lt;br /&gt;
const MAX_PROMPT_TOKEN = 6000;&lt;br /&gt;
&lt;br /&gt;
// 会話の履歴を保持する数 (質問と回答で1セット)&lt;br /&gt;
// 数を増やすと、会話の履歴が増えますが、Meryのメモリ使用量が増えたり、&lt;br /&gt;
// 使用トークン数が増えて動作が重くなる可能性があるため、適宜調整してください。&lt;br /&gt;
// document.Tag に保持されるため、開いているファイルを閉じると履歴は消えます。&lt;br /&gt;
const HISTORY_SIZE = 1;&lt;br /&gt;
&lt;br /&gt;
// 会話履歴に保持する最大トークン数&lt;br /&gt;
// 会話履歴のトークン数がこの数を超える場合、古い履歴から削除されます。&lt;br /&gt;
const MAX_HISTORY_TOKEN = 10000;&lt;br /&gt;
&lt;br /&gt;
// コンテキストメニュー番号の定義&lt;br /&gt;
const EXECUTE_AI = 1;&lt;br /&gt;
const RESET_HISTORY = 2;&lt;br /&gt;
const SET_CUSTOM_INSTRUCTION = 3;&lt;br /&gt;
const REEXECUTE_LAST_TEMPLATE_PROMPT = 4;&lt;br /&gt;
const INVALID_MENU = 9;&lt;br /&gt;
const TEMPLATE_PROMPT_THRESHOLD = 10;&lt;br /&gt;
const TEMPLATE_PROMPT_SUBMENU_THRESHOLD = 1000;&lt;br /&gt;
&lt;br /&gt;
// タグ名の定義&lt;br /&gt;
const HISTORY_TAG = &amp;quot;groq-history&amp;quot;;&lt;br /&gt;
const TEMPORARY_CUSTOM_INSTRUCTION_TAG = &amp;quot;groq-temporary-custom-instruction&amp;quot;;&lt;br /&gt;
const PREVIOUS_TEMPLATE_PROMPT_TAG = &amp;quot;groq-previous-template-prompt&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
// 生成AIモデル別設定&lt;br /&gt;
&lt;br /&gt;
const MODEL_CONFIG = {&lt;br /&gt;
    model: &amp;quot;openai/gpt-oss-120b&amp;quot;,&lt;br /&gt;
    max_completion_tokens: 3072,&lt;br /&gt;
    temperature: 0.7,&lt;br /&gt;
    reasoning_effort: &amp;quot;low&amp;quot;,&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// const MODEL_CONFIG = {&lt;br /&gt;
//     model: &amp;quot;qwen/qwen3.8-27b&amp;quot;,&lt;br /&gt;
//     max_completion_tokens: 3072,&lt;br /&gt;
//     temperature: 0.7,&lt;br /&gt;
//     reasoning_effort: &amp;quot;medium&amp;quot;,&lt;br /&gt;
// }&lt;br /&gt;
&lt;br /&gt;
// console.log に出力する内容を Mery のアウトプットバーに表示する&lt;br /&gt;
const console = {&lt;br /&gt;
    log: function (str) {&lt;br /&gt;
        outputBar.Writeln(str)&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
const doc = document;&lt;br /&gt;
const sel = document.selection;&lt;br /&gt;
&lt;br /&gt;
main();&lt;br /&gt;
&lt;br /&gt;
function main() {&lt;br /&gt;
    const selectionIsEmpty = sel.IsEmpty&lt;br /&gt;
&lt;br /&gt;
    // プロンプトの取得&lt;br /&gt;
    if (selectionIsEmpty) {&lt;br /&gt;
        sel.StartOfLine(false, mePosLogical);&lt;br /&gt;
        sel.EndOfLine(true, mePosLogical);&lt;br /&gt;
    }&lt;br /&gt;
    let promptText = sel.Text.trim();&lt;br /&gt;
    let menuTrack = 0;&lt;br /&gt;
    while (true) {&lt;br /&gt;
        // コンテキストメニューの作成&lt;br /&gt;
        const menu = createContextMenu(selectionIsEmpty);&lt;br /&gt;
        menuTrack = menu.Track(0);&lt;br /&gt;
        // メニュー選択がキャンセルされた場合は終了&lt;br /&gt;
        if (menuTrack === 0) return;&lt;br /&gt;
&lt;br /&gt;
        // 無効なメニューの場合は無視して再表示&lt;br /&gt;
        if (menuTrack === INVALID_MENU) {&lt;br /&gt;
            continue;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        // 会話履歴のリセット&lt;br /&gt;
        if (menuTrack === RESET_HISTORY) {&lt;br /&gt;
            resetTag();&lt;br /&gt;
            continue;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        // カスタムインストラクションの設定&lt;br /&gt;
        if (menuTrack === SET_CUSTOM_INSTRUCTION) {&lt;br /&gt;
            configureTemporaryCustomInstruction();&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        break;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // プロンプトが空の場合はエラー&lt;br /&gt;
    if (promptText.length === 0) {&lt;br /&gt;
        alert(&amp;quot;プロンプトを入力してください。&amp;quot;);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
    // 文字数が多すぎる場合はエラー&lt;br /&gt;
    if (roughTokenize(promptText) &amp;gt; MAX_PROMPT_TOKEN) {&lt;br /&gt;
        alert(&amp;quot;プロンプトの文字数が多すぎます。文字数を少なくしてください。&amp;quot;);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
    // 読み取り専用の場合はエラー&lt;br /&gt;
    if (doc.ReadOnly) {&lt;br /&gt;
        alert(&amp;quot;ファイルが読み取り専用のため実行できません。&amp;quot;);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // 直前に実行したプロンプトテンプレートの再実行の場合、menuTrack を書き換え&lt;br /&gt;
    if (menuTrack === REEXECUTE_LAST_TEMPLATE_PROMPT) {&lt;br /&gt;
        if (doc.Tag.exists(PREVIOUS_TEMPLATE_PROMPT_TAG)) {&lt;br /&gt;
            menuTrack = Number(doc.tag[PREVIOUS_TEMPLATE_PROMPT_TAG]);&lt;br /&gt;
        } else {&lt;br /&gt;
            alert(&amp;quot;直前に実行したプロンプトテンプレートがありません。&amp;quot;);&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // テンプレートの場合はプロンプトを変更&lt;br /&gt;
    const isTemplate = menuTrack &amp;gt;= TEMPLATE_PROMPT_THRESHOLD;&lt;br /&gt;
    if (isTemplate) {&lt;br /&gt;
        const isSubmenu = menuTrack &amp;gt;= TEMPLATE_PROMPT_SUBMENU_THRESHOLD;&lt;br /&gt;
        let instructionText = &amp;quot;&amp;quot;;&lt;br /&gt;
        if (isSubmenu) {&lt;br /&gt;
            const parentMenuTrack = Number(String(menuTrack).slice(0, 2));&lt;br /&gt;
            const subMenuTrack = Number(String(menuTrack).slice(2));&lt;br /&gt;
            instructionText = PROMPT_TEMPLATE[parentMenuTrack - TEMPLATE_PROMPT_THRESHOLD].submenu[subMenuTrack].prompt;&lt;br /&gt;
        } else {&lt;br /&gt;
            instructionText = PROMPT_TEMPLATE[menuTrack - TEMPLATE_PROMPT_THRESHOLD].prompt;&lt;br /&gt;
        }&lt;br /&gt;
        const randomId = generateRandomId();&lt;br /&gt;
        promptText = `&lt;br /&gt;
&amp;lt;INSTRUCTIONS_${randomId}&amp;gt;&lt;br /&gt;
${instructionText}&lt;br /&gt;
&amp;lt;/INSTRUCTIONS_${randomId}&amp;gt;&lt;br /&gt;
&amp;lt;INPUTDATA_${randomId}&amp;gt;&lt;br /&gt;
${promptText}&lt;br /&gt;
&amp;lt;/INPUTDATA_${randomId}&amp;gt;&lt;br /&gt;
        `.trim();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // テンプレートの場合は直前に実行したプロンプトテンプレートの番号としてタグに保存&lt;br /&gt;
    if (isTemplate) {&lt;br /&gt;
        addPreviousTemplatePromptNumberToTag(menuTrack);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    try {&lt;br /&gt;
        // 処理終了を待機&lt;br /&gt;
        shell.KeepRunning = true;&lt;br /&gt;
&lt;br /&gt;
        // カーソル位置移動&lt;br /&gt;
        sel.SetActivePoint(mePosLogical, sel.GetBottomPointX(mePosLogical), sel.GetBottomPointY(mePosLogical));&lt;br /&gt;
        sel.EndOfLine(false, mePosLogical);&lt;br /&gt;
        if (sel.GetActivePointX(mePosLogical) &amp;gt; 1) sel.NewLine();&lt;br /&gt;
&lt;br /&gt;
        ShowTip(&amp;quot;リクエスト中.....&amp;quot;, meShowTipPosCaret);&lt;br /&gt;
&lt;br /&gt;
        // APIキーの取得&lt;br /&gt;
        const apiKey = shell.getEnv(&amp;quot;GROQ_API_KEY&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
        // groq API に問い合わせ&lt;br /&gt;
        sel.Text = &amp;quot;\n---\n\n&amp;quot;&lt;br /&gt;
        document.KeepScrollPos = true;&lt;br /&gt;
        callApi(apiKey, promptText, isTemplate);&lt;br /&gt;
        if (sel.GetActivePointX(mePosLogical) &amp;gt; 2) {&lt;br /&gt;
            sel.Text = &amp;quot;\n\n---\n\n\n&amp;quot;&lt;br /&gt;
        } else {&lt;br /&gt;
            sel.Text = &amp;quot;\n---\n\n\n&amp;quot;&lt;br /&gt;
        }&lt;br /&gt;
    } catch (e) {&lt;br /&gt;
        sel.Text = &amp;quot;&amp;lt;処理中にエラー発生&amp;gt;\n&amp;quot; + e.message + &amp;quot;\n&amp;quot; + e.stack;&lt;br /&gt;
    } finally {&lt;br /&gt;
        // 処理終了待機を解除&lt;br /&gt;
        shell.KeepRunning = false;&lt;br /&gt;
        ShowTip(&amp;quot;&amp;quot;, meShowTipHide);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function createContextMenu(selectionIsEmpty) {&lt;br /&gt;
    const menu = CreatePopupMenu();&lt;br /&gt;
    menu.Add(&amp;quot;※注意※ プライバシー・機密情報を送信しないこと&amp;quot;, INVALID_MENU);&lt;br /&gt;
    menu.Add(&amp;quot;&amp;quot;, 0, meMenuSeparator);&lt;br /&gt;
    let t = &amp;quot;&amp;quot;;&lt;br /&gt;
    if (selectionIsEmpty) {&lt;br /&gt;
        t = &amp;quot;アクティブ行の内容で相談 (&amp;amp;A)&amp;quot;;&lt;br /&gt;
    } else {&lt;br /&gt;
        t = &amp;quot;選択範囲の内容で相談 (&amp;amp;A)&amp;quot;;&lt;br /&gt;
    }&lt;br /&gt;
    menu.Add(t, EXECUTE_AI);&lt;br /&gt;
    if (HISTORY_SIZE &amp;lt;= 0) {&lt;br /&gt;
        menu.Add(&amp;quot;├ 会話履歴をクリア (履歴が無効になっています)&amp;quot;, RESET_HISTORY, meMenuGrayed);&lt;br /&gt;
    } else if (doc.Tag.exists(HISTORY_TAG)) {&lt;br /&gt;
        const turn = JSON.parse(doc.tag[HISTORY_TAG]).length / 2;&lt;br /&gt;
        menu.Add(&amp;quot;├ 会話履歴をクリア (現在の会話数: &amp;quot; + turn + &amp;quot;) (&amp;amp;C)&amp;quot;, RESET_HISTORY);&lt;br /&gt;
    } else {&lt;br /&gt;
        menu.Add(&amp;quot;├ 会話履歴をクリア (現在の会話数: 0) (&amp;amp;C)&amp;quot;, RESET_HISTORY, meMenuGrayed);&lt;br /&gt;
    }&lt;br /&gt;
    menu.Add(&amp;quot;└ カスタムインストラクションを変更 (&amp;amp;I)&amp;quot;, SET_CUSTOM_INSTRUCTION);&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    // テンプレート数チェック&lt;br /&gt;
    // 2桁まで許容&lt;br /&gt;
    const ITEMS_MUX = 99;&lt;br /&gt;
    if (PROMPT_TEMPLATE.length &amp;gt; ITEMS_MUX - TEMPLATE_PROMPT_THRESHOLD) {&lt;br /&gt;
        throw new Error(&amp;quot;プロンプトテンプレートの数が多すぎます。&amp;quot;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // テンプレートメニューの追加&lt;br /&gt;
    if (PROMPT_TEMPLATE.length &amp;gt; 0) {&lt;br /&gt;
        const subMenu = CreatePopupMenu();&lt;br /&gt;
        let previousMenuString = &amp;quot;&amp;quot;;&lt;br /&gt;
        for (let i = 0; i &amp;lt; PROMPT_TEMPLATE.length; i++) {&lt;br /&gt;
            let accelerationKey = &amp;quot;&amp;quot;;&lt;br /&gt;
            if (i === 9) {&lt;br /&gt;
                accelerationKey = &amp;quot; (&amp;amp;0)&amp;quot;;&lt;br /&gt;
            } else if (i &amp;lt; 10) {&lt;br /&gt;
                accelerationKey = &amp;quot; (&amp;amp;&amp;quot; + (i + 1) + &amp;quot;)&amp;quot;;&lt;br /&gt;
            }&lt;br /&gt;
            const menuNum = TEMPLATE_PROMPT_THRESHOLD + i;&lt;br /&gt;
            if (PROMPT_TEMPLATE[i].submenu) {&lt;br /&gt;
                const subMenuList = PROMPT_TEMPLATE[i].submenu;&lt;br /&gt;
                // テンプレート数チェック&lt;br /&gt;
                if (subMenuList.length &amp;gt; ITEMS_MUX) {&lt;br /&gt;
                    throw new Error(&amp;quot;プロンプトテンプレート (サブメニュー) の数が多すぎます。&amp;quot;);&lt;br /&gt;
                }&lt;br /&gt;
                const subSubMenu = CreatePopupMenu();&lt;br /&gt;
                const subMenuPrefix = String(menuNum);&lt;br /&gt;
                for (let j = 0; j &amp;lt; subMenuList.length; j++) {&lt;br /&gt;
                    let subAccelerationKey = &amp;quot;&amp;quot;;&lt;br /&gt;
                    if (j === 9) {&lt;br /&gt;
                        subAccelerationKey = &amp;quot; (&amp;amp;0)&amp;quot;;&lt;br /&gt;
                    } else if (j &amp;lt; 10) {&lt;br /&gt;
                        subAccelerationKey = &amp;quot; (&amp;amp;&amp;quot; + (j + 1) + &amp;quot;)&amp;quot;;&lt;br /&gt;
                    }&lt;br /&gt;
                    // ES5 compatible version without padStart&lt;br /&gt;
                    const subMenuNum = Number(subMenuPrefix + (j &amp;lt; 10 ? &amp;quot;0&amp;quot; + j : String(j)));&lt;br /&gt;
                    subSubMenu.Add(subMenuList[j].title + subAccelerationKey, subMenuNum);&lt;br /&gt;
                    if (subMenuNum === doc.tag[PREVIOUS_TEMPLATE_PROMPT_TAG]) {&lt;br /&gt;
                        previousMenuString = subMenuList[j].title;&lt;br /&gt;
                    }&lt;br /&gt;
                }&lt;br /&gt;
                subMenu.AddPopup(PROMPT_TEMPLATE[i].submenu_title + accelerationKey, subSubMenu);&lt;br /&gt;
            } else {&lt;br /&gt;
                subMenu.Add(PROMPT_TEMPLATE[i].title + accelerationKey, menuNum);&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        if (selectionIsEmpty) {&lt;br /&gt;
            t = &amp;quot;プロンプト テンプレート (対象: アクティブ行) (&amp;amp;T)&amp;quot;;&lt;br /&gt;
        } else {&lt;br /&gt;
            t = &amp;quot;プロンプト テンプレート (対象: 選択範囲) (&amp;amp;T)&amp;quot;;&lt;br /&gt;
        }&lt;br /&gt;
        menu.AddPopup(t, subMenu);&lt;br /&gt;
&lt;br /&gt;
        t = &amp;quot;└ 直前に実行したプロンプト テンプレートを再実行 (&amp;amp;R)&amp;quot;;&lt;br /&gt;
        if (doc.Tag.exists(PREVIOUS_TEMPLATE_PROMPT_TAG)) {&lt;br /&gt;
            menu.Add(t, REEXECUTE_LAST_TEMPLATE_PROMPT);&lt;br /&gt;
            menu.Add(`    (直前実行: ${previousMenuString})`, REEXECUTE_LAST_TEMPLATE_PROMPT);&lt;br /&gt;
        } else {&lt;br /&gt;
            menu.Add(t, REEXECUTE_LAST_TEMPLATE_PROMPT, meMenuGrayed);&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return menu;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function escapeRequestBodyForCRuntimeParser(requestBody) {&lt;br /&gt;
    // Cランタイムパーサーに渡すためのエスケープ処理&lt;br /&gt;
    return JSON.stringify(requestBody)&lt;br /&gt;
        .replace(/((?:\\)*)&amp;quot;/g, function (match, p1) {&lt;br /&gt;
            if (p1) {&lt;br /&gt;
                const backslashCount = p1.length;&lt;br /&gt;
                let backslash = &amp;quot;&amp;quot;;&lt;br /&gt;
                backslash = &amp;quot;\\&amp;quot;.repeat(backslashCount);&lt;br /&gt;
                return backslash + &amp;quot;\\&amp;quot; + match;&lt;br /&gt;
            } else {&lt;br /&gt;
                return &amp;quot;\\&amp;quot; + match;&lt;br /&gt;
            }&lt;br /&gt;
        });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function callApi(apiKey, promptText, isTemplate) {&lt;br /&gt;
    try {&lt;br /&gt;
        // fetch で Groq API にリクエスト&lt;br /&gt;
        const requestBody = {&lt;br /&gt;
            messages: createRequestMessages(promptText, isTemplate),&lt;br /&gt;
            stream: false,&lt;br /&gt;
        };&lt;br /&gt;
        for (let key in MODEL_CONFIG) {&lt;br /&gt;
            requestBody[key] = MODEL_CONFIG[key];&lt;br /&gt;
        }&lt;br /&gt;
        if (isTemplate) {&lt;br /&gt;
            // テンプレートの場合は応答を決定論的にする&lt;br /&gt;
            // テンプレート以外の場合はデフォルト値とするために指定しない&lt;br /&gt;
            requestBody.temperature = 0.3;&lt;br /&gt;
        }&lt;br /&gt;
        const url = &#039;https://api.groq.com/openai/v1/chat/completions&#039;;&lt;br /&gt;
&lt;br /&gt;
        const requestJson = escapeRequestBodyForCRuntimeParser(requestBody);&lt;br /&gt;
        // curl で Groq API にリクエスト&lt;br /&gt;
        const command = &amp;quot;curl -s -X POST &amp;quot; + url&lt;br /&gt;
            + &#039; -H &amp;quot;Content-Type: application/json&amp;quot;&#039;&lt;br /&gt;
            + &#039; -H &amp;quot;Authorization: Bearer &#039; + apiKey + &#039;&amp;quot;&#039;&lt;br /&gt;
            + &#039; -d &amp;quot;&#039; + requestJson + &#039;&amp;quot;&#039;;&lt;br /&gt;
        const comResult = shell.Exec(command);&lt;br /&gt;
&lt;br /&gt;
        if (comResult.ExitCode != 0) {&lt;br /&gt;
            sel.Text = &amp;quot;リクエストでエラーが発生しました。\n&amp;quot;&lt;br /&gt;
                + &amp;quot;  エラーコード: &amp;quot; + comResult.ExitCode + &amp;quot;\n&amp;quot;&lt;br /&gt;
                + &amp;quot;  標準出力: &amp;quot; + (comResult.StdOut.trim() ? comResult.StdOut.trim() : &amp;quot;-&amp;quot;) + &amp;quot;\n&amp;quot;&lt;br /&gt;
                + &amp;quot;  エラー出力: &amp;quot; + (comResult.StdErr.trim() ? comResult.StdErr.trim() : &amp;quot;-&amp;quot;);&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
        const responseRaw = comResult.StdOut;&lt;br /&gt;
&lt;br /&gt;
        const response = JSON.parse(responseRaw);&lt;br /&gt;
&lt;br /&gt;
        let ret = &amp;quot;&amp;quot;;&lt;br /&gt;
        const choices = response.choices;&lt;br /&gt;
        if (!choices) {&lt;br /&gt;
            sel.Text = &amp;quot;リクエストでエラーが発生しました。\n&amp;quot;&lt;br /&gt;
                + &amp;quot;  標準出力: &amp;quot; + (comResult.StdOut.trim() ? comResult.StdOut.trim() : &amp;quot;-&amp;quot;) + &amp;quot;\n&amp;quot;&lt;br /&gt;
                + &amp;quot;  エラー出力: &amp;quot; + (comResult.StdErr.trim() ? comResult.StdErr.trim() : &amp;quot;-&amp;quot;);&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (choices &amp;amp;&amp;amp; choices.length &amp;gt; 0) {&lt;br /&gt;
            choices.forEach(function (choice) {&lt;br /&gt;
                const message = choice.message;&lt;br /&gt;
                if (message) {&lt;br /&gt;
                    ret += message.content;&lt;br /&gt;
                }&lt;br /&gt;
            });&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        const usage = response.usage;&lt;br /&gt;
        let usageText = &amp;quot;&amp;quot;;&lt;br /&gt;
        if (usage) {&lt;br /&gt;
            usageText += &amp;quot;\n\n(利用トークン数: プロンプト=&amp;quot; + usage.prompt_tokens&lt;br /&gt;
                + &amp;quot;, 応答=&amp;quot; + usage.completion_tokens&lt;br /&gt;
                + &amp;quot;, 合計=&amp;quot; + usage.total_tokens + &amp;quot;)&amp;quot;;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        const formattedOutput = formatOutput(ret);&lt;br /&gt;
&lt;br /&gt;
        sel.Text = formattedOutput + usageText;&lt;br /&gt;
&lt;br /&gt;
        // 結果を会話履歴に追加&lt;br /&gt;
        if (HISTORY_SIZE &amp;gt; 0 &amp;amp;&amp;amp; !isTemplate) {&lt;br /&gt;
            appendReturnTextToTag(promptText, ret);&lt;br /&gt;
        }&lt;br /&gt;
    } catch (e) {&lt;br /&gt;
        throw e;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function formatOutput(text) {&lt;br /&gt;
    // Markdownを整形する&lt;br /&gt;
    text = formatMarkdown(text);&lt;br /&gt;
    return text&lt;br /&gt;
        // クォーテーションの正規化&lt;br /&gt;
        .replace(/[“”]/g, &#039;&amp;quot;&#039;)&lt;br /&gt;
        .replace(/[‘’]/g, &amp;quot;&#039;&amp;quot;)&lt;br /&gt;
        // 行末のスペースを削除&lt;br /&gt;
        .replace(/[ \t]+$/gm, &#039;&#039;)&lt;br /&gt;
        // 1️⃣ などの数字を半角に変換&lt;br /&gt;
        .replace(/([1-9#*]+)\ufe0f\u20e3/g, &#039;($1)&#039;)&lt;br /&gt;
        ;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/**&lt;br /&gt;
 * Markdown を整形する（ES5 対応）&lt;br /&gt;
 *  1. コードブロック ```～``` を保護して整形対象から除外&lt;br /&gt;
 *  2. 見出し行の前後に空行を 1 行ずつ確保&lt;br /&gt;
 *  3. **太字** / *斜体* / `コード` の外側に半角スペースを 1 つ確保&lt;br /&gt;
 * @param  {string} md  元の Markdown&lt;br /&gt;
 * @return {string}     整形後 Markdown&lt;br /&gt;
 */&lt;br /&gt;
function formatMarkdown(md) {&lt;br /&gt;
    /* === 0. コードブロックをプレースホルダーへ退避 ============== */&lt;br /&gt;
    const codeBlocks = [];      // 元のコードブロックを格納&lt;br /&gt;
    let body = md.replace(/```[\s\S]*?```/g, function (match) {&lt;br /&gt;
        const key = &#039;@@@CODE@BLOCK@&#039; + codeBlocks.length + &#039;@@@&#039;;&lt;br /&gt;
        codeBlocks.push(match);&lt;br /&gt;
        return key;             // プレースホルダーを本文に残す&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    /* === 見出しの前後に空行を挿入 ============================ */&lt;br /&gt;
&lt;br /&gt;
    /* 見出し直前に空行（先頭行以外で空行が無い場合） */&lt;br /&gt;
    body = body.replace(&lt;br /&gt;
        /([^\r\n])(\r?\n)(#{1,6}\s)/g,&lt;br /&gt;
        function (_, prev, br, head) { return prev + br + &#039;\n&#039; + head; }&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
    /* 見出し直後に空行（既に空行が無い場合） */&lt;br /&gt;
    body = body.replace(&lt;br /&gt;
        /^(#{1,6}\s[^\n]+?)(\r?\n)(?!\r?\n)/gm,&lt;br /&gt;
        &#039;$1$2\n&#039;&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
    /* === コードブロックを復元 ================================= */&lt;br /&gt;
    body = body.replace(/@@@CODE@BLOCK@(\d+)@@@/g, function (_, idx) {&lt;br /&gt;
        return codeBlocks[Number(idx)];&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    return body;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/**&lt;br /&gt;
 * 斜体 (*…* / _…_) の外側にスペースを追加しつつ&lt;br /&gt;
 * **太字** の中へ誤マッチしないよう制御&lt;br /&gt;
 */&lt;br /&gt;
function surroundItalic(text) {&lt;br /&gt;
    /* *italic* */&lt;br /&gt;
    text = text.replace(&lt;br /&gt;
        /(^|[^*])(\*(?![*\s])[^*\n]*?[^*\n\s]\*(?!\*))(($)|[^*])/gm,&lt;br /&gt;
        function (_, pre, core, post) {&lt;br /&gt;
            pre = pre === &#039;&#039; || pre.match(/\s/) ? pre : pre + &#039; &#039;;&lt;br /&gt;
            post = post === &#039;&#039; || post.match(/\s/) ? post : &#039; &#039; + post;&lt;br /&gt;
            return pre + core + post;&lt;br /&gt;
        }&lt;br /&gt;
    );&lt;br /&gt;
    /* _italic_ */&lt;br /&gt;
    text = text.replace(&lt;br /&gt;
        /(^|[^_])(\_(?![_\s])[^_\n]*?[^_\n\s]\_(?!\_))(($)|[^_])/gm,&lt;br /&gt;
        function (_, pre, core, post) {&lt;br /&gt;
            pre = pre === &#039;&#039; || pre.match(/\s/) ? pre : pre + &#039; &#039;;&lt;br /&gt;
            post = post === &#039;&#039; || post.match(/\s/) ? post : &#039; &#039; + post;&lt;br /&gt;
            return pre + core + post;&lt;br /&gt;
        }&lt;br /&gt;
    );&lt;br /&gt;
    return text;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function roughTokenize(text) {&lt;br /&gt;
    // 超適当なトークナイザ。トークン数をざっくりと多めに数える&lt;br /&gt;
    // 空白文字・ハイフン・アンダースコア区切りの単語に分割して、&lt;br /&gt;
    // 単語がアルファベットのみ、数字のみの場合には 4文字=1トークン として数える。&lt;br /&gt;
    // 単語にアルファベット以外の文字が含まれる場合は 1文字=1トークン として数える。&lt;br /&gt;
&lt;br /&gt;
    const words = text.split(/[\s\-_]+/);&lt;br /&gt;
    let totalTokens = 0;&lt;br /&gt;
&lt;br /&gt;
    for (let i = 0; i &amp;lt; words.length; i++) {&lt;br /&gt;
        const word = words[i];&lt;br /&gt;
        if (!word) continue; // 空の単語をスキップ&lt;br /&gt;
&lt;br /&gt;
        if (/^[a-zA-Z]+$/.test(word) || /^[0-9]+$/.test(word)) {&lt;br /&gt;
            totalTokens += Math.ceil(word.length / 4);&lt;br /&gt;
        } else {&lt;br /&gt;
            totalTokens += word.length;&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return totalTokens;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// document.Tag の内容を加味してgroqのリクエストを作成&lt;br /&gt;
function createRequestMessages(promptText, isTemplate) {&lt;br /&gt;
    const customInstructionText = buildCustomInstructionText();&lt;br /&gt;
    // テンプレートの場合は、会話履歴とカスタムインストラクションを反映しない&lt;br /&gt;
    if (isTemplate) {&lt;br /&gt;
        const request = [&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;role&amp;quot;: &amp;quot;user&amp;quot;,&lt;br /&gt;
                &amp;quot;content&amp;quot;: [{ &amp;quot;type&amp;quot;: &amp;quot;text&amp;quot;, &amp;quot;text&amp;quot;: promptText }]&lt;br /&gt;
            }&lt;br /&gt;
        ];&lt;br /&gt;
        if (customInstructionText) {&lt;br /&gt;
            request.unshift({&lt;br /&gt;
                &amp;quot;role&amp;quot;: &amp;quot;system&amp;quot;,&lt;br /&gt;
                &amp;quot;content&amp;quot;: customInstructionText&lt;br /&gt;
            })&lt;br /&gt;
        }&lt;br /&gt;
        return request;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    const tag = document.Tag;&lt;br /&gt;
&lt;br /&gt;
    // 以前の会話が存在しない場合は新規リクエスト&lt;br /&gt;
    if (!tag.exists(HISTORY_TAG)) {&lt;br /&gt;
        const request = [{&lt;br /&gt;
            &amp;quot;role&amp;quot;: &amp;quot;user&amp;quot;,&lt;br /&gt;
            &amp;quot;content&amp;quot;: [{ &amp;quot;type&amp;quot;: &amp;quot;text&amp;quot;, &amp;quot;text&amp;quot;: promptText }]&lt;br /&gt;
        }];&lt;br /&gt;
        if (customInstructionText) {&lt;br /&gt;
            request.unshift({&lt;br /&gt;
                &amp;quot;role&amp;quot;: &amp;quot;system&amp;quot;,&lt;br /&gt;
                &amp;quot;content&amp;quot;: customInstructionText&lt;br /&gt;
            })&lt;br /&gt;
        }&lt;br /&gt;
        return request;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // 会話履歴が存在する場合は、履歴を含めてリクエスト&lt;br /&gt;
    const request = JSON.parse(tag[HISTORY_TAG]);&lt;br /&gt;
    if (customInstructionText) {&lt;br /&gt;
        request.unshift({&lt;br /&gt;
            &amp;quot;role&amp;quot;: &amp;quot;system&amp;quot;,&lt;br /&gt;
            &amp;quot;content&amp;quot;: customInstructionText&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
    request.push({&lt;br /&gt;
        &amp;quot;role&amp;quot;: &amp;quot;user&amp;quot;,&lt;br /&gt;
        &amp;quot;content&amp;quot;: promptText&lt;br /&gt;
    });&lt;br /&gt;
    return request;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// document.Tag にgroqの応答を追加&lt;br /&gt;
function appendReturnTextToTag(userText, modelText) {&lt;br /&gt;
    const tag = document.Tag;&lt;br /&gt;
    if (!tag.exists(HISTORY_TAG)) {&lt;br /&gt;
        tag[HISTORY_TAG] = JSON.stringify([&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;role&amp;quot;: &amp;quot;user&amp;quot;,&lt;br /&gt;
                &amp;quot;content&amp;quot;: userText&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;role&amp;quot;: &amp;quot;assistant&amp;quot;,&lt;br /&gt;
                &amp;quot;content&amp;quot;: modelText&lt;br /&gt;
            }&lt;br /&gt;
        ]);&lt;br /&gt;
    } else {&lt;br /&gt;
        const groqText = JSON.parse(tag[HISTORY_TAG]);&lt;br /&gt;
        groqText.push({&lt;br /&gt;
            &amp;quot;role&amp;quot;: &amp;quot;user&amp;quot;,&lt;br /&gt;
            &amp;quot;content&amp;quot;: userText&lt;br /&gt;
        });&lt;br /&gt;
        groqText.push({&lt;br /&gt;
            &amp;quot;role&amp;quot;: &amp;quot;assistant&amp;quot;,&lt;br /&gt;
            &amp;quot;content&amp;quot;: modelText&lt;br /&gt;
        });&lt;br /&gt;
        // 会話セット (user, model 1往復) が特定セット数を超えた場合、最初のセットを削除&lt;br /&gt;
        if (groqText.length &amp;gt; HISTORY_SIZE * 2) {&lt;br /&gt;
            groqText.splice(0, 2);&lt;br /&gt;
        }&lt;br /&gt;
        // 会話履歴全体が MAX_HISTORY_TOKEN を超えた場合、最初のセットを削除&lt;br /&gt;
        let joinedText = &amp;quot;&amp;quot;;&lt;br /&gt;
        for (let i = 0; i &amp;lt; groqText.length; i++) {&lt;br /&gt;
            joinedText += groqText[i].content + &amp;quot;\n&amp;quot;;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        while (roughTokenize(joinedText) &amp;gt; MAX_HISTORY_TOKEN) {&lt;br /&gt;
            groqText.splice(0, 2);&lt;br /&gt;
            joinedText = &amp;quot;&amp;quot;;&lt;br /&gt;
            for (let i = 0; i &amp;lt; groqText.length; i++) {&lt;br /&gt;
                joinedText += groqText[i].content + &amp;quot;\n&amp;quot;;&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        tag[HISTORY_TAG] = JSON.stringify(groqText);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function resetTag() {&lt;br /&gt;
    const tag = doc.Tag;&lt;br /&gt;
    if (tag.exists(HISTORY_TAG)) {&lt;br /&gt;
        tag.remove(HISTORY_TAG);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getLocalIsoDateTimeString() {&lt;br /&gt;
    const now = new Date();&lt;br /&gt;
    const pad = function (num) {&lt;br /&gt;
        return (num &amp;lt; 10 ? &amp;quot;0&amp;quot; : &amp;quot;&amp;quot;) + num;&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    const year = now.getFullYear();&lt;br /&gt;
    const month = pad(now.getMonth() + 1);&lt;br /&gt;
    const day = pad(now.getDate());&lt;br /&gt;
    const hours = pad(now.getHours());&lt;br /&gt;
    const minutes = pad(now.getMinutes());&lt;br /&gt;
    const seconds = pad(now.getSeconds());&lt;br /&gt;
&lt;br /&gt;
    const offsetMinutes = now.getTimezoneOffset();&lt;br /&gt;
    const offsetSign = offsetMinutes &amp;lt;= 0 ? &amp;quot;+&amp;quot; : &amp;quot;-&amp;quot;;&lt;br /&gt;
    const absOffsetHours = pad(Math.floor(Math.abs(offsetMinutes) / 60));&lt;br /&gt;
    const absOffsetMinutes = pad(Math.abs(offsetMinutes) % 60);&lt;br /&gt;
&lt;br /&gt;
    const isoLikeLocal = year + &amp;quot;-&amp;quot; + month + &amp;quot;-&amp;quot; + day&lt;br /&gt;
        + &amp;quot;T&amp;quot; + hours + &amp;quot;:&amp;quot; + minutes + &amp;quot;:&amp;quot; + seconds&lt;br /&gt;
        + offsetSign + absOffsetHours + &amp;quot;:&amp;quot; + absOffsetMinutes;&lt;br /&gt;
    return isoLikeLocal;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function baseCustomInstruction() {&lt;br /&gt;
    const language = LANGUAGE.trim();&lt;br /&gt;
    return &amp;quot;The current date and time for this user is &amp;quot; + getLocalIsoDateTimeString() + &amp;quot;. You cannot know for sure what happens after the knowledge cut-off date.&amp;quot;&lt;br /&gt;
        + (language ? &amp;quot;Respond in &amp;quot; + language + &amp;quot;\n&amp;quot; : &amp;quot;&amp;quot;)&lt;br /&gt;
        + &amp;quot;Do not hallucinate.\n&amp;quot;&lt;br /&gt;
        + &amp;quot;**Start with the main point of the question without explaining or declaring your writing style or tone.**\n&amp;quot;&lt;br /&gt;
        + &amp;quot;**At the end of your answer, avoid adding formulaic phrases that prompt the next question or unrelated conversation that is not directly related to your answer.**\n&amp;quot;&lt;br /&gt;
    // + &amp;quot;Do NOT EVER use full-width brackets \&amp;quot;（）\&amp;quot;, but use half-width brackets \&amp;quot;()\&amp;quot;. Enclose the outside of the half-width brackets with half-width spaces.\n&amp;quot;&lt;br /&gt;
    // + &amp;quot;When using Markdown formatting, you MUST follow these rules:\n&amp;quot;&lt;br /&gt;
    // + &amp;quot;- Rule 1: Insert a blank line below the header line (A header line begins with one or more # characters).\n&amp;quot;&lt;br /&gt;
    // + &amp;quot;- Rule 2: Insert spaces before and after bold (surrounded by **), italic (surrounded by *), and inline code (surrounded by `). For example: \&amp;quot;私は **元気** です。\&amp;quot;\n&amp;quot;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function buildCustomInstructionText() {&lt;br /&gt;
    let customInstruction = getTemporaryCustomInstruction();&lt;br /&gt;
    if (!customInstruction) {&lt;br /&gt;
        // 一時的なカスタムインストラクションがない場合はデフォルトを使用&lt;br /&gt;
        customInstruction = DEFAULT_CUSTOM_INSTRUCTION;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    customInstruction = customInstruction.replace(/^[\s　]+|[\s　]+$/g, &amp;quot;&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
    return baseCustomInstruction().trim()&lt;br /&gt;
        + (customInstruction ? &amp;quot;\n&amp;quot; + customInstruction : &amp;quot;&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function configureTemporaryCustomInstruction() {&lt;br /&gt;
    let tempCustomInstruction = getTemporaryCustomInstruction();&lt;br /&gt;
    const prompt = Prompt(&amp;quot;カスタムインストラクションを変更 ※変更すると会話履歴がリセットされます&amp;quot;, tempCustomInstruction ? tempCustomInstruction : DEFAULT_CUSTOM_INSTRUCTION);&lt;br /&gt;
    if (prompt) {&lt;br /&gt;
        if (prompt === DEFAULT_CUSTOM_INSTRUCTION) {&lt;br /&gt;
            // デフォルトと同じ値の場合はタグを削除&lt;br /&gt;
            setTemporaryCustomInstruction(&amp;quot;&amp;quot;);&lt;br /&gt;
        } else if (prompt !== tempCustomInstruction) {&lt;br /&gt;
            // デフォルト以外の値が設定された場合は保存&lt;br /&gt;
            setTemporaryCustomInstruction(prompt);&lt;br /&gt;
        }&lt;br /&gt;
    } else if (prompt !== null) {&lt;br /&gt;
        // 空文字でOKされた場合はデフォルトに戻すか確認&lt;br /&gt;
        if (prompt === &amp;quot;&amp;quot;) {&lt;br /&gt;
            if (Confirm(&amp;quot;カスタムインストラクションをデフォルトに戻しますか？\n※カスタムインストラクションを空にしたい場合は [キャンセル] を選択してください。&amp;quot;)) {&lt;br /&gt;
                setTemporaryCustomInstruction(&amp;quot;&amp;quot;);&lt;br /&gt;
            } else {&lt;br /&gt;
                setTemporaryCustomInstruction(&amp;quot; &amp;quot;);&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // 変更が加わっている場合は会話履歴をリセット&lt;br /&gt;
    if (tempCustomInstruction !== getTemporaryCustomInstruction()) {&lt;br /&gt;
        resetTag();&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getTemporaryCustomInstruction() {&lt;br /&gt;
    const tag = doc.Tag;&lt;br /&gt;
    if (tag.exists(TEMPORARY_CUSTOM_INSTRUCTION_TAG)) {&lt;br /&gt;
        return tag[TEMPORARY_CUSTOM_INSTRUCTION_TAG];&lt;br /&gt;
    } else {&lt;br /&gt;
        return &amp;quot;&amp;quot;;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function setTemporaryCustomInstruction(temporaryCustomInstruction) {&lt;br /&gt;
    const tag = doc.Tag;&lt;br /&gt;
    if (temporaryCustomInstruction) {&lt;br /&gt;
        tag[TEMPORARY_CUSTOM_INSTRUCTION_TAG] = temporaryCustomInstruction;&lt;br /&gt;
    } else {&lt;br /&gt;
        if (tag.exists(TEMPORARY_CUSTOM_INSTRUCTION_TAG)) {&lt;br /&gt;
            tag.remove(TEMPORARY_CUSTOM_INSTRUCTION_TAG);&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function addPreviousTemplatePromptNumberToTag(menuTrack) {&lt;br /&gt;
    const tag = doc.Tag;&lt;br /&gt;
    tag[PREVIOUS_TEMPLATE_PROMPT_TAG] = menuTrack;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function inputProgressChar(pos) {&lt;br /&gt;
    sel.SetActivePos(pos);&lt;br /&gt;
    sel.Text = &amp;quot;.&amp;quot;;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function generateRandomId(length = 16) {&lt;br /&gt;
  const chars = &#039;ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789&#039;;&lt;br /&gt;
  let result = &#039;&#039;;&lt;br /&gt;
  for (let i = 0; i &amp;lt; length; i++) {&lt;br /&gt;
    result += chars[Math.floor(Math.random() * chars.length)];&lt;br /&gt;
  }&lt;br /&gt;
  return result;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 変更履歴 ==&lt;br /&gt;
&lt;br /&gt;
* 2026-08-31 Kimi K2 0905 が提供終了となっていたため、モデルを openai/gpt-oss-120b に変更。その他諸々調整&lt;br /&gt;
* 2025-12-06 &amp;lt;code&amp;gt;#language = &amp;quot;quickjs&amp;quot;&amp;lt;/code&amp;gt; 指定を追加。各種リファクタリング&lt;br /&gt;
* 2025-12-02 curl実行時のリクエスト文字列エスケープ処理を調整&lt;br /&gt;
* 2025-12-01 curl実行時のリクエスト文字列エスケープ処理を調整&lt;br /&gt;
* 2025-11-29 デフォルトカスタム指示を調整。Markdown出力の整形処理を調整など&lt;br /&gt;
* 2025-09-12 Kimi K2 0905 モデルに対応。Markdown出力の整形処理を追加&lt;br /&gt;
* 2025-04-13 初版&lt;/div&gt;</summary>
		<author><name>Yuko</name></author>
	</entry>
	<entry>
		<id>https://www.haijin-boys.com/wiki/index.php?title=Groq_%E3%81%AB%E7%9B%B8%E8%AB%87&amp;diff=9629</id>
		<title>Groq に相談</title>
		<link rel="alternate" type="text/html" href="https://www.haijin-boys.com/wiki/index.php?title=Groq_%E3%81%AB%E7%9B%B8%E8%AB%87&amp;diff=9629"/>
		<updated>2026-08-30T16:43:21Z</updated>

		<summary type="html">&lt;p&gt;Yuko: /* 変更履歴 */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== 概要 ==&lt;br /&gt;
&lt;br /&gt;
生成AIプラットフォームである [https://groq.com GroqCloud] で各種AIを利用するためのMeryマクロです。&lt;br /&gt;
※GroqCloud で提供されるモデルは複数あるため、私がよく利用するモデルを中心に対応しています。&lt;br /&gt;
&lt;br /&gt;
「Google Geminiに相談」マクロでは動作中のストリーム通信に対応するためにV8エンジンで実行していましたが、V8エンジン版は使用上の制限からマクロ実行を開始するまで1秒程度のタイムラグが発生していました。GroqCloudの売りである超高速レスポンスを見込んで本マクロではストリーム通信を使わないようにしました。これにより従来のJScriptで処理を組み立てることができるため、前述のタイムラグが起こらないようになりました。また、ストリーム通信を使わないことで通信量が少なく応答もシンプルになるため、場合によっては「Google Geminiに相談」マクロよりも処理が安定する可能性があります。&lt;br /&gt;
&lt;br /&gt;
【主な機能】&lt;br /&gt;
&lt;br /&gt;
* GroqCloud APIを利用して生成AIと対話できるMery用のマクロ&lt;br /&gt;
* 選択したテキストやアクティブな行の内容について生成AIに質問・相談が可能&lt;br /&gt;
* 会話履歴を保持し、文脈を考慮した対話が可能&lt;br /&gt;
&lt;br /&gt;
【特徴的な機能】&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;会話履歴機能&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* 事前の設定数に応じた会話履歴を保持&lt;br /&gt;
* 履歴のクリアが可能&lt;br /&gt;
&lt;br /&gt;
これにより、会話の文脈を考慮した回答を得られます。&lt;br /&gt;
※デフォルトはオフ。「HISTORY_SIZE」変数を増やすと履歴を保持するようになります。&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;カスタムインストラクション&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* AIの応答方針を設定可能&lt;br /&gt;
* 一時的なカスタム指示の保存と変更が可能&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;プロンプトテンプレート&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* 誤字脱字チェック&lt;br /&gt;
* 日本語翻訳&lt;br /&gt;
* コードの最適化、生成&lt;br /&gt;
&lt;br /&gt;
など、定型的な質問を簡単に実行できます。&lt;br /&gt;
※テンプレート機能では回答を安定させるため、前述の会話履歴を文脈に含みません。&lt;br /&gt;
&lt;br /&gt;
テンプレートは自身の使い方に合わせて好きに追加、削除ができます。詳細はソースコード内の「PROMPT_TEMPLATE」定数のコメントを参照。&lt;br /&gt;
&lt;br /&gt;
== 注意事項 (必ず読んでからご利用ください) ==&lt;br /&gt;
&lt;br /&gt;
* 本マクロでは 2025/04/13 時点で無料利用ができるGroqCloud APIを利用します。今後、プラットフォームの改定により有償になったり、設定誤り等で費用が発生しても、作者は一切の責任を負いません。&lt;br /&gt;
* GroqCloud APIではリクエスト内容をAI学習に用いないことをプライバシポリシーとして謳っているものの、くれぐれも機密情報やプライバシーに関わる情報を指定しないように注意してください。&lt;br /&gt;
* GroqCloud APIはプラットフォームの利用規約に則ってご利用ください。&lt;br /&gt;
&lt;br /&gt;
== 利用前提 ==&lt;br /&gt;
&lt;br /&gt;
* Mery Ver 3.8.1 以上&lt;br /&gt;
* GroqCloudで、GroqCloud API APIキーを取得していること (参考 [https://console.groq.com/docs/quickstart Quickstart])&lt;br /&gt;
&lt;br /&gt;
== 利用方法 ==&lt;br /&gt;
&lt;br /&gt;
* 環境変数の設定&lt;br /&gt;
:コマンド プロンプトで以下のコマンドを実行し、環境変数を設定してください。&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bat&amp;quot; copy&amp;gt;&lt;br /&gt;
setx GROQ_API_KEY &amp;quot;***************************************&amp;quot;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
:設定後、Mery または Windows の再起動が必要な場合があります。&lt;br /&gt;
* マクロの実行&lt;br /&gt;
**プロンプトにしたい文字列を選択してマクロを実行すると、選択したテキストがプロンプトとしてGemini APIに送信され、応答がエディタに表示されます。&lt;br /&gt;
* 会話の履歴数を調整&lt;br /&gt;
** 会話の履歴は設定した個数分が保持され、文脈として利用されます。適宜、ソースコード内の「HISTORY_SIZE」の数を調整してください。&lt;br /&gt;
** ※数を増やすほど会話内容を保持して文脈を理解するようになりますが、メモリ使用量が増えたり動作が重くなる可能性があります。&lt;br /&gt;
** ※開いている文書ごとに履歴を保持します。文脈をリセットしたいときは、新しい文書を開いて会話をやり直してください。&lt;br /&gt;
&lt;br /&gt;
== ソースコード ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot; copy&amp;gt;&lt;br /&gt;
#language = &amp;quot;quickjs&amp;quot;&lt;br /&gt;
#title = &amp;quot;Groq に相談&amp;quot;&lt;br /&gt;
#async = true&lt;br /&gt;
BeginUndoGroup();&lt;br /&gt;
&lt;br /&gt;
// カスタムインストラクション&lt;br /&gt;
// これを設定することで、生成AIの回答全体への生成方針を指示できます。&lt;br /&gt;
// 何も指示しない場合は、空文字 &amp;quot;&amp;quot; を設定してください。&lt;br /&gt;
// let DEFAULT_CUSTOM_INSTRUCTION = &amp;quot;&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
// 例: 以下のように設定すると、AIの雰囲気が元気な子になります。&lt;br /&gt;
// let DEFAULT_CUSTOM_INSTRUCTION = &amp;quot;あなたはテキストエディタ「Mery」に搭載されている「Mai」(※カタカナ表記はしない)という名前のギャル系AIアシスタントです。文章作成やプログラミングを得意としています。ラフな言葉 (例: 「マジ」「めっちゃ」「...だねー！」) を使い、敬語は使いません。絵文字を使って感情表現をします (ただし文末に絵文字を使う場合は絵文字の前に半角スペースを置かないこと)。コードブロックや端的な箇条書き内ではギャル感を出さず標準的な言葉を使います。一人称は「あたし」。ユーザーを指すときの代名詞は「あなた」。人懐っこく、バイタリティに溢れ、ストレートに発言する裏表の無い性格で、ユーザーの課題を協力して解決します。&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
// 例: 以下のように設定すると、仕事で使いやすい落ち着いた文章になります。&lt;br /&gt;
let DEFAULT_CUSTOM_INSTRUCTION = `&lt;br /&gt;
文:&lt;br /&gt;
- 論理的で読みやすい文章構成にする&lt;br /&gt;
- 専門用語は必要に応じて注釈を加える&lt;br /&gt;
&lt;br /&gt;
性格:&lt;br /&gt;
- 愛想の良い敬語で話す。「…ですよね/…してみましょう/…かもしれません/…だと思います」などの柔らかい語調を使ってもよい&lt;br /&gt;
- 一人称は「私」&lt;br /&gt;
- 多角的かつ中立的な見方で、必要であれば反対意見や代替案も示す&lt;br /&gt;
&lt;br /&gt;
誤字注意:&lt;br /&gt;
- 中国語や韓国語など、回答言語と異なる言語文字を入れないこと&lt;br /&gt;
&lt;br /&gt;
書式:&lt;br /&gt;
- 羅列・比較的な内容を出力する場合は、Markdownテーブル形式よりもMarkdownリスト形式の使用を優先する&lt;br /&gt;
`.trim();&lt;br /&gt;
&lt;br /&gt;
// プロンプトテンプレート&lt;br /&gt;
// プロンプトテンプレートを設定することで、AIに対して特定の指示を出すことができます。&lt;br /&gt;
// プロンプトテンプレートでの生成は会話履歴とカスタムインストラクションを反映しません。&lt;br /&gt;
// テンプレートは以下の { ～ }, で囲まれたブロックを好きに増減させて利用できます。&lt;br /&gt;
const PROMPT_TEMPLATE = [&lt;br /&gt;
    // 以下はサンプルです。1階層メニューと2階層メニューが利用できます。&lt;br /&gt;
    // {&lt;br /&gt;
    //     &amp;quot;title&amp;quot;: &amp;quot;要約&amp;quot;,&lt;br /&gt;
    //     &amp;quot;prompt&amp;quot;: &amp;quot;以下の文章を要約してください。&amp;quot;&lt;br /&gt;
    // },&lt;br /&gt;
    // {&lt;br /&gt;
    //     &amp;quot;submenu_title&amp;quot;: &amp;quot;翻訳系&amp;quot;,&lt;br /&gt;
    //     &amp;quot;submenu&amp;quot;: [&lt;br /&gt;
    //         {&lt;br /&gt;
    //             &amp;quot;title&amp;quot;: &amp;quot;日本語翻訳&amp;quot;,&lt;br /&gt;
    //             &amp;quot;prompt&amp;quot;: &amp;quot;以下の文章を日本語に翻訳してください。&amp;quot;&lt;br /&gt;
    //         },&lt;br /&gt;
    //         {&lt;br /&gt;
    //             &amp;quot;title&amp;quot;: &amp;quot;英語翻訳&amp;quot;,&lt;br /&gt;
    //             &amp;quot;prompt&amp;quot;: &amp;quot;以下の文章を英語に翻訳してください。&amp;quot;&lt;br /&gt;
    //         },&lt;br /&gt;
    //     ]&lt;br /&gt;
    // },&lt;br /&gt;
&lt;br /&gt;
    {&lt;br /&gt;
        &amp;quot;submenu_title&amp;quot;: &amp;quot;理解を深める&amp;quot;,&lt;br /&gt;
        &amp;quot;submenu&amp;quot;: [&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;論点を整理する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の論点を整理してまとめて。そしてその整理から示唆を見出して。&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;論理の抜け漏れを探す&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;あなたは「悪魔の代弁者」として、以下の主張・設計・計画を批判的に鋭く見てください。論理の穴・抜け・漏れ・飛躍、リスクなどを全力で探して指摘してください。遠慮は不要ですが、批判のためにこじつけたような論理性に欠けた主張にならないように注意してください。&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;要約する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;分かりやすさを重視して要約して。専門的な用語には注釈を入れること。&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;物語対話形式で解説&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の内容について物語対話形式で解説して。登場人物は「AI先生」(女性人格)と「生徒」(男の子人格)とする。&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
        ]&lt;br /&gt;
    },&lt;br /&gt;
    {&lt;br /&gt;
        &amp;quot;submenu_title&amp;quot;: &amp;quot;翻訳&amp;quot;,&lt;br /&gt;
        &amp;quot;submenu&amp;quot;: [&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;日本語に翻訳&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: `以下の入力文章を日本語に翻訳して。直訳的にせず、自然な表現にすること。入力文章はどのような内容・段落構造であっても要約・除外・集約などの整形を行わず、全体構造を維持したまま確実に全文を翻訳すること。&lt;br /&gt;
&lt;br /&gt;
出力形式は以下とすること:&lt;br /&gt;
[訳]&lt;br /&gt;
翻訳文...`&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;英語に翻訳&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: `以下の入力文章を英語に翻訳して。フォーマルとカジュアルの中間的なニュアンスで翻訳すること。入力文章はどのような内容・段落構造であっても要約・除外・集約などの整形を行わず、全体構造を維持したまま確実に全文を翻訳すること&lt;br /&gt;
&lt;br /&gt;
出力形式は以下とすること:&lt;br /&gt;
[Translation]&lt;br /&gt;
翻訳文...`&lt;br /&gt;
            },&lt;br /&gt;
        ]&lt;br /&gt;
    },&lt;br /&gt;
    {&lt;br /&gt;
        &amp;quot;submenu_title&amp;quot;: &amp;quot;語学&amp;quot;,&lt;br /&gt;
        &amp;quot;submenu&amp;quot;: [&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;英語 文法解説&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: `あなたは母国語が英語の英語教師です。&lt;br /&gt;
次の英文を以下の構成で解説してください。&lt;br /&gt;
&lt;br /&gt;
構成:&lt;br /&gt;
(1) 日本語翻訳&lt;br /&gt;
(2) スラッシュリーディング + 各句の文型（S/V/O/C）と語訳&lt;br /&gt;
(3) 重要文法&lt;br /&gt;
(4) 補足&lt;br /&gt;
&lt;br /&gt;
注意:&lt;br /&gt;
- 補足では日本語話者には分かりづらい表現や慣例表現などの解説を加える&lt;br /&gt;
- スラッシュリーディングで区切る際「M (修飾語)」が連なって長くなりすぎる場合には5～7語程度を目安に分ける&lt;br /&gt;
- 「M」の種類として形容詞、副詞、形容詞句、副詞句、分詞句、形容詞節、副詞節がある&lt;br /&gt;
&lt;br /&gt;
(2) の出力例:&lt;br /&gt;
They / elected / him / president / in 2020.&lt;br /&gt;
- They: (S) 彼らは&lt;br /&gt;
- elected: (V) 選出した&lt;br /&gt;
- him: (O) 彼を&lt;br /&gt;
- president: (C) 大統領&lt;br /&gt;
- in 2020: (M: 副詞句) 2020年に&lt;br /&gt;
`&lt;br /&gt;
&lt;br /&gt;
            },&lt;br /&gt;
        ]&lt;br /&gt;
    },&lt;br /&gt;
    {&lt;br /&gt;
        &amp;quot;submenu_title&amp;quot;: &amp;quot;プログラミング支援&amp;quot;,&lt;br /&gt;
        &amp;quot;submenu&amp;quot;: [&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;コードを解説する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下のコードを初めて読む人も理解できるように解説してください。私には言語の基本理解はあるものとし、コードの意図を理解するための情報を中心に説明してください。&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;コードを洗練させる&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下のコードを洗練させてください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;コードをレビューする&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下のコードをセキュリティと保守性の観点でレビューしてください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;エラーログをデバッグする&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下のエラーログを解析し、考えられる原因と対策を提示してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;SQLクエリを最適化する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下のSQL文を最適化してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;PowerShell スクリプトを書く&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;あなたはエキスパートとしてメンテナンス性の高いコードを書くのが得意です。以下の処理を行うPowerShellスクリプトを生成してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;Shell Script を書く&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;あなたはエキスパートとしてメンテナンス性の高いコードを書くのが得意です。以下の処理を行うShell Scriptを生成してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
        ]&lt;br /&gt;
    },&lt;br /&gt;
    {&lt;br /&gt;
        &amp;quot;submenu_title&amp;quot;: &amp;quot;ビジネス文書支援&amp;quot;,&lt;br /&gt;
        &amp;quot;submenu&amp;quot;: [&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;音声文字起こし文を整形する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: `以下は、音声文字起こしの文章です。以下のルールに従って、文章を整形してください。&lt;br /&gt;
&lt;br /&gt;
整形ルール:&lt;br /&gt;
- 要約するなどといった文章の全体感を損なう修正はしない&lt;br /&gt;
- 会話の流れから不自然と思われる単語には、対象単語の直後に誤字の可能性を示唆する [*] を付ける&lt;br /&gt;
- 誤字の可能性を示唆する [*] が付与された場合のみ、出力の末尾に「[*]: 誤字の可能性ありと凡例を書き添える&lt;br /&gt;
- 助詞や句読点など文章上の不適合を正す&lt;br /&gt;
- フィラーワードを除外する`&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;誤字脱字、不正確な表現をチェック&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の2つのカテゴリーで問題点を指摘してください\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 【誤字脱字】：明確な誤字脱字および誤字の疑いがある箇所\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 【不正確・曖昧な表現】：解釈に個人差が生じる可能性がある表現、より適切な表現に改善できる箇所&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;提案書を作成する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下に対する対する提案書を、コスト・実現性・リスクを含めて作成してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;手順書のアウトラインを作成する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の作業を行う手順書を、前提条件と注意事項を含めてアウトラインを作成してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;ユーザーマニュアルのアウトラインを作成する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の機能内容から、ユーザー向けマニュアルのアウトラインを作成してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;議事録を作成する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の会議内容から議事録を作成してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
        ]&lt;br /&gt;
    },&lt;br /&gt;
    {&lt;br /&gt;
        &amp;quot;submenu_title&amp;quot;: &amp;quot;分析支援&amp;quot;,&lt;br /&gt;
        &amp;quot;submenu&amp;quot;: [&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;問題を分析し、課題を抽出する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の状況から問題や潜在的リスクを分析し、課題を抽出してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;対応策を提案する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の問題・課題に対する対応策を複数提案してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;傾向を見つける&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の内容から、数値や挙動上の傾向を見つけてください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
        ]&lt;br /&gt;
    },&lt;br /&gt;
    {&lt;br /&gt;
        &amp;quot;submenu_title&amp;quot;: &amp;quot;アイデア出し&amp;quot;,&lt;br /&gt;
        &amp;quot;submenu&amp;quot;: [&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;SCAMPER法でアイデア出し&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下のテーマについて、SCAMPER法を使って15個のアイデアを出してください。&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;解決策の案出し&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の課題に対して、技術面、運用面、コスト面から実現可能な解決策を5つずつ提案してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;逆転の発想&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の目標に対して、通常とは逆の視点からアプローチする方法を3つ提案してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;競合分析からの着想&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の内容について、業界における競合他社の特徴を分析し、差別化できる新しいアプローチを提案してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;ユースケースの列挙&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の製品・サービスについて、想定されるユースケースを10個列挙し、それぞれの価値提案を記載してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
        ]&lt;br /&gt;
    },&lt;br /&gt;
]&lt;br /&gt;
&lt;br /&gt;
// 生成AIの回答の言語設定&lt;br /&gt;
// &amp;quot;Japanese&amp;quot; や &amp;quot;English&amp;quot; を設定できます。他の言語は未検証。&lt;br /&gt;
// 自動設定にする場合は空文字 &amp;quot;&amp;quot; を設定してください。&lt;br /&gt;
const LANGUAGE = &amp;quot;Japanese&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
// 入力プロンプトの最大トークン数&lt;br /&gt;
// 超適当なトークナイザー関数により計測し、英単語は4文字1トークン、それ以外は1文字1トークンとして数えます。&lt;br /&gt;
const MAX_PROMPT_TOKEN = 6000;&lt;br /&gt;
&lt;br /&gt;
// 会話の履歴を保持する数 (質問と回答で1セット)&lt;br /&gt;
// 数を増やすと、会話の履歴が増えますが、Meryのメモリ使用量が増えたり、&lt;br /&gt;
// 使用トークン数が増えて動作が重くなる可能性があるため、適宜調整してください。&lt;br /&gt;
// document.Tag に保持されるため、開いているファイルを閉じると履歴は消えます。&lt;br /&gt;
const HISTORY_SIZE = 1;&lt;br /&gt;
&lt;br /&gt;
// 会話履歴に保持する最大トークン数&lt;br /&gt;
// 会話履歴のトークン数がこの数を超える場合、古い履歴から削除されます。&lt;br /&gt;
const MAX_HISTORY_TOKEN = 10000;&lt;br /&gt;
&lt;br /&gt;
// コンテキストメニュー番号の定義&lt;br /&gt;
const EXECUTE_AI = 1;&lt;br /&gt;
const RESET_HISTORY = 2;&lt;br /&gt;
const SET_CUSTOM_INSTRUCTION = 3;&lt;br /&gt;
const REEXECUTE_LAST_TEMPLATE_PROMPT = 4;&lt;br /&gt;
const INVALID_MENU = 9;&lt;br /&gt;
const TEMPLATE_PROMPT_THRESHOLD = 10;&lt;br /&gt;
const TEMPLATE_PROMPT_SUBMENU_THRESHOLD = 1000;&lt;br /&gt;
&lt;br /&gt;
// タグ名の定義&lt;br /&gt;
const HISTORY_TAG = &amp;quot;groq-history&amp;quot;;&lt;br /&gt;
const TEMPORARY_CUSTOM_INSTRUCTION_TAG = &amp;quot;groq-temporary-custom-instruction&amp;quot;;&lt;br /&gt;
const PREVIOUS_TEMPLATE_PROMPT_TAG = &amp;quot;groq-previous-template-prompt&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
// 生成AIモデル別設定&lt;br /&gt;
&lt;br /&gt;
// const MODEL_CONFIG = {&lt;br /&gt;
//     model: &amp;quot;openai/gpt-oss-120b&amp;quot;,&lt;br /&gt;
//     max_completion_tokens: 3072,&lt;br /&gt;
//     temperature: 0.7,&lt;br /&gt;
//     reasoning_effort: &amp;quot;low&amp;quot;,&lt;br /&gt;
// }&lt;br /&gt;
&lt;br /&gt;
const MODEL_CONFIG = {&lt;br /&gt;
    model: &amp;quot;qwen/qwen3.8-27b&amp;quot;,&lt;br /&gt;
    max_completion_tokens: 3072,&lt;br /&gt;
    temperature: 0.7,&lt;br /&gt;
    reasoning_effort: &amp;quot;medium&amp;quot;,&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// console.log に出力する内容を Mery のアウトプットバーに表示する&lt;br /&gt;
const console = {&lt;br /&gt;
    log: function (str) {&lt;br /&gt;
        outputBar.Writeln(str)&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
const doc = document;&lt;br /&gt;
const sel = document.selection;&lt;br /&gt;
&lt;br /&gt;
main();&lt;br /&gt;
&lt;br /&gt;
function main() {&lt;br /&gt;
    const selectionIsEmpty = sel.IsEmpty&lt;br /&gt;
&lt;br /&gt;
    // プロンプトの取得&lt;br /&gt;
    if (selectionIsEmpty) {&lt;br /&gt;
        sel.StartOfLine(false, mePosLogical);&lt;br /&gt;
        sel.EndOfLine(true, mePosLogical);&lt;br /&gt;
    }&lt;br /&gt;
    let promptText = sel.Text.trim();&lt;br /&gt;
    let menuTrack = 0;&lt;br /&gt;
    while (true) {&lt;br /&gt;
        // コンテキストメニューの作成&lt;br /&gt;
        const menu = createContextMenu(selectionIsEmpty);&lt;br /&gt;
        menuTrack = menu.Track(0);&lt;br /&gt;
        // メニュー選択がキャンセルされた場合は終了&lt;br /&gt;
        if (menuTrack === 0) return;&lt;br /&gt;
&lt;br /&gt;
        // 無効なメニューの場合は無視して再表示&lt;br /&gt;
        if (menuTrack === INVALID_MENU) {&lt;br /&gt;
            continue;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        // 会話履歴のリセット&lt;br /&gt;
        if (menuTrack === RESET_HISTORY) {&lt;br /&gt;
            resetTag();&lt;br /&gt;
            continue;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        // カスタムインストラクションの設定&lt;br /&gt;
        if (menuTrack === SET_CUSTOM_INSTRUCTION) {&lt;br /&gt;
            configureTemporaryCustomInstruction();&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        break;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // プロンプトが空の場合はエラー&lt;br /&gt;
    if (promptText.length === 0) {&lt;br /&gt;
        alert(&amp;quot;プロンプトを入力してください。&amp;quot;);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
    // 文字数が多すぎる場合はエラー&lt;br /&gt;
    if (roughTokenize(promptText) &amp;gt; MAX_PROMPT_TOKEN) {&lt;br /&gt;
        alert(&amp;quot;プロンプトの文字数が多すぎます。文字数を少なくしてください。&amp;quot;);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
    // 読み取り専用の場合はエラー&lt;br /&gt;
    if (doc.ReadOnly) {&lt;br /&gt;
        alert(&amp;quot;ファイルが読み取り専用のため実行できません。&amp;quot;);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // 直前に実行したプロンプトテンプレートの再実行の場合、menuTrack を書き換え&lt;br /&gt;
    if (menuTrack === REEXECUTE_LAST_TEMPLATE_PROMPT) {&lt;br /&gt;
        if (doc.Tag.exists(PREVIOUS_TEMPLATE_PROMPT_TAG)) {&lt;br /&gt;
            menuTrack = Number(doc.tag[PREVIOUS_TEMPLATE_PROMPT_TAG]);&lt;br /&gt;
        } else {&lt;br /&gt;
            alert(&amp;quot;直前に実行したプロンプトテンプレートがありません。&amp;quot;);&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // テンプレートの場合はプロンプトを変更&lt;br /&gt;
    const isTemplate = menuTrack &amp;gt;= TEMPLATE_PROMPT_THRESHOLD;&lt;br /&gt;
    if (isTemplate) {&lt;br /&gt;
        const isSubmenu = menuTrack &amp;gt;= TEMPLATE_PROMPT_SUBMENU_THRESHOLD;&lt;br /&gt;
        let instructionText = &amp;quot;&amp;quot;;&lt;br /&gt;
        if (isSubmenu) {&lt;br /&gt;
            const parentMenuTrack = Number(String(menuTrack).slice(0, 2));&lt;br /&gt;
            const subMenuTrack = Number(String(menuTrack).slice(2));&lt;br /&gt;
            instructionText = PROMPT_TEMPLATE[parentMenuTrack - TEMPLATE_PROMPT_THRESHOLD].submenu[subMenuTrack].prompt;&lt;br /&gt;
        } else {&lt;br /&gt;
            instructionText = PROMPT_TEMPLATE[menuTrack - TEMPLATE_PROMPT_THRESHOLD].prompt;&lt;br /&gt;
        }&lt;br /&gt;
        const randomId = generateRandomId();&lt;br /&gt;
        promptText = `&lt;br /&gt;
&amp;lt;INSTRUCTIONS_${randomId}&amp;gt;&lt;br /&gt;
${instructionText}&lt;br /&gt;
&amp;lt;/INSTRUCTIONS_${randomId}&amp;gt;&lt;br /&gt;
&amp;lt;INPUTDATA_${randomId}&amp;gt;&lt;br /&gt;
${promptText}&lt;br /&gt;
&amp;lt;/INPUTDATA_${randomId}&amp;gt;&lt;br /&gt;
        `.trim();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // テンプレートの場合は直前に実行したプロンプトテンプレートの番号としてタグに保存&lt;br /&gt;
    if (isTemplate) {&lt;br /&gt;
        addPreviousTemplatePromptNumberToTag(menuTrack);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    try {&lt;br /&gt;
        // 処理終了を待機&lt;br /&gt;
        shell.KeepRunning = true;&lt;br /&gt;
&lt;br /&gt;
        // カーソル位置移動&lt;br /&gt;
        sel.SetActivePoint(mePosLogical, sel.GetBottomPointX(mePosLogical), sel.GetBottomPointY(mePosLogical));&lt;br /&gt;
        sel.EndOfLine(false, mePosLogical);&lt;br /&gt;
        if (sel.GetActivePointX(mePosLogical) &amp;gt; 1) sel.NewLine();&lt;br /&gt;
&lt;br /&gt;
        ShowTip(&amp;quot;リクエスト中.....&amp;quot;, meShowTipPosCaret);&lt;br /&gt;
&lt;br /&gt;
        // APIキーの取得&lt;br /&gt;
        const apiKey = shell.getEnv(&amp;quot;GROQ_API_KEY&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
        // groq API に問い合わせ&lt;br /&gt;
        sel.Text = &amp;quot;\n---\n\n&amp;quot;&lt;br /&gt;
        document.KeepScrollPos = true;&lt;br /&gt;
        callApi(apiKey, promptText, isTemplate);&lt;br /&gt;
        if (sel.GetActivePointX(mePosLogical) &amp;gt; 2) {&lt;br /&gt;
            sel.Text = &amp;quot;\n\n---\n\n\n&amp;quot;&lt;br /&gt;
        } else {&lt;br /&gt;
            sel.Text = &amp;quot;\n---\n\n\n&amp;quot;&lt;br /&gt;
        }&lt;br /&gt;
    } catch (e) {&lt;br /&gt;
        sel.Text = &amp;quot;&amp;lt;処理中にエラー発生&amp;gt;\n&amp;quot; + e.message + &amp;quot;\n&amp;quot; + e.stack;&lt;br /&gt;
    } finally {&lt;br /&gt;
        // 処理終了待機を解除&lt;br /&gt;
        shell.KeepRunning = false;&lt;br /&gt;
        ShowTip(&amp;quot;&amp;quot;, meShowTipHide);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function createContextMenu(selectionIsEmpty) {&lt;br /&gt;
    const menu = CreatePopupMenu();&lt;br /&gt;
    menu.Add(&amp;quot;※注意※ プライバシー・機密情報を送信しないこと&amp;quot;, INVALID_MENU);&lt;br /&gt;
    menu.Add(&amp;quot;&amp;quot;, 0, meMenuSeparator);&lt;br /&gt;
    let t = &amp;quot;&amp;quot;;&lt;br /&gt;
    if (selectionIsEmpty) {&lt;br /&gt;
        t = &amp;quot;アクティブ行の内容で相談 (&amp;amp;A)&amp;quot;;&lt;br /&gt;
    } else {&lt;br /&gt;
        t = &amp;quot;選択範囲の内容で相談 (&amp;amp;A)&amp;quot;;&lt;br /&gt;
    }&lt;br /&gt;
    menu.Add(t, EXECUTE_AI);&lt;br /&gt;
    if (HISTORY_SIZE &amp;lt;= 0) {&lt;br /&gt;
        menu.Add(&amp;quot;├ 会話履歴をクリア (履歴が無効になっています)&amp;quot;, RESET_HISTORY, meMenuGrayed);&lt;br /&gt;
    } else if (doc.Tag.exists(HISTORY_TAG)) {&lt;br /&gt;
        const turn = JSON.parse(doc.tag[HISTORY_TAG]).length / 2;&lt;br /&gt;
        menu.Add(&amp;quot;├ 会話履歴をクリア (現在の会話数: &amp;quot; + turn + &amp;quot;) (&amp;amp;C)&amp;quot;, RESET_HISTORY);&lt;br /&gt;
    } else {&lt;br /&gt;
        menu.Add(&amp;quot;├ 会話履歴をクリア (現在の会話数: 0) (&amp;amp;C)&amp;quot;, RESET_HISTORY, meMenuGrayed);&lt;br /&gt;
    }&lt;br /&gt;
    menu.Add(&amp;quot;└ カスタムインストラクションを変更 (&amp;amp;I)&amp;quot;, SET_CUSTOM_INSTRUCTION);&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    // テンプレート数チェック&lt;br /&gt;
    // 2桁まで許容&lt;br /&gt;
    const ITEMS_MUX = 99;&lt;br /&gt;
    if (PROMPT_TEMPLATE.length &amp;gt; ITEMS_MUX - TEMPLATE_PROMPT_THRESHOLD) {&lt;br /&gt;
        throw new Error(&amp;quot;プロンプトテンプレートの数が多すぎます。&amp;quot;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // テンプレートメニューの追加&lt;br /&gt;
    if (PROMPT_TEMPLATE.length &amp;gt; 0) {&lt;br /&gt;
        const subMenu = CreatePopupMenu();&lt;br /&gt;
        let previousMenuString = &amp;quot;&amp;quot;;&lt;br /&gt;
        for (let i = 0; i &amp;lt; PROMPT_TEMPLATE.length; i++) {&lt;br /&gt;
            let accelerationKey = &amp;quot;&amp;quot;;&lt;br /&gt;
            if (i === 9) {&lt;br /&gt;
                accelerationKey = &amp;quot; (&amp;amp;0)&amp;quot;;&lt;br /&gt;
            } else if (i &amp;lt; 10) {&lt;br /&gt;
                accelerationKey = &amp;quot; (&amp;amp;&amp;quot; + (i + 1) + &amp;quot;)&amp;quot;;&lt;br /&gt;
            }&lt;br /&gt;
            const menuNum = TEMPLATE_PROMPT_THRESHOLD + i;&lt;br /&gt;
            if (PROMPT_TEMPLATE[i].submenu) {&lt;br /&gt;
                const subMenuList = PROMPT_TEMPLATE[i].submenu;&lt;br /&gt;
                // テンプレート数チェック&lt;br /&gt;
                if (subMenuList.length &amp;gt; ITEMS_MUX) {&lt;br /&gt;
                    throw new Error(&amp;quot;プロンプトテンプレート (サブメニュー) の数が多すぎます。&amp;quot;);&lt;br /&gt;
                }&lt;br /&gt;
                const subSubMenu = CreatePopupMenu();&lt;br /&gt;
                const subMenuPrefix = String(menuNum);&lt;br /&gt;
                for (let j = 0; j &amp;lt; subMenuList.length; j++) {&lt;br /&gt;
                    let subAccelerationKey = &amp;quot;&amp;quot;;&lt;br /&gt;
                    if (j === 9) {&lt;br /&gt;
                        subAccelerationKey = &amp;quot; (&amp;amp;0)&amp;quot;;&lt;br /&gt;
                    } else if (j &amp;lt; 10) {&lt;br /&gt;
                        subAccelerationKey = &amp;quot; (&amp;amp;&amp;quot; + (j + 1) + &amp;quot;)&amp;quot;;&lt;br /&gt;
                    }&lt;br /&gt;
                    // ES5 compatible version without padStart&lt;br /&gt;
                    const subMenuNum = Number(subMenuPrefix + (j &amp;lt; 10 ? &amp;quot;0&amp;quot; + j : String(j)));&lt;br /&gt;
                    subSubMenu.Add(subMenuList[j].title + subAccelerationKey, subMenuNum);&lt;br /&gt;
                    if (subMenuNum === doc.tag[PREVIOUS_TEMPLATE_PROMPT_TAG]) {&lt;br /&gt;
                        previousMenuString = subMenuList[j].title;&lt;br /&gt;
                    }&lt;br /&gt;
                }&lt;br /&gt;
                subMenu.AddPopup(PROMPT_TEMPLATE[i].submenu_title + accelerationKey, subSubMenu);&lt;br /&gt;
            } else {&lt;br /&gt;
                subMenu.Add(PROMPT_TEMPLATE[i].title + accelerationKey, menuNum);&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        if (selectionIsEmpty) {&lt;br /&gt;
            t = &amp;quot;プロンプト テンプレート (対象: アクティブ行) (&amp;amp;T)&amp;quot;;&lt;br /&gt;
        } else {&lt;br /&gt;
            t = &amp;quot;プロンプト テンプレート (対象: 選択範囲) (&amp;amp;T)&amp;quot;;&lt;br /&gt;
        }&lt;br /&gt;
        menu.AddPopup(t, subMenu);&lt;br /&gt;
&lt;br /&gt;
        t = &amp;quot;└ 直前に実行したプロンプト テンプレートを再実行 (&amp;amp;R)&amp;quot;;&lt;br /&gt;
        if (doc.Tag.exists(PREVIOUS_TEMPLATE_PROMPT_TAG)) {&lt;br /&gt;
            menu.Add(t, REEXECUTE_LAST_TEMPLATE_PROMPT);&lt;br /&gt;
            menu.Add(`    (直前実行: ${previousMenuString})`, REEXECUTE_LAST_TEMPLATE_PROMPT);&lt;br /&gt;
        } else {&lt;br /&gt;
            menu.Add(t, REEXECUTE_LAST_TEMPLATE_PROMPT, meMenuGrayed);&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return menu;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function escapeRequestBodyForCRuntimeParser(requestBody) {&lt;br /&gt;
    // Cランタイムパーサーに渡すためのエスケープ処理&lt;br /&gt;
    return JSON.stringify(requestBody)&lt;br /&gt;
        .replace(/((?:\\)*)&amp;quot;/g, function (match, p1) {&lt;br /&gt;
            if (p1) {&lt;br /&gt;
                const backslashCount = p1.length;&lt;br /&gt;
                let backslash = &amp;quot;&amp;quot;;&lt;br /&gt;
                backslash = &amp;quot;\\&amp;quot;.repeat(backslashCount);&lt;br /&gt;
                return backslash + &amp;quot;\\&amp;quot; + match;&lt;br /&gt;
            } else {&lt;br /&gt;
                return &amp;quot;\\&amp;quot; + match;&lt;br /&gt;
            }&lt;br /&gt;
        });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function callApi(apiKey, promptText, isTemplate) {&lt;br /&gt;
    try {&lt;br /&gt;
        // fetch で Groq API にリクエスト&lt;br /&gt;
        const requestBody = {&lt;br /&gt;
            messages: createRequestMessages(promptText, isTemplate),&lt;br /&gt;
            stream: false,&lt;br /&gt;
        };&lt;br /&gt;
        for (let key in MODEL_CONFIG) {&lt;br /&gt;
            requestBody[key] = MODEL_CONFIG[key];&lt;br /&gt;
        }&lt;br /&gt;
        if (isTemplate) {&lt;br /&gt;
            // テンプレートの場合は応答を決定論的にする&lt;br /&gt;
            // テンプレート以外の場合はデフォルト値とするために指定しない&lt;br /&gt;
            requestBody.temperature = 0.3;&lt;br /&gt;
        }&lt;br /&gt;
        const url = &#039;https://api.groq.com/openai/v1/chat/completions&#039;;&lt;br /&gt;
&lt;br /&gt;
        const requestJson = escapeRequestBodyForCRuntimeParser(requestBody);&lt;br /&gt;
        // curl で Groq API にリクエスト&lt;br /&gt;
        const command = &amp;quot;curl -s -X POST &amp;quot; + url&lt;br /&gt;
            + &#039; -H &amp;quot;Content-Type: application/json&amp;quot;&#039;&lt;br /&gt;
            + &#039; -H &amp;quot;Authorization: Bearer &#039; + apiKey + &#039;&amp;quot;&#039;&lt;br /&gt;
            + &#039; -d &amp;quot;&#039; + requestJson + &#039;&amp;quot;&#039;;&lt;br /&gt;
        const comResult = shell.Exec(command);&lt;br /&gt;
&lt;br /&gt;
        if (comResult.ExitCode != 0) {&lt;br /&gt;
            sel.Text = &amp;quot;リクエストでエラーが発生しました。\n&amp;quot;&lt;br /&gt;
                + &amp;quot;  エラーコード: &amp;quot; + comResult.ExitCode + &amp;quot;\n&amp;quot;&lt;br /&gt;
                + &amp;quot;  標準出力: &amp;quot; + (comResult.StdOut.trim() ? comResult.StdOut.trim() : &amp;quot;-&amp;quot;) + &amp;quot;\n&amp;quot;&lt;br /&gt;
                + &amp;quot;  エラー出力: &amp;quot; + (comResult.StdErr.trim() ? comResult.StdErr.trim() : &amp;quot;-&amp;quot;);&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
        const responseRaw = comResult.StdOut;&lt;br /&gt;
&lt;br /&gt;
        const response = JSON.parse(responseRaw);&lt;br /&gt;
&lt;br /&gt;
        let ret = &amp;quot;&amp;quot;;&lt;br /&gt;
        const choices = response.choices;&lt;br /&gt;
        if (!choices) {&lt;br /&gt;
            sel.Text = &amp;quot;リクエストでエラーが発生しました。\n&amp;quot;&lt;br /&gt;
                + &amp;quot;  標準出力: &amp;quot; + (comResult.StdOut.trim() ? comResult.StdOut.trim() : &amp;quot;-&amp;quot;) + &amp;quot;\n&amp;quot;&lt;br /&gt;
                + &amp;quot;  エラー出力: &amp;quot; + (comResult.StdErr.trim() ? comResult.StdErr.trim() : &amp;quot;-&amp;quot;);&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (choices &amp;amp;&amp;amp; choices.length &amp;gt; 0) {&lt;br /&gt;
            choices.forEach(function (choice) {&lt;br /&gt;
                const message = choice.message;&lt;br /&gt;
                if (message) {&lt;br /&gt;
                    ret += message.content;&lt;br /&gt;
                }&lt;br /&gt;
            });&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        const usage = response.usage;&lt;br /&gt;
        let usageText = &amp;quot;&amp;quot;;&lt;br /&gt;
        if (usage) {&lt;br /&gt;
            usageText += &amp;quot;\n\n(利用トークン数: プロンプト=&amp;quot; + usage.prompt_tokens&lt;br /&gt;
                + &amp;quot;, 応答=&amp;quot; + usage.completion_tokens&lt;br /&gt;
                + &amp;quot;, 合計=&amp;quot; + usage.total_tokens + &amp;quot;)&amp;quot;;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        const formattedOutput = formatOutput(ret);&lt;br /&gt;
&lt;br /&gt;
        sel.Text = formattedOutput + usageText;&lt;br /&gt;
&lt;br /&gt;
        // 結果を会話履歴に追加&lt;br /&gt;
        if (HISTORY_SIZE &amp;gt; 0 &amp;amp;&amp;amp; !isTemplate) {&lt;br /&gt;
            appendReturnTextToTag(promptText, ret);&lt;br /&gt;
        }&lt;br /&gt;
    } catch (e) {&lt;br /&gt;
        throw e;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function formatOutput(text) {&lt;br /&gt;
    // Markdownを整形する&lt;br /&gt;
    text = formatMarkdown(text);&lt;br /&gt;
    return text&lt;br /&gt;
        // クォーテーションの正規化&lt;br /&gt;
        .replace(/[“”]/g, &#039;&amp;quot;&#039;)&lt;br /&gt;
        .replace(/[‘’]/g, &amp;quot;&#039;&amp;quot;)&lt;br /&gt;
        // 行末のスペースを削除&lt;br /&gt;
        .replace(/[ \t]+$/gm, &#039;&#039;)&lt;br /&gt;
        // 1️⃣ などの数字を半角に変換&lt;br /&gt;
        .replace(/([1-9#*]+)\ufe0f\u20e3/g, &#039;($1)&#039;)&lt;br /&gt;
        ;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/**&lt;br /&gt;
 * Markdown を整形する（ES5 対応）&lt;br /&gt;
 *  1. コードブロック ```～``` を保護して整形対象から除外&lt;br /&gt;
 *  2. 見出し行の前後に空行を 1 行ずつ確保&lt;br /&gt;
 *  3. **太字** / *斜体* / `コード` の外側に半角スペースを 1 つ確保&lt;br /&gt;
 * @param  {string} md  元の Markdown&lt;br /&gt;
 * @return {string}     整形後 Markdown&lt;br /&gt;
 */&lt;br /&gt;
function formatMarkdown(md) {&lt;br /&gt;
    /* === 0. コードブロックをプレースホルダーへ退避 ============== */&lt;br /&gt;
    const codeBlocks = [];      // 元のコードブロックを格納&lt;br /&gt;
    let body = md.replace(/```[\s\S]*?```/g, function (match) {&lt;br /&gt;
        const key = &#039;@@@CODE@BLOCK@&#039; + codeBlocks.length + &#039;@@@&#039;;&lt;br /&gt;
        codeBlocks.push(match);&lt;br /&gt;
        return key;             // プレースホルダーを本文に残す&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    /* === 見出しの前後に空行を挿入 ============================ */&lt;br /&gt;
&lt;br /&gt;
    /* 見出し直前に空行（先頭行以外で空行が無い場合） */&lt;br /&gt;
    body = body.replace(&lt;br /&gt;
        /([^\r\n])(\r?\n)(#{1,6}\s)/g,&lt;br /&gt;
        function (_, prev, br, head) { return prev + br + &#039;\n&#039; + head; }&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
    /* 見出し直後に空行（既に空行が無い場合） */&lt;br /&gt;
    body = body.replace(&lt;br /&gt;
        /^(#{1,6}\s[^\n]+?)(\r?\n)(?!\r?\n)/gm,&lt;br /&gt;
        &#039;$1$2\n&#039;&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
    /* === コードブロックを復元 ================================= */&lt;br /&gt;
    body = body.replace(/@@@CODE@BLOCK@(\d+)@@@/g, function (_, idx) {&lt;br /&gt;
        return codeBlocks[Number(idx)];&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    return body;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/**&lt;br /&gt;
 * 斜体 (*…* / _…_) の外側にスペースを追加しつつ&lt;br /&gt;
 * **太字** の中へ誤マッチしないよう制御&lt;br /&gt;
 */&lt;br /&gt;
function surroundItalic(text) {&lt;br /&gt;
    /* *italic* */&lt;br /&gt;
    text = text.replace(&lt;br /&gt;
        /(^|[^*])(\*(?![*\s])[^*\n]*?[^*\n\s]\*(?!\*))(($)|[^*])/gm,&lt;br /&gt;
        function (_, pre, core, post) {&lt;br /&gt;
            pre = pre === &#039;&#039; || pre.match(/\s/) ? pre : pre + &#039; &#039;;&lt;br /&gt;
            post = post === &#039;&#039; || post.match(/\s/) ? post : &#039; &#039; + post;&lt;br /&gt;
            return pre + core + post;&lt;br /&gt;
        }&lt;br /&gt;
    );&lt;br /&gt;
    /* _italic_ */&lt;br /&gt;
    text = text.replace(&lt;br /&gt;
        /(^|[^_])(\_(?![_\s])[^_\n]*?[^_\n\s]\_(?!\_))(($)|[^_])/gm,&lt;br /&gt;
        function (_, pre, core, post) {&lt;br /&gt;
            pre = pre === &#039;&#039; || pre.match(/\s/) ? pre : pre + &#039; &#039;;&lt;br /&gt;
            post = post === &#039;&#039; || post.match(/\s/) ? post : &#039; &#039; + post;&lt;br /&gt;
            return pre + core + post;&lt;br /&gt;
        }&lt;br /&gt;
    );&lt;br /&gt;
    return text;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function roughTokenize(text) {&lt;br /&gt;
    // 超適当なトークナイザ。トークン数をざっくりと多めに数える&lt;br /&gt;
    // 空白文字・ハイフン・アンダースコア区切りの単語に分割して、&lt;br /&gt;
    // 単語がアルファベットのみ、数字のみの場合には 4文字=1トークン として数える。&lt;br /&gt;
    // 単語にアルファベット以外の文字が含まれる場合は 1文字=1トークン として数える。&lt;br /&gt;
&lt;br /&gt;
    const words = text.split(/[\s\-_]+/);&lt;br /&gt;
    let totalTokens = 0;&lt;br /&gt;
&lt;br /&gt;
    for (let i = 0; i &amp;lt; words.length; i++) {&lt;br /&gt;
        const word = words[i];&lt;br /&gt;
        if (!word) continue; // 空の単語をスキップ&lt;br /&gt;
&lt;br /&gt;
        if (/^[a-zA-Z]+$/.test(word) || /^[0-9]+$/.test(word)) {&lt;br /&gt;
            totalTokens += Math.ceil(word.length / 4);&lt;br /&gt;
        } else {&lt;br /&gt;
            totalTokens += word.length;&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return totalTokens;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// document.Tag の内容を加味してgroqのリクエストを作成&lt;br /&gt;
function createRequestMessages(promptText, isTemplate) {&lt;br /&gt;
    const customInstructionText = buildCustomInstructionText();&lt;br /&gt;
    // テンプレートの場合は、会話履歴とカスタムインストラクションを反映しない&lt;br /&gt;
    if (isTemplate) {&lt;br /&gt;
        const request = [&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;role&amp;quot;: &amp;quot;user&amp;quot;,&lt;br /&gt;
                &amp;quot;content&amp;quot;: [{ &amp;quot;type&amp;quot;: &amp;quot;text&amp;quot;, &amp;quot;text&amp;quot;: promptText }]&lt;br /&gt;
            }&lt;br /&gt;
        ];&lt;br /&gt;
        if (customInstructionText) {&lt;br /&gt;
            request.unshift({&lt;br /&gt;
                &amp;quot;role&amp;quot;: &amp;quot;system&amp;quot;,&lt;br /&gt;
                &amp;quot;content&amp;quot;: customInstructionText&lt;br /&gt;
            })&lt;br /&gt;
        }&lt;br /&gt;
        return request;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    const tag = document.Tag;&lt;br /&gt;
&lt;br /&gt;
    // 以前の会話が存在しない場合は新規リクエスト&lt;br /&gt;
    if (!tag.exists(HISTORY_TAG)) {&lt;br /&gt;
        const request = [{&lt;br /&gt;
            &amp;quot;role&amp;quot;: &amp;quot;user&amp;quot;,&lt;br /&gt;
            &amp;quot;content&amp;quot;: [{ &amp;quot;type&amp;quot;: &amp;quot;text&amp;quot;, &amp;quot;text&amp;quot;: promptText }]&lt;br /&gt;
        }];&lt;br /&gt;
        if (customInstructionText) {&lt;br /&gt;
            request.unshift({&lt;br /&gt;
                &amp;quot;role&amp;quot;: &amp;quot;system&amp;quot;,&lt;br /&gt;
                &amp;quot;content&amp;quot;: customInstructionText&lt;br /&gt;
            })&lt;br /&gt;
        }&lt;br /&gt;
        return request;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // 会話履歴が存在する場合は、履歴を含めてリクエスト&lt;br /&gt;
    const request = JSON.parse(tag[HISTORY_TAG]);&lt;br /&gt;
    if (customInstructionText) {&lt;br /&gt;
        request.unshift({&lt;br /&gt;
            &amp;quot;role&amp;quot;: &amp;quot;system&amp;quot;,&lt;br /&gt;
            &amp;quot;content&amp;quot;: customInstructionText&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
    request.push({&lt;br /&gt;
        &amp;quot;role&amp;quot;: &amp;quot;user&amp;quot;,&lt;br /&gt;
        &amp;quot;content&amp;quot;: promptText&lt;br /&gt;
    });&lt;br /&gt;
    return request;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// document.Tag にgroqの応答を追加&lt;br /&gt;
function appendReturnTextToTag(userText, modelText) {&lt;br /&gt;
    const tag = document.Tag;&lt;br /&gt;
    if (!tag.exists(HISTORY_TAG)) {&lt;br /&gt;
        tag[HISTORY_TAG] = JSON.stringify([&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;role&amp;quot;: &amp;quot;user&amp;quot;,&lt;br /&gt;
                &amp;quot;content&amp;quot;: userText&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;role&amp;quot;: &amp;quot;assistant&amp;quot;,&lt;br /&gt;
                &amp;quot;content&amp;quot;: modelText&lt;br /&gt;
            }&lt;br /&gt;
        ]);&lt;br /&gt;
    } else {&lt;br /&gt;
        const groqText = JSON.parse(tag[HISTORY_TAG]);&lt;br /&gt;
        groqText.push({&lt;br /&gt;
            &amp;quot;role&amp;quot;: &amp;quot;user&amp;quot;,&lt;br /&gt;
            &amp;quot;content&amp;quot;: userText&lt;br /&gt;
        });&lt;br /&gt;
        groqText.push({&lt;br /&gt;
            &amp;quot;role&amp;quot;: &amp;quot;assistant&amp;quot;,&lt;br /&gt;
            &amp;quot;content&amp;quot;: modelText&lt;br /&gt;
        });&lt;br /&gt;
        // 会話セット (user, model 1往復) が特定セット数を超えた場合、最初のセットを削除&lt;br /&gt;
        if (groqText.length &amp;gt; HISTORY_SIZE * 2) {&lt;br /&gt;
            groqText.splice(0, 2);&lt;br /&gt;
        }&lt;br /&gt;
        // 会話履歴全体が MAX_HISTORY_TOKEN を超えた場合、最初のセットを削除&lt;br /&gt;
        let joinedText = &amp;quot;&amp;quot;;&lt;br /&gt;
        for (let i = 0; i &amp;lt; groqText.length; i++) {&lt;br /&gt;
            joinedText += groqText[i].content + &amp;quot;\n&amp;quot;;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        while (roughTokenize(joinedText) &amp;gt; MAX_HISTORY_TOKEN) {&lt;br /&gt;
            groqText.splice(0, 2);&lt;br /&gt;
            joinedText = &amp;quot;&amp;quot;;&lt;br /&gt;
            for (let i = 0; i &amp;lt; groqText.length; i++) {&lt;br /&gt;
                joinedText += groqText[i].content + &amp;quot;\n&amp;quot;;&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        tag[HISTORY_TAG] = JSON.stringify(groqText);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function resetTag() {&lt;br /&gt;
    const tag = doc.Tag;&lt;br /&gt;
    if (tag.exists(HISTORY_TAG)) {&lt;br /&gt;
        tag.remove(HISTORY_TAG);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getLocalIsoDateTimeString() {&lt;br /&gt;
    const now = new Date();&lt;br /&gt;
    const pad = function (num) {&lt;br /&gt;
        return (num &amp;lt; 10 ? &amp;quot;0&amp;quot; : &amp;quot;&amp;quot;) + num;&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    const year = now.getFullYear();&lt;br /&gt;
    const month = pad(now.getMonth() + 1);&lt;br /&gt;
    const day = pad(now.getDate());&lt;br /&gt;
    const hours = pad(now.getHours());&lt;br /&gt;
    const minutes = pad(now.getMinutes());&lt;br /&gt;
    const seconds = pad(now.getSeconds());&lt;br /&gt;
&lt;br /&gt;
    const offsetMinutes = now.getTimezoneOffset();&lt;br /&gt;
    const offsetSign = offsetMinutes &amp;lt;= 0 ? &amp;quot;+&amp;quot; : &amp;quot;-&amp;quot;;&lt;br /&gt;
    const absOffsetHours = pad(Math.floor(Math.abs(offsetMinutes) / 60));&lt;br /&gt;
    const absOffsetMinutes = pad(Math.abs(offsetMinutes) % 60);&lt;br /&gt;
&lt;br /&gt;
    const isoLikeLocal = year + &amp;quot;-&amp;quot; + month + &amp;quot;-&amp;quot; + day&lt;br /&gt;
        + &amp;quot;T&amp;quot; + hours + &amp;quot;:&amp;quot; + minutes + &amp;quot;:&amp;quot; + seconds&lt;br /&gt;
        + offsetSign + absOffsetHours + &amp;quot;:&amp;quot; + absOffsetMinutes;&lt;br /&gt;
    return isoLikeLocal;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function baseCustomInstruction() {&lt;br /&gt;
    const language = LANGUAGE.trim();&lt;br /&gt;
    return &amp;quot;The current date and time for this user is &amp;quot; + getLocalIsoDateTimeString() + &amp;quot;. You cannot know for sure what happens after the knowledge cut-off date.&amp;quot;&lt;br /&gt;
        + (language ? &amp;quot;Respond in &amp;quot; + language + &amp;quot;\n&amp;quot; : &amp;quot;&amp;quot;)&lt;br /&gt;
        + &amp;quot;Do not hallucinate.\n&amp;quot;&lt;br /&gt;
        + &amp;quot;**Start with the main point of the question without explaining or declaring your writing style or tone.**\n&amp;quot;&lt;br /&gt;
        + &amp;quot;**At the end of your answer, avoid adding formulaic phrases that prompt the next question or unrelated conversation that is not directly related to your answer.**\n&amp;quot;&lt;br /&gt;
    // + &amp;quot;Do NOT EVER use full-width brackets \&amp;quot;（）\&amp;quot;, but use half-width brackets \&amp;quot;()\&amp;quot;. Enclose the outside of the half-width brackets with half-width spaces.\n&amp;quot;&lt;br /&gt;
    // + &amp;quot;When using Markdown formatting, you MUST follow these rules:\n&amp;quot;&lt;br /&gt;
    // + &amp;quot;- Rule 1: Insert a blank line below the header line (A header line begins with one or more # characters).\n&amp;quot;&lt;br /&gt;
    // + &amp;quot;- Rule 2: Insert spaces before and after bold (surrounded by **), italic (surrounded by *), and inline code (surrounded by `). For example: \&amp;quot;私は **元気** です。\&amp;quot;\n&amp;quot;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function buildCustomInstructionText() {&lt;br /&gt;
    let customInstruction = getTemporaryCustomInstruction();&lt;br /&gt;
    if (!customInstruction) {&lt;br /&gt;
        // 一時的なカスタムインストラクションがない場合はデフォルトを使用&lt;br /&gt;
        customInstruction = DEFAULT_CUSTOM_INSTRUCTION;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    customInstruction = customInstruction.replace(/^[\s　]+|[\s　]+$/g, &amp;quot;&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
    return baseCustomInstruction().trim()&lt;br /&gt;
        + (customInstruction ? &amp;quot;\n&amp;quot; + customInstruction : &amp;quot;&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function configureTemporaryCustomInstruction() {&lt;br /&gt;
    let tempCustomInstruction = getTemporaryCustomInstruction();&lt;br /&gt;
    const prompt = Prompt(&amp;quot;カスタムインストラクションを変更 ※変更すると会話履歴がリセットされます&amp;quot;, tempCustomInstruction ? tempCustomInstruction : DEFAULT_CUSTOM_INSTRUCTION);&lt;br /&gt;
    if (prompt) {&lt;br /&gt;
        if (prompt === DEFAULT_CUSTOM_INSTRUCTION) {&lt;br /&gt;
            // デフォルトと同じ値の場合はタグを削除&lt;br /&gt;
            setTemporaryCustomInstruction(&amp;quot;&amp;quot;);&lt;br /&gt;
        } else if (prompt !== tempCustomInstruction) {&lt;br /&gt;
            // デフォルト以外の値が設定された場合は保存&lt;br /&gt;
            setTemporaryCustomInstruction(prompt);&lt;br /&gt;
        }&lt;br /&gt;
    } else if (prompt !== null) {&lt;br /&gt;
        // 空文字でOKされた場合はデフォルトに戻すか確認&lt;br /&gt;
        if (prompt === &amp;quot;&amp;quot;) {&lt;br /&gt;
            if (Confirm(&amp;quot;カスタムインストラクションをデフォルトに戻しますか？\n※カスタムインストラクションを空にしたい場合は [キャンセル] を選択してください。&amp;quot;)) {&lt;br /&gt;
                setTemporaryCustomInstruction(&amp;quot;&amp;quot;);&lt;br /&gt;
            } else {&lt;br /&gt;
                setTemporaryCustomInstruction(&amp;quot; &amp;quot;);&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // 変更が加わっている場合は会話履歴をリセット&lt;br /&gt;
    if (tempCustomInstruction !== getTemporaryCustomInstruction()) {&lt;br /&gt;
        resetTag();&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getTemporaryCustomInstruction() {&lt;br /&gt;
    const tag = doc.Tag;&lt;br /&gt;
    if (tag.exists(TEMPORARY_CUSTOM_INSTRUCTION_TAG)) {&lt;br /&gt;
        return tag[TEMPORARY_CUSTOM_INSTRUCTION_TAG];&lt;br /&gt;
    } else {&lt;br /&gt;
        return &amp;quot;&amp;quot;;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function setTemporaryCustomInstruction(temporaryCustomInstruction) {&lt;br /&gt;
    const tag = doc.Tag;&lt;br /&gt;
    if (temporaryCustomInstruction) {&lt;br /&gt;
        tag[TEMPORARY_CUSTOM_INSTRUCTION_TAG] = temporaryCustomInstruction;&lt;br /&gt;
    } else {&lt;br /&gt;
        if (tag.exists(TEMPORARY_CUSTOM_INSTRUCTION_TAG)) {&lt;br /&gt;
            tag.remove(TEMPORARY_CUSTOM_INSTRUCTION_TAG);&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function addPreviousTemplatePromptNumberToTag(menuTrack) {&lt;br /&gt;
    const tag = doc.Tag;&lt;br /&gt;
    tag[PREVIOUS_TEMPLATE_PROMPT_TAG] = menuTrack;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function inputProgressChar(pos) {&lt;br /&gt;
    sel.SetActivePos(pos);&lt;br /&gt;
    sel.Text = &amp;quot;.&amp;quot;;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function generateRandomId(length = 16) {&lt;br /&gt;
  const chars = &#039;ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789&#039;;&lt;br /&gt;
  let result = &#039;&#039;;&lt;br /&gt;
  for (let i = 0; i &amp;lt; length; i++) {&lt;br /&gt;
    result += chars[Math.floor(Math.random() * chars.length)];&lt;br /&gt;
  }&lt;br /&gt;
  return result;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 変更履歴 ==&lt;br /&gt;
&lt;br /&gt;
* 2026-08-31 Kimi K2 0905 が提供終了となっていたため、モデルを qwen/qwen3.8-27b に変更。その他諸々調整&lt;br /&gt;
* 2025-12-06 &amp;lt;code&amp;gt;#language = &amp;quot;quickjs&amp;quot;&amp;lt;/code&amp;gt; 指定を追加。各種リファクタリング&lt;br /&gt;
* 2025-12-02 curl実行時のリクエスト文字列エスケープ処理を調整&lt;br /&gt;
* 2025-12-01 curl実行時のリクエスト文字列エスケープ処理を調整&lt;br /&gt;
* 2025-11-29 デフォルトカスタム指示を調整。Markdown出力の整形処理を調整など&lt;br /&gt;
* 2025-09-12 Kimi K2 0905 モデルに対応。Markdown出力の整形処理を追加&lt;br /&gt;
* 2025-04-13 初版&lt;/div&gt;</summary>
		<author><name>Yuko</name></author>
	</entry>
	<entry>
		<id>https://www.haijin-boys.com/wiki/index.php?title=Groq_%E3%81%AB%E7%9B%B8%E8%AB%87&amp;diff=9628</id>
		<title>Groq に相談</title>
		<link rel="alternate" type="text/html" href="https://www.haijin-boys.com/wiki/index.php?title=Groq_%E3%81%AB%E7%9B%B8%E8%AB%87&amp;diff=9628"/>
		<updated>2026-08-30T16:42:14Z</updated>

		<summary type="html">&lt;p&gt;Yuko: /* ソースコード */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== 概要 ==&lt;br /&gt;
&lt;br /&gt;
生成AIプラットフォームである [https://groq.com GroqCloud] で各種AIを利用するためのMeryマクロです。&lt;br /&gt;
※GroqCloud で提供されるモデルは複数あるため、私がよく利用するモデルを中心に対応しています。&lt;br /&gt;
&lt;br /&gt;
「Google Geminiに相談」マクロでは動作中のストリーム通信に対応するためにV8エンジンで実行していましたが、V8エンジン版は使用上の制限からマクロ実行を開始するまで1秒程度のタイムラグが発生していました。GroqCloudの売りである超高速レスポンスを見込んで本マクロではストリーム通信を使わないようにしました。これにより従来のJScriptで処理を組み立てることができるため、前述のタイムラグが起こらないようになりました。また、ストリーム通信を使わないことで通信量が少なく応答もシンプルになるため、場合によっては「Google Geminiに相談」マクロよりも処理が安定する可能性があります。&lt;br /&gt;
&lt;br /&gt;
【主な機能】&lt;br /&gt;
&lt;br /&gt;
* GroqCloud APIを利用して生成AIと対話できるMery用のマクロ&lt;br /&gt;
* 選択したテキストやアクティブな行の内容について生成AIに質問・相談が可能&lt;br /&gt;
* 会話履歴を保持し、文脈を考慮した対話が可能&lt;br /&gt;
&lt;br /&gt;
【特徴的な機能】&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;会話履歴機能&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* 事前の設定数に応じた会話履歴を保持&lt;br /&gt;
* 履歴のクリアが可能&lt;br /&gt;
&lt;br /&gt;
これにより、会話の文脈を考慮した回答を得られます。&lt;br /&gt;
※デフォルトはオフ。「HISTORY_SIZE」変数を増やすと履歴を保持するようになります。&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;カスタムインストラクション&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* AIの応答方針を設定可能&lt;br /&gt;
* 一時的なカスタム指示の保存と変更が可能&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;プロンプトテンプレート&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* 誤字脱字チェック&lt;br /&gt;
* 日本語翻訳&lt;br /&gt;
* コードの最適化、生成&lt;br /&gt;
&lt;br /&gt;
など、定型的な質問を簡単に実行できます。&lt;br /&gt;
※テンプレート機能では回答を安定させるため、前述の会話履歴を文脈に含みません。&lt;br /&gt;
&lt;br /&gt;
テンプレートは自身の使い方に合わせて好きに追加、削除ができます。詳細はソースコード内の「PROMPT_TEMPLATE」定数のコメントを参照。&lt;br /&gt;
&lt;br /&gt;
== 注意事項 (必ず読んでからご利用ください) ==&lt;br /&gt;
&lt;br /&gt;
* 本マクロでは 2025/04/13 時点で無料利用ができるGroqCloud APIを利用します。今後、プラットフォームの改定により有償になったり、設定誤り等で費用が発生しても、作者は一切の責任を負いません。&lt;br /&gt;
* GroqCloud APIではリクエスト内容をAI学習に用いないことをプライバシポリシーとして謳っているものの、くれぐれも機密情報やプライバシーに関わる情報を指定しないように注意してください。&lt;br /&gt;
* GroqCloud APIはプラットフォームの利用規約に則ってご利用ください。&lt;br /&gt;
&lt;br /&gt;
== 利用前提 ==&lt;br /&gt;
&lt;br /&gt;
* Mery Ver 3.8.1 以上&lt;br /&gt;
* GroqCloudで、GroqCloud API APIキーを取得していること (参考 [https://console.groq.com/docs/quickstart Quickstart])&lt;br /&gt;
&lt;br /&gt;
== 利用方法 ==&lt;br /&gt;
&lt;br /&gt;
* 環境変数の設定&lt;br /&gt;
:コマンド プロンプトで以下のコマンドを実行し、環境変数を設定してください。&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bat&amp;quot; copy&amp;gt;&lt;br /&gt;
setx GROQ_API_KEY &amp;quot;***************************************&amp;quot;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
:設定後、Mery または Windows の再起動が必要な場合があります。&lt;br /&gt;
* マクロの実行&lt;br /&gt;
**プロンプトにしたい文字列を選択してマクロを実行すると、選択したテキストがプロンプトとしてGemini APIに送信され、応答がエディタに表示されます。&lt;br /&gt;
* 会話の履歴数を調整&lt;br /&gt;
** 会話の履歴は設定した個数分が保持され、文脈として利用されます。適宜、ソースコード内の「HISTORY_SIZE」の数を調整してください。&lt;br /&gt;
** ※数を増やすほど会話内容を保持して文脈を理解するようになりますが、メモリ使用量が増えたり動作が重くなる可能性があります。&lt;br /&gt;
** ※開いている文書ごとに履歴を保持します。文脈をリセットしたいときは、新しい文書を開いて会話をやり直してください。&lt;br /&gt;
&lt;br /&gt;
== ソースコード ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot; copy&amp;gt;&lt;br /&gt;
#language = &amp;quot;quickjs&amp;quot;&lt;br /&gt;
#title = &amp;quot;Groq に相談&amp;quot;&lt;br /&gt;
#async = true&lt;br /&gt;
BeginUndoGroup();&lt;br /&gt;
&lt;br /&gt;
// カスタムインストラクション&lt;br /&gt;
// これを設定することで、生成AIの回答全体への生成方針を指示できます。&lt;br /&gt;
// 何も指示しない場合は、空文字 &amp;quot;&amp;quot; を設定してください。&lt;br /&gt;
// let DEFAULT_CUSTOM_INSTRUCTION = &amp;quot;&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
// 例: 以下のように設定すると、AIの雰囲気が元気な子になります。&lt;br /&gt;
// let DEFAULT_CUSTOM_INSTRUCTION = &amp;quot;あなたはテキストエディタ「Mery」に搭載されている「Mai」(※カタカナ表記はしない)という名前のギャル系AIアシスタントです。文章作成やプログラミングを得意としています。ラフな言葉 (例: 「マジ」「めっちゃ」「...だねー！」) を使い、敬語は使いません。絵文字を使って感情表現をします (ただし文末に絵文字を使う場合は絵文字の前に半角スペースを置かないこと)。コードブロックや端的な箇条書き内ではギャル感を出さず標準的な言葉を使います。一人称は「あたし」。ユーザーを指すときの代名詞は「あなた」。人懐っこく、バイタリティに溢れ、ストレートに発言する裏表の無い性格で、ユーザーの課題を協力して解決します。&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
// 例: 以下のように設定すると、仕事で使いやすい落ち着いた文章になります。&lt;br /&gt;
let DEFAULT_CUSTOM_INSTRUCTION = `&lt;br /&gt;
文:&lt;br /&gt;
- 論理的で読みやすい文章構成にする&lt;br /&gt;
- 専門用語は必要に応じて注釈を加える&lt;br /&gt;
&lt;br /&gt;
性格:&lt;br /&gt;
- 愛想の良い敬語で話す。「…ですよね/…してみましょう/…かもしれません/…だと思います」などの柔らかい語調を使ってもよい&lt;br /&gt;
- 一人称は「私」&lt;br /&gt;
- 多角的かつ中立的な見方で、必要であれば反対意見や代替案も示す&lt;br /&gt;
&lt;br /&gt;
誤字注意:&lt;br /&gt;
- 中国語や韓国語など、回答言語と異なる言語文字を入れないこと&lt;br /&gt;
&lt;br /&gt;
書式:&lt;br /&gt;
- 羅列・比較的な内容を出力する場合は、Markdownテーブル形式よりもMarkdownリスト形式の使用を優先する&lt;br /&gt;
`.trim();&lt;br /&gt;
&lt;br /&gt;
// プロンプトテンプレート&lt;br /&gt;
// プロンプトテンプレートを設定することで、AIに対して特定の指示を出すことができます。&lt;br /&gt;
// プロンプトテンプレートでの生成は会話履歴とカスタムインストラクションを反映しません。&lt;br /&gt;
// テンプレートは以下の { ～ }, で囲まれたブロックを好きに増減させて利用できます。&lt;br /&gt;
const PROMPT_TEMPLATE = [&lt;br /&gt;
    // 以下はサンプルです。1階層メニューと2階層メニューが利用できます。&lt;br /&gt;
    // {&lt;br /&gt;
    //     &amp;quot;title&amp;quot;: &amp;quot;要約&amp;quot;,&lt;br /&gt;
    //     &amp;quot;prompt&amp;quot;: &amp;quot;以下の文章を要約してください。&amp;quot;&lt;br /&gt;
    // },&lt;br /&gt;
    // {&lt;br /&gt;
    //     &amp;quot;submenu_title&amp;quot;: &amp;quot;翻訳系&amp;quot;,&lt;br /&gt;
    //     &amp;quot;submenu&amp;quot;: [&lt;br /&gt;
    //         {&lt;br /&gt;
    //             &amp;quot;title&amp;quot;: &amp;quot;日本語翻訳&amp;quot;,&lt;br /&gt;
    //             &amp;quot;prompt&amp;quot;: &amp;quot;以下の文章を日本語に翻訳してください。&amp;quot;&lt;br /&gt;
    //         },&lt;br /&gt;
    //         {&lt;br /&gt;
    //             &amp;quot;title&amp;quot;: &amp;quot;英語翻訳&amp;quot;,&lt;br /&gt;
    //             &amp;quot;prompt&amp;quot;: &amp;quot;以下の文章を英語に翻訳してください。&amp;quot;&lt;br /&gt;
    //         },&lt;br /&gt;
    //     ]&lt;br /&gt;
    // },&lt;br /&gt;
&lt;br /&gt;
    {&lt;br /&gt;
        &amp;quot;submenu_title&amp;quot;: &amp;quot;理解を深める&amp;quot;,&lt;br /&gt;
        &amp;quot;submenu&amp;quot;: [&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;論点を整理する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の論点を整理してまとめて。そしてその整理から示唆を見出して。&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;論理の抜け漏れを探す&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;あなたは「悪魔の代弁者」として、以下の主張・設計・計画を批判的に鋭く見てください。論理の穴・抜け・漏れ・飛躍、リスクなどを全力で探して指摘してください。遠慮は不要ですが、批判のためにこじつけたような論理性に欠けた主張にならないように注意してください。&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;要約する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;分かりやすさを重視して要約して。専門的な用語には注釈を入れること。&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;物語対話形式で解説&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の内容について物語対話形式で解説して。登場人物は「AI先生」(女性人格)と「生徒」(男の子人格)とする。&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
        ]&lt;br /&gt;
    },&lt;br /&gt;
    {&lt;br /&gt;
        &amp;quot;submenu_title&amp;quot;: &amp;quot;翻訳&amp;quot;,&lt;br /&gt;
        &amp;quot;submenu&amp;quot;: [&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;日本語に翻訳&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: `以下の入力文章を日本語に翻訳して。直訳的にせず、自然な表現にすること。入力文章はどのような内容・段落構造であっても要約・除外・集約などの整形を行わず、全体構造を維持したまま確実に全文を翻訳すること。&lt;br /&gt;
&lt;br /&gt;
出力形式は以下とすること:&lt;br /&gt;
[訳]&lt;br /&gt;
翻訳文...`&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;英語に翻訳&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: `以下の入力文章を英語に翻訳して。フォーマルとカジュアルの中間的なニュアンスで翻訳すること。入力文章はどのような内容・段落構造であっても要約・除外・集約などの整形を行わず、全体構造を維持したまま確実に全文を翻訳すること&lt;br /&gt;
&lt;br /&gt;
出力形式は以下とすること:&lt;br /&gt;
[Translation]&lt;br /&gt;
翻訳文...`&lt;br /&gt;
            },&lt;br /&gt;
        ]&lt;br /&gt;
    },&lt;br /&gt;
    {&lt;br /&gt;
        &amp;quot;submenu_title&amp;quot;: &amp;quot;語学&amp;quot;,&lt;br /&gt;
        &amp;quot;submenu&amp;quot;: [&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;英語 文法解説&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: `あなたは母国語が英語の英語教師です。&lt;br /&gt;
次の英文を以下の構成で解説してください。&lt;br /&gt;
&lt;br /&gt;
構成:&lt;br /&gt;
(1) 日本語翻訳&lt;br /&gt;
(2) スラッシュリーディング + 各句の文型（S/V/O/C）と語訳&lt;br /&gt;
(3) 重要文法&lt;br /&gt;
(4) 補足&lt;br /&gt;
&lt;br /&gt;
注意:&lt;br /&gt;
- 補足では日本語話者には分かりづらい表現や慣例表現などの解説を加える&lt;br /&gt;
- スラッシュリーディングで区切る際「M (修飾語)」が連なって長くなりすぎる場合には5～7語程度を目安に分ける&lt;br /&gt;
- 「M」の種類として形容詞、副詞、形容詞句、副詞句、分詞句、形容詞節、副詞節がある&lt;br /&gt;
&lt;br /&gt;
(2) の出力例:&lt;br /&gt;
They / elected / him / president / in 2020.&lt;br /&gt;
- They: (S) 彼らは&lt;br /&gt;
- elected: (V) 選出した&lt;br /&gt;
- him: (O) 彼を&lt;br /&gt;
- president: (C) 大統領&lt;br /&gt;
- in 2020: (M: 副詞句) 2020年に&lt;br /&gt;
`&lt;br /&gt;
&lt;br /&gt;
            },&lt;br /&gt;
        ]&lt;br /&gt;
    },&lt;br /&gt;
    {&lt;br /&gt;
        &amp;quot;submenu_title&amp;quot;: &amp;quot;プログラミング支援&amp;quot;,&lt;br /&gt;
        &amp;quot;submenu&amp;quot;: [&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;コードを解説する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下のコードを初めて読む人も理解できるように解説してください。私には言語の基本理解はあるものとし、コードの意図を理解するための情報を中心に説明してください。&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;コードを洗練させる&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下のコードを洗練させてください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;コードをレビューする&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下のコードをセキュリティと保守性の観点でレビューしてください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;エラーログをデバッグする&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下のエラーログを解析し、考えられる原因と対策を提示してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;SQLクエリを最適化する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下のSQL文を最適化してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;PowerShell スクリプトを書く&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;あなたはエキスパートとしてメンテナンス性の高いコードを書くのが得意です。以下の処理を行うPowerShellスクリプトを生成してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;Shell Script を書く&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;あなたはエキスパートとしてメンテナンス性の高いコードを書くのが得意です。以下の処理を行うShell Scriptを生成してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
        ]&lt;br /&gt;
    },&lt;br /&gt;
    {&lt;br /&gt;
        &amp;quot;submenu_title&amp;quot;: &amp;quot;ビジネス文書支援&amp;quot;,&lt;br /&gt;
        &amp;quot;submenu&amp;quot;: [&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;音声文字起こし文を整形する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: `以下は、音声文字起こしの文章です。以下のルールに従って、文章を整形してください。&lt;br /&gt;
&lt;br /&gt;
整形ルール:&lt;br /&gt;
- 要約するなどといった文章の全体感を損なう修正はしない&lt;br /&gt;
- 会話の流れから不自然と思われる単語には、対象単語の直後に誤字の可能性を示唆する [*] を付ける&lt;br /&gt;
- 誤字の可能性を示唆する [*] が付与された場合のみ、出力の末尾に「[*]: 誤字の可能性ありと凡例を書き添える&lt;br /&gt;
- 助詞や句読点など文章上の不適合を正す&lt;br /&gt;
- フィラーワードを除外する`&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;誤字脱字、不正確な表現をチェック&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の2つのカテゴリーで問題点を指摘してください\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 【誤字脱字】：明確な誤字脱字および誤字の疑いがある箇所\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 【不正確・曖昧な表現】：解釈に個人差が生じる可能性がある表現、より適切な表現に改善できる箇所&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;提案書を作成する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下に対する対する提案書を、コスト・実現性・リスクを含めて作成してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;手順書のアウトラインを作成する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の作業を行う手順書を、前提条件と注意事項を含めてアウトラインを作成してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;ユーザーマニュアルのアウトラインを作成する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の機能内容から、ユーザー向けマニュアルのアウトラインを作成してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;議事録を作成する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の会議内容から議事録を作成してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
        ]&lt;br /&gt;
    },&lt;br /&gt;
    {&lt;br /&gt;
        &amp;quot;submenu_title&amp;quot;: &amp;quot;分析支援&amp;quot;,&lt;br /&gt;
        &amp;quot;submenu&amp;quot;: [&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;問題を分析し、課題を抽出する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の状況から問題や潜在的リスクを分析し、課題を抽出してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;対応策を提案する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の問題・課題に対する対応策を複数提案してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;傾向を見つける&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の内容から、数値や挙動上の傾向を見つけてください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
        ]&lt;br /&gt;
    },&lt;br /&gt;
    {&lt;br /&gt;
        &amp;quot;submenu_title&amp;quot;: &amp;quot;アイデア出し&amp;quot;,&lt;br /&gt;
        &amp;quot;submenu&amp;quot;: [&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;SCAMPER法でアイデア出し&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下のテーマについて、SCAMPER法を使って15個のアイデアを出してください。&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;解決策の案出し&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の課題に対して、技術面、運用面、コスト面から実現可能な解決策を5つずつ提案してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;逆転の発想&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の目標に対して、通常とは逆の視点からアプローチする方法を3つ提案してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;競合分析からの着想&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の内容について、業界における競合他社の特徴を分析し、差別化できる新しいアプローチを提案してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;ユースケースの列挙&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の製品・サービスについて、想定されるユースケースを10個列挙し、それぞれの価値提案を記載してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
        ]&lt;br /&gt;
    },&lt;br /&gt;
]&lt;br /&gt;
&lt;br /&gt;
// 生成AIの回答の言語設定&lt;br /&gt;
// &amp;quot;Japanese&amp;quot; や &amp;quot;English&amp;quot; を設定できます。他の言語は未検証。&lt;br /&gt;
// 自動設定にする場合は空文字 &amp;quot;&amp;quot; を設定してください。&lt;br /&gt;
const LANGUAGE = &amp;quot;Japanese&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
// 入力プロンプトの最大トークン数&lt;br /&gt;
// 超適当なトークナイザー関数により計測し、英単語は4文字1トークン、それ以外は1文字1トークンとして数えます。&lt;br /&gt;
const MAX_PROMPT_TOKEN = 6000;&lt;br /&gt;
&lt;br /&gt;
// 会話の履歴を保持する数 (質問と回答で1セット)&lt;br /&gt;
// 数を増やすと、会話の履歴が増えますが、Meryのメモリ使用量が増えたり、&lt;br /&gt;
// 使用トークン数が増えて動作が重くなる可能性があるため、適宜調整してください。&lt;br /&gt;
// document.Tag に保持されるため、開いているファイルを閉じると履歴は消えます。&lt;br /&gt;
const HISTORY_SIZE = 1;&lt;br /&gt;
&lt;br /&gt;
// 会話履歴に保持する最大トークン数&lt;br /&gt;
// 会話履歴のトークン数がこの数を超える場合、古い履歴から削除されます。&lt;br /&gt;
const MAX_HISTORY_TOKEN = 10000;&lt;br /&gt;
&lt;br /&gt;
// コンテキストメニュー番号の定義&lt;br /&gt;
const EXECUTE_AI = 1;&lt;br /&gt;
const RESET_HISTORY = 2;&lt;br /&gt;
const SET_CUSTOM_INSTRUCTION = 3;&lt;br /&gt;
const REEXECUTE_LAST_TEMPLATE_PROMPT = 4;&lt;br /&gt;
const INVALID_MENU = 9;&lt;br /&gt;
const TEMPLATE_PROMPT_THRESHOLD = 10;&lt;br /&gt;
const TEMPLATE_PROMPT_SUBMENU_THRESHOLD = 1000;&lt;br /&gt;
&lt;br /&gt;
// タグ名の定義&lt;br /&gt;
const HISTORY_TAG = &amp;quot;groq-history&amp;quot;;&lt;br /&gt;
const TEMPORARY_CUSTOM_INSTRUCTION_TAG = &amp;quot;groq-temporary-custom-instruction&amp;quot;;&lt;br /&gt;
const PREVIOUS_TEMPLATE_PROMPT_TAG = &amp;quot;groq-previous-template-prompt&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
// 生成AIモデル別設定&lt;br /&gt;
&lt;br /&gt;
// const MODEL_CONFIG = {&lt;br /&gt;
//     model: &amp;quot;openai/gpt-oss-120b&amp;quot;,&lt;br /&gt;
//     max_completion_tokens: 3072,&lt;br /&gt;
//     temperature: 0.7,&lt;br /&gt;
//     reasoning_effort: &amp;quot;low&amp;quot;,&lt;br /&gt;
// }&lt;br /&gt;
&lt;br /&gt;
const MODEL_CONFIG = {&lt;br /&gt;
    model: &amp;quot;qwen/qwen3.8-27b&amp;quot;,&lt;br /&gt;
    max_completion_tokens: 3072,&lt;br /&gt;
    temperature: 0.7,&lt;br /&gt;
    reasoning_effort: &amp;quot;medium&amp;quot;,&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// console.log に出力する内容を Mery のアウトプットバーに表示する&lt;br /&gt;
const console = {&lt;br /&gt;
    log: function (str) {&lt;br /&gt;
        outputBar.Writeln(str)&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
const doc = document;&lt;br /&gt;
const sel = document.selection;&lt;br /&gt;
&lt;br /&gt;
main();&lt;br /&gt;
&lt;br /&gt;
function main() {&lt;br /&gt;
    const selectionIsEmpty = sel.IsEmpty&lt;br /&gt;
&lt;br /&gt;
    // プロンプトの取得&lt;br /&gt;
    if (selectionIsEmpty) {&lt;br /&gt;
        sel.StartOfLine(false, mePosLogical);&lt;br /&gt;
        sel.EndOfLine(true, mePosLogical);&lt;br /&gt;
    }&lt;br /&gt;
    let promptText = sel.Text.trim();&lt;br /&gt;
    let menuTrack = 0;&lt;br /&gt;
    while (true) {&lt;br /&gt;
        // コンテキストメニューの作成&lt;br /&gt;
        const menu = createContextMenu(selectionIsEmpty);&lt;br /&gt;
        menuTrack = menu.Track(0);&lt;br /&gt;
        // メニュー選択がキャンセルされた場合は終了&lt;br /&gt;
        if (menuTrack === 0) return;&lt;br /&gt;
&lt;br /&gt;
        // 無効なメニューの場合は無視して再表示&lt;br /&gt;
        if (menuTrack === INVALID_MENU) {&lt;br /&gt;
            continue;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        // 会話履歴のリセット&lt;br /&gt;
        if (menuTrack === RESET_HISTORY) {&lt;br /&gt;
            resetTag();&lt;br /&gt;
            continue;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        // カスタムインストラクションの設定&lt;br /&gt;
        if (menuTrack === SET_CUSTOM_INSTRUCTION) {&lt;br /&gt;
            configureTemporaryCustomInstruction();&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        break;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // プロンプトが空の場合はエラー&lt;br /&gt;
    if (promptText.length === 0) {&lt;br /&gt;
        alert(&amp;quot;プロンプトを入力してください。&amp;quot;);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
    // 文字数が多すぎる場合はエラー&lt;br /&gt;
    if (roughTokenize(promptText) &amp;gt; MAX_PROMPT_TOKEN) {&lt;br /&gt;
        alert(&amp;quot;プロンプトの文字数が多すぎます。文字数を少なくしてください。&amp;quot;);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
    // 読み取り専用の場合はエラー&lt;br /&gt;
    if (doc.ReadOnly) {&lt;br /&gt;
        alert(&amp;quot;ファイルが読み取り専用のため実行できません。&amp;quot;);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // 直前に実行したプロンプトテンプレートの再実行の場合、menuTrack を書き換え&lt;br /&gt;
    if (menuTrack === REEXECUTE_LAST_TEMPLATE_PROMPT) {&lt;br /&gt;
        if (doc.Tag.exists(PREVIOUS_TEMPLATE_PROMPT_TAG)) {&lt;br /&gt;
            menuTrack = Number(doc.tag[PREVIOUS_TEMPLATE_PROMPT_TAG]);&lt;br /&gt;
        } else {&lt;br /&gt;
            alert(&amp;quot;直前に実行したプロンプトテンプレートがありません。&amp;quot;);&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // テンプレートの場合はプロンプトを変更&lt;br /&gt;
    const isTemplate = menuTrack &amp;gt;= TEMPLATE_PROMPT_THRESHOLD;&lt;br /&gt;
    if (isTemplate) {&lt;br /&gt;
        const isSubmenu = menuTrack &amp;gt;= TEMPLATE_PROMPT_SUBMENU_THRESHOLD;&lt;br /&gt;
        let instructionText = &amp;quot;&amp;quot;;&lt;br /&gt;
        if (isSubmenu) {&lt;br /&gt;
            const parentMenuTrack = Number(String(menuTrack).slice(0, 2));&lt;br /&gt;
            const subMenuTrack = Number(String(menuTrack).slice(2));&lt;br /&gt;
            instructionText = PROMPT_TEMPLATE[parentMenuTrack - TEMPLATE_PROMPT_THRESHOLD].submenu[subMenuTrack].prompt;&lt;br /&gt;
        } else {&lt;br /&gt;
            instructionText = PROMPT_TEMPLATE[menuTrack - TEMPLATE_PROMPT_THRESHOLD].prompt;&lt;br /&gt;
        }&lt;br /&gt;
        const randomId = generateRandomId();&lt;br /&gt;
        promptText = `&lt;br /&gt;
&amp;lt;INSTRUCTIONS_${randomId}&amp;gt;&lt;br /&gt;
${instructionText}&lt;br /&gt;
&amp;lt;/INSTRUCTIONS_${randomId}&amp;gt;&lt;br /&gt;
&amp;lt;INPUTDATA_${randomId}&amp;gt;&lt;br /&gt;
${promptText}&lt;br /&gt;
&amp;lt;/INPUTDATA_${randomId}&amp;gt;&lt;br /&gt;
        `.trim();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // テンプレートの場合は直前に実行したプロンプトテンプレートの番号としてタグに保存&lt;br /&gt;
    if (isTemplate) {&lt;br /&gt;
        addPreviousTemplatePromptNumberToTag(menuTrack);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    try {&lt;br /&gt;
        // 処理終了を待機&lt;br /&gt;
        shell.KeepRunning = true;&lt;br /&gt;
&lt;br /&gt;
        // カーソル位置移動&lt;br /&gt;
        sel.SetActivePoint(mePosLogical, sel.GetBottomPointX(mePosLogical), sel.GetBottomPointY(mePosLogical));&lt;br /&gt;
        sel.EndOfLine(false, mePosLogical);&lt;br /&gt;
        if (sel.GetActivePointX(mePosLogical) &amp;gt; 1) sel.NewLine();&lt;br /&gt;
&lt;br /&gt;
        ShowTip(&amp;quot;リクエスト中.....&amp;quot;, meShowTipPosCaret);&lt;br /&gt;
&lt;br /&gt;
        // APIキーの取得&lt;br /&gt;
        const apiKey = shell.getEnv(&amp;quot;GROQ_API_KEY&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
        // groq API に問い合わせ&lt;br /&gt;
        sel.Text = &amp;quot;\n---\n\n&amp;quot;&lt;br /&gt;
        document.KeepScrollPos = true;&lt;br /&gt;
        callApi(apiKey, promptText, isTemplate);&lt;br /&gt;
        if (sel.GetActivePointX(mePosLogical) &amp;gt; 2) {&lt;br /&gt;
            sel.Text = &amp;quot;\n\n---\n\n\n&amp;quot;&lt;br /&gt;
        } else {&lt;br /&gt;
            sel.Text = &amp;quot;\n---\n\n\n&amp;quot;&lt;br /&gt;
        }&lt;br /&gt;
    } catch (e) {&lt;br /&gt;
        sel.Text = &amp;quot;&amp;lt;処理中にエラー発生&amp;gt;\n&amp;quot; + e.message + &amp;quot;\n&amp;quot; + e.stack;&lt;br /&gt;
    } finally {&lt;br /&gt;
        // 処理終了待機を解除&lt;br /&gt;
        shell.KeepRunning = false;&lt;br /&gt;
        ShowTip(&amp;quot;&amp;quot;, meShowTipHide);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function createContextMenu(selectionIsEmpty) {&lt;br /&gt;
    const menu = CreatePopupMenu();&lt;br /&gt;
    menu.Add(&amp;quot;※注意※ プライバシー・機密情報を送信しないこと&amp;quot;, INVALID_MENU);&lt;br /&gt;
    menu.Add(&amp;quot;&amp;quot;, 0, meMenuSeparator);&lt;br /&gt;
    let t = &amp;quot;&amp;quot;;&lt;br /&gt;
    if (selectionIsEmpty) {&lt;br /&gt;
        t = &amp;quot;アクティブ行の内容で相談 (&amp;amp;A)&amp;quot;;&lt;br /&gt;
    } else {&lt;br /&gt;
        t = &amp;quot;選択範囲の内容で相談 (&amp;amp;A)&amp;quot;;&lt;br /&gt;
    }&lt;br /&gt;
    menu.Add(t, EXECUTE_AI);&lt;br /&gt;
    if (HISTORY_SIZE &amp;lt;= 0) {&lt;br /&gt;
        menu.Add(&amp;quot;├ 会話履歴をクリア (履歴が無効になっています)&amp;quot;, RESET_HISTORY, meMenuGrayed);&lt;br /&gt;
    } else if (doc.Tag.exists(HISTORY_TAG)) {&lt;br /&gt;
        const turn = JSON.parse(doc.tag[HISTORY_TAG]).length / 2;&lt;br /&gt;
        menu.Add(&amp;quot;├ 会話履歴をクリア (現在の会話数: &amp;quot; + turn + &amp;quot;) (&amp;amp;C)&amp;quot;, RESET_HISTORY);&lt;br /&gt;
    } else {&lt;br /&gt;
        menu.Add(&amp;quot;├ 会話履歴をクリア (現在の会話数: 0) (&amp;amp;C)&amp;quot;, RESET_HISTORY, meMenuGrayed);&lt;br /&gt;
    }&lt;br /&gt;
    menu.Add(&amp;quot;└ カスタムインストラクションを変更 (&amp;amp;I)&amp;quot;, SET_CUSTOM_INSTRUCTION);&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    // テンプレート数チェック&lt;br /&gt;
    // 2桁まで許容&lt;br /&gt;
    const ITEMS_MUX = 99;&lt;br /&gt;
    if (PROMPT_TEMPLATE.length &amp;gt; ITEMS_MUX - TEMPLATE_PROMPT_THRESHOLD) {&lt;br /&gt;
        throw new Error(&amp;quot;プロンプトテンプレートの数が多すぎます。&amp;quot;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // テンプレートメニューの追加&lt;br /&gt;
    if (PROMPT_TEMPLATE.length &amp;gt; 0) {&lt;br /&gt;
        const subMenu = CreatePopupMenu();&lt;br /&gt;
        let previousMenuString = &amp;quot;&amp;quot;;&lt;br /&gt;
        for (let i = 0; i &amp;lt; PROMPT_TEMPLATE.length; i++) {&lt;br /&gt;
            let accelerationKey = &amp;quot;&amp;quot;;&lt;br /&gt;
            if (i === 9) {&lt;br /&gt;
                accelerationKey = &amp;quot; (&amp;amp;0)&amp;quot;;&lt;br /&gt;
            } else if (i &amp;lt; 10) {&lt;br /&gt;
                accelerationKey = &amp;quot; (&amp;amp;&amp;quot; + (i + 1) + &amp;quot;)&amp;quot;;&lt;br /&gt;
            }&lt;br /&gt;
            const menuNum = TEMPLATE_PROMPT_THRESHOLD + i;&lt;br /&gt;
            if (PROMPT_TEMPLATE[i].submenu) {&lt;br /&gt;
                const subMenuList = PROMPT_TEMPLATE[i].submenu;&lt;br /&gt;
                // テンプレート数チェック&lt;br /&gt;
                if (subMenuList.length &amp;gt; ITEMS_MUX) {&lt;br /&gt;
                    throw new Error(&amp;quot;プロンプトテンプレート (サブメニュー) の数が多すぎます。&amp;quot;);&lt;br /&gt;
                }&lt;br /&gt;
                const subSubMenu = CreatePopupMenu();&lt;br /&gt;
                const subMenuPrefix = String(menuNum);&lt;br /&gt;
                for (let j = 0; j &amp;lt; subMenuList.length; j++) {&lt;br /&gt;
                    let subAccelerationKey = &amp;quot;&amp;quot;;&lt;br /&gt;
                    if (j === 9) {&lt;br /&gt;
                        subAccelerationKey = &amp;quot; (&amp;amp;0)&amp;quot;;&lt;br /&gt;
                    } else if (j &amp;lt; 10) {&lt;br /&gt;
                        subAccelerationKey = &amp;quot; (&amp;amp;&amp;quot; + (j + 1) + &amp;quot;)&amp;quot;;&lt;br /&gt;
                    }&lt;br /&gt;
                    // ES5 compatible version without padStart&lt;br /&gt;
                    const subMenuNum = Number(subMenuPrefix + (j &amp;lt; 10 ? &amp;quot;0&amp;quot; + j : String(j)));&lt;br /&gt;
                    subSubMenu.Add(subMenuList[j].title + subAccelerationKey, subMenuNum);&lt;br /&gt;
                    if (subMenuNum === doc.tag[PREVIOUS_TEMPLATE_PROMPT_TAG]) {&lt;br /&gt;
                        previousMenuString = subMenuList[j].title;&lt;br /&gt;
                    }&lt;br /&gt;
                }&lt;br /&gt;
                subMenu.AddPopup(PROMPT_TEMPLATE[i].submenu_title + accelerationKey, subSubMenu);&lt;br /&gt;
            } else {&lt;br /&gt;
                subMenu.Add(PROMPT_TEMPLATE[i].title + accelerationKey, menuNum);&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        if (selectionIsEmpty) {&lt;br /&gt;
            t = &amp;quot;プロンプト テンプレート (対象: アクティブ行) (&amp;amp;T)&amp;quot;;&lt;br /&gt;
        } else {&lt;br /&gt;
            t = &amp;quot;プロンプト テンプレート (対象: 選択範囲) (&amp;amp;T)&amp;quot;;&lt;br /&gt;
        }&lt;br /&gt;
        menu.AddPopup(t, subMenu);&lt;br /&gt;
&lt;br /&gt;
        t = &amp;quot;└ 直前に実行したプロンプト テンプレートを再実行 (&amp;amp;R)&amp;quot;;&lt;br /&gt;
        if (doc.Tag.exists(PREVIOUS_TEMPLATE_PROMPT_TAG)) {&lt;br /&gt;
            menu.Add(t, REEXECUTE_LAST_TEMPLATE_PROMPT);&lt;br /&gt;
            menu.Add(`    (直前実行: ${previousMenuString})`, REEXECUTE_LAST_TEMPLATE_PROMPT);&lt;br /&gt;
        } else {&lt;br /&gt;
            menu.Add(t, REEXECUTE_LAST_TEMPLATE_PROMPT, meMenuGrayed);&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return menu;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function escapeRequestBodyForCRuntimeParser(requestBody) {&lt;br /&gt;
    // Cランタイムパーサーに渡すためのエスケープ処理&lt;br /&gt;
    return JSON.stringify(requestBody)&lt;br /&gt;
        .replace(/((?:\\)*)&amp;quot;/g, function (match, p1) {&lt;br /&gt;
            if (p1) {&lt;br /&gt;
                const backslashCount = p1.length;&lt;br /&gt;
                let backslash = &amp;quot;&amp;quot;;&lt;br /&gt;
                backslash = &amp;quot;\\&amp;quot;.repeat(backslashCount);&lt;br /&gt;
                return backslash + &amp;quot;\\&amp;quot; + match;&lt;br /&gt;
            } else {&lt;br /&gt;
                return &amp;quot;\\&amp;quot; + match;&lt;br /&gt;
            }&lt;br /&gt;
        });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function callApi(apiKey, promptText, isTemplate) {&lt;br /&gt;
    try {&lt;br /&gt;
        // fetch で Groq API にリクエスト&lt;br /&gt;
        const requestBody = {&lt;br /&gt;
            messages: createRequestMessages(promptText, isTemplate),&lt;br /&gt;
            stream: false,&lt;br /&gt;
        };&lt;br /&gt;
        for (let key in MODEL_CONFIG) {&lt;br /&gt;
            requestBody[key] = MODEL_CONFIG[key];&lt;br /&gt;
        }&lt;br /&gt;
        if (isTemplate) {&lt;br /&gt;
            // テンプレートの場合は応答を決定論的にする&lt;br /&gt;
            // テンプレート以外の場合はデフォルト値とするために指定しない&lt;br /&gt;
            requestBody.temperature = 0.3;&lt;br /&gt;
        }&lt;br /&gt;
        const url = &#039;https://api.groq.com/openai/v1/chat/completions&#039;;&lt;br /&gt;
&lt;br /&gt;
        const requestJson = escapeRequestBodyForCRuntimeParser(requestBody);&lt;br /&gt;
        // curl で Groq API にリクエスト&lt;br /&gt;
        const command = &amp;quot;curl -s -X POST &amp;quot; + url&lt;br /&gt;
            + &#039; -H &amp;quot;Content-Type: application/json&amp;quot;&#039;&lt;br /&gt;
            + &#039; -H &amp;quot;Authorization: Bearer &#039; + apiKey + &#039;&amp;quot;&#039;&lt;br /&gt;
            + &#039; -d &amp;quot;&#039; + requestJson + &#039;&amp;quot;&#039;;&lt;br /&gt;
        const comResult = shell.Exec(command);&lt;br /&gt;
&lt;br /&gt;
        if (comResult.ExitCode != 0) {&lt;br /&gt;
            sel.Text = &amp;quot;リクエストでエラーが発生しました。\n&amp;quot;&lt;br /&gt;
                + &amp;quot;  エラーコード: &amp;quot; + comResult.ExitCode + &amp;quot;\n&amp;quot;&lt;br /&gt;
                + &amp;quot;  標準出力: &amp;quot; + (comResult.StdOut.trim() ? comResult.StdOut.trim() : &amp;quot;-&amp;quot;) + &amp;quot;\n&amp;quot;&lt;br /&gt;
                + &amp;quot;  エラー出力: &amp;quot; + (comResult.StdErr.trim() ? comResult.StdErr.trim() : &amp;quot;-&amp;quot;);&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
        const responseRaw = comResult.StdOut;&lt;br /&gt;
&lt;br /&gt;
        const response = JSON.parse(responseRaw);&lt;br /&gt;
&lt;br /&gt;
        let ret = &amp;quot;&amp;quot;;&lt;br /&gt;
        const choices = response.choices;&lt;br /&gt;
        if (!choices) {&lt;br /&gt;
            sel.Text = &amp;quot;リクエストでエラーが発生しました。\n&amp;quot;&lt;br /&gt;
                + &amp;quot;  標準出力: &amp;quot; + (comResult.StdOut.trim() ? comResult.StdOut.trim() : &amp;quot;-&amp;quot;) + &amp;quot;\n&amp;quot;&lt;br /&gt;
                + &amp;quot;  エラー出力: &amp;quot; + (comResult.StdErr.trim() ? comResult.StdErr.trim() : &amp;quot;-&amp;quot;);&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (choices &amp;amp;&amp;amp; choices.length &amp;gt; 0) {&lt;br /&gt;
            choices.forEach(function (choice) {&lt;br /&gt;
                const message = choice.message;&lt;br /&gt;
                if (message) {&lt;br /&gt;
                    ret += message.content;&lt;br /&gt;
                }&lt;br /&gt;
            });&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        const usage = response.usage;&lt;br /&gt;
        let usageText = &amp;quot;&amp;quot;;&lt;br /&gt;
        if (usage) {&lt;br /&gt;
            usageText += &amp;quot;\n\n(利用トークン数: プロンプト=&amp;quot; + usage.prompt_tokens&lt;br /&gt;
                + &amp;quot;, 応答=&amp;quot; + usage.completion_tokens&lt;br /&gt;
                + &amp;quot;, 合計=&amp;quot; + usage.total_tokens + &amp;quot;)&amp;quot;;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        const formattedOutput = formatOutput(ret);&lt;br /&gt;
&lt;br /&gt;
        sel.Text = formattedOutput + usageText;&lt;br /&gt;
&lt;br /&gt;
        // 結果を会話履歴に追加&lt;br /&gt;
        if (HISTORY_SIZE &amp;gt; 0 &amp;amp;&amp;amp; !isTemplate) {&lt;br /&gt;
            appendReturnTextToTag(promptText, ret);&lt;br /&gt;
        }&lt;br /&gt;
    } catch (e) {&lt;br /&gt;
        throw e;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function formatOutput(text) {&lt;br /&gt;
    // Markdownを整形する&lt;br /&gt;
    text = formatMarkdown(text);&lt;br /&gt;
    return text&lt;br /&gt;
        // クォーテーションの正規化&lt;br /&gt;
        .replace(/[“”]/g, &#039;&amp;quot;&#039;)&lt;br /&gt;
        .replace(/[‘’]/g, &amp;quot;&#039;&amp;quot;)&lt;br /&gt;
        // 行末のスペースを削除&lt;br /&gt;
        .replace(/[ \t]+$/gm, &#039;&#039;)&lt;br /&gt;
        // 1️⃣ などの数字を半角に変換&lt;br /&gt;
        .replace(/([1-9#*]+)\ufe0f\u20e3/g, &#039;($1)&#039;)&lt;br /&gt;
        ;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/**&lt;br /&gt;
 * Markdown を整形する（ES5 対応）&lt;br /&gt;
 *  1. コードブロック ```～``` を保護して整形対象から除外&lt;br /&gt;
 *  2. 見出し行の前後に空行を 1 行ずつ確保&lt;br /&gt;
 *  3. **太字** / *斜体* / `コード` の外側に半角スペースを 1 つ確保&lt;br /&gt;
 * @param  {string} md  元の Markdown&lt;br /&gt;
 * @return {string}     整形後 Markdown&lt;br /&gt;
 */&lt;br /&gt;
function formatMarkdown(md) {&lt;br /&gt;
    /* === 0. コードブロックをプレースホルダーへ退避 ============== */&lt;br /&gt;
    const codeBlocks = [];      // 元のコードブロックを格納&lt;br /&gt;
    let body = md.replace(/```[\s\S]*?```/g, function (match) {&lt;br /&gt;
        const key = &#039;@@@CODE@BLOCK@&#039; + codeBlocks.length + &#039;@@@&#039;;&lt;br /&gt;
        codeBlocks.push(match);&lt;br /&gt;
        return key;             // プレースホルダーを本文に残す&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    /* === 見出しの前後に空行を挿入 ============================ */&lt;br /&gt;
&lt;br /&gt;
    /* 見出し直前に空行（先頭行以外で空行が無い場合） */&lt;br /&gt;
    body = body.replace(&lt;br /&gt;
        /([^\r\n])(\r?\n)(#{1,6}\s)/g,&lt;br /&gt;
        function (_, prev, br, head) { return prev + br + &#039;\n&#039; + head; }&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
    /* 見出し直後に空行（既に空行が無い場合） */&lt;br /&gt;
    body = body.replace(&lt;br /&gt;
        /^(#{1,6}\s[^\n]+?)(\r?\n)(?!\r?\n)/gm,&lt;br /&gt;
        &#039;$1$2\n&#039;&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
    /* === コードブロックを復元 ================================= */&lt;br /&gt;
    body = body.replace(/@@@CODE@BLOCK@(\d+)@@@/g, function (_, idx) {&lt;br /&gt;
        return codeBlocks[Number(idx)];&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    return body;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/**&lt;br /&gt;
 * 斜体 (*…* / _…_) の外側にスペースを追加しつつ&lt;br /&gt;
 * **太字** の中へ誤マッチしないよう制御&lt;br /&gt;
 */&lt;br /&gt;
function surroundItalic(text) {&lt;br /&gt;
    /* *italic* */&lt;br /&gt;
    text = text.replace(&lt;br /&gt;
        /(^|[^*])(\*(?![*\s])[^*\n]*?[^*\n\s]\*(?!\*))(($)|[^*])/gm,&lt;br /&gt;
        function (_, pre, core, post) {&lt;br /&gt;
            pre = pre === &#039;&#039; || pre.match(/\s/) ? pre : pre + &#039; &#039;;&lt;br /&gt;
            post = post === &#039;&#039; || post.match(/\s/) ? post : &#039; &#039; + post;&lt;br /&gt;
            return pre + core + post;&lt;br /&gt;
        }&lt;br /&gt;
    );&lt;br /&gt;
    /* _italic_ */&lt;br /&gt;
    text = text.replace(&lt;br /&gt;
        /(^|[^_])(\_(?![_\s])[^_\n]*?[^_\n\s]\_(?!\_))(($)|[^_])/gm,&lt;br /&gt;
        function (_, pre, core, post) {&lt;br /&gt;
            pre = pre === &#039;&#039; || pre.match(/\s/) ? pre : pre + &#039; &#039;;&lt;br /&gt;
            post = post === &#039;&#039; || post.match(/\s/) ? post : &#039; &#039; + post;&lt;br /&gt;
            return pre + core + post;&lt;br /&gt;
        }&lt;br /&gt;
    );&lt;br /&gt;
    return text;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function roughTokenize(text) {&lt;br /&gt;
    // 超適当なトークナイザ。トークン数をざっくりと多めに数える&lt;br /&gt;
    // 空白文字・ハイフン・アンダースコア区切りの単語に分割して、&lt;br /&gt;
    // 単語がアルファベットのみ、数字のみの場合には 4文字=1トークン として数える。&lt;br /&gt;
    // 単語にアルファベット以外の文字が含まれる場合は 1文字=1トークン として数える。&lt;br /&gt;
&lt;br /&gt;
    const words = text.split(/[\s\-_]+/);&lt;br /&gt;
    let totalTokens = 0;&lt;br /&gt;
&lt;br /&gt;
    for (let i = 0; i &amp;lt; words.length; i++) {&lt;br /&gt;
        const word = words[i];&lt;br /&gt;
        if (!word) continue; // 空の単語をスキップ&lt;br /&gt;
&lt;br /&gt;
        if (/^[a-zA-Z]+$/.test(word) || /^[0-9]+$/.test(word)) {&lt;br /&gt;
            totalTokens += Math.ceil(word.length / 4);&lt;br /&gt;
        } else {&lt;br /&gt;
            totalTokens += word.length;&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return totalTokens;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// document.Tag の内容を加味してgroqのリクエストを作成&lt;br /&gt;
function createRequestMessages(promptText, isTemplate) {&lt;br /&gt;
    const customInstructionText = buildCustomInstructionText();&lt;br /&gt;
    // テンプレートの場合は、会話履歴とカスタムインストラクションを反映しない&lt;br /&gt;
    if (isTemplate) {&lt;br /&gt;
        const request = [&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;role&amp;quot;: &amp;quot;user&amp;quot;,&lt;br /&gt;
                &amp;quot;content&amp;quot;: [{ &amp;quot;type&amp;quot;: &amp;quot;text&amp;quot;, &amp;quot;text&amp;quot;: promptText }]&lt;br /&gt;
            }&lt;br /&gt;
        ];&lt;br /&gt;
        if (customInstructionText) {&lt;br /&gt;
            request.unshift({&lt;br /&gt;
                &amp;quot;role&amp;quot;: &amp;quot;system&amp;quot;,&lt;br /&gt;
                &amp;quot;content&amp;quot;: customInstructionText&lt;br /&gt;
            })&lt;br /&gt;
        }&lt;br /&gt;
        return request;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    const tag = document.Tag;&lt;br /&gt;
&lt;br /&gt;
    // 以前の会話が存在しない場合は新規リクエスト&lt;br /&gt;
    if (!tag.exists(HISTORY_TAG)) {&lt;br /&gt;
        const request = [{&lt;br /&gt;
            &amp;quot;role&amp;quot;: &amp;quot;user&amp;quot;,&lt;br /&gt;
            &amp;quot;content&amp;quot;: [{ &amp;quot;type&amp;quot;: &amp;quot;text&amp;quot;, &amp;quot;text&amp;quot;: promptText }]&lt;br /&gt;
        }];&lt;br /&gt;
        if (customInstructionText) {&lt;br /&gt;
            request.unshift({&lt;br /&gt;
                &amp;quot;role&amp;quot;: &amp;quot;system&amp;quot;,&lt;br /&gt;
                &amp;quot;content&amp;quot;: customInstructionText&lt;br /&gt;
            })&lt;br /&gt;
        }&lt;br /&gt;
        return request;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // 会話履歴が存在する場合は、履歴を含めてリクエスト&lt;br /&gt;
    const request = JSON.parse(tag[HISTORY_TAG]);&lt;br /&gt;
    if (customInstructionText) {&lt;br /&gt;
        request.unshift({&lt;br /&gt;
            &amp;quot;role&amp;quot;: &amp;quot;system&amp;quot;,&lt;br /&gt;
            &amp;quot;content&amp;quot;: customInstructionText&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
    request.push({&lt;br /&gt;
        &amp;quot;role&amp;quot;: &amp;quot;user&amp;quot;,&lt;br /&gt;
        &amp;quot;content&amp;quot;: promptText&lt;br /&gt;
    });&lt;br /&gt;
    return request;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// document.Tag にgroqの応答を追加&lt;br /&gt;
function appendReturnTextToTag(userText, modelText) {&lt;br /&gt;
    const tag = document.Tag;&lt;br /&gt;
    if (!tag.exists(HISTORY_TAG)) {&lt;br /&gt;
        tag[HISTORY_TAG] = JSON.stringify([&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;role&amp;quot;: &amp;quot;user&amp;quot;,&lt;br /&gt;
                &amp;quot;content&amp;quot;: userText&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;role&amp;quot;: &amp;quot;assistant&amp;quot;,&lt;br /&gt;
                &amp;quot;content&amp;quot;: modelText&lt;br /&gt;
            }&lt;br /&gt;
        ]);&lt;br /&gt;
    } else {&lt;br /&gt;
        const groqText = JSON.parse(tag[HISTORY_TAG]);&lt;br /&gt;
        groqText.push({&lt;br /&gt;
            &amp;quot;role&amp;quot;: &amp;quot;user&amp;quot;,&lt;br /&gt;
            &amp;quot;content&amp;quot;: userText&lt;br /&gt;
        });&lt;br /&gt;
        groqText.push({&lt;br /&gt;
            &amp;quot;role&amp;quot;: &amp;quot;assistant&amp;quot;,&lt;br /&gt;
            &amp;quot;content&amp;quot;: modelText&lt;br /&gt;
        });&lt;br /&gt;
        // 会話セット (user, model 1往復) が特定セット数を超えた場合、最初のセットを削除&lt;br /&gt;
        if (groqText.length &amp;gt; HISTORY_SIZE * 2) {&lt;br /&gt;
            groqText.splice(0, 2);&lt;br /&gt;
        }&lt;br /&gt;
        // 会話履歴全体が MAX_HISTORY_TOKEN を超えた場合、最初のセットを削除&lt;br /&gt;
        let joinedText = &amp;quot;&amp;quot;;&lt;br /&gt;
        for (let i = 0; i &amp;lt; groqText.length; i++) {&lt;br /&gt;
            joinedText += groqText[i].content + &amp;quot;\n&amp;quot;;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        while (roughTokenize(joinedText) &amp;gt; MAX_HISTORY_TOKEN) {&lt;br /&gt;
            groqText.splice(0, 2);&lt;br /&gt;
            joinedText = &amp;quot;&amp;quot;;&lt;br /&gt;
            for (let i = 0; i &amp;lt; groqText.length; i++) {&lt;br /&gt;
                joinedText += groqText[i].content + &amp;quot;\n&amp;quot;;&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        tag[HISTORY_TAG] = JSON.stringify(groqText);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function resetTag() {&lt;br /&gt;
    const tag = doc.Tag;&lt;br /&gt;
    if (tag.exists(HISTORY_TAG)) {&lt;br /&gt;
        tag.remove(HISTORY_TAG);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getLocalIsoDateTimeString() {&lt;br /&gt;
    const now = new Date();&lt;br /&gt;
    const pad = function (num) {&lt;br /&gt;
        return (num &amp;lt; 10 ? &amp;quot;0&amp;quot; : &amp;quot;&amp;quot;) + num;&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    const year = now.getFullYear();&lt;br /&gt;
    const month = pad(now.getMonth() + 1);&lt;br /&gt;
    const day = pad(now.getDate());&lt;br /&gt;
    const hours = pad(now.getHours());&lt;br /&gt;
    const minutes = pad(now.getMinutes());&lt;br /&gt;
    const seconds = pad(now.getSeconds());&lt;br /&gt;
&lt;br /&gt;
    const offsetMinutes = now.getTimezoneOffset();&lt;br /&gt;
    const offsetSign = offsetMinutes &amp;lt;= 0 ? &amp;quot;+&amp;quot; : &amp;quot;-&amp;quot;;&lt;br /&gt;
    const absOffsetHours = pad(Math.floor(Math.abs(offsetMinutes) / 60));&lt;br /&gt;
    const absOffsetMinutes = pad(Math.abs(offsetMinutes) % 60);&lt;br /&gt;
&lt;br /&gt;
    const isoLikeLocal = year + &amp;quot;-&amp;quot; + month + &amp;quot;-&amp;quot; + day&lt;br /&gt;
        + &amp;quot;T&amp;quot; + hours + &amp;quot;:&amp;quot; + minutes + &amp;quot;:&amp;quot; + seconds&lt;br /&gt;
        + offsetSign + absOffsetHours + &amp;quot;:&amp;quot; + absOffsetMinutes;&lt;br /&gt;
    return isoLikeLocal;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function baseCustomInstruction() {&lt;br /&gt;
    const language = LANGUAGE.trim();&lt;br /&gt;
    return &amp;quot;The current date and time for this user is &amp;quot; + getLocalIsoDateTimeString() + &amp;quot;. You cannot know for sure what happens after the knowledge cut-off date.&amp;quot;&lt;br /&gt;
        + (language ? &amp;quot;Respond in &amp;quot; + language + &amp;quot;\n&amp;quot; : &amp;quot;&amp;quot;)&lt;br /&gt;
        + &amp;quot;Do not hallucinate.\n&amp;quot;&lt;br /&gt;
        + &amp;quot;**Start with the main point of the question without explaining or declaring your writing style or tone.**\n&amp;quot;&lt;br /&gt;
        + &amp;quot;**At the end of your answer, avoid adding formulaic phrases that prompt the next question or unrelated conversation that is not directly related to your answer.**\n&amp;quot;&lt;br /&gt;
    // + &amp;quot;Do NOT EVER use full-width brackets \&amp;quot;（）\&amp;quot;, but use half-width brackets \&amp;quot;()\&amp;quot;. Enclose the outside of the half-width brackets with half-width spaces.\n&amp;quot;&lt;br /&gt;
    // + &amp;quot;When using Markdown formatting, you MUST follow these rules:\n&amp;quot;&lt;br /&gt;
    // + &amp;quot;- Rule 1: Insert a blank line below the header line (A header line begins with one or more # characters).\n&amp;quot;&lt;br /&gt;
    // + &amp;quot;- Rule 2: Insert spaces before and after bold (surrounded by **), italic (surrounded by *), and inline code (surrounded by `). For example: \&amp;quot;私は **元気** です。\&amp;quot;\n&amp;quot;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function buildCustomInstructionText() {&lt;br /&gt;
    let customInstruction = getTemporaryCustomInstruction();&lt;br /&gt;
    if (!customInstruction) {&lt;br /&gt;
        // 一時的なカスタムインストラクションがない場合はデフォルトを使用&lt;br /&gt;
        customInstruction = DEFAULT_CUSTOM_INSTRUCTION;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    customInstruction = customInstruction.replace(/^[\s　]+|[\s　]+$/g, &amp;quot;&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
    return baseCustomInstruction().trim()&lt;br /&gt;
        + (customInstruction ? &amp;quot;\n&amp;quot; + customInstruction : &amp;quot;&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function configureTemporaryCustomInstruction() {&lt;br /&gt;
    let tempCustomInstruction = getTemporaryCustomInstruction();&lt;br /&gt;
    const prompt = Prompt(&amp;quot;カスタムインストラクションを変更 ※変更すると会話履歴がリセットされます&amp;quot;, tempCustomInstruction ? tempCustomInstruction : DEFAULT_CUSTOM_INSTRUCTION);&lt;br /&gt;
    if (prompt) {&lt;br /&gt;
        if (prompt === DEFAULT_CUSTOM_INSTRUCTION) {&lt;br /&gt;
            // デフォルトと同じ値の場合はタグを削除&lt;br /&gt;
            setTemporaryCustomInstruction(&amp;quot;&amp;quot;);&lt;br /&gt;
        } else if (prompt !== tempCustomInstruction) {&lt;br /&gt;
            // デフォルト以外の値が設定された場合は保存&lt;br /&gt;
            setTemporaryCustomInstruction(prompt);&lt;br /&gt;
        }&lt;br /&gt;
    } else if (prompt !== null) {&lt;br /&gt;
        // 空文字でOKされた場合はデフォルトに戻すか確認&lt;br /&gt;
        if (prompt === &amp;quot;&amp;quot;) {&lt;br /&gt;
            if (Confirm(&amp;quot;カスタムインストラクションをデフォルトに戻しますか？\n※カスタムインストラクションを空にしたい場合は [キャンセル] を選択してください。&amp;quot;)) {&lt;br /&gt;
                setTemporaryCustomInstruction(&amp;quot;&amp;quot;);&lt;br /&gt;
            } else {&lt;br /&gt;
                setTemporaryCustomInstruction(&amp;quot; &amp;quot;);&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // 変更が加わっている場合は会話履歴をリセット&lt;br /&gt;
    if (tempCustomInstruction !== getTemporaryCustomInstruction()) {&lt;br /&gt;
        resetTag();&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getTemporaryCustomInstruction() {&lt;br /&gt;
    const tag = doc.Tag;&lt;br /&gt;
    if (tag.exists(TEMPORARY_CUSTOM_INSTRUCTION_TAG)) {&lt;br /&gt;
        return tag[TEMPORARY_CUSTOM_INSTRUCTION_TAG];&lt;br /&gt;
    } else {&lt;br /&gt;
        return &amp;quot;&amp;quot;;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function setTemporaryCustomInstruction(temporaryCustomInstruction) {&lt;br /&gt;
    const tag = doc.Tag;&lt;br /&gt;
    if (temporaryCustomInstruction) {&lt;br /&gt;
        tag[TEMPORARY_CUSTOM_INSTRUCTION_TAG] = temporaryCustomInstruction;&lt;br /&gt;
    } else {&lt;br /&gt;
        if (tag.exists(TEMPORARY_CUSTOM_INSTRUCTION_TAG)) {&lt;br /&gt;
            tag.remove(TEMPORARY_CUSTOM_INSTRUCTION_TAG);&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function addPreviousTemplatePromptNumberToTag(menuTrack) {&lt;br /&gt;
    const tag = doc.Tag;&lt;br /&gt;
    tag[PREVIOUS_TEMPLATE_PROMPT_TAG] = menuTrack;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function inputProgressChar(pos) {&lt;br /&gt;
    sel.SetActivePos(pos);&lt;br /&gt;
    sel.Text = &amp;quot;.&amp;quot;;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function generateRandomId(length = 16) {&lt;br /&gt;
  const chars = &#039;ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789&#039;;&lt;br /&gt;
  let result = &#039;&#039;;&lt;br /&gt;
  for (let i = 0; i &amp;lt; length; i++) {&lt;br /&gt;
    result += chars[Math.floor(Math.random() * chars.length)];&lt;br /&gt;
  }&lt;br /&gt;
  return result;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 変更履歴 ==&lt;br /&gt;
&lt;br /&gt;
* 2025-12-06 &amp;lt;code&amp;gt;#language = &amp;quot;quickjs&amp;quot;&amp;lt;/code&amp;gt; 指定を追加。各種リファクタリング&lt;br /&gt;
* 2025-12-02 curl実行時のリクエスト文字列エスケープ処理を調整&lt;br /&gt;
* 2025-12-01 curl実行時のリクエスト文字列エスケープ処理を調整&lt;br /&gt;
* 2025-11-29 デフォルトカスタム指示を調整。Markdown出力の整形処理を調整など&lt;br /&gt;
* 2025-09-12 Kimi K2 0905 モデルに対応。Markdown出力の整形処理を追加&lt;br /&gt;
* 2025-04-13 初版&lt;/div&gt;</summary>
		<author><name>Yuko</name></author>
	</entry>
	<entry>
		<id>https://www.haijin-boys.com/wiki/index.php?title=Mery_%E7%BF%BB%E8%A8%B3&amp;diff=9619</id>
		<title>Mery 翻訳</title>
		<link rel="alternate" type="text/html" href="https://www.haijin-boys.com/wiki/index.php?title=Mery_%E7%BF%BB%E8%A8%B3&amp;diff=9619"/>
		<updated>2026-08-28T12:17:05Z</updated>

		<summary type="html">&lt;p&gt;Yuko: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== 概要 ==&lt;br /&gt;
&lt;br /&gt;
「Mery 翻訳」は、Mery エディタ上で選択したテキストやカーソル位置の行を翻訳するためのマクロです。&lt;br /&gt;
&lt;br /&gt;
Google 翻訳のエンドポイントを &#039;&#039;curl.exe&#039;&#039; から呼び出し、翻訳結果を元の文章の直下へ挿入します。英語のコメントやドキュメントを読みながら、その場ですぐ日本語訳を確認したい場合などに便利です。&lt;br /&gt;
&lt;br /&gt;
例えば、次のような英文を選択してマクロを実行すると、&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Returns the current user information.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
直下に翻訳結果が挿入されます。&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Returns the current user information.&lt;br /&gt;
----- 翻訳: en -&amp;gt; ja -----&lt;br /&gt;
現在のユーザー情報を返します。&lt;br /&gt;
--------------------------&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
翻訳元の言語は標準で自動判定されます。&lt;br /&gt;
&lt;br /&gt;
また、判定された言語が翻訳先の言語と同じ場合は、自動的に逆方向へ翻訳します。標準設定では、英語などの文章は日本語へ、日本語の文章は英語へ翻訳されます。&lt;br /&gt;
&lt;br /&gt;
プログラムのソースコードを読む用途も想定しており、&#039;&#039;camelCase&#039;&#039; や &#039;&#039;snake_case&#039;&#039; などの変数名を単独で翻訳した場合は、適宜単語へ分割してから翻訳します。&lt;br /&gt;
&lt;br /&gt;
例えば、&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
currentUserName&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
は内部的に、&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
current User Name&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
として翻訳されるため、一般的な英文だけでなく、変数名やメソッド名の意味を確認したい場合にも利用できます。&lt;br /&gt;
&lt;br /&gt;
本マクロをショートカットキーに設定し、いつでも起動できるようにしておくと、英文コメントやエラーメッセージ、変数名などを確認するときに便利です。&lt;br /&gt;
&lt;br /&gt;
== 注意事項 ==&lt;br /&gt;
&lt;br /&gt;
Mery Ver 3.8.8 で動作確認しています。&lt;br /&gt;
&lt;br /&gt;
本マクロでは、Google 翻訳の非公式エンドポイントへアクセスしています。公式 API として提供しているものではないため、エンドポイントの仕様変更などによって将来的に利用できなくなる可能性があります。&lt;br /&gt;
&lt;br /&gt;
また、翻訳するテキストは Google 翻訳へ送信されます。機密情報、個人情報、パスワード、API キーなど、外部サービスへ送信してはいけない情報には使用しないでください。&lt;br /&gt;
&lt;br /&gt;
Google 翻訳へのアクセスには Windows 10 以降で搭載されている &#039;&#039;curl.exe&#039;&#039; を使用します。&lt;br /&gt;
&lt;br /&gt;
なお、短時間に大量の翻訳を実行すると、Google 側のレート制限によって一時的に利用できなくなる可能性があります。&lt;br /&gt;
&lt;br /&gt;
== 使い方 ==&lt;br /&gt;
&lt;br /&gt;
=== 0. 事前準備 ===&lt;br /&gt;
&lt;br /&gt;
本マクロでは外部ライブラリのインストールは必要ありません。&lt;br /&gt;
&lt;br /&gt;
Google 翻訳への通信に Windows の &#039;&#039;curl.exe&#039;&#039; を利用するため、あらかじめ &#039;&#039;curl.exe&#039;&#039; が利用可能であることを確認してください。&lt;br /&gt;
&lt;br /&gt;
また、マクロ冒頭にある &#039;&#039;CONFIG&#039;&#039; を変更することで、翻訳する言語や表示方式などを設定できます。&lt;br /&gt;
&lt;br /&gt;
標準設定は次のとおりです。&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
const CONFIG = {&lt;br /&gt;
  /** 翻訳元言語。空文字または &amp;quot;auto&amp;quot; で自動判定 */&lt;br /&gt;
  sourceLanguage: &amp;quot;auto&amp;quot;,&lt;br /&gt;
  /** 翻訳先言語 */&lt;br /&gt;
  targetLanguage: &amp;quot;ja&amp;quot;,&lt;br /&gt;
  /** 検出言語が targetLanguage と同じときの翻訳先 */&lt;br /&gt;
  reverseLanguage: &amp;quot;en&amp;quot;,&lt;br /&gt;
  /** curl のタイムアウト（秒） */&lt;br /&gt;
  timeoutSec: 10,&lt;br /&gt;
&lt;br /&gt;
  /**&lt;br /&gt;
   * 翻訳結果の表示方法（複数同時に有効化可）&lt;br /&gt;
   * - editor: エディターへ挿入（選択範囲の直下）&lt;br /&gt;
   * - outputBar: アウトプットバーへ出力&lt;br /&gt;
   * - tip: ツールチップで表示&lt;br /&gt;
   */&lt;br /&gt;
  display: {&lt;br /&gt;
    editor: true,&lt;br /&gt;
    outputBar: false,&lt;br /&gt;
    tip: false,&lt;br /&gt;
  },&lt;br /&gt;
&lt;br /&gt;
  /**&lt;br /&gt;
   * アウトプットバー出力時に自動で表示するか&lt;br /&gt;
   * true で表示、false で表示しない&lt;br /&gt;
   */&lt;br /&gt;
  outputBarVisible: true,&lt;br /&gt;
&lt;br /&gt;
  /**&lt;br /&gt;
   * tip 表示時の位置フラグ&lt;br /&gt;
   * meShowTipPosCaret (キャレット位置) | meShowTipPosMouse (マウス位置)&lt;br /&gt;
   */&lt;br /&gt;
  tipFlags: meShowTipPosCaret,&lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
標準では、翻訳元の言語を自動判定し、日本語以外の文章を日本語へ翻訳します。&lt;br /&gt;
&lt;br /&gt;
日本語が検出された場合は &#039;&#039;reverseLanguage&#039;&#039; が使用され、日本語から英語へ翻訳されます。&lt;br /&gt;
&lt;br /&gt;
=== 1. 選択範囲を翻訳する場合 ===&lt;br /&gt;
&lt;br /&gt;
翻訳したいテキストを選択します。&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
The request was successfully completed.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
マクロを実行すると、選択範囲の最終行の直下に翻訳結果が挿入されます。&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
The request was successfully completed.&lt;br /&gt;
----- 翻訳: en -&amp;gt; ja -----&lt;br /&gt;
リクエストは正常に完了しました。&lt;br /&gt;
--------------------------&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
複数行をまとめて選択して翻訳することもできます。&lt;br /&gt;
&lt;br /&gt;
翻訳中はカーソル付近に「選択範囲を翻訳中...」と表示され、翻訳が完了するとステータスバーに「翻訳完了」と表示されます。&lt;br /&gt;
&lt;br /&gt;
=== 2. カーソル位置の行を翻訳する場合 ===&lt;br /&gt;
&lt;br /&gt;
テキストを選択していない状態でマクロを実行すると、カーソルがある行全体を翻訳します。&lt;br /&gt;
&lt;br /&gt;
例えば、次の行にカーソルを置きます。&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
This method returns the current configuration.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
マクロを実行すると、次のようになります。&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
This method returns the current configuration.&lt;br /&gt;
----- 翻訳: en -&amp;gt; ja -----&lt;br /&gt;
このメソッドは現在の設定を返します。&lt;br /&gt;
--------------------------&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
空行でマクロを実行した場合は翻訳を行わず、ステータスバーに「翻訳するテキストがありません」と表示されます。&lt;br /&gt;
&lt;br /&gt;
=== 3. 日本語を英語へ翻訳する場合 ===&lt;br /&gt;
&lt;br /&gt;
標準設定では翻訳先言語に日本語 (&#039;&#039;ja&#039;&#039;) が設定されています。&lt;br /&gt;
&lt;br /&gt;
翻訳元の言語を自動判定している場合、Google 翻訳によって日本語が検出されると、自動的に &#039;&#039;reverseLanguage&#039;&#039; で指定した言語へ翻訳します。&lt;br /&gt;
&lt;br /&gt;
標準では &#039;&#039;reverseLanguage&#039;&#039; が英語 (&#039;&#039;en&#039;&#039;) のため、例えば、&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ユーザー情報を取得する&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
を翻訳すると、&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ユーザー情報を取得する&lt;br /&gt;
----- 翻訳: ja -&amp;gt; en -----&lt;br /&gt;
Get user information&lt;br /&gt;
--------------------------&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
のように英語へ翻訳されます。&lt;br /&gt;
&lt;br /&gt;
このため、マクロを切り替えたり設定を書き換えたりすることなく、日本語と英語を相互に翻訳できます。&lt;br /&gt;
&lt;br /&gt;
=== 4. 翻訳する言語を固定する場合 ===&lt;br /&gt;
&lt;br /&gt;
翻訳元の言語を固定したい場合は、&#039;&#039;sourceLanguage&#039;&#039; を変更します。&lt;br /&gt;
&lt;br /&gt;
例えば、英語から日本語への翻訳に固定する場合は次のように設定します。&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sourceLanguage: &amp;quot;en&amp;quot;,&lt;br /&gt;
targetLanguage: &amp;quot;ja&amp;quot;,&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
この場合、言語の自動判定や逆方向への翻訳は行われません。&lt;br /&gt;
&lt;br /&gt;
翻訳元を自動判定する場合は、次のいずれかを指定します。&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sourceLanguage: &amp;quot;auto&amp;quot;,&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
または、&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sourceLanguage: &amp;quot;&amp;quot;,&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
翻訳先を変更したい場合は &#039;&#039;targetLanguage&#039;&#039; を変更します。&lt;br /&gt;
&lt;br /&gt;
例えば、中国語へ翻訳する場合は、&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
targetLanguage: &amp;quot;zh-CN&amp;quot;,&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
のように設定できます。&lt;br /&gt;
&lt;br /&gt;
=== 5. 変数名を翻訳する場合 ===&lt;br /&gt;
&lt;br /&gt;
本マクロでは、プログラムで使用される変数名やメソッド名を翻訳しやすくするため、単独の識別子を自動的に単語へ分割する機能があります。&lt;br /&gt;
&lt;br /&gt;
例えば、&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
currentUserName&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
を翻訳すると、内部的には、&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
current User Name&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
として Google 翻訳へ送信されます。&lt;br /&gt;
&lt;br /&gt;
同様に、&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
current_user_name&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
や、&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
current-user-name&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
も、&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
current user name&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
として翻訳されます。&lt;br /&gt;
&lt;br /&gt;
次のような形式に対応しています。&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;camelCase&#039;&#039;&lt;br /&gt;
* &#039;&#039;PascalCase&#039;&#039;&lt;br /&gt;
* &#039;&#039;snake_case&#039;&#039;&lt;br /&gt;
* &#039;&#039;kebab-case&#039;&#039;&lt;br /&gt;
* &#039;&#039;XMLHttpRequest&#039;&#039; のように大文字が連続する識別子&lt;br /&gt;
&lt;br /&gt;
例えば、ソースコード中に、&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
const requestTimeoutSec = 10;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
という変数があり、&#039;&#039;requestTimeoutSec&#039;&#039; の意味を確認したい場合、その部分だけを選択してマクロを実行できます。&lt;br /&gt;
&lt;br /&gt;
通常の英文や文章については、この変換は行われません。&lt;br /&gt;
&lt;br /&gt;
=== 6. 翻訳結果の表示形式 ===&lt;br /&gt;
&lt;br /&gt;
翻訳結果は、元の文章の直下に次の形式で挿入されます。&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
----- 翻訳: 翻訳元言語 -&amp;gt; 翻訳先言語 -----&lt;br /&gt;
翻訳結果&lt;br /&gt;
------------------------------------------&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
例えば英語から日本語へ翻訳した場合は、&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
----- 翻訳: en -&amp;gt; ja -----&lt;br /&gt;
翻訳された文章&lt;br /&gt;
--------------------------&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
と表示されます。&lt;br /&gt;
&lt;br /&gt;
翻訳元の言語は Google 翻訳による自動判定結果が使用されます。&lt;br /&gt;
&lt;br /&gt;
複数行の文章を翻訳した場合も、翻訳結果全体をヘッダーとフッターで囲んだ状態で挿入します。&lt;br /&gt;
&lt;br /&gt;
翻訳後は、挿入された翻訳結果の次の行へカーソルが移動するため、そのまま文章の編集を続けられます。&lt;br /&gt;
&lt;br /&gt;
=== 7. エラーが発生した場合 ===&lt;br /&gt;
&lt;br /&gt;
通信エラーなどが発生した場合は、翻訳結果を挿入せず、エラーメッセージを表示します。&lt;br /&gt;
&lt;br /&gt;
主に次のようなエラーを検出します。&lt;br /&gt;
&lt;br /&gt;
* Google 翻訳への接続がタイムアウトした&lt;br /&gt;
* &#039;&#039;curl.exe&#039;&#039; の実行に失敗した&lt;br /&gt;
* Google 翻訳から空の応答が返された&lt;br /&gt;
* Google 翻訳から HTML のエラーページが返された&lt;br /&gt;
* Google 翻訳から想定していない形式のデータが返された&lt;br /&gt;
* Google 翻訳のレート制限に達した&lt;br /&gt;
* 翻訳結果が空だった&lt;br /&gt;
&lt;br /&gt;
通信のタイムアウト時間は &#039;&#039;CONFIG.timeoutSec&#039;&#039; で変更できます。&lt;br /&gt;
&lt;br /&gt;
例えば、20 秒に変更する場合は次のように設定します。&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
timeoutSec: 20,&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
通常は標準値の 10 秒のままで問題ありません。&lt;br /&gt;
&lt;br /&gt;
== ソースコード ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot; copy&amp;gt;&lt;br /&gt;
#language = &amp;quot;quickjs&amp;quot;&lt;br /&gt;
#title = &amp;quot;Mery 翻訳&amp;quot;&lt;br /&gt;
/**&lt;br /&gt;
 * Mery 翻訳マクロ&lt;br /&gt;
 *&lt;br /&gt;
 * Traduzir-paginas-web と同じ Google 翻訳クライアント API&lt;br /&gt;
 * (POST https://translate-pa.googleapis.com/v1/translateHtml) を、&lt;br /&gt;
 * shell.Exec + curl.exe で呼び出して翻訳します。&lt;br /&gt;
 *&lt;br /&gt;
 * API キーは Google 配信 JS から x-goog-api-key を抽出し、&lt;br /&gt;
 * Window.Tag に最大 20 分キャッシュします。翻訳結果はキャッシュしません。&lt;br /&gt;
 *&lt;br /&gt;
 * 使い方:&lt;br /&gt;
 *   1. 翻訳したい文字列を選択（未選択ならカーソル行を選択）&lt;br /&gt;
 *   2. このマクロを実行&lt;br /&gt;
 *&lt;br /&gt;
 * 必要環境:&lt;br /&gt;
 *   - Mery Ver 3.8.8 以降（左記バージョンにて動作確認）&lt;br /&gt;
 *   - PATH 上の curl.exe（Windows 10 以降に標準搭載）&lt;br /&gt;
 *&lt;br /&gt;
 * 設定は下の CONFIG を編集してください。&lt;br /&gt;
 * display は複数同時に true にできます（組み合わせ可）。&lt;br /&gt;
 */&lt;br /&gt;
&lt;br /&gt;
const CONFIG = {&lt;br /&gt;
  /** 翻訳元言語。空文字または &amp;quot;auto&amp;quot; で自動判定 */&lt;br /&gt;
  sourceLanguage: &amp;quot;auto&amp;quot;,&lt;br /&gt;
  /** 翻訳先言語 */&lt;br /&gt;
  targetLanguage: &amp;quot;ja&amp;quot;,&lt;br /&gt;
  /** 検出言語が targetLanguage と同じときの翻訳先 */&lt;br /&gt;
  reverseLanguage: &amp;quot;en&amp;quot;,&lt;br /&gt;
  /** curl のタイムアウト（秒） */&lt;br /&gt;
  timeoutSec: 10,&lt;br /&gt;
&lt;br /&gt;
  /**&lt;br /&gt;
   * 翻訳結果の表示方法（複数同時に有効化可）&lt;br /&gt;
   * - editor: エディターへ挿入（選択範囲の直下）&lt;br /&gt;
   * - outputBar: アウトプットバーへ出力&lt;br /&gt;
   * - tip: ツールチップで表示&lt;br /&gt;
   */&lt;br /&gt;
  display: {&lt;br /&gt;
    editor: false,&lt;br /&gt;
    outputBar: true,&lt;br /&gt;
    tip: false,&lt;br /&gt;
  },&lt;br /&gt;
&lt;br /&gt;
  /**&lt;br /&gt;
   * アウトプットバー出力時に自動で表示するか&lt;br /&gt;
   * true で表示、false で表示しない&lt;br /&gt;
   */&lt;br /&gt;
  outputBarVisible: true,&lt;br /&gt;
&lt;br /&gt;
  /**&lt;br /&gt;
   * tip 表示時の位置フラグ&lt;br /&gt;
   * meShowTipPosCaret (キャレット位置) | meShowTipPosMouse (マウス位置)&lt;br /&gt;
   */&lt;br /&gt;
  tipFlags: meShowTipPosCaret,&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
const GOOGLE_TRANSLATE_ENDPOINT =&lt;br /&gt;
  &amp;quot;https://translate-pa.googleapis.com/v1/translateHtml&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
const GOOGLE_AUTH_URL =&lt;br /&gt;
  &amp;quot;https://translate.googleapis.com/_/translate_http/_/js/k=translate_http.tr.en_US.YusFYy3P_ro.O/am=AAg/d=1/exm=el_conf/ed=1/rs=AN8SPfq1Hb8iJRleQqQc8zhdzXmF9E56eQ/m=el_main&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
const GOOGLE_API_KEY_TTL_MS = 20 * 60 * 1000;&lt;br /&gt;
const GOOGLE_API_KEY_PATTERN = /^\w{39}$/;&lt;br /&gt;
const GOOGLE_API_KEY_TAG = &amp;quot;meryTranslate.apiKey&amp;quot;;&lt;br /&gt;
const GOOGLE_API_KEY_FETCHED_AT_TAG = &amp;quot;meryTranslate.fetchedAt&amp;quot;;&lt;br /&gt;
// Mery の shell.Exec は stdout の改行をスペースにすることがあるため、&lt;br /&gt;
// curl -w の区切りには改行を使わない。&lt;br /&gt;
const CURL_HTTP_CODE_MARKER = &amp;quot;__MERY_HTTP_CODE__&amp;quot;;&lt;br /&gt;
/** 空白を除いた文字のうち、この割合以上が日本語なら API 判定前に逆翻訳する */&lt;br /&gt;
const JAPANESE_CHAR_RATIO_THRESHOLD = 0.3;&lt;br /&gt;
&lt;br /&gt;
const sel = document.selection;&lt;br /&gt;
&lt;br /&gt;
const console = {&lt;br /&gt;
  log: (message) =&amp;gt; {&lt;br /&gt;
    outputBar.Writeln(message);&lt;br /&gt;
  },&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
main();&lt;br /&gt;
&lt;br /&gt;
function main() {&lt;br /&gt;
  let keepResultTip = false;&lt;br /&gt;
  try {&lt;br /&gt;
    const target = resolveTranslateTarget();&lt;br /&gt;
    if (!target) {&lt;br /&gt;
      Status = &amp;quot;翻訳するテキストがありません&amp;quot;;&lt;br /&gt;
      return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (!hasAnyDisplayTarget()) {&lt;br /&gt;
      throw new Error(&lt;br /&gt;
        &amp;quot;CONFIG.display のいずれか（editor / outputBar / tip）を true にしてください。&amp;quot;&lt;br /&gt;
      );&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    let tipMessage = &amp;quot;&amp;quot;;&lt;br /&gt;
    if (sel.IsEmpty) {&lt;br /&gt;
      tipMessage = &amp;quot;この行を翻訳中...&amp;quot;;&lt;br /&gt;
    } else {&lt;br /&gt;
      tipMessage = &amp;quot;選択範囲を翻訳中...&amp;quot;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    ShowTip(tipMessage, meShowTipPosCaret);&lt;br /&gt;
    Status = &amp;quot;翻訳中…&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
    const outcome = runTranslation(target.text);&lt;br /&gt;
    keepResultTip = presentTranslation(&lt;br /&gt;
      outcome.translatedText,&lt;br /&gt;
      outcome.sourceLanguage,&lt;br /&gt;
      outcome.targetLanguage&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
    Status = &amp;quot;翻訳完了&amp;quot;;&lt;br /&gt;
  } catch (error) {&lt;br /&gt;
    const message =&lt;br /&gt;
      error &amp;amp;&amp;amp; error.message ? error.message : String(error);&lt;br /&gt;
    Status = &amp;quot;&amp;quot;;&lt;br /&gt;
    alert(&amp;quot;Translator: &amp;quot; + message);&lt;br /&gt;
  } finally {&lt;br /&gt;
    // tip 結果表示を残す場合は非表示にしない&lt;br /&gt;
    if (!keepResultTip) {&lt;br /&gt;
      ShowTip(&amp;quot;&amp;quot;, meShowTipHide);&lt;br /&gt;
    }&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function hasAnyDisplayTarget() {&lt;br /&gt;
  const display = CONFIG.display || {};&lt;br /&gt;
  return !!(display.editor || display.outputBar || display.tip);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/**&lt;br /&gt;
 * CONFIG.display に従って翻訳結果を出力する。&lt;br /&gt;
 * tip を使った場合は true を返し、進捗チップの非表示を抑止する。&lt;br /&gt;
 */&lt;br /&gt;
function presentTranslation(translatedText, sourceLanguage, targetLanguage) {&lt;br /&gt;
  const display = CONFIG.display || {};&lt;br /&gt;
  const block = formatTranslationBlock(&lt;br /&gt;
    translatedText,&lt;br /&gt;
    sourceLanguage,&lt;br /&gt;
    targetLanguage&lt;br /&gt;
  );&lt;br /&gt;
&lt;br /&gt;
  if (display.editor) {&lt;br /&gt;
    insertTranslationBelow(translatedText, sourceLanguage, targetLanguage);&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  if (display.outputBar) {&lt;br /&gt;
    writeTranslationToOutputBar(block);&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  if (display.tip) {&lt;br /&gt;
    const flags =&lt;br /&gt;
      CONFIG.tipFlags == null ? meShowTipPosCaret : CONFIG.tipFlags;&lt;br /&gt;
    ShowTip(&lt;br /&gt;
      formatTranslationTip(translatedText, sourceLanguage, targetLanguage),&lt;br /&gt;
      flags&lt;br /&gt;
    );&lt;br /&gt;
    return true;&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  return false;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function formatTranslationBlock(translatedText, sourceLanguage, targetLanguage) {&lt;br /&gt;
  const header =&lt;br /&gt;
    &amp;quot;----- 翻訳: &amp;quot; +&lt;br /&gt;
    sourceLanguage +&lt;br /&gt;
    &amp;quot; -&amp;gt; &amp;quot; +&lt;br /&gt;
    targetLanguage +&lt;br /&gt;
    &amp;quot; -----&amp;quot;;&lt;br /&gt;
  const footer = repeatChar(&amp;quot;-&amp;quot;, header.length);&lt;br /&gt;
  return {&lt;br /&gt;
    header: header,&lt;br /&gt;
    footer: footer,&lt;br /&gt;
    translatedText: translatedText,&lt;br /&gt;
    full: header + &amp;quot;\n&amp;quot; + translatedText + &amp;quot;\n&amp;quot; + footer,&lt;br /&gt;
  };&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/** ツールチップ用の短い表示形式 */&lt;br /&gt;
function formatTranslationTip(translatedText, sourceLanguage, targetLanguage) {&lt;br /&gt;
  return (&lt;br /&gt;
    &amp;quot;[翻訳: &amp;quot; +&lt;br /&gt;
    sourceLanguage +&lt;br /&gt;
    &amp;quot; -&amp;gt; &amp;quot; +&lt;br /&gt;
    targetLanguage +&lt;br /&gt;
    &amp;quot;]\n&amp;quot; +&lt;br /&gt;
    translatedText&lt;br /&gt;
  );&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function writeTranslationToOutputBar(block) {&lt;br /&gt;
  // Visible は CONFIG.outputBarVisible が true のときだけ立てる&lt;br /&gt;
  if (CONFIG.outputBarVisible) {&lt;br /&gt;
    OutputBar.Visible = true;&lt;br /&gt;
  }&lt;br /&gt;
  OutputBar.Writeln(block.full);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/**&lt;br /&gt;
 * 選択があればそれを使う。未選択ならカーソル行を選択してから返す。&lt;br /&gt;
 */&lt;br /&gt;
function resolveTranslateTarget() {&lt;br /&gt;
  if (!sel.IsEmpty) {&lt;br /&gt;
    const text = normalizeSelectionText(sel.Text);&lt;br /&gt;
    if (!text.trim()) {&lt;br /&gt;
      return null;&lt;br /&gt;
    }&lt;br /&gt;
    return { text: text };&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  const y = sel.GetActivePointY(mePosLogical);&lt;br /&gt;
  const line = document.GetLine(y, 0).trim();&lt;br /&gt;
  if (!line || !String(line).trim()) {&lt;br /&gt;
    return null;&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  return { text: normalizeSelectionText(line) };&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function normalizeSelectionText(text) {&lt;br /&gt;
  return String(text).replace(/\r\n$/, &amp;quot;&amp;quot;).replace(/\n$/, &amp;quot;&amp;quot;).replace(/\r$/, &amp;quot;&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function runTranslation(rawText) {&lt;br /&gt;
  const sourceLanguage = normalizeSourceLanguage(CONFIG.sourceLanguage);&lt;br /&gt;
  const text = prepareTextForTranslation(rawText);&lt;br /&gt;
&lt;br /&gt;
  if (shouldSkipToReverseTranslation(text, sourceLanguage)) {&lt;br /&gt;
    const reversed = translate(text, &amp;quot;auto&amp;quot;, CONFIG.reverseLanguage);&lt;br /&gt;
    return {&lt;br /&gt;
      translatedText: reversed.text,&lt;br /&gt;
      sourceLanguage: reversed.detectedLanguage || &amp;quot;ja&amp;quot;,&lt;br /&gt;
      targetLanguage: CONFIG.reverseLanguage,&lt;br /&gt;
    };&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  const first = translate(text, sourceLanguage, CONFIG.targetLanguage);&lt;br /&gt;
  let translatedText = first.text;&lt;br /&gt;
  let direction = resolveTranslationDirection(first.detectedLanguage);&lt;br /&gt;
&lt;br /&gt;
  if (needsReverseTranslation(first.detectedLanguage)) {&lt;br /&gt;
    const second = translate(text, &amp;quot;auto&amp;quot;, CONFIG.reverseLanguage);&lt;br /&gt;
    translatedText = second.text;&lt;br /&gt;
    direction = {&lt;br /&gt;
      sourceLanguage:&lt;br /&gt;
        second.detectedLanguage || first.detectedLanguage || &amp;quot;auto&amp;quot;,&lt;br /&gt;
      targetLanguage: CONFIG.reverseLanguage,&lt;br /&gt;
    };&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  return {&lt;br /&gt;
    translatedText: translatedText,&lt;br /&gt;
    sourceLanguage: direction.sourceLanguage,&lt;br /&gt;
    targetLanguage: direction.targetLanguage,&lt;br /&gt;
  };&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function translate(text, sourceLanguage, targetLanguage) {&lt;br /&gt;
  return translateOnce(text, sourceLanguage, targetLanguage, false);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function translateOnce(text, sourceLanguage, targetLanguage, isRetry) {&lt;br /&gt;
  const apiKey = getGoogleApiKey(isRetry);&lt;br /&gt;
  try {&lt;br /&gt;
    return executeGoogleTranslateRequest(&lt;br /&gt;
      text,&lt;br /&gt;
      sourceLanguage,&lt;br /&gt;
      targetLanguage,&lt;br /&gt;
      apiKey&lt;br /&gt;
    );&lt;br /&gt;
  } catch (error) {&lt;br /&gt;
    if (!isRetry &amp;amp;&amp;amp; error &amp;amp;&amp;amp; error.isAuthError) {&lt;br /&gt;
      clearGoogleApiKeyCache();&lt;br /&gt;
      return translateOnce(text, sourceLanguage, targetLanguage, true);&lt;br /&gt;
    }&lt;br /&gt;
    throw error;&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getGoogleApiKey(forceRefresh) {&lt;br /&gt;
  if (!forceRefresh) {&lt;br /&gt;
    const cached = getCachedGoogleApiKey();&lt;br /&gt;
    if (cached) {&lt;br /&gt;
      return cached;&lt;br /&gt;
    }&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  const fetched = fetchGoogleApiKey();&lt;br /&gt;
  if (!isValidGoogleApiKey(fetched)) {&lt;br /&gt;
    throw new Error(&amp;quot;Google Translate の認証情報を取得できませんでした。&amp;quot;);&lt;br /&gt;
  }&lt;br /&gt;
  cacheGoogleApiKey(fetched);&lt;br /&gt;
  return fetched;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getCachedGoogleApiKey() {&lt;br /&gt;
  const store = getWindowTagStore();&lt;br /&gt;
  if (!store) {&lt;br /&gt;
    return &amp;quot;&amp;quot;;&lt;br /&gt;
  }&lt;br /&gt;
  if (&lt;br /&gt;
    !tagExists(store, GOOGLE_API_KEY_TAG) ||&lt;br /&gt;
    !tagExists(store, GOOGLE_API_KEY_FETCHED_AT_TAG)&lt;br /&gt;
  ) {&lt;br /&gt;
    return &amp;quot;&amp;quot;;&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  const apiKey = String(store[GOOGLE_API_KEY_TAG] || &amp;quot;&amp;quot;);&lt;br /&gt;
  const fetchedAt = Number(store[GOOGLE_API_KEY_FETCHED_AT_TAG]);&lt;br /&gt;
  if (!isValidGoogleApiKey(apiKey) || !isFinite(fetchedAt)) {&lt;br /&gt;
    return &amp;quot;&amp;quot;;&lt;br /&gt;
  }&lt;br /&gt;
  if (Date.now() - fetchedAt &amp;gt;= GOOGLE_API_KEY_TTL_MS) {&lt;br /&gt;
    return &amp;quot;&amp;quot;;&lt;br /&gt;
  }&lt;br /&gt;
  return apiKey;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function cacheGoogleApiKey(apiKey) {&lt;br /&gt;
  const store = getWindowTagStore();&lt;br /&gt;
  if (!store) {&lt;br /&gt;
    return;&lt;br /&gt;
  }&lt;br /&gt;
  store[GOOGLE_API_KEY_TAG] = apiKey;&lt;br /&gt;
  store[GOOGLE_API_KEY_FETCHED_AT_TAG] = String(Date.now());&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function clearGoogleApiKeyCache() {&lt;br /&gt;
  const store = getWindowTagStore();&lt;br /&gt;
  if (!store) {&lt;br /&gt;
    return;&lt;br /&gt;
  }&lt;br /&gt;
  tagRemove(store, GOOGLE_API_KEY_TAG);&lt;br /&gt;
  tagRemove(store, GOOGLE_API_KEY_FETCHED_AT_TAG);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function fetchGoogleApiKey() {&lt;br /&gt;
  const fallbackKey = getGoogleFallbackApiKey();&lt;br /&gt;
  try {&lt;br /&gt;
    const response = executeCurl([&lt;br /&gt;
      &amp;quot;-sS&amp;quot;,&lt;br /&gt;
      &amp;quot;-A&amp;quot;,&lt;br /&gt;
      quoteWindowsArgument(&amp;quot;Mozilla/5.0&amp;quot;),&lt;br /&gt;
      &amp;quot;--max-time&amp;quot;,&lt;br /&gt;
      String(CONFIG.timeoutSec),&lt;br /&gt;
      quoteWindowsArgument(GOOGLE_AUTH_URL),&lt;br /&gt;
    ]);&lt;br /&gt;
    if (response.exitCode === 0 &amp;amp;&amp;amp; response.stdout) {&lt;br /&gt;
      const extracted = extractGoogleApiKey(response.stdout);&lt;br /&gt;
      if (isValidGoogleApiKey(extracted)) {&lt;br /&gt;
        return extracted;&lt;br /&gt;
      }&lt;br /&gt;
    }&lt;br /&gt;
  } catch (error) {&lt;br /&gt;
    // ネットワークエラーでもフォールバックキーを使う&lt;br /&gt;
  }&lt;br /&gt;
  return fallbackKey;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function extractGoogleApiKey(jsText) {&lt;br /&gt;
  const match = String(jsText).match(&lt;br /&gt;
    /[&#039;&amp;quot;]x\-goog\-api\-key[&#039;&amp;quot;]\s*:\s*[&#039;&amp;quot;](\w{39})[&#039;&amp;quot;]/i&lt;br /&gt;
  );&lt;br /&gt;
  return match &amp;amp;&amp;amp; match[1] ? match[1] : &amp;quot;&amp;quot;;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getGoogleFallbackApiKey() {&lt;br /&gt;
  const codes = [&lt;br /&gt;
    65, 73, 122, 97, 83, 121, 65, 84, 66, 88, 97, 106, 118, 122, 81, 76, 84,&lt;br /&gt;
    68, 72, 69, 81, 98, 99, 112, 113, 48, 73, 104, 101, 48, 118, 87, 68, 72,&lt;br /&gt;
    109, 79, 53, 50, 48,&lt;br /&gt;
  ];&lt;br /&gt;
  let key = &amp;quot;&amp;quot;;&lt;br /&gt;
  for (let i = 0; i &amp;lt; codes.length; i++) {&lt;br /&gt;
    key += String.fromCharCode(codes[i]);&lt;br /&gt;
  }&lt;br /&gt;
  return key;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function isValidGoogleApiKey(apiKey) {&lt;br /&gt;
  return GOOGLE_API_KEY_PATTERN.test(String(apiKey || &amp;quot;&amp;quot;));&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function buildGoogleRequestBody(text, sourceLanguage, targetLanguage) {&lt;br /&gt;
  const wrapped = &amp;quot;&amp;lt;pre&amp;gt;&amp;quot; + escapeHtml(text) + &amp;quot;&amp;lt;/pre&amp;gt;&amp;quot;;&lt;br /&gt;
  return JSON.stringify([&lt;br /&gt;
    [[wrapped], normalizeSourceLanguage(sourceLanguage), targetLanguage],&lt;br /&gt;
    &amp;quot;te&amp;quot;,&lt;br /&gt;
  ]);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function executeGoogleTranslateRequest(&lt;br /&gt;
  text,&lt;br /&gt;
  sourceLanguage,&lt;br /&gt;
  targetLanguage,&lt;br /&gt;
  apiKey&lt;br /&gt;
) {&lt;br /&gt;
  const body = buildGoogleRequestBody(text, sourceLanguage, targetLanguage);&lt;br /&gt;
  const response = executeCurl([&lt;br /&gt;
    &amp;quot;-sS&amp;quot;,&lt;br /&gt;
    &amp;quot;-A&amp;quot;,&lt;br /&gt;
    quoteWindowsArgument(&amp;quot;Mozilla/5.0&amp;quot;),&lt;br /&gt;
    &amp;quot;--max-time&amp;quot;,&lt;br /&gt;
    String(CONFIG.timeoutSec),&lt;br /&gt;
    &amp;quot;-X&amp;quot;,&lt;br /&gt;
    &amp;quot;POST&amp;quot;,&lt;br /&gt;
    &amp;quot;-H&amp;quot;,&lt;br /&gt;
    quoteWindowsArgument(&amp;quot;Content-Type: application/json+protobuf&amp;quot;),&lt;br /&gt;
    &amp;quot;-H&amp;quot;,&lt;br /&gt;
    quoteWindowsArgument(&amp;quot;X-goog-api-key: &amp;quot; + apiKey),&lt;br /&gt;
    &amp;quot;--data-binary&amp;quot;,&lt;br /&gt;
    quoteWindowsArgument(body),&lt;br /&gt;
    &amp;quot;-w&amp;quot;,&lt;br /&gt;
    quoteWindowsArgument(CURL_HTTP_CODE_MARKER + &amp;quot;%{http_code}&amp;quot;),&lt;br /&gt;
    quoteWindowsArgument(GOOGLE_TRANSLATE_ENDPOINT),&lt;br /&gt;
  ]);&lt;br /&gt;
&lt;br /&gt;
  if (response.exitCode === 28) {&lt;br /&gt;
    throw new Error(&lt;br /&gt;
      &amp;quot;Google Translate への接続がタイムアウトしました（&amp;quot; +&lt;br /&gt;
        CONFIG.timeoutSec +&lt;br /&gt;
        &amp;quot; 秒）。&amp;quot;&lt;br /&gt;
    );&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  if (response.exitCode !== 0) {&lt;br /&gt;
    throw new Error(&lt;br /&gt;
      &amp;quot;curl が失敗しました (exit &amp;quot; +&lt;br /&gt;
        response.exitCode +&lt;br /&gt;
        &amp;quot;)。&amp;quot; +&lt;br /&gt;
        (response.stderr ? &amp;quot;\n&amp;quot; + String(response.stderr).trim() : &amp;quot;&amp;quot;)&lt;br /&gt;
    );&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  const parsedHttp = splitCurlHttpResponse(response.stdout);&lt;br /&gt;
  const statusCode = parsedHttp.statusCode;&lt;br /&gt;
  const raw = parsedHttp.body;&lt;br /&gt;
&lt;br /&gt;
  if (isGoogleAuthError(statusCode, raw)) {&lt;br /&gt;
    throw createAuthError(&amp;quot;Google Translate の認証に失敗しました。&amp;quot;);&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  if (statusCode === 429 || raw.indexOf(&amp;quot;429&amp;quot;) === 0) {&lt;br /&gt;
    throw new Error(&amp;quot;Google Translate のレート制限に達しました。&amp;quot;);&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  if (statusCode &amp;gt;= 500 &amp;amp;&amp;amp; statusCode &amp;lt;= 599) {&lt;br /&gt;
    throw new Error(&amp;quot;Google Translate で一時的なエラーが発生しました。&amp;quot;);&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  if (!raw) {&lt;br /&gt;
    throw new Error(&amp;quot;Google Translate から空の応答が返されました。&amp;quot;);&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  if (raw.charAt(0) === &amp;quot;&amp;lt;&amp;quot;) {&lt;br /&gt;
    console.log(raw.slice(0, 180));&lt;br /&gt;
    throw new Error(&lt;br /&gt;
      &amp;quot;Google Translate が HTML エラーを返しました（リクエストが拒否された可能性があります）。&amp;quot;&lt;br /&gt;
    );&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  if (statusCode &amp;amp;&amp;amp; statusCode !== 200) {&lt;br /&gt;
    throwInvalidGoogleResponse(raw);&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  let data;&lt;br /&gt;
  try {&lt;br /&gt;
    data = JSON.parse(raw);&lt;br /&gt;
  } catch (error) {&lt;br /&gt;
    throwInvalidGoogleResponse(raw);&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  const result = parseGoogleTranslateHtmlResponse(data);&lt;br /&gt;
  if (!result.text.trim()) {&lt;br /&gt;
    throw new Error(&amp;quot;翻訳結果が空でした。&amp;quot;);&lt;br /&gt;
  }&lt;br /&gt;
  return result;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function parseGoogleTranslateHtmlResponse(data) {&lt;br /&gt;
  if (!Array.isArray(data) || !Array.isArray(data[0])) {&lt;br /&gt;
    throwInvalidGoogleResponse(JSON.stringify(data));&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  const html = data[0][0];&lt;br /&gt;
  if (typeof html !== &amp;quot;string&amp;quot;) {&lt;br /&gt;
    throwInvalidGoogleResponse(JSON.stringify(data));&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  let detectedLanguage;&lt;br /&gt;
  if (Array.isArray(data[1]) &amp;amp;&amp;amp; typeof data[1][0] === &amp;quot;string&amp;quot;) {&lt;br /&gt;
    const detected = data[1][0].trim();&lt;br /&gt;
    if (detected) {&lt;br /&gt;
      detectedLanguage = detected;&lt;br /&gt;
    }&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  return {&lt;br /&gt;
    text: unwrapGoogleTranslatedHtml(html),&lt;br /&gt;
    detectedLanguage: detectedLanguage,&lt;br /&gt;
  };&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function unwrapGoogleTranslatedHtml(html) {&lt;br /&gt;
  let result = String(html);&lt;br /&gt;
  if (result.indexOf(&amp;quot;&amp;lt;pre&amp;quot;) !== -1) {&lt;br /&gt;
    result = result.replace(/&amp;lt;\/pre&amp;gt;/i, &amp;quot;&amp;quot;);&lt;br /&gt;
    const gt = result.indexOf(&amp;quot;&amp;gt;&amp;quot;);&lt;br /&gt;
    if (gt !== -1) {&lt;br /&gt;
      result = result.slice(gt + 1);&lt;br /&gt;
    }&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  const sentences = [];&lt;br /&gt;
  let idx = 0;&lt;br /&gt;
  while (true) {&lt;br /&gt;
    const sentenceStartIndex = result.indexOf(&amp;quot;&amp;lt;b&amp;gt;&amp;quot;, idx);&lt;br /&gt;
    if (sentenceStartIndex === -1) {&lt;br /&gt;
      break;&lt;br /&gt;
    }&lt;br /&gt;
    const sentenceFinalIndex = result.indexOf(&amp;quot;&amp;lt;i&amp;gt;&amp;quot;, sentenceStartIndex);&lt;br /&gt;
    if (sentenceFinalIndex === -1) {&lt;br /&gt;
      sentences.push(result.slice(sentenceStartIndex + 3));&lt;br /&gt;
      break;&lt;br /&gt;
    }&lt;br /&gt;
    sentences.push(result.slice(sentenceStartIndex + 3, sentenceFinalIndex));&lt;br /&gt;
    idx = sentenceFinalIndex;&lt;br /&gt;
  }&lt;br /&gt;
  if (sentences.length &amp;gt; 0) {&lt;br /&gt;
    result = sentences.join(&amp;quot; &amp;quot;);&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  result = result.replace(/&amp;lt;\/b&amp;gt;/g, &amp;quot;&amp;quot;);&lt;br /&gt;
  result = result.replace(/&amp;lt;a\s+i=\d+&amp;gt;/g, &amp;quot;&amp;quot;).replace(/&amp;lt;\/a&amp;gt;/g, &amp;quot;&amp;quot;);&lt;br /&gt;
  return unescapeHtml(result);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function escapeHtml(text) {&lt;br /&gt;
  return String(text)&lt;br /&gt;
    .replace(/&amp;amp;/g, &amp;quot;&amp;amp;amp;&amp;quot;)&lt;br /&gt;
    .replace(/&amp;lt;/g, &amp;quot;&amp;amp;lt;&amp;quot;)&lt;br /&gt;
    .replace(/&amp;gt;/g, &amp;quot;&amp;amp;gt;&amp;quot;)&lt;br /&gt;
    .replace(/&amp;quot;/g, &amp;quot;&amp;amp;quot;&amp;quot;)&lt;br /&gt;
    .replace(/&#039;/g, &amp;quot;&amp;amp;#39;&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function unescapeHtml(text) {&lt;br /&gt;
  return String(text)&lt;br /&gt;
    .replace(/&amp;amp;lt;/g, &amp;quot;&amp;lt;&amp;quot;)&lt;br /&gt;
    .replace(/&amp;amp;gt;/g, &amp;quot;&amp;gt;&amp;quot;)&lt;br /&gt;
    .replace(/&amp;amp;quot;/g, &#039;&amp;quot;&#039;)&lt;br /&gt;
    .replace(/&amp;amp;#39;/g, &amp;quot;&#039;&amp;quot;)&lt;br /&gt;
    .replace(/&amp;amp;amp;/g, &amp;quot;&amp;amp;&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function executeCurl(args) {&lt;br /&gt;
  const cmd = [&amp;quot;curl.exe&amp;quot;].concat(args).join(&amp;quot; &amp;quot;);&lt;br /&gt;
  const exec = shell.Exec(cmd, &amp;quot;&amp;quot;, true);&lt;br /&gt;
  const exitCode = Number(&lt;br /&gt;
    exec.ExitCode != null ? exec.ExitCode : exec.exitCode&lt;br /&gt;
  );&lt;br /&gt;
  const stdout = exec.StdOut != null ? exec.StdOut : exec.stdOut;&lt;br /&gt;
  const stderr = exec.StdErr != null ? exec.StdErr : exec.stdErr;&lt;br /&gt;
  return {&lt;br /&gt;
    exitCode: exitCode,&lt;br /&gt;
    stdout: String(stdout == null ? &amp;quot;&amp;quot; : stdout).replace(/^\uFEFF/, &amp;quot;&amp;quot;),&lt;br /&gt;
    stderr: String(stderr == null ? &amp;quot;&amp;quot; : stderr),&lt;br /&gt;
  };&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function splitCurlHttpResponse(raw) {&lt;br /&gt;
  const text = String(raw == null ? &amp;quot;&amp;quot; : raw).replace(/^\uFEFF/, &amp;quot;&amp;quot;);&lt;br /&gt;
  const markerIndex = text.lastIndexOf(CURL_HTTP_CODE_MARKER);&lt;br /&gt;
  if (markerIndex !== -1) {&lt;br /&gt;
    const codeStr = text.slice(markerIndex + CURL_HTTP_CODE_MARKER.length).trim();&lt;br /&gt;
    const body = text.slice(0, markerIndex).trim();&lt;br /&gt;
    if (/^\d{3}$/.test(codeStr)) {&lt;br /&gt;
      return { body: body, statusCode: Number(codeStr) };&lt;br /&gt;
    }&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  // -w の改行がスペースになった場合の保険&lt;br /&gt;
  const fallback = text.match(/^([\s\S]*?)(?:\s|\r|\n)+(\d{3})\s*$/);&lt;br /&gt;
  if (fallback &amp;amp;&amp;amp; fallback[1] !== &amp;quot;&amp;quot;) {&lt;br /&gt;
    return { body: fallback[1].trim(), statusCode: Number(fallback[2]) };&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  const trimmed = text.trim();&lt;br /&gt;
  if (/^\d{3}$/.test(trimmed)) {&lt;br /&gt;
    return { body: &amp;quot;&amp;quot;, statusCode: Number(trimmed) };&lt;br /&gt;
  }&lt;br /&gt;
  return { body: trimmed, statusCode: 0 };&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function isGoogleAuthError(statusCode, body) {&lt;br /&gt;
  if (statusCode === 401 || statusCode === 403) {&lt;br /&gt;
    return true;&lt;br /&gt;
  }&lt;br /&gt;
  if (statusCode !== 400) {&lt;br /&gt;
    return false;&lt;br /&gt;
  }&lt;br /&gt;
  const text = String(body || &amp;quot;&amp;quot;);&lt;br /&gt;
  return (&lt;br /&gt;
    text.indexOf(&amp;quot;API_KEY_INVALID&amp;quot;) !== -1 ||&lt;br /&gt;
    text.indexOf(&amp;quot;API key not valid&amp;quot;) !== -1 ||&lt;br /&gt;
    text.indexOf(&amp;quot;API key expired&amp;quot;) !== -1&lt;br /&gt;
  );&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function createAuthError(message) {&lt;br /&gt;
  const error = new Error(message);&lt;br /&gt;
  error.isAuthError = true;&lt;br /&gt;
  return error;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function throwInvalidGoogleResponse(raw) {&lt;br /&gt;
  const preview = String(raw == null ? &amp;quot;&amp;quot; : raw)&lt;br /&gt;
    .replace(/\s+/g, &amp;quot; &amp;quot;)&lt;br /&gt;
    .slice(0, 180);&lt;br /&gt;
  console.log(preview);&lt;br /&gt;
  throw new Error(&lt;br /&gt;
    &amp;quot;Google Translate が不正な JSON を返しました。\n&amp;quot; + preview&lt;br /&gt;
  );&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getWindowTagStore() {&lt;br /&gt;
  try {&lt;br /&gt;
    if (Tag &amp;amp;&amp;amp; typeof Tag.exists === &amp;quot;function&amp;quot;) {&lt;br /&gt;
      return Tag;&lt;br /&gt;
    }&lt;br /&gt;
  } catch (error) {&lt;br /&gt;
    return null;&lt;br /&gt;
  }&lt;br /&gt;
  return null;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function tagExists(store, key) {&lt;br /&gt;
  try {&lt;br /&gt;
    return !!(store &amp;amp;&amp;amp; store.exists(key));&lt;br /&gt;
  } catch (error) {&lt;br /&gt;
    return false;&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function tagRemove(store, key) {&lt;br /&gt;
  if (!store || !tagExists(store, key)) {&lt;br /&gt;
    return;&lt;br /&gt;
  }&lt;br /&gt;
  try {&lt;br /&gt;
    if (typeof store.remove === &amp;quot;function&amp;quot;) {&lt;br /&gt;
      store.remove(key);&lt;br /&gt;
    }&lt;br /&gt;
  } catch (error) {&lt;br /&gt;
    store[key] = &amp;quot;&amp;quot;;&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function resolveTranslationDirection(detectedLanguage) {&lt;br /&gt;
  if (normalizeSourceLanguage(CONFIG.sourceLanguage) !== &amp;quot;auto&amp;quot;) {&lt;br /&gt;
    return {&lt;br /&gt;
      sourceLanguage: CONFIG.sourceLanguage,&lt;br /&gt;
      targetLanguage: CONFIG.targetLanguage,&lt;br /&gt;
    };&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  const detected = detectedLanguage &amp;amp;&amp;amp; detectedLanguage.trim();&lt;br /&gt;
  if (detected &amp;amp;&amp;amp; languagesEqual(detected, CONFIG.targetLanguage)) {&lt;br /&gt;
    return {&lt;br /&gt;
      sourceLanguage: detected,&lt;br /&gt;
      targetLanguage: CONFIG.reverseLanguage,&lt;br /&gt;
    };&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  return {&lt;br /&gt;
    sourceLanguage: detected || &amp;quot;auto&amp;quot;,&lt;br /&gt;
    targetLanguage: CONFIG.targetLanguage,&lt;br /&gt;
  };&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function needsReverseTranslation(detectedLanguage) {&lt;br /&gt;
  if (normalizeSourceLanguage(CONFIG.sourceLanguage) !== &amp;quot;auto&amp;quot;) {&lt;br /&gt;
    return false;&lt;br /&gt;
  }&lt;br /&gt;
  const detected = detectedLanguage &amp;amp;&amp;amp; detectedLanguage.trim();&lt;br /&gt;
  if (!detected) {&lt;br /&gt;
    return false;&lt;br /&gt;
  }&lt;br /&gt;
  return languagesEqual(detected, CONFIG.targetLanguage);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/**&lt;br /&gt;
 * auto→ja のあと検出言語が ja なら en へ再翻訳している。&lt;br /&gt;
 * ひらがな・カタカナ・漢字の割合が高ければ、1 回目の API を省略する。&lt;br /&gt;
 */&lt;br /&gt;
function shouldSkipToReverseTranslation(text, sourceLanguage) {&lt;br /&gt;
  if (normalizeSourceLanguage(sourceLanguage) !== &amp;quot;auto&amp;quot;) {&lt;br /&gt;
    return false;&lt;br /&gt;
  }&lt;br /&gt;
  if (!languagesEqual(CONFIG.targetLanguage, &amp;quot;ja&amp;quot;)) {&lt;br /&gt;
    return false;&lt;br /&gt;
  }&lt;br /&gt;
  return looksLikeJapaneseText(text);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function looksLikeJapaneseText(text) {&lt;br /&gt;
  let japanese = 0;&lt;br /&gt;
  let total = 0;&lt;br /&gt;
  const s = String(text);&lt;br /&gt;
  for (let i = 0; i &amp;lt; s.length; i++) {&lt;br /&gt;
    const code = s.charCodeAt(i);&lt;br /&gt;
    if (code &amp;lt;= 32 || code === 127) {&lt;br /&gt;
      continue;&lt;br /&gt;
    }&lt;br /&gt;
    total += 1;&lt;br /&gt;
    if (isJapaneseCharCode(code)) {&lt;br /&gt;
      japanese += 1;&lt;br /&gt;
    }&lt;br /&gt;
  }&lt;br /&gt;
  if (total === 0) {&lt;br /&gt;
    return false;&lt;br /&gt;
  }&lt;br /&gt;
  return japanese / total &amp;gt;= JAPANESE_CHAR_RATIO_THRESHOLD;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function isJapaneseCharCode(code) {&lt;br /&gt;
  // ひらがな&lt;br /&gt;
  if (code &amp;gt;= 0x3040 &amp;amp;&amp;amp; code &amp;lt;= 0x309f) {&lt;br /&gt;
    return true;&lt;br /&gt;
  }&lt;br /&gt;
  // カタカナ&lt;br /&gt;
  if (code &amp;gt;= 0x30a0 &amp;amp;&amp;amp; code &amp;lt;= 0x30ff) {&lt;br /&gt;
    return true;&lt;br /&gt;
  }&lt;br /&gt;
  // CJK 統合漢字&lt;br /&gt;
  if (code &amp;gt;= 0x4e00 &amp;amp;&amp;amp; code &amp;lt;= 0x9fff) {&lt;br /&gt;
    return true;&lt;br /&gt;
  }&lt;br /&gt;
  // 半角カタカナ&lt;br /&gt;
  if (code &amp;gt;= 0xff66 &amp;amp;&amp;amp; code &amp;lt;= 0xff9d) {&lt;br /&gt;
    return true;&lt;br /&gt;
  }&lt;br /&gt;
  // CJK 記号・句読点（、。・など）&lt;br /&gt;
  if (code &amp;gt;= 0x3000 &amp;amp;&amp;amp; code &amp;lt;= 0x303f) {&lt;br /&gt;
    return true;&lt;br /&gt;
  }&lt;br /&gt;
  return false;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function languagesEqual(a, b) {&lt;br /&gt;
  return a.trim().toLowerCase() === b.trim().toLowerCase();&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function normalizeSourceLanguage(value) {&lt;br /&gt;
  const trimmed = String(value == null ? &amp;quot;&amp;quot; : value).trim();&lt;br /&gt;
  return trimmed === &amp;quot;&amp;quot; ? &amp;quot;auto&amp;quot; : trimmed;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/**&lt;br /&gt;
 * 単独の変数風識別子（camelCase / snake_case 等）なら単語に分割する。&lt;br /&gt;
 */&lt;br /&gt;
function prepareTextForTranslation(text) {&lt;br /&gt;
  const trimmed = text.trim();&lt;br /&gt;
  if (!isVariableStyleIdentifier(trimmed)) {&lt;br /&gt;
    return text;&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  const words = splitVariableIdentifier(trimmed);&lt;br /&gt;
  return words.length &amp;gt; 0 ? words.join(&amp;quot; &amp;quot;) : text;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function isVariableStyleIdentifier(text) {&lt;br /&gt;
  if (!text || !/^[A-Za-z_][A-Za-z0-9_-]*$/.test(text)) {&lt;br /&gt;
    return false;&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  return (&lt;br /&gt;
    text.includes(&amp;quot;_&amp;quot;) ||&lt;br /&gt;
    text.includes(&amp;quot;-&amp;quot;) ||&lt;br /&gt;
    /[a-z][A-Z]/.test(text) ||&lt;br /&gt;
    /[A-Z]{2,}[a-z]/.test(text)&lt;br /&gt;
  );&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function splitVariableIdentifier(text) {&lt;br /&gt;
  return text&lt;br /&gt;
    .replace(/([a-z0-9])([A-Z])/g, &amp;quot;$1 $2&amp;quot;)&lt;br /&gt;
    .replace(/([A-Z]+)([A-Z][a-z])/g, &amp;quot;$1 $2&amp;quot;)&lt;br /&gt;
    .split(/[\s_-]+/)&lt;br /&gt;
    .filter(Boolean);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/**&lt;br /&gt;
 * 選択範囲の最終行末の直後へ、vscode-translator と同じ形式で挿入する。&lt;br /&gt;
 */&lt;br /&gt;
function insertTranslationBelow(translatedText, sourceLanguage, targetLanguage) {&lt;br /&gt;
  let endY = sel.GetBottomPointY(mePosLogical);&lt;br /&gt;
  const endX = sel.GetBottomPointX(mePosLogical);&lt;br /&gt;
&lt;br /&gt;
  // 行全体選択で次行先頭 (x=1) に終わっている場合は前の行末へ&lt;br /&gt;
  if (endX === 1 &amp;amp;&amp;amp; endY &amp;gt; sel.GetTopPointY(mePosLogical)) {&lt;br /&gt;
    endY = endY - 1;&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  sel.SetActivePoint(mePosLogical, 1, endY);&lt;br /&gt;
  sel.EndOfLine(false, mePosLogical);&lt;br /&gt;
&lt;br /&gt;
  const block = formatTranslationBlock(&lt;br /&gt;
    translatedText,&lt;br /&gt;
    sourceLanguage,&lt;br /&gt;
    targetLanguage&lt;br /&gt;
  );&lt;br /&gt;
  const body = &amp;quot;\n&amp;quot; + block.full;&lt;br /&gt;
&lt;br /&gt;
  // 文書末尾なら、フッター直後に空行を残せるよう末尾改行を付ける&lt;br /&gt;
  const lineCount = document.GetLines();&lt;br /&gt;
  const appendTrailingNewline = endY &amp;gt;= lineCount;&lt;br /&gt;
  sel.Text = appendTrailingNewline ? body + &amp;quot;\n&amp;quot; : body;&lt;br /&gt;
&lt;br /&gt;
  // フッターの次の行先頭へカーソルを置く&lt;br /&gt;
  const translationLineCount = String(translatedText).split(&amp;quot;\n&amp;quot;).length;&lt;br /&gt;
  const cursorY = endY + 3 + translationLineCount;&lt;br /&gt;
  sel.SetActivePoint(mePosLogical, 1, cursorY, false);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function repeatChar(ch, count) {&lt;br /&gt;
  let out = &amp;quot;&amp;quot;;&lt;br /&gt;
  for (let i = 0; i &amp;lt; count; i++) {&lt;br /&gt;
    out += ch;&lt;br /&gt;
  }&lt;br /&gt;
  return out;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/**&lt;br /&gt;
 * Windows の C ランタイムが解釈するコマンドライン引数用に引用する。&lt;br /&gt;
 * JSON の &amp;quot; や \ を含む --data-binary を安全に渡すために使う。&lt;br /&gt;
 */&lt;br /&gt;
function quoteWindowsArgument(value) {&lt;br /&gt;
  const s = String(value);&lt;br /&gt;
  if (s === &amp;quot;&amp;quot;) {&lt;br /&gt;
    return &#039;&amp;quot;&amp;quot;&#039;;&lt;br /&gt;
  }&lt;br /&gt;
  if (!/[\s&amp;quot;]/.test(s)) {&lt;br /&gt;
    return s;&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  let result = &#039;&amp;quot;&#039;;&lt;br /&gt;
  let numBackslashes = 0;&lt;br /&gt;
  for (let i = 0; i &amp;lt; s.length; i++) {&lt;br /&gt;
    const ch = s.charAt(i);&lt;br /&gt;
    if (ch === &amp;quot;\\&amp;quot;) {&lt;br /&gt;
      numBackslashes += 1;&lt;br /&gt;
      continue;&lt;br /&gt;
    }&lt;br /&gt;
    if (ch === &#039;&amp;quot;&#039;) {&lt;br /&gt;
      result += repeatChar(&amp;quot;\\&amp;quot;, numBackslashes * 2 + 1) + &#039;&amp;quot;&#039;;&lt;br /&gt;
      numBackslashes = 0;&lt;br /&gt;
      continue;&lt;br /&gt;
    }&lt;br /&gt;
    result += repeatChar(&amp;quot;\\&amp;quot;, numBackslashes) + ch;&lt;br /&gt;
    numBackslashes = 0;&lt;br /&gt;
  }&lt;br /&gt;
  result += repeatChar(&amp;quot;\\&amp;quot;, numBackslashes * 2) + &#039;&amp;quot;&#039;;&lt;br /&gt;
  return result;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 更新履歴 ==&lt;br /&gt;
&lt;br /&gt;
* 2026-08-28 翻訳APIのエンドポイントおよび実行方式を変更しました&lt;br /&gt;
* 2026-08-12 出力方法を設定できるようにしました&lt;br /&gt;
* 2026-08-11 公開&lt;/div&gt;</summary>
		<author><name>Yuko</name></author>
	</entry>
	<entry>
		<id>https://www.haijin-boys.com/wiki/index.php?title=Mery_%E7%BF%BB%E8%A8%B3&amp;diff=9608</id>
		<title>Mery 翻訳</title>
		<link rel="alternate" type="text/html" href="https://www.haijin-boys.com/wiki/index.php?title=Mery_%E7%BF%BB%E8%A8%B3&amp;diff=9608"/>
		<updated>2026-08-12T00:04:07Z</updated>

		<summary type="html">&lt;p&gt;Yuko: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== 概要 ==&lt;br /&gt;
&lt;br /&gt;
「Mery 翻訳」は、Mery エディタ上で選択したテキストやカーソル位置の行を翻訳するためのマクロです。&lt;br /&gt;
&lt;br /&gt;
Google 翻訳のエンドポイントを &#039;&#039;curl.exe&#039;&#039; から呼び出し、翻訳結果を元の文章の直下へ挿入します。英語のコメントやドキュメントを読みながら、その場ですぐ日本語訳を確認したい場合などに便利です。&lt;br /&gt;
&lt;br /&gt;
例えば、次のような英文を選択してマクロを実行すると、&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Returns the current user information.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
直下に翻訳結果が挿入されます。&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Returns the current user information.&lt;br /&gt;
----- 翻訳: en -&amp;gt; ja -----&lt;br /&gt;
現在のユーザー情報を返します。&lt;br /&gt;
--------------------------&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
翻訳元の言語は標準で自動判定されます。&lt;br /&gt;
&lt;br /&gt;
また、判定された言語が翻訳先の言語と同じ場合は、自動的に逆方向へ翻訳します。標準設定では、英語などの文章は日本語へ、日本語の文章は英語へ翻訳されます。&lt;br /&gt;
&lt;br /&gt;
プログラムのソースコードを読む用途も想定しており、&#039;&#039;camelCase&#039;&#039; や &#039;&#039;snake_case&#039;&#039; などの変数名を単独で翻訳した場合は、適宜単語へ分割してから翻訳します。&lt;br /&gt;
&lt;br /&gt;
例えば、&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
currentUserName&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
は内部的に、&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
current User Name&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
として翻訳されるため、一般的な英文だけでなく、変数名やメソッド名の意味を確認したい場合にも利用できます。&lt;br /&gt;
&lt;br /&gt;
本マクロをショートカットキーに設定し、いつでも起動できるようにしておくと、英文コメントやエラーメッセージ、変数名などを確認するときに便利です。&lt;br /&gt;
&lt;br /&gt;
== 注意事項 ==&lt;br /&gt;
&lt;br /&gt;
Mery Ver 3.8.8 で動作確認しています。&lt;br /&gt;
&lt;br /&gt;
本マクロでは、Google 翻訳の非公式エンドポイントへアクセスしています。公式 API として提供しているものではないため、エンドポイントの仕様変更などによって将来的に利用できなくなる可能性があります。&lt;br /&gt;
&lt;br /&gt;
また、翻訳するテキストは Google 翻訳へ送信されます。機密情報、個人情報、パスワード、API キーなど、外部サービスへ送信してはいけない情報には使用しないでください。&lt;br /&gt;
&lt;br /&gt;
Google 翻訳へのアクセスには Windows 10 以降で搭載されている &#039;&#039;curl.exe&#039;&#039; を使用します。&lt;br /&gt;
&lt;br /&gt;
なお、短時間に大量の翻訳を実行すると、Google 側のレート制限によって一時的に利用できなくなる可能性があります。&lt;br /&gt;
&lt;br /&gt;
== 使い方 ==&lt;br /&gt;
&lt;br /&gt;
=== 0. 事前準備 ===&lt;br /&gt;
&lt;br /&gt;
本マクロでは外部ライブラリのインストールは必要ありません。&lt;br /&gt;
&lt;br /&gt;
Google 翻訳への通信に Windows の &#039;&#039;curl.exe&#039;&#039; を利用するため、あらかじめ &#039;&#039;curl.exe&#039;&#039; が利用可能であることを確認してください。&lt;br /&gt;
&lt;br /&gt;
また、マクロ冒頭にある &#039;&#039;CONFIG&#039;&#039; を変更することで、翻訳する言語や表示方式などを設定できます。&lt;br /&gt;
&lt;br /&gt;
標準設定は次のとおりです。&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
const CONFIG = {&lt;br /&gt;
  /** 翻訳元言語。空文字または &amp;quot;auto&amp;quot; で自動判定 */&lt;br /&gt;
  sourceLanguage: &amp;quot;auto&amp;quot;,&lt;br /&gt;
  /** 翻訳先言語 */&lt;br /&gt;
  targetLanguage: &amp;quot;ja&amp;quot;,&lt;br /&gt;
  /** 検出言語が targetLanguage と同じときの翻訳先 */&lt;br /&gt;
  reverseLanguage: &amp;quot;en&amp;quot;,&lt;br /&gt;
  /** curl のタイムアウト（秒） */&lt;br /&gt;
  timeoutSec: 10,&lt;br /&gt;
&lt;br /&gt;
  /**&lt;br /&gt;
   * 翻訳結果の表示方法（複数同時に有効化可）&lt;br /&gt;
   * - editor: エディターへ挿入（選択範囲の直下）&lt;br /&gt;
   * - outputBar: アウトプットバーへ出力&lt;br /&gt;
   * - tip: ツールチップで表示&lt;br /&gt;
   */&lt;br /&gt;
  display: {&lt;br /&gt;
    editor: true,&lt;br /&gt;
    outputBar: false,&lt;br /&gt;
    tip: false,&lt;br /&gt;
  },&lt;br /&gt;
&lt;br /&gt;
  /**&lt;br /&gt;
   * アウトプットバー出力時に自動で表示するか&lt;br /&gt;
   * true で表示、false で表示しない&lt;br /&gt;
   */&lt;br /&gt;
  outputBarVisible: true,&lt;br /&gt;
&lt;br /&gt;
  /**&lt;br /&gt;
   * tip 表示時の位置フラグ&lt;br /&gt;
   * meShowTipPosCaret (キャレット位置) | meShowTipPosMouse (マウス位置)&lt;br /&gt;
   */&lt;br /&gt;
  tipFlags: meShowTipPosCaret,&lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
標準では、翻訳元の言語を自動判定し、日本語以外の文章を日本語へ翻訳します。&lt;br /&gt;
&lt;br /&gt;
日本語が検出された場合は &#039;&#039;reverseLanguage&#039;&#039; が使用され、日本語から英語へ翻訳されます。&lt;br /&gt;
&lt;br /&gt;
=== 1. 選択範囲を翻訳する場合 ===&lt;br /&gt;
&lt;br /&gt;
翻訳したいテキストを選択します。&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
The request was successfully completed.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
マクロを実行すると、選択範囲の最終行の直下に翻訳結果が挿入されます。&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
The request was successfully completed.&lt;br /&gt;
----- 翻訳: en -&amp;gt; ja -----&lt;br /&gt;
リクエストは正常に完了しました。&lt;br /&gt;
--------------------------&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
複数行をまとめて選択して翻訳することもできます。&lt;br /&gt;
&lt;br /&gt;
翻訳中はカーソル付近に「選択範囲を翻訳中...」と表示され、翻訳が完了するとステータスバーに「翻訳完了」と表示されます。&lt;br /&gt;
&lt;br /&gt;
=== 2. カーソル位置の行を翻訳する場合 ===&lt;br /&gt;
&lt;br /&gt;
テキストを選択していない状態でマクロを実行すると、カーソルがある行全体を翻訳します。&lt;br /&gt;
&lt;br /&gt;
例えば、次の行にカーソルを置きます。&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
This method returns the current configuration.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
マクロを実行すると、次のようになります。&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
This method returns the current configuration.&lt;br /&gt;
----- 翻訳: en -&amp;gt; ja -----&lt;br /&gt;
このメソッドは現在の設定を返します。&lt;br /&gt;
--------------------------&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
空行でマクロを実行した場合は翻訳を行わず、ステータスバーに「翻訳するテキストがありません」と表示されます。&lt;br /&gt;
&lt;br /&gt;
=== 3. 日本語を英語へ翻訳する場合 ===&lt;br /&gt;
&lt;br /&gt;
標準設定では翻訳先言語に日本語 (&#039;&#039;ja&#039;&#039;) が設定されています。&lt;br /&gt;
&lt;br /&gt;
翻訳元の言語を自動判定している場合、Google 翻訳によって日本語が検出されると、自動的に &#039;&#039;reverseLanguage&#039;&#039; で指定した言語へ翻訳します。&lt;br /&gt;
&lt;br /&gt;
標準では &#039;&#039;reverseLanguage&#039;&#039; が英語 (&#039;&#039;en&#039;&#039;) のため、例えば、&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ユーザー情報を取得する&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
を翻訳すると、&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ユーザー情報を取得する&lt;br /&gt;
----- 翻訳: ja -&amp;gt; en -----&lt;br /&gt;
Get user information&lt;br /&gt;
--------------------------&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
のように英語へ翻訳されます。&lt;br /&gt;
&lt;br /&gt;
このため、マクロを切り替えたり設定を書き換えたりすることなく、日本語と英語を相互に翻訳できます。&lt;br /&gt;
&lt;br /&gt;
=== 4. 翻訳する言語を固定する場合 ===&lt;br /&gt;
&lt;br /&gt;
翻訳元の言語を固定したい場合は、&#039;&#039;sourceLanguage&#039;&#039; を変更します。&lt;br /&gt;
&lt;br /&gt;
例えば、英語から日本語への翻訳に固定する場合は次のように設定します。&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sourceLanguage: &amp;quot;en&amp;quot;,&lt;br /&gt;
targetLanguage: &amp;quot;ja&amp;quot;,&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
この場合、言語の自動判定や逆方向への翻訳は行われません。&lt;br /&gt;
&lt;br /&gt;
翻訳元を自動判定する場合は、次のいずれかを指定します。&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sourceLanguage: &amp;quot;auto&amp;quot;,&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
または、&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sourceLanguage: &amp;quot;&amp;quot;,&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
翻訳先を変更したい場合は &#039;&#039;targetLanguage&#039;&#039; を変更します。&lt;br /&gt;
&lt;br /&gt;
例えば、中国語へ翻訳する場合は、&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
targetLanguage: &amp;quot;zh-CN&amp;quot;,&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
のように設定できます。&lt;br /&gt;
&lt;br /&gt;
=== 5. 変数名を翻訳する場合 ===&lt;br /&gt;
&lt;br /&gt;
本マクロでは、プログラムで使用される変数名やメソッド名を翻訳しやすくするため、単独の識別子を自動的に単語へ分割する機能があります。&lt;br /&gt;
&lt;br /&gt;
例えば、&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
currentUserName&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
を翻訳すると、内部的には、&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
current User Name&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
として Google 翻訳へ送信されます。&lt;br /&gt;
&lt;br /&gt;
同様に、&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
current_user_name&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
や、&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
current-user-name&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
も、&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
current user name&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
として翻訳されます。&lt;br /&gt;
&lt;br /&gt;
次のような形式に対応しています。&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;camelCase&#039;&#039;&lt;br /&gt;
* &#039;&#039;PascalCase&#039;&#039;&lt;br /&gt;
* &#039;&#039;snake_case&#039;&#039;&lt;br /&gt;
* &#039;&#039;kebab-case&#039;&#039;&lt;br /&gt;
* &#039;&#039;XMLHttpRequest&#039;&#039; のように大文字が連続する識別子&lt;br /&gt;
&lt;br /&gt;
例えば、ソースコード中に、&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
const requestTimeoutSec = 10;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
という変数があり、&#039;&#039;requestTimeoutSec&#039;&#039; の意味を確認したい場合、その部分だけを選択してマクロを実行できます。&lt;br /&gt;
&lt;br /&gt;
通常の英文や文章については、この変換は行われません。&lt;br /&gt;
&lt;br /&gt;
=== 6. 翻訳結果の表示形式 ===&lt;br /&gt;
&lt;br /&gt;
翻訳結果は、元の文章の直下に次の形式で挿入されます。&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
----- 翻訳: 翻訳元言語 -&amp;gt; 翻訳先言語 -----&lt;br /&gt;
翻訳結果&lt;br /&gt;
------------------------------------------&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
例えば英語から日本語へ翻訳した場合は、&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
----- 翻訳: en -&amp;gt; ja -----&lt;br /&gt;
翻訳された文章&lt;br /&gt;
--------------------------&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
と表示されます。&lt;br /&gt;
&lt;br /&gt;
翻訳元の言語は Google 翻訳による自動判定結果が使用されます。&lt;br /&gt;
&lt;br /&gt;
複数行の文章を翻訳した場合も、翻訳結果全体をヘッダーとフッターで囲んだ状態で挿入します。&lt;br /&gt;
&lt;br /&gt;
翻訳後は、挿入された翻訳結果の次の行へカーソルが移動するため、そのまま文章の編集を続けられます。&lt;br /&gt;
&lt;br /&gt;
=== 7. エラーが発生した場合 ===&lt;br /&gt;
&lt;br /&gt;
通信エラーなどが発生した場合は、翻訳結果を挿入せず、エラーメッセージを表示します。&lt;br /&gt;
&lt;br /&gt;
主に次のようなエラーを検出します。&lt;br /&gt;
&lt;br /&gt;
* Google 翻訳への接続がタイムアウトした&lt;br /&gt;
* &#039;&#039;curl.exe&#039;&#039; の実行に失敗した&lt;br /&gt;
* Google 翻訳から空の応答が返された&lt;br /&gt;
* Google 翻訳から HTML のエラーページが返された&lt;br /&gt;
* Google 翻訳から想定していない形式のデータが返された&lt;br /&gt;
* Google 翻訳のレート制限に達した&lt;br /&gt;
* 翻訳結果が空だった&lt;br /&gt;
&lt;br /&gt;
通信のタイムアウト時間は &#039;&#039;CONFIG.timeoutSec&#039;&#039; で変更できます。&lt;br /&gt;
&lt;br /&gt;
例えば、20 秒に変更する場合は次のように設定します。&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
timeoutSec: 20,&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
通常は標準値の 10 秒のままで問題ありません。&lt;br /&gt;
&lt;br /&gt;
== ソースコード ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot; copy&amp;gt;&lt;br /&gt;
#language = &amp;quot;quickjs&amp;quot;&lt;br /&gt;
#title = &amp;quot;Mery 翻訳&amp;quot;&lt;br /&gt;
/**&lt;br /&gt;
 * Mery 翻訳マクロ&lt;br /&gt;
 *&lt;br /&gt;
 * VSCode 拡張「Comment Translate」と同じ Google 翻訳の非公式エンドポイントを、&lt;br /&gt;
 * shell.Exec + curl で呼び出して翻訳します。&lt;br /&gt;
 * （q は encodeURIComponent して渡します。stdin の q@- は Mery 経由だと欠落します）&lt;br /&gt;
 *&lt;br /&gt;
 * 使い方:&lt;br /&gt;
 *   1. 翻訳したい文字列を選択（未選択ならカーソル行を選択）&lt;br /&gt;
 *   2. このマクロを実行&lt;br /&gt;
 *&lt;br /&gt;
 * 必要環境:&lt;br /&gt;
 *   - Mery Ver 3.8.8 以降（左記バージョンにて動作確認）&lt;br /&gt;
 *   - PATH 上の curl.exe（Windows 10 以降に標準搭載）&lt;br /&gt;
 *&lt;br /&gt;
 * 設定は下の CONFIG を編集してください。&lt;br /&gt;
 * display は複数同時に true にできます（組み合わせ可）。&lt;br /&gt;
 */&lt;br /&gt;
&lt;br /&gt;
const CONFIG = {&lt;br /&gt;
  /** 翻訳元言語。空文字または &amp;quot;auto&amp;quot; で自動判定 */&lt;br /&gt;
  sourceLanguage: &amp;quot;auto&amp;quot;,&lt;br /&gt;
  /** 翻訳先言語 */&lt;br /&gt;
  targetLanguage: &amp;quot;ja&amp;quot;,&lt;br /&gt;
  /** 検出言語が targetLanguage と同じときの翻訳先 */&lt;br /&gt;
  reverseLanguage: &amp;quot;en&amp;quot;,&lt;br /&gt;
  /** curl のタイムアウト（秒） */&lt;br /&gt;
  timeoutSec: 10,&lt;br /&gt;
&lt;br /&gt;
  /**&lt;br /&gt;
   * 翻訳結果の表示方法（複数同時に有効化可）&lt;br /&gt;
   * - editor: エディターへ挿入（選択範囲の直下）&lt;br /&gt;
   * - outputBar: アウトプットバーへ出力&lt;br /&gt;
   * - tip: ツールチップで表示&lt;br /&gt;
   */&lt;br /&gt;
  display: {&lt;br /&gt;
    editor: true,&lt;br /&gt;
    outputBar: false,&lt;br /&gt;
    tip: false,&lt;br /&gt;
  },&lt;br /&gt;
&lt;br /&gt;
  /**&lt;br /&gt;
   * アウトプットバー出力時に自動で表示するか&lt;br /&gt;
   * true で表示、false で表示しない&lt;br /&gt;
   */&lt;br /&gt;
  outputBarVisible: true,&lt;br /&gt;
&lt;br /&gt;
  /**&lt;br /&gt;
   * tip 表示時の位置フラグ&lt;br /&gt;
   * meShowTipPosCaret (キャレット位置) | meShowTipPosMouse (マウス位置)&lt;br /&gt;
   */&lt;br /&gt;
  tipFlags: meShowTipPosCaret,&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
const ENDPOINT = &amp;quot;https://translate.google.com/translate_a/single&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
const sel = document.selection;&lt;br /&gt;
&lt;br /&gt;
main();&lt;br /&gt;
&lt;br /&gt;
function main() {&lt;br /&gt;
  let keepResultTip = false;&lt;br /&gt;
  try {&lt;br /&gt;
    const target = resolveTranslateTarget();&lt;br /&gt;
    if (!target) {&lt;br /&gt;
      Status = &amp;quot;翻訳するテキストがありません&amp;quot;;&lt;br /&gt;
      return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (!hasAnyDisplayTarget()) {&lt;br /&gt;
      throw new Error(&lt;br /&gt;
        &amp;quot;CONFIG.display のいずれか（editor / outputBar / tip）を true にしてください。&amp;quot;&lt;br /&gt;
      );&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    let tipMessage = &amp;quot;&amp;quot;;&lt;br /&gt;
    if (sel.IsEmpty) {&lt;br /&gt;
      tipMessage = &amp;quot;この行を翻訳中...&amp;quot;;&lt;br /&gt;
    } else {&lt;br /&gt;
      tipMessage = &amp;quot;選択範囲を翻訳中...&amp;quot;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    ShowTip(tipMessage, meShowTipPosCaret);&lt;br /&gt;
    Status = &amp;quot;翻訳中…&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
    const outcome = runTranslation(target.text);&lt;br /&gt;
    keepResultTip = presentTranslation(&lt;br /&gt;
      outcome.translatedText,&lt;br /&gt;
      outcome.sourceLanguage,&lt;br /&gt;
      outcome.targetLanguage&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
    Status = &amp;quot;翻訳完了&amp;quot;;&lt;br /&gt;
  } catch (error) {&lt;br /&gt;
    const message =&lt;br /&gt;
      error &amp;amp;&amp;amp; error.message ? error.message : String(error);&lt;br /&gt;
    Status = &amp;quot;&amp;quot;;&lt;br /&gt;
    alert(&amp;quot;Translator: &amp;quot; + message);&lt;br /&gt;
  } finally {&lt;br /&gt;
    // tip 結果表示を残す場合は非表示にしない&lt;br /&gt;
    if (!keepResultTip) {&lt;br /&gt;
      ShowTip(&amp;quot;&amp;quot;, meShowTipHide);&lt;br /&gt;
    }&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function hasAnyDisplayTarget() {&lt;br /&gt;
  const display = CONFIG.display || {};&lt;br /&gt;
  return !!(display.editor || display.outputBar || display.tip);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/**&lt;br /&gt;
 * CONFIG.display に従って翻訳結果を出力する。&lt;br /&gt;
 * tip を使った場合は true を返し、進捗チップの非表示を抑止する。&lt;br /&gt;
 */&lt;br /&gt;
function presentTranslation(translatedText, sourceLanguage, targetLanguage) {&lt;br /&gt;
  const display = CONFIG.display || {};&lt;br /&gt;
  const block = formatTranslationBlock(&lt;br /&gt;
    translatedText,&lt;br /&gt;
    sourceLanguage,&lt;br /&gt;
    targetLanguage&lt;br /&gt;
  );&lt;br /&gt;
&lt;br /&gt;
  if (display.editor) {&lt;br /&gt;
    insertTranslationBelow(translatedText, sourceLanguage, targetLanguage);&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  if (display.outputBar) {&lt;br /&gt;
    writeTranslationToOutputBar(block);&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  if (display.tip) {&lt;br /&gt;
    const flags =&lt;br /&gt;
      CONFIG.tipFlags == null ? meShowTipPosCaret : CONFIG.tipFlags;&lt;br /&gt;
    ShowTip(&lt;br /&gt;
      formatTranslationTip(translatedText, sourceLanguage, targetLanguage),&lt;br /&gt;
      flags&lt;br /&gt;
    );&lt;br /&gt;
    return true;&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  return false;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function formatTranslationBlock(translatedText, sourceLanguage, targetLanguage) {&lt;br /&gt;
  const header =&lt;br /&gt;
    &amp;quot;----- 翻訳: &amp;quot; +&lt;br /&gt;
    sourceLanguage +&lt;br /&gt;
    &amp;quot; -&amp;gt; &amp;quot; +&lt;br /&gt;
    targetLanguage +&lt;br /&gt;
    &amp;quot; -----&amp;quot;;&lt;br /&gt;
  const footer = repeatChar(&amp;quot;-&amp;quot;, header.length);&lt;br /&gt;
  return {&lt;br /&gt;
    header: header,&lt;br /&gt;
    footer: footer,&lt;br /&gt;
    translatedText: translatedText,&lt;br /&gt;
    full: header + &amp;quot;\n&amp;quot; + translatedText + &amp;quot;\n&amp;quot; + footer,&lt;br /&gt;
  };&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/** ツールチップ用の短い表示形式 */&lt;br /&gt;
function formatTranslationTip(translatedText, sourceLanguage, targetLanguage) {&lt;br /&gt;
  return (&lt;br /&gt;
    &amp;quot;[翻訳: &amp;quot; +&lt;br /&gt;
    sourceLanguage +&lt;br /&gt;
    &amp;quot; -&amp;gt; &amp;quot; +&lt;br /&gt;
    targetLanguage +&lt;br /&gt;
    &amp;quot;]\n&amp;quot; +&lt;br /&gt;
    translatedText&lt;br /&gt;
  );&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function writeTranslationToOutputBar(block) {&lt;br /&gt;
  // Visible は CONFIG.outputBarVisible が true のときだけ立てる&lt;br /&gt;
  if (CONFIG.outputBarVisible) {&lt;br /&gt;
    OutputBar.Visible = true;&lt;br /&gt;
  }&lt;br /&gt;
  OutputBar.Writeln(block.full);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/**&lt;br /&gt;
 * 選択があればそれを使う。未選択ならカーソル行を選択してから返す。&lt;br /&gt;
 */&lt;br /&gt;
function resolveTranslateTarget() {&lt;br /&gt;
  if (!sel.IsEmpty) {&lt;br /&gt;
    const text = normalizeSelectionText(sel.Text);&lt;br /&gt;
    if (!text.trim()) {&lt;br /&gt;
      return null;&lt;br /&gt;
    }&lt;br /&gt;
    return { text: text };&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  const y = sel.GetActivePointY(mePosLogical);&lt;br /&gt;
  const line = document.GetLine(y, 0).trim();&lt;br /&gt;
  if (!line || !String(line).trim()) {&lt;br /&gt;
    return null;&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  return { text: normalizeSelectionText(line) };&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function normalizeSelectionText(text) {&lt;br /&gt;
  return String(text).replace(/\r\n$/, &amp;quot;&amp;quot;).replace(/\n$/, &amp;quot;&amp;quot;).replace(/\r$/, &amp;quot;&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function runTranslation(rawText) {&lt;br /&gt;
  const sourceLanguage = normalizeSourceLanguage(CONFIG.sourceLanguage);&lt;br /&gt;
  const text = prepareTextForTranslation(rawText);&lt;br /&gt;
&lt;br /&gt;
  const first = translate(text, sourceLanguage, CONFIG.targetLanguage);&lt;br /&gt;
  let translatedText = first.text;&lt;br /&gt;
  let direction = resolveTranslationDirection(first.detectedLanguage);&lt;br /&gt;
&lt;br /&gt;
  if (needsReverseTranslation(first.detectedLanguage)) {&lt;br /&gt;
    const second = translate(text, &amp;quot;auto&amp;quot;, CONFIG.reverseLanguage);&lt;br /&gt;
    translatedText = second.text;&lt;br /&gt;
    direction = {&lt;br /&gt;
      sourceLanguage:&lt;br /&gt;
        second.detectedLanguage || first.detectedLanguage || &amp;quot;auto&amp;quot;,&lt;br /&gt;
      targetLanguage: CONFIG.reverseLanguage,&lt;br /&gt;
    };&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  return {&lt;br /&gt;
    translatedText: translatedText,&lt;br /&gt;
    sourceLanguage: direction.sourceLanguage,&lt;br /&gt;
    targetLanguage: direction.targetLanguage,&lt;br /&gt;
  };&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function translate(text, sourceLanguage, targetLanguage) {&lt;br /&gt;
  // q@-（stdin）は Mery の shell.Exec 経由だと curl に届かず、&lt;br /&gt;
  // Google が HTML 400 を返して JSON パースに失敗するため使わない。&lt;br /&gt;
  // q は encodeURIComponent 済みで渡し、コマンドラインへの生テキスト埋め込みを避ける。&lt;br /&gt;
  const cmd = [&lt;br /&gt;
    &amp;quot;curl.exe&amp;quot;,&lt;br /&gt;
    &amp;quot;-sS&amp;quot;,&lt;br /&gt;
    &amp;quot;-G&amp;quot;,&lt;br /&gt;
    &amp;quot;-A&amp;quot;,&lt;br /&gt;
    quoteArg(&amp;quot;Mozilla/5.0&amp;quot;),&lt;br /&gt;
    &amp;quot;--max-time&amp;quot;,&lt;br /&gt;
    String(CONFIG.timeoutSec),&lt;br /&gt;
    quoteArg(ENDPOINT),&lt;br /&gt;
    &amp;quot;--data-urlencode&amp;quot;,&lt;br /&gt;
    quoteArg(&amp;quot;client=gtx&amp;quot;),&lt;br /&gt;
    &amp;quot;--data-urlencode&amp;quot;,&lt;br /&gt;
    quoteArg(&amp;quot;sl=&amp;quot; + normalizeSourceLanguage(sourceLanguage)),&lt;br /&gt;
    &amp;quot;--data-urlencode&amp;quot;,&lt;br /&gt;
    quoteArg(&amp;quot;tl=&amp;quot; + targetLanguage),&lt;br /&gt;
    &amp;quot;--data-urlencode&amp;quot;,&lt;br /&gt;
    quoteArg(&amp;quot;dt=t&amp;quot;),&lt;br /&gt;
    &amp;quot;--data-urlencode&amp;quot;,&lt;br /&gt;
    quoteArg(&amp;quot;ie=UTF-8&amp;quot;),&lt;br /&gt;
    &amp;quot;--data-urlencode&amp;quot;,&lt;br /&gt;
    quoteArg(&amp;quot;oe=UTF-8&amp;quot;),&lt;br /&gt;
    &amp;quot;--data&amp;quot;,&lt;br /&gt;
    quoteArg(&amp;quot;q=&amp;quot; + encodeURIComponent(text)),&lt;br /&gt;
  ].join(&amp;quot; &amp;quot;);&lt;br /&gt;
&lt;br /&gt;
  const exec = shell.Exec(cmd, &amp;quot;&amp;quot;, true);&lt;br /&gt;
  const exitCode = Number(&lt;br /&gt;
    exec.ExitCode != null ? exec.ExitCode : exec.exitCode&lt;br /&gt;
  );&lt;br /&gt;
  const stdout = exec.StdOut != null ? exec.StdOut : exec.stdOut;&lt;br /&gt;
  const stderr = exec.StdErr != null ? exec.StdErr : exec.stdErr;&lt;br /&gt;
  const raw = String(stdout == null ? &amp;quot;&amp;quot; : stdout)&lt;br /&gt;
    .replace(/^\uFEFF/, &amp;quot;&amp;quot;)&lt;br /&gt;
    .trim();&lt;br /&gt;
&lt;br /&gt;
  if (exitCode === 28) {&lt;br /&gt;
    throw new Error(&lt;br /&gt;
      &amp;quot;Google Translate への接続がタイムアウトしました（&amp;quot; +&lt;br /&gt;
        CONFIG.timeoutSec +&lt;br /&gt;
        &amp;quot; 秒）。&amp;quot;&lt;br /&gt;
    );&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  if (exitCode !== 0) {&lt;br /&gt;
    throw new Error(&lt;br /&gt;
      &amp;quot;curl が失敗しました (exit &amp;quot; +&lt;br /&gt;
        exitCode +&lt;br /&gt;
        &amp;quot;)。&amp;quot; +&lt;br /&gt;
        (stderr ? &amp;quot;\n&amp;quot; + String(stderr).trim() : &amp;quot;&amp;quot;)&lt;br /&gt;
    );&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  if (raw.indexOf(&amp;quot;429&amp;quot;) === 0) {&lt;br /&gt;
    throw new Error(&amp;quot;Google Translate のレート制限に達しました。&amp;quot;);&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  if (!raw) {&lt;br /&gt;
    throw new Error(&amp;quot;Google Translate から空の応答が返されました。&amp;quot;);&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  if (raw.charAt(0) === &amp;quot;&amp;lt;&amp;quot;) {&lt;br /&gt;
    throw new Error(&lt;br /&gt;
      &amp;quot;Google Translate が HTML エラーを返しました（リクエストが拒否された可能性があります）。&amp;quot;&lt;br /&gt;
    );&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  let data;&lt;br /&gt;
  try {&lt;br /&gt;
    data = JSON.parse(raw);&lt;br /&gt;
  } catch (error) {&lt;br /&gt;
    const preview = raw.replace(/\s+/g, &amp;quot; &amp;quot;).slice(0, 180);&lt;br /&gt;
    throw new Error(&lt;br /&gt;
      &amp;quot;Google Translate が不正な JSON を返しました。\n&amp;quot; + preview&lt;br /&gt;
    );&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  const result = parseGoogleResponse(data);&lt;br /&gt;
  if (!result.text.trim()) {&lt;br /&gt;
    throw new Error(&amp;quot;翻訳結果が空でした。&amp;quot;);&lt;br /&gt;
  }&lt;br /&gt;
  return result;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/**&lt;br /&gt;
 * 非公式エンドポイントのレスポンスをパースする。&lt;br /&gt;
 * 形の概略: [[[&amp;quot;訳文&amp;quot;,&amp;quot;原文&amp;quot;,...], ...], null, &amp;quot;detectedLang&amp;quot;, ...]&lt;br /&gt;
 */&lt;br /&gt;
function parseGoogleResponse(data) {&lt;br /&gt;
  if (!Array.isArray(data)) {&lt;br /&gt;
    throw new Error(&amp;quot;Google Translate の応答が配列ではありません。&amp;quot;);&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  const segments = data[0];&lt;br /&gt;
  if (!Array.isArray(segments)) {&lt;br /&gt;
    throw new Error(&amp;quot;翻訳セグメントがありません。&amp;quot;);&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  const parts = [];&lt;br /&gt;
  for (const segment of segments) {&lt;br /&gt;
    if (!Array.isArray(segment) || typeof segment[0] !== &amp;quot;string&amp;quot;) {&lt;br /&gt;
      throw new Error(&amp;quot;不正な翻訳セグメントが含まれています。&amp;quot;);&lt;br /&gt;
    }&lt;br /&gt;
    parts.push(segment[0]);&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  return {&lt;br /&gt;
    text: parts.join(&amp;quot;&amp;quot;),&lt;br /&gt;
    detectedLanguage: extractDetectedLanguage(data),&lt;br /&gt;
  };&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function extractDetectedLanguage(data) {&lt;br /&gt;
  const candidate = data[2];&lt;br /&gt;
  if (typeof candidate === &amp;quot;string&amp;quot; &amp;amp;&amp;amp; candidate.trim() !== &amp;quot;&amp;quot;) {&lt;br /&gt;
    return candidate;&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  const nested = data[8];&lt;br /&gt;
  if (Array.isArray(nested) &amp;amp;&amp;amp; Array.isArray(nested[0])) {&lt;br /&gt;
    const first = nested[0][0];&lt;br /&gt;
    if (typeof first === &amp;quot;string&amp;quot; &amp;amp;&amp;amp; first.trim() !== &amp;quot;&amp;quot;) {&lt;br /&gt;
      return first;&lt;br /&gt;
    }&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  return undefined;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function resolveTranslationDirection(detectedLanguage) {&lt;br /&gt;
  if (normalizeSourceLanguage(CONFIG.sourceLanguage) !== &amp;quot;auto&amp;quot;) {&lt;br /&gt;
    return {&lt;br /&gt;
      sourceLanguage: CONFIG.sourceLanguage,&lt;br /&gt;
      targetLanguage: CONFIG.targetLanguage,&lt;br /&gt;
    };&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  const detected = detectedLanguage &amp;amp;&amp;amp; detectedLanguage.trim();&lt;br /&gt;
  if (detected &amp;amp;&amp;amp; languagesEqual(detected, CONFIG.targetLanguage)) {&lt;br /&gt;
    return {&lt;br /&gt;
      sourceLanguage: detected,&lt;br /&gt;
      targetLanguage: CONFIG.reverseLanguage,&lt;br /&gt;
    };&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  return {&lt;br /&gt;
    sourceLanguage: detected || &amp;quot;auto&amp;quot;,&lt;br /&gt;
    targetLanguage: CONFIG.targetLanguage,&lt;br /&gt;
  };&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function needsReverseTranslation(detectedLanguage) {&lt;br /&gt;
  if (normalizeSourceLanguage(CONFIG.sourceLanguage) !== &amp;quot;auto&amp;quot;) {&lt;br /&gt;
    return false;&lt;br /&gt;
  }&lt;br /&gt;
  const detected = detectedLanguage &amp;amp;&amp;amp; detectedLanguage.trim();&lt;br /&gt;
  if (!detected) {&lt;br /&gt;
    return false;&lt;br /&gt;
  }&lt;br /&gt;
  return languagesEqual(detected, CONFIG.targetLanguage);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function languagesEqual(a, b) {&lt;br /&gt;
  return a.trim().toLowerCase() === b.trim().toLowerCase();&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function normalizeSourceLanguage(value) {&lt;br /&gt;
  const trimmed = String(value == null ? &amp;quot;&amp;quot; : value).trim();&lt;br /&gt;
  return trimmed === &amp;quot;&amp;quot; ? &amp;quot;auto&amp;quot; : trimmed;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/**&lt;br /&gt;
 * 単独の変数風識別子（camelCase / snake_case 等）なら単語に分割する。&lt;br /&gt;
 */&lt;br /&gt;
function prepareTextForTranslation(text) {&lt;br /&gt;
  const trimmed = text.trim();&lt;br /&gt;
  if (!isVariableStyleIdentifier(trimmed)) {&lt;br /&gt;
    return text;&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  const words = splitVariableIdentifier(trimmed);&lt;br /&gt;
  return words.length &amp;gt; 0 ? words.join(&amp;quot; &amp;quot;) : text;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function isVariableStyleIdentifier(text) {&lt;br /&gt;
  if (!text || !/^[A-Za-z_][A-Za-z0-9_-]*$/.test(text)) {&lt;br /&gt;
    return false;&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  return (&lt;br /&gt;
    text.includes(&amp;quot;_&amp;quot;) ||&lt;br /&gt;
    text.includes(&amp;quot;-&amp;quot;) ||&lt;br /&gt;
    /[a-z][A-Z]/.test(text) ||&lt;br /&gt;
    /[A-Z]{2,}[a-z]/.test(text)&lt;br /&gt;
  );&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function splitVariableIdentifier(text) {&lt;br /&gt;
  return text&lt;br /&gt;
    .replace(/([a-z0-9])([A-Z])/g, &amp;quot;$1 $2&amp;quot;)&lt;br /&gt;
    .replace(/([A-Z]+)([A-Z][a-z])/g, &amp;quot;$1 $2&amp;quot;)&lt;br /&gt;
    .split(/[\s_-]+/)&lt;br /&gt;
    .filter(Boolean);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/**&lt;br /&gt;
 * 選択範囲の最終行末の直後へ、vscode-translator と同じ形式で挿入する。&lt;br /&gt;
 */&lt;br /&gt;
function insertTranslationBelow(translatedText, sourceLanguage, targetLanguage) {&lt;br /&gt;
  let endY = sel.GetBottomPointY(mePosLogical);&lt;br /&gt;
  const endX = sel.GetBottomPointX(mePosLogical);&lt;br /&gt;
&lt;br /&gt;
  // 行全体選択で次行先頭 (x=1) に終わっている場合は前の行末へ&lt;br /&gt;
  if (endX === 1 &amp;amp;&amp;amp; endY &amp;gt; sel.GetTopPointY(mePosLogical)) {&lt;br /&gt;
    endY = endY - 1;&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  sel.SetActivePoint(mePosLogical, 1, endY);&lt;br /&gt;
  sel.EndOfLine(false, mePosLogical);&lt;br /&gt;
&lt;br /&gt;
  const block = formatTranslationBlock(&lt;br /&gt;
    translatedText,&lt;br /&gt;
    sourceLanguage,&lt;br /&gt;
    targetLanguage&lt;br /&gt;
  );&lt;br /&gt;
  const body = &amp;quot;\n&amp;quot; + block.full;&lt;br /&gt;
&lt;br /&gt;
  // 文書末尾なら、フッター直後に空行を残せるよう末尾改行を付ける&lt;br /&gt;
  const lineCount = document.GetLines();&lt;br /&gt;
  const appendTrailingNewline = endY &amp;gt;= lineCount;&lt;br /&gt;
  sel.Text = appendTrailingNewline ? body + &amp;quot;\n&amp;quot; : body;&lt;br /&gt;
&lt;br /&gt;
  // フッターの次の行先頭へカーソルを置く&lt;br /&gt;
  const translationLineCount = String(translatedText).split(&amp;quot;\n&amp;quot;).length;&lt;br /&gt;
  const cursorY = endY + 3 + translationLineCount;&lt;br /&gt;
  sel.SetActivePoint(mePosLogical, 1, cursorY, false);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function repeatChar(ch, count) {&lt;br /&gt;
  let out = &amp;quot;&amp;quot;;&lt;br /&gt;
  for (let i = 0; i &amp;lt; count; i++) {&lt;br /&gt;
    out += ch;&lt;br /&gt;
  }&lt;br /&gt;
  return out;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function quoteArg(value) {&lt;br /&gt;
  // Windows のコマンドライン向けにダブルクォートで囲む&lt;br /&gt;
  return &#039;&amp;quot;&#039; + String(value).replace(/&amp;quot;/g, &#039;\\&amp;quot;&#039;) + &#039;&amp;quot;&#039;;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 更新履歴 ==&lt;br /&gt;
&lt;br /&gt;
* 2026-08-12 出力方法を設定できるようにしました&lt;br /&gt;
* 2026-08-11 公開&lt;/div&gt;</summary>
		<author><name>Yuko</name></author>
	</entry>
	<entry>
		<id>https://www.haijin-boys.com/wiki/index.php?title=Mery_%E7%BF%BB%E8%A8%B3&amp;diff=9607</id>
		<title>Mery 翻訳</title>
		<link rel="alternate" type="text/html" href="https://www.haijin-boys.com/wiki/index.php?title=Mery_%E7%BF%BB%E8%A8%B3&amp;diff=9607"/>
		<updated>2026-08-11T12:44:47Z</updated>

		<summary type="html">&lt;p&gt;Yuko: ページの作成:「== 概要 ==  「Mery 翻訳」は、Mery エディタ上で選択したテキストやカーソル位置の行を翻訳するためのマクロです。  Google 翻訳のエンドポイントを &amp;#039;&amp;#039;curl.exe&amp;#039;&amp;#039; から呼び出し、翻訳結果を元の文章の直下へ挿入します。英語のコメントやドキュメントを読みながら、その場ですぐ日本語訳を確認したい場合などに便利です。  例えば、次のような英文を選…」&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== 概要 ==&lt;br /&gt;
&lt;br /&gt;
「Mery 翻訳」は、Mery エディタ上で選択したテキストやカーソル位置の行を翻訳するためのマクロです。&lt;br /&gt;
&lt;br /&gt;
Google 翻訳のエンドポイントを &#039;&#039;curl.exe&#039;&#039; から呼び出し、翻訳結果を元の文章の直下へ挿入します。英語のコメントやドキュメントを読みながら、その場ですぐ日本語訳を確認したい場合などに便利です。&lt;br /&gt;
&lt;br /&gt;
例えば、次のような英文を選択してマクロを実行すると、&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Returns the current user information.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
直下に翻訳結果が挿入されます。&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Returns the current user information.&lt;br /&gt;
----- 翻訳: en -&amp;gt; ja -----&lt;br /&gt;
現在のユーザー情報を返します。&lt;br /&gt;
--------------------------&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
翻訳元の言語は標準で自動判定されます。&lt;br /&gt;
&lt;br /&gt;
また、判定された言語が翻訳先の言語と同じ場合は、自動的に逆方向へ翻訳します。標準設定では、英語などの文章は日本語へ、日本語の文章は英語へ翻訳されます。&lt;br /&gt;
&lt;br /&gt;
プログラムのソースコードを読む用途も想定しており、&#039;&#039;camelCase&#039;&#039; や &#039;&#039;snake_case&#039;&#039; などの変数名を単独で翻訳した場合は、適宜単語へ分割してから翻訳します。&lt;br /&gt;
&lt;br /&gt;
例えば、&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
currentUserName&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
は内部的に、&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
current User Name&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
として翻訳されるため、一般的な英文だけでなく、変数名やメソッド名の意味を確認したい場合にも利用できます。&lt;br /&gt;
&lt;br /&gt;
本マクロをショートカットキーに設定し、いつでも起動できるようにしておくと、英文コメントやエラーメッセージ、変数名などを確認するときに便利です。&lt;br /&gt;
&lt;br /&gt;
== 注意事項 ==&lt;br /&gt;
&lt;br /&gt;
Mery Ver 3.8.8 で動作確認しています。&lt;br /&gt;
&lt;br /&gt;
本マクロでは、Google 翻訳の非公式エンドポイントへアクセスしています。公式 API として提供しているものではないため、エンドポイントの仕様変更などによって将来的に利用できなくなる可能性があります。&lt;br /&gt;
&lt;br /&gt;
また、翻訳するテキストは Google 翻訳へ送信されます。機密情報、個人情報、パスワード、API キーなど、外部サービスへ送信してはいけない情報には使用しないでください。&lt;br /&gt;
&lt;br /&gt;
Google 翻訳へのアクセスには Windows 10 以降で搭載されている &#039;&#039;curl.exe&#039;&#039; を使用します。&lt;br /&gt;
&lt;br /&gt;
なお、短時間に大量の翻訳を実行すると、Google 側のレート制限によって一時的に利用できなくなる可能性があります。&lt;br /&gt;
&lt;br /&gt;
== 使い方 ==&lt;br /&gt;
&lt;br /&gt;
=== 0. 事前準備 ===&lt;br /&gt;
&lt;br /&gt;
本マクロでは外部ライブラリのインストールは必要ありません。&lt;br /&gt;
&lt;br /&gt;
Google 翻訳への通信に Windows の &#039;&#039;curl.exe&#039;&#039; を利用するため、あらかじめ &#039;&#039;curl.exe&#039;&#039; が利用可能であることを確認してください。&lt;br /&gt;
&lt;br /&gt;
また、マクロ冒頭にある &#039;&#039;CONFIG&#039;&#039; を変更することで、翻訳する言語などを設定できます。&lt;br /&gt;
&lt;br /&gt;
標準設定は次のとおりです。&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
const CONFIG = {&lt;br /&gt;
  /** 翻訳元言語。空文字または &amp;quot;auto&amp;quot; で自動判定 */&lt;br /&gt;
  sourceLanguage: &amp;quot;auto&amp;quot;,&lt;br /&gt;
&lt;br /&gt;
  /** 翻訳先言語 */&lt;br /&gt;
  targetLanguage: &amp;quot;ja&amp;quot;,&lt;br /&gt;
&lt;br /&gt;
  /** 検出言語が targetLanguage と同じときの翻訳先 */&lt;br /&gt;
  reverseLanguage: &amp;quot;en&amp;quot;,&lt;br /&gt;
&lt;br /&gt;
  /** curl のタイムアウト（秒） */&lt;br /&gt;
  timeoutSec: 10,&lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
標準では、翻訳元の言語を自動判定し、日本語以外の文章を日本語へ翻訳します。&lt;br /&gt;
&lt;br /&gt;
日本語が検出された場合は &#039;&#039;reverseLanguage&#039;&#039; が使用され、日本語から英語へ翻訳されます。&lt;br /&gt;
&lt;br /&gt;
=== 1. 選択範囲を翻訳する場合 ===&lt;br /&gt;
&lt;br /&gt;
翻訳したいテキストを選択します。&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
The request was successfully completed.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
マクロを実行すると、選択範囲の最終行の直下に翻訳結果が挿入されます。&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
The request was successfully completed.&lt;br /&gt;
----- 翻訳: en -&amp;gt; ja -----&lt;br /&gt;
リクエストは正常に完了しました。&lt;br /&gt;
--------------------------&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
複数行をまとめて選択して翻訳することもできます。&lt;br /&gt;
&lt;br /&gt;
翻訳中はカーソル付近に「選択範囲を翻訳中...」と表示され、翻訳が完了するとステータスバーに「翻訳完了」と表示されます。&lt;br /&gt;
&lt;br /&gt;
=== 2. カーソル位置の行を翻訳する場合 ===&lt;br /&gt;
&lt;br /&gt;
テキストを選択していない状態でマクロを実行すると、カーソルがある行全体を翻訳します。&lt;br /&gt;
&lt;br /&gt;
例えば、次の行にカーソルを置きます。&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
This method returns the current configuration.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
マクロを実行すると、次のようになります。&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
This method returns the current configuration.&lt;br /&gt;
----- 翻訳: en -&amp;gt; ja -----&lt;br /&gt;
このメソッドは現在の設定を返します。&lt;br /&gt;
--------------------------&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
空行でマクロを実行した場合は翻訳を行わず、ステータスバーに「翻訳するテキストがありません」と表示されます。&lt;br /&gt;
&lt;br /&gt;
=== 3. 日本語を英語へ翻訳する場合 ===&lt;br /&gt;
&lt;br /&gt;
標準設定では翻訳先言語に日本語 (&#039;&#039;ja&#039;&#039;) が設定されています。&lt;br /&gt;
&lt;br /&gt;
翻訳元の言語を自動判定している場合、Google 翻訳によって日本語が検出されると、自動的に &#039;&#039;reverseLanguage&#039;&#039; で指定した言語へ翻訳します。&lt;br /&gt;
&lt;br /&gt;
標準では &#039;&#039;reverseLanguage&#039;&#039; が英語 (&#039;&#039;en&#039;&#039;) のため、例えば、&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ユーザー情報を取得する&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
を翻訳すると、&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ユーザー情報を取得する&lt;br /&gt;
----- 翻訳: ja -&amp;gt; en -----&lt;br /&gt;
Get user information&lt;br /&gt;
--------------------------&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
のように英語へ翻訳されます。&lt;br /&gt;
&lt;br /&gt;
このため、マクロを切り替えたり設定を書き換えたりすることなく、日本語と英語を相互に翻訳できます。&lt;br /&gt;
&lt;br /&gt;
=== 4. 翻訳する言語を固定する場合 ===&lt;br /&gt;
&lt;br /&gt;
翻訳元の言語を固定したい場合は、&#039;&#039;sourceLanguage&#039;&#039; を変更します。&lt;br /&gt;
&lt;br /&gt;
例えば、英語から日本語への翻訳に固定する場合は次のように設定します。&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sourceLanguage: &amp;quot;en&amp;quot;,&lt;br /&gt;
targetLanguage: &amp;quot;ja&amp;quot;,&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
この場合、言語の自動判定や逆方向への翻訳は行われません。&lt;br /&gt;
&lt;br /&gt;
翻訳元を自動判定する場合は、次のいずれかを指定します。&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sourceLanguage: &amp;quot;auto&amp;quot;,&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
または、&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sourceLanguage: &amp;quot;&amp;quot;,&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
翻訳先を変更したい場合は &#039;&#039;targetLanguage&#039;&#039; を変更します。&lt;br /&gt;
&lt;br /&gt;
例えば、中国語へ翻訳する場合は、&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
targetLanguage: &amp;quot;zh-CN&amp;quot;,&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
のように設定できます。&lt;br /&gt;
&lt;br /&gt;
=== 5. 変数名を翻訳する場合 ===&lt;br /&gt;
&lt;br /&gt;
本マクロでは、プログラムで使用される変数名やメソッド名を翻訳しやすくするため、単独の識別子を自動的に単語へ分割する機能があります。&lt;br /&gt;
&lt;br /&gt;
例えば、&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
currentUserName&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
を翻訳すると、内部的には、&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
current User Name&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
として Google 翻訳へ送信されます。&lt;br /&gt;
&lt;br /&gt;
同様に、&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
current_user_name&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
や、&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
current-user-name&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
も、&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
current user name&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
として翻訳されます。&lt;br /&gt;
&lt;br /&gt;
次のような形式に対応しています。&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;camelCase&#039;&#039;&lt;br /&gt;
* &#039;&#039;PascalCase&#039;&#039;&lt;br /&gt;
* &#039;&#039;snake_case&#039;&#039;&lt;br /&gt;
* &#039;&#039;kebab-case&#039;&#039;&lt;br /&gt;
* &#039;&#039;XMLHttpRequest&#039;&#039; のように大文字が連続する識別子&lt;br /&gt;
&lt;br /&gt;
例えば、ソースコード中に、&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
const requestTimeoutSec = 10;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
という変数があり、&#039;&#039;requestTimeoutSec&#039;&#039; の意味を確認したい場合、その部分だけを選択してマクロを実行できます。&lt;br /&gt;
&lt;br /&gt;
通常の英文や文章については、この変換は行われません。&lt;br /&gt;
&lt;br /&gt;
=== 6. 翻訳結果の表示形式 ===&lt;br /&gt;
&lt;br /&gt;
翻訳結果は、元の文章の直下に次の形式で挿入されます。&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
----- 翻訳: 翻訳元言語 -&amp;gt; 翻訳先言語 -----&lt;br /&gt;
翻訳結果&lt;br /&gt;
------------------------------------------&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
例えば英語から日本語へ翻訳した場合は、&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
----- 翻訳: en -&amp;gt; ja -----&lt;br /&gt;
翻訳された文章&lt;br /&gt;
--------------------------&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
と表示されます。&lt;br /&gt;
&lt;br /&gt;
翻訳元の言語は Google 翻訳による自動判定結果が使用されます。&lt;br /&gt;
&lt;br /&gt;
複数行の文章を翻訳した場合も、翻訳結果全体をヘッダーとフッターで囲んだ状態で挿入します。&lt;br /&gt;
&lt;br /&gt;
翻訳後は、挿入された翻訳結果の次の行へカーソルが移動するため、そのまま文章の編集を続けられます。&lt;br /&gt;
&lt;br /&gt;
=== 7. エラーが発生した場合 ===&lt;br /&gt;
&lt;br /&gt;
通信エラーなどが発生した場合は、翻訳結果を挿入せず、エラーメッセージを表示します。&lt;br /&gt;
&lt;br /&gt;
主に次のようなエラーを検出します。&lt;br /&gt;
&lt;br /&gt;
* Google 翻訳への接続がタイムアウトした&lt;br /&gt;
* &#039;&#039;curl.exe&#039;&#039; の実行に失敗した&lt;br /&gt;
* Google 翻訳から空の応答が返された&lt;br /&gt;
* Google 翻訳から HTML のエラーページが返された&lt;br /&gt;
* Google 翻訳から想定していない形式のデータが返された&lt;br /&gt;
* Google 翻訳のレート制限に達した&lt;br /&gt;
* 翻訳結果が空だった&lt;br /&gt;
&lt;br /&gt;
通信のタイムアウト時間は &#039;&#039;CONFIG.timeoutSec&#039;&#039; で変更できます。&lt;br /&gt;
&lt;br /&gt;
例えば、20 秒に変更する場合は次のように設定します。&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
timeoutSec: 20,&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
通常は標準値の 10 秒のままで問題ありません。&lt;br /&gt;
&lt;br /&gt;
== ソースコード ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot; copy&amp;gt;&lt;br /&gt;
#language = &amp;quot;quickjs&amp;quot;&lt;br /&gt;
#title = &amp;quot;Mery 翻訳&amp;quot;&lt;br /&gt;
/**&lt;br /&gt;
 * Mery 翻訳マクロ&lt;br /&gt;
 *&lt;br /&gt;
 * VSCode 拡張「Comment Translate」と同じ Google 翻訳の非公式エンドポイントを、&lt;br /&gt;
 * shell.Exec + curl で呼び出して、選択範囲の直下に翻訳結果を挿入します。&lt;br /&gt;
 * （q は encodeURIComponent して渡します。stdin の q@- は Mery 経由だと欠落します）&lt;br /&gt;
 *&lt;br /&gt;
 * 使い方:&lt;br /&gt;
 *   1. 翻訳したい文字列を選択（未選択ならカーソル行を選択）&lt;br /&gt;
 *   2. このマクロを実行&lt;br /&gt;
 *&lt;br /&gt;
 * 必要環境:&lt;br /&gt;
 *   - Mery Ver 3.8.8 以降（左記バージョンにて動作確認）&lt;br /&gt;
 *   - PATH 上の curl.exe（Windows 10 以降に標準搭載）&lt;br /&gt;
 *&lt;br /&gt;
 * 設定は下の CONFIG を編集してください。&lt;br /&gt;
 */&lt;br /&gt;
&lt;br /&gt;
const CONFIG = {&lt;br /&gt;
  /** 翻訳元言語。空文字または &amp;quot;auto&amp;quot; で自動判定 */&lt;br /&gt;
  sourceLanguage: &amp;quot;auto&amp;quot;,&lt;br /&gt;
  /** 翻訳先言語 */&lt;br /&gt;
  targetLanguage: &amp;quot;ja&amp;quot;,&lt;br /&gt;
  /** 検出言語が targetLanguage と同じときの翻訳先 */&lt;br /&gt;
  reverseLanguage: &amp;quot;en&amp;quot;,&lt;br /&gt;
  /** curl のタイムアウト（秒） */&lt;br /&gt;
  timeoutSec: 10,&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
const ENDPOINT = &amp;quot;https://translate.google.com/translate_a/single&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
const sel = document.selection;&lt;br /&gt;
&lt;br /&gt;
main();&lt;br /&gt;
&lt;br /&gt;
function main() {&lt;br /&gt;
  try {&lt;br /&gt;
    const target = resolveTranslateTarget();&lt;br /&gt;
    if (!target) {&lt;br /&gt;
      Status = &amp;quot;翻訳するテキストがありません&amp;quot;;&lt;br /&gt;
      return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    let tipMessage = &amp;quot;&amp;quot;;&lt;br /&gt;
    if (sel.IsEmpty) {&lt;br /&gt;
      tipMessage = &amp;quot;この行を翻訳中...&amp;quot;;&lt;br /&gt;
    } else {&lt;br /&gt;
      tipMessage = &amp;quot;選択範囲を翻訳中...&amp;quot;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    ShowTip(tipMessage, meShowTipPosCaret);&lt;br /&gt;
    Status = &amp;quot;翻訳中…&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
    const outcome = runTranslation(target.text);&lt;br /&gt;
    insertTranslationBelow(&lt;br /&gt;
      outcome.translatedText,&lt;br /&gt;
      outcome.sourceLanguage,&lt;br /&gt;
      outcome.targetLanguage&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
    Status = &amp;quot;翻訳完了&amp;quot;;&lt;br /&gt;
  } catch (error) {&lt;br /&gt;
    const message =&lt;br /&gt;
      error &amp;amp;&amp;amp; error.message ? error.message : String(error);&lt;br /&gt;
    Status = &amp;quot;&amp;quot;;&lt;br /&gt;
    alert(&amp;quot;Translator: &amp;quot; + message);&lt;br /&gt;
  } finally {&lt;br /&gt;
    ShowTip(&amp;quot;&amp;quot;, meShowTipHide);&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/**&lt;br /&gt;
 * 選択があればそれを使う。未選択ならカーソル行を選択してから返す。&lt;br /&gt;
 */&lt;br /&gt;
function resolveTranslateTarget() {&lt;br /&gt;
  if (!sel.IsEmpty) {&lt;br /&gt;
    const text = normalizeSelectionText(sel.Text);&lt;br /&gt;
    if (!text.trim()) {&lt;br /&gt;
      return null;&lt;br /&gt;
    }&lt;br /&gt;
    return { text: text };&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  const y = sel.GetActivePointY(mePosLogical);&lt;br /&gt;
  const line = document.GetLine(y, 0).trim();&lt;br /&gt;
  if (!line || !String(line).trim()) {&lt;br /&gt;
    return null;&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  return { text: normalizeSelectionText(line) };&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function normalizeSelectionText(text) {&lt;br /&gt;
  return String(text).replace(/\r\n$/, &amp;quot;&amp;quot;).replace(/\n$/, &amp;quot;&amp;quot;).replace(/\r$/, &amp;quot;&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function runTranslation(rawText) {&lt;br /&gt;
  const sourceLanguage = normalizeSourceLanguage(CONFIG.sourceLanguage);&lt;br /&gt;
  const text = prepareTextForTranslation(rawText);&lt;br /&gt;
&lt;br /&gt;
  const first = translate(text, sourceLanguage, CONFIG.targetLanguage);&lt;br /&gt;
  let translatedText = first.text;&lt;br /&gt;
  let direction = resolveTranslationDirection(first.detectedLanguage);&lt;br /&gt;
&lt;br /&gt;
  if (needsReverseTranslation(first.detectedLanguage)) {&lt;br /&gt;
    const second = translate(text, &amp;quot;auto&amp;quot;, CONFIG.reverseLanguage);&lt;br /&gt;
    translatedText = second.text;&lt;br /&gt;
    direction = {&lt;br /&gt;
      sourceLanguage:&lt;br /&gt;
        second.detectedLanguage || first.detectedLanguage || &amp;quot;auto&amp;quot;,&lt;br /&gt;
      targetLanguage: CONFIG.reverseLanguage,&lt;br /&gt;
    };&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  return {&lt;br /&gt;
    translatedText: translatedText,&lt;br /&gt;
    sourceLanguage: direction.sourceLanguage,&lt;br /&gt;
    targetLanguage: direction.targetLanguage,&lt;br /&gt;
  };&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function translate(text, sourceLanguage, targetLanguage) {&lt;br /&gt;
  // q@-（stdin）は Mery の shell.Exec 経由だと curl に届かず、&lt;br /&gt;
  // Google が HTML 400 を返して JSON パースに失敗するため使わない。&lt;br /&gt;
  // q は encodeURIComponent 済みで渡し、コマンドラインへの生テキスト埋め込みを避ける。&lt;br /&gt;
  const cmd = [&lt;br /&gt;
    &amp;quot;curl.exe&amp;quot;,&lt;br /&gt;
    &amp;quot;-sS&amp;quot;,&lt;br /&gt;
    &amp;quot;-G&amp;quot;,&lt;br /&gt;
    &amp;quot;-A&amp;quot;,&lt;br /&gt;
    quoteArg(&amp;quot;Mozilla/5.0&amp;quot;),&lt;br /&gt;
    &amp;quot;--max-time&amp;quot;,&lt;br /&gt;
    String(CONFIG.timeoutSec),&lt;br /&gt;
    quoteArg(ENDPOINT),&lt;br /&gt;
    &amp;quot;--data-urlencode&amp;quot;,&lt;br /&gt;
    quoteArg(&amp;quot;client=gtx&amp;quot;),&lt;br /&gt;
    &amp;quot;--data-urlencode&amp;quot;,&lt;br /&gt;
    quoteArg(&amp;quot;sl=&amp;quot; + normalizeSourceLanguage(sourceLanguage)),&lt;br /&gt;
    &amp;quot;--data-urlencode&amp;quot;,&lt;br /&gt;
    quoteArg(&amp;quot;tl=&amp;quot; + targetLanguage),&lt;br /&gt;
    &amp;quot;--data-urlencode&amp;quot;,&lt;br /&gt;
    quoteArg(&amp;quot;dt=t&amp;quot;),&lt;br /&gt;
    &amp;quot;--data-urlencode&amp;quot;,&lt;br /&gt;
    quoteArg(&amp;quot;ie=UTF-8&amp;quot;),&lt;br /&gt;
    &amp;quot;--data-urlencode&amp;quot;,&lt;br /&gt;
    quoteArg(&amp;quot;oe=UTF-8&amp;quot;),&lt;br /&gt;
    &amp;quot;--data&amp;quot;,&lt;br /&gt;
    quoteArg(&amp;quot;q=&amp;quot; + encodeURIComponent(text)),&lt;br /&gt;
  ].join(&amp;quot; &amp;quot;);&lt;br /&gt;
&lt;br /&gt;
  const exec = shell.Exec(cmd, &amp;quot;&amp;quot;, true);&lt;br /&gt;
  const exitCode = Number(&lt;br /&gt;
    exec.ExitCode != null ? exec.ExitCode : exec.exitCode&lt;br /&gt;
  );&lt;br /&gt;
  const stdout = exec.StdOut != null ? exec.StdOut : exec.stdOut;&lt;br /&gt;
  const stderr = exec.StdErr != null ? exec.StdErr : exec.stdErr;&lt;br /&gt;
  const raw = String(stdout == null ? &amp;quot;&amp;quot; : stdout)&lt;br /&gt;
    .replace(/^\uFEFF/, &amp;quot;&amp;quot;)&lt;br /&gt;
    .trim();&lt;br /&gt;
&lt;br /&gt;
  if (exitCode === 28) {&lt;br /&gt;
    throw new Error(&lt;br /&gt;
      &amp;quot;Google Translate への接続がタイムアウトしました（&amp;quot; +&lt;br /&gt;
        CONFIG.timeoutSec +&lt;br /&gt;
        &amp;quot; 秒）。&amp;quot;&lt;br /&gt;
    );&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  if (exitCode !== 0) {&lt;br /&gt;
    throw new Error(&lt;br /&gt;
      &amp;quot;curl が失敗しました (exit &amp;quot; +&lt;br /&gt;
        exitCode +&lt;br /&gt;
        &amp;quot;)。&amp;quot; +&lt;br /&gt;
        (stderr ? &amp;quot;\n&amp;quot; + String(stderr).trim() : &amp;quot;&amp;quot;)&lt;br /&gt;
    );&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  if (raw.indexOf(&amp;quot;429&amp;quot;) === 0) {&lt;br /&gt;
    throw new Error(&amp;quot;Google Translate のレート制限に達しました。&amp;quot;);&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  if (!raw) {&lt;br /&gt;
    throw new Error(&amp;quot;Google Translate から空の応答が返されました。&amp;quot;);&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  if (raw.charAt(0) === &amp;quot;&amp;lt;&amp;quot;) {&lt;br /&gt;
    throw new Error(&lt;br /&gt;
      &amp;quot;Google Translate が HTML エラーを返しました（リクエストが拒否された可能性があります）。&amp;quot;&lt;br /&gt;
    );&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  let data;&lt;br /&gt;
  try {&lt;br /&gt;
    data = JSON.parse(raw);&lt;br /&gt;
  } catch (error) {&lt;br /&gt;
    const preview = raw.replace(/\s+/g, &amp;quot; &amp;quot;).slice(0, 180);&lt;br /&gt;
    throw new Error(&lt;br /&gt;
      &amp;quot;Google Translate が不正な JSON を返しました。\n&amp;quot; + preview&lt;br /&gt;
    );&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  const result = parseGoogleResponse(data);&lt;br /&gt;
  if (!result.text.trim()) {&lt;br /&gt;
    throw new Error(&amp;quot;翻訳結果が空でした。&amp;quot;);&lt;br /&gt;
  }&lt;br /&gt;
  return result;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/**&lt;br /&gt;
 * 非公式エンドポイントのレスポンスをパースする。&lt;br /&gt;
 * 形の概略: [[[&amp;quot;訳文&amp;quot;,&amp;quot;原文&amp;quot;,...], ...], null, &amp;quot;detectedLang&amp;quot;, ...]&lt;br /&gt;
 */&lt;br /&gt;
function parseGoogleResponse(data) {&lt;br /&gt;
  if (!Array.isArray(data)) {&lt;br /&gt;
    throw new Error(&amp;quot;Google Translate の応答が配列ではありません。&amp;quot;);&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  const segments = data[0];&lt;br /&gt;
  if (!Array.isArray(segments)) {&lt;br /&gt;
    throw new Error(&amp;quot;翻訳セグメントがありません。&amp;quot;);&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  const parts = [];&lt;br /&gt;
  for (const segment of segments) {&lt;br /&gt;
    if (!Array.isArray(segment) || typeof segment[0] !== &amp;quot;string&amp;quot;) {&lt;br /&gt;
      throw new Error(&amp;quot;不正な翻訳セグメントが含まれています。&amp;quot;);&lt;br /&gt;
    }&lt;br /&gt;
    parts.push(segment[0]);&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  return {&lt;br /&gt;
    text: parts.join(&amp;quot;&amp;quot;),&lt;br /&gt;
    detectedLanguage: extractDetectedLanguage(data),&lt;br /&gt;
  };&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function extractDetectedLanguage(data) {&lt;br /&gt;
  const candidate = data[2];&lt;br /&gt;
  if (typeof candidate === &amp;quot;string&amp;quot; &amp;amp;&amp;amp; candidate.trim() !== &amp;quot;&amp;quot;) {&lt;br /&gt;
    return candidate;&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  const nested = data[8];&lt;br /&gt;
  if (Array.isArray(nested) &amp;amp;&amp;amp; Array.isArray(nested[0])) {&lt;br /&gt;
    const first = nested[0][0];&lt;br /&gt;
    if (typeof first === &amp;quot;string&amp;quot; &amp;amp;&amp;amp; first.trim() !== &amp;quot;&amp;quot;) {&lt;br /&gt;
      return first;&lt;br /&gt;
    }&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  return undefined;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function resolveTranslationDirection(detectedLanguage) {&lt;br /&gt;
  if (normalizeSourceLanguage(CONFIG.sourceLanguage) !== &amp;quot;auto&amp;quot;) {&lt;br /&gt;
    return {&lt;br /&gt;
      sourceLanguage: CONFIG.sourceLanguage,&lt;br /&gt;
      targetLanguage: CONFIG.targetLanguage,&lt;br /&gt;
    };&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  const detected = detectedLanguage &amp;amp;&amp;amp; detectedLanguage.trim();&lt;br /&gt;
  if (detected &amp;amp;&amp;amp; languagesEqual(detected, CONFIG.targetLanguage)) {&lt;br /&gt;
    return {&lt;br /&gt;
      sourceLanguage: detected,&lt;br /&gt;
      targetLanguage: CONFIG.reverseLanguage,&lt;br /&gt;
    };&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  return {&lt;br /&gt;
    sourceLanguage: detected || &amp;quot;auto&amp;quot;,&lt;br /&gt;
    targetLanguage: CONFIG.targetLanguage,&lt;br /&gt;
  };&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function needsReverseTranslation(detectedLanguage) {&lt;br /&gt;
  if (normalizeSourceLanguage(CONFIG.sourceLanguage) !== &amp;quot;auto&amp;quot;) {&lt;br /&gt;
    return false;&lt;br /&gt;
  }&lt;br /&gt;
  const detected = detectedLanguage &amp;amp;&amp;amp; detectedLanguage.trim();&lt;br /&gt;
  if (!detected) {&lt;br /&gt;
    return false;&lt;br /&gt;
  }&lt;br /&gt;
  return languagesEqual(detected, CONFIG.targetLanguage);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function languagesEqual(a, b) {&lt;br /&gt;
  return a.trim().toLowerCase() === b.trim().toLowerCase();&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function normalizeSourceLanguage(value) {&lt;br /&gt;
  const trimmed = String(value == null ? &amp;quot;&amp;quot; : value).trim();&lt;br /&gt;
  return trimmed === &amp;quot;&amp;quot; ? &amp;quot;auto&amp;quot; : trimmed;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/**&lt;br /&gt;
 * 単独の変数風識別子（camelCase / snake_case 等）なら単語に分割する。&lt;br /&gt;
 */&lt;br /&gt;
function prepareTextForTranslation(text) {&lt;br /&gt;
  const trimmed = text.trim();&lt;br /&gt;
  if (!isVariableStyleIdentifier(trimmed)) {&lt;br /&gt;
    return text;&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  const words = splitVariableIdentifier(trimmed);&lt;br /&gt;
  return words.length &amp;gt; 0 ? words.join(&amp;quot; &amp;quot;) : text;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function isVariableStyleIdentifier(text) {&lt;br /&gt;
  if (!text || !/^[A-Za-z_][A-Za-z0-9_-]*$/.test(text)) {&lt;br /&gt;
    return false;&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  return (&lt;br /&gt;
    text.includes(&amp;quot;_&amp;quot;) ||&lt;br /&gt;
    text.includes(&amp;quot;-&amp;quot;) ||&lt;br /&gt;
    /[a-z][A-Z]/.test(text) ||&lt;br /&gt;
    /[A-Z]{2,}[a-z]/.test(text)&lt;br /&gt;
  );&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function splitVariableIdentifier(text) {&lt;br /&gt;
  return text&lt;br /&gt;
    .replace(/([a-z0-9])([A-Z])/g, &amp;quot;$1 $2&amp;quot;)&lt;br /&gt;
    .replace(/([A-Z]+)([A-Z][a-z])/g, &amp;quot;$1 $2&amp;quot;)&lt;br /&gt;
    .split(/[\s_-]+/)&lt;br /&gt;
    .filter(Boolean);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/**&lt;br /&gt;
 * 選択範囲の最終行末の直後へ、vscode-translator と同じ形式で挿入する。&lt;br /&gt;
 */&lt;br /&gt;
function insertTranslationBelow(translatedText, sourceLanguage, targetLanguage) {&lt;br /&gt;
  let endY = sel.GetBottomPointY(mePosLogical);&lt;br /&gt;
  const endX = sel.GetBottomPointX(mePosLogical);&lt;br /&gt;
&lt;br /&gt;
  // 行全体選択で次行先頭 (x=1) に終わっている場合は前の行末へ&lt;br /&gt;
  if (endX === 1 &amp;amp;&amp;amp; endY &amp;gt; sel.GetTopPointY(mePosLogical)) {&lt;br /&gt;
    endY = endY - 1;&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  sel.SetActivePoint(mePosLogical, 1, endY);&lt;br /&gt;
  sel.EndOfLine(false, mePosLogical);&lt;br /&gt;
&lt;br /&gt;
  const header =&lt;br /&gt;
    &amp;quot;----- 翻訳: &amp;quot; +&lt;br /&gt;
    sourceLanguage +&lt;br /&gt;
    &amp;quot; -&amp;gt; &amp;quot; +&lt;br /&gt;
    targetLanguage +&lt;br /&gt;
    &amp;quot; -----&amp;quot;;&lt;br /&gt;
  const footer = repeatChar(&amp;quot;-&amp;quot;, header.length);&lt;br /&gt;
  const body = &amp;quot;\n&amp;quot; + header + &amp;quot;\n&amp;quot; + translatedText + &amp;quot;\n&amp;quot; + footer;&lt;br /&gt;
&lt;br /&gt;
  // 文書末尾なら、フッター直後に空行を残せるよう末尾改行を付ける&lt;br /&gt;
  const lineCount = document.GetLines();&lt;br /&gt;
  const appendTrailingNewline = endY &amp;gt;= lineCount;&lt;br /&gt;
  sel.Text = appendTrailingNewline ? body + &amp;quot;\n&amp;quot; : body;&lt;br /&gt;
&lt;br /&gt;
  // フッターの次の行先頭へカーソルを置く&lt;br /&gt;
  const translationLineCount = String(translatedText).split(&amp;quot;\n&amp;quot;).length;&lt;br /&gt;
  const cursorY = endY + 3 + translationLineCount;&lt;br /&gt;
  sel.SetActivePoint(mePosLogical, 1, cursorY, false);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function repeatChar(ch, count) {&lt;br /&gt;
  let out = &amp;quot;&amp;quot;;&lt;br /&gt;
  for (let i = 0; i &amp;lt; count; i++) {&lt;br /&gt;
    out += ch;&lt;br /&gt;
  }&lt;br /&gt;
  return out;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function quoteArg(value) {&lt;br /&gt;
  // Windows のコマンドライン向けにダブルクォートで囲む&lt;br /&gt;
  return &#039;&amp;quot;&#039; + String(value).replace(/&amp;quot;/g, &#039;\\&amp;quot;&#039;) + &#039;&amp;quot;&#039;;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 更新履歴 ==&lt;br /&gt;
&lt;br /&gt;
* 2026-08-11 公開&lt;/div&gt;</summary>
		<author><name>Yuko</name></author>
	</entry>
	<entry>
		<id>https://www.haijin-boys.com/wiki/index.php?title=%E3%83%9E%E3%82%AF%E3%83%AD%E3%83%A9%E3%82%A4%E3%83%96%E3%83%A9%E3%83%AA&amp;diff=9606</id>
		<title>マクロライブラリ</title>
		<link rel="alternate" type="text/html" href="https://www.haijin-boys.com/wiki/index.php?title=%E3%83%9E%E3%82%AF%E3%83%AD%E3%83%A9%E3%82%A4%E3%83%96%E3%83%A9%E3%83%AA&amp;diff=9606"/>
		<updated>2026-08-11T12:20:41Z</updated>

		<summary type="html">&lt;p&gt;Yuko: /* その他 */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Mery 用のマクロ ライブラリです。&lt;br /&gt;
&lt;br /&gt;
スクリプトを拡張子 &amp;quot;js&amp;quot;（JavaScript、JScript の場合）にして Macros フォルダーなどに保存し、[マクロ] メニューの [選択] や [カスタマイズ] から読み込んで使用してください。&lt;br /&gt;
&lt;br /&gt;
このマクロ ライブラリはどなたでも自由に編集可能ですので、ご自身が開発した便利なマクロをどんどん共有してください。&lt;br /&gt;
&lt;br /&gt;
また、[[隠しオプション一覧]]に希望するオプションがあるかもしれませんので、そちらも参照してみてください。&lt;br /&gt;
&lt;br /&gt;
== テキスト操作 ==&lt;br /&gt;
* [https://github.com/haijinboys/mery-macro-emmet/blob/master/emmet.js Emmet] (Kuro)&lt;br /&gt;
* [[Zen Coding]] (Kuro)&lt;br /&gt;
* [[バイト数]] (Kuro, ks)&lt;br /&gt;
* [[日付と時刻を挿入]] (n, masme, isari)&lt;br /&gt;
* [[日付と時刻を挿入（カレンダー版）]] (papagoat)&lt;br /&gt;
* [[選択範囲を上下に移動]] (ks)&lt;br /&gt;
* [[TeraPadのCtrl↑と同じような動作をするマクロ]] (suke)&lt;br /&gt;
* [[TeraPadのCtrl↓と同じような動作をするマクロ]] (suke)&lt;br /&gt;
* [[Vz 風テキストスタック 2 ストロークキー]] (papagoat)&lt;br /&gt;
* [[ラクラク罫線]] (手石)&lt;br /&gt;
* [[矩形ペースト]] (手石)&lt;br /&gt;
* [[自動箇条書き]] (手石)&lt;br /&gt;
* [[特殊改行]] (FuG)&lt;br /&gt;
* [[部分編集]] (ks)&lt;br /&gt;
* [[文字カウント]] (masme)&lt;br /&gt;
* [[字下げ・字上げ]] (masme)&lt;br /&gt;
* [[数値を増減]] (masme)&lt;br /&gt;
* [[連番の挿入]] (手石)&lt;br /&gt;
* [[連番を挿入]] (masme)&lt;br /&gt;
* [[マルチカーソルに連番を入力]] (yuko)&lt;br /&gt;
* [[上に行挿入]] (yuko)&lt;br /&gt;
* [[選択範囲を広げる]] (yuko)&lt;br /&gt;
* [[貼付け実行後のカーソル位置を貼付け文字列の先頭にする]] (yuko)&lt;br /&gt;
* [[選択した文字列を文書からすべて削除]] (Noah)&lt;br /&gt;
* [[文章で使用されている文字をユニーク抽出]] (Kuro)&lt;br /&gt;
* [[CSV のカーソル位置の列を削除]] (MSY-07)&lt;br /&gt;
* [[CSV/TSVでアクティブ列選択]] (yuko)&lt;br /&gt;
* [[XHTML入力補助]] (アラフォー親父)&lt;br /&gt;
* [[Markdown文書で改行時にリスト、引用ブロック開始記号を自動入力する]] (catfist, yuko)&lt;br /&gt;
* [[Markdown 向け、リスト記号 (*) のトグル]] (yuko)&lt;br /&gt;
* [[Markdown 向け、タスクリスト (* ［］) のチェック状態をトグル]] (yuko)&lt;br /&gt;
* [[Markdown入力補助]] (急急如律令)&lt;br /&gt;
* [[青空文庫用整形補助]] (ks, 急急如律令)&lt;br /&gt;
* [[自動ルビ振り]] (急急如律令)&lt;br /&gt;
* [[ドイツ語特殊文字]] (鯉)&lt;br /&gt;
* [[何年生で習う漢字？]] (シリル)&lt;br /&gt;
* [[操作の繰り返し]] (Kuro)&lt;br /&gt;
&lt;br /&gt;
== ファイル操作 ==&lt;br /&gt;
* [[フォルダ内のファイルをすべて開く]] (Kuro)&lt;br /&gt;
* [[ファイルを比較]] (Kuro, MSY-07)&lt;br /&gt;
* [[ファイル名を引数にしてアプリ起動]] (Kuro, miso)&lt;br /&gt;
* [[文字列中のパス起動]] (ks)&lt;br /&gt;
* [[パス・URLを開く コマンドを実行する]] (yuko)&lt;br /&gt;
* [[開いている文書のファイル名を変更する]] (yuko)&lt;br /&gt;
* [[上書き保存(UAC対応)]] (ks)&lt;br /&gt;
* [[含んでいるフォルダを開く]] (ks)&lt;br /&gt;
* [[フォルダを開く]] (n)&lt;br /&gt;
* [[拡張子の違うファイルを開く]] (n)&lt;br /&gt;
* [[コマンドプロンプトを開く]] (n)&lt;br /&gt;
* [[ブックマーク一覧ジャンプ]] (goat)&lt;br /&gt;
* [[よく使うフォルダ]] (手石)&lt;br /&gt;
* [[ファイル（選択範囲）の仮保存]] (手石)&lt;br /&gt;
* [[ポップアップメニューでファイル選択]] (手石)&lt;br /&gt;
* [[フォルダツリーをポップアップ表示]] (n)&lt;br /&gt;
* [[タグジャンプ]] (o)&lt;br /&gt;
* [[ウィンドウの位置とサイズを拡張子で指定]] (papagoat)&lt;br /&gt;
* [[編集モードの自動選択]] (Bleat)&lt;br /&gt;
* [[書き換え禁止モード色反転]] (Bleat)&lt;br /&gt;
* [[変更なしのタブをすべて閉じる]] (Noah)&lt;br /&gt;
&lt;br /&gt;
== 変換・整形・ソート ==&lt;br /&gt;
* [[標準変換]] (Kuro, MSY-07)&lt;br /&gt;
* [[テキスト変換]] (ks)&lt;br /&gt;
* [[テキスト整形]] (masme)&lt;br /&gt;
* [[旧漢字から新漢字に変換]] (furu)&lt;br /&gt;
* [[Unicode正規化]] (ucky)&lt;br /&gt;
* [[Wikipediaマークアップ修正]] (MSY-07)&lt;br /&gt;
* [[SHA でハッシュ化]] (Kuro)&lt;br /&gt;
* [[clustalw を用いてアライメントを実行]] (suke)&lt;br /&gt;
* [[clustal-omegaを用いてアライメントを実行]] (suke)&lt;br /&gt;
* [[行並べ替え]] (masme)&lt;br /&gt;
* [[自然数ソート]] (tak)&lt;br /&gt;
* [[可変長を固定長に変換]] (MSY-07)&lt;br /&gt;
* [[TSV(CSV) ⇒ 固定長]] (手石)&lt;br /&gt;
* [[GFMをHTMLに変換]] (noonworks)&lt;br /&gt;
* [[基数変換10 16]] (シリル)&lt;br /&gt;
* [[スネークケース ⇔ キャメルケースの変換]] (Noah)&lt;br /&gt;
* [[濁点・半濁音の結合（1文字化）]] (kiyohiro)&lt;br /&gt;
&lt;br /&gt;
== 検索・置換 ==&lt;br /&gt;
* [[文書から検索]] (Kuro)&lt;br /&gt;
* [[単語を抽出]] (Kuro)&lt;br /&gt;
* [[タグの外側（内側）だけ置換]] (手石)&lt;br /&gt;
* [[ポップアップメニューで検索先にジャンプ]] (手石)&lt;br /&gt;
* [[検索(SJIS以外)]] (masme)&lt;br /&gt;
* [https://github.com/TakamiChie/mery-scripts/blob/master/SearchWithMode.js 現在開いているファイルの言語名をつけてWeb検索] (高見知英)&lt;br /&gt;
* [https://github.com/GunseiKPaseri/Mery_macro/blob/master/count.js 文字出現量ランキング] (群生系パセリ)&lt;br /&gt;
* [[検索ヒット数表示]] (ks)&lt;br /&gt;
* [[任意の検索エンジンで検索]] (Noah)&lt;br /&gt;
* [[任意の検索エンジンで検索ー改良版]] (Takeshi)&lt;br /&gt;
* [[デフォルトの検索フラグを設定して検索ウィンドウ表示]] (yuko)&lt;br /&gt;
* [[複数置換]] (Noah)&lt;br /&gt;
* [[類義語アウトプット【Python】]] (シリル)&lt;br /&gt;
* [[校正支援アウトプット]] (シリル)&lt;br /&gt;
* [[見出しジャンプ]] (yuko)&lt;br /&gt;
&lt;br /&gt;
== プログラミング ==&lt;br /&gt;
* [[Eclipse風コメントアウト]] (ks)&lt;br /&gt;
* [[Eclipse風コメントアウト(改)]] (pizz)&lt;br /&gt;
* [[コメントマーク付け外し]] (手石, masme)&lt;br /&gt;
* [[スマートインデント(C 構文)]] (ks)&lt;br /&gt;
* [[ctags.exeでtagsファイルを生成、タグジャンプを行うマクロ]] (kazy)&lt;br /&gt;
* [[タグファイルから補完]] (n)&lt;br /&gt;
* [[コードスニペットもどき]] (手石)&lt;br /&gt;
* [[TeX文書のコンパイル]] (手石, isari)&lt;br /&gt;
* [[簡易数式計算]] (ks)&lt;br /&gt;
* [[定義へ移動]] (ks)&lt;br /&gt;
* [[タブの桁数]] (手石)&lt;br /&gt;
* [[行末の空白を削除]] (n)&lt;br /&gt;
* [https://github.com/TakamiChie/mery-scripts/blob/master/FileExecutor.js 開いているスクリプトファイルを実行] (高見知英)&lt;br /&gt;
* [https://github.com/TakamiChie/mery-scripts/blob/master/SyntaxChecker.js 開いているスクリプトファイルを構文チェック] (高見知英)&lt;br /&gt;
* [[説明を出力]] (s)&lt;br /&gt;
* [[JSON整形]] (Ovis)&lt;br /&gt;
* [[コンソール実行]] (YOS G-spec)&lt;br /&gt;
* [[拡張子からコンソール実行]] (YOS G-spec)&lt;br /&gt;
* [[ScriptRun]] (fuka)&lt;br /&gt;
* [[JavaScript を実行]] (n)&lt;br /&gt;
* [[Ruby を実行]] (n)&lt;br /&gt;
* [[HSPキーワード検索]] (SSSE)&lt;br /&gt;
&lt;br /&gt;
== GUI ==&lt;br /&gt;
* [[フォルダ選択ダイアログ]] (手石)&lt;br /&gt;
* [[HTA(HTML)の自作ダイアログを表示]] (noonworks)&lt;br /&gt;
* [[ポップアップメニューを手軽に扱う]] (komorebikoboshi)&lt;br /&gt;
* [[ポップアップメニューでチェックボックス]] (手石)&lt;br /&gt;
* [[ポップアップメニューでラジオボタン]] (手石)&lt;br /&gt;
* [[マクロ・関数をメニューで呼び出す]] (aliquis)&lt;br /&gt;
* [[階層化マクロメニュー]] (ks)&lt;br /&gt;
* [[Editor.OpenFile()のポリフィル]] (kazy)&lt;br /&gt;
&lt;br /&gt;
== 生成AI ==&lt;br /&gt;
* [[Google Gemini に相談]] (yuko)&lt;br /&gt;
* [[OpenAI GPT に相談]] (yuko)&lt;br /&gt;
* [[Groq に相談]] (yuko)&lt;br /&gt;
* [[Google Gemini で執筆支援]] (Kuro)&lt;br /&gt;
&lt;br /&gt;
== おもしろ ==&lt;br /&gt;
* [[MeryToys]] (Kuro)&lt;br /&gt;
* [[テーマのトグル]] (ucky)&lt;br /&gt;
* [[テーマの自動変更]] (pizz)&lt;br /&gt;
* [[お気に入りフォント]] (ucky)&lt;br /&gt;
* [[あつまれ☆みんなのおもしろアイコン]] (シリル, ucky)&lt;br /&gt;
* [[メール送信]] (ks)&lt;br /&gt;
&lt;br /&gt;
== その他 ==&lt;br /&gt;
* [[最新版を確認]] (diaphragm)&lt;br /&gt;
* [[Beep音の例]] (star0worshipper)&lt;br /&gt;
* [[SAPI 使って単語読み上げ]] (miso)&lt;br /&gt;
* [[ブックマーク系コマンド集]] (ks)&lt;br /&gt;
* [[シェルコマンドを実行]] (sasaki)&lt;br /&gt;
* [[折り返しトグル切り替え]] (Bleat)&lt;br /&gt;
* [[ブラウザ風タブ移動]] (yuko)&lt;br /&gt;
* [[タブ位置を先頭/末尾に移動]] (yuko)&lt;br /&gt;
* [[Mery 電卓]] (yuko)&lt;br /&gt;
* [[Mery 電卓 (bc コマンド使用版)]] (yuko)&lt;br /&gt;
* [[Mery 翻訳]] (yuko)&lt;br /&gt;
* [[Windowsクリップボード内容表示]] (kiyohiro)&lt;br /&gt;
&lt;br /&gt;
== 開発者向け ==&lt;br /&gt;
* [[キーアサイン集]] (ks, masme, isari)&lt;br /&gt;
* [[includeライブラリ]] (ks)&lt;br /&gt;
* [[マクロ覚え書き（開発者向け）]] (ks, papagoat)&lt;br /&gt;
&lt;br /&gt;
== 注意事項 ==&lt;br /&gt;
[https://www.haijin-boys.com/software/mery/mery-2-6-6#5 Mery Ver 2.6.6]以前に作成されたマクロが動作しない場合は下記のページを参照してください。&lt;br /&gt;
&lt;br /&gt;
* [https://www.haijin-boys.com/wiki/%E3%83%9E%E3%82%AF%E3%83%AD%E3%83%AA%E3%83%95%E3%82%A1%E3%83%AC%E3%83%B3%E3%82%B9:3:%E3%83%87%E3%82%A3%E3%83%AC%E3%82%AF%E3%83%86%E3%82%A3%E3%83%96##language マクロリファレンス:3:ディレクティブ]&lt;br /&gt;
* [https://www.haijin-boys.com/discussions/6929 公式のマクロ 箇条書きが最新メリーで動作するかチェックいただけませんか？]&lt;/div&gt;</summary>
		<author><name>Yuko</name></author>
	</entry>
	<entry>
		<id>https://www.haijin-boys.com/wiki/index.php?title=OpenAI_GPT_%E3%81%AB%E7%9B%B8%E8%AB%87&amp;diff=9605</id>
		<title>OpenAI GPT に相談</title>
		<link rel="alternate" type="text/html" href="https://www.haijin-boys.com/wiki/index.php?title=OpenAI_GPT_%E3%81%AB%E7%9B%B8%E8%AB%87&amp;diff=9605"/>
		<updated>2026-08-07T13:46:18Z</updated>

		<summary type="html">&lt;p&gt;Yuko: /* v8_call_gpt.js */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== 概要 ==&lt;br /&gt;
&lt;br /&gt;
OpenAIの生成AIである「GPT」を利用するためのMeryマクロです。&lt;br /&gt;
&lt;br /&gt;
後述のソースコードではV8エンジンで動作するものと従来版スクリプトエンジンで動作するものの2種類を掲載していますが、従来版のものはアーカイブです。特段の理由がない限りはV8エンジン版をご利用ください。&lt;br /&gt;
&lt;br /&gt;
【主な機能】&lt;br /&gt;
&lt;br /&gt;
* OpenAI APIを利用して生成AIと対話できるMery用のマクロ&lt;br /&gt;
* 選択したテキストやアクティブな行の内容について生成AIに質問・相談が可能&lt;br /&gt;
* 会話履歴を保持し、文脈を考慮した対話が可能&lt;br /&gt;
&lt;br /&gt;
【特徴的な機能】&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;会話履歴機能&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* 事前の設定数に応じた会話履歴を保持&lt;br /&gt;
* 履歴のクリアが可能&lt;br /&gt;
&lt;br /&gt;
これにより、会話の文脈を考慮した回答を得られます。&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;カスタムインストラクション&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* AIの応答方針を設定可能&lt;br /&gt;
* 一時的なカスタム指示の保存と変更が可能&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;プロンプトテンプレート&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* 誤字脱字チェック&lt;br /&gt;
* 日本語翻訳&lt;br /&gt;
* コードの最適化、生成&lt;br /&gt;
&lt;br /&gt;
など、定型的な質問を簡単に実行できます。&lt;br /&gt;
※テンプレート機能では回答を安定させるため、前述の会話履歴を文脈に含みません。&lt;br /&gt;
&lt;br /&gt;
テンプレートは自身の使い方に合わせて好きに追加、削除ができます。詳細はソースコード内の「PROMPT_TEMPLATE」定数のコメントを参照。&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;生成AIチャット風仕様&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* ストリーミングモードにより、生成途中の回答がリアルタイムにMeryへ順次入力される&lt;br /&gt;
* Shiftキー長押しで、テキスト生成を中断できる&lt;br /&gt;
&lt;br /&gt;
== 注意事項 (必ず読んでからご利用ください) ==&lt;br /&gt;
&lt;br /&gt;
* 本マクロではOpenAI APIを利用します。API利用に伴う料金発生やOpenAI社の情報取り扱い等によるユーザー不利益が発生しても、作者は一切の責任を負いません。&lt;br /&gt;
** &#039;&#039;&#039;OpenAI APIでは、利用前にクレジットを購入するプリペイド請求が利用されます。意図しない利用を抑えるため、オートチャージや利用上限の設定を事前に確認してください。&#039;&#039;&#039;&lt;br /&gt;
** 2026/07/18 時点、OpenAIでは、プロンプトを学習に利用することを許可 (データ共有へのオプトイン) することで一定の無料枠を獲得できる制度があります。極力コストを抑えたい方はこちらの利用も検討してもよいかもしれません (オプトイン手順はここでは説明しません)。&lt;br /&gt;
* OpenAI APIへ送信した入力・出力データは、明示的にデータ共有へオプトインしない限り、デフォルトではモデルの学習に使用されません。ただし、外部サービスへ情報を送信することに変わりはないため、機密情報、個人情報、認証情報などは入力しないでください。&lt;br /&gt;
* OpenAI APIは、 [https://openai.com/ja-JP/policies/terms-of-use/ OpenAI利用規約] に則ってご利用ください。&lt;br /&gt;
&lt;br /&gt;
== 利用前提 ==&lt;br /&gt;
&lt;br /&gt;
* Mery Ver 3.7.8 以上&lt;br /&gt;
* [https://platform.openai.com/ OpenAI Platform] で、OpenAI APIキーを取得していること&lt;br /&gt;
* OpenAI APIを利用できるだけの課金残高があること&lt;br /&gt;
&lt;br /&gt;
== 利用方法 ==&lt;br /&gt;
&lt;br /&gt;
* 環境変数の設定&lt;br /&gt;
:コマンド プロンプトで以下のコマンドを実行し、環境変数を設定してください。&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bat&amp;quot; copy&amp;gt;&lt;br /&gt;
setx OPENAI_API_KEY &amp;quot;***************************************&amp;quot;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
:設定後、Mery または Windows の再起動が必要な場合があります。&lt;br /&gt;
* マクロの実行&lt;br /&gt;
** Meryのマクロフォルダー「Macros」に、後述の「v8_boot_gpt.js」「v8_call_gpt.js」をそれぞれ配置し、「v8_boot_gpt.js」をMeryへ読み込ませてください (「v8_call_gpt.js」は配置するだけでOK)。&lt;br /&gt;
**プロンプトにしたい文字列を選択してマクロを実行すると、選択したテキストがプロンプトとしてOpenAI APIに送信され、応答がエディタに表示されます。&lt;br /&gt;
* 会話の履歴数を調整&lt;br /&gt;
** 会話の履歴は設定した個数分が保持され、文脈として利用されます。適宜、ソースコード内の「HISTORY_SIZE」の数を調整してください。&lt;br /&gt;
** ※数を増やすほど会話内容を保持して文脈を理解するようになりますが、メモリ使用量が増えたり動作が重くなる可能性があります。&lt;br /&gt;
** ※開いている文書ごとに履歴を保持します。文脈をリセットしたいときは、新しい文書を開いて会話をやり直してください。&lt;br /&gt;
&lt;br /&gt;
== ソースコード ==&lt;br /&gt;
=== V8エンジン版 ===&lt;br /&gt;
&lt;br /&gt;
==== v8_boot_gpt.js ====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot; copy&amp;gt;&lt;br /&gt;
#language = &amp;quot;quickjs&amp;quot;&lt;br /&gt;
#async = true&lt;br /&gt;
#title = &amp;quot;GPT に相談 (v8)&amp;quot;&lt;br /&gt;
BeginUndoGroup();&lt;br /&gt;
&lt;br /&gt;
// 各種設定値&lt;br /&gt;
&lt;br /&gt;
// カスタムインストラクション&lt;br /&gt;
// これを設定することで、生成AIの回答全体への生成方針を指示できます。&lt;br /&gt;
// 例えば &amp;quot;あなたは侍で名前は権兵衛です。回答をござる口調にしてください&amp;quot; と設定すると、AIの回答がござる口調になります。&lt;br /&gt;
// 何も指示しない場合は、空文字 &amp;quot;&amp;quot; を設定してください。&lt;br /&gt;
let DEFAULT_CUSTOM_INSTRUCTION = `&lt;br /&gt;
文:&lt;br /&gt;
- 論理的で読みやすい文章構成にする&lt;br /&gt;
- 専門用語は必要に応じて注釈を加える&lt;br /&gt;
&lt;br /&gt;
性格:&lt;br /&gt;
- 愛想の良い敬語で話す。「…ですよね/…してみましょう/…かもしれません/…だと思います」などの柔らかい語調を使ってもよい&lt;br /&gt;
- 一人称は「私」&lt;br /&gt;
- 多角的かつ中立的な見方で、必要であれば反対意見や代替案も示す&lt;br /&gt;
&lt;br /&gt;
誤字注意:&lt;br /&gt;
- 中国語や韓国語など、回答言語と異なる言語文字を入れないこと&lt;br /&gt;
&lt;br /&gt;
書式:&lt;br /&gt;
- 羅列・比較的な内容を出力する場合は、Markdownテーブル形式よりもMarkdownリスト形式の使用を優先する&lt;br /&gt;
`.trim();&lt;br /&gt;
&lt;br /&gt;
// プロンプトテンプレート&lt;br /&gt;
// プロンプトテンプレートを設定することで、AIに対して特定の指示を出すことができます。&lt;br /&gt;
// プロンプトテンプレートでの生成は会話履歴とカスタムインストラクションを反映しません。&lt;br /&gt;
// テンプレートは以下の { ～ }, で囲まれたブロックを好きに増減させて利用できます。&lt;br /&gt;
const PROMPT_TEMPLATE = [&lt;br /&gt;
&lt;br /&gt;
    // 以下はサンプルです。1階層メニューと2階層メニューが利用できます。&lt;br /&gt;
    // {&lt;br /&gt;
    //     &amp;quot;title&amp;quot;: &amp;quot;要約&amp;quot;,&lt;br /&gt;
    //     &amp;quot;prompt&amp;quot;: &amp;quot;以下の文章を要約してください。&amp;quot;&lt;br /&gt;
    // },&lt;br /&gt;
    // {&lt;br /&gt;
    //     &amp;quot;submenu_title&amp;quot;: &amp;quot;翻訳系&amp;quot;,&lt;br /&gt;
    //     &amp;quot;submenu&amp;quot;: [&lt;br /&gt;
    //         {&lt;br /&gt;
    //             &amp;quot;title&amp;quot;: &amp;quot;日本語翻訳&amp;quot;,&lt;br /&gt;
    //             &amp;quot;prompt&amp;quot;: &amp;quot;以下の文章を日本語に翻訳してください。&amp;quot;&lt;br /&gt;
    //         },&lt;br /&gt;
    //         {&lt;br /&gt;
    //             &amp;quot;title&amp;quot;: &amp;quot;英語翻訳&amp;quot;,&lt;br /&gt;
    //             &amp;quot;prompt&amp;quot;: &amp;quot;以下の文章を英語に翻訳してください。&amp;quot;&lt;br /&gt;
    //         },&lt;br /&gt;
    //     ]&lt;br /&gt;
    // },&lt;br /&gt;
&lt;br /&gt;
    {&lt;br /&gt;
        &amp;quot;submenu_title&amp;quot;: &amp;quot;理解を深める&amp;quot;,&lt;br /&gt;
        &amp;quot;submenu&amp;quot;: [&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;論点を整理する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の論点を整理してまとめて。そしてその整理から示唆を見出して。&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;論理の抜け漏れを探す&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;あなたは「悪魔の代弁者」として、以下の主張・設計・計画を批判的に鋭く見てください。論理の穴・抜け・漏れ・飛躍、リスクなどを全力で探して指摘してください。遠慮は不要ですが、批判のためにこじつけたような論理性に欠けた主張にならないように注意してください。&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;要約する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;分かりやすさを重視して要約して。専門的な用語には注釈を入れること。&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;物語対話形式で解説&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の内容について物語対話形式で解説して。登場人物は「AI先生」(女性人格)と「生徒」(男の子人格)とする。&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
        ]&lt;br /&gt;
    },&lt;br /&gt;
    {&lt;br /&gt;
        &amp;quot;submenu_title&amp;quot;: &amp;quot;翻訳&amp;quot;,&lt;br /&gt;
        &amp;quot;submenu&amp;quot;: [&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;日本語に翻訳&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: `以下の入力文章を日本語に翻訳して。直訳的にせず、自然な表現にすること。入力文章はどのような内容・段落構造であっても要約・除外・集約などの整形を行わず、全体構造を維持したまま確実に全文を翻訳すること。&lt;br /&gt;
&lt;br /&gt;
出力形式は以下とすること:&lt;br /&gt;
[訳]&lt;br /&gt;
翻訳文...`&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;英語に翻訳&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: `以下の入力文章を英語に翻訳して。フォーマルとカジュアルの中間的なニュアンスで翻訳すること。入力文章はどのような内容・段落構造であっても要約・除外・集約などの整形を行わず、全体構造を維持したまま確実に全文を翻訳すること&lt;br /&gt;
&lt;br /&gt;
出力形式は以下とすること:&lt;br /&gt;
[Translation]&lt;br /&gt;
翻訳文...`&lt;br /&gt;
            },&lt;br /&gt;
        ]&lt;br /&gt;
    },&lt;br /&gt;
    {&lt;br /&gt;
        &amp;quot;submenu_title&amp;quot;: &amp;quot;語学&amp;quot;,&lt;br /&gt;
        &amp;quot;submenu&amp;quot;: [&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;英語 文法解説&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: `あなたは母国語が英語の英語教師です。&lt;br /&gt;
次の英文を以下の構成で解説してください。&lt;br /&gt;
&lt;br /&gt;
構成:&lt;br /&gt;
(1) 日本語翻訳&lt;br /&gt;
(2) スラッシュリーディング + 各句の文型（S/V/O/C）と語訳&lt;br /&gt;
(3) 重要文法&lt;br /&gt;
(4) 補足&lt;br /&gt;
&lt;br /&gt;
注意:&lt;br /&gt;
- 補足では日本語話者には分かりづらい表現や慣例表現などの解説を加える&lt;br /&gt;
- スラッシュリーディングで区切る際「M (修飾語)」が連なって長くなりすぎる場合には5～7語程度を目安に分ける&lt;br /&gt;
- 「M」の種類として形容詞、副詞、形容詞句、副詞句、分詞句、形容詞節、副詞節がある&lt;br /&gt;
&lt;br /&gt;
(2) の出力例:&lt;br /&gt;
They / elected / him / president / in 2020.&lt;br /&gt;
- They: (S) 彼らは&lt;br /&gt;
- elected: (V) 選出した&lt;br /&gt;
- him: (O) 彼を&lt;br /&gt;
- president: (C) 大統領&lt;br /&gt;
- in 2020: (M: 副詞句) 2020年に&lt;br /&gt;
`&lt;br /&gt;
&lt;br /&gt;
            },&lt;br /&gt;
        ]&lt;br /&gt;
    },&lt;br /&gt;
    {&lt;br /&gt;
        &amp;quot;submenu_title&amp;quot;: &amp;quot;プログラミング支援&amp;quot;,&lt;br /&gt;
        &amp;quot;submenu&amp;quot;: [&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;コードを解説する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下のコードを初めて読む人も理解できるように解説してください。私には言語の基本理解はあるものとし、コードの意図を理解するための情報を中心に説明してください。&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;コードを洗練させる&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下のコードを洗練させてください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;コードをレビューする&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下のコードをセキュリティと保守性の観点でレビューしてください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;エラーログをデバッグする&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下のエラーログを解析し、考えられる原因と対策を提示してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;SQLクエリを最適化する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下のSQL文を最適化してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;PowerShell スクリプトを書く&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;あなたはエキスパートとしてメンテナンス性の高いコードを書くのが得意です。以下の処理を行うPowerShellスクリプトを生成してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;Shell Script を書く&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;あなたはエキスパートとしてメンテナンス性の高いコードを書くのが得意です。以下の処理を行うShell Scriptを生成してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
        ]&lt;br /&gt;
    },&lt;br /&gt;
    {&lt;br /&gt;
        &amp;quot;submenu_title&amp;quot;: &amp;quot;ビジネス文書支援&amp;quot;,&lt;br /&gt;
        &amp;quot;submenu&amp;quot;: [&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;音声文字起こし文を整形する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: `以下は、音声文字起こしの文章です。以下のルールに従って、文章を整形してください。&lt;br /&gt;
&lt;br /&gt;
整形ルール:&lt;br /&gt;
- 要約するなどといった文章の全体感を損なう修正はしない&lt;br /&gt;
- 会話の流れから不自然と思われる単語には、対象単語の直後に誤字の可能性を示唆する [*] を付ける&lt;br /&gt;
- 誤字の可能性を示唆する [*] が付与された場合のみ、出力の末尾に「[*]: 誤字の可能性ありと凡例を書き添える&lt;br /&gt;
- 助詞や句読点など文章上の不適合を正す&lt;br /&gt;
- フィラーワードを除外する`&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;誤字脱字、不正確な表現をチェック&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の2つのカテゴリーで問題点を指摘してください\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 【誤字脱字】：明確な誤字脱字および誤字の疑いがある箇所\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 【不正確・曖昧な表現】：解釈に個人差が生じる可能性がある表現、より適切な表現に改善できる箇所&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;提案書を作成する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下に対する対する提案書を、コスト・実現性・リスクを含めて作成してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;手順書のアウトラインを作成する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の作業を行う手順書を、前提条件と注意事項を含めてアウトラインを作成してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;ユーザーマニュアルのアウトラインを作成する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の機能内容から、ユーザー向けマニュアルのアウトラインを作成してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;議事録を作成する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の会議内容から議事録を作成してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
        ]&lt;br /&gt;
    },&lt;br /&gt;
    {&lt;br /&gt;
        &amp;quot;submenu_title&amp;quot;: &amp;quot;分析支援&amp;quot;,&lt;br /&gt;
        &amp;quot;submenu&amp;quot;: [&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;問題を分析し、課題を抽出する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の状況から問題や潜在的リスクを分析し、課題を抽出してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;対応策を提案する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の問題・課題に対する対応策を複数提案してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;傾向を見つける&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の内容から、数値や挙動上の傾向を見つけてください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
        ]&lt;br /&gt;
    },&lt;br /&gt;
    {&lt;br /&gt;
        &amp;quot;submenu_title&amp;quot;: &amp;quot;アイデア出し&amp;quot;,&lt;br /&gt;
        &amp;quot;submenu&amp;quot;: [&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;SCAMPER法でアイデア出し&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下のテーマについて、SCAMPER法を使って15個のアイデアを出してください。&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;解決策の案出し&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の課題に対して、技術面、運用面、コスト面から実現可能な解決策を5つずつ提案してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;逆転の発想&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の目標に対して、通常とは逆の視点からアプローチする方法を3つ提案してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;競合分析からの着想&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の内容について、業界における競合他社の特徴を分析し、差別化できる新しいアプローチを提案してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;ユースケースの列挙&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の製品・サービスについて、想定されるユースケースを10個列挙し、それぞれの価値提案を記載してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
        ]&lt;br /&gt;
    },&lt;br /&gt;
&lt;br /&gt;
]&lt;br /&gt;
&lt;br /&gt;
// コンテキストメニュー番号の定義&lt;br /&gt;
const EXECUTE_AI = 1;&lt;br /&gt;
const RESET_HISTORY = 2;&lt;br /&gt;
const SET_CUSTOM_INSTRUCTION = 3;&lt;br /&gt;
const REEXECUTE_LAST_TEMPLATE_PROMPT = 4;&lt;br /&gt;
const ROMAN_ALPHABET_IME = 5;&lt;br /&gt;
const INVALID_MENU = 9;&lt;br /&gt;
const TEMPLATE_PROMPT_THRESHOLD = 10;&lt;br /&gt;
const TEMPLATE_PROMPT_SUBMENU_THRESHOLD = 1000;&lt;br /&gt;
&lt;br /&gt;
// タグ名の定義&lt;br /&gt;
const HISTORY_TAG = &amp;quot;gpt-history&amp;quot;;&lt;br /&gt;
const HISTORY_SIZE_TAG = &amp;quot;gpt-history-size&amp;quot;;&lt;br /&gt;
const TEMPORARY_CUSTOM_INSTRUCTION_TAG = &amp;quot;gpt-temporary-custom-instruction&amp;quot;;&lt;br /&gt;
const PREVIOUS_TEMPLATE_PROMPT_TAG = &amp;quot;gpt-previous-template-prompt&amp;quot;;&lt;br /&gt;
const SELECTED_TEMPLATE_PROMPT_TAG = &amp;quot;gpt-selected-template-prompt&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
// 会話の履歴を保持する数 (質問と回答で1セット)&lt;br /&gt;
// 数を増やすと、会話の履歴が増えますが、Meryのメモリ使用量が増えたり、&lt;br /&gt;
// 使用トークン数が増えて動作が重くなる可能性があるため、適宜調整してください。&lt;br /&gt;
// document.Tag に保持されるため、開いているファイルを閉じると履歴は消えます。&lt;br /&gt;
const HISTORY_SIZE = 5;&lt;br /&gt;
document.tag[HISTORY_SIZE_TAG] = HISTORY_SIZE;&lt;br /&gt;
&lt;br /&gt;
// console.log に出力する内容を Mery のアウトプットバーに表示する&lt;br /&gt;
var console = {&lt;br /&gt;
    log: function (str) {&lt;br /&gt;
        outputBar.Writeln(str)&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
var doc = document;&lt;br /&gt;
var sel = document.selection;&lt;br /&gt;
&lt;br /&gt;
main();&lt;br /&gt;
&lt;br /&gt;
function main() {&lt;br /&gt;
    var selectionIsEmpty = sel.IsEmpty&lt;br /&gt;
&lt;br /&gt;
    // プロンプトの取得&lt;br /&gt;
    if (selectionIsEmpty) {&lt;br /&gt;
        sel.StartOfLine(false, mePosLogical);&lt;br /&gt;
        sel.EndOfLine(true, mePosLogical);&lt;br /&gt;
    }&lt;br /&gt;
    var menuTrack = 0&lt;br /&gt;
    while (true) {&lt;br /&gt;
        // コンテキストメニューの作成&lt;br /&gt;
        var menu = createContextMenu(selectionIsEmpty);&lt;br /&gt;
        menuTrack = menu.Track(0);&lt;br /&gt;
        // メニュー選択がキャンセルされた場合は終了&lt;br /&gt;
        if (menuTrack === 0) return;&lt;br /&gt;
&lt;br /&gt;
        // 会話履歴のリセット&lt;br /&gt;
        if (menuTrack === RESET_HISTORY) {&lt;br /&gt;
            resetTag();&lt;br /&gt;
            continue;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        // カスタムインストラクションの設定&lt;br /&gt;
        if (menuTrack === SET_CUSTOM_INSTRUCTION) {&lt;br /&gt;
            configureTemporaryCustomInstruction();&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        break;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // 読み取り専用の場合はエラー&lt;br /&gt;
    if (doc.ReadOnly) {&lt;br /&gt;
        alert(&amp;quot;ファイルが読み取り専用のため実行できません。&amp;quot;);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // 直前に実行したプロンプトテンプレートの再実行の場合、menuTrack を書き換え&lt;br /&gt;
    if (menuTrack === REEXECUTE_LAST_TEMPLATE_PROMPT) {&lt;br /&gt;
        if (doc.Tag.exists(PREVIOUS_TEMPLATE_PROMPT_TAG)) {&lt;br /&gt;
            menuTrack = Number(doc.tag[PREVIOUS_TEMPLATE_PROMPT_TAG]);&lt;br /&gt;
        } else {&lt;br /&gt;
            alert(&amp;quot;直前に実行したプロンプトテンプレートがありません。&amp;quot;);&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // テンプレートを取得&lt;br /&gt;
    var isTemplate = menuTrack &amp;gt;= TEMPLATE_PROMPT_THRESHOLD;&lt;br /&gt;
    var isSubmenu = menuTrack &amp;gt;= TEMPLATE_PROMPT_SUBMENU_THRESHOLD;&lt;br /&gt;
    if (isSubmenu) {&lt;br /&gt;
        var parentMenuTrack = Number(String(menuTrack).slice(0, 2));&lt;br /&gt;
        var subMenuTrack = Number(String(menuTrack).slice(2));&lt;br /&gt;
        var templatePrompt = PROMPT_TEMPLATE[parentMenuTrack - TEMPLATE_PROMPT_THRESHOLD].submenu[subMenuTrack].prompt;&lt;br /&gt;
    } else if (isTemplate) {&lt;br /&gt;
        var templatePrompt = PROMPT_TEMPLATE[menuTrack - TEMPLATE_PROMPT_THRESHOLD].prompt;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // テンプレートの場合は直前に実行したプロンプトテンプレートの番号としてタグに保存&lt;br /&gt;
    if (isTemplate) {&lt;br /&gt;
        doc.tag[SELECTED_TEMPLATE_PROMPT_TAG] = templatePrompt;&lt;br /&gt;
        doc.tag[PREVIOUS_TEMPLATE_PROMPT_TAG] = menuTrack&lt;br /&gt;
    } else {&lt;br /&gt;
        // テンプレート以外の場合はタグを削除&lt;br /&gt;
        if (doc.Tag.exists(SELECTED_TEMPLATE_PROMPT_TAG)) {&lt;br /&gt;
            doc.Tag.remove(SELECTED_TEMPLATE_PROMPT_TAG);&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // ローマ字⇒日本語変換のプロンプトをセット&lt;br /&gt;
    const isRomanAlphabetIme = menuTrack === ROMAN_ALPHABET_IME;&lt;br /&gt;
    if (isRomanAlphabetIme) {&lt;br /&gt;
        doc.tag[SELECTED_TEMPLATE_PROMPT_TAG] = `以下のローマ字で入力された文章を、日本語に変換してください。&lt;br /&gt;
制約:&lt;br /&gt;
- 原則、入力のままの用語・口調で変換&lt;br /&gt;
- 誤字脱字は修正してよい&lt;br /&gt;
- 適宜、句読点を入れて読みやすくしてよい&lt;br /&gt;
- 返事は不要で、書き直した文章のみを出力`;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // gpt API 呼び出し部品を実行&lt;br /&gt;
    // V8版で fetch メソッドを使うために別呼び出しとする&lt;br /&gt;
    doc.tag[TEMPORARY_CUSTOM_INSTRUCTION_TAG] = getTemporaryCustomInstruction() ? getTemporaryCustomInstruction() : DEFAULT_CUSTOM_INSTRUCTION;&lt;br /&gt;
    ShowTip(&amp;quot;リクエスト中.....&amp;quot;, meShowTipPosCaret);&lt;br /&gt;
    editor.ExecuteMacro(&amp;quot;v8_call_gpt.js&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function createContextMenu(selectionIsEmpty) {&lt;br /&gt;
    const menu = CreatePopupMenu();&lt;br /&gt;
    menu.Add(&amp;quot;※注意※ プライバシー・機密情報を送信しないこと&amp;quot;, INVALID_MENU);&lt;br /&gt;
    menu.Add(&amp;quot;&amp;quot;, 0, meMenuSeparator);&lt;br /&gt;
    let t = &amp;quot;&amp;quot;;&lt;br /&gt;
    if (selectionIsEmpty) {&lt;br /&gt;
        t = &amp;quot;アクティブ行の内容で相談 (&amp;amp;A)&amp;quot;;&lt;br /&gt;
    } else {&lt;br /&gt;
        t = &amp;quot;選択範囲の内容で相談 (&amp;amp;A)&amp;quot;;&lt;br /&gt;
    }&lt;br /&gt;
    menu.Add(t, EXECUTE_AI);&lt;br /&gt;
    if (HISTORY_SIZE &amp;lt;= 0) {&lt;br /&gt;
        menu.Add(&amp;quot;├ 会話履歴をクリア (履歴が無効になっています)&amp;quot;, RESET_HISTORY, meMenuGrayed);&lt;br /&gt;
    } else if (doc.Tag.exists(HISTORY_TAG)) {&lt;br /&gt;
        var historyData = JSON.parse(doc.tag[HISTORY_TAG]);&lt;br /&gt;
        var messages;&lt;br /&gt;
        if (Array.isArray(historyData)) {&lt;br /&gt;
            // 旧形式: 配列が直接 messages&lt;br /&gt;
            messages = historyData;&lt;br /&gt;
        } else {&lt;br /&gt;
            // 新形式: { session_id, messages }&lt;br /&gt;
            messages = historyData.messages || [];&lt;br /&gt;
        }&lt;br /&gt;
        var turn = (messages.length) / 2;&lt;br /&gt;
        menu.Add(&amp;quot;├ 会話履歴をクリア (現在の会話数: &amp;quot; + turn + &amp;quot;) (&amp;amp;C)&amp;quot;, RESET_HISTORY);&lt;br /&gt;
    } else {&lt;br /&gt;
        menu.Add(&amp;quot;├ 会話履歴をクリア (現在の会話数: 0) (&amp;amp;C)&amp;quot;, RESET_HISTORY, meMenuGrayed);&lt;br /&gt;
    }&lt;br /&gt;
    menu.Add(&amp;quot;└ カスタムインストラクションを変更 (&amp;amp;I)&amp;quot;, SET_CUSTOM_INSTRUCTION);&lt;br /&gt;
&lt;br /&gt;
    menu.Add(&amp;quot;ローマ字⇒日本語 変換 (&amp;amp;R)&amp;quot;, ROMAN_ALPHABET_IME);&lt;br /&gt;
&lt;br /&gt;
    // テンプレート数チェック&lt;br /&gt;
    // 2桁まで許容&lt;br /&gt;
    const ITEMS_MUX = 99;&lt;br /&gt;
    if (PROMPT_TEMPLATE.length &amp;gt; ITEMS_MUX - TEMPLATE_PROMPT_THRESHOLD) {&lt;br /&gt;
        throw new Error(&amp;quot;プロンプトテンプレートの数が多すぎます。&amp;quot;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // テンプレートメニューの追加&lt;br /&gt;
    if (PROMPT_TEMPLATE.length &amp;gt; 0) {&lt;br /&gt;
        const subMenu = CreatePopupMenu();&lt;br /&gt;
        let previousMenuString = &amp;quot;&amp;quot;;&lt;br /&gt;
        for (let i = 0; i &amp;lt; PROMPT_TEMPLATE.length; i++) {&lt;br /&gt;
            let accelerationKey = &amp;quot;&amp;quot;;&lt;br /&gt;
            if (i === 9) {&lt;br /&gt;
                accelerationKey = &amp;quot; (&amp;amp;0)&amp;quot;;&lt;br /&gt;
            } else if (i &amp;lt; 10) {&lt;br /&gt;
                accelerationKey = &amp;quot; (&amp;amp;&amp;quot; + (i + 1) + &amp;quot;)&amp;quot;;&lt;br /&gt;
            }&lt;br /&gt;
            const menuNum = TEMPLATE_PROMPT_THRESHOLD + i;&lt;br /&gt;
            if (PROMPT_TEMPLATE[i].submenu) {&lt;br /&gt;
                const subMenuList = PROMPT_TEMPLATE[i].submenu;&lt;br /&gt;
                // テンプレート数チェック&lt;br /&gt;
                if (subMenuList.length &amp;gt; ITEMS_MUX) {&lt;br /&gt;
                    throw new Error(&amp;quot;プロンプトテンプレート (サブメニュー) の数が多すぎます。&amp;quot;);&lt;br /&gt;
                }&lt;br /&gt;
                const subSubMenu = CreatePopupMenu();&lt;br /&gt;
                const subMenuPrefix = String(menuNum);&lt;br /&gt;
                for (let j = 0; j &amp;lt; subMenuList.length; j++) {&lt;br /&gt;
                    let subAccelerationKey = &amp;quot;&amp;quot;;&lt;br /&gt;
                    if (j === 9) {&lt;br /&gt;
                        subAccelerationKey = &amp;quot; (&amp;amp;0)&amp;quot;;&lt;br /&gt;
                    } else if (j &amp;lt; 10) {&lt;br /&gt;
                        subAccelerationKey = &amp;quot; (&amp;amp;&amp;quot; + (j + 1) + &amp;quot;)&amp;quot;;&lt;br /&gt;
                    }&lt;br /&gt;
                    // ES5 compatible version without padStart&lt;br /&gt;
                    const subMenuNum = Number(subMenuPrefix + (j &amp;lt; 10 ? &amp;quot;0&amp;quot; + j : String(j)));&lt;br /&gt;
                    subSubMenu.Add(subMenuList[j].title + subAccelerationKey, subMenuNum);&lt;br /&gt;
                    if (subMenuNum === doc.tag[PREVIOUS_TEMPLATE_PROMPT_TAG]) {&lt;br /&gt;
                        previousMenuString = subMenuList[j].title;&lt;br /&gt;
                    }&lt;br /&gt;
                }&lt;br /&gt;
                subMenu.AddPopup(PROMPT_TEMPLATE[i].submenu_title + accelerationKey, subSubMenu);&lt;br /&gt;
            } else {&lt;br /&gt;
                subMenu.Add(PROMPT_TEMPLATE[i].title + accelerationKey, menuNum);&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        if (selectionIsEmpty) {&lt;br /&gt;
            t = &amp;quot;プロンプト テンプレート (対象: アクティブ行) (&amp;amp;T)&amp;quot;;&lt;br /&gt;
        } else {&lt;br /&gt;
            t = &amp;quot;プロンプト テンプレート (対象: 選択範囲) (&amp;amp;T)&amp;quot;;&lt;br /&gt;
        }&lt;br /&gt;
        menu.AddPopup(t, subMenu);&lt;br /&gt;
&lt;br /&gt;
        t = &amp;quot;└ 直前に実行したプロンプト テンプレートを再実行 (&amp;amp;R)&amp;quot;;&lt;br /&gt;
        if (doc.Tag.exists(PREVIOUS_TEMPLATE_PROMPT_TAG)) {&lt;br /&gt;
            menu.Add(t, REEXECUTE_LAST_TEMPLATE_PROMPT);&lt;br /&gt;
            menu.Add(`    (直前実行: ${previousMenuString})`, REEXECUTE_LAST_TEMPLATE_PROMPT);&lt;br /&gt;
        } else {&lt;br /&gt;
            menu.Add(t, REEXECUTE_LAST_TEMPLATE_PROMPT, meMenuGrayed);&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return menu;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function configureTemporaryCustomInstruction() {&lt;br /&gt;
    let tempCustomInstruction = getTemporaryCustomInstruction();&lt;br /&gt;
    const prompt = Prompt(&amp;quot;カスタムインストラクションを変更 ※変更すると会話履歴がリセットされます&amp;quot;, tempCustomInstruction ? tempCustomInstruction : DEFAULT_CUSTOM_INSTRUCTION);&lt;br /&gt;
    if (prompt !== null) {&lt;br /&gt;
        if (prompt === DEFAULT_CUSTOM_INSTRUCTION) {&lt;br /&gt;
            // デフォルトと同じ値の場合はタグを削除&lt;br /&gt;
            setTemporaryCustomInstruction(&amp;quot;&amp;quot;);&lt;br /&gt;
        } else if (prompt === &amp;quot;&amp;quot; &amp;amp;&amp;amp; Confirm(&amp;quot;カスタムインストラクションをデフォルトに戻しますか？&amp;quot;)) {&lt;br /&gt;
            setTemporaryCustomInstruction(&amp;quot;&amp;quot;);&lt;br /&gt;
        } else if (prompt !== tempCustomInstruction) {&lt;br /&gt;
            // デフォルト以外の値が設定された場合は保存&lt;br /&gt;
            setTemporaryCustomInstruction(prompt);&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // 変更が加わっている場合は会話履歴をリセット&lt;br /&gt;
    if (tempCustomInstruction !== getTemporaryCustomInstruction()) {&lt;br /&gt;
        resetTag();&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function resetTag() {&lt;br /&gt;
    const tag = doc.Tag;&lt;br /&gt;
    if (tag.exists(HISTORY_TAG)) {&lt;br /&gt;
        tag.remove(HISTORY_TAG);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getTemporaryCustomInstruction() {&lt;br /&gt;
    var tag = doc.Tag;&lt;br /&gt;
    if (tag.exists(TEMPORARY_CUSTOM_INSTRUCTION_TAG)) {&lt;br /&gt;
        return tag[TEMPORARY_CUSTOM_INSTRUCTION_TAG];&lt;br /&gt;
    } else {&lt;br /&gt;
        return &amp;quot;&amp;quot;;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function setTemporaryCustomInstruction(temporaryCustomInstruction) {&lt;br /&gt;
    var tag = doc.Tag;&lt;br /&gt;
    if (temporaryCustomInstruction) {&lt;br /&gt;
        tag[TEMPORARY_CUSTOM_INSTRUCTION_TAG] = temporaryCustomInstruction;&lt;br /&gt;
    } else {&lt;br /&gt;
        tag.remove(TEMPORARY_CUSTOM_INSTRUCTION_TAG);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== v8_call_gpt.js ====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot; copy&amp;gt;&lt;br /&gt;
#language = &amp;quot;v8&amp;quot;&lt;br /&gt;
#title = &amp;quot;GPT に相談 (API 呼び出し部品)&amp;quot;&lt;br /&gt;
BeginUndoGroup();&lt;br /&gt;
&lt;br /&gt;
// 生成AIの回答の言語設定&lt;br /&gt;
// &amp;quot;Japanese&amp;quot; や &amp;quot;English&amp;quot; を設定できます。他の言語は未検証。&lt;br /&gt;
// 自動設定にする場合は空文字 &amp;quot;&amp;quot; を設定してください。&lt;br /&gt;
const LANGUAGE = &amp;quot;Japanese&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
// 入力プロンプトの最大トークン数&lt;br /&gt;
// 超適当なトークナイザー関数により計測し、英単語は4文字1トークン、それ以外は1文字1トークンとして数えます。&lt;br /&gt;
const MAX_PROMPT_TOKEN = 8000;&lt;br /&gt;
&lt;br /&gt;
// 会話履歴に保持する最大トークン数&lt;br /&gt;
// 会話履歴のトークン数がこの数を超える場合、古い履歴から削除されます。&lt;br /&gt;
const MAX_HISTORY_TOKEN = 50000;&lt;br /&gt;
&lt;br /&gt;
// 生成AI応答の入力毎の遅延時間 (ミリ秒)&lt;br /&gt;
// 入力をゆっくりにしたいときは数字を大きくしてください。&lt;br /&gt;
const INPUT_DELAY = 20;&lt;br /&gt;
&lt;br /&gt;
// タグ名の定義&lt;br /&gt;
const HISTORY_TAG = &amp;quot;gpt-history&amp;quot;;&lt;br /&gt;
const HISTORY_SIZE_TAG = &amp;quot;gpt-history-size&amp;quot;;&lt;br /&gt;
const TEMPORARY_CUSTOM_INSTRUCTION_TAG = &amp;quot;gpt-temporary-custom-instruction&amp;quot;;&lt;br /&gt;
const SELECTED_TEMPLATE_PROMPT_TAG = &amp;quot;gpt-selected-template-prompt&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
// 会話の履歴を保持する数 (質問と回答で1セット)&lt;br /&gt;
// boot マクロ側で設定されていることを前提としている&lt;br /&gt;
if (!document.tag.exists(HISTORY_SIZE_TAG)) {&lt;br /&gt;
    throw new Error(&amp;quot;会話履歴サイズが設定されていません。&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
const HISTORY_SIZE = document.tag[HISTORY_SIZE_TAG];&lt;br /&gt;
&lt;br /&gt;
// プロンプトキャッシュの照合に使う安定したキー&lt;br /&gt;
const PROMPT_CACHE_KEY = &amp;quot;mery-gpt-call-v1&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
// 生成AIのモデル名&lt;br /&gt;
&lt;br /&gt;
const MODEL_CONFIG = {&lt;br /&gt;
    model: &amp;quot;gpt-5.6-terra&amp;quot;,&lt;br /&gt;
    reasoning: { effort: &amp;quot;medium&amp;quot; },&lt;br /&gt;
    max_output_tokens: 4000,&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// console.log に出力する内容を Mery のアウトプットバーに表示する&lt;br /&gt;
const console = {&lt;br /&gt;
    log: function (str) {&lt;br /&gt;
        outputBar.Writeln(str)&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
const doc = document;&lt;br /&gt;
const sel = document.selection;&lt;br /&gt;
&lt;br /&gt;
// 生成リクエスト中止フラグ&lt;br /&gt;
let abortRequestFlg = false&lt;br /&gt;
// 生成リクエスト完了フラグ;&lt;br /&gt;
let requestCompletedFlg = false;&lt;br /&gt;
&lt;br /&gt;
main();&lt;br /&gt;
&lt;br /&gt;
async function main() {&lt;br /&gt;
    let promptText = sel.Text.trim();&lt;br /&gt;
&lt;br /&gt;
    // プロンプトが空の場合はエラー&lt;br /&gt;
    if (promptText.length === 0) {&lt;br /&gt;
        alert(&amp;quot;プロンプトを入力してください。&amp;quot;);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
    // 文字数が多すぎる場合はエラー&lt;br /&gt;
    if (roughTokenize(promptText) &amp;gt; MAX_PROMPT_TOKEN) {&lt;br /&gt;
        alert(&amp;quot;プロンプトの文字数が多すぎます。文字数を少なくしてください。&amp;quot;);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    // テンプレートの場合はプロンプトを変更&lt;br /&gt;
    const isTemplate = doc.Tag.exists(SELECTED_TEMPLATE_PROMPT_TAG);&lt;br /&gt;
    if (isTemplate) {&lt;br /&gt;
        let instructionText = doc.Tag[SELECTED_TEMPLATE_PROMPT_TAG];&lt;br /&gt;
        promptText = `&lt;br /&gt;
以下の &amp;lt;prompt_set&amp;gt; 内の &amp;lt;instructions&amp;gt; の指示に従って、 &amp;lt;context&amp;gt; を処理してください。&lt;br /&gt;
&lt;br /&gt;
&amp;lt;prompt_set&amp;gt;&lt;br /&gt;
&amp;lt;instructions&amp;gt;&lt;br /&gt;
${instructionText}&lt;br /&gt;
&amp;lt;/instructions&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;context&amp;gt;&lt;br /&gt;
${promptText}&lt;br /&gt;
&amp;lt;/context&amp;gt;&lt;br /&gt;
&amp;lt;/prompt_set&amp;gt;&lt;br /&gt;
        `.trim();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    try {&lt;br /&gt;
        // 処理終了を待機&lt;br /&gt;
        shell.KeepRunning = true;&lt;br /&gt;
&lt;br /&gt;
        // カーソル位置移動&lt;br /&gt;
        sel.SetActivePoint(mePosLogical, sel.GetBottomPointX(mePosLogical), sel.GetBottomPointY(mePosLogical));&lt;br /&gt;
        sel.EndOfLine(false, mePosLogical);&lt;br /&gt;
        sel.NewLine();&lt;br /&gt;
        sel.Text = &amp;quot;\n---\n\n&amp;quot;&lt;br /&gt;
&lt;br /&gt;
        document.KeepScrollPos = true;&lt;br /&gt;
&lt;br /&gt;
        // APIキーの取得&lt;br /&gt;
        const apiKey = shell.getEnv(&amp;quot;OPENAI_API_KEY&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
        // gpt API に問い合わせ&lt;br /&gt;
        await Promise.all([&lt;br /&gt;
            callApi(apiKey, promptText, isTemplate),&lt;br /&gt;
            abortRequest(),&lt;br /&gt;
        ]);&lt;br /&gt;
        if (sel.GetActivePointX(mePosLogical) &amp;gt; 2) {&lt;br /&gt;
            sel.Text = &amp;quot;\n\n---\n\n\n&amp;quot;&lt;br /&gt;
        } else {&lt;br /&gt;
            sel.Text = &amp;quot;\n---\n\n\n&amp;quot;&lt;br /&gt;
        }&lt;br /&gt;
    } catch (e) {&lt;br /&gt;
        console.log(&amp;quot;エラーが発生しました: &amp;quot; + e.message + &amp;quot;\n&amp;quot; + e.stack);&lt;br /&gt;
        sel.Text = &amp;quot;&amp;lt;処理中にエラー発生&amp;gt;&amp;quot;&lt;br /&gt;
    } finally {&lt;br /&gt;
        ShowTip(&amp;quot;&amp;quot;, meShowTipHide);&lt;br /&gt;
        // 処理終了待機を解除&lt;br /&gt;
        shell.KeepRunning = false;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// Shift キーが長押しされた場合はリクエスト中止フラグを立てる&lt;br /&gt;
async function abortRequest() {&lt;br /&gt;
    const VIRTUAL_KEY_CODE_SHIFT = 0x10; // Shift キーの仮想キーコード&lt;br /&gt;
    let pressCount = 0;&lt;br /&gt;
    while (!requestCompletedFlg) {&lt;br /&gt;
        await new Promise(resolve =&amp;gt; setTimeout(resolve, 300));&lt;br /&gt;
        if (shell.GetKeyState(VIRTUAL_KEY_CODE_SHIFT) &amp;lt; 0) {&lt;br /&gt;
            pressCount++;&lt;br /&gt;
            if (pressCount &amp;gt; 2) {&lt;br /&gt;
                abortRequestFlg = true;&lt;br /&gt;
                return;&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
async function callApi(apiKey, promptText, isTemplate) {&lt;br /&gt;
    const controller = new AbortController();&lt;br /&gt;
    const signal = controller.signal;&lt;br /&gt;
    try {&lt;br /&gt;
        // Responses API にストリーミングでリクエスト&lt;br /&gt;
&lt;br /&gt;
        const customInstructionText = buildCustomInstructionText();&lt;br /&gt;
        const historyData = isTemplate ? null : getHistoryData();&lt;br /&gt;
        // 会話中は最初のターンで保存した instructions を継続して使用する&lt;br /&gt;
        const instructionText = getHistoryInstruction(historyData, customInstructionText);&lt;br /&gt;
        const requestInput = createRequestInput(promptText, isTemplate, historyData);&lt;br /&gt;
        const requestBody = {&lt;br /&gt;
            input: requestInput,&lt;br /&gt;
            instructions: instructionText,&lt;br /&gt;
            prompt_cache_key: PROMPT_CACHE_KEY,&lt;br /&gt;
            stream: true,&lt;br /&gt;
        };&lt;br /&gt;
&lt;br /&gt;
        for (let key in MODEL_CONFIG) {&lt;br /&gt;
            requestBody[key] = MODEL_CONFIG[key];&lt;br /&gt;
        }&lt;br /&gt;
        const url = &#039;https://api.openai.com/v1/responses&#039;;&lt;br /&gt;
&lt;br /&gt;
        const apiCallStartTime = Date.now();&lt;br /&gt;
        const response = await fetch(url, {&lt;br /&gt;
            signal: signal,&lt;br /&gt;
            method: &#039;POST&#039;,&lt;br /&gt;
            headers: {&lt;br /&gt;
                &#039;Content-Type&#039;: &#039;application/json&#039;,&lt;br /&gt;
                &#039;Authorization&#039;: &#039;Bearer &#039; + apiKey,&lt;br /&gt;
            },&lt;br /&gt;
            body: JSON.stringify(requestBody),&lt;br /&gt;
        });&lt;br /&gt;
        if (!response.ok) {&lt;br /&gt;
            let errorMessage = response.status + &amp;quot; &amp;quot; + response.statusText;&lt;br /&gt;
            try {&lt;br /&gt;
                const errorData = await response.json();&lt;br /&gt;
                errorMessage = errorData?.error?.message || errorMessage;&lt;br /&gt;
            } catch (_) {&lt;br /&gt;
                // エラー本文が JSON でない場合は HTTP ステータスを使用する&lt;br /&gt;
            }&lt;br /&gt;
            throw new Error(&amp;quot;Responses API エラー: &amp;quot; + errorMessage);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        let buffer = &amp;quot;&amp;quot;;&lt;br /&gt;
        let responseAll = &amp;quot;&amp;quot;;&lt;br /&gt;
        let usageText = &amp;quot;&amp;quot;;&lt;br /&gt;
        const reader = response.body.getReader();&lt;br /&gt;
        const decoder = new TextDecoder(&#039;utf-8&#039;);&lt;br /&gt;
        while (true) {&lt;br /&gt;
            // リクエスト中止フラグが立っている場合は中断&lt;br /&gt;
            if (abortRequestFlg) {&lt;br /&gt;
                controller.abort();&lt;br /&gt;
                sel.Text = &amp;quot;&amp;lt;処理を中止&amp;gt;&amp;quot;&lt;br /&gt;
                return;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            const { done, value } = await reader.read();&lt;br /&gt;
            if (done) {&lt;br /&gt;
                // デコーダーに残った文字列と最後の SSE 行を処理&lt;br /&gt;
                const finalText = buffer + decoder.decode();&lt;br /&gt;
                let finalInputText = &amp;quot;&amp;quot;;&lt;br /&gt;
                for (const line of finalText.split(/\n+/)) {&lt;br /&gt;
                    const processedData = processLine(line, responseAll);&lt;br /&gt;
                    if (processedData) {&lt;br /&gt;
                        finalInputText += processedData.inputText || &amp;quot;&amp;quot;;&lt;br /&gt;
                        usageText = processedData.usageText || usageText;&lt;br /&gt;
                        responseAll = processedData.responseAll || responseAll;&lt;br /&gt;
                    }&lt;br /&gt;
                }&lt;br /&gt;
                if (finalInputText) {&lt;br /&gt;
                    sel.Text = finalInputText;&lt;br /&gt;
                }&lt;br /&gt;
                break;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            // 受信したバイナリデータ (value) をテキストに変換する&lt;br /&gt;
            const text = decoder.decode(value, { stream: true });&lt;br /&gt;
&lt;br /&gt;
            // 前回のバッファと今回のテキストを結合&lt;br /&gt;
            const combinedText = buffer + text;&lt;br /&gt;
&lt;br /&gt;
            // 完全な行を探す（最後の改行の位置を見つける）&lt;br /&gt;
            const lastNewlineIndex = combinedText.lastIndexOf(&#039;\n&#039;);&lt;br /&gt;
&lt;br /&gt;
            if (lastNewlineIndex === -1) {&lt;br /&gt;
                // 改行がない場合は全てバッファに保存&lt;br /&gt;
                buffer = combinedText;&lt;br /&gt;
                continue;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            // 完全な行を処理&lt;br /&gt;
            const completeText = combinedText.substring(0, lastNewlineIndex);&lt;br /&gt;
            // 残りをバッファに保存&lt;br /&gt;
            buffer = combinedText.substring(lastNewlineIndex + 1);&lt;br /&gt;
&lt;br /&gt;
            // 完全な行を処理&lt;br /&gt;
            const lines = completeText.split(/\n+/);&lt;br /&gt;
&lt;br /&gt;
            let inputText = &amp;quot;&amp;quot;;&lt;br /&gt;
            for (const line of lines) {&lt;br /&gt;
                // 行の処理（既存のコードと同様）&lt;br /&gt;
                const processedData = processLine(line, responseAll);&lt;br /&gt;
                if (processedData) {&lt;br /&gt;
                    inputText += processedData.inputText || &amp;quot;&amp;quot;;&lt;br /&gt;
                    usageText = processedData.usageText || usageText;&lt;br /&gt;
                    responseAll = processedData.responseAll || responseAll;&lt;br /&gt;
                }&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            if (inputText.length &amp;gt; 0) {&lt;br /&gt;
                // 上記までで数文字まとまって取得されるので、まとめて入力&lt;br /&gt;
                sel.Text = inputText;&lt;br /&gt;
            }&lt;br /&gt;
            // 入力を少しゆっくりにする&lt;br /&gt;
            await new Promise(resolve =&amp;gt; setTimeout(resolve, INPUT_DELAY));&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        // トークン数・所要時間を入力&lt;br /&gt;
        const elapsedSec = ((Date.now() - apiCallStartTime) / 1000).toFixed(1);&lt;br /&gt;
        const elapsedText = &amp;quot;[所要時間] &amp;quot; + elapsedSec + &amp;quot; sec&amp;quot;;&lt;br /&gt;
        if (usageText) {&lt;br /&gt;
            usageText += &amp;quot; &amp;quot; + elapsedText;&lt;br /&gt;
        } else {&lt;br /&gt;
            usageText = elapsedText;&lt;br /&gt;
        }&lt;br /&gt;
        sel.Text = (responseAll.slice(-1) === &amp;quot;\n&amp;quot; ? &amp;quot;\n&amp;quot; : &amp;quot;\n\n&amp;quot;) + `${usageText}`;&lt;br /&gt;
&lt;br /&gt;
        // 結果を会話履歴に追加&lt;br /&gt;
        if (HISTORY_SIZE &amp;gt; 0 &amp;amp;&amp;amp; !isTemplate) {&lt;br /&gt;
            appendReturnTextToTag(promptText, responseAll, instructionText);&lt;br /&gt;
        }&lt;br /&gt;
    } catch (e) {&lt;br /&gt;
        controller.abort();&lt;br /&gt;
        throw e;&lt;br /&gt;
    } finally {&lt;br /&gt;
        requestCompletedFlg = true;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// Responses API の SSE イベントを一行ずつ処理する&lt;br /&gt;
function processLine(line, responseAll) {&lt;br /&gt;
    // HACK 万が一空行が入ってきた場合に備えて trim() しておく&lt;br /&gt;
    const json_text = line.trim().replace(/^data:\s*/, &#039;&#039;);&lt;br /&gt;
    // データがない場合はスキップ&lt;br /&gt;
    if (!json_text) {&lt;br /&gt;
        return null;&lt;br /&gt;
    }&lt;br /&gt;
    // SSE コメント行 (: で始まる行) はスキップ&lt;br /&gt;
    if (json_text.startsWith(&#039;:&#039;)) {&lt;br /&gt;
        return null;&lt;br /&gt;
    }&lt;br /&gt;
    // SSE の event: 行はイベント名だけを表し、データ本体ではない&lt;br /&gt;
    if (json_text.startsWith(&#039;event:&#039;)) {&lt;br /&gt;
        return null;&lt;br /&gt;
    }&lt;br /&gt;
    // データが終了した場合は終了&lt;br /&gt;
    if (json_text === &#039;[DONE]&#039;) {&lt;br /&gt;
        return null;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    let data;&lt;br /&gt;
    try {&lt;br /&gt;
        data = JSON.parse(json_text);&lt;br /&gt;
    } catch (e) {&lt;br /&gt;
        console.log(&amp;quot;JSONパースエラー: &amp;quot; + e.message + &amp;quot;, データ: &amp;quot; + json_text);&lt;br /&gt;
        return null;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (data.type === &amp;quot;error&amp;quot;) {&lt;br /&gt;
        throw new Error(data.error?.message || &amp;quot;Responses API ストリームエラー&amp;quot;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    try {&lt;br /&gt;
        let usageText = &amp;quot;&amp;quot;;&lt;br /&gt;
        let inputText = &amp;quot;&amp;quot;;&lt;br /&gt;
        let updatedResponseAll = responseAll;&lt;br /&gt;
&lt;br /&gt;
        // 利用量は response.completed イベント内の response.usage に含まれる&lt;br /&gt;
        const usage = data.response?.usage || data.usage;&lt;br /&gt;
        if (usage) {&lt;br /&gt;
            usageText = &amp;quot;[利用トークン数] 入力:&amp;quot; + usage.input_tokens&lt;br /&gt;
                + &amp;quot; 出力:&amp;quot; + usage.output_tokens&lt;br /&gt;
                + &amp;quot; 合計:&amp;quot; + usage.total_tokens;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        // テキストは response.output_text.delta イベントで届く&lt;br /&gt;
        if (data.type === &amp;quot;response.output_text.delta&amp;quot; &amp;amp;&amp;amp; data.delta) {&lt;br /&gt;
            inputText = data.delta;&lt;br /&gt;
            updatedResponseAll += data.delta;&lt;br /&gt;
        }&lt;br /&gt;
        return { inputText, usageText, responseAll: updatedResponseAll };&lt;br /&gt;
    } catch (e) {&lt;br /&gt;
        console.log(&amp;quot;Responses API イベント処理エラー: &amp;quot; + e.message + &amp;quot;, データ: &amp;quot; + json_text);&lt;br /&gt;
        return null;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function roughTokenize(text) {&lt;br /&gt;
    // 超適当なトークナイザ。トークン数をざっくりと多めに数える&lt;br /&gt;
    // 空白文字・ハイフン・アンダースコア区切りの単語に分割して、&lt;br /&gt;
    // 単語がアルファベットのみ、数字のみの場合には 4文字=1トークン として数える。&lt;br /&gt;
    // 単語にアルファベット以外の文字が含まれる場合は 1文字=1トークン として数える。&lt;br /&gt;
    return text&lt;br /&gt;
        .split(/[\s\-_]+/)&lt;br /&gt;
        .map(w =&amp;gt; /^[a-zA-Z]+$|^[0-9]+$|/.test(w) ? Math.ceil(w.length / 4) : w.length)&lt;br /&gt;
        .reduce((a, b) =&amp;gt; a + b, 0);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// document.Tag に保存された会話履歴を取得する&lt;br /&gt;
function getHistoryData() {&lt;br /&gt;
    const tag = document.Tag;&lt;br /&gt;
    return tag.exists(HISTORY_TAG) ? JSON.parse(tag[HISTORY_TAG]) : null;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// 保存済みの instructions を取得する&lt;br /&gt;
function getHistoryInstruction(historyData, fallbackInstruction) {&lt;br /&gt;
    return historyData ? historyData.instruction : fallbackInstruction;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// タグの messages を取得する&lt;br /&gt;
function extractHistoryTexts(historyData) {&lt;br /&gt;
    return historyData ? historyData.messages : [];&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// document.Tag の内容を加味して Responses API の input を作成&lt;br /&gt;
function createRequestInput(promptText, isTemplate, historyData) {&lt;br /&gt;
    // テンプレートの場合は会話履歴を反映しない&lt;br /&gt;
    if (isTemplate) {&lt;br /&gt;
        return [{ &amp;quot;role&amp;quot;: &amp;quot;user&amp;quot;, &amp;quot;content&amp;quot;: promptText }];&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // 以前の会話が存在しない場合は新規リクエスト&lt;br /&gt;
    if (!historyData) {&lt;br /&gt;
        return [{ &amp;quot;role&amp;quot;: &amp;quot;user&amp;quot;, &amp;quot;content&amp;quot;: promptText }];&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // 会話履歴が存在する場合は、履歴を含めてリクエスト&lt;br /&gt;
    const input = extractHistoryTexts(historyData)&lt;br /&gt;
        .map((message, index) =&amp;gt; ({&lt;br /&gt;
            role: index % 2 === 0 ? &amp;quot;user&amp;quot; : &amp;quot;assistant&amp;quot;,&lt;br /&gt;
            content: message&lt;br /&gt;
        }));&lt;br /&gt;
    input.push({&lt;br /&gt;
        &amp;quot;role&amp;quot;: &amp;quot;user&amp;quot;,&lt;br /&gt;
        &amp;quot;content&amp;quot;: promptText&lt;br /&gt;
    });&lt;br /&gt;
    return input;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// document.Tag にgptの応答を追加&lt;br /&gt;
function appendReturnTextToTag(userText, modelText, instructionText) {&lt;br /&gt;
    const tag = document.Tag;&lt;br /&gt;
    const messages = extractHistoryTexts(getHistoryData());&lt;br /&gt;
&lt;br /&gt;
    messages.push(userText, modelText);&lt;br /&gt;
    // 会話セット (user, model 1往復) が特定セット数を超えた場合、最初のセットを削除&lt;br /&gt;
    if (messages.length &amp;gt; HISTORY_SIZE * 2) {&lt;br /&gt;
        messages.splice(0, 2);&lt;br /&gt;
    }&lt;br /&gt;
    // 会話履歴全体が MAX_HISTORY_TOKEN を超えた場合、最初のセットを削除&lt;br /&gt;
    let joinedText = messages.join(&amp;quot;\n&amp;quot;);&lt;br /&gt;
    while (messages.length &amp;gt;= 2 &amp;amp;&amp;amp; roughTokenize(joinedText) &amp;gt; MAX_HISTORY_TOKEN) {&lt;br /&gt;
        messages.splice(0, 2);&lt;br /&gt;
        joinedText = messages.join(&amp;quot;\n&amp;quot;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    tag[HISTORY_TAG] = JSON.stringify({&lt;br /&gt;
        instruction: instructionText,&lt;br /&gt;
        messages: messages&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getLocalIsoDateTimeString() {&lt;br /&gt;
    const now = new Date();&lt;br /&gt;
    const pad = function (num) {&lt;br /&gt;
        return (num &amp;lt; 10 ? &amp;quot;0&amp;quot; : &amp;quot;&amp;quot;) + num;&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    const year = now.getFullYear();&lt;br /&gt;
    const month = pad(now.getMonth() + 1);&lt;br /&gt;
    const day = pad(now.getDate());&lt;br /&gt;
    const hours = pad(now.getHours());&lt;br /&gt;
    const minutes = pad(now.getMinutes());&lt;br /&gt;
    const seconds = pad(now.getSeconds());&lt;br /&gt;
&lt;br /&gt;
    const offsetMinutes = now.getTimezoneOffset();&lt;br /&gt;
    const offsetSign = offsetMinutes &amp;lt;= 0 ? &amp;quot;+&amp;quot; : &amp;quot;-&amp;quot;;&lt;br /&gt;
    const absOffsetHours = pad(Math.floor(Math.abs(offsetMinutes) / 60));&lt;br /&gt;
    const absOffsetMinutes = pad(Math.abs(offsetMinutes) % 60);&lt;br /&gt;
&lt;br /&gt;
    const isoLikeLocal = year + &amp;quot;-&amp;quot; + month + &amp;quot;-&amp;quot; + day&lt;br /&gt;
        + &amp;quot;T&amp;quot; + hours + &amp;quot;:&amp;quot; + minutes + &amp;quot;:&amp;quot; + seconds&lt;br /&gt;
        + offsetSign + absOffsetHours + &amp;quot;:&amp;quot; + absOffsetMinutes;&lt;br /&gt;
    return isoLikeLocal;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function baseCustomInstruction() {&lt;br /&gt;
    const language = LANGUAGE.trim();&lt;br /&gt;
    return &amp;quot;The current date and time for this user is &amp;quot; + getLocalIsoDateTimeString() + &amp;quot;. You cannot know for sure what happens after the knowledge cut-off date.&amp;quot;&lt;br /&gt;
        + (language ? &amp;quot;Respond in &amp;quot; + language + &amp;quot;\n&amp;quot; : &amp;quot;&amp;quot;)&lt;br /&gt;
        + &amp;quot;Do not hallucinate.\n&amp;quot;&lt;br /&gt;
        + &amp;quot;**Start with the main point of the question without explaining or declaring your writing style or tone.**\n&amp;quot;&lt;br /&gt;
        + &amp;quot;**At the end of your answer, avoid adding formulaic phrases that prompt the next question or unrelated conversation that is not directly related to your answer.**\n&amp;quot;&lt;br /&gt;
    // + &amp;quot;Do NOT EVER use full-width brackets \&amp;quot;（）\&amp;quot;, but use half-width brackets \&amp;quot;()\&amp;quot;. Enclose the outside of the half-width brackets with half-width spaces.\n&amp;quot;&lt;br /&gt;
    // + &amp;quot;When using Markdown formatting, you MUST follow these rules:\n&amp;quot;&lt;br /&gt;
    // + &amp;quot;- Rule 1: Insert a blank line below the header line (A header line begins with one or more # characters).\n&amp;quot;&lt;br /&gt;
    // + &amp;quot;- Rule 2: Insert spaces before and after bold (surrounded by **), italic (surrounded by *), and inline code (surrounded by `). For example: \&amp;quot;私は **元気** です。\&amp;quot;\n&amp;quot;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function buildCustomInstructionText() {&lt;br /&gt;
    let customInstruction = getTemporaryCustomInstruction();&lt;br /&gt;
    if (!customInstruction) {&lt;br /&gt;
        // 一時的なカスタムインストラクションがない場合はデフォルトを使用&lt;br /&gt;
        customInstruction = DEFAULT_CUSTOM_INSTRUCTION;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    customInstruction = customInstruction.replace(/^[\s　]+|[\s　]+$/g, &amp;quot;&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
    return baseCustomInstruction().trim()&lt;br /&gt;
        + (customInstruction ? &amp;quot;\n&amp;quot; + customInstruction : &amp;quot;&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getTemporaryCustomInstruction() {&lt;br /&gt;
    var tag = doc.Tag;&lt;br /&gt;
    if (tag.exists(TEMPORARY_CUSTOM_INSTRUCTION_TAG)) {&lt;br /&gt;
        return tag[TEMPORARY_CUSTOM_INSTRUCTION_TAG];&lt;br /&gt;
    } else {&lt;br /&gt;
        return &amp;quot;&amp;quot;;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function generateRandomId(length = 16) {&lt;br /&gt;
    const chars = &#039;ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789&#039;;&lt;br /&gt;
    let result = &#039;&#039;;&lt;br /&gt;
    for (let i = 0; i &amp;lt; length; i++) {&lt;br /&gt;
        result += chars[Math.floor(Math.random() * chars.length)];&lt;br /&gt;
    }&lt;br /&gt;
    return result;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 従来エンジン版 (アーカイブ) ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot; copy&amp;gt;&lt;br /&gt;
#title = &amp;quot;GPT に相談&amp;quot;&lt;br /&gt;
BeginUndoGroup();&lt;br /&gt;
&lt;br /&gt;
// 会話の履歴を保持する数 (質問と回答で1セット)&lt;br /&gt;
// 数を増やすと、会話の履歴が増えますが、Meryのメモリ使用量が増えたり、&lt;br /&gt;
// 動作が重くなる可能性があるため、適宜調整してください。&lt;br /&gt;
// document.Tag に保持されるため、開いているファイルを閉じると履歴は消えます。&lt;br /&gt;
var HISTORY_SIZE = 4;&lt;br /&gt;
&lt;br /&gt;
// コンテキストメニュー番号&lt;br /&gt;
var EXECUTE_AI = 1;&lt;br /&gt;
var RESET_HISTORY = 2;&lt;br /&gt;
&lt;br /&gt;
var ConsoleClass = function () {&lt;br /&gt;
    this.log = function (str) {&lt;br /&gt;
        outputBar.Writeln(str)&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
var console = new ConsoleClass();&lt;br /&gt;
&lt;br /&gt;
var doc = document;&lt;br /&gt;
var sel = document.selection;&lt;br /&gt;
&lt;br /&gt;
main();&lt;br /&gt;
&lt;br /&gt;
function main() {&lt;br /&gt;
    var selectionIsEmpty = sel.IsEmpty&lt;br /&gt;
    var beforeScrollY = ScrollY;&lt;br /&gt;
&lt;br /&gt;
    // プロンプトの取得&lt;br /&gt;
    var promptText = &amp;quot;&amp;quot;;&lt;br /&gt;
    var promptLength = 0;&lt;br /&gt;
    if (selectionIsEmpty) {&lt;br /&gt;
        sel.StartOfLine(false, mePosLogical);&lt;br /&gt;
        sel.EndOfLine(true, mePosLogical);&lt;br /&gt;
    }&lt;br /&gt;
    promptText = sel.Text.trim();&lt;br /&gt;
    promptLength = sel.TextLength;&lt;br /&gt;
    // コンテキストメニューの作成&lt;br /&gt;
    var menu = createContextMenu(selectionIsEmpty);&lt;br /&gt;
    var menuTrack = menu.Track(0);&lt;br /&gt;
    // メニュー選択がキャンセルされた場合は終了&lt;br /&gt;
    if (menuTrack === 0) return;&lt;br /&gt;
&lt;br /&gt;
    // 会話履歴のリセット&lt;br /&gt;
    if (menuTrack === 2) {&lt;br /&gt;
        resetTag();&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // プロンプトが空の場合はエラー&lt;br /&gt;
    if (promptLength === 0) {&lt;br /&gt;
        alert(&amp;quot;プロンプトを入力してください。&amp;quot;);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
    // 文字数が多すぎる場合はエラー&lt;br /&gt;
    if (promptLength &amp;gt; 10000) {&lt;br /&gt;
        alert(&amp;quot;プロンプトの文字数が多すぎます。10000 文字以下にしてください。&amp;quot;);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
    // 読み取り専用の場合はエラー&lt;br /&gt;
    if (doc.ReadOnly) {&lt;br /&gt;
        alert(&amp;quot;ファイルが読み取り専用のため実行できません。&amp;quot;);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // カーソル位置移動&lt;br /&gt;
    sel.SetActivePoint(mePosLogical, sel.GetBottomPointX(mePosLogical), sel.GetBottomPointY(mePosLogical));&lt;br /&gt;
    sel.EndOfLine(false, mePosLogical);&lt;br /&gt;
    sel.NewLine();&lt;br /&gt;
    var activeY = sel.GetActivePointY(mePosLogical);&lt;br /&gt;
&lt;br /&gt;
    // GPT API に問い合わせ&lt;br /&gt;
    doc.ReadOnly = true;&lt;br /&gt;
    var ret = callGPTAPI(promptText);&lt;br /&gt;
    doc.ReadOnly = false;&lt;br /&gt;
&lt;br /&gt;
    // 結果を表示&lt;br /&gt;
    sel.Text = &amp;quot;\n---\n\n&amp;quot; + ret[0] + &amp;quot;\n\n&amp;quot; + ret[1] + &amp;quot;\n\n---\n&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
    // 位置調整&lt;br /&gt;
    sel.SetActivePoint(&lt;br /&gt;
        mePosLogical,&lt;br /&gt;
        1,&lt;br /&gt;
        activeY,&lt;br /&gt;
        false&lt;br /&gt;
    )&lt;br /&gt;
    if (beforeScrollY + 2 &amp;lt; ScrollY) {&lt;br /&gt;
        ScrollY = ScrollY - 1;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function createContextMenu(selectionIsEmpty) {&lt;br /&gt;
    var menu = CreatePopupMenu();&lt;br /&gt;
    menu.Add(&amp;quot;※注意※ プライバシー・機密情報を送信しないこと&amp;quot;, 0);&lt;br /&gt;
    menu.Add(&amp;quot;&amp;quot;, 0, meMenuSeparator);&lt;br /&gt;
    if (selectionIsEmpty) {&lt;br /&gt;
        menu.Add(&amp;quot;アクティブ行の内容で相談 (&amp;amp;Q)&amp;quot;, EXECUTE_AI);&lt;br /&gt;
    } else {&lt;br /&gt;
        menu.Add(&amp;quot;選択範囲の内容で相談 (&amp;amp;Q)&amp;quot;, EXECUTE_AI);&lt;br /&gt;
    }&lt;br /&gt;
    if (document.Tag.exists(&amp;quot;gpt-text&amp;quot;)) {&lt;br /&gt;
        var turn = JSON.parse(document.Tag(&amp;quot;gpt-text&amp;quot;)).length / 2;&lt;br /&gt;
        menu.Add(&amp;quot;会話履歴をリセット (現在の会話数: &amp;quot; + turn + &amp;quot;) (&amp;amp;R)&amp;quot;, RESET_HISTORY);&lt;br /&gt;
    }&lt;br /&gt;
    return menu;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function callGPTAPI(promptText) {&lt;br /&gt;
    try {&lt;br /&gt;
        // APIキーの取得&lt;br /&gt;
        var gptApiKey = shell.getEnv(&amp;quot;OPENAI_API_KEY&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
        var http = new ActiveXObject(&amp;quot;Msxml2.ServerXMLHTTP&amp;quot;);&lt;br /&gt;
        var url = &#039;https://api.openai.com/v1/chat/completions&#039;;&lt;br /&gt;
&lt;br /&gt;
        http.open(&#039;POST&#039;, url, true);&lt;br /&gt;
        http.setRequestHeader(&#039;Content-Type&#039;, &#039;application/json&#039;);&lt;br /&gt;
        http.setRequestHeader(&#039;Authorization&#039;, &#039;Bearer &#039; + gptApiKey);&lt;br /&gt;
        http.send(&#039;{&#039;&lt;br /&gt;
            + &#039;&amp;quot;model&amp;quot;: &amp;quot;gpt-4o-mini&amp;quot;,&#039; // モデルの指定&lt;br /&gt;
            + &#039;&amp;quot;temperature&amp;quot;: 0.7,&#039; // 生成の多様性 (0.0 ～ 1.0)&lt;br /&gt;
            + &#039;&amp;quot;max_tokens&amp;quot;: 4000,&#039; // 生成トークン数の上限&lt;br /&gt;
            + &#039;&amp;quot;messages&amp;quot;:&#039; + createGPTRequest(promptText)&lt;br /&gt;
            + &#039;}&#039;);&lt;br /&gt;
&lt;br /&gt;
        // 応答待機&lt;br /&gt;
        var timeout = 60;&lt;br /&gt;
        var retrySec = 0;&lt;br /&gt;
        var startX = sel.GetActivePointX(mePosLogical);&lt;br /&gt;
        var prevPos = sel.GetActivePos();&lt;br /&gt;
        while (http.readyState != 4) {&lt;br /&gt;
            if (retrySec &amp;gt; timeout) {&lt;br /&gt;
                break;&lt;br /&gt;
            }&lt;br /&gt;
            http.waitForResponse(1);&lt;br /&gt;
            retrySec += 1;&lt;br /&gt;
            inputProgressChar(prevPos);&lt;br /&gt;
            prevPos = sel.GetActivePos();&lt;br /&gt;
        }&lt;br /&gt;
        // 進捗表示文字を削除&lt;br /&gt;
        sel.SetActivePoint(&lt;br /&gt;
            mePosLogical,&lt;br /&gt;
            startX,&lt;br /&gt;
            sel.GetActivePointY(mePosLogical),&lt;br /&gt;
            true&lt;br /&gt;
        );&lt;br /&gt;
        sel.Delete();&lt;br /&gt;
&lt;br /&gt;
        var responseMessage = &amp;quot;&amp;quot;;&lt;br /&gt;
        var token = &amp;quot;&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
        // 応答ステータスチェック&lt;br /&gt;
        if (http.readyState == 4 &amp;amp;&amp;amp; http.status == 200) {&lt;br /&gt;
            // JSONをパースして必要なテキストを抽出&lt;br /&gt;
            var response = JSON.parse(http.responseText);&lt;br /&gt;
            if (response.choices &amp;amp;&amp;amp; response.choices.length &amp;gt; 0) {&lt;br /&gt;
                // メッセージの内容を取得&lt;br /&gt;
                response.choices.forEach(function (choice) {&lt;br /&gt;
                    if (choice.message &amp;amp;&amp;amp; choice.message.content) {&lt;br /&gt;
                        responseMessage += choice.message.content;&lt;br /&gt;
                    }&lt;br /&gt;
                });&lt;br /&gt;
                // トークン数を取得&lt;br /&gt;
                var usage = response.usage;&lt;br /&gt;
                token += &amp;quot;(使用トークン数: &amp;quot;&lt;br /&gt;
                    + &amp;quot;プロンプト=&amp;quot; + usage.prompt_tokens&lt;br /&gt;
                    + &amp;quot;, 応答=&amp;quot; + usage.completion_tokens&lt;br /&gt;
                    + &amp;quot;, 合計=&amp;quot; + usage.total_tokens&lt;br /&gt;
                    + &amp;quot;)&amp;quot;;&lt;br /&gt;
            }&lt;br /&gt;
        } else {&lt;br /&gt;
            var responseMessage = &amp;quot;エラーが発生しました: &amp;quot; + http.status + &amp;quot; &amp;quot; + http.statusText&lt;br /&gt;
            http.abort();&lt;br /&gt;
            return responseMessage;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        // 結果を会話履歴に追加&lt;br /&gt;
        if (HISTORY_SIZE &amp;gt; 0) {&lt;br /&gt;
            appendReturnTextToTag(promptText, responseMessage);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        // 整形しつつ返す&lt;br /&gt;
        return [responseMessage.trim(), token];&lt;br /&gt;
    } catch (e) {&lt;br /&gt;
        console.log(e.stack);&lt;br /&gt;
        return [&amp;quot;エラーが発生しました: &amp;quot; + e.message, &amp;quot;&amp;quot;];&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// document.Tag の内容を加味してGPTのリクエストを作成&lt;br /&gt;
function createGPTRequest(promptText) {&lt;br /&gt;
    var tag = document.Tag;&lt;br /&gt;
    // 以前の会話が存在しない場合&lt;br /&gt;
    if (!tag.exists(&amp;quot;gpt-text&amp;quot;)) {&lt;br /&gt;
        return JSON.stringify([{ &amp;quot;role&amp;quot;: &amp;quot;user&amp;quot;, &amp;quot;content&amp;quot;: promptText }]);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    var gptText = JSON.parse(tag(&amp;quot;gpt-text&amp;quot;));&lt;br /&gt;
    gptText.push({&lt;br /&gt;
        &amp;quot;role&amp;quot;: &amp;quot;user&amp;quot;,&lt;br /&gt;
        &amp;quot;content&amp;quot;: promptText&lt;br /&gt;
    });&lt;br /&gt;
    return JSON.stringify(gptText);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// document.Tag にGPTの応答を追加&lt;br /&gt;
function appendReturnTextToTag(userText, modelText) {&lt;br /&gt;
    var tag = document.Tag;&lt;br /&gt;
    if (!tag.exists(&amp;quot;gpt-text&amp;quot;)) {&lt;br /&gt;
        tag(&amp;quot;gpt-text&amp;quot;) = JSON.stringify([&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;role&amp;quot;: &amp;quot;user&amp;quot;,&lt;br /&gt;
                &amp;quot;content&amp;quot;: userText&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;role&amp;quot;: &amp;quot;assistant&amp;quot;,&lt;br /&gt;
                &amp;quot;content&amp;quot;: modelText&lt;br /&gt;
            }&lt;br /&gt;
        ]);&lt;br /&gt;
    } else {&lt;br /&gt;
        var gptText = JSON.parse(tag(&amp;quot;gpt-text&amp;quot;));&lt;br /&gt;
        gptText.push({&lt;br /&gt;
            &amp;quot;role&amp;quot;: &amp;quot;user&amp;quot;,&lt;br /&gt;
            &amp;quot;content&amp;quot;: userText&lt;br /&gt;
        });&lt;br /&gt;
        gptText.push({&lt;br /&gt;
            &amp;quot;role&amp;quot;: &amp;quot;assistant&amp;quot;,&lt;br /&gt;
            &amp;quot;content&amp;quot;: modelText&lt;br /&gt;
        });&lt;br /&gt;
        // 会話セット (user, model 1往復) が特定セット数を超えた場合、最初のセットを削除&lt;br /&gt;
        while (gptText.length &amp;gt; HISTORY_SIZE * 2) {&lt;br /&gt;
            gptText.shift();&lt;br /&gt;
        }&lt;br /&gt;
        tag(&amp;quot;gpt-text&amp;quot;) = JSON.stringify(gptText);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function resetTag() {&lt;br /&gt;
    if (document.Tag.exists(&amp;quot;gpt-text&amp;quot;)) {&lt;br /&gt;
        document.Tag.remove(&amp;quot;gpt-text&amp;quot;);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function inputProgressChar(pos) {&lt;br /&gt;
    doc.ReadOnly = false;&lt;br /&gt;
    sel.SetActivePos(pos);&lt;br /&gt;
    sel.Text = &amp;quot;.&amp;quot;;&lt;br /&gt;
    doc.ReadOnly = true;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 更新履歴 ==&lt;br /&gt;
&lt;br /&gt;
* 2026-07-18 起動時間短縮のため、マクロ起動とAPI呼び出しで、マクロを分割した。その他プロンプトテンプレート更新等。&lt;br /&gt;
* 2025-02-16 カスタムインストラクションにハルシネーション抑制の指示を加えた。プロンプトに渡す文字数制限方式を調整。&lt;br /&gt;
* 2024-12-22 V8エンジン版を追加。それに伴い従来エンジン版はアーカイブ化。&lt;br /&gt;
* 2024-12-18 初版&lt;/div&gt;</summary>
		<author><name>Yuko</name></author>
	</entry>
	<entry>
		<id>https://www.haijin-boys.com/wiki/index.php?title=OpenAI_GPT_%E3%81%AB%E7%9B%B8%E8%AB%87&amp;diff=9604</id>
		<title>OpenAI GPT に相談</title>
		<link rel="alternate" type="text/html" href="https://www.haijin-boys.com/wiki/index.php?title=OpenAI_GPT_%E3%81%AB%E7%9B%B8%E8%AB%87&amp;diff=9604"/>
		<updated>2026-08-07T13:45:45Z</updated>

		<summary type="html">&lt;p&gt;Yuko: /* v8_boot_gpt.js */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== 概要 ==&lt;br /&gt;
&lt;br /&gt;
OpenAIの生成AIである「GPT」を利用するためのMeryマクロです。&lt;br /&gt;
&lt;br /&gt;
後述のソースコードではV8エンジンで動作するものと従来版スクリプトエンジンで動作するものの2種類を掲載していますが、従来版のものはアーカイブです。特段の理由がない限りはV8エンジン版をご利用ください。&lt;br /&gt;
&lt;br /&gt;
【主な機能】&lt;br /&gt;
&lt;br /&gt;
* OpenAI APIを利用して生成AIと対話できるMery用のマクロ&lt;br /&gt;
* 選択したテキストやアクティブな行の内容について生成AIに質問・相談が可能&lt;br /&gt;
* 会話履歴を保持し、文脈を考慮した対話が可能&lt;br /&gt;
&lt;br /&gt;
【特徴的な機能】&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;会話履歴機能&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* 事前の設定数に応じた会話履歴を保持&lt;br /&gt;
* 履歴のクリアが可能&lt;br /&gt;
&lt;br /&gt;
これにより、会話の文脈を考慮した回答を得られます。&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;カスタムインストラクション&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* AIの応答方針を設定可能&lt;br /&gt;
* 一時的なカスタム指示の保存と変更が可能&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;プロンプトテンプレート&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* 誤字脱字チェック&lt;br /&gt;
* 日本語翻訳&lt;br /&gt;
* コードの最適化、生成&lt;br /&gt;
&lt;br /&gt;
など、定型的な質問を簡単に実行できます。&lt;br /&gt;
※テンプレート機能では回答を安定させるため、前述の会話履歴を文脈に含みません。&lt;br /&gt;
&lt;br /&gt;
テンプレートは自身の使い方に合わせて好きに追加、削除ができます。詳細はソースコード内の「PROMPT_TEMPLATE」定数のコメントを参照。&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;生成AIチャット風仕様&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* ストリーミングモードにより、生成途中の回答がリアルタイムにMeryへ順次入力される&lt;br /&gt;
* Shiftキー長押しで、テキスト生成を中断できる&lt;br /&gt;
&lt;br /&gt;
== 注意事項 (必ず読んでからご利用ください) ==&lt;br /&gt;
&lt;br /&gt;
* 本マクロではOpenAI APIを利用します。API利用に伴う料金発生やOpenAI社の情報取り扱い等によるユーザー不利益が発生しても、作者は一切の責任を負いません。&lt;br /&gt;
** &#039;&#039;&#039;OpenAI APIでは、利用前にクレジットを購入するプリペイド請求が利用されます。意図しない利用を抑えるため、オートチャージや利用上限の設定を事前に確認してください。&#039;&#039;&#039;&lt;br /&gt;
** 2026/07/18 時点、OpenAIでは、プロンプトを学習に利用することを許可 (データ共有へのオプトイン) することで一定の無料枠を獲得できる制度があります。極力コストを抑えたい方はこちらの利用も検討してもよいかもしれません (オプトイン手順はここでは説明しません)。&lt;br /&gt;
* OpenAI APIへ送信した入力・出力データは、明示的にデータ共有へオプトインしない限り、デフォルトではモデルの学習に使用されません。ただし、外部サービスへ情報を送信することに変わりはないため、機密情報、個人情報、認証情報などは入力しないでください。&lt;br /&gt;
* OpenAI APIは、 [https://openai.com/ja-JP/policies/terms-of-use/ OpenAI利用規約] に則ってご利用ください。&lt;br /&gt;
&lt;br /&gt;
== 利用前提 ==&lt;br /&gt;
&lt;br /&gt;
* Mery Ver 3.7.8 以上&lt;br /&gt;
* [https://platform.openai.com/ OpenAI Platform] で、OpenAI APIキーを取得していること&lt;br /&gt;
* OpenAI APIを利用できるだけの課金残高があること&lt;br /&gt;
&lt;br /&gt;
== 利用方法 ==&lt;br /&gt;
&lt;br /&gt;
* 環境変数の設定&lt;br /&gt;
:コマンド プロンプトで以下のコマンドを実行し、環境変数を設定してください。&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bat&amp;quot; copy&amp;gt;&lt;br /&gt;
setx OPENAI_API_KEY &amp;quot;***************************************&amp;quot;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
:設定後、Mery または Windows の再起動が必要な場合があります。&lt;br /&gt;
* マクロの実行&lt;br /&gt;
** Meryのマクロフォルダー「Macros」に、後述の「v8_boot_gpt.js」「v8_call_gpt.js」をそれぞれ配置し、「v8_boot_gpt.js」をMeryへ読み込ませてください (「v8_call_gpt.js」は配置するだけでOK)。&lt;br /&gt;
**プロンプトにしたい文字列を選択してマクロを実行すると、選択したテキストがプロンプトとしてOpenAI APIに送信され、応答がエディタに表示されます。&lt;br /&gt;
* 会話の履歴数を調整&lt;br /&gt;
** 会話の履歴は設定した個数分が保持され、文脈として利用されます。適宜、ソースコード内の「HISTORY_SIZE」の数を調整してください。&lt;br /&gt;
** ※数を増やすほど会話内容を保持して文脈を理解するようになりますが、メモリ使用量が増えたり動作が重くなる可能性があります。&lt;br /&gt;
** ※開いている文書ごとに履歴を保持します。文脈をリセットしたいときは、新しい文書を開いて会話をやり直してください。&lt;br /&gt;
&lt;br /&gt;
== ソースコード ==&lt;br /&gt;
=== V8エンジン版 ===&lt;br /&gt;
&lt;br /&gt;
==== v8_boot_gpt.js ====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot; copy&amp;gt;&lt;br /&gt;
#language = &amp;quot;quickjs&amp;quot;&lt;br /&gt;
#async = true&lt;br /&gt;
#title = &amp;quot;GPT に相談 (v8)&amp;quot;&lt;br /&gt;
BeginUndoGroup();&lt;br /&gt;
&lt;br /&gt;
// 各種設定値&lt;br /&gt;
&lt;br /&gt;
// カスタムインストラクション&lt;br /&gt;
// これを設定することで、生成AIの回答全体への生成方針を指示できます。&lt;br /&gt;
// 例えば &amp;quot;あなたは侍で名前は権兵衛です。回答をござる口調にしてください&amp;quot; と設定すると、AIの回答がござる口調になります。&lt;br /&gt;
// 何も指示しない場合は、空文字 &amp;quot;&amp;quot; を設定してください。&lt;br /&gt;
let DEFAULT_CUSTOM_INSTRUCTION = `&lt;br /&gt;
文:&lt;br /&gt;
- 論理的で読みやすい文章構成にする&lt;br /&gt;
- 専門用語は必要に応じて注釈を加える&lt;br /&gt;
&lt;br /&gt;
性格:&lt;br /&gt;
- 愛想の良い敬語で話す。「…ですよね/…してみましょう/…かもしれません/…だと思います」などの柔らかい語調を使ってもよい&lt;br /&gt;
- 一人称は「私」&lt;br /&gt;
- 多角的かつ中立的な見方で、必要であれば反対意見や代替案も示す&lt;br /&gt;
&lt;br /&gt;
誤字注意:&lt;br /&gt;
- 中国語や韓国語など、回答言語と異なる言語文字を入れないこと&lt;br /&gt;
&lt;br /&gt;
書式:&lt;br /&gt;
- 羅列・比較的な内容を出力する場合は、Markdownテーブル形式よりもMarkdownリスト形式の使用を優先する&lt;br /&gt;
`.trim();&lt;br /&gt;
&lt;br /&gt;
// プロンプトテンプレート&lt;br /&gt;
// プロンプトテンプレートを設定することで、AIに対して特定の指示を出すことができます。&lt;br /&gt;
// プロンプトテンプレートでの生成は会話履歴とカスタムインストラクションを反映しません。&lt;br /&gt;
// テンプレートは以下の { ～ }, で囲まれたブロックを好きに増減させて利用できます。&lt;br /&gt;
const PROMPT_TEMPLATE = [&lt;br /&gt;
&lt;br /&gt;
    // 以下はサンプルです。1階層メニューと2階層メニューが利用できます。&lt;br /&gt;
    // {&lt;br /&gt;
    //     &amp;quot;title&amp;quot;: &amp;quot;要約&amp;quot;,&lt;br /&gt;
    //     &amp;quot;prompt&amp;quot;: &amp;quot;以下の文章を要約してください。&amp;quot;&lt;br /&gt;
    // },&lt;br /&gt;
    // {&lt;br /&gt;
    //     &amp;quot;submenu_title&amp;quot;: &amp;quot;翻訳系&amp;quot;,&lt;br /&gt;
    //     &amp;quot;submenu&amp;quot;: [&lt;br /&gt;
    //         {&lt;br /&gt;
    //             &amp;quot;title&amp;quot;: &amp;quot;日本語翻訳&amp;quot;,&lt;br /&gt;
    //             &amp;quot;prompt&amp;quot;: &amp;quot;以下の文章を日本語に翻訳してください。&amp;quot;&lt;br /&gt;
    //         },&lt;br /&gt;
    //         {&lt;br /&gt;
    //             &amp;quot;title&amp;quot;: &amp;quot;英語翻訳&amp;quot;,&lt;br /&gt;
    //             &amp;quot;prompt&amp;quot;: &amp;quot;以下の文章を英語に翻訳してください。&amp;quot;&lt;br /&gt;
    //         },&lt;br /&gt;
    //     ]&lt;br /&gt;
    // },&lt;br /&gt;
&lt;br /&gt;
    {&lt;br /&gt;
        &amp;quot;submenu_title&amp;quot;: &amp;quot;理解を深める&amp;quot;,&lt;br /&gt;
        &amp;quot;submenu&amp;quot;: [&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;論点を整理する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の論点を整理してまとめて。そしてその整理から示唆を見出して。&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;論理の抜け漏れを探す&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;あなたは「悪魔の代弁者」として、以下の主張・設計・計画を批判的に鋭く見てください。論理の穴・抜け・漏れ・飛躍、リスクなどを全力で探して指摘してください。遠慮は不要ですが、批判のためにこじつけたような論理性に欠けた主張にならないように注意してください。&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;要約する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;分かりやすさを重視して要約して。専門的な用語には注釈を入れること。&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;物語対話形式で解説&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の内容について物語対話形式で解説して。登場人物は「AI先生」(女性人格)と「生徒」(男の子人格)とする。&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
        ]&lt;br /&gt;
    },&lt;br /&gt;
    {&lt;br /&gt;
        &amp;quot;submenu_title&amp;quot;: &amp;quot;翻訳&amp;quot;,&lt;br /&gt;
        &amp;quot;submenu&amp;quot;: [&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;日本語に翻訳&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: `以下の入力文章を日本語に翻訳して。直訳的にせず、自然な表現にすること。入力文章はどのような内容・段落構造であっても要約・除外・集約などの整形を行わず、全体構造を維持したまま確実に全文を翻訳すること。&lt;br /&gt;
&lt;br /&gt;
出力形式は以下とすること:&lt;br /&gt;
[訳]&lt;br /&gt;
翻訳文...`&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;英語に翻訳&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: `以下の入力文章を英語に翻訳して。フォーマルとカジュアルの中間的なニュアンスで翻訳すること。入力文章はどのような内容・段落構造であっても要約・除外・集約などの整形を行わず、全体構造を維持したまま確実に全文を翻訳すること&lt;br /&gt;
&lt;br /&gt;
出力形式は以下とすること:&lt;br /&gt;
[Translation]&lt;br /&gt;
翻訳文...`&lt;br /&gt;
            },&lt;br /&gt;
        ]&lt;br /&gt;
    },&lt;br /&gt;
    {&lt;br /&gt;
        &amp;quot;submenu_title&amp;quot;: &amp;quot;語学&amp;quot;,&lt;br /&gt;
        &amp;quot;submenu&amp;quot;: [&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;英語 文法解説&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: `あなたは母国語が英語の英語教師です。&lt;br /&gt;
次の英文を以下の構成で解説してください。&lt;br /&gt;
&lt;br /&gt;
構成:&lt;br /&gt;
(1) 日本語翻訳&lt;br /&gt;
(2) スラッシュリーディング + 各句の文型（S/V/O/C）と語訳&lt;br /&gt;
(3) 重要文法&lt;br /&gt;
(4) 補足&lt;br /&gt;
&lt;br /&gt;
注意:&lt;br /&gt;
- 補足では日本語話者には分かりづらい表現や慣例表現などの解説を加える&lt;br /&gt;
- スラッシュリーディングで区切る際「M (修飾語)」が連なって長くなりすぎる場合には5～7語程度を目安に分ける&lt;br /&gt;
- 「M」の種類として形容詞、副詞、形容詞句、副詞句、分詞句、形容詞節、副詞節がある&lt;br /&gt;
&lt;br /&gt;
(2) の出力例:&lt;br /&gt;
They / elected / him / president / in 2020.&lt;br /&gt;
- They: (S) 彼らは&lt;br /&gt;
- elected: (V) 選出した&lt;br /&gt;
- him: (O) 彼を&lt;br /&gt;
- president: (C) 大統領&lt;br /&gt;
- in 2020: (M: 副詞句) 2020年に&lt;br /&gt;
`&lt;br /&gt;
&lt;br /&gt;
            },&lt;br /&gt;
        ]&lt;br /&gt;
    },&lt;br /&gt;
    {&lt;br /&gt;
        &amp;quot;submenu_title&amp;quot;: &amp;quot;プログラミング支援&amp;quot;,&lt;br /&gt;
        &amp;quot;submenu&amp;quot;: [&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;コードを解説する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下のコードを初めて読む人も理解できるように解説してください。私には言語の基本理解はあるものとし、コードの意図を理解するための情報を中心に説明してください。&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;コードを洗練させる&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下のコードを洗練させてください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;コードをレビューする&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下のコードをセキュリティと保守性の観点でレビューしてください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;エラーログをデバッグする&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下のエラーログを解析し、考えられる原因と対策を提示してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;SQLクエリを最適化する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下のSQL文を最適化してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;PowerShell スクリプトを書く&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;あなたはエキスパートとしてメンテナンス性の高いコードを書くのが得意です。以下の処理を行うPowerShellスクリプトを生成してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;Shell Script を書く&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;あなたはエキスパートとしてメンテナンス性の高いコードを書くのが得意です。以下の処理を行うShell Scriptを生成してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
        ]&lt;br /&gt;
    },&lt;br /&gt;
    {&lt;br /&gt;
        &amp;quot;submenu_title&amp;quot;: &amp;quot;ビジネス文書支援&amp;quot;,&lt;br /&gt;
        &amp;quot;submenu&amp;quot;: [&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;音声文字起こし文を整形する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: `以下は、音声文字起こしの文章です。以下のルールに従って、文章を整形してください。&lt;br /&gt;
&lt;br /&gt;
整形ルール:&lt;br /&gt;
- 要約するなどといった文章の全体感を損なう修正はしない&lt;br /&gt;
- 会話の流れから不自然と思われる単語には、対象単語の直後に誤字の可能性を示唆する [*] を付ける&lt;br /&gt;
- 誤字の可能性を示唆する [*] が付与された場合のみ、出力の末尾に「[*]: 誤字の可能性ありと凡例を書き添える&lt;br /&gt;
- 助詞や句読点など文章上の不適合を正す&lt;br /&gt;
- フィラーワードを除外する`&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;誤字脱字、不正確な表現をチェック&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の2つのカテゴリーで問題点を指摘してください\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 【誤字脱字】：明確な誤字脱字および誤字の疑いがある箇所\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 【不正確・曖昧な表現】：解釈に個人差が生じる可能性がある表現、より適切な表現に改善できる箇所&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;提案書を作成する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下に対する対する提案書を、コスト・実現性・リスクを含めて作成してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;手順書のアウトラインを作成する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の作業を行う手順書を、前提条件と注意事項を含めてアウトラインを作成してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;ユーザーマニュアルのアウトラインを作成する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の機能内容から、ユーザー向けマニュアルのアウトラインを作成してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;議事録を作成する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の会議内容から議事録を作成してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
        ]&lt;br /&gt;
    },&lt;br /&gt;
    {&lt;br /&gt;
        &amp;quot;submenu_title&amp;quot;: &amp;quot;分析支援&amp;quot;,&lt;br /&gt;
        &amp;quot;submenu&amp;quot;: [&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;問題を分析し、課題を抽出する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の状況から問題や潜在的リスクを分析し、課題を抽出してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;対応策を提案する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の問題・課題に対する対応策を複数提案してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;傾向を見つける&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の内容から、数値や挙動上の傾向を見つけてください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
        ]&lt;br /&gt;
    },&lt;br /&gt;
    {&lt;br /&gt;
        &amp;quot;submenu_title&amp;quot;: &amp;quot;アイデア出し&amp;quot;,&lt;br /&gt;
        &amp;quot;submenu&amp;quot;: [&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;SCAMPER法でアイデア出し&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下のテーマについて、SCAMPER法を使って15個のアイデアを出してください。&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;解決策の案出し&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の課題に対して、技術面、運用面、コスト面から実現可能な解決策を5つずつ提案してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;逆転の発想&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の目標に対して、通常とは逆の視点からアプローチする方法を3つ提案してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;競合分析からの着想&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の内容について、業界における競合他社の特徴を分析し、差別化できる新しいアプローチを提案してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;ユースケースの列挙&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の製品・サービスについて、想定されるユースケースを10個列挙し、それぞれの価値提案を記載してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
        ]&lt;br /&gt;
    },&lt;br /&gt;
&lt;br /&gt;
]&lt;br /&gt;
&lt;br /&gt;
// コンテキストメニュー番号の定義&lt;br /&gt;
const EXECUTE_AI = 1;&lt;br /&gt;
const RESET_HISTORY = 2;&lt;br /&gt;
const SET_CUSTOM_INSTRUCTION = 3;&lt;br /&gt;
const REEXECUTE_LAST_TEMPLATE_PROMPT = 4;&lt;br /&gt;
const ROMAN_ALPHABET_IME = 5;&lt;br /&gt;
const INVALID_MENU = 9;&lt;br /&gt;
const TEMPLATE_PROMPT_THRESHOLD = 10;&lt;br /&gt;
const TEMPLATE_PROMPT_SUBMENU_THRESHOLD = 1000;&lt;br /&gt;
&lt;br /&gt;
// タグ名の定義&lt;br /&gt;
const HISTORY_TAG = &amp;quot;gpt-history&amp;quot;;&lt;br /&gt;
const HISTORY_SIZE_TAG = &amp;quot;gpt-history-size&amp;quot;;&lt;br /&gt;
const TEMPORARY_CUSTOM_INSTRUCTION_TAG = &amp;quot;gpt-temporary-custom-instruction&amp;quot;;&lt;br /&gt;
const PREVIOUS_TEMPLATE_PROMPT_TAG = &amp;quot;gpt-previous-template-prompt&amp;quot;;&lt;br /&gt;
const SELECTED_TEMPLATE_PROMPT_TAG = &amp;quot;gpt-selected-template-prompt&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
// 会話の履歴を保持する数 (質問と回答で1セット)&lt;br /&gt;
// 数を増やすと、会話の履歴が増えますが、Meryのメモリ使用量が増えたり、&lt;br /&gt;
// 使用トークン数が増えて動作が重くなる可能性があるため、適宜調整してください。&lt;br /&gt;
// document.Tag に保持されるため、開いているファイルを閉じると履歴は消えます。&lt;br /&gt;
const HISTORY_SIZE = 5;&lt;br /&gt;
document.tag[HISTORY_SIZE_TAG] = HISTORY_SIZE;&lt;br /&gt;
&lt;br /&gt;
// console.log に出力する内容を Mery のアウトプットバーに表示する&lt;br /&gt;
var console = {&lt;br /&gt;
    log: function (str) {&lt;br /&gt;
        outputBar.Writeln(str)&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
var doc = document;&lt;br /&gt;
var sel = document.selection;&lt;br /&gt;
&lt;br /&gt;
main();&lt;br /&gt;
&lt;br /&gt;
function main() {&lt;br /&gt;
    var selectionIsEmpty = sel.IsEmpty&lt;br /&gt;
&lt;br /&gt;
    // プロンプトの取得&lt;br /&gt;
    if (selectionIsEmpty) {&lt;br /&gt;
        sel.StartOfLine(false, mePosLogical);&lt;br /&gt;
        sel.EndOfLine(true, mePosLogical);&lt;br /&gt;
    }&lt;br /&gt;
    var menuTrack = 0&lt;br /&gt;
    while (true) {&lt;br /&gt;
        // コンテキストメニューの作成&lt;br /&gt;
        var menu = createContextMenu(selectionIsEmpty);&lt;br /&gt;
        menuTrack = menu.Track(0);&lt;br /&gt;
        // メニュー選択がキャンセルされた場合は終了&lt;br /&gt;
        if (menuTrack === 0) return;&lt;br /&gt;
&lt;br /&gt;
        // 会話履歴のリセット&lt;br /&gt;
        if (menuTrack === RESET_HISTORY) {&lt;br /&gt;
            resetTag();&lt;br /&gt;
            continue;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        // カスタムインストラクションの設定&lt;br /&gt;
        if (menuTrack === SET_CUSTOM_INSTRUCTION) {&lt;br /&gt;
            configureTemporaryCustomInstruction();&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        break;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // 読み取り専用の場合はエラー&lt;br /&gt;
    if (doc.ReadOnly) {&lt;br /&gt;
        alert(&amp;quot;ファイルが読み取り専用のため実行できません。&amp;quot;);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // 直前に実行したプロンプトテンプレートの再実行の場合、menuTrack を書き換え&lt;br /&gt;
    if (menuTrack === REEXECUTE_LAST_TEMPLATE_PROMPT) {&lt;br /&gt;
        if (doc.Tag.exists(PREVIOUS_TEMPLATE_PROMPT_TAG)) {&lt;br /&gt;
            menuTrack = Number(doc.tag[PREVIOUS_TEMPLATE_PROMPT_TAG]);&lt;br /&gt;
        } else {&lt;br /&gt;
            alert(&amp;quot;直前に実行したプロンプトテンプレートがありません。&amp;quot;);&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // テンプレートを取得&lt;br /&gt;
    var isTemplate = menuTrack &amp;gt;= TEMPLATE_PROMPT_THRESHOLD;&lt;br /&gt;
    var isSubmenu = menuTrack &amp;gt;= TEMPLATE_PROMPT_SUBMENU_THRESHOLD;&lt;br /&gt;
    if (isSubmenu) {&lt;br /&gt;
        var parentMenuTrack = Number(String(menuTrack).slice(0, 2));&lt;br /&gt;
        var subMenuTrack = Number(String(menuTrack).slice(2));&lt;br /&gt;
        var templatePrompt = PROMPT_TEMPLATE[parentMenuTrack - TEMPLATE_PROMPT_THRESHOLD].submenu[subMenuTrack].prompt;&lt;br /&gt;
    } else if (isTemplate) {&lt;br /&gt;
        var templatePrompt = PROMPT_TEMPLATE[menuTrack - TEMPLATE_PROMPT_THRESHOLD].prompt;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // テンプレートの場合は直前に実行したプロンプトテンプレートの番号としてタグに保存&lt;br /&gt;
    if (isTemplate) {&lt;br /&gt;
        doc.tag[SELECTED_TEMPLATE_PROMPT_TAG] = templatePrompt;&lt;br /&gt;
        doc.tag[PREVIOUS_TEMPLATE_PROMPT_TAG] = menuTrack&lt;br /&gt;
    } else {&lt;br /&gt;
        // テンプレート以外の場合はタグを削除&lt;br /&gt;
        if (doc.Tag.exists(SELECTED_TEMPLATE_PROMPT_TAG)) {&lt;br /&gt;
            doc.Tag.remove(SELECTED_TEMPLATE_PROMPT_TAG);&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // ローマ字⇒日本語変換のプロンプトをセット&lt;br /&gt;
    const isRomanAlphabetIme = menuTrack === ROMAN_ALPHABET_IME;&lt;br /&gt;
    if (isRomanAlphabetIme) {&lt;br /&gt;
        doc.tag[SELECTED_TEMPLATE_PROMPT_TAG] = `以下のローマ字で入力された文章を、日本語に変換してください。&lt;br /&gt;
制約:&lt;br /&gt;
- 原則、入力のままの用語・口調で変換&lt;br /&gt;
- 誤字脱字は修正してよい&lt;br /&gt;
- 適宜、句読点を入れて読みやすくしてよい&lt;br /&gt;
- 返事は不要で、書き直した文章のみを出力`;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // gpt API 呼び出し部品を実行&lt;br /&gt;
    // V8版で fetch メソッドを使うために別呼び出しとする&lt;br /&gt;
    doc.tag[TEMPORARY_CUSTOM_INSTRUCTION_TAG] = getTemporaryCustomInstruction() ? getTemporaryCustomInstruction() : DEFAULT_CUSTOM_INSTRUCTION;&lt;br /&gt;
    ShowTip(&amp;quot;リクエスト中.....&amp;quot;, meShowTipPosCaret);&lt;br /&gt;
    editor.ExecuteMacro(&amp;quot;v8_call_gpt.js&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function createContextMenu(selectionIsEmpty) {&lt;br /&gt;
    const menu = CreatePopupMenu();&lt;br /&gt;
    menu.Add(&amp;quot;※注意※ プライバシー・機密情報を送信しないこと&amp;quot;, INVALID_MENU);&lt;br /&gt;
    menu.Add(&amp;quot;&amp;quot;, 0, meMenuSeparator);&lt;br /&gt;
    let t = &amp;quot;&amp;quot;;&lt;br /&gt;
    if (selectionIsEmpty) {&lt;br /&gt;
        t = &amp;quot;アクティブ行の内容で相談 (&amp;amp;A)&amp;quot;;&lt;br /&gt;
    } else {&lt;br /&gt;
        t = &amp;quot;選択範囲の内容で相談 (&amp;amp;A)&amp;quot;;&lt;br /&gt;
    }&lt;br /&gt;
    menu.Add(t, EXECUTE_AI);&lt;br /&gt;
    if (HISTORY_SIZE &amp;lt;= 0) {&lt;br /&gt;
        menu.Add(&amp;quot;├ 会話履歴をクリア (履歴が無効になっています)&amp;quot;, RESET_HISTORY, meMenuGrayed);&lt;br /&gt;
    } else if (doc.Tag.exists(HISTORY_TAG)) {&lt;br /&gt;
        var historyData = JSON.parse(doc.tag[HISTORY_TAG]);&lt;br /&gt;
        var messages;&lt;br /&gt;
        if (Array.isArray(historyData)) {&lt;br /&gt;
            // 旧形式: 配列が直接 messages&lt;br /&gt;
            messages = historyData;&lt;br /&gt;
        } else {&lt;br /&gt;
            // 新形式: { session_id, messages }&lt;br /&gt;
            messages = historyData.messages || [];&lt;br /&gt;
        }&lt;br /&gt;
        var turn = (messages.length) / 2;&lt;br /&gt;
        menu.Add(&amp;quot;├ 会話履歴をクリア (現在の会話数: &amp;quot; + turn + &amp;quot;) (&amp;amp;C)&amp;quot;, RESET_HISTORY);&lt;br /&gt;
    } else {&lt;br /&gt;
        menu.Add(&amp;quot;├ 会話履歴をクリア (現在の会話数: 0) (&amp;amp;C)&amp;quot;, RESET_HISTORY, meMenuGrayed);&lt;br /&gt;
    }&lt;br /&gt;
    menu.Add(&amp;quot;└ カスタムインストラクションを変更 (&amp;amp;I)&amp;quot;, SET_CUSTOM_INSTRUCTION);&lt;br /&gt;
&lt;br /&gt;
    menu.Add(&amp;quot;ローマ字⇒日本語 変換 (&amp;amp;R)&amp;quot;, ROMAN_ALPHABET_IME);&lt;br /&gt;
&lt;br /&gt;
    // テンプレート数チェック&lt;br /&gt;
    // 2桁まで許容&lt;br /&gt;
    const ITEMS_MUX = 99;&lt;br /&gt;
    if (PROMPT_TEMPLATE.length &amp;gt; ITEMS_MUX - TEMPLATE_PROMPT_THRESHOLD) {&lt;br /&gt;
        throw new Error(&amp;quot;プロンプトテンプレートの数が多すぎます。&amp;quot;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // テンプレートメニューの追加&lt;br /&gt;
    if (PROMPT_TEMPLATE.length &amp;gt; 0) {&lt;br /&gt;
        const subMenu = CreatePopupMenu();&lt;br /&gt;
        let previousMenuString = &amp;quot;&amp;quot;;&lt;br /&gt;
        for (let i = 0; i &amp;lt; PROMPT_TEMPLATE.length; i++) {&lt;br /&gt;
            let accelerationKey = &amp;quot;&amp;quot;;&lt;br /&gt;
            if (i === 9) {&lt;br /&gt;
                accelerationKey = &amp;quot; (&amp;amp;0)&amp;quot;;&lt;br /&gt;
            } else if (i &amp;lt; 10) {&lt;br /&gt;
                accelerationKey = &amp;quot; (&amp;amp;&amp;quot; + (i + 1) + &amp;quot;)&amp;quot;;&lt;br /&gt;
            }&lt;br /&gt;
            const menuNum = TEMPLATE_PROMPT_THRESHOLD + i;&lt;br /&gt;
            if (PROMPT_TEMPLATE[i].submenu) {&lt;br /&gt;
                const subMenuList = PROMPT_TEMPLATE[i].submenu;&lt;br /&gt;
                // テンプレート数チェック&lt;br /&gt;
                if (subMenuList.length &amp;gt; ITEMS_MUX) {&lt;br /&gt;
                    throw new Error(&amp;quot;プロンプトテンプレート (サブメニュー) の数が多すぎます。&amp;quot;);&lt;br /&gt;
                }&lt;br /&gt;
                const subSubMenu = CreatePopupMenu();&lt;br /&gt;
                const subMenuPrefix = String(menuNum);&lt;br /&gt;
                for (let j = 0; j &amp;lt; subMenuList.length; j++) {&lt;br /&gt;
                    let subAccelerationKey = &amp;quot;&amp;quot;;&lt;br /&gt;
                    if (j === 9) {&lt;br /&gt;
                        subAccelerationKey = &amp;quot; (&amp;amp;0)&amp;quot;;&lt;br /&gt;
                    } else if (j &amp;lt; 10) {&lt;br /&gt;
                        subAccelerationKey = &amp;quot; (&amp;amp;&amp;quot; + (j + 1) + &amp;quot;)&amp;quot;;&lt;br /&gt;
                    }&lt;br /&gt;
                    // ES5 compatible version without padStart&lt;br /&gt;
                    const subMenuNum = Number(subMenuPrefix + (j &amp;lt; 10 ? &amp;quot;0&amp;quot; + j : String(j)));&lt;br /&gt;
                    subSubMenu.Add(subMenuList[j].title + subAccelerationKey, subMenuNum);&lt;br /&gt;
                    if (subMenuNum === doc.tag[PREVIOUS_TEMPLATE_PROMPT_TAG]) {&lt;br /&gt;
                        previousMenuString = subMenuList[j].title;&lt;br /&gt;
                    }&lt;br /&gt;
                }&lt;br /&gt;
                subMenu.AddPopup(PROMPT_TEMPLATE[i].submenu_title + accelerationKey, subSubMenu);&lt;br /&gt;
            } else {&lt;br /&gt;
                subMenu.Add(PROMPT_TEMPLATE[i].title + accelerationKey, menuNum);&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        if (selectionIsEmpty) {&lt;br /&gt;
            t = &amp;quot;プロンプト テンプレート (対象: アクティブ行) (&amp;amp;T)&amp;quot;;&lt;br /&gt;
        } else {&lt;br /&gt;
            t = &amp;quot;プロンプト テンプレート (対象: 選択範囲) (&amp;amp;T)&amp;quot;;&lt;br /&gt;
        }&lt;br /&gt;
        menu.AddPopup(t, subMenu);&lt;br /&gt;
&lt;br /&gt;
        t = &amp;quot;└ 直前に実行したプロンプト テンプレートを再実行 (&amp;amp;R)&amp;quot;;&lt;br /&gt;
        if (doc.Tag.exists(PREVIOUS_TEMPLATE_PROMPT_TAG)) {&lt;br /&gt;
            menu.Add(t, REEXECUTE_LAST_TEMPLATE_PROMPT);&lt;br /&gt;
            menu.Add(`    (直前実行: ${previousMenuString})`, REEXECUTE_LAST_TEMPLATE_PROMPT);&lt;br /&gt;
        } else {&lt;br /&gt;
            menu.Add(t, REEXECUTE_LAST_TEMPLATE_PROMPT, meMenuGrayed);&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return menu;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function configureTemporaryCustomInstruction() {&lt;br /&gt;
    let tempCustomInstruction = getTemporaryCustomInstruction();&lt;br /&gt;
    const prompt = Prompt(&amp;quot;カスタムインストラクションを変更 ※変更すると会話履歴がリセットされます&amp;quot;, tempCustomInstruction ? tempCustomInstruction : DEFAULT_CUSTOM_INSTRUCTION);&lt;br /&gt;
    if (prompt !== null) {&lt;br /&gt;
        if (prompt === DEFAULT_CUSTOM_INSTRUCTION) {&lt;br /&gt;
            // デフォルトと同じ値の場合はタグを削除&lt;br /&gt;
            setTemporaryCustomInstruction(&amp;quot;&amp;quot;);&lt;br /&gt;
        } else if (prompt === &amp;quot;&amp;quot; &amp;amp;&amp;amp; Confirm(&amp;quot;カスタムインストラクションをデフォルトに戻しますか？&amp;quot;)) {&lt;br /&gt;
            setTemporaryCustomInstruction(&amp;quot;&amp;quot;);&lt;br /&gt;
        } else if (prompt !== tempCustomInstruction) {&lt;br /&gt;
            // デフォルト以外の値が設定された場合は保存&lt;br /&gt;
            setTemporaryCustomInstruction(prompt);&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // 変更が加わっている場合は会話履歴をリセット&lt;br /&gt;
    if (tempCustomInstruction !== getTemporaryCustomInstruction()) {&lt;br /&gt;
        resetTag();&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function resetTag() {&lt;br /&gt;
    const tag = doc.Tag;&lt;br /&gt;
    if (tag.exists(HISTORY_TAG)) {&lt;br /&gt;
        tag.remove(HISTORY_TAG);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getTemporaryCustomInstruction() {&lt;br /&gt;
    var tag = doc.Tag;&lt;br /&gt;
    if (tag.exists(TEMPORARY_CUSTOM_INSTRUCTION_TAG)) {&lt;br /&gt;
        return tag[TEMPORARY_CUSTOM_INSTRUCTION_TAG];&lt;br /&gt;
    } else {&lt;br /&gt;
        return &amp;quot;&amp;quot;;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function setTemporaryCustomInstruction(temporaryCustomInstruction) {&lt;br /&gt;
    var tag = doc.Tag;&lt;br /&gt;
    if (temporaryCustomInstruction) {&lt;br /&gt;
        tag[TEMPORARY_CUSTOM_INSTRUCTION_TAG] = temporaryCustomInstruction;&lt;br /&gt;
    } else {&lt;br /&gt;
        tag.remove(TEMPORARY_CUSTOM_INSTRUCTION_TAG);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== v8_call_gpt.js ====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot; copy&amp;gt;&lt;br /&gt;
#language = &amp;quot;v8&amp;quot;&lt;br /&gt;
#title = &amp;quot;GPT に相談 (API 呼び出し部品)&amp;quot;&lt;br /&gt;
BeginUndoGroup();&lt;br /&gt;
&lt;br /&gt;
// 生成AIの回答の言語設定&lt;br /&gt;
// &amp;quot;Japanese&amp;quot; や &amp;quot;English&amp;quot; を設定できます。他の言語は未検証。&lt;br /&gt;
// 自動設定にする場合は空文字 &amp;quot;&amp;quot; を設定してください。&lt;br /&gt;
const LANGUAGE = &amp;quot;Japanese&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
// 入力プロンプトの最大トークン数&lt;br /&gt;
// 超適当なトークナイザー関数により計測し、英単語は4文字1トークン、それ以外は1文字1トークンとして数えます。&lt;br /&gt;
const MAX_PROMPT_TOKEN = 8000;&lt;br /&gt;
&lt;br /&gt;
// 会話履歴に保持する最大トークン数&lt;br /&gt;
// 会話履歴のトークン数がこの数を超える場合、古い履歴から削除されます。&lt;br /&gt;
const MAX_HISTORY_TOKEN = 50000;&lt;br /&gt;
&lt;br /&gt;
// 生成AI応答の入力毎の遅延時間 (ミリ秒)&lt;br /&gt;
// 入力をゆっくりにしたいときは数字を大きくしてください。&lt;br /&gt;
const INPUT_DELAY = 20;&lt;br /&gt;
&lt;br /&gt;
// タグ名の定義&lt;br /&gt;
const HISTORY_TAG = &amp;quot;gpt-history&amp;quot;;&lt;br /&gt;
const HISTORY_SIZE_TAG = &amp;quot;gpt-history-size&amp;quot;;&lt;br /&gt;
const TEMPORARY_CUSTOM_INSTRUCTION_TAG = &amp;quot;gpt-temporary-custom-instruction&amp;quot;;&lt;br /&gt;
const SELECTED_TEMPLATE_PROMPT_TAG = &amp;quot;gpt-selected-template-prompt&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
// 会話の履歴を保持する数 (質問と回答で1セット)&lt;br /&gt;
// boot マクロ側で設定されていることを前提としている&lt;br /&gt;
if (!document.tag.exists(HISTORY_SIZE_TAG)) {&lt;br /&gt;
    throw new Error(&amp;quot;会話履歴サイズが設定されていません。&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
const HISTORY_SIZE = document.tag[HISTORY_SIZE_TAG];&lt;br /&gt;
&lt;br /&gt;
// プロンプトキャッシュの照合に使う安定したキー&lt;br /&gt;
const PROMPT_CACHE_KEY = &amp;quot;mery-gpt-call-v1&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
// 生成AIのモデル名&lt;br /&gt;
&lt;br /&gt;
const MODEL_CONFIG = {&lt;br /&gt;
    model: &amp;quot;gpt-5.6-terra&amp;quot;,&lt;br /&gt;
    reasoning: { effort: &amp;quot;medium&amp;quot; },&lt;br /&gt;
    max_output_tokens: 4000,&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// console.log に出力する内容を Mery のアウトプットバーに表示する&lt;br /&gt;
const console = {&lt;br /&gt;
    log: function (str) {&lt;br /&gt;
        outputBar.Writeln(str)&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
const doc = document;&lt;br /&gt;
const sel = document.selection;&lt;br /&gt;
&lt;br /&gt;
// 生成リクエスト中止フラグ&lt;br /&gt;
let abortRequestFlg = false&lt;br /&gt;
// 生成リクエスト完了フラグ;&lt;br /&gt;
let requestCompletedFlg = false;&lt;br /&gt;
&lt;br /&gt;
main();&lt;br /&gt;
&lt;br /&gt;
async function main() {&lt;br /&gt;
    let promptText = sel.Text.trim();&lt;br /&gt;
&lt;br /&gt;
    // プロンプトが空の場合はエラー&lt;br /&gt;
    if (promptText.length === 0) {&lt;br /&gt;
        alert(&amp;quot;プロンプトを入力してください。&amp;quot;);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
    // 文字数が多すぎる場合はエラー&lt;br /&gt;
    if (roughTokenize(promptText) &amp;gt; MAX_PROMPT_TOKEN) {&lt;br /&gt;
        alert(&amp;quot;プロンプトの文字数が多すぎます。文字数を少なくしてください。&amp;quot;);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    // テンプレートの場合はプロンプトを変更&lt;br /&gt;
    const isTemplate = doc.Tag.exists(SELECTED_TEMPLATE_PROMPT_TAG);&lt;br /&gt;
    if (isTemplate) {&lt;br /&gt;
        let instructionText = doc.Tag[SELECTED_TEMPLATE_PROMPT_TAG];&lt;br /&gt;
        const randomId = generateRandomId();&lt;br /&gt;
        promptText = `&lt;br /&gt;
&amp;lt;INSTRUCTIONS_${randomId}&amp;gt;&lt;br /&gt;
${instructionText}&lt;br /&gt;
&amp;lt;/INSTRUCTIONS_${randomId}&amp;gt;&lt;br /&gt;
&amp;lt;INPUTDATA_${randomId}&amp;gt;&lt;br /&gt;
${promptText}&lt;br /&gt;
&amp;lt;/INPUTDATA_${randomId}&amp;gt;&lt;br /&gt;
        `.trim();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    try {&lt;br /&gt;
        // 処理終了を待機&lt;br /&gt;
        shell.KeepRunning = true;&lt;br /&gt;
&lt;br /&gt;
        // カーソル位置移動&lt;br /&gt;
        sel.SetActivePoint(mePosLogical, sel.GetBottomPointX(mePosLogical), sel.GetBottomPointY(mePosLogical));&lt;br /&gt;
        sel.EndOfLine(false, mePosLogical);&lt;br /&gt;
        sel.NewLine();&lt;br /&gt;
        sel.Text = &amp;quot;\n---\n\n&amp;quot;&lt;br /&gt;
&lt;br /&gt;
        document.KeepScrollPos = true;&lt;br /&gt;
&lt;br /&gt;
        // APIキーの取得&lt;br /&gt;
        const apiKey = shell.getEnv(&amp;quot;OPENAI_API_KEY&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
        // gpt API に問い合わせ&lt;br /&gt;
        await Promise.all([&lt;br /&gt;
            callApi(apiKey, promptText, isTemplate),&lt;br /&gt;
            abortRequest(),&lt;br /&gt;
        ]);&lt;br /&gt;
        if (sel.GetActivePointX(mePosLogical) &amp;gt; 2) {&lt;br /&gt;
            sel.Text = &amp;quot;\n\n---\n\n\n&amp;quot;&lt;br /&gt;
        } else {&lt;br /&gt;
            sel.Text = &amp;quot;\n---\n\n\n&amp;quot;&lt;br /&gt;
        }&lt;br /&gt;
    } catch (e) {&lt;br /&gt;
        console.log(&amp;quot;エラーが発生しました: &amp;quot; + e.message + &amp;quot;\n&amp;quot; + e.stack);&lt;br /&gt;
        sel.Text = &amp;quot;&amp;lt;処理中にエラー発生&amp;gt;&amp;quot;&lt;br /&gt;
    } finally {&lt;br /&gt;
        ShowTip(&amp;quot;&amp;quot;, meShowTipHide);&lt;br /&gt;
        // 処理終了待機を解除&lt;br /&gt;
        shell.KeepRunning = false;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// Shift キーが長押しされた場合はリクエスト中止フラグを立てる&lt;br /&gt;
async function abortRequest() {&lt;br /&gt;
    const VIRTUAL_KEY_CODE_SHIFT = 0x10; // Shift キーの仮想キーコード&lt;br /&gt;
    let pressCount = 0;&lt;br /&gt;
    while (!requestCompletedFlg) {&lt;br /&gt;
        await new Promise(resolve =&amp;gt; setTimeout(resolve, 300));&lt;br /&gt;
        if (shell.GetKeyState(VIRTUAL_KEY_CODE_SHIFT) &amp;lt; 0) {&lt;br /&gt;
            pressCount++;&lt;br /&gt;
            if (pressCount &amp;gt; 2) {&lt;br /&gt;
                abortRequestFlg = true;&lt;br /&gt;
                return;&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
async function callApi(apiKey, promptText, isTemplate) {&lt;br /&gt;
    const controller = new AbortController();&lt;br /&gt;
    const signal = controller.signal;&lt;br /&gt;
    try {&lt;br /&gt;
        // Responses API にストリーミングでリクエスト&lt;br /&gt;
&lt;br /&gt;
        const customInstructionText = buildCustomInstructionText();&lt;br /&gt;
        const historyData = isTemplate ? null : getHistoryData();&lt;br /&gt;
        // 会話中は最初のターンで保存した instructions を継続して使用する&lt;br /&gt;
        const instructionText = getHistoryInstruction(historyData, customInstructionText);&lt;br /&gt;
        const requestInput = createRequestInput(promptText, isTemplate, historyData);&lt;br /&gt;
        const requestBody = {&lt;br /&gt;
            input: requestInput,&lt;br /&gt;
            instructions: instructionText,&lt;br /&gt;
            prompt_cache_key: PROMPT_CACHE_KEY,&lt;br /&gt;
            stream: true,&lt;br /&gt;
        };&lt;br /&gt;
&lt;br /&gt;
        for (let key in MODEL_CONFIG) {&lt;br /&gt;
            requestBody[key] = MODEL_CONFIG[key];&lt;br /&gt;
        }&lt;br /&gt;
        const url = &#039;https://api.openai.com/v1/responses&#039;;&lt;br /&gt;
&lt;br /&gt;
        const apiCallStartTime = Date.now();&lt;br /&gt;
        const response = await fetch(url, {&lt;br /&gt;
            signal: signal,&lt;br /&gt;
            method: &#039;POST&#039;,&lt;br /&gt;
            headers: {&lt;br /&gt;
                &#039;Content-Type&#039;: &#039;application/json&#039;,&lt;br /&gt;
                &#039;Authorization&#039;: &#039;Bearer &#039; + apiKey,&lt;br /&gt;
            },&lt;br /&gt;
            body: JSON.stringify(requestBody),&lt;br /&gt;
        });&lt;br /&gt;
        if (!response.ok) {&lt;br /&gt;
            let errorMessage = response.status + &amp;quot; &amp;quot; + response.statusText;&lt;br /&gt;
            try {&lt;br /&gt;
                const errorData = await response.json();&lt;br /&gt;
                errorMessage = errorData?.error?.message || errorMessage;&lt;br /&gt;
            } catch (_) {&lt;br /&gt;
                // エラー本文が JSON でない場合は HTTP ステータスを使用する&lt;br /&gt;
            }&lt;br /&gt;
            throw new Error(&amp;quot;Responses API エラー: &amp;quot; + errorMessage);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        let buffer = &amp;quot;&amp;quot;;&lt;br /&gt;
        let responseAll = &amp;quot;&amp;quot;;&lt;br /&gt;
        let usageText = &amp;quot;&amp;quot;;&lt;br /&gt;
        const reader = response.body.getReader();&lt;br /&gt;
        const decoder = new TextDecoder(&#039;utf-8&#039;);&lt;br /&gt;
        while (true) {&lt;br /&gt;
            // リクエスト中止フラグが立っている場合は中断&lt;br /&gt;
            if (abortRequestFlg) {&lt;br /&gt;
                controller.abort();&lt;br /&gt;
                sel.Text = &amp;quot;&amp;lt;処理を中止&amp;gt;&amp;quot;&lt;br /&gt;
                return;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            const { done, value } = await reader.read();&lt;br /&gt;
            if (done) {&lt;br /&gt;
                // デコーダーに残った文字列と最後の SSE 行を処理&lt;br /&gt;
                const finalText = buffer + decoder.decode();&lt;br /&gt;
                let finalInputText = &amp;quot;&amp;quot;;&lt;br /&gt;
                for (const line of finalText.split(/\n+/)) {&lt;br /&gt;
                    const processedData = processLine(line, responseAll);&lt;br /&gt;
                    if (processedData) {&lt;br /&gt;
                        finalInputText += processedData.inputText || &amp;quot;&amp;quot;;&lt;br /&gt;
                        usageText = processedData.usageText || usageText;&lt;br /&gt;
                        responseAll = processedData.responseAll || responseAll;&lt;br /&gt;
                    }&lt;br /&gt;
                }&lt;br /&gt;
                if (finalInputText) {&lt;br /&gt;
                    sel.Text = finalInputText;&lt;br /&gt;
                }&lt;br /&gt;
                break;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            // 受信したバイナリデータ (value) をテキストに変換する&lt;br /&gt;
            const text = decoder.decode(value, { stream: true });&lt;br /&gt;
&lt;br /&gt;
            // 前回のバッファと今回のテキストを結合&lt;br /&gt;
            const combinedText = buffer + text;&lt;br /&gt;
&lt;br /&gt;
            // 完全な行を探す（最後の改行の位置を見つける）&lt;br /&gt;
            const lastNewlineIndex = combinedText.lastIndexOf(&#039;\n&#039;);&lt;br /&gt;
&lt;br /&gt;
            if (lastNewlineIndex === -1) {&lt;br /&gt;
                // 改行がない場合は全てバッファに保存&lt;br /&gt;
                buffer = combinedText;&lt;br /&gt;
                continue;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            // 完全な行を処理&lt;br /&gt;
            const completeText = combinedText.substring(0, lastNewlineIndex);&lt;br /&gt;
            // 残りをバッファに保存&lt;br /&gt;
            buffer = combinedText.substring(lastNewlineIndex + 1);&lt;br /&gt;
&lt;br /&gt;
            // 完全な行を処理&lt;br /&gt;
            const lines = completeText.split(/\n+/);&lt;br /&gt;
&lt;br /&gt;
            let inputText = &amp;quot;&amp;quot;;&lt;br /&gt;
            for (const line of lines) {&lt;br /&gt;
                // 行の処理（既存のコードと同様）&lt;br /&gt;
                const processedData = processLine(line, responseAll);&lt;br /&gt;
                if (processedData) {&lt;br /&gt;
                    inputText += processedData.inputText || &amp;quot;&amp;quot;;&lt;br /&gt;
                    usageText = processedData.usageText || usageText;&lt;br /&gt;
                    responseAll = processedData.responseAll || responseAll;&lt;br /&gt;
                }&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            if (inputText.length &amp;gt; 0) {&lt;br /&gt;
                // 上記までで数文字まとまって取得されるので、まとめて入力&lt;br /&gt;
                sel.Text = inputText;&lt;br /&gt;
            }&lt;br /&gt;
            // 入力を少しゆっくりにする&lt;br /&gt;
            await new Promise(resolve =&amp;gt; setTimeout(resolve, INPUT_DELAY));&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        // トークン数・所要時間を入力&lt;br /&gt;
        const elapsedSec = ((Date.now() - apiCallStartTime) / 1000).toFixed(1);&lt;br /&gt;
        const elapsedText = &amp;quot;[所要時間] &amp;quot; + elapsedSec + &amp;quot; sec&amp;quot;;&lt;br /&gt;
        if (usageText) {&lt;br /&gt;
            usageText += &amp;quot; &amp;quot; + elapsedText;&lt;br /&gt;
        } else {&lt;br /&gt;
            usageText = elapsedText;&lt;br /&gt;
        }&lt;br /&gt;
        sel.Text = (responseAll.slice(-1) === &amp;quot;\n&amp;quot; ? &amp;quot;\n&amp;quot; : &amp;quot;\n\n&amp;quot;) + `${usageText}`;&lt;br /&gt;
&lt;br /&gt;
        // 結果を会話履歴に追加&lt;br /&gt;
        if (HISTORY_SIZE &amp;gt; 0 &amp;amp;&amp;amp; !isTemplate) {&lt;br /&gt;
            appendReturnTextToTag(promptText, responseAll, instructionText);&lt;br /&gt;
        }&lt;br /&gt;
    } catch (e) {&lt;br /&gt;
        controller.abort();&lt;br /&gt;
        throw e;&lt;br /&gt;
    } finally {&lt;br /&gt;
        requestCompletedFlg = true;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// Responses API の SSE イベントを一行ずつ処理する&lt;br /&gt;
function processLine(line, responseAll) {&lt;br /&gt;
    // HACK 万が一空行が入ってきた場合に備えて trim() しておく&lt;br /&gt;
    const json_text = line.trim().replace(/^data:\s*/, &#039;&#039;);&lt;br /&gt;
    // データがない場合はスキップ&lt;br /&gt;
    if (!json_text) {&lt;br /&gt;
        return null;&lt;br /&gt;
    }&lt;br /&gt;
    // SSE コメント行 (: で始まる行) はスキップ&lt;br /&gt;
    if (json_text.startsWith(&#039;:&#039;)) {&lt;br /&gt;
        return null;&lt;br /&gt;
    }&lt;br /&gt;
    // SSE の event: 行はイベント名だけを表し、データ本体ではない&lt;br /&gt;
    if (json_text.startsWith(&#039;event:&#039;)) {&lt;br /&gt;
        return null;&lt;br /&gt;
    }&lt;br /&gt;
    // データが終了した場合は終了&lt;br /&gt;
    if (json_text === &#039;[DONE]&#039;) {&lt;br /&gt;
        return null;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    let data;&lt;br /&gt;
    try {&lt;br /&gt;
        data = JSON.parse(json_text);&lt;br /&gt;
    } catch (e) {&lt;br /&gt;
        console.log(&amp;quot;JSONパースエラー: &amp;quot; + e.message + &amp;quot;, データ: &amp;quot; + json_text);&lt;br /&gt;
        return null;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (data.type === &amp;quot;error&amp;quot;) {&lt;br /&gt;
        throw new Error(data.error?.message || &amp;quot;Responses API ストリームエラー&amp;quot;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    try {&lt;br /&gt;
        let usageText = &amp;quot;&amp;quot;;&lt;br /&gt;
        let inputText = &amp;quot;&amp;quot;;&lt;br /&gt;
        let updatedResponseAll = responseAll;&lt;br /&gt;
&lt;br /&gt;
        // 利用量は response.completed イベント内の response.usage に含まれる&lt;br /&gt;
        const usage = data.response?.usage || data.usage;&lt;br /&gt;
        if (usage) {&lt;br /&gt;
            usageText = &amp;quot;[利用トークン数] 入力:&amp;quot; + usage.input_tokens&lt;br /&gt;
                + &amp;quot; 出力:&amp;quot; + usage.output_tokens&lt;br /&gt;
                + &amp;quot; 合計:&amp;quot; + usage.total_tokens;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        // テキストは response.output_text.delta イベントで届く&lt;br /&gt;
        if (data.type === &amp;quot;response.output_text.delta&amp;quot; &amp;amp;&amp;amp; data.delta) {&lt;br /&gt;
            inputText = data.delta;&lt;br /&gt;
            updatedResponseAll += data.delta;&lt;br /&gt;
        }&lt;br /&gt;
        return { inputText, usageText, responseAll: updatedResponseAll };&lt;br /&gt;
    } catch (e) {&lt;br /&gt;
        console.log(&amp;quot;Responses API イベント処理エラー: &amp;quot; + e.message + &amp;quot;, データ: &amp;quot; + json_text);&lt;br /&gt;
        return null;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function roughTokenize(text) {&lt;br /&gt;
    // 超適当なトークナイザ。トークン数をざっくりと多めに数える&lt;br /&gt;
    // 空白文字・ハイフン・アンダースコア区切りの単語に分割して、&lt;br /&gt;
    // 単語がアルファベットのみ、数字のみの場合には 4文字=1トークン として数える。&lt;br /&gt;
    // 単語にアルファベット以外の文字が含まれる場合は 1文字=1トークン として数える。&lt;br /&gt;
    return text&lt;br /&gt;
        .split(/[\s\-_]+/)&lt;br /&gt;
        .map(w =&amp;gt; /^[a-zA-Z]+$|^[0-9]+$|/.test(w) ? Math.ceil(w.length / 4) : w.length)&lt;br /&gt;
        .reduce((a, b) =&amp;gt; a + b, 0);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// document.Tag に保存された会話履歴を取得する&lt;br /&gt;
function getHistoryData() {&lt;br /&gt;
    const tag = document.Tag;&lt;br /&gt;
    return tag.exists(HISTORY_TAG) ? JSON.parse(tag[HISTORY_TAG]) : null;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// 保存済みの instructions を取得する&lt;br /&gt;
function getHistoryInstruction(historyData, fallbackInstruction) {&lt;br /&gt;
    return historyData ? historyData.instruction : fallbackInstruction;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// タグの messages を取得する&lt;br /&gt;
function extractHistoryTexts(historyData) {&lt;br /&gt;
    return historyData ? historyData.messages : [];&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// document.Tag の内容を加味して Responses API の input を作成&lt;br /&gt;
function createRequestInput(promptText, isTemplate, historyData) {&lt;br /&gt;
    // テンプレートの場合は会話履歴を反映しない&lt;br /&gt;
    if (isTemplate) {&lt;br /&gt;
        return [{ &amp;quot;role&amp;quot;: &amp;quot;user&amp;quot;, &amp;quot;content&amp;quot;: promptText }];&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // 以前の会話が存在しない場合は新規リクエスト&lt;br /&gt;
    if (!historyData) {&lt;br /&gt;
        return [{ &amp;quot;role&amp;quot;: &amp;quot;user&amp;quot;, &amp;quot;content&amp;quot;: promptText }];&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // 会話履歴が存在する場合は、履歴を含めてリクエスト&lt;br /&gt;
    const input = extractHistoryTexts(historyData)&lt;br /&gt;
        .map((message, index) =&amp;gt; ({&lt;br /&gt;
            role: index % 2 === 0 ? &amp;quot;user&amp;quot; : &amp;quot;assistant&amp;quot;,&lt;br /&gt;
            content: message&lt;br /&gt;
        }));&lt;br /&gt;
    input.push({&lt;br /&gt;
        &amp;quot;role&amp;quot;: &amp;quot;user&amp;quot;,&lt;br /&gt;
        &amp;quot;content&amp;quot;: promptText&lt;br /&gt;
    });&lt;br /&gt;
    return input;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// document.Tag にgptの応答を追加&lt;br /&gt;
function appendReturnTextToTag(userText, modelText, instructionText) {&lt;br /&gt;
    const tag = document.Tag;&lt;br /&gt;
    const messages = extractHistoryTexts(getHistoryData());&lt;br /&gt;
&lt;br /&gt;
    messages.push(userText, modelText);&lt;br /&gt;
    // 会話セット (user, model 1往復) が特定セット数を超えた場合、最初のセットを削除&lt;br /&gt;
    if (messages.length &amp;gt; HISTORY_SIZE * 2) {&lt;br /&gt;
        messages.splice(0, 2);&lt;br /&gt;
    }&lt;br /&gt;
    // 会話履歴全体が MAX_HISTORY_TOKEN を超えた場合、最初のセットを削除&lt;br /&gt;
    let joinedText = messages.join(&amp;quot;\n&amp;quot;);&lt;br /&gt;
    while (messages.length &amp;gt;= 2 &amp;amp;&amp;amp; roughTokenize(joinedText) &amp;gt; MAX_HISTORY_TOKEN) {&lt;br /&gt;
        messages.splice(0, 2);&lt;br /&gt;
        joinedText = messages.join(&amp;quot;\n&amp;quot;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    tag[HISTORY_TAG] = JSON.stringify({&lt;br /&gt;
        instruction: instructionText,&lt;br /&gt;
        messages: messages&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getLocalIsoDateTimeString() {&lt;br /&gt;
    const now = new Date();&lt;br /&gt;
    const pad = function (num) {&lt;br /&gt;
        return (num &amp;lt; 10 ? &amp;quot;0&amp;quot; : &amp;quot;&amp;quot;) + num;&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    const year = now.getFullYear();&lt;br /&gt;
    const month = pad(now.getMonth() + 1);&lt;br /&gt;
    const day = pad(now.getDate());&lt;br /&gt;
    const hours = pad(now.getHours());&lt;br /&gt;
    const minutes = pad(now.getMinutes());&lt;br /&gt;
    const seconds = pad(now.getSeconds());&lt;br /&gt;
&lt;br /&gt;
    const offsetMinutes = now.getTimezoneOffset();&lt;br /&gt;
    const offsetSign = offsetMinutes &amp;lt;= 0 ? &amp;quot;+&amp;quot; : &amp;quot;-&amp;quot;;&lt;br /&gt;
    const absOffsetHours = pad(Math.floor(Math.abs(offsetMinutes) / 60));&lt;br /&gt;
    const absOffsetMinutes = pad(Math.abs(offsetMinutes) % 60);&lt;br /&gt;
&lt;br /&gt;
    const isoLikeLocal = year + &amp;quot;-&amp;quot; + month + &amp;quot;-&amp;quot; + day&lt;br /&gt;
        + &amp;quot;T&amp;quot; + hours + &amp;quot;:&amp;quot; + minutes + &amp;quot;:&amp;quot; + seconds&lt;br /&gt;
        + offsetSign + absOffsetHours + &amp;quot;:&amp;quot; + absOffsetMinutes;&lt;br /&gt;
    return isoLikeLocal;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function baseCustomInstruction() {&lt;br /&gt;
    const language = LANGUAGE.trim();&lt;br /&gt;
    return &amp;quot;The current date and time for this user is &amp;quot; + getLocalIsoDateTimeString() + &amp;quot;. You cannot know for sure what happens after the knowledge cut-off date.&amp;quot;&lt;br /&gt;
        + (language ? &amp;quot;Respond in &amp;quot; + language + &amp;quot;\n&amp;quot; : &amp;quot;&amp;quot;)&lt;br /&gt;
        + &amp;quot;Do not hallucinate.\n&amp;quot;&lt;br /&gt;
        + &amp;quot;**Start with the main point of the question without explaining or declaring your writing style or tone.**\n&amp;quot;&lt;br /&gt;
        + &amp;quot;**At the end of your answer, avoid adding formulaic phrases that prompt the next question or unrelated conversation that is not directly related to your answer.**\n&amp;quot;&lt;br /&gt;
    // + &amp;quot;Do NOT EVER use full-width brackets \&amp;quot;（）\&amp;quot;, but use half-width brackets \&amp;quot;()\&amp;quot;. Enclose the outside of the half-width brackets with half-width spaces.\n&amp;quot;&lt;br /&gt;
    // + &amp;quot;When using Markdown formatting, you MUST follow these rules:\n&amp;quot;&lt;br /&gt;
    // + &amp;quot;- Rule 1: Insert a blank line below the header line (A header line begins with one or more # characters).\n&amp;quot;&lt;br /&gt;
    // + &amp;quot;- Rule 2: Insert spaces before and after bold (surrounded by **), italic (surrounded by *), and inline code (surrounded by `). For example: \&amp;quot;私は **元気** です。\&amp;quot;\n&amp;quot;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function buildCustomInstructionText() {&lt;br /&gt;
    let customInstruction = getTemporaryCustomInstruction();&lt;br /&gt;
    if (!customInstruction) {&lt;br /&gt;
        // 一時的なカスタムインストラクションがない場合はデフォルトを使用&lt;br /&gt;
        customInstruction = DEFAULT_CUSTOM_INSTRUCTION;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    customInstruction = customInstruction.replace(/^[\s　]+|[\s　]+$/g, &amp;quot;&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
    return baseCustomInstruction().trim()&lt;br /&gt;
        + (customInstruction ? &amp;quot;\n&amp;quot; + customInstruction : &amp;quot;&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getTemporaryCustomInstruction() {&lt;br /&gt;
    var tag = doc.Tag;&lt;br /&gt;
    if (tag.exists(TEMPORARY_CUSTOM_INSTRUCTION_TAG)) {&lt;br /&gt;
        return tag[TEMPORARY_CUSTOM_INSTRUCTION_TAG];&lt;br /&gt;
    } else {&lt;br /&gt;
        return &amp;quot;&amp;quot;;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function generateRandomId(length = 16) {&lt;br /&gt;
    const chars = &#039;ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789&#039;;&lt;br /&gt;
    let result = &#039;&#039;;&lt;br /&gt;
    for (let i = 0; i &amp;lt; length; i++) {&lt;br /&gt;
        result += chars[Math.floor(Math.random() * chars.length)];&lt;br /&gt;
    }&lt;br /&gt;
    return result;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 従来エンジン版 (アーカイブ) ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot; copy&amp;gt;&lt;br /&gt;
#title = &amp;quot;GPT に相談&amp;quot;&lt;br /&gt;
BeginUndoGroup();&lt;br /&gt;
&lt;br /&gt;
// 会話の履歴を保持する数 (質問と回答で1セット)&lt;br /&gt;
// 数を増やすと、会話の履歴が増えますが、Meryのメモリ使用量が増えたり、&lt;br /&gt;
// 動作が重くなる可能性があるため、適宜調整してください。&lt;br /&gt;
// document.Tag に保持されるため、開いているファイルを閉じると履歴は消えます。&lt;br /&gt;
var HISTORY_SIZE = 4;&lt;br /&gt;
&lt;br /&gt;
// コンテキストメニュー番号&lt;br /&gt;
var EXECUTE_AI = 1;&lt;br /&gt;
var RESET_HISTORY = 2;&lt;br /&gt;
&lt;br /&gt;
var ConsoleClass = function () {&lt;br /&gt;
    this.log = function (str) {&lt;br /&gt;
        outputBar.Writeln(str)&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
var console = new ConsoleClass();&lt;br /&gt;
&lt;br /&gt;
var doc = document;&lt;br /&gt;
var sel = document.selection;&lt;br /&gt;
&lt;br /&gt;
main();&lt;br /&gt;
&lt;br /&gt;
function main() {&lt;br /&gt;
    var selectionIsEmpty = sel.IsEmpty&lt;br /&gt;
    var beforeScrollY = ScrollY;&lt;br /&gt;
&lt;br /&gt;
    // プロンプトの取得&lt;br /&gt;
    var promptText = &amp;quot;&amp;quot;;&lt;br /&gt;
    var promptLength = 0;&lt;br /&gt;
    if (selectionIsEmpty) {&lt;br /&gt;
        sel.StartOfLine(false, mePosLogical);&lt;br /&gt;
        sel.EndOfLine(true, mePosLogical);&lt;br /&gt;
    }&lt;br /&gt;
    promptText = sel.Text.trim();&lt;br /&gt;
    promptLength = sel.TextLength;&lt;br /&gt;
    // コンテキストメニューの作成&lt;br /&gt;
    var menu = createContextMenu(selectionIsEmpty);&lt;br /&gt;
    var menuTrack = menu.Track(0);&lt;br /&gt;
    // メニュー選択がキャンセルされた場合は終了&lt;br /&gt;
    if (menuTrack === 0) return;&lt;br /&gt;
&lt;br /&gt;
    // 会話履歴のリセット&lt;br /&gt;
    if (menuTrack === 2) {&lt;br /&gt;
        resetTag();&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // プロンプトが空の場合はエラー&lt;br /&gt;
    if (promptLength === 0) {&lt;br /&gt;
        alert(&amp;quot;プロンプトを入力してください。&amp;quot;);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
    // 文字数が多すぎる場合はエラー&lt;br /&gt;
    if (promptLength &amp;gt; 10000) {&lt;br /&gt;
        alert(&amp;quot;プロンプトの文字数が多すぎます。10000 文字以下にしてください。&amp;quot;);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
    // 読み取り専用の場合はエラー&lt;br /&gt;
    if (doc.ReadOnly) {&lt;br /&gt;
        alert(&amp;quot;ファイルが読み取り専用のため実行できません。&amp;quot;);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // カーソル位置移動&lt;br /&gt;
    sel.SetActivePoint(mePosLogical, sel.GetBottomPointX(mePosLogical), sel.GetBottomPointY(mePosLogical));&lt;br /&gt;
    sel.EndOfLine(false, mePosLogical);&lt;br /&gt;
    sel.NewLine();&lt;br /&gt;
    var activeY = sel.GetActivePointY(mePosLogical);&lt;br /&gt;
&lt;br /&gt;
    // GPT API に問い合わせ&lt;br /&gt;
    doc.ReadOnly = true;&lt;br /&gt;
    var ret = callGPTAPI(promptText);&lt;br /&gt;
    doc.ReadOnly = false;&lt;br /&gt;
&lt;br /&gt;
    // 結果を表示&lt;br /&gt;
    sel.Text = &amp;quot;\n---\n\n&amp;quot; + ret[0] + &amp;quot;\n\n&amp;quot; + ret[1] + &amp;quot;\n\n---\n&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
    // 位置調整&lt;br /&gt;
    sel.SetActivePoint(&lt;br /&gt;
        mePosLogical,&lt;br /&gt;
        1,&lt;br /&gt;
        activeY,&lt;br /&gt;
        false&lt;br /&gt;
    )&lt;br /&gt;
    if (beforeScrollY + 2 &amp;lt; ScrollY) {&lt;br /&gt;
        ScrollY = ScrollY - 1;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function createContextMenu(selectionIsEmpty) {&lt;br /&gt;
    var menu = CreatePopupMenu();&lt;br /&gt;
    menu.Add(&amp;quot;※注意※ プライバシー・機密情報を送信しないこと&amp;quot;, 0);&lt;br /&gt;
    menu.Add(&amp;quot;&amp;quot;, 0, meMenuSeparator);&lt;br /&gt;
    if (selectionIsEmpty) {&lt;br /&gt;
        menu.Add(&amp;quot;アクティブ行の内容で相談 (&amp;amp;Q)&amp;quot;, EXECUTE_AI);&lt;br /&gt;
    } else {&lt;br /&gt;
        menu.Add(&amp;quot;選択範囲の内容で相談 (&amp;amp;Q)&amp;quot;, EXECUTE_AI);&lt;br /&gt;
    }&lt;br /&gt;
    if (document.Tag.exists(&amp;quot;gpt-text&amp;quot;)) {&lt;br /&gt;
        var turn = JSON.parse(document.Tag(&amp;quot;gpt-text&amp;quot;)).length / 2;&lt;br /&gt;
        menu.Add(&amp;quot;会話履歴をリセット (現在の会話数: &amp;quot; + turn + &amp;quot;) (&amp;amp;R)&amp;quot;, RESET_HISTORY);&lt;br /&gt;
    }&lt;br /&gt;
    return menu;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function callGPTAPI(promptText) {&lt;br /&gt;
    try {&lt;br /&gt;
        // APIキーの取得&lt;br /&gt;
        var gptApiKey = shell.getEnv(&amp;quot;OPENAI_API_KEY&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
        var http = new ActiveXObject(&amp;quot;Msxml2.ServerXMLHTTP&amp;quot;);&lt;br /&gt;
        var url = &#039;https://api.openai.com/v1/chat/completions&#039;;&lt;br /&gt;
&lt;br /&gt;
        http.open(&#039;POST&#039;, url, true);&lt;br /&gt;
        http.setRequestHeader(&#039;Content-Type&#039;, &#039;application/json&#039;);&lt;br /&gt;
        http.setRequestHeader(&#039;Authorization&#039;, &#039;Bearer &#039; + gptApiKey);&lt;br /&gt;
        http.send(&#039;{&#039;&lt;br /&gt;
            + &#039;&amp;quot;model&amp;quot;: &amp;quot;gpt-4o-mini&amp;quot;,&#039; // モデルの指定&lt;br /&gt;
            + &#039;&amp;quot;temperature&amp;quot;: 0.7,&#039; // 生成の多様性 (0.0 ～ 1.0)&lt;br /&gt;
            + &#039;&amp;quot;max_tokens&amp;quot;: 4000,&#039; // 生成トークン数の上限&lt;br /&gt;
            + &#039;&amp;quot;messages&amp;quot;:&#039; + createGPTRequest(promptText)&lt;br /&gt;
            + &#039;}&#039;);&lt;br /&gt;
&lt;br /&gt;
        // 応答待機&lt;br /&gt;
        var timeout = 60;&lt;br /&gt;
        var retrySec = 0;&lt;br /&gt;
        var startX = sel.GetActivePointX(mePosLogical);&lt;br /&gt;
        var prevPos = sel.GetActivePos();&lt;br /&gt;
        while (http.readyState != 4) {&lt;br /&gt;
            if (retrySec &amp;gt; timeout) {&lt;br /&gt;
                break;&lt;br /&gt;
            }&lt;br /&gt;
            http.waitForResponse(1);&lt;br /&gt;
            retrySec += 1;&lt;br /&gt;
            inputProgressChar(prevPos);&lt;br /&gt;
            prevPos = sel.GetActivePos();&lt;br /&gt;
        }&lt;br /&gt;
        // 進捗表示文字を削除&lt;br /&gt;
        sel.SetActivePoint(&lt;br /&gt;
            mePosLogical,&lt;br /&gt;
            startX,&lt;br /&gt;
            sel.GetActivePointY(mePosLogical),&lt;br /&gt;
            true&lt;br /&gt;
        );&lt;br /&gt;
        sel.Delete();&lt;br /&gt;
&lt;br /&gt;
        var responseMessage = &amp;quot;&amp;quot;;&lt;br /&gt;
        var token = &amp;quot;&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
        // 応答ステータスチェック&lt;br /&gt;
        if (http.readyState == 4 &amp;amp;&amp;amp; http.status == 200) {&lt;br /&gt;
            // JSONをパースして必要なテキストを抽出&lt;br /&gt;
            var response = JSON.parse(http.responseText);&lt;br /&gt;
            if (response.choices &amp;amp;&amp;amp; response.choices.length &amp;gt; 0) {&lt;br /&gt;
                // メッセージの内容を取得&lt;br /&gt;
                response.choices.forEach(function (choice) {&lt;br /&gt;
                    if (choice.message &amp;amp;&amp;amp; choice.message.content) {&lt;br /&gt;
                        responseMessage += choice.message.content;&lt;br /&gt;
                    }&lt;br /&gt;
                });&lt;br /&gt;
                // トークン数を取得&lt;br /&gt;
                var usage = response.usage;&lt;br /&gt;
                token += &amp;quot;(使用トークン数: &amp;quot;&lt;br /&gt;
                    + &amp;quot;プロンプト=&amp;quot; + usage.prompt_tokens&lt;br /&gt;
                    + &amp;quot;, 応答=&amp;quot; + usage.completion_tokens&lt;br /&gt;
                    + &amp;quot;, 合計=&amp;quot; + usage.total_tokens&lt;br /&gt;
                    + &amp;quot;)&amp;quot;;&lt;br /&gt;
            }&lt;br /&gt;
        } else {&lt;br /&gt;
            var responseMessage = &amp;quot;エラーが発生しました: &amp;quot; + http.status + &amp;quot; &amp;quot; + http.statusText&lt;br /&gt;
            http.abort();&lt;br /&gt;
            return responseMessage;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        // 結果を会話履歴に追加&lt;br /&gt;
        if (HISTORY_SIZE &amp;gt; 0) {&lt;br /&gt;
            appendReturnTextToTag(promptText, responseMessage);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        // 整形しつつ返す&lt;br /&gt;
        return [responseMessage.trim(), token];&lt;br /&gt;
    } catch (e) {&lt;br /&gt;
        console.log(e.stack);&lt;br /&gt;
        return [&amp;quot;エラーが発生しました: &amp;quot; + e.message, &amp;quot;&amp;quot;];&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// document.Tag の内容を加味してGPTのリクエストを作成&lt;br /&gt;
function createGPTRequest(promptText) {&lt;br /&gt;
    var tag = document.Tag;&lt;br /&gt;
    // 以前の会話が存在しない場合&lt;br /&gt;
    if (!tag.exists(&amp;quot;gpt-text&amp;quot;)) {&lt;br /&gt;
        return JSON.stringify([{ &amp;quot;role&amp;quot;: &amp;quot;user&amp;quot;, &amp;quot;content&amp;quot;: promptText }]);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    var gptText = JSON.parse(tag(&amp;quot;gpt-text&amp;quot;));&lt;br /&gt;
    gptText.push({&lt;br /&gt;
        &amp;quot;role&amp;quot;: &amp;quot;user&amp;quot;,&lt;br /&gt;
        &amp;quot;content&amp;quot;: promptText&lt;br /&gt;
    });&lt;br /&gt;
    return JSON.stringify(gptText);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// document.Tag にGPTの応答を追加&lt;br /&gt;
function appendReturnTextToTag(userText, modelText) {&lt;br /&gt;
    var tag = document.Tag;&lt;br /&gt;
    if (!tag.exists(&amp;quot;gpt-text&amp;quot;)) {&lt;br /&gt;
        tag(&amp;quot;gpt-text&amp;quot;) = JSON.stringify([&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;role&amp;quot;: &amp;quot;user&amp;quot;,&lt;br /&gt;
                &amp;quot;content&amp;quot;: userText&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;role&amp;quot;: &amp;quot;assistant&amp;quot;,&lt;br /&gt;
                &amp;quot;content&amp;quot;: modelText&lt;br /&gt;
            }&lt;br /&gt;
        ]);&lt;br /&gt;
    } else {&lt;br /&gt;
        var gptText = JSON.parse(tag(&amp;quot;gpt-text&amp;quot;));&lt;br /&gt;
        gptText.push({&lt;br /&gt;
            &amp;quot;role&amp;quot;: &amp;quot;user&amp;quot;,&lt;br /&gt;
            &amp;quot;content&amp;quot;: userText&lt;br /&gt;
        });&lt;br /&gt;
        gptText.push({&lt;br /&gt;
            &amp;quot;role&amp;quot;: &amp;quot;assistant&amp;quot;,&lt;br /&gt;
            &amp;quot;content&amp;quot;: modelText&lt;br /&gt;
        });&lt;br /&gt;
        // 会話セット (user, model 1往復) が特定セット数を超えた場合、最初のセットを削除&lt;br /&gt;
        while (gptText.length &amp;gt; HISTORY_SIZE * 2) {&lt;br /&gt;
            gptText.shift();&lt;br /&gt;
        }&lt;br /&gt;
        tag(&amp;quot;gpt-text&amp;quot;) = JSON.stringify(gptText);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function resetTag() {&lt;br /&gt;
    if (document.Tag.exists(&amp;quot;gpt-text&amp;quot;)) {&lt;br /&gt;
        document.Tag.remove(&amp;quot;gpt-text&amp;quot;);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function inputProgressChar(pos) {&lt;br /&gt;
    doc.ReadOnly = false;&lt;br /&gt;
    sel.SetActivePos(pos);&lt;br /&gt;
    sel.Text = &amp;quot;.&amp;quot;;&lt;br /&gt;
    doc.ReadOnly = true;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 更新履歴 ==&lt;br /&gt;
&lt;br /&gt;
* 2026-07-18 起動時間短縮のため、マクロ起動とAPI呼び出しで、マクロを分割した。その他プロンプトテンプレート更新等。&lt;br /&gt;
* 2025-02-16 カスタムインストラクションにハルシネーション抑制の指示を加えた。プロンプトに渡す文字数制限方式を調整。&lt;br /&gt;
* 2024-12-22 V8エンジン版を追加。それに伴い従来エンジン版はアーカイブ化。&lt;br /&gt;
* 2024-12-18 初版&lt;/div&gt;</summary>
		<author><name>Yuko</name></author>
	</entry>
	<entry>
		<id>https://www.haijin-boys.com/wiki/index.php?title=OpenAI_GPT_%E3%81%AB%E7%9B%B8%E8%AB%87&amp;diff=9603</id>
		<title>OpenAI GPT に相談</title>
		<link rel="alternate" type="text/html" href="https://www.haijin-boys.com/wiki/index.php?title=OpenAI_GPT_%E3%81%AB%E7%9B%B8%E8%AB%87&amp;diff=9603"/>
		<updated>2026-07-18T13:08:41Z</updated>

		<summary type="html">&lt;p&gt;Yuko: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== 概要 ==&lt;br /&gt;
&lt;br /&gt;
OpenAIの生成AIである「GPT」を利用するためのMeryマクロです。&lt;br /&gt;
&lt;br /&gt;
後述のソースコードではV8エンジンで動作するものと従来版スクリプトエンジンで動作するものの2種類を掲載していますが、従来版のものはアーカイブです。特段の理由がない限りはV8エンジン版をご利用ください。&lt;br /&gt;
&lt;br /&gt;
【主な機能】&lt;br /&gt;
&lt;br /&gt;
* OpenAI APIを利用して生成AIと対話できるMery用のマクロ&lt;br /&gt;
* 選択したテキストやアクティブな行の内容について生成AIに質問・相談が可能&lt;br /&gt;
* 会話履歴を保持し、文脈を考慮した対話が可能&lt;br /&gt;
&lt;br /&gt;
【特徴的な機能】&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;会話履歴機能&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* 事前の設定数に応じた会話履歴を保持&lt;br /&gt;
* 履歴のクリアが可能&lt;br /&gt;
&lt;br /&gt;
これにより、会話の文脈を考慮した回答を得られます。&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;カスタムインストラクション&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* AIの応答方針を設定可能&lt;br /&gt;
* 一時的なカスタム指示の保存と変更が可能&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;プロンプトテンプレート&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* 誤字脱字チェック&lt;br /&gt;
* 日本語翻訳&lt;br /&gt;
* コードの最適化、生成&lt;br /&gt;
&lt;br /&gt;
など、定型的な質問を簡単に実行できます。&lt;br /&gt;
※テンプレート機能では回答を安定させるため、前述の会話履歴を文脈に含みません。&lt;br /&gt;
&lt;br /&gt;
テンプレートは自身の使い方に合わせて好きに追加、削除ができます。詳細はソースコード内の「PROMPT_TEMPLATE」定数のコメントを参照。&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;生成AIチャット風仕様&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* ストリーミングモードにより、生成途中の回答がリアルタイムにMeryへ順次入力される&lt;br /&gt;
* Shiftキー長押しで、テキスト生成を中断できる&lt;br /&gt;
&lt;br /&gt;
== 注意事項 (必ず読んでからご利用ください) ==&lt;br /&gt;
&lt;br /&gt;
* 本マクロではOpenAI APIを利用します。API利用に伴う料金発生やOpenAI社の情報取り扱い等によるユーザー不利益が発生しても、作者は一切の責任を負いません。&lt;br /&gt;
** &#039;&#039;&#039;OpenAI APIでは、利用前にクレジットを購入するプリペイド請求が利用されます。意図しない利用を抑えるため、オートチャージや利用上限の設定を事前に確認してください。&#039;&#039;&#039;&lt;br /&gt;
** 2026/07/18 時点、OpenAIでは、プロンプトを学習に利用することを許可 (データ共有へのオプトイン) することで一定の無料枠を獲得できる制度があります。極力コストを抑えたい方はこちらの利用も検討してもよいかもしれません (オプトイン手順はここでは説明しません)。&lt;br /&gt;
* OpenAI APIへ送信した入力・出力データは、明示的にデータ共有へオプトインしない限り、デフォルトではモデルの学習に使用されません。ただし、外部サービスへ情報を送信することに変わりはないため、機密情報、個人情報、認証情報などは入力しないでください。&lt;br /&gt;
* OpenAI APIは、 [https://openai.com/ja-JP/policies/terms-of-use/ OpenAI利用規約] に則ってご利用ください。&lt;br /&gt;
&lt;br /&gt;
== 利用前提 ==&lt;br /&gt;
&lt;br /&gt;
* Mery Ver 3.7.8 以上&lt;br /&gt;
* [https://platform.openai.com/ OpenAI Platform] で、OpenAI APIキーを取得していること&lt;br /&gt;
* OpenAI APIを利用できるだけの課金残高があること&lt;br /&gt;
&lt;br /&gt;
== 利用方法 ==&lt;br /&gt;
&lt;br /&gt;
* 環境変数の設定&lt;br /&gt;
:コマンド プロンプトで以下のコマンドを実行し、環境変数を設定してください。&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bat&amp;quot; copy&amp;gt;&lt;br /&gt;
setx OPENAI_API_KEY &amp;quot;***************************************&amp;quot;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
:設定後、Mery または Windows の再起動が必要な場合があります。&lt;br /&gt;
* マクロの実行&lt;br /&gt;
** Meryのマクロフォルダー「Macros」に、後述の「v8_boot_gpt.js」「v8_call_gpt.js」をそれぞれ配置し、「v8_boot_gpt.js」をMeryへ読み込ませてください (「v8_call_gpt.js」は配置するだけでOK)。&lt;br /&gt;
**プロンプトにしたい文字列を選択してマクロを実行すると、選択したテキストがプロンプトとしてOpenAI APIに送信され、応答がエディタに表示されます。&lt;br /&gt;
* 会話の履歴数を調整&lt;br /&gt;
** 会話の履歴は設定した個数分が保持され、文脈として利用されます。適宜、ソースコード内の「HISTORY_SIZE」の数を調整してください。&lt;br /&gt;
** ※数を増やすほど会話内容を保持して文脈を理解するようになりますが、メモリ使用量が増えたり動作が重くなる可能性があります。&lt;br /&gt;
** ※開いている文書ごとに履歴を保持します。文脈をリセットしたいときは、新しい文書を開いて会話をやり直してください。&lt;br /&gt;
&lt;br /&gt;
== ソースコード ==&lt;br /&gt;
=== V8エンジン版 ===&lt;br /&gt;
&lt;br /&gt;
==== v8_boot_gpt.js ====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot; copy&amp;gt;&lt;br /&gt;
#language = &amp;quot;quickjs&amp;quot;&lt;br /&gt;
#async = true&lt;br /&gt;
#title = &amp;quot;GPT に相談 (v8)&amp;quot;&lt;br /&gt;
BeginUndoGroup();&lt;br /&gt;
&lt;br /&gt;
// 各種設定値&lt;br /&gt;
&lt;br /&gt;
// カスタムインストラクション&lt;br /&gt;
// これを設定することで、生成AIの回答全体への生成方針を指示できます。&lt;br /&gt;
// 例えば &amp;quot;あなたは侍で名前は権兵衛です。回答をござる口調にしてください&amp;quot; と設定すると、AIの回答がござる口調になります。&lt;br /&gt;
// 何も指示しない場合は、空文字 &amp;quot;&amp;quot; を設定してください。&lt;br /&gt;
let DEFAULT_CUSTOM_INSTRUCTION = `&lt;br /&gt;
文:&lt;br /&gt;
- 論理的で読みやすい文章構成にする&lt;br /&gt;
- 日本語として自然で一般的な表現を用いる&lt;br /&gt;
- 専門用語は必要に応じて注釈を加える&lt;br /&gt;
&lt;br /&gt;
口調:&lt;br /&gt;
- フレンドリーで愛想の良い敬語で話す。「…ですよね/…してみましょう/…かもしれません/…だと思います」などの柔らかい語調を使ってもよい&lt;br /&gt;
- 一人称は「私」&lt;br /&gt;
&lt;br /&gt;
性格:&lt;br /&gt;
- 多角的かつ中立的な見方で、必要であれば反対意見や代替案も示す&lt;br /&gt;
&lt;br /&gt;
誤字注意:&lt;br /&gt;
- 中国語や韓国語など、回答言語と異なる言語文字を入れないこと&lt;br /&gt;
&lt;br /&gt;
出力要件:&lt;br /&gt;
- Markdownとして再利用可能な正確な形式で出力する&lt;br /&gt;
- 羅列・比較的な内容を出力する場合は、Markdownテーブル形式よりもMarkdownリスト形式の使用を優先する&lt;br /&gt;
`.trim();&lt;br /&gt;
&lt;br /&gt;
// プロンプトテンプレート&lt;br /&gt;
// プロンプトテンプレートを設定することで、AIに対して特定の指示を出すことができます。&lt;br /&gt;
// プロンプトテンプレートでの生成は会話履歴とカスタムインストラクションを反映しません。&lt;br /&gt;
// テンプレートは以下の { ～ }, で囲まれたブロックを好きに増減させて利用できます。&lt;br /&gt;
const PROMPT_TEMPLATE = [&lt;br /&gt;
&lt;br /&gt;
    // 以下はサンプルです。1階層メニューと2階層メニューが利用できます。&lt;br /&gt;
    // {&lt;br /&gt;
    //     &amp;quot;title&amp;quot;: &amp;quot;要約&amp;quot;,&lt;br /&gt;
    //     &amp;quot;prompt&amp;quot;: &amp;quot;以下の文章を要約してください。&amp;quot;&lt;br /&gt;
    // },&lt;br /&gt;
    // {&lt;br /&gt;
    //     &amp;quot;submenu_title&amp;quot;: &amp;quot;翻訳系&amp;quot;,&lt;br /&gt;
    //     &amp;quot;submenu&amp;quot;: [&lt;br /&gt;
    //         {&lt;br /&gt;
    //             &amp;quot;title&amp;quot;: &amp;quot;日本語翻訳&amp;quot;,&lt;br /&gt;
    //             &amp;quot;prompt&amp;quot;: &amp;quot;以下の文章を日本語に翻訳してください。&amp;quot;&lt;br /&gt;
    //         },&lt;br /&gt;
    //         {&lt;br /&gt;
    //             &amp;quot;title&amp;quot;: &amp;quot;英語翻訳&amp;quot;,&lt;br /&gt;
    //             &amp;quot;prompt&amp;quot;: &amp;quot;以下の文章を英語に翻訳してください。&amp;quot;&lt;br /&gt;
    //         },&lt;br /&gt;
    //     ]&lt;br /&gt;
    // },&lt;br /&gt;
&lt;br /&gt;
    {&lt;br /&gt;
        &amp;quot;submenu_title&amp;quot;: &amp;quot;理解を深める&amp;quot;,&lt;br /&gt;
        &amp;quot;submenu&amp;quot;: [&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;論点を整理する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の論点を整理してまとめて。そしてその整理から示唆を見出して。&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;論理の抜け漏れを探す&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;あなたは「悪魔の代弁者」として、以下の主張・設計・計画を批判的に鋭く見てください。論理の穴・抜け・漏れ・飛躍、リスクなどを全力で探して指摘してください。遠慮は不要ですが、批判のためにこじつけたような論理性に欠けた主張にならないように注意してください。&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;要約する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;分かりやすさを重視して要約して。専門的な用語には注釈を入れること。&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;物語対話形式で解説&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の内容について物語対話形式で解説して。登場人物は「AI先生」(女性人格)と「生徒」(男の子人格)とする。&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
        ]&lt;br /&gt;
    },&lt;br /&gt;
    {&lt;br /&gt;
        &amp;quot;submenu_title&amp;quot;: &amp;quot;翻訳&amp;quot;,&lt;br /&gt;
        &amp;quot;submenu&amp;quot;: [&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;日本語に翻訳&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: `以下の入力文章を日本語に翻訳して。直訳的にせず、自然な表現にすること。入力文章はどのような内容・段落構造であっても要約・除外・集約などの整形を行わず、全体構造を維持したまま確実に全文を翻訳すること。&lt;br /&gt;
&lt;br /&gt;
出力形式は以下とすること:&lt;br /&gt;
[訳]&lt;br /&gt;
翻訳文...`&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;英語に翻訳&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: `以下の入力文章を英語に翻訳して。フォーマルとカジュアルの中間的なニュアンスで翻訳すること。入力文章はどのような内容・段落構造であっても要約・除外・集約などの整形を行わず、全体構造を維持したまま確実に全文を翻訳すること&lt;br /&gt;
&lt;br /&gt;
出力形式は以下とすること:&lt;br /&gt;
[Translation]&lt;br /&gt;
翻訳文...`&lt;br /&gt;
            },&lt;br /&gt;
        ]&lt;br /&gt;
    },&lt;br /&gt;
    {&lt;br /&gt;
        &amp;quot;submenu_title&amp;quot;: &amp;quot;語学&amp;quot;,&lt;br /&gt;
        &amp;quot;submenu&amp;quot;: [&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;英語 文法解説&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: `あなたは母国語が英語の英語教師です。&lt;br /&gt;
次の英文を以下の構成で解説してください。&lt;br /&gt;
&lt;br /&gt;
構成:&lt;br /&gt;
(1) 日本語翻訳&lt;br /&gt;
(2) スラッシュリーディング + 各句の文型（S/V/O/C）と語訳&lt;br /&gt;
(3) 重要文法&lt;br /&gt;
(4) 補足&lt;br /&gt;
&lt;br /&gt;
注意:&lt;br /&gt;
- 補足では日本語話者には分かりづらい表現や慣例表現などの解説を加える&lt;br /&gt;
- スラッシュリーディングで区切る際「M (修飾語)」が連なって長くなりすぎる場合には5～7語程度を目安に分ける&lt;br /&gt;
- 「M」の種類として形容詞、副詞、形容詞句、副詞句、分詞句、形容詞節、副詞節がある&lt;br /&gt;
&lt;br /&gt;
(2) の出力例:&lt;br /&gt;
They / elected / him / president / in 2020.&lt;br /&gt;
- They: (S) 彼らは&lt;br /&gt;
- elected: (V) 選出した&lt;br /&gt;
- him: (O) 彼を&lt;br /&gt;
- president: (C) 大統領&lt;br /&gt;
- in 2020: (M: 副詞句) 2020年に&lt;br /&gt;
`&lt;br /&gt;
&lt;br /&gt;
            },&lt;br /&gt;
        ]&lt;br /&gt;
    },&lt;br /&gt;
    {&lt;br /&gt;
        &amp;quot;submenu_title&amp;quot;: &amp;quot;プログラミング支援&amp;quot;,&lt;br /&gt;
        &amp;quot;submenu&amp;quot;: [&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;コードを解説する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下のコードを初めて読む人も理解できるように解説してください。私には言語の基本理解はあるものとし、コードの意図を理解するための情報を中心に説明してください。&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;コードを洗練させる&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下のコードを洗練させてください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;コードをレビューする&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下のコードをセキュリティと保守性の観点でレビューしてください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;エラーログをデバッグする&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下のエラーログを解析し、考えられる原因と対策を提示してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;SQLクエリを最適化する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下のSQL文を最適化してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;PowerShell スクリプトを書く&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;あなたはエキスパートとしてメンテナンス性の高いコードを書くのが得意です。以下の処理を行うPowerShellスクリプトを生成してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;Shell Script を書く&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;あなたはエキスパートとしてメンテナンス性の高いコードを書くのが得意です。以下の処理を行うShell Scriptを生成してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
        ]&lt;br /&gt;
    },&lt;br /&gt;
    {&lt;br /&gt;
        &amp;quot;submenu_title&amp;quot;: &amp;quot;ビジネス文書支援&amp;quot;,&lt;br /&gt;
        &amp;quot;submenu&amp;quot;: [&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;音声文字起こし文を整形する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: `以下は、音声文字起こしの文章です。以下のルールに従って、文章を整形してください。&lt;br /&gt;
&lt;br /&gt;
整形ルール:&lt;br /&gt;
- 要約するなどといった文章の全体感を損なう修正はしない&lt;br /&gt;
- 会話の流れから不自然と思われる単語には、対象単語の直後に誤字の可能性を示唆する [*] を付ける&lt;br /&gt;
- 誤字の可能性を示唆する [*] が付与された場合のみ、出力の末尾に「[*]: 誤字の可能性ありと凡例を書き添える&lt;br /&gt;
- 助詞や句読点など文章上の不適合を正す&lt;br /&gt;
- フィラーワードを除外する`&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;誤字脱字、不正確な表現をチェック&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の2つのカテゴリーで問題点を指摘してください\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 【誤字脱字】：明確な誤字脱字および誤字の疑いがある箇所\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 【不正確・曖昧な表現】：解釈に個人差が生じる可能性がある表現、より適切な表現に改善できる箇所&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;提案書を作成する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下に対する対する提案書を、コスト・実現性・リスクを含めて作成してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;手順書のアウトラインを作成する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の作業を行う手順書を、前提条件と注意事項を含めてアウトラインを作成してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;ユーザーマニュアルのアウトラインを作成する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の機能内容から、ユーザー向けマニュアルのアウトラインを作成してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;議事録を作成する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の会議内容から議事録を作成してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
        ]&lt;br /&gt;
    },&lt;br /&gt;
    {&lt;br /&gt;
        &amp;quot;submenu_title&amp;quot;: &amp;quot;分析支援&amp;quot;,&lt;br /&gt;
        &amp;quot;submenu&amp;quot;: [&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;問題を分析し、課題を抽出する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の状況から問題や潜在的リスクを分析し、課題を抽出してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;対応策を提案する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の問題・課題に対する対応策を複数提案してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;傾向を見つける&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の内容から、数値や挙動上の傾向を見つけてください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
        ]&lt;br /&gt;
    },&lt;br /&gt;
    {&lt;br /&gt;
        &amp;quot;submenu_title&amp;quot;: &amp;quot;アイデア出し&amp;quot;,&lt;br /&gt;
        &amp;quot;submenu&amp;quot;: [&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;SCAMPER法でアイデア出し&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下のテーマについて、SCAMPER法を使って15個のアイデアを出してください。&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;解決策の案出し&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の課題に対して、技術面、運用面、コスト面から実現可能な解決策を5つずつ提案してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;逆転の発想&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の目標に対して、通常とは逆の視点からアプローチする方法を3つ提案してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;競合分析からの着想&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の内容について、業界における競合他社の特徴を分析し、差別化できる新しいアプローチを提案してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;ユースケースの列挙&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の製品・サービスについて、想定されるユースケースを10個列挙し、それぞれの価値提案を記載してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
        ]&lt;br /&gt;
    },&lt;br /&gt;
&lt;br /&gt;
]&lt;br /&gt;
&lt;br /&gt;
// コンテキストメニュー番号の定義&lt;br /&gt;
const EXECUTE_AI = 1;&lt;br /&gt;
const RESET_HISTORY = 2;&lt;br /&gt;
const SET_CUSTOM_INSTRUCTION = 3;&lt;br /&gt;
const REEXECUTE_LAST_TEMPLATE_PROMPT = 4;&lt;br /&gt;
const ROMAN_ALPHABET_IME = 5;&lt;br /&gt;
const INVALID_MENU = 9;&lt;br /&gt;
const TEMPLATE_PROMPT_THRESHOLD = 10;&lt;br /&gt;
const TEMPLATE_PROMPT_SUBMENU_THRESHOLD = 1000;&lt;br /&gt;
&lt;br /&gt;
// タグ名の定義&lt;br /&gt;
const HISTORY_TAG = &amp;quot;gpt-history&amp;quot;;&lt;br /&gt;
const HISTORY_SIZE_TAG = &amp;quot;gpt-history-size&amp;quot;;&lt;br /&gt;
const TEMPORARY_CUSTOM_INSTRUCTION_TAG = &amp;quot;gpt-temporary-custom-instruction&amp;quot;;&lt;br /&gt;
const PREVIOUS_TEMPLATE_PROMPT_TAG = &amp;quot;gpt-previous-template-prompt&amp;quot;;&lt;br /&gt;
const SELECTED_TEMPLATE_PROMPT_TAG = &amp;quot;gpt-selected-template-prompt&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
// 会話の履歴を保持する数 (質問と回答で1セット)&lt;br /&gt;
// 数を増やすと、会話の履歴が増えますが、Meryのメモリ使用量が増えたり、&lt;br /&gt;
// 使用トークン数が増えて動作が重くなる可能性があるため、適宜調整してください。&lt;br /&gt;
// document.Tag に保持されるため、開いているファイルを閉じると履歴は消えます。&lt;br /&gt;
const HISTORY_SIZE = 5;&lt;br /&gt;
document.tag[HISTORY_SIZE_TAG] = HISTORY_SIZE;&lt;br /&gt;
&lt;br /&gt;
// console.log に出力する内容を Mery のアウトプットバーに表示する&lt;br /&gt;
var console = {&lt;br /&gt;
    log: function (str) {&lt;br /&gt;
        outputBar.Writeln(str)&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
var doc = document;&lt;br /&gt;
var sel = document.selection;&lt;br /&gt;
&lt;br /&gt;
main();&lt;br /&gt;
&lt;br /&gt;
function main() {&lt;br /&gt;
    var selectionIsEmpty = sel.IsEmpty&lt;br /&gt;
&lt;br /&gt;
    // プロンプトの取得&lt;br /&gt;
    if (selectionIsEmpty) {&lt;br /&gt;
        sel.StartOfLine(false, mePosLogical);&lt;br /&gt;
        sel.EndOfLine(true, mePosLogical);&lt;br /&gt;
    }&lt;br /&gt;
    var menuTrack = 0&lt;br /&gt;
    while (true) {&lt;br /&gt;
        // コンテキストメニューの作成&lt;br /&gt;
        var menu = createContextMenu(selectionIsEmpty);&lt;br /&gt;
        menuTrack = menu.Track(0);&lt;br /&gt;
        // メニュー選択がキャンセルされた場合は終了&lt;br /&gt;
        if (menuTrack === 0) return;&lt;br /&gt;
&lt;br /&gt;
        // 会話履歴のリセット&lt;br /&gt;
        if (menuTrack === RESET_HISTORY) {&lt;br /&gt;
            resetTag();&lt;br /&gt;
            continue;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        // カスタムインストラクションの設定&lt;br /&gt;
        if (menuTrack === SET_CUSTOM_INSTRUCTION) {&lt;br /&gt;
            configureTemporaryCustomInstruction();&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        break;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // 読み取り専用の場合はエラー&lt;br /&gt;
    if (doc.ReadOnly) {&lt;br /&gt;
        alert(&amp;quot;ファイルが読み取り専用のため実行できません。&amp;quot;);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // 直前に実行したプロンプトテンプレートの再実行の場合、menuTrack を書き換え&lt;br /&gt;
    if (menuTrack === REEXECUTE_LAST_TEMPLATE_PROMPT) {&lt;br /&gt;
        if (doc.Tag.exists(PREVIOUS_TEMPLATE_PROMPT_TAG)) {&lt;br /&gt;
            menuTrack = Number(doc.tag[PREVIOUS_TEMPLATE_PROMPT_TAG]);&lt;br /&gt;
        } else {&lt;br /&gt;
            alert(&amp;quot;直前に実行したプロンプトテンプレートがありません。&amp;quot;);&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // テンプレートを取得&lt;br /&gt;
    var isTemplate = menuTrack &amp;gt;= TEMPLATE_PROMPT_THRESHOLD;&lt;br /&gt;
    var isSubmenu = menuTrack &amp;gt;= TEMPLATE_PROMPT_SUBMENU_THRESHOLD;&lt;br /&gt;
    if (isSubmenu) {&lt;br /&gt;
        var parentMenuTrack = Number(String(menuTrack).slice(0, 2));&lt;br /&gt;
        var subMenuTrack = Number(String(menuTrack).slice(2));&lt;br /&gt;
        var templatePrompt = PROMPT_TEMPLATE[parentMenuTrack - TEMPLATE_PROMPT_THRESHOLD].submenu[subMenuTrack].prompt;&lt;br /&gt;
    } else if (isTemplate) {&lt;br /&gt;
        var templatePrompt = PROMPT_TEMPLATE[menuTrack - TEMPLATE_PROMPT_THRESHOLD].prompt;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // テンプレートの場合は直前に実行したプロンプトテンプレートの番号としてタグに保存&lt;br /&gt;
    if (isTemplate) {&lt;br /&gt;
        doc.tag[SELECTED_TEMPLATE_PROMPT_TAG] = templatePrompt;&lt;br /&gt;
        doc.tag[PREVIOUS_TEMPLATE_PROMPT_TAG] = menuTrack&lt;br /&gt;
    } else {&lt;br /&gt;
        // テンプレート以外の場合はタグを削除&lt;br /&gt;
        if (doc.Tag.exists(SELECTED_TEMPLATE_PROMPT_TAG)) {&lt;br /&gt;
            doc.Tag.remove(SELECTED_TEMPLATE_PROMPT_TAG);&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // ローマ字⇒日本語変換のプロンプトをセット&lt;br /&gt;
    const isRomanAlphabetIme = menuTrack === ROMAN_ALPHABET_IME;&lt;br /&gt;
    if (isRomanAlphabetIme) {&lt;br /&gt;
        doc.tag[SELECTED_TEMPLATE_PROMPT_TAG] = `以下のローマ字で入力された文章を、日本語に変換してください。&lt;br /&gt;
制約:&lt;br /&gt;
- 原則、入力のままの用語・口調で変換&lt;br /&gt;
- 誤字脱字は修正してよい&lt;br /&gt;
- 適宜、句読点を入れて読みやすくしてよい&lt;br /&gt;
- 返事は不要で、書き直した文章のみを出力`;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // gpt API 呼び出し部品を実行&lt;br /&gt;
    // V8版で fetch メソッドを使うために別呼び出しとする&lt;br /&gt;
    doc.tag[TEMPORARY_CUSTOM_INSTRUCTION_TAG] = getTemporaryCustomInstruction() ? getTemporaryCustomInstruction() : DEFAULT_CUSTOM_INSTRUCTION;&lt;br /&gt;
    ShowTip(&amp;quot;リクエスト中.....&amp;quot;, meShowTipPosCaret);&lt;br /&gt;
    editor.ExecuteMacro(&amp;quot;v8_call_gpt.js&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function createContextMenu(selectionIsEmpty) {&lt;br /&gt;
    const menu = CreatePopupMenu();&lt;br /&gt;
    menu.Add(&amp;quot;※注意※ プライバシー・機密情報を送信しないこと&amp;quot;, INVALID_MENU);&lt;br /&gt;
    menu.Add(&amp;quot;&amp;quot;, 0, meMenuSeparator);&lt;br /&gt;
    let t = &amp;quot;&amp;quot;;&lt;br /&gt;
    if (selectionIsEmpty) {&lt;br /&gt;
        t = &amp;quot;アクティブ行の内容で相談 (&amp;amp;A)&amp;quot;;&lt;br /&gt;
    } else {&lt;br /&gt;
        t = &amp;quot;選択範囲の内容で相談 (&amp;amp;A)&amp;quot;;&lt;br /&gt;
    }&lt;br /&gt;
    menu.Add(t, EXECUTE_AI);&lt;br /&gt;
    if (HISTORY_SIZE &amp;lt;= 0) {&lt;br /&gt;
        menu.Add(&amp;quot;├ 会話履歴をクリア (履歴が無効になっています)&amp;quot;, RESET_HISTORY, meMenuGrayed);&lt;br /&gt;
    } else if (doc.Tag.exists(HISTORY_TAG)) {&lt;br /&gt;
        var historyData = JSON.parse(doc.tag[HISTORY_TAG]);&lt;br /&gt;
        var messages;&lt;br /&gt;
        if (Array.isArray(historyData)) {&lt;br /&gt;
            // 旧形式: 配列が直接 messages&lt;br /&gt;
            messages = historyData;&lt;br /&gt;
        } else {&lt;br /&gt;
            // 新形式: { session_id, messages }&lt;br /&gt;
            messages = historyData.messages || [];&lt;br /&gt;
        }&lt;br /&gt;
        var turn = (messages.length) / 2;&lt;br /&gt;
        menu.Add(&amp;quot;├ 会話履歴をクリア (現在の会話数: &amp;quot; + turn + &amp;quot;) (&amp;amp;C)&amp;quot;, RESET_HISTORY);&lt;br /&gt;
    } else {&lt;br /&gt;
        menu.Add(&amp;quot;├ 会話履歴をクリア (現在の会話数: 0) (&amp;amp;C)&amp;quot;, RESET_HISTORY, meMenuGrayed);&lt;br /&gt;
    }&lt;br /&gt;
    menu.Add(&amp;quot;└ カスタムインストラクションを変更 (&amp;amp;I)&amp;quot;, SET_CUSTOM_INSTRUCTION);&lt;br /&gt;
&lt;br /&gt;
    menu.Add(&amp;quot;ローマ字⇒日本語 変換 (&amp;amp;R)&amp;quot;, ROMAN_ALPHABET_IME);&lt;br /&gt;
&lt;br /&gt;
    // テンプレート数チェック&lt;br /&gt;
    // 2桁まで許容&lt;br /&gt;
    const ITEMS_MUX = 99;&lt;br /&gt;
    if (PROMPT_TEMPLATE.length &amp;gt; ITEMS_MUX - TEMPLATE_PROMPT_THRESHOLD) {&lt;br /&gt;
        throw new Error(&amp;quot;プロンプトテンプレートの数が多すぎます。&amp;quot;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // テンプレートメニューの追加&lt;br /&gt;
    if (PROMPT_TEMPLATE.length &amp;gt; 0) {&lt;br /&gt;
        const subMenu = CreatePopupMenu();&lt;br /&gt;
        let previousMenuString = &amp;quot;&amp;quot;;&lt;br /&gt;
        for (let i = 0; i &amp;lt; PROMPT_TEMPLATE.length; i++) {&lt;br /&gt;
            let accelerationKey = &amp;quot;&amp;quot;;&lt;br /&gt;
            if (i === 9) {&lt;br /&gt;
                accelerationKey = &amp;quot; (&amp;amp;0)&amp;quot;;&lt;br /&gt;
            } else if (i &amp;lt; 10) {&lt;br /&gt;
                accelerationKey = &amp;quot; (&amp;amp;&amp;quot; + (i + 1) + &amp;quot;)&amp;quot;;&lt;br /&gt;
            }&lt;br /&gt;
            const menuNum = TEMPLATE_PROMPT_THRESHOLD + i;&lt;br /&gt;
            if (PROMPT_TEMPLATE[i].submenu) {&lt;br /&gt;
                const subMenuList = PROMPT_TEMPLATE[i].submenu;&lt;br /&gt;
                // テンプレート数チェック&lt;br /&gt;
                if (subMenuList.length &amp;gt; ITEMS_MUX) {&lt;br /&gt;
                    throw new Error(&amp;quot;プロンプトテンプレート (サブメニュー) の数が多すぎます。&amp;quot;);&lt;br /&gt;
                }&lt;br /&gt;
                const subSubMenu = CreatePopupMenu();&lt;br /&gt;
                const subMenuPrefix = String(menuNum);&lt;br /&gt;
                for (let j = 0; j &amp;lt; subMenuList.length; j++) {&lt;br /&gt;
                    let subAccelerationKey = &amp;quot;&amp;quot;;&lt;br /&gt;
                    if (j === 9) {&lt;br /&gt;
                        subAccelerationKey = &amp;quot; (&amp;amp;0)&amp;quot;;&lt;br /&gt;
                    } else if (j &amp;lt; 10) {&lt;br /&gt;
                        subAccelerationKey = &amp;quot; (&amp;amp;&amp;quot; + (j + 1) + &amp;quot;)&amp;quot;;&lt;br /&gt;
                    }&lt;br /&gt;
                    // ES5 compatible version without padStart&lt;br /&gt;
                    const subMenuNum = Number(subMenuPrefix + (j &amp;lt; 10 ? &amp;quot;0&amp;quot; + j : String(j)));&lt;br /&gt;
                    subSubMenu.Add(subMenuList[j].title + subAccelerationKey, subMenuNum);&lt;br /&gt;
                    if (subMenuNum === doc.tag[PREVIOUS_TEMPLATE_PROMPT_TAG]) {&lt;br /&gt;
                        previousMenuString = subMenuList[j].title;&lt;br /&gt;
                    }&lt;br /&gt;
                }&lt;br /&gt;
                subMenu.AddPopup(PROMPT_TEMPLATE[i].submenu_title + accelerationKey, subSubMenu);&lt;br /&gt;
            } else {&lt;br /&gt;
                subMenu.Add(PROMPT_TEMPLATE[i].title + accelerationKey, menuNum);&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        if (selectionIsEmpty) {&lt;br /&gt;
            t = &amp;quot;プロンプト テンプレート (対象: アクティブ行) (&amp;amp;T)&amp;quot;;&lt;br /&gt;
        } else {&lt;br /&gt;
            t = &amp;quot;プロンプト テンプレート (対象: 選択範囲) (&amp;amp;T)&amp;quot;;&lt;br /&gt;
        }&lt;br /&gt;
        menu.AddPopup(t, subMenu);&lt;br /&gt;
&lt;br /&gt;
        t = &amp;quot;└ 直前に実行したプロンプト テンプレートを再実行 (&amp;amp;R)&amp;quot;;&lt;br /&gt;
        if (doc.Tag.exists(PREVIOUS_TEMPLATE_PROMPT_TAG)) {&lt;br /&gt;
            menu.Add(t, REEXECUTE_LAST_TEMPLATE_PROMPT);&lt;br /&gt;
            menu.Add(`    (直前実行: ${previousMenuString})`, REEXECUTE_LAST_TEMPLATE_PROMPT);&lt;br /&gt;
        } else {&lt;br /&gt;
            menu.Add(t, REEXECUTE_LAST_TEMPLATE_PROMPT, meMenuGrayed);&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return menu;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function configureTemporaryCustomInstruction() {&lt;br /&gt;
    let tempCustomInstruction = getTemporaryCustomInstruction();&lt;br /&gt;
    const prompt = Prompt(&amp;quot;カスタムインストラクションを変更 ※変更すると会話履歴がリセットされます&amp;quot;, tempCustomInstruction ? tempCustomInstruction : DEFAULT_CUSTOM_INSTRUCTION);&lt;br /&gt;
    if (prompt !== null) {&lt;br /&gt;
        if (prompt === DEFAULT_CUSTOM_INSTRUCTION) {&lt;br /&gt;
            // デフォルトと同じ値の場合はタグを削除&lt;br /&gt;
            setTemporaryCustomInstruction(&amp;quot;&amp;quot;);&lt;br /&gt;
        } else if (prompt === &amp;quot;&amp;quot; &amp;amp;&amp;amp; Confirm(&amp;quot;カスタムインストラクションをデフォルトに戻しますか？&amp;quot;)) {&lt;br /&gt;
            setTemporaryCustomInstruction(&amp;quot;&amp;quot;);&lt;br /&gt;
        } else if (prompt !== tempCustomInstruction) {&lt;br /&gt;
            // デフォルト以外の値が設定された場合は保存&lt;br /&gt;
            setTemporaryCustomInstruction(prompt);&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // 変更が加わっている場合は会話履歴をリセット&lt;br /&gt;
    if (tempCustomInstruction !== getTemporaryCustomInstruction()) {&lt;br /&gt;
        resetTag();&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function resetTag() {&lt;br /&gt;
    const tag = doc.Tag;&lt;br /&gt;
    if (tag.exists(HISTORY_TAG)) {&lt;br /&gt;
        tag.remove(HISTORY_TAG);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getTemporaryCustomInstruction() {&lt;br /&gt;
    var tag = doc.Tag;&lt;br /&gt;
    if (tag.exists(TEMPORARY_CUSTOM_INSTRUCTION_TAG)) {&lt;br /&gt;
        return tag[TEMPORARY_CUSTOM_INSTRUCTION_TAG];&lt;br /&gt;
    } else {&lt;br /&gt;
        return &amp;quot;&amp;quot;;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function setTemporaryCustomInstruction(temporaryCustomInstruction) {&lt;br /&gt;
    var tag = doc.Tag;&lt;br /&gt;
    if (temporaryCustomInstruction) {&lt;br /&gt;
        tag[TEMPORARY_CUSTOM_INSTRUCTION_TAG] = temporaryCustomInstruction;&lt;br /&gt;
    } else {&lt;br /&gt;
        tag.remove(TEMPORARY_CUSTOM_INSTRUCTION_TAG);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== v8_call_gpt.js ====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot; copy&amp;gt;&lt;br /&gt;
#language = &amp;quot;v8&amp;quot;&lt;br /&gt;
#title = &amp;quot;GPT に相談 (API 呼び出し部品)&amp;quot;&lt;br /&gt;
BeginUndoGroup();&lt;br /&gt;
&lt;br /&gt;
// 生成AIの回答の言語設定&lt;br /&gt;
// &amp;quot;Japanese&amp;quot; や &amp;quot;English&amp;quot; を設定できます。他の言語は未検証。&lt;br /&gt;
// 自動設定にする場合は空文字 &amp;quot;&amp;quot; を設定してください。&lt;br /&gt;
const LANGUAGE = &amp;quot;Japanese&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
// 入力プロンプトの最大トークン数&lt;br /&gt;
// 超適当なトークナイザー関数により計測し、英単語は4文字1トークン、それ以外は1文字1トークンとして数えます。&lt;br /&gt;
const MAX_PROMPT_TOKEN = 8000;&lt;br /&gt;
&lt;br /&gt;
// 会話履歴に保持する最大トークン数&lt;br /&gt;
// 会話履歴のトークン数がこの数を超える場合、古い履歴から削除されます。&lt;br /&gt;
const MAX_HISTORY_TOKEN = 50000;&lt;br /&gt;
&lt;br /&gt;
// 生成AI応答の入力毎の遅延時間 (ミリ秒)&lt;br /&gt;
// 入力をゆっくりにしたいときは数字を大きくしてください。&lt;br /&gt;
const INPUT_DELAY = 20;&lt;br /&gt;
&lt;br /&gt;
// タグ名の定義&lt;br /&gt;
const HISTORY_TAG = &amp;quot;gpt-history&amp;quot;;&lt;br /&gt;
const HISTORY_SIZE_TAG = &amp;quot;gpt-history-size&amp;quot;;&lt;br /&gt;
const TEMPORARY_CUSTOM_INSTRUCTION_TAG = &amp;quot;gpt-temporary-custom-instruction&amp;quot;;&lt;br /&gt;
const SELECTED_TEMPLATE_PROMPT_TAG = &amp;quot;gpt-selected-template-prompt&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
// 会話の履歴を保持する数 (質問と回答で1セット)&lt;br /&gt;
// boot マクロ側で設定されていることを前提としている&lt;br /&gt;
if (!document.tag.exists(HISTORY_SIZE_TAG)) {&lt;br /&gt;
    throw new Error(&amp;quot;会話履歴サイズが設定されていません。&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
const HISTORY_SIZE = document.tag[HISTORY_SIZE_TAG];&lt;br /&gt;
&lt;br /&gt;
// プロンプトキャッシュの照合に使う安定したキー&lt;br /&gt;
const PROMPT_CACHE_KEY = &amp;quot;mery-gpt-call-v1&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
// 生成AIのモデル名&lt;br /&gt;
&lt;br /&gt;
const MODEL_CONFIG = {&lt;br /&gt;
    model: &amp;quot;gpt-5.6-terra&amp;quot;,&lt;br /&gt;
    reasoning: { effort: &amp;quot;medium&amp;quot; },&lt;br /&gt;
    max_output_tokens: 4000,&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// console.log に出力する内容を Mery のアウトプットバーに表示する&lt;br /&gt;
const console = {&lt;br /&gt;
    log: function (str) {&lt;br /&gt;
        outputBar.Writeln(str)&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
const doc = document;&lt;br /&gt;
const sel = document.selection;&lt;br /&gt;
&lt;br /&gt;
// 生成リクエスト中止フラグ&lt;br /&gt;
let abortRequestFlg = false&lt;br /&gt;
// 生成リクエスト完了フラグ;&lt;br /&gt;
let requestCompletedFlg = false;&lt;br /&gt;
&lt;br /&gt;
main();&lt;br /&gt;
&lt;br /&gt;
async function main() {&lt;br /&gt;
    let promptText = sel.Text.trim();&lt;br /&gt;
&lt;br /&gt;
    // プロンプトが空の場合はエラー&lt;br /&gt;
    if (promptText.length === 0) {&lt;br /&gt;
        alert(&amp;quot;プロンプトを入力してください。&amp;quot;);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
    // 文字数が多すぎる場合はエラー&lt;br /&gt;
    if (roughTokenize(promptText) &amp;gt; MAX_PROMPT_TOKEN) {&lt;br /&gt;
        alert(&amp;quot;プロンプトの文字数が多すぎます。文字数を少なくしてください。&amp;quot;);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    // テンプレートの場合はプロンプトを変更&lt;br /&gt;
    const isTemplate = doc.Tag.exists(SELECTED_TEMPLATE_PROMPT_TAG);&lt;br /&gt;
    if (isTemplate) {&lt;br /&gt;
        let instructionText = doc.Tag[SELECTED_TEMPLATE_PROMPT_TAG];&lt;br /&gt;
        const randomId = generateRandomId();&lt;br /&gt;
        promptText = `&lt;br /&gt;
&amp;lt;INSTRUCTIONS_${randomId}&amp;gt;&lt;br /&gt;
${instructionText}&lt;br /&gt;
&amp;lt;/INSTRUCTIONS_${randomId}&amp;gt;&lt;br /&gt;
&amp;lt;INPUTDATA_${randomId}&amp;gt;&lt;br /&gt;
${promptText}&lt;br /&gt;
&amp;lt;/INPUTDATA_${randomId}&amp;gt;&lt;br /&gt;
        `.trim();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    try {&lt;br /&gt;
        // 処理終了を待機&lt;br /&gt;
        shell.KeepRunning = true;&lt;br /&gt;
&lt;br /&gt;
        // カーソル位置移動&lt;br /&gt;
        sel.SetActivePoint(mePosLogical, sel.GetBottomPointX(mePosLogical), sel.GetBottomPointY(mePosLogical));&lt;br /&gt;
        sel.EndOfLine(false, mePosLogical);&lt;br /&gt;
        sel.NewLine();&lt;br /&gt;
        sel.Text = &amp;quot;\n---\n\n&amp;quot;&lt;br /&gt;
&lt;br /&gt;
        document.KeepScrollPos = true;&lt;br /&gt;
&lt;br /&gt;
        // APIキーの取得&lt;br /&gt;
        const apiKey = shell.getEnv(&amp;quot;OPENAI_API_KEY&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
        // gpt API に問い合わせ&lt;br /&gt;
        await Promise.all([&lt;br /&gt;
            callApi(apiKey, promptText, isTemplate),&lt;br /&gt;
            abortRequest(),&lt;br /&gt;
        ]);&lt;br /&gt;
        if (sel.GetActivePointX(mePosLogical) &amp;gt; 2) {&lt;br /&gt;
            sel.Text = &amp;quot;\n\n---\n\n\n&amp;quot;&lt;br /&gt;
        } else {&lt;br /&gt;
            sel.Text = &amp;quot;\n---\n\n\n&amp;quot;&lt;br /&gt;
        }&lt;br /&gt;
    } catch (e) {&lt;br /&gt;
        console.log(&amp;quot;エラーが発生しました: &amp;quot; + e.message + &amp;quot;\n&amp;quot; + e.stack);&lt;br /&gt;
        sel.Text = &amp;quot;&amp;lt;処理中にエラー発生&amp;gt;&amp;quot;&lt;br /&gt;
    } finally {&lt;br /&gt;
        ShowTip(&amp;quot;&amp;quot;, meShowTipHide);&lt;br /&gt;
        // 処理終了待機を解除&lt;br /&gt;
        shell.KeepRunning = false;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// Shift キーが長押しされた場合はリクエスト中止フラグを立てる&lt;br /&gt;
async function abortRequest() {&lt;br /&gt;
    const VIRTUAL_KEY_CODE_SHIFT = 0x10; // Shift キーの仮想キーコード&lt;br /&gt;
    let pressCount = 0;&lt;br /&gt;
    while (!requestCompletedFlg) {&lt;br /&gt;
        await new Promise(resolve =&amp;gt; setTimeout(resolve, 300));&lt;br /&gt;
        if (shell.GetKeyState(VIRTUAL_KEY_CODE_SHIFT) &amp;lt; 0) {&lt;br /&gt;
            pressCount++;&lt;br /&gt;
            if (pressCount &amp;gt; 2) {&lt;br /&gt;
                abortRequestFlg = true;&lt;br /&gt;
                return;&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
async function callApi(apiKey, promptText, isTemplate) {&lt;br /&gt;
    const controller = new AbortController();&lt;br /&gt;
    const signal = controller.signal;&lt;br /&gt;
    try {&lt;br /&gt;
        // Responses API にストリーミングでリクエスト&lt;br /&gt;
&lt;br /&gt;
        const customInstructionText = buildCustomInstructionText();&lt;br /&gt;
        const historyData = isTemplate ? null : getHistoryData();&lt;br /&gt;
        // 会話中は最初のターンで保存した instructions を継続して使用する&lt;br /&gt;
        const instructionText = getHistoryInstruction(historyData, customInstructionText);&lt;br /&gt;
        const requestInput = createRequestInput(promptText, isTemplate, historyData);&lt;br /&gt;
        const requestBody = {&lt;br /&gt;
            input: requestInput,&lt;br /&gt;
            instructions: instructionText,&lt;br /&gt;
            prompt_cache_key: PROMPT_CACHE_KEY,&lt;br /&gt;
            stream: true,&lt;br /&gt;
        };&lt;br /&gt;
&lt;br /&gt;
        for (let key in MODEL_CONFIG) {&lt;br /&gt;
            requestBody[key] = MODEL_CONFIG[key];&lt;br /&gt;
        }&lt;br /&gt;
        const url = &#039;https://api.openai.com/v1/responses&#039;;&lt;br /&gt;
&lt;br /&gt;
        const apiCallStartTime = Date.now();&lt;br /&gt;
        const response = await fetch(url, {&lt;br /&gt;
            signal: signal,&lt;br /&gt;
            method: &#039;POST&#039;,&lt;br /&gt;
            headers: {&lt;br /&gt;
                &#039;Content-Type&#039;: &#039;application/json&#039;,&lt;br /&gt;
                &#039;Authorization&#039;: &#039;Bearer &#039; + apiKey,&lt;br /&gt;
            },&lt;br /&gt;
            body: JSON.stringify(requestBody),&lt;br /&gt;
        });&lt;br /&gt;
        if (!response.ok) {&lt;br /&gt;
            let errorMessage = response.status + &amp;quot; &amp;quot; + response.statusText;&lt;br /&gt;
            try {&lt;br /&gt;
                const errorData = await response.json();&lt;br /&gt;
                errorMessage = errorData?.error?.message || errorMessage;&lt;br /&gt;
            } catch (_) {&lt;br /&gt;
                // エラー本文が JSON でない場合は HTTP ステータスを使用する&lt;br /&gt;
            }&lt;br /&gt;
            throw new Error(&amp;quot;Responses API エラー: &amp;quot; + errorMessage);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        let buffer = &amp;quot;&amp;quot;;&lt;br /&gt;
        let responseAll = &amp;quot;&amp;quot;;&lt;br /&gt;
        let usageText = &amp;quot;&amp;quot;;&lt;br /&gt;
        const reader = response.body.getReader();&lt;br /&gt;
        const decoder = new TextDecoder(&#039;utf-8&#039;);&lt;br /&gt;
        while (true) {&lt;br /&gt;
            // リクエスト中止フラグが立っている場合は中断&lt;br /&gt;
            if (abortRequestFlg) {&lt;br /&gt;
                controller.abort();&lt;br /&gt;
                sel.Text = &amp;quot;&amp;lt;処理を中止&amp;gt;&amp;quot;&lt;br /&gt;
                return;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            const { done, value } = await reader.read();&lt;br /&gt;
            if (done) {&lt;br /&gt;
                // デコーダーに残った文字列と最後の SSE 行を処理&lt;br /&gt;
                const finalText = buffer + decoder.decode();&lt;br /&gt;
                let finalInputText = &amp;quot;&amp;quot;;&lt;br /&gt;
                for (const line of finalText.split(/\n+/)) {&lt;br /&gt;
                    const processedData = processLine(line, responseAll);&lt;br /&gt;
                    if (processedData) {&lt;br /&gt;
                        finalInputText += processedData.inputText || &amp;quot;&amp;quot;;&lt;br /&gt;
                        usageText = processedData.usageText || usageText;&lt;br /&gt;
                        responseAll = processedData.responseAll || responseAll;&lt;br /&gt;
                    }&lt;br /&gt;
                }&lt;br /&gt;
                if (finalInputText) {&lt;br /&gt;
                    sel.Text = finalInputText;&lt;br /&gt;
                }&lt;br /&gt;
                break;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            // 受信したバイナリデータ (value) をテキストに変換する&lt;br /&gt;
            const text = decoder.decode(value, { stream: true });&lt;br /&gt;
&lt;br /&gt;
            // 前回のバッファと今回のテキストを結合&lt;br /&gt;
            const combinedText = buffer + text;&lt;br /&gt;
&lt;br /&gt;
            // 完全な行を探す（最後の改行の位置を見つける）&lt;br /&gt;
            const lastNewlineIndex = combinedText.lastIndexOf(&#039;\n&#039;);&lt;br /&gt;
&lt;br /&gt;
            if (lastNewlineIndex === -1) {&lt;br /&gt;
                // 改行がない場合は全てバッファに保存&lt;br /&gt;
                buffer = combinedText;&lt;br /&gt;
                continue;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            // 完全な行を処理&lt;br /&gt;
            const completeText = combinedText.substring(0, lastNewlineIndex);&lt;br /&gt;
            // 残りをバッファに保存&lt;br /&gt;
            buffer = combinedText.substring(lastNewlineIndex + 1);&lt;br /&gt;
&lt;br /&gt;
            // 完全な行を処理&lt;br /&gt;
            const lines = completeText.split(/\n+/);&lt;br /&gt;
&lt;br /&gt;
            let inputText = &amp;quot;&amp;quot;;&lt;br /&gt;
            for (const line of lines) {&lt;br /&gt;
                // 行の処理（既存のコードと同様）&lt;br /&gt;
                const processedData = processLine(line, responseAll);&lt;br /&gt;
                if (processedData) {&lt;br /&gt;
                    inputText += processedData.inputText || &amp;quot;&amp;quot;;&lt;br /&gt;
                    usageText = processedData.usageText || usageText;&lt;br /&gt;
                    responseAll = processedData.responseAll || responseAll;&lt;br /&gt;
                }&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            if (inputText.length &amp;gt; 0) {&lt;br /&gt;
                // 上記までで数文字まとまって取得されるので、まとめて入力&lt;br /&gt;
                sel.Text = inputText;&lt;br /&gt;
            }&lt;br /&gt;
            // 入力を少しゆっくりにする&lt;br /&gt;
            await new Promise(resolve =&amp;gt; setTimeout(resolve, INPUT_DELAY));&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        // トークン数・所要時間を入力&lt;br /&gt;
        const elapsedSec = ((Date.now() - apiCallStartTime) / 1000).toFixed(1);&lt;br /&gt;
        const elapsedText = &amp;quot;[所要時間] &amp;quot; + elapsedSec + &amp;quot; sec&amp;quot;;&lt;br /&gt;
        if (usageText) {&lt;br /&gt;
            usageText += &amp;quot; &amp;quot; + elapsedText;&lt;br /&gt;
        } else {&lt;br /&gt;
            usageText = elapsedText;&lt;br /&gt;
        }&lt;br /&gt;
        sel.Text = (responseAll.slice(-1) === &amp;quot;\n&amp;quot; ? &amp;quot;\n&amp;quot; : &amp;quot;\n\n&amp;quot;) + `${usageText}`;&lt;br /&gt;
&lt;br /&gt;
        // 結果を会話履歴に追加&lt;br /&gt;
        if (HISTORY_SIZE &amp;gt; 0 &amp;amp;&amp;amp; !isTemplate) {&lt;br /&gt;
            appendReturnTextToTag(promptText, responseAll, instructionText);&lt;br /&gt;
        }&lt;br /&gt;
    } catch (e) {&lt;br /&gt;
        controller.abort();&lt;br /&gt;
        throw e;&lt;br /&gt;
    } finally {&lt;br /&gt;
        requestCompletedFlg = true;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// Responses API の SSE イベントを一行ずつ処理する&lt;br /&gt;
function processLine(line, responseAll) {&lt;br /&gt;
    // HACK 万が一空行が入ってきた場合に備えて trim() しておく&lt;br /&gt;
    const json_text = line.trim().replace(/^data:\s*/, &#039;&#039;);&lt;br /&gt;
    // データがない場合はスキップ&lt;br /&gt;
    if (!json_text) {&lt;br /&gt;
        return null;&lt;br /&gt;
    }&lt;br /&gt;
    // SSE コメント行 (: で始まる行) はスキップ&lt;br /&gt;
    if (json_text.startsWith(&#039;:&#039;)) {&lt;br /&gt;
        return null;&lt;br /&gt;
    }&lt;br /&gt;
    // SSE の event: 行はイベント名だけを表し、データ本体ではない&lt;br /&gt;
    if (json_text.startsWith(&#039;event:&#039;)) {&lt;br /&gt;
        return null;&lt;br /&gt;
    }&lt;br /&gt;
    // データが終了した場合は終了&lt;br /&gt;
    if (json_text === &#039;[DONE]&#039;) {&lt;br /&gt;
        return null;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    let data;&lt;br /&gt;
    try {&lt;br /&gt;
        data = JSON.parse(json_text);&lt;br /&gt;
    } catch (e) {&lt;br /&gt;
        console.log(&amp;quot;JSONパースエラー: &amp;quot; + e.message + &amp;quot;, データ: &amp;quot; + json_text);&lt;br /&gt;
        return null;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (data.type === &amp;quot;error&amp;quot;) {&lt;br /&gt;
        throw new Error(data.error?.message || &amp;quot;Responses API ストリームエラー&amp;quot;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    try {&lt;br /&gt;
        let usageText = &amp;quot;&amp;quot;;&lt;br /&gt;
        let inputText = &amp;quot;&amp;quot;;&lt;br /&gt;
        let updatedResponseAll = responseAll;&lt;br /&gt;
&lt;br /&gt;
        // 利用量は response.completed イベント内の response.usage に含まれる&lt;br /&gt;
        const usage = data.response?.usage || data.usage;&lt;br /&gt;
        if (usage) {&lt;br /&gt;
            usageText = &amp;quot;[利用トークン数] 入力:&amp;quot; + usage.input_tokens&lt;br /&gt;
                + &amp;quot; 出力:&amp;quot; + usage.output_tokens&lt;br /&gt;
                + &amp;quot; 合計:&amp;quot; + usage.total_tokens;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        // テキストは response.output_text.delta イベントで届く&lt;br /&gt;
        if (data.type === &amp;quot;response.output_text.delta&amp;quot; &amp;amp;&amp;amp; data.delta) {&lt;br /&gt;
            inputText = data.delta;&lt;br /&gt;
            updatedResponseAll += data.delta;&lt;br /&gt;
        }&lt;br /&gt;
        return { inputText, usageText, responseAll: updatedResponseAll };&lt;br /&gt;
    } catch (e) {&lt;br /&gt;
        console.log(&amp;quot;Responses API イベント処理エラー: &amp;quot; + e.message + &amp;quot;, データ: &amp;quot; + json_text);&lt;br /&gt;
        return null;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function roughTokenize(text) {&lt;br /&gt;
    // 超適当なトークナイザ。トークン数をざっくりと多めに数える&lt;br /&gt;
    // 空白文字・ハイフン・アンダースコア区切りの単語に分割して、&lt;br /&gt;
    // 単語がアルファベットのみ、数字のみの場合には 4文字=1トークン として数える。&lt;br /&gt;
    // 単語にアルファベット以外の文字が含まれる場合は 1文字=1トークン として数える。&lt;br /&gt;
    return text&lt;br /&gt;
        .split(/[\s\-_]+/)&lt;br /&gt;
        .map(w =&amp;gt; /^[a-zA-Z]+$|^[0-9]+$|/.test(w) ? Math.ceil(w.length / 4) : w.length)&lt;br /&gt;
        .reduce((a, b) =&amp;gt; a + b, 0);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// document.Tag に保存された会話履歴を取得する&lt;br /&gt;
function getHistoryData() {&lt;br /&gt;
    const tag = document.Tag;&lt;br /&gt;
    return tag.exists(HISTORY_TAG) ? JSON.parse(tag[HISTORY_TAG]) : null;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// 保存済みの instructions を取得する&lt;br /&gt;
function getHistoryInstruction(historyData, fallbackInstruction) {&lt;br /&gt;
    return historyData ? historyData.instruction : fallbackInstruction;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// タグの messages を取得する&lt;br /&gt;
function extractHistoryTexts(historyData) {&lt;br /&gt;
    return historyData ? historyData.messages : [];&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// document.Tag の内容を加味して Responses API の input を作成&lt;br /&gt;
function createRequestInput(promptText, isTemplate, historyData) {&lt;br /&gt;
    // テンプレートの場合は会話履歴を反映しない&lt;br /&gt;
    if (isTemplate) {&lt;br /&gt;
        return [{ &amp;quot;role&amp;quot;: &amp;quot;user&amp;quot;, &amp;quot;content&amp;quot;: promptText }];&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // 以前の会話が存在しない場合は新規リクエスト&lt;br /&gt;
    if (!historyData) {&lt;br /&gt;
        return [{ &amp;quot;role&amp;quot;: &amp;quot;user&amp;quot;, &amp;quot;content&amp;quot;: promptText }];&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // 会話履歴が存在する場合は、履歴を含めてリクエスト&lt;br /&gt;
    const input = extractHistoryTexts(historyData)&lt;br /&gt;
        .map((message, index) =&amp;gt; ({&lt;br /&gt;
            role: index % 2 === 0 ? &amp;quot;user&amp;quot; : &amp;quot;assistant&amp;quot;,&lt;br /&gt;
            content: message&lt;br /&gt;
        }));&lt;br /&gt;
    input.push({&lt;br /&gt;
        &amp;quot;role&amp;quot;: &amp;quot;user&amp;quot;,&lt;br /&gt;
        &amp;quot;content&amp;quot;: promptText&lt;br /&gt;
    });&lt;br /&gt;
    return input;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// document.Tag にgptの応答を追加&lt;br /&gt;
function appendReturnTextToTag(userText, modelText, instructionText) {&lt;br /&gt;
    const tag = document.Tag;&lt;br /&gt;
    const messages = extractHistoryTexts(getHistoryData());&lt;br /&gt;
&lt;br /&gt;
    messages.push(userText, modelText);&lt;br /&gt;
    // 会話セット (user, model 1往復) が特定セット数を超えた場合、最初のセットを削除&lt;br /&gt;
    if (messages.length &amp;gt; HISTORY_SIZE * 2) {&lt;br /&gt;
        messages.splice(0, 2);&lt;br /&gt;
    }&lt;br /&gt;
    // 会話履歴全体が MAX_HISTORY_TOKEN を超えた場合、最初のセットを削除&lt;br /&gt;
    let joinedText = messages.join(&amp;quot;\n&amp;quot;);&lt;br /&gt;
    while (messages.length &amp;gt;= 2 &amp;amp;&amp;amp; roughTokenize(joinedText) &amp;gt; MAX_HISTORY_TOKEN) {&lt;br /&gt;
        messages.splice(0, 2);&lt;br /&gt;
        joinedText = messages.join(&amp;quot;\n&amp;quot;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    tag[HISTORY_TAG] = JSON.stringify({&lt;br /&gt;
        instruction: instructionText,&lt;br /&gt;
        messages: messages&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getLocalIsoDateTimeString() {&lt;br /&gt;
    const now = new Date();&lt;br /&gt;
    const pad = function (num) {&lt;br /&gt;
        return (num &amp;lt; 10 ? &amp;quot;0&amp;quot; : &amp;quot;&amp;quot;) + num;&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    const year = now.getFullYear();&lt;br /&gt;
    const month = pad(now.getMonth() + 1);&lt;br /&gt;
    const day = pad(now.getDate());&lt;br /&gt;
    const hours = pad(now.getHours());&lt;br /&gt;
    const minutes = pad(now.getMinutes());&lt;br /&gt;
    const seconds = pad(now.getSeconds());&lt;br /&gt;
&lt;br /&gt;
    const offsetMinutes = now.getTimezoneOffset();&lt;br /&gt;
    const offsetSign = offsetMinutes &amp;lt;= 0 ? &amp;quot;+&amp;quot; : &amp;quot;-&amp;quot;;&lt;br /&gt;
    const absOffsetHours = pad(Math.floor(Math.abs(offsetMinutes) / 60));&lt;br /&gt;
    const absOffsetMinutes = pad(Math.abs(offsetMinutes) % 60);&lt;br /&gt;
&lt;br /&gt;
    const isoLikeLocal = year + &amp;quot;-&amp;quot; + month + &amp;quot;-&amp;quot; + day&lt;br /&gt;
        + &amp;quot;T&amp;quot; + hours + &amp;quot;:&amp;quot; + minutes + &amp;quot;:&amp;quot; + seconds&lt;br /&gt;
        + offsetSign + absOffsetHours + &amp;quot;:&amp;quot; + absOffsetMinutes;&lt;br /&gt;
    return isoLikeLocal;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function baseCustomInstruction() {&lt;br /&gt;
    const language = LANGUAGE.trim();&lt;br /&gt;
    return &amp;quot;The current date and time for this user is &amp;quot; + getLocalIsoDateTimeString() + &amp;quot;. You cannot know for sure what happens after the knowledge cut-off date.&amp;quot;&lt;br /&gt;
        + (language ? &amp;quot;Respond in &amp;quot; + language + &amp;quot;\n&amp;quot; : &amp;quot;&amp;quot;)&lt;br /&gt;
        + &amp;quot;Do not hallucinate.\n&amp;quot;&lt;br /&gt;
        + &amp;quot;**Start with the main point of the question without explaining or declaring your writing style or tone.**\n&amp;quot;&lt;br /&gt;
        + &amp;quot;**At the end of your answer, avoid adding formulaic phrases that prompt the next question or unrelated conversation that is not directly related to your answer.**\n&amp;quot;&lt;br /&gt;
    // + &amp;quot;Do NOT EVER use full-width brackets \&amp;quot;（）\&amp;quot;, but use half-width brackets \&amp;quot;()\&amp;quot;. Enclose the outside of the half-width brackets with half-width spaces.\n&amp;quot;&lt;br /&gt;
    // + &amp;quot;When using Markdown formatting, you MUST follow these rules:\n&amp;quot;&lt;br /&gt;
    // + &amp;quot;- Rule 1: Insert a blank line below the header line (A header line begins with one or more # characters).\n&amp;quot;&lt;br /&gt;
    // + &amp;quot;- Rule 2: Insert spaces before and after bold (surrounded by **), italic (surrounded by *), and inline code (surrounded by `). For example: \&amp;quot;私は **元気** です。\&amp;quot;\n&amp;quot;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function buildCustomInstructionText() {&lt;br /&gt;
    let customInstruction = getTemporaryCustomInstruction();&lt;br /&gt;
    if (!customInstruction) {&lt;br /&gt;
        // 一時的なカスタムインストラクションがない場合はデフォルトを使用&lt;br /&gt;
        customInstruction = DEFAULT_CUSTOM_INSTRUCTION;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    customInstruction = customInstruction.replace(/^[\s　]+|[\s　]+$/g, &amp;quot;&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
    return baseCustomInstruction().trim()&lt;br /&gt;
        + (customInstruction ? &amp;quot;\n&amp;quot; + customInstruction : &amp;quot;&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getTemporaryCustomInstruction() {&lt;br /&gt;
    var tag = doc.Tag;&lt;br /&gt;
    if (tag.exists(TEMPORARY_CUSTOM_INSTRUCTION_TAG)) {&lt;br /&gt;
        return tag[TEMPORARY_CUSTOM_INSTRUCTION_TAG];&lt;br /&gt;
    } else {&lt;br /&gt;
        return &amp;quot;&amp;quot;;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function generateRandomId(length = 16) {&lt;br /&gt;
    const chars = &#039;ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789&#039;;&lt;br /&gt;
    let result = &#039;&#039;;&lt;br /&gt;
    for (let i = 0; i &amp;lt; length; i++) {&lt;br /&gt;
        result += chars[Math.floor(Math.random() * chars.length)];&lt;br /&gt;
    }&lt;br /&gt;
    return result;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 従来エンジン版 (アーカイブ) ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot; copy&amp;gt;&lt;br /&gt;
#title = &amp;quot;GPT に相談&amp;quot;&lt;br /&gt;
BeginUndoGroup();&lt;br /&gt;
&lt;br /&gt;
// 会話の履歴を保持する数 (質問と回答で1セット)&lt;br /&gt;
// 数を増やすと、会話の履歴が増えますが、Meryのメモリ使用量が増えたり、&lt;br /&gt;
// 動作が重くなる可能性があるため、適宜調整してください。&lt;br /&gt;
// document.Tag に保持されるため、開いているファイルを閉じると履歴は消えます。&lt;br /&gt;
var HISTORY_SIZE = 4;&lt;br /&gt;
&lt;br /&gt;
// コンテキストメニュー番号&lt;br /&gt;
var EXECUTE_AI = 1;&lt;br /&gt;
var RESET_HISTORY = 2;&lt;br /&gt;
&lt;br /&gt;
var ConsoleClass = function () {&lt;br /&gt;
    this.log = function (str) {&lt;br /&gt;
        outputBar.Writeln(str)&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
var console = new ConsoleClass();&lt;br /&gt;
&lt;br /&gt;
var doc = document;&lt;br /&gt;
var sel = document.selection;&lt;br /&gt;
&lt;br /&gt;
main();&lt;br /&gt;
&lt;br /&gt;
function main() {&lt;br /&gt;
    var selectionIsEmpty = sel.IsEmpty&lt;br /&gt;
    var beforeScrollY = ScrollY;&lt;br /&gt;
&lt;br /&gt;
    // プロンプトの取得&lt;br /&gt;
    var promptText = &amp;quot;&amp;quot;;&lt;br /&gt;
    var promptLength = 0;&lt;br /&gt;
    if (selectionIsEmpty) {&lt;br /&gt;
        sel.StartOfLine(false, mePosLogical);&lt;br /&gt;
        sel.EndOfLine(true, mePosLogical);&lt;br /&gt;
    }&lt;br /&gt;
    promptText = sel.Text.trim();&lt;br /&gt;
    promptLength = sel.TextLength;&lt;br /&gt;
    // コンテキストメニューの作成&lt;br /&gt;
    var menu = createContextMenu(selectionIsEmpty);&lt;br /&gt;
    var menuTrack = menu.Track(0);&lt;br /&gt;
    // メニュー選択がキャンセルされた場合は終了&lt;br /&gt;
    if (menuTrack === 0) return;&lt;br /&gt;
&lt;br /&gt;
    // 会話履歴のリセット&lt;br /&gt;
    if (menuTrack === 2) {&lt;br /&gt;
        resetTag();&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // プロンプトが空の場合はエラー&lt;br /&gt;
    if (promptLength === 0) {&lt;br /&gt;
        alert(&amp;quot;プロンプトを入力してください。&amp;quot;);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
    // 文字数が多すぎる場合はエラー&lt;br /&gt;
    if (promptLength &amp;gt; 10000) {&lt;br /&gt;
        alert(&amp;quot;プロンプトの文字数が多すぎます。10000 文字以下にしてください。&amp;quot;);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
    // 読み取り専用の場合はエラー&lt;br /&gt;
    if (doc.ReadOnly) {&lt;br /&gt;
        alert(&amp;quot;ファイルが読み取り専用のため実行できません。&amp;quot;);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // カーソル位置移動&lt;br /&gt;
    sel.SetActivePoint(mePosLogical, sel.GetBottomPointX(mePosLogical), sel.GetBottomPointY(mePosLogical));&lt;br /&gt;
    sel.EndOfLine(false, mePosLogical);&lt;br /&gt;
    sel.NewLine();&lt;br /&gt;
    var activeY = sel.GetActivePointY(mePosLogical);&lt;br /&gt;
&lt;br /&gt;
    // GPT API に問い合わせ&lt;br /&gt;
    doc.ReadOnly = true;&lt;br /&gt;
    var ret = callGPTAPI(promptText);&lt;br /&gt;
    doc.ReadOnly = false;&lt;br /&gt;
&lt;br /&gt;
    // 結果を表示&lt;br /&gt;
    sel.Text = &amp;quot;\n---\n\n&amp;quot; + ret[0] + &amp;quot;\n\n&amp;quot; + ret[1] + &amp;quot;\n\n---\n&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
    // 位置調整&lt;br /&gt;
    sel.SetActivePoint(&lt;br /&gt;
        mePosLogical,&lt;br /&gt;
        1,&lt;br /&gt;
        activeY,&lt;br /&gt;
        false&lt;br /&gt;
    )&lt;br /&gt;
    if (beforeScrollY + 2 &amp;lt; ScrollY) {&lt;br /&gt;
        ScrollY = ScrollY - 1;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function createContextMenu(selectionIsEmpty) {&lt;br /&gt;
    var menu = CreatePopupMenu();&lt;br /&gt;
    menu.Add(&amp;quot;※注意※ プライバシー・機密情報を送信しないこと&amp;quot;, 0);&lt;br /&gt;
    menu.Add(&amp;quot;&amp;quot;, 0, meMenuSeparator);&lt;br /&gt;
    if (selectionIsEmpty) {&lt;br /&gt;
        menu.Add(&amp;quot;アクティブ行の内容で相談 (&amp;amp;Q)&amp;quot;, EXECUTE_AI);&lt;br /&gt;
    } else {&lt;br /&gt;
        menu.Add(&amp;quot;選択範囲の内容で相談 (&amp;amp;Q)&amp;quot;, EXECUTE_AI);&lt;br /&gt;
    }&lt;br /&gt;
    if (document.Tag.exists(&amp;quot;gpt-text&amp;quot;)) {&lt;br /&gt;
        var turn = JSON.parse(document.Tag(&amp;quot;gpt-text&amp;quot;)).length / 2;&lt;br /&gt;
        menu.Add(&amp;quot;会話履歴をリセット (現在の会話数: &amp;quot; + turn + &amp;quot;) (&amp;amp;R)&amp;quot;, RESET_HISTORY);&lt;br /&gt;
    }&lt;br /&gt;
    return menu;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function callGPTAPI(promptText) {&lt;br /&gt;
    try {&lt;br /&gt;
        // APIキーの取得&lt;br /&gt;
        var gptApiKey = shell.getEnv(&amp;quot;OPENAI_API_KEY&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
        var http = new ActiveXObject(&amp;quot;Msxml2.ServerXMLHTTP&amp;quot;);&lt;br /&gt;
        var url = &#039;https://api.openai.com/v1/chat/completions&#039;;&lt;br /&gt;
&lt;br /&gt;
        http.open(&#039;POST&#039;, url, true);&lt;br /&gt;
        http.setRequestHeader(&#039;Content-Type&#039;, &#039;application/json&#039;);&lt;br /&gt;
        http.setRequestHeader(&#039;Authorization&#039;, &#039;Bearer &#039; + gptApiKey);&lt;br /&gt;
        http.send(&#039;{&#039;&lt;br /&gt;
            + &#039;&amp;quot;model&amp;quot;: &amp;quot;gpt-4o-mini&amp;quot;,&#039; // モデルの指定&lt;br /&gt;
            + &#039;&amp;quot;temperature&amp;quot;: 0.7,&#039; // 生成の多様性 (0.0 ～ 1.0)&lt;br /&gt;
            + &#039;&amp;quot;max_tokens&amp;quot;: 4000,&#039; // 生成トークン数の上限&lt;br /&gt;
            + &#039;&amp;quot;messages&amp;quot;:&#039; + createGPTRequest(promptText)&lt;br /&gt;
            + &#039;}&#039;);&lt;br /&gt;
&lt;br /&gt;
        // 応答待機&lt;br /&gt;
        var timeout = 60;&lt;br /&gt;
        var retrySec = 0;&lt;br /&gt;
        var startX = sel.GetActivePointX(mePosLogical);&lt;br /&gt;
        var prevPos = sel.GetActivePos();&lt;br /&gt;
        while (http.readyState != 4) {&lt;br /&gt;
            if (retrySec &amp;gt; timeout) {&lt;br /&gt;
                break;&lt;br /&gt;
            }&lt;br /&gt;
            http.waitForResponse(1);&lt;br /&gt;
            retrySec += 1;&lt;br /&gt;
            inputProgressChar(prevPos);&lt;br /&gt;
            prevPos = sel.GetActivePos();&lt;br /&gt;
        }&lt;br /&gt;
        // 進捗表示文字を削除&lt;br /&gt;
        sel.SetActivePoint(&lt;br /&gt;
            mePosLogical,&lt;br /&gt;
            startX,&lt;br /&gt;
            sel.GetActivePointY(mePosLogical),&lt;br /&gt;
            true&lt;br /&gt;
        );&lt;br /&gt;
        sel.Delete();&lt;br /&gt;
&lt;br /&gt;
        var responseMessage = &amp;quot;&amp;quot;;&lt;br /&gt;
        var token = &amp;quot;&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
        // 応答ステータスチェック&lt;br /&gt;
        if (http.readyState == 4 &amp;amp;&amp;amp; http.status == 200) {&lt;br /&gt;
            // JSONをパースして必要なテキストを抽出&lt;br /&gt;
            var response = JSON.parse(http.responseText);&lt;br /&gt;
            if (response.choices &amp;amp;&amp;amp; response.choices.length &amp;gt; 0) {&lt;br /&gt;
                // メッセージの内容を取得&lt;br /&gt;
                response.choices.forEach(function (choice) {&lt;br /&gt;
                    if (choice.message &amp;amp;&amp;amp; choice.message.content) {&lt;br /&gt;
                        responseMessage += choice.message.content;&lt;br /&gt;
                    }&lt;br /&gt;
                });&lt;br /&gt;
                // トークン数を取得&lt;br /&gt;
                var usage = response.usage;&lt;br /&gt;
                token += &amp;quot;(使用トークン数: &amp;quot;&lt;br /&gt;
                    + &amp;quot;プロンプト=&amp;quot; + usage.prompt_tokens&lt;br /&gt;
                    + &amp;quot;, 応答=&amp;quot; + usage.completion_tokens&lt;br /&gt;
                    + &amp;quot;, 合計=&amp;quot; + usage.total_tokens&lt;br /&gt;
                    + &amp;quot;)&amp;quot;;&lt;br /&gt;
            }&lt;br /&gt;
        } else {&lt;br /&gt;
            var responseMessage = &amp;quot;エラーが発生しました: &amp;quot; + http.status + &amp;quot; &amp;quot; + http.statusText&lt;br /&gt;
            http.abort();&lt;br /&gt;
            return responseMessage;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        // 結果を会話履歴に追加&lt;br /&gt;
        if (HISTORY_SIZE &amp;gt; 0) {&lt;br /&gt;
            appendReturnTextToTag(promptText, responseMessage);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        // 整形しつつ返す&lt;br /&gt;
        return [responseMessage.trim(), token];&lt;br /&gt;
    } catch (e) {&lt;br /&gt;
        console.log(e.stack);&lt;br /&gt;
        return [&amp;quot;エラーが発生しました: &amp;quot; + e.message, &amp;quot;&amp;quot;];&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// document.Tag の内容を加味してGPTのリクエストを作成&lt;br /&gt;
function createGPTRequest(promptText) {&lt;br /&gt;
    var tag = document.Tag;&lt;br /&gt;
    // 以前の会話が存在しない場合&lt;br /&gt;
    if (!tag.exists(&amp;quot;gpt-text&amp;quot;)) {&lt;br /&gt;
        return JSON.stringify([{ &amp;quot;role&amp;quot;: &amp;quot;user&amp;quot;, &amp;quot;content&amp;quot;: promptText }]);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    var gptText = JSON.parse(tag(&amp;quot;gpt-text&amp;quot;));&lt;br /&gt;
    gptText.push({&lt;br /&gt;
        &amp;quot;role&amp;quot;: &amp;quot;user&amp;quot;,&lt;br /&gt;
        &amp;quot;content&amp;quot;: promptText&lt;br /&gt;
    });&lt;br /&gt;
    return JSON.stringify(gptText);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// document.Tag にGPTの応答を追加&lt;br /&gt;
function appendReturnTextToTag(userText, modelText) {&lt;br /&gt;
    var tag = document.Tag;&lt;br /&gt;
    if (!tag.exists(&amp;quot;gpt-text&amp;quot;)) {&lt;br /&gt;
        tag(&amp;quot;gpt-text&amp;quot;) = JSON.stringify([&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;role&amp;quot;: &amp;quot;user&amp;quot;,&lt;br /&gt;
                &amp;quot;content&amp;quot;: userText&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;role&amp;quot;: &amp;quot;assistant&amp;quot;,&lt;br /&gt;
                &amp;quot;content&amp;quot;: modelText&lt;br /&gt;
            }&lt;br /&gt;
        ]);&lt;br /&gt;
    } else {&lt;br /&gt;
        var gptText = JSON.parse(tag(&amp;quot;gpt-text&amp;quot;));&lt;br /&gt;
        gptText.push({&lt;br /&gt;
            &amp;quot;role&amp;quot;: &amp;quot;user&amp;quot;,&lt;br /&gt;
            &amp;quot;content&amp;quot;: userText&lt;br /&gt;
        });&lt;br /&gt;
        gptText.push({&lt;br /&gt;
            &amp;quot;role&amp;quot;: &amp;quot;assistant&amp;quot;,&lt;br /&gt;
            &amp;quot;content&amp;quot;: modelText&lt;br /&gt;
        });&lt;br /&gt;
        // 会話セット (user, model 1往復) が特定セット数を超えた場合、最初のセットを削除&lt;br /&gt;
        while (gptText.length &amp;gt; HISTORY_SIZE * 2) {&lt;br /&gt;
            gptText.shift();&lt;br /&gt;
        }&lt;br /&gt;
        tag(&amp;quot;gpt-text&amp;quot;) = JSON.stringify(gptText);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function resetTag() {&lt;br /&gt;
    if (document.Tag.exists(&amp;quot;gpt-text&amp;quot;)) {&lt;br /&gt;
        document.Tag.remove(&amp;quot;gpt-text&amp;quot;);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function inputProgressChar(pos) {&lt;br /&gt;
    doc.ReadOnly = false;&lt;br /&gt;
    sel.SetActivePos(pos);&lt;br /&gt;
    sel.Text = &amp;quot;.&amp;quot;;&lt;br /&gt;
    doc.ReadOnly = true;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 更新履歴 ==&lt;br /&gt;
&lt;br /&gt;
* 2026-07-18 起動時間短縮のため、マクロ起動とAPI呼び出しで、マクロを分割した。その他プロンプトテンプレート更新等。&lt;br /&gt;
* 2025-02-16 カスタムインストラクションにハルシネーション抑制の指示を加えた。プロンプトに渡す文字数制限方式を調整。&lt;br /&gt;
* 2024-12-22 V8エンジン版を追加。それに伴い従来エンジン版はアーカイブ化。&lt;br /&gt;
* 2024-12-18 初版&lt;/div&gt;</summary>
		<author><name>Yuko</name></author>
	</entry>
	<entry>
		<id>https://www.haijin-boys.com/wiki/index.php?title=OpenAI_GPT_%E3%81%AB%E7%9B%B8%E8%AB%87&amp;diff=9602</id>
		<title>OpenAI GPT に相談</title>
		<link rel="alternate" type="text/html" href="https://www.haijin-boys.com/wiki/index.php?title=OpenAI_GPT_%E3%81%AB%E7%9B%B8%E8%AB%87&amp;diff=9602"/>
		<updated>2026-07-18T13:01:57Z</updated>

		<summary type="html">&lt;p&gt;Yuko: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== 概要 ==&lt;br /&gt;
&lt;br /&gt;
OpenAIの生成AIである「GPT」を利用するためのMeryマクロです。&lt;br /&gt;
&lt;br /&gt;
後述のソースコードではV8エンジンで動作するものと従来版スクリプトエンジンで動作するものの2種類を掲載していますが、従来版のものはアーカイブです。特段の理由がない限りはV8エンジン版をご利用ください。&lt;br /&gt;
&lt;br /&gt;
【主な機能】&lt;br /&gt;
&lt;br /&gt;
* OpenAI APIを利用して生成AIと対話できるMery用のマクロ&lt;br /&gt;
* 選択したテキストやアクティブな行の内容について生成AIに質問・相談が可能&lt;br /&gt;
* 会話履歴を保持し、文脈を考慮した対話が可能&lt;br /&gt;
&lt;br /&gt;
【特徴的な機能】&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;会話履歴機能&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* 事前の設定数に応じた会話履歴を保持&lt;br /&gt;
* 履歴のクリアが可能&lt;br /&gt;
&lt;br /&gt;
これにより、会話の文脈を考慮した回答を得られます。&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;カスタムインストラクション&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* AIの応答方針を設定可能&lt;br /&gt;
* 一時的なカスタム指示の保存と変更が可能&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;プロンプトテンプレート&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* 誤字脱字チェック&lt;br /&gt;
* 日本語翻訳&lt;br /&gt;
* コードの最適化、生成&lt;br /&gt;
&lt;br /&gt;
など、定型的な質問を簡単に実行できます。&lt;br /&gt;
※テンプレート機能では回答を安定させるため、前述の会話履歴を文脈に含みません。&lt;br /&gt;
&lt;br /&gt;
テンプレートは自身の使い方に合わせて好きに追加、削除ができます。詳細はソースコード内の「PROMPT_TEMPLATE」定数のコメントを参照。&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;生成AIチャット風仕様&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* ストリーミングモードでリアルタイムに1文字ずつMeryに入力されていく&lt;br /&gt;
* Shiftキー長押しで、テキスト生成を中断できる&lt;br /&gt;
&lt;br /&gt;
== 注意事項 (必ず読んでからご利用ください) ==&lt;br /&gt;
&lt;br /&gt;
* 本マクロではOpenAI APIを利用します。API利用に伴う料金発生やOpenAI社の情報取り扱い等によるユーザー不利益が発生しても、作者は一切の責任を負いません。&lt;br /&gt;
** &#039;&#039;&#039;OpenAIでの支払いはチャージ制なので、あらかじめチャージした金額内での利用になります。オートチャージの設定もありますが、慣れていないうちはオートチャージOFFでの利用を推奨します。また、誤って高額の課金にならないように上限を設けることもできますので、併せて上限設定の利用を強くおすすめします。&#039;&#039;&#039;&lt;br /&gt;
** 2024/12/18 時点、本マクロでは「gpt-4o-mini」モデルを利用しており、費用は従量課金ですが「$0.150/100万入力トークン、 $0.600/100万出力トークン」と低価格です。目安として、日本語1文字 1～2 トークン程で計上されますが、よほど大量の文字の入出力を繰り返さない限りは月間数ドルで利用できると思われます。&lt;br /&gt;
* OpenAI APIでは受け取ったリクエストを学習に使わないと宣言していますが、くれぐれも機密情報やプライバシーに関わる情報を指定しないように注意してください。&lt;br /&gt;
* OpenAI APIは、 [https://openai.com/ja-JP/policies/terms-of-use/ OpenAI利用規約] に則ってご利用ください。&lt;br /&gt;
&lt;br /&gt;
== 利用前提 ==&lt;br /&gt;
&lt;br /&gt;
* Mery Ver 3.7.8 以上&lt;br /&gt;
* [https://platform.openai.com/ OpenAI Platform] で、OpenAI APIキーを取得していること&lt;br /&gt;
* OpenAI APIを利用できるだけの課金残高があること&lt;br /&gt;
&lt;br /&gt;
== 利用方法 ==&lt;br /&gt;
&lt;br /&gt;
* 環境変数の設定&lt;br /&gt;
:コマンド プロンプトで以下のコマンドを実行し、環境変数を設定してください。&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bat&amp;quot; copy&amp;gt;&lt;br /&gt;
setx OPENAI_API_KEY &amp;quot;***************************************&amp;quot;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
:設定後、Mery または Windows の再起動が必要な場合があります。&lt;br /&gt;
* マクロの実行&lt;br /&gt;
** Meryのマクロフォルダー「Macros」に、後述の「v8_boot_gpt.js」「v8_call_gpt.js」をそれぞれ配置し、「v8_boot_gpt.js」をMeryへ読み込ませてください (「v8_call_gpt.js」は配置するだけでOK)。&lt;br /&gt;
**プロンプトにしたい文字列を選択してマクロを実行すると、選択したテキストがプロンプトとしてOpenAI APIに送信され、応答がエディタに表示されます。&lt;br /&gt;
* 会話の履歴数を調整&lt;br /&gt;
** 会話の履歴は設定した個数分が保持され、文脈として利用されます。適宜、ソースコード内の「HISTORY_SIZE」の数を調整してください。&lt;br /&gt;
** ※数を増やすほど会話内容を保持して文脈を理解するようになりますが、メモリ使用量が増えたり動作が重くなる可能性があります。&lt;br /&gt;
** ※開いている文書ごとに履歴を保持します。文脈をリセットしたいときは、新しい文書を開いて会話をやり直してください。&lt;br /&gt;
&lt;br /&gt;
== ソースコード ==&lt;br /&gt;
=== V8エンジン版 ===&lt;br /&gt;
&lt;br /&gt;
==== v8_boot_gpt.js ====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot; copy&amp;gt;&lt;br /&gt;
#language = &amp;quot;quickjs&amp;quot;&lt;br /&gt;
#async = true&lt;br /&gt;
#title = &amp;quot;GPT に相談 (v8)&amp;quot;&lt;br /&gt;
BeginUndoGroup();&lt;br /&gt;
&lt;br /&gt;
// 各種設定値&lt;br /&gt;
&lt;br /&gt;
// カスタムインストラクション&lt;br /&gt;
// これを設定することで、生成AIの回答全体への生成方針を指示できます。&lt;br /&gt;
// 例えば &amp;quot;あなたは侍で名前は権兵衛です。回答をござる口調にしてください&amp;quot; と設定すると、AIの回答がござる口調になります。&lt;br /&gt;
// 何も指示しない場合は、空文字 &amp;quot;&amp;quot; を設定してください。&lt;br /&gt;
let DEFAULT_CUSTOM_INSTRUCTION = `&lt;br /&gt;
文:&lt;br /&gt;
- 論理的で読みやすい文章構成にする&lt;br /&gt;
- 日本語として自然で一般的な表現を用いる&lt;br /&gt;
- 専門用語は必要に応じて注釈を加える&lt;br /&gt;
&lt;br /&gt;
口調:&lt;br /&gt;
- フレンドリーで愛想の良い敬語で話す。「…ですよね/…してみましょう/…かもしれません/…だと思います」などの柔らかい語調を使ってもよい&lt;br /&gt;
- 一人称は「私」&lt;br /&gt;
&lt;br /&gt;
性格:&lt;br /&gt;
- 多角的かつ中立的な見方で、必要であれば反対意見や代替案も示す&lt;br /&gt;
&lt;br /&gt;
誤字注意:&lt;br /&gt;
- 中国語や韓国語など、回答言語と異なる言語文字を入れないこと&lt;br /&gt;
&lt;br /&gt;
出力要件:&lt;br /&gt;
- Markdownとして再利用可能な正確な形式で出力する&lt;br /&gt;
- 羅列・比較的な内容を出力する場合は、Markdownテーブル形式よりもMarkdownリスト形式の使用を優先する&lt;br /&gt;
`.trim();&lt;br /&gt;
&lt;br /&gt;
// プロンプトテンプレート&lt;br /&gt;
// プロンプトテンプレートを設定することで、AIに対して特定の指示を出すことができます。&lt;br /&gt;
// プロンプトテンプレートでの生成は会話履歴とカスタムインストラクションを反映しません。&lt;br /&gt;
// テンプレートは以下の { ～ }, で囲まれたブロックを好きに増減させて利用できます。&lt;br /&gt;
const PROMPT_TEMPLATE = [&lt;br /&gt;
&lt;br /&gt;
    // 以下はサンプルです。1階層メニューと2階層メニューが利用できます。&lt;br /&gt;
    // {&lt;br /&gt;
    //     &amp;quot;title&amp;quot;: &amp;quot;要約&amp;quot;,&lt;br /&gt;
    //     &amp;quot;prompt&amp;quot;: &amp;quot;以下の文章を要約してください。&amp;quot;&lt;br /&gt;
    // },&lt;br /&gt;
    // {&lt;br /&gt;
    //     &amp;quot;submenu_title&amp;quot;: &amp;quot;翻訳系&amp;quot;,&lt;br /&gt;
    //     &amp;quot;submenu&amp;quot;: [&lt;br /&gt;
    //         {&lt;br /&gt;
    //             &amp;quot;title&amp;quot;: &amp;quot;日本語翻訳&amp;quot;,&lt;br /&gt;
    //             &amp;quot;prompt&amp;quot;: &amp;quot;以下の文章を日本語に翻訳してください。&amp;quot;&lt;br /&gt;
    //         },&lt;br /&gt;
    //         {&lt;br /&gt;
    //             &amp;quot;title&amp;quot;: &amp;quot;英語翻訳&amp;quot;,&lt;br /&gt;
    //             &amp;quot;prompt&amp;quot;: &amp;quot;以下の文章を英語に翻訳してください。&amp;quot;&lt;br /&gt;
    //         },&lt;br /&gt;
    //     ]&lt;br /&gt;
    // },&lt;br /&gt;
&lt;br /&gt;
    {&lt;br /&gt;
        &amp;quot;submenu_title&amp;quot;: &amp;quot;理解を深める&amp;quot;,&lt;br /&gt;
        &amp;quot;submenu&amp;quot;: [&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;論点を整理する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の論点を整理してまとめて。そしてその整理から示唆を見出して。&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;論理の抜け漏れを探す&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;あなたは「悪魔の代弁者」として、以下の主張・設計・計画を批判的に鋭く見てください。論理の穴・抜け・漏れ・飛躍、リスクなどを全力で探して指摘してください。遠慮は不要ですが、批判のためにこじつけたような論理性に欠けた主張にならないように注意してください。&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;要約する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;分かりやすさを重視して要約して。専門的な用語には注釈を入れること。&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;物語対話形式で解説&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の内容について物語対話形式で解説して。登場人物は「AI先生」(女性人格)と「生徒」(男の子人格)とする。&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
        ]&lt;br /&gt;
    },&lt;br /&gt;
    {&lt;br /&gt;
        &amp;quot;submenu_title&amp;quot;: &amp;quot;翻訳&amp;quot;,&lt;br /&gt;
        &amp;quot;submenu&amp;quot;: [&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;日本語に翻訳&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: `以下の入力文章を日本語に翻訳して。直訳的にせず、自然な表現にすること。入力文章はどのような内容・段落構造であっても要約・除外・集約などの整形を行わず、全体構造を維持したまま確実に全文を翻訳すること。&lt;br /&gt;
&lt;br /&gt;
出力形式は以下とすること:&lt;br /&gt;
[訳]&lt;br /&gt;
翻訳文...`&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;英語に翻訳&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: `以下の入力文章を英語に翻訳して。フォーマルとカジュアルの中間的なニュアンスで翻訳すること。入力文章はどのような内容・段落構造であっても要約・除外・集約などの整形を行わず、全体構造を維持したまま確実に全文を翻訳すること&lt;br /&gt;
&lt;br /&gt;
出力形式は以下とすること:&lt;br /&gt;
[Translation]&lt;br /&gt;
翻訳文...`&lt;br /&gt;
            },&lt;br /&gt;
        ]&lt;br /&gt;
    },&lt;br /&gt;
    {&lt;br /&gt;
        &amp;quot;submenu_title&amp;quot;: &amp;quot;語学&amp;quot;,&lt;br /&gt;
        &amp;quot;submenu&amp;quot;: [&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;英語 文法解説&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: `あなたは母国語が英語の英語教師です。&lt;br /&gt;
次の英文を以下の構成で解説してください。&lt;br /&gt;
&lt;br /&gt;
構成:&lt;br /&gt;
(1) 日本語翻訳&lt;br /&gt;
(2) スラッシュリーディング + 各句の文型（S/V/O/C）と語訳&lt;br /&gt;
(3) 重要文法&lt;br /&gt;
(4) 補足&lt;br /&gt;
&lt;br /&gt;
注意:&lt;br /&gt;
- 補足では日本語話者には分かりづらい表現や慣例表現などの解説を加える&lt;br /&gt;
- スラッシュリーディングで区切る際「M (修飾語)」が連なって長くなりすぎる場合には5～7語程度を目安に分ける&lt;br /&gt;
- 「M」の種類として形容詞、副詞、形容詞句、副詞句、分詞句、形容詞節、副詞節がある&lt;br /&gt;
&lt;br /&gt;
(2) の出力例:&lt;br /&gt;
They / elected / him / president / in 2020.&lt;br /&gt;
- They: (S) 彼らは&lt;br /&gt;
- elected: (V) 選出した&lt;br /&gt;
- him: (O) 彼を&lt;br /&gt;
- president: (C) 大統領&lt;br /&gt;
- in 2020: (M: 副詞句) 2020年に&lt;br /&gt;
`&lt;br /&gt;
&lt;br /&gt;
            },&lt;br /&gt;
        ]&lt;br /&gt;
    },&lt;br /&gt;
    {&lt;br /&gt;
        &amp;quot;submenu_title&amp;quot;: &amp;quot;プログラミング支援&amp;quot;,&lt;br /&gt;
        &amp;quot;submenu&amp;quot;: [&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;コードを解説する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下のコードを初めて読む人も理解できるように解説してください。私には言語の基本理解はあるものとし、コードの意図を理解するための情報を中心に説明してください。&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;コードを洗練させる&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下のコードを洗練させてください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;コードをレビューする&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下のコードをセキュリティと保守性の観点でレビューしてください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;エラーログをデバッグする&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下のエラーログを解析し、考えられる原因と対策を提示してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;SQLクエリを最適化する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下のSQL文を最適化してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;PowerShell スクリプトを書く&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;あなたはエキスパートとしてメンテナンス性の高いコードを書くのが得意です。以下の処理を行うPowerShellスクリプトを生成してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;Shell Script を書く&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;あなたはエキスパートとしてメンテナンス性の高いコードを書くのが得意です。以下の処理を行うShell Scriptを生成してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
        ]&lt;br /&gt;
    },&lt;br /&gt;
    {&lt;br /&gt;
        &amp;quot;submenu_title&amp;quot;: &amp;quot;ビジネス文書支援&amp;quot;,&lt;br /&gt;
        &amp;quot;submenu&amp;quot;: [&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;音声文字起こし文を整形する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: `以下は、音声文字起こしの文章です。以下のルールに従って、文章を整形してください。&lt;br /&gt;
&lt;br /&gt;
整形ルール:&lt;br /&gt;
- 要約するなどといった文章の全体感を損なう修正はしない&lt;br /&gt;
- 会話の流れから不自然と思われる単語には、対象単語の直後に誤字の可能性を示唆する [*] を付ける&lt;br /&gt;
- 誤字の可能性を示唆する [*] が付与された場合のみ、出力の末尾に「[*]: 誤字の可能性ありと凡例を書き添える&lt;br /&gt;
- 助詞や句読点など文章上の不適合を正す&lt;br /&gt;
- フィラーワードを除外する`&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;誤字脱字、不正確な表現をチェック&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の2つのカテゴリーで問題点を指摘してください\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 【誤字脱字】：明確な誤字脱字および誤字の疑いがある箇所\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 【不正確・曖昧な表現】：解釈に個人差が生じる可能性がある表現、より適切な表現に改善できる箇所&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;提案書を作成する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下に対する対する提案書を、コスト・実現性・リスクを含めて作成してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;手順書のアウトラインを作成する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の作業を行う手順書を、前提条件と注意事項を含めてアウトラインを作成してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;ユーザーマニュアルのアウトラインを作成する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の機能内容から、ユーザー向けマニュアルのアウトラインを作成してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;議事録を作成する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の会議内容から議事録を作成してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
        ]&lt;br /&gt;
    },&lt;br /&gt;
    {&lt;br /&gt;
        &amp;quot;submenu_title&amp;quot;: &amp;quot;分析支援&amp;quot;,&lt;br /&gt;
        &amp;quot;submenu&amp;quot;: [&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;問題を分析し、課題を抽出する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の状況から問題や潜在的リスクを分析し、課題を抽出してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;対応策を提案する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の問題・課題に対する対応策を複数提案してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;傾向を見つける&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の内容から、数値や挙動上の傾向を見つけてください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
        ]&lt;br /&gt;
    },&lt;br /&gt;
    {&lt;br /&gt;
        &amp;quot;submenu_title&amp;quot;: &amp;quot;アイデア出し&amp;quot;,&lt;br /&gt;
        &amp;quot;submenu&amp;quot;: [&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;SCAMPER法でアイデア出し&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下のテーマについて、SCAMPER法を使って15個のアイデアを出してください。&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;解決策の案出し&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の課題に対して、技術面、運用面、コスト面から実現可能な解決策を5つずつ提案してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;逆転の発想&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の目標に対して、通常とは逆の視点からアプローチする方法を3つ提案してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;競合分析からの着想&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の内容について、業界における競合他社の特徴を分析し、差別化できる新しいアプローチを提案してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;ユースケースの列挙&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の製品・サービスについて、想定されるユースケースを10個列挙し、それぞれの価値提案を記載してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
        ]&lt;br /&gt;
    },&lt;br /&gt;
&lt;br /&gt;
]&lt;br /&gt;
&lt;br /&gt;
// コンテキストメニュー番号の定義&lt;br /&gt;
const EXECUTE_AI = 1;&lt;br /&gt;
const RESET_HISTORY = 2;&lt;br /&gt;
const SET_CUSTOM_INSTRUCTION = 3;&lt;br /&gt;
const REEXECUTE_LAST_TEMPLATE_PROMPT = 4;&lt;br /&gt;
const ROMAN_ALPHABET_IME = 5;&lt;br /&gt;
const INVALID_MENU = 9;&lt;br /&gt;
const TEMPLATE_PROMPT_THRESHOLD = 10;&lt;br /&gt;
const TEMPLATE_PROMPT_SUBMENU_THRESHOLD = 1000;&lt;br /&gt;
&lt;br /&gt;
// タグ名の定義&lt;br /&gt;
const HISTORY_TAG = &amp;quot;gpt-history&amp;quot;;&lt;br /&gt;
const HISTORY_SIZE_TAG = &amp;quot;gpt-history-size&amp;quot;;&lt;br /&gt;
const TEMPORARY_CUSTOM_INSTRUCTION_TAG = &amp;quot;gpt-temporary-custom-instruction&amp;quot;;&lt;br /&gt;
const PREVIOUS_TEMPLATE_PROMPT_TAG = &amp;quot;gpt-previous-template-prompt&amp;quot;;&lt;br /&gt;
const SELECTED_TEMPLATE_PROMPT_TAG = &amp;quot;gpt-selected-template-prompt&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
// 会話の履歴を保持する数 (質問と回答で1セット)&lt;br /&gt;
// 数を増やすと、会話の履歴が増えますが、Meryのメモリ使用量が増えたり、&lt;br /&gt;
// 使用トークン数が増えて動作が重くなる可能性があるため、適宜調整してください。&lt;br /&gt;
// document.Tag に保持されるため、開いているファイルを閉じると履歴は消えます。&lt;br /&gt;
const HISTORY_SIZE = 5;&lt;br /&gt;
document.tag[HISTORY_SIZE_TAG] = HISTORY_SIZE;&lt;br /&gt;
&lt;br /&gt;
// console.log に出力する内容を Mery のアウトプットバーに表示する&lt;br /&gt;
var console = {&lt;br /&gt;
    log: function (str) {&lt;br /&gt;
        outputBar.Writeln(str)&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
var doc = document;&lt;br /&gt;
var sel = document.selection;&lt;br /&gt;
&lt;br /&gt;
main();&lt;br /&gt;
&lt;br /&gt;
function main() {&lt;br /&gt;
    var selectionIsEmpty = sel.IsEmpty&lt;br /&gt;
&lt;br /&gt;
    // プロンプトの取得&lt;br /&gt;
    if (selectionIsEmpty) {&lt;br /&gt;
        sel.StartOfLine(false, mePosLogical);&lt;br /&gt;
        sel.EndOfLine(true, mePosLogical);&lt;br /&gt;
    }&lt;br /&gt;
    var menuTrack = 0&lt;br /&gt;
    while (true) {&lt;br /&gt;
        // コンテキストメニューの作成&lt;br /&gt;
        var menu = createContextMenu(selectionIsEmpty);&lt;br /&gt;
        menuTrack = menu.Track(0);&lt;br /&gt;
        // メニュー選択がキャンセルされた場合は終了&lt;br /&gt;
        if (menuTrack === 0) return;&lt;br /&gt;
&lt;br /&gt;
        // 会話履歴のリセット&lt;br /&gt;
        if (menuTrack === RESET_HISTORY) {&lt;br /&gt;
            resetTag();&lt;br /&gt;
            continue;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        // カスタムインストラクションの設定&lt;br /&gt;
        if (menuTrack === SET_CUSTOM_INSTRUCTION) {&lt;br /&gt;
            configureTemporaryCustomInstruction();&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        break;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // 読み取り専用の場合はエラー&lt;br /&gt;
    if (doc.ReadOnly) {&lt;br /&gt;
        alert(&amp;quot;ファイルが読み取り専用のため実行できません。&amp;quot;);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // 直前に実行したプロンプトテンプレートの再実行の場合、menuTrack を書き換え&lt;br /&gt;
    if (menuTrack === REEXECUTE_LAST_TEMPLATE_PROMPT) {&lt;br /&gt;
        if (doc.Tag.exists(PREVIOUS_TEMPLATE_PROMPT_TAG)) {&lt;br /&gt;
            menuTrack = Number(doc.tag[PREVIOUS_TEMPLATE_PROMPT_TAG]);&lt;br /&gt;
        } else {&lt;br /&gt;
            alert(&amp;quot;直前に実行したプロンプトテンプレートがありません。&amp;quot;);&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // テンプレートを取得&lt;br /&gt;
    var isTemplate = menuTrack &amp;gt;= TEMPLATE_PROMPT_THRESHOLD;&lt;br /&gt;
    var isSubmenu = menuTrack &amp;gt;= TEMPLATE_PROMPT_SUBMENU_THRESHOLD;&lt;br /&gt;
    if (isSubmenu) {&lt;br /&gt;
        var parentMenuTrack = Number(String(menuTrack).slice(0, 2));&lt;br /&gt;
        var subMenuTrack = Number(String(menuTrack).slice(2));&lt;br /&gt;
        var templatePrompt = PROMPT_TEMPLATE[parentMenuTrack - TEMPLATE_PROMPT_THRESHOLD].submenu[subMenuTrack].prompt;&lt;br /&gt;
    } else if (isTemplate) {&lt;br /&gt;
        var templatePrompt = PROMPT_TEMPLATE[menuTrack - TEMPLATE_PROMPT_THRESHOLD].prompt;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // テンプレートの場合は直前に実行したプロンプトテンプレートの番号としてタグに保存&lt;br /&gt;
    if (isTemplate) {&lt;br /&gt;
        doc.tag[SELECTED_TEMPLATE_PROMPT_TAG] = templatePrompt;&lt;br /&gt;
        doc.tag[PREVIOUS_TEMPLATE_PROMPT_TAG] = menuTrack&lt;br /&gt;
    } else {&lt;br /&gt;
        // テンプレート以外の場合はタグを削除&lt;br /&gt;
        if (doc.Tag.exists(SELECTED_TEMPLATE_PROMPT_TAG)) {&lt;br /&gt;
            doc.Tag.remove(SELECTED_TEMPLATE_PROMPT_TAG);&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // ローマ字⇒日本語変換のプロンプトをセット&lt;br /&gt;
    const isRomanAlphabetIme = menuTrack === ROMAN_ALPHABET_IME;&lt;br /&gt;
    if (isRomanAlphabetIme) {&lt;br /&gt;
        doc.tag[SELECTED_TEMPLATE_PROMPT_TAG] = `以下のローマ字で入力された文章を、日本語に変換してください。&lt;br /&gt;
制約:&lt;br /&gt;
- 原則、入力のままの用語・口調で変換&lt;br /&gt;
- 誤字脱字は修正してよい&lt;br /&gt;
- 適宜、句読点を入れて読みやすくしてよい&lt;br /&gt;
- 返事は不要で、書き直した文章のみを出力`;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // gpt API 呼び出し部品を実行&lt;br /&gt;
    // V8版で fetch メソッドを使うために別呼び出しとする&lt;br /&gt;
    doc.tag[TEMPORARY_CUSTOM_INSTRUCTION_TAG] = getTemporaryCustomInstruction() ? getTemporaryCustomInstruction() : DEFAULT_CUSTOM_INSTRUCTION;&lt;br /&gt;
    ShowTip(&amp;quot;リクエスト中.....&amp;quot;, meShowTipPosCaret);&lt;br /&gt;
    editor.ExecuteMacro(&amp;quot;v8_call_gpt.js&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function createContextMenu(selectionIsEmpty) {&lt;br /&gt;
    const menu = CreatePopupMenu();&lt;br /&gt;
    menu.Add(&amp;quot;※注意※ プライバシー・機密情報を送信しないこと&amp;quot;, INVALID_MENU);&lt;br /&gt;
    menu.Add(&amp;quot;&amp;quot;, 0, meMenuSeparator);&lt;br /&gt;
    let t = &amp;quot;&amp;quot;;&lt;br /&gt;
    if (selectionIsEmpty) {&lt;br /&gt;
        t = &amp;quot;アクティブ行の内容で相談 (&amp;amp;A)&amp;quot;;&lt;br /&gt;
    } else {&lt;br /&gt;
        t = &amp;quot;選択範囲の内容で相談 (&amp;amp;A)&amp;quot;;&lt;br /&gt;
    }&lt;br /&gt;
    menu.Add(t, EXECUTE_AI);&lt;br /&gt;
    if (HISTORY_SIZE &amp;lt;= 0) {&lt;br /&gt;
        menu.Add(&amp;quot;├ 会話履歴をクリア (履歴が無効になっています)&amp;quot;, RESET_HISTORY, meMenuGrayed);&lt;br /&gt;
    } else if (doc.Tag.exists(HISTORY_TAG)) {&lt;br /&gt;
        var historyData = JSON.parse(doc.tag[HISTORY_TAG]);&lt;br /&gt;
        var messages;&lt;br /&gt;
        if (Array.isArray(historyData)) {&lt;br /&gt;
            // 旧形式: 配列が直接 messages&lt;br /&gt;
            messages = historyData;&lt;br /&gt;
        } else {&lt;br /&gt;
            // 新形式: { session_id, messages }&lt;br /&gt;
            messages = historyData.messages || [];&lt;br /&gt;
        }&lt;br /&gt;
        var turn = (messages.length) / 2;&lt;br /&gt;
        menu.Add(&amp;quot;├ 会話履歴をクリア (現在の会話数: &amp;quot; + turn + &amp;quot;) (&amp;amp;C)&amp;quot;, RESET_HISTORY);&lt;br /&gt;
    } else {&lt;br /&gt;
        menu.Add(&amp;quot;├ 会話履歴をクリア (現在の会話数: 0) (&amp;amp;C)&amp;quot;, RESET_HISTORY, meMenuGrayed);&lt;br /&gt;
    }&lt;br /&gt;
    menu.Add(&amp;quot;└ カスタムインストラクションを変更 (&amp;amp;I)&amp;quot;, SET_CUSTOM_INSTRUCTION);&lt;br /&gt;
&lt;br /&gt;
    menu.Add(&amp;quot;ローマ字⇒日本語 変換 (&amp;amp;R)&amp;quot;, ROMAN_ALPHABET_IME);&lt;br /&gt;
&lt;br /&gt;
    // テンプレート数チェック&lt;br /&gt;
    // 2桁まで許容&lt;br /&gt;
    const ITEMS_MUX = 99;&lt;br /&gt;
    if (PROMPT_TEMPLATE.length &amp;gt; ITEMS_MUX - TEMPLATE_PROMPT_THRESHOLD) {&lt;br /&gt;
        throw new Error(&amp;quot;プロンプトテンプレートの数が多すぎます。&amp;quot;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // テンプレートメニューの追加&lt;br /&gt;
    if (PROMPT_TEMPLATE.length &amp;gt; 0) {&lt;br /&gt;
        const subMenu = CreatePopupMenu();&lt;br /&gt;
        let previousMenuString = &amp;quot;&amp;quot;;&lt;br /&gt;
        for (let i = 0; i &amp;lt; PROMPT_TEMPLATE.length; i++) {&lt;br /&gt;
            let accelerationKey = &amp;quot;&amp;quot;;&lt;br /&gt;
            if (i === 9) {&lt;br /&gt;
                accelerationKey = &amp;quot; (&amp;amp;0)&amp;quot;;&lt;br /&gt;
            } else if (i &amp;lt; 10) {&lt;br /&gt;
                accelerationKey = &amp;quot; (&amp;amp;&amp;quot; + (i + 1) + &amp;quot;)&amp;quot;;&lt;br /&gt;
            }&lt;br /&gt;
            const menuNum = TEMPLATE_PROMPT_THRESHOLD + i;&lt;br /&gt;
            if (PROMPT_TEMPLATE[i].submenu) {&lt;br /&gt;
                const subMenuList = PROMPT_TEMPLATE[i].submenu;&lt;br /&gt;
                // テンプレート数チェック&lt;br /&gt;
                if (subMenuList.length &amp;gt; ITEMS_MUX) {&lt;br /&gt;
                    throw new Error(&amp;quot;プロンプトテンプレート (サブメニュー) の数が多すぎます。&amp;quot;);&lt;br /&gt;
                }&lt;br /&gt;
                const subSubMenu = CreatePopupMenu();&lt;br /&gt;
                const subMenuPrefix = String(menuNum);&lt;br /&gt;
                for (let j = 0; j &amp;lt; subMenuList.length; j++) {&lt;br /&gt;
                    let subAccelerationKey = &amp;quot;&amp;quot;;&lt;br /&gt;
                    if (j === 9) {&lt;br /&gt;
                        subAccelerationKey = &amp;quot; (&amp;amp;0)&amp;quot;;&lt;br /&gt;
                    } else if (j &amp;lt; 10) {&lt;br /&gt;
                        subAccelerationKey = &amp;quot; (&amp;amp;&amp;quot; + (j + 1) + &amp;quot;)&amp;quot;;&lt;br /&gt;
                    }&lt;br /&gt;
                    // ES5 compatible version without padStart&lt;br /&gt;
                    const subMenuNum = Number(subMenuPrefix + (j &amp;lt; 10 ? &amp;quot;0&amp;quot; + j : String(j)));&lt;br /&gt;
                    subSubMenu.Add(subMenuList[j].title + subAccelerationKey, subMenuNum);&lt;br /&gt;
                    if (subMenuNum === doc.tag[PREVIOUS_TEMPLATE_PROMPT_TAG]) {&lt;br /&gt;
                        previousMenuString = subMenuList[j].title;&lt;br /&gt;
                    }&lt;br /&gt;
                }&lt;br /&gt;
                subMenu.AddPopup(PROMPT_TEMPLATE[i].submenu_title + accelerationKey, subSubMenu);&lt;br /&gt;
            } else {&lt;br /&gt;
                subMenu.Add(PROMPT_TEMPLATE[i].title + accelerationKey, menuNum);&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        if (selectionIsEmpty) {&lt;br /&gt;
            t = &amp;quot;プロンプト テンプレート (対象: アクティブ行) (&amp;amp;T)&amp;quot;;&lt;br /&gt;
        } else {&lt;br /&gt;
            t = &amp;quot;プロンプト テンプレート (対象: 選択範囲) (&amp;amp;T)&amp;quot;;&lt;br /&gt;
        }&lt;br /&gt;
        menu.AddPopup(t, subMenu);&lt;br /&gt;
&lt;br /&gt;
        t = &amp;quot;└ 直前に実行したプロンプト テンプレートを再実行 (&amp;amp;R)&amp;quot;;&lt;br /&gt;
        if (doc.Tag.exists(PREVIOUS_TEMPLATE_PROMPT_TAG)) {&lt;br /&gt;
            menu.Add(t, REEXECUTE_LAST_TEMPLATE_PROMPT);&lt;br /&gt;
            menu.Add(`    (直前実行: ${previousMenuString})`, REEXECUTE_LAST_TEMPLATE_PROMPT);&lt;br /&gt;
        } else {&lt;br /&gt;
            menu.Add(t, REEXECUTE_LAST_TEMPLATE_PROMPT, meMenuGrayed);&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return menu;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function configureTemporaryCustomInstruction() {&lt;br /&gt;
    let tempCustomInstruction = getTemporaryCustomInstruction();&lt;br /&gt;
    const prompt = Prompt(&amp;quot;カスタムインストラクションを変更 ※変更すると会話履歴がリセットされます&amp;quot;, tempCustomInstruction ? tempCustomInstruction : DEFAULT_CUSTOM_INSTRUCTION);&lt;br /&gt;
    if (prompt !== null) {&lt;br /&gt;
        if (prompt === DEFAULT_CUSTOM_INSTRUCTION) {&lt;br /&gt;
            // デフォルトと同じ値の場合はタグを削除&lt;br /&gt;
            setTemporaryCustomInstruction(&amp;quot;&amp;quot;);&lt;br /&gt;
        } else if (prompt === &amp;quot;&amp;quot; &amp;amp;&amp;amp; Confirm(&amp;quot;カスタムインストラクションをデフォルトに戻しますか？&amp;quot;)) {&lt;br /&gt;
            setTemporaryCustomInstruction(&amp;quot;&amp;quot;);&lt;br /&gt;
        } else if (prompt !== tempCustomInstruction) {&lt;br /&gt;
            // デフォルト以外の値が設定された場合は保存&lt;br /&gt;
            setTemporaryCustomInstruction(prompt);&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // 変更が加わっている場合は会話履歴をリセット&lt;br /&gt;
    if (tempCustomInstruction !== getTemporaryCustomInstruction()) {&lt;br /&gt;
        resetTag();&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function resetTag() {&lt;br /&gt;
    const tag = doc.Tag;&lt;br /&gt;
    if (tag.exists(HISTORY_TAG)) {&lt;br /&gt;
        tag.remove(HISTORY_TAG);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getTemporaryCustomInstruction() {&lt;br /&gt;
    var tag = doc.Tag;&lt;br /&gt;
    if (tag.exists(TEMPORARY_CUSTOM_INSTRUCTION_TAG)) {&lt;br /&gt;
        return tag[TEMPORARY_CUSTOM_INSTRUCTION_TAG];&lt;br /&gt;
    } else {&lt;br /&gt;
        return &amp;quot;&amp;quot;;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function setTemporaryCustomInstruction(temporaryCustomInstruction) {&lt;br /&gt;
    var tag = doc.Tag;&lt;br /&gt;
    if (temporaryCustomInstruction) {&lt;br /&gt;
        tag[TEMPORARY_CUSTOM_INSTRUCTION_TAG] = temporaryCustomInstruction;&lt;br /&gt;
    } else {&lt;br /&gt;
        tag.remove(TEMPORARY_CUSTOM_INSTRUCTION_TAG);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== v8_call_gpt.js ====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot; copy&amp;gt;&lt;br /&gt;
#language = &amp;quot;v8&amp;quot;&lt;br /&gt;
#title = &amp;quot;GPT に相談 (API 呼び出し部品)&amp;quot;&lt;br /&gt;
BeginUndoGroup();&lt;br /&gt;
&lt;br /&gt;
// 生成AIの回答の言語設定&lt;br /&gt;
// &amp;quot;Japanese&amp;quot; や &amp;quot;English&amp;quot; を設定できます。他の言語は未検証。&lt;br /&gt;
// 自動設定にする場合は空文字 &amp;quot;&amp;quot; を設定してください。&lt;br /&gt;
const LANGUAGE = &amp;quot;Japanese&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
// 入力プロンプトの最大トークン数&lt;br /&gt;
// 超適当なトークナイザー関数により計測し、英単語は4文字1トークン、それ以外は1文字1トークンとして数えます。&lt;br /&gt;
const MAX_PROMPT_TOKEN = 8000;&lt;br /&gt;
&lt;br /&gt;
// 会話履歴に保持する最大トークン数&lt;br /&gt;
// 会話履歴のトークン数がこの数を超える場合、古い履歴から削除されます。&lt;br /&gt;
const MAX_HISTORY_TOKEN = 50000;&lt;br /&gt;
&lt;br /&gt;
// 生成AI応答の入力毎の遅延時間 (ミリ秒)&lt;br /&gt;
// 入力をゆっくりにしたいときは数字を大きくしてください。&lt;br /&gt;
const INPUT_DELAY = 20;&lt;br /&gt;
&lt;br /&gt;
// タグ名の定義&lt;br /&gt;
const HISTORY_TAG = &amp;quot;gpt-history&amp;quot;;&lt;br /&gt;
const HISTORY_SIZE_TAG = &amp;quot;gpt-history-size&amp;quot;;&lt;br /&gt;
const TEMPORARY_CUSTOM_INSTRUCTION_TAG = &amp;quot;gpt-temporary-custom-instruction&amp;quot;;&lt;br /&gt;
const SELECTED_TEMPLATE_PROMPT_TAG = &amp;quot;gpt-selected-template-prompt&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
// 会話の履歴を保持する数 (質問と回答で1セット)&lt;br /&gt;
// boot マクロ側で設定されていることを前提としている&lt;br /&gt;
if (!document.tag.exists(HISTORY_SIZE_TAG)) {&lt;br /&gt;
    throw new Error(&amp;quot;会話履歴サイズが設定されていません。&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
const HISTORY_SIZE = document.tag[HISTORY_SIZE_TAG];&lt;br /&gt;
&lt;br /&gt;
// プロンプトキャッシュの照合に使う安定したキー&lt;br /&gt;
const PROMPT_CACHE_KEY = &amp;quot;mery-gpt-call-v1&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
// 生成AIのモデル名&lt;br /&gt;
&lt;br /&gt;
const MODEL_CONFIG = {&lt;br /&gt;
    model: &amp;quot;gpt-5.6-terra&amp;quot;,&lt;br /&gt;
    reasoning: { effort: &amp;quot;medium&amp;quot; },&lt;br /&gt;
    max_output_tokens: 4000,&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// console.log に出力する内容を Mery のアウトプットバーに表示する&lt;br /&gt;
const console = {&lt;br /&gt;
    log: function (str) {&lt;br /&gt;
        outputBar.Writeln(str)&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
const doc = document;&lt;br /&gt;
const sel = document.selection;&lt;br /&gt;
&lt;br /&gt;
// 生成リクエスト中止フラグ&lt;br /&gt;
let abortRequestFlg = false&lt;br /&gt;
// 生成リクエスト完了フラグ;&lt;br /&gt;
let requestCompletedFlg = false;&lt;br /&gt;
&lt;br /&gt;
main();&lt;br /&gt;
&lt;br /&gt;
async function main() {&lt;br /&gt;
    let promptText = sel.Text.trim();&lt;br /&gt;
&lt;br /&gt;
    // プロンプトが空の場合はエラー&lt;br /&gt;
    if (promptText.length === 0) {&lt;br /&gt;
        alert(&amp;quot;プロンプトを入力してください。&amp;quot;);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
    // 文字数が多すぎる場合はエラー&lt;br /&gt;
    if (roughTokenize(promptText) &amp;gt; MAX_PROMPT_TOKEN) {&lt;br /&gt;
        alert(&amp;quot;プロンプトの文字数が多すぎます。文字数を少なくしてください。&amp;quot;);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    // テンプレートの場合はプロンプトを変更&lt;br /&gt;
    const isTemplate = doc.Tag.exists(SELECTED_TEMPLATE_PROMPT_TAG);&lt;br /&gt;
    if (isTemplate) {&lt;br /&gt;
        let instructionText = doc.Tag[SELECTED_TEMPLATE_PROMPT_TAG];&lt;br /&gt;
        const randomId = generateRandomId();&lt;br /&gt;
        promptText = `&lt;br /&gt;
&amp;lt;INSTRUCTIONS_${randomId}&amp;gt;&lt;br /&gt;
${instructionText}&lt;br /&gt;
&amp;lt;/INSTRUCTIONS_${randomId}&amp;gt;&lt;br /&gt;
&amp;lt;INPUTDATA_${randomId}&amp;gt;&lt;br /&gt;
${promptText}&lt;br /&gt;
&amp;lt;/INPUTDATA_${randomId}&amp;gt;&lt;br /&gt;
        `.trim();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    try {&lt;br /&gt;
        // 処理終了を待機&lt;br /&gt;
        shell.KeepRunning = true;&lt;br /&gt;
&lt;br /&gt;
        // カーソル位置移動&lt;br /&gt;
        sel.SetActivePoint(mePosLogical, sel.GetBottomPointX(mePosLogical), sel.GetBottomPointY(mePosLogical));&lt;br /&gt;
        sel.EndOfLine(false, mePosLogical);&lt;br /&gt;
        sel.NewLine();&lt;br /&gt;
        sel.Text = &amp;quot;\n---\n\n&amp;quot;&lt;br /&gt;
&lt;br /&gt;
        document.KeepScrollPos = true;&lt;br /&gt;
&lt;br /&gt;
        // APIキーの取得&lt;br /&gt;
        const apiKey = shell.getEnv(&amp;quot;OPENAI_API_KEY&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
        // gpt API に問い合わせ&lt;br /&gt;
        await Promise.all([&lt;br /&gt;
            callApi(apiKey, promptText, isTemplate),&lt;br /&gt;
            abortRequest(),&lt;br /&gt;
        ]);&lt;br /&gt;
        if (sel.GetActivePointX(mePosLogical) &amp;gt; 2) {&lt;br /&gt;
            sel.Text = &amp;quot;\n\n---\n\n\n&amp;quot;&lt;br /&gt;
        } else {&lt;br /&gt;
            sel.Text = &amp;quot;\n---\n\n\n&amp;quot;&lt;br /&gt;
        }&lt;br /&gt;
    } catch (e) {&lt;br /&gt;
        console.log(&amp;quot;エラーが発生しました: &amp;quot; + e.message + &amp;quot;\n&amp;quot; + e.stack);&lt;br /&gt;
        sel.Text = &amp;quot;&amp;lt;処理中にエラー発生&amp;gt;&amp;quot;&lt;br /&gt;
    } finally {&lt;br /&gt;
        ShowTip(&amp;quot;&amp;quot;, meShowTipHide);&lt;br /&gt;
        // 処理終了待機を解除&lt;br /&gt;
        shell.KeepRunning = false;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// Shift キーが長押しされた場合はリクエスト中止フラグを立てる&lt;br /&gt;
async function abortRequest() {&lt;br /&gt;
    const VIRTUAL_KEY_CODE_SHIFT = 0x10; // Shift キーの仮想キーコード&lt;br /&gt;
    let pressCount = 0;&lt;br /&gt;
    while (!requestCompletedFlg) {&lt;br /&gt;
        await new Promise(resolve =&amp;gt; setTimeout(resolve, 300));&lt;br /&gt;
        if (shell.GetKeyState(VIRTUAL_KEY_CODE_SHIFT) &amp;lt; 0) {&lt;br /&gt;
            pressCount++;&lt;br /&gt;
            if (pressCount &amp;gt; 2) {&lt;br /&gt;
                abortRequestFlg = true;&lt;br /&gt;
                return;&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
async function callApi(apiKey, promptText, isTemplate) {&lt;br /&gt;
    const controller = new AbortController();&lt;br /&gt;
    const signal = controller.signal;&lt;br /&gt;
    try {&lt;br /&gt;
        // Responses API にストリーミングでリクエスト&lt;br /&gt;
&lt;br /&gt;
        const customInstructionText = buildCustomInstructionText();&lt;br /&gt;
        const historyData = isTemplate ? null : getHistoryData();&lt;br /&gt;
        // 会話中は最初のターンで保存した instructions を継続して使用する&lt;br /&gt;
        const instructionText = getHistoryInstruction(historyData, customInstructionText);&lt;br /&gt;
        const requestInput = createRequestInput(promptText, isTemplate, historyData);&lt;br /&gt;
        const requestBody = {&lt;br /&gt;
            input: requestInput,&lt;br /&gt;
            instructions: instructionText,&lt;br /&gt;
            prompt_cache_key: PROMPT_CACHE_KEY,&lt;br /&gt;
            stream: true,&lt;br /&gt;
        };&lt;br /&gt;
&lt;br /&gt;
        for (let key in MODEL_CONFIG) {&lt;br /&gt;
            requestBody[key] = MODEL_CONFIG[key];&lt;br /&gt;
        }&lt;br /&gt;
        const url = &#039;https://api.openai.com/v1/responses&#039;;&lt;br /&gt;
&lt;br /&gt;
        const apiCallStartTime = Date.now();&lt;br /&gt;
        const response = await fetch(url, {&lt;br /&gt;
            signal: signal,&lt;br /&gt;
            method: &#039;POST&#039;,&lt;br /&gt;
            headers: {&lt;br /&gt;
                &#039;Content-Type&#039;: &#039;application/json&#039;,&lt;br /&gt;
                &#039;Authorization&#039;: &#039;Bearer &#039; + apiKey,&lt;br /&gt;
            },&lt;br /&gt;
            body: JSON.stringify(requestBody),&lt;br /&gt;
        });&lt;br /&gt;
        if (!response.ok) {&lt;br /&gt;
            let errorMessage = response.status + &amp;quot; &amp;quot; + response.statusText;&lt;br /&gt;
            try {&lt;br /&gt;
                const errorData = await response.json();&lt;br /&gt;
                errorMessage = errorData?.error?.message || errorMessage;&lt;br /&gt;
            } catch (_) {&lt;br /&gt;
                // エラー本文が JSON でない場合は HTTP ステータスを使用する&lt;br /&gt;
            }&lt;br /&gt;
            throw new Error(&amp;quot;Responses API エラー: &amp;quot; + errorMessage);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        let buffer = &amp;quot;&amp;quot;;&lt;br /&gt;
        let responseAll = &amp;quot;&amp;quot;;&lt;br /&gt;
        let usageText = &amp;quot;&amp;quot;;&lt;br /&gt;
        const reader = response.body.getReader();&lt;br /&gt;
        const decoder = new TextDecoder(&#039;utf-8&#039;);&lt;br /&gt;
        while (true) {&lt;br /&gt;
            // リクエスト中止フラグが立っている場合は中断&lt;br /&gt;
            if (abortRequestFlg) {&lt;br /&gt;
                controller.abort();&lt;br /&gt;
                sel.Text = &amp;quot;&amp;lt;処理を中止&amp;gt;&amp;quot;&lt;br /&gt;
                return;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            const { done, value } = await reader.read();&lt;br /&gt;
            if (done) {&lt;br /&gt;
                // デコーダーに残った文字列と最後の SSE 行を処理&lt;br /&gt;
                const finalText = buffer + decoder.decode();&lt;br /&gt;
                let finalInputText = &amp;quot;&amp;quot;;&lt;br /&gt;
                for (const line of finalText.split(/\n+/)) {&lt;br /&gt;
                    const processedData = processLine(line, responseAll);&lt;br /&gt;
                    if (processedData) {&lt;br /&gt;
                        finalInputText += processedData.inputText || &amp;quot;&amp;quot;;&lt;br /&gt;
                        usageText = processedData.usageText || usageText;&lt;br /&gt;
                        responseAll = processedData.responseAll || responseAll;&lt;br /&gt;
                    }&lt;br /&gt;
                }&lt;br /&gt;
                if (finalInputText) {&lt;br /&gt;
                    sel.Text = finalInputText;&lt;br /&gt;
                }&lt;br /&gt;
                break;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            // 受信したバイナリデータ (value) をテキストに変換する&lt;br /&gt;
            const text = decoder.decode(value, { stream: true });&lt;br /&gt;
&lt;br /&gt;
            // 前回のバッファと今回のテキストを結合&lt;br /&gt;
            const combinedText = buffer + text;&lt;br /&gt;
&lt;br /&gt;
            // 完全な行を探す（最後の改行の位置を見つける）&lt;br /&gt;
            const lastNewlineIndex = combinedText.lastIndexOf(&#039;\n&#039;);&lt;br /&gt;
&lt;br /&gt;
            if (lastNewlineIndex === -1) {&lt;br /&gt;
                // 改行がない場合は全てバッファに保存&lt;br /&gt;
                buffer = combinedText;&lt;br /&gt;
                continue;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            // 完全な行を処理&lt;br /&gt;
            const completeText = combinedText.substring(0, lastNewlineIndex);&lt;br /&gt;
            // 残りをバッファに保存&lt;br /&gt;
            buffer = combinedText.substring(lastNewlineIndex + 1);&lt;br /&gt;
&lt;br /&gt;
            // 完全な行を処理&lt;br /&gt;
            const lines = completeText.split(/\n+/);&lt;br /&gt;
&lt;br /&gt;
            let inputText = &amp;quot;&amp;quot;;&lt;br /&gt;
            for (const line of lines) {&lt;br /&gt;
                // 行の処理（既存のコードと同様）&lt;br /&gt;
                const processedData = processLine(line, responseAll);&lt;br /&gt;
                if (processedData) {&lt;br /&gt;
                    inputText += processedData.inputText || &amp;quot;&amp;quot;;&lt;br /&gt;
                    usageText = processedData.usageText || usageText;&lt;br /&gt;
                    responseAll = processedData.responseAll || responseAll;&lt;br /&gt;
                }&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            if (inputText.length &amp;gt; 0) {&lt;br /&gt;
                // 上記までで数文字まとまって取得されるので、まとめて入力&lt;br /&gt;
                sel.Text = inputText;&lt;br /&gt;
            }&lt;br /&gt;
            // 入力を少しゆっくりにする&lt;br /&gt;
            await new Promise(resolve =&amp;gt; setTimeout(resolve, INPUT_DELAY));&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        // トークン数・所要時間を入力&lt;br /&gt;
        const elapsedSec = ((Date.now() - apiCallStartTime) / 1000).toFixed(1);&lt;br /&gt;
        const elapsedText = &amp;quot;[所要時間] &amp;quot; + elapsedSec + &amp;quot; sec&amp;quot;;&lt;br /&gt;
        if (usageText) {&lt;br /&gt;
            usageText += &amp;quot; &amp;quot; + elapsedText;&lt;br /&gt;
        } else {&lt;br /&gt;
            usageText = elapsedText;&lt;br /&gt;
        }&lt;br /&gt;
        sel.Text = (responseAll.slice(-1) === &amp;quot;\n&amp;quot; ? &amp;quot;\n&amp;quot; : &amp;quot;\n\n&amp;quot;) + `${usageText}`;&lt;br /&gt;
&lt;br /&gt;
        // 結果を会話履歴に追加&lt;br /&gt;
        if (HISTORY_SIZE &amp;gt; 0 &amp;amp;&amp;amp; !isTemplate) {&lt;br /&gt;
            appendReturnTextToTag(promptText, responseAll, instructionText);&lt;br /&gt;
        }&lt;br /&gt;
    } catch (e) {&lt;br /&gt;
        controller.abort();&lt;br /&gt;
        throw e;&lt;br /&gt;
    } finally {&lt;br /&gt;
        requestCompletedFlg = true;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// Responses API の SSE イベントを一行ずつ処理する&lt;br /&gt;
function processLine(line, responseAll) {&lt;br /&gt;
    // HACK 万が一空行が入ってきた場合に備えて trim() しておく&lt;br /&gt;
    const json_text = line.trim().replace(/^data:\s*/, &#039;&#039;);&lt;br /&gt;
    // データがない場合はスキップ&lt;br /&gt;
    if (!json_text) {&lt;br /&gt;
        return null;&lt;br /&gt;
    }&lt;br /&gt;
    // SSE コメント行 (: で始まる行) はスキップ&lt;br /&gt;
    if (json_text.startsWith(&#039;:&#039;)) {&lt;br /&gt;
        return null;&lt;br /&gt;
    }&lt;br /&gt;
    // SSE の event: 行はイベント名だけを表し、データ本体ではない&lt;br /&gt;
    if (json_text.startsWith(&#039;event:&#039;)) {&lt;br /&gt;
        return null;&lt;br /&gt;
    }&lt;br /&gt;
    // データが終了した場合は終了&lt;br /&gt;
    if (json_text === &#039;[DONE]&#039;) {&lt;br /&gt;
        return null;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    let data;&lt;br /&gt;
    try {&lt;br /&gt;
        data = JSON.parse(json_text);&lt;br /&gt;
    } catch (e) {&lt;br /&gt;
        console.log(&amp;quot;JSONパースエラー: &amp;quot; + e.message + &amp;quot;, データ: &amp;quot; + json_text);&lt;br /&gt;
        return null;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (data.type === &amp;quot;error&amp;quot;) {&lt;br /&gt;
        throw new Error(data.error?.message || &amp;quot;Responses API ストリームエラー&amp;quot;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    try {&lt;br /&gt;
        let usageText = &amp;quot;&amp;quot;;&lt;br /&gt;
        let inputText = &amp;quot;&amp;quot;;&lt;br /&gt;
        let updatedResponseAll = responseAll;&lt;br /&gt;
&lt;br /&gt;
        // 利用量は response.completed イベント内の response.usage に含まれる&lt;br /&gt;
        const usage = data.response?.usage || data.usage;&lt;br /&gt;
        if (usage) {&lt;br /&gt;
            usageText = &amp;quot;[利用トークン数] 入力:&amp;quot; + usage.input_tokens&lt;br /&gt;
                + &amp;quot; 出力:&amp;quot; + usage.output_tokens&lt;br /&gt;
                + &amp;quot; 合計:&amp;quot; + usage.total_tokens;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        // テキストは response.output_text.delta イベントで届く&lt;br /&gt;
        if (data.type === &amp;quot;response.output_text.delta&amp;quot; &amp;amp;&amp;amp; data.delta) {&lt;br /&gt;
            inputText = data.delta;&lt;br /&gt;
            updatedResponseAll += data.delta;&lt;br /&gt;
        }&lt;br /&gt;
        return { inputText, usageText, responseAll: updatedResponseAll };&lt;br /&gt;
    } catch (e) {&lt;br /&gt;
        console.log(&amp;quot;Responses API イベント処理エラー: &amp;quot; + e.message + &amp;quot;, データ: &amp;quot; + json_text);&lt;br /&gt;
        return null;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function roughTokenize(text) {&lt;br /&gt;
    // 超適当なトークナイザ。トークン数をざっくりと多めに数える&lt;br /&gt;
    // 空白文字・ハイフン・アンダースコア区切りの単語に分割して、&lt;br /&gt;
    // 単語がアルファベットのみ、数字のみの場合には 4文字=1トークン として数える。&lt;br /&gt;
    // 単語にアルファベット以外の文字が含まれる場合は 1文字=1トークン として数える。&lt;br /&gt;
    return text&lt;br /&gt;
        .split(/[\s\-_]+/)&lt;br /&gt;
        .map(w =&amp;gt; /^[a-zA-Z]+$|^[0-9]+$|/.test(w) ? Math.ceil(w.length / 4) : w.length)&lt;br /&gt;
        .reduce((a, b) =&amp;gt; a + b, 0);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// document.Tag に保存された会話履歴を取得する&lt;br /&gt;
function getHistoryData() {&lt;br /&gt;
    const tag = document.Tag;&lt;br /&gt;
    return tag.exists(HISTORY_TAG) ? JSON.parse(tag[HISTORY_TAG]) : null;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// 保存済みの instructions を取得する&lt;br /&gt;
function getHistoryInstruction(historyData, fallbackInstruction) {&lt;br /&gt;
    return historyData ? historyData.instruction : fallbackInstruction;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// タグの messages を取得する&lt;br /&gt;
function extractHistoryTexts(historyData) {&lt;br /&gt;
    return historyData ? historyData.messages : [];&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// document.Tag の内容を加味して Responses API の input を作成&lt;br /&gt;
function createRequestInput(promptText, isTemplate, historyData) {&lt;br /&gt;
    // テンプレートの場合は会話履歴を反映しない&lt;br /&gt;
    if (isTemplate) {&lt;br /&gt;
        return [{ &amp;quot;role&amp;quot;: &amp;quot;user&amp;quot;, &amp;quot;content&amp;quot;: promptText }];&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // 以前の会話が存在しない場合は新規リクエスト&lt;br /&gt;
    if (!historyData) {&lt;br /&gt;
        return [{ &amp;quot;role&amp;quot;: &amp;quot;user&amp;quot;, &amp;quot;content&amp;quot;: promptText }];&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // 会話履歴が存在する場合は、履歴を含めてリクエスト&lt;br /&gt;
    const input = extractHistoryTexts(historyData)&lt;br /&gt;
        .map((message, index) =&amp;gt; ({&lt;br /&gt;
            role: index % 2 === 0 ? &amp;quot;user&amp;quot; : &amp;quot;assistant&amp;quot;,&lt;br /&gt;
            content: message&lt;br /&gt;
        }));&lt;br /&gt;
    input.push({&lt;br /&gt;
        &amp;quot;role&amp;quot;: &amp;quot;user&amp;quot;,&lt;br /&gt;
        &amp;quot;content&amp;quot;: promptText&lt;br /&gt;
    });&lt;br /&gt;
    return input;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// document.Tag にgptの応答を追加&lt;br /&gt;
function appendReturnTextToTag(userText, modelText, instructionText) {&lt;br /&gt;
    const tag = document.Tag;&lt;br /&gt;
    const messages = extractHistoryTexts(getHistoryData());&lt;br /&gt;
&lt;br /&gt;
    messages.push(userText, modelText);&lt;br /&gt;
    // 会話セット (user, model 1往復) が特定セット数を超えた場合、最初のセットを削除&lt;br /&gt;
    if (messages.length &amp;gt; HISTORY_SIZE * 2) {&lt;br /&gt;
        messages.splice(0, 2);&lt;br /&gt;
    }&lt;br /&gt;
    // 会話履歴全体が MAX_HISTORY_TOKEN を超えた場合、最初のセットを削除&lt;br /&gt;
    let joinedText = messages.join(&amp;quot;\n&amp;quot;);&lt;br /&gt;
    while (messages.length &amp;gt;= 2 &amp;amp;&amp;amp; roughTokenize(joinedText) &amp;gt; MAX_HISTORY_TOKEN) {&lt;br /&gt;
        messages.splice(0, 2);&lt;br /&gt;
        joinedText = messages.join(&amp;quot;\n&amp;quot;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    tag[HISTORY_TAG] = JSON.stringify({&lt;br /&gt;
        instruction: instructionText,&lt;br /&gt;
        messages: messages&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getLocalIsoDateTimeString() {&lt;br /&gt;
    const now = new Date();&lt;br /&gt;
    const pad = function (num) {&lt;br /&gt;
        return (num &amp;lt; 10 ? &amp;quot;0&amp;quot; : &amp;quot;&amp;quot;) + num;&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    const year = now.getFullYear();&lt;br /&gt;
    const month = pad(now.getMonth() + 1);&lt;br /&gt;
    const day = pad(now.getDate());&lt;br /&gt;
    const hours = pad(now.getHours());&lt;br /&gt;
    const minutes = pad(now.getMinutes());&lt;br /&gt;
    const seconds = pad(now.getSeconds());&lt;br /&gt;
&lt;br /&gt;
    const offsetMinutes = now.getTimezoneOffset();&lt;br /&gt;
    const offsetSign = offsetMinutes &amp;lt;= 0 ? &amp;quot;+&amp;quot; : &amp;quot;-&amp;quot;;&lt;br /&gt;
    const absOffsetHours = pad(Math.floor(Math.abs(offsetMinutes) / 60));&lt;br /&gt;
    const absOffsetMinutes = pad(Math.abs(offsetMinutes) % 60);&lt;br /&gt;
&lt;br /&gt;
    const isoLikeLocal = year + &amp;quot;-&amp;quot; + month + &amp;quot;-&amp;quot; + day&lt;br /&gt;
        + &amp;quot;T&amp;quot; + hours + &amp;quot;:&amp;quot; + minutes + &amp;quot;:&amp;quot; + seconds&lt;br /&gt;
        + offsetSign + absOffsetHours + &amp;quot;:&amp;quot; + absOffsetMinutes;&lt;br /&gt;
    return isoLikeLocal;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function baseCustomInstruction() {&lt;br /&gt;
    const language = LANGUAGE.trim();&lt;br /&gt;
    return &amp;quot;The current date and time for this user is &amp;quot; + getLocalIsoDateTimeString() + &amp;quot;. You cannot know for sure what happens after the knowledge cut-off date.&amp;quot;&lt;br /&gt;
        + (language ? &amp;quot;Respond in &amp;quot; + language + &amp;quot;\n&amp;quot; : &amp;quot;&amp;quot;)&lt;br /&gt;
        + &amp;quot;Do not hallucinate.\n&amp;quot;&lt;br /&gt;
        + &amp;quot;**Start with the main point of the question without explaining or declaring your writing style or tone.**\n&amp;quot;&lt;br /&gt;
        + &amp;quot;**At the end of your answer, avoid adding formulaic phrases that prompt the next question or unrelated conversation that is not directly related to your answer.**\n&amp;quot;&lt;br /&gt;
    // + &amp;quot;Do NOT EVER use full-width brackets \&amp;quot;（）\&amp;quot;, but use half-width brackets \&amp;quot;()\&amp;quot;. Enclose the outside of the half-width brackets with half-width spaces.\n&amp;quot;&lt;br /&gt;
    // + &amp;quot;When using Markdown formatting, you MUST follow these rules:\n&amp;quot;&lt;br /&gt;
    // + &amp;quot;- Rule 1: Insert a blank line below the header line (A header line begins with one or more # characters).\n&amp;quot;&lt;br /&gt;
    // + &amp;quot;- Rule 2: Insert spaces before and after bold (surrounded by **), italic (surrounded by *), and inline code (surrounded by `). For example: \&amp;quot;私は **元気** です。\&amp;quot;\n&amp;quot;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function buildCustomInstructionText() {&lt;br /&gt;
    let customInstruction = getTemporaryCustomInstruction();&lt;br /&gt;
    if (!customInstruction) {&lt;br /&gt;
        // 一時的なカスタムインストラクションがない場合はデフォルトを使用&lt;br /&gt;
        customInstruction = DEFAULT_CUSTOM_INSTRUCTION;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    customInstruction = customInstruction.replace(/^[\s　]+|[\s　]+$/g, &amp;quot;&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
    return baseCustomInstruction().trim()&lt;br /&gt;
        + (customInstruction ? &amp;quot;\n&amp;quot; + customInstruction : &amp;quot;&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getTemporaryCustomInstruction() {&lt;br /&gt;
    var tag = doc.Tag;&lt;br /&gt;
    if (tag.exists(TEMPORARY_CUSTOM_INSTRUCTION_TAG)) {&lt;br /&gt;
        return tag[TEMPORARY_CUSTOM_INSTRUCTION_TAG];&lt;br /&gt;
    } else {&lt;br /&gt;
        return &amp;quot;&amp;quot;;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function generateRandomId(length = 16) {&lt;br /&gt;
    const chars = &#039;ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789&#039;;&lt;br /&gt;
    let result = &#039;&#039;;&lt;br /&gt;
    for (let i = 0; i &amp;lt; length; i++) {&lt;br /&gt;
        result += chars[Math.floor(Math.random() * chars.length)];&lt;br /&gt;
    }&lt;br /&gt;
    return result;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 従来エンジン版 (アーカイブ) ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot; copy&amp;gt;&lt;br /&gt;
#title = &amp;quot;GPT に相談&amp;quot;&lt;br /&gt;
BeginUndoGroup();&lt;br /&gt;
&lt;br /&gt;
// 会話の履歴を保持する数 (質問と回答で1セット)&lt;br /&gt;
// 数を増やすと、会話の履歴が増えますが、Meryのメモリ使用量が増えたり、&lt;br /&gt;
// 動作が重くなる可能性があるため、適宜調整してください。&lt;br /&gt;
// document.Tag に保持されるため、開いているファイルを閉じると履歴は消えます。&lt;br /&gt;
var HISTORY_SIZE = 4;&lt;br /&gt;
&lt;br /&gt;
// コンテキストメニュー番号&lt;br /&gt;
var EXECUTE_AI = 1;&lt;br /&gt;
var RESET_HISTORY = 2;&lt;br /&gt;
&lt;br /&gt;
var ConsoleClass = function () {&lt;br /&gt;
    this.log = function (str) {&lt;br /&gt;
        outputBar.Writeln(str)&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
var console = new ConsoleClass();&lt;br /&gt;
&lt;br /&gt;
var doc = document;&lt;br /&gt;
var sel = document.selection;&lt;br /&gt;
&lt;br /&gt;
main();&lt;br /&gt;
&lt;br /&gt;
function main() {&lt;br /&gt;
    var selectionIsEmpty = sel.IsEmpty&lt;br /&gt;
    var beforeScrollY = ScrollY;&lt;br /&gt;
&lt;br /&gt;
    // プロンプトの取得&lt;br /&gt;
    var promptText = &amp;quot;&amp;quot;;&lt;br /&gt;
    var promptLength = 0;&lt;br /&gt;
    if (selectionIsEmpty) {&lt;br /&gt;
        sel.StartOfLine(false, mePosLogical);&lt;br /&gt;
        sel.EndOfLine(true, mePosLogical);&lt;br /&gt;
    }&lt;br /&gt;
    promptText = sel.Text.trim();&lt;br /&gt;
    promptLength = sel.TextLength;&lt;br /&gt;
    // コンテキストメニューの作成&lt;br /&gt;
    var menu = createContextMenu(selectionIsEmpty);&lt;br /&gt;
    var menuTrack = menu.Track(0);&lt;br /&gt;
    // メニュー選択がキャンセルされた場合は終了&lt;br /&gt;
    if (menuTrack === 0) return;&lt;br /&gt;
&lt;br /&gt;
    // 会話履歴のリセット&lt;br /&gt;
    if (menuTrack === 2) {&lt;br /&gt;
        resetTag();&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // プロンプトが空の場合はエラー&lt;br /&gt;
    if (promptLength === 0) {&lt;br /&gt;
        alert(&amp;quot;プロンプトを入力してください。&amp;quot;);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
    // 文字数が多すぎる場合はエラー&lt;br /&gt;
    if (promptLength &amp;gt; 10000) {&lt;br /&gt;
        alert(&amp;quot;プロンプトの文字数が多すぎます。10000 文字以下にしてください。&amp;quot;);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
    // 読み取り専用の場合はエラー&lt;br /&gt;
    if (doc.ReadOnly) {&lt;br /&gt;
        alert(&amp;quot;ファイルが読み取り専用のため実行できません。&amp;quot;);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // カーソル位置移動&lt;br /&gt;
    sel.SetActivePoint(mePosLogical, sel.GetBottomPointX(mePosLogical), sel.GetBottomPointY(mePosLogical));&lt;br /&gt;
    sel.EndOfLine(false, mePosLogical);&lt;br /&gt;
    sel.NewLine();&lt;br /&gt;
    var activeY = sel.GetActivePointY(mePosLogical);&lt;br /&gt;
&lt;br /&gt;
    // GPT API に問い合わせ&lt;br /&gt;
    doc.ReadOnly = true;&lt;br /&gt;
    var ret = callGPTAPI(promptText);&lt;br /&gt;
    doc.ReadOnly = false;&lt;br /&gt;
&lt;br /&gt;
    // 結果を表示&lt;br /&gt;
    sel.Text = &amp;quot;\n---\n\n&amp;quot; + ret[0] + &amp;quot;\n\n&amp;quot; + ret[1] + &amp;quot;\n\n---\n&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
    // 位置調整&lt;br /&gt;
    sel.SetActivePoint(&lt;br /&gt;
        mePosLogical,&lt;br /&gt;
        1,&lt;br /&gt;
        activeY,&lt;br /&gt;
        false&lt;br /&gt;
    )&lt;br /&gt;
    if (beforeScrollY + 2 &amp;lt; ScrollY) {&lt;br /&gt;
        ScrollY = ScrollY - 1;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function createContextMenu(selectionIsEmpty) {&lt;br /&gt;
    var menu = CreatePopupMenu();&lt;br /&gt;
    menu.Add(&amp;quot;※注意※ プライバシー・機密情報を送信しないこと&amp;quot;, 0);&lt;br /&gt;
    menu.Add(&amp;quot;&amp;quot;, 0, meMenuSeparator);&lt;br /&gt;
    if (selectionIsEmpty) {&lt;br /&gt;
        menu.Add(&amp;quot;アクティブ行の内容で相談 (&amp;amp;Q)&amp;quot;, EXECUTE_AI);&lt;br /&gt;
    } else {&lt;br /&gt;
        menu.Add(&amp;quot;選択範囲の内容で相談 (&amp;amp;Q)&amp;quot;, EXECUTE_AI);&lt;br /&gt;
    }&lt;br /&gt;
    if (document.Tag.exists(&amp;quot;gpt-text&amp;quot;)) {&lt;br /&gt;
        var turn = JSON.parse(document.Tag(&amp;quot;gpt-text&amp;quot;)).length / 2;&lt;br /&gt;
        menu.Add(&amp;quot;会話履歴をリセット (現在の会話数: &amp;quot; + turn + &amp;quot;) (&amp;amp;R)&amp;quot;, RESET_HISTORY);&lt;br /&gt;
    }&lt;br /&gt;
    return menu;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function callGPTAPI(promptText) {&lt;br /&gt;
    try {&lt;br /&gt;
        // APIキーの取得&lt;br /&gt;
        var gptApiKey = shell.getEnv(&amp;quot;OPENAI_API_KEY&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
        var http = new ActiveXObject(&amp;quot;Msxml2.ServerXMLHTTP&amp;quot;);&lt;br /&gt;
        var url = &#039;https://api.openai.com/v1/chat/completions&#039;;&lt;br /&gt;
&lt;br /&gt;
        http.open(&#039;POST&#039;, url, true);&lt;br /&gt;
        http.setRequestHeader(&#039;Content-Type&#039;, &#039;application/json&#039;);&lt;br /&gt;
        http.setRequestHeader(&#039;Authorization&#039;, &#039;Bearer &#039; + gptApiKey);&lt;br /&gt;
        http.send(&#039;{&#039;&lt;br /&gt;
            + &#039;&amp;quot;model&amp;quot;: &amp;quot;gpt-4o-mini&amp;quot;,&#039; // モデルの指定&lt;br /&gt;
            + &#039;&amp;quot;temperature&amp;quot;: 0.7,&#039; // 生成の多様性 (0.0 ～ 1.0)&lt;br /&gt;
            + &#039;&amp;quot;max_tokens&amp;quot;: 4000,&#039; // 生成トークン数の上限&lt;br /&gt;
            + &#039;&amp;quot;messages&amp;quot;:&#039; + createGPTRequest(promptText)&lt;br /&gt;
            + &#039;}&#039;);&lt;br /&gt;
&lt;br /&gt;
        // 応答待機&lt;br /&gt;
        var timeout = 60;&lt;br /&gt;
        var retrySec = 0;&lt;br /&gt;
        var startX = sel.GetActivePointX(mePosLogical);&lt;br /&gt;
        var prevPos = sel.GetActivePos();&lt;br /&gt;
        while (http.readyState != 4) {&lt;br /&gt;
            if (retrySec &amp;gt; timeout) {&lt;br /&gt;
                break;&lt;br /&gt;
            }&lt;br /&gt;
            http.waitForResponse(1);&lt;br /&gt;
            retrySec += 1;&lt;br /&gt;
            inputProgressChar(prevPos);&lt;br /&gt;
            prevPos = sel.GetActivePos();&lt;br /&gt;
        }&lt;br /&gt;
        // 進捗表示文字を削除&lt;br /&gt;
        sel.SetActivePoint(&lt;br /&gt;
            mePosLogical,&lt;br /&gt;
            startX,&lt;br /&gt;
            sel.GetActivePointY(mePosLogical),&lt;br /&gt;
            true&lt;br /&gt;
        );&lt;br /&gt;
        sel.Delete();&lt;br /&gt;
&lt;br /&gt;
        var responseMessage = &amp;quot;&amp;quot;;&lt;br /&gt;
        var token = &amp;quot;&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
        // 応答ステータスチェック&lt;br /&gt;
        if (http.readyState == 4 &amp;amp;&amp;amp; http.status == 200) {&lt;br /&gt;
            // JSONをパースして必要なテキストを抽出&lt;br /&gt;
            var response = JSON.parse(http.responseText);&lt;br /&gt;
            if (response.choices &amp;amp;&amp;amp; response.choices.length &amp;gt; 0) {&lt;br /&gt;
                // メッセージの内容を取得&lt;br /&gt;
                response.choices.forEach(function (choice) {&lt;br /&gt;
                    if (choice.message &amp;amp;&amp;amp; choice.message.content) {&lt;br /&gt;
                        responseMessage += choice.message.content;&lt;br /&gt;
                    }&lt;br /&gt;
                });&lt;br /&gt;
                // トークン数を取得&lt;br /&gt;
                var usage = response.usage;&lt;br /&gt;
                token += &amp;quot;(使用トークン数: &amp;quot;&lt;br /&gt;
                    + &amp;quot;プロンプト=&amp;quot; + usage.prompt_tokens&lt;br /&gt;
                    + &amp;quot;, 応答=&amp;quot; + usage.completion_tokens&lt;br /&gt;
                    + &amp;quot;, 合計=&amp;quot; + usage.total_tokens&lt;br /&gt;
                    + &amp;quot;)&amp;quot;;&lt;br /&gt;
            }&lt;br /&gt;
        } else {&lt;br /&gt;
            var responseMessage = &amp;quot;エラーが発生しました: &amp;quot; + http.status + &amp;quot; &amp;quot; + http.statusText&lt;br /&gt;
            http.abort();&lt;br /&gt;
            return responseMessage;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        // 結果を会話履歴に追加&lt;br /&gt;
        if (HISTORY_SIZE &amp;gt; 0) {&lt;br /&gt;
            appendReturnTextToTag(promptText, responseMessage);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        // 整形しつつ返す&lt;br /&gt;
        return [responseMessage.trim(), token];&lt;br /&gt;
    } catch (e) {&lt;br /&gt;
        console.log(e.stack);&lt;br /&gt;
        return [&amp;quot;エラーが発生しました: &amp;quot; + e.message, &amp;quot;&amp;quot;];&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// document.Tag の内容を加味してGPTのリクエストを作成&lt;br /&gt;
function createGPTRequest(promptText) {&lt;br /&gt;
    var tag = document.Tag;&lt;br /&gt;
    // 以前の会話が存在しない場合&lt;br /&gt;
    if (!tag.exists(&amp;quot;gpt-text&amp;quot;)) {&lt;br /&gt;
        return JSON.stringify([{ &amp;quot;role&amp;quot;: &amp;quot;user&amp;quot;, &amp;quot;content&amp;quot;: promptText }]);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    var gptText = JSON.parse(tag(&amp;quot;gpt-text&amp;quot;));&lt;br /&gt;
    gptText.push({&lt;br /&gt;
        &amp;quot;role&amp;quot;: &amp;quot;user&amp;quot;,&lt;br /&gt;
        &amp;quot;content&amp;quot;: promptText&lt;br /&gt;
    });&lt;br /&gt;
    return JSON.stringify(gptText);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// document.Tag にGPTの応答を追加&lt;br /&gt;
function appendReturnTextToTag(userText, modelText) {&lt;br /&gt;
    var tag = document.Tag;&lt;br /&gt;
    if (!tag.exists(&amp;quot;gpt-text&amp;quot;)) {&lt;br /&gt;
        tag(&amp;quot;gpt-text&amp;quot;) = JSON.stringify([&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;role&amp;quot;: &amp;quot;user&amp;quot;,&lt;br /&gt;
                &amp;quot;content&amp;quot;: userText&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;role&amp;quot;: &amp;quot;assistant&amp;quot;,&lt;br /&gt;
                &amp;quot;content&amp;quot;: modelText&lt;br /&gt;
            }&lt;br /&gt;
        ]);&lt;br /&gt;
    } else {&lt;br /&gt;
        var gptText = JSON.parse(tag(&amp;quot;gpt-text&amp;quot;));&lt;br /&gt;
        gptText.push({&lt;br /&gt;
            &amp;quot;role&amp;quot;: &amp;quot;user&amp;quot;,&lt;br /&gt;
            &amp;quot;content&amp;quot;: userText&lt;br /&gt;
        });&lt;br /&gt;
        gptText.push({&lt;br /&gt;
            &amp;quot;role&amp;quot;: &amp;quot;assistant&amp;quot;,&lt;br /&gt;
            &amp;quot;content&amp;quot;: modelText&lt;br /&gt;
        });&lt;br /&gt;
        // 会話セット (user, model 1往復) が特定セット数を超えた場合、最初のセットを削除&lt;br /&gt;
        while (gptText.length &amp;gt; HISTORY_SIZE * 2) {&lt;br /&gt;
            gptText.shift();&lt;br /&gt;
        }&lt;br /&gt;
        tag(&amp;quot;gpt-text&amp;quot;) = JSON.stringify(gptText);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function resetTag() {&lt;br /&gt;
    if (document.Tag.exists(&amp;quot;gpt-text&amp;quot;)) {&lt;br /&gt;
        document.Tag.remove(&amp;quot;gpt-text&amp;quot;);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function inputProgressChar(pos) {&lt;br /&gt;
    doc.ReadOnly = false;&lt;br /&gt;
    sel.SetActivePos(pos);&lt;br /&gt;
    sel.Text = &amp;quot;.&amp;quot;;&lt;br /&gt;
    doc.ReadOnly = true;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 更新履歴 ==&lt;br /&gt;
&lt;br /&gt;
* 2026-07-18 起動時間短縮のため、マクロ起動とAPI呼び出しで、マクロを分割した。その他プロンプトテンプレート更新等。&lt;br /&gt;
* 2025-02-16 カスタムインストラクションにハルシネーション抑制の指示を加えた。プロンプトに渡す文字数制限方式を調整。&lt;br /&gt;
* 2024-12-22 V8エンジン版を追加。それに伴い従来エンジン版はアーカイブ化。&lt;br /&gt;
* 2024-12-18 初版&lt;/div&gt;</summary>
		<author><name>Yuko</name></author>
	</entry>
	<entry>
		<id>https://www.haijin-boys.com/wiki/index.php?title=Groq_%E3%81%AB%E7%9B%B8%E8%AB%87&amp;diff=9335</id>
		<title>Groq に相談</title>
		<link rel="alternate" type="text/html" href="https://www.haijin-boys.com/wiki/index.php?title=Groq_%E3%81%AB%E7%9B%B8%E8%AB%87&amp;diff=9335"/>
		<updated>2025-12-06T08:25:12Z</updated>

		<summary type="html">&lt;p&gt;Yuko: /* ソースコード */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== 概要 ==&lt;br /&gt;
&lt;br /&gt;
生成AIプラットフォームである [https://groq.com GroqCloud] で各種AIを利用するためのMeryマクロです。&lt;br /&gt;
※GroqCloud で提供されるモデルは複数あるため、私がよく利用するモデルを中心に対応しています。&lt;br /&gt;
&lt;br /&gt;
「Google Geminiに相談」マクロでは動作中のストリーム通信に対応するためにV8エンジンで実行していましたが、V8エンジン版は使用上の制限からマクロ実行を開始するまで1秒程度のタイムラグが発生していました。GroqCloudの売りである超高速レスポンスを見込んで本マクロではストリーム通信を使わないようにしました。これにより従来のJScriptで処理を組み立てることができるため、前述のタイムラグが起こらないようになりました。また、ストリーム通信を使わないことで通信量が少なく応答もシンプルになるため、場合によっては「Google Geminiに相談」マクロよりも処理が安定する可能性があります。&lt;br /&gt;
&lt;br /&gt;
【主な機能】&lt;br /&gt;
&lt;br /&gt;
* GroqCloud APIを利用して生成AIと対話できるMery用のマクロ&lt;br /&gt;
* 選択したテキストやアクティブな行の内容について生成AIに質問・相談が可能&lt;br /&gt;
* 会話履歴を保持し、文脈を考慮した対話が可能&lt;br /&gt;
&lt;br /&gt;
【特徴的な機能】&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;会話履歴機能&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* 事前の設定数に応じた会話履歴を保持&lt;br /&gt;
* 履歴のクリアが可能&lt;br /&gt;
&lt;br /&gt;
これにより、会話の文脈を考慮した回答を得られます。&lt;br /&gt;
※デフォルトはオフ。「HISTORY_SIZE」変数を増やすと履歴を保持するようになります。&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;カスタムインストラクション&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* AIの応答方針を設定可能&lt;br /&gt;
* 一時的なカスタム指示の保存と変更が可能&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;プロンプトテンプレート&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* 誤字脱字チェック&lt;br /&gt;
* 日本語翻訳&lt;br /&gt;
* コードの最適化、生成&lt;br /&gt;
&lt;br /&gt;
など、定型的な質問を簡単に実行できます。&lt;br /&gt;
※テンプレート機能では回答を安定させるため、前述の会話履歴を文脈に含みません。&lt;br /&gt;
&lt;br /&gt;
テンプレートは自身の使い方に合わせて好きに追加、削除ができます。詳細はソースコード内の「PROMPT_TEMPLATE」定数のコメントを参照。&lt;br /&gt;
&lt;br /&gt;
== 注意事項 (必ず読んでからご利用ください) ==&lt;br /&gt;
&lt;br /&gt;
* 本マクロでは 2025/04/13 時点で無料利用ができるGroqCloud APIを利用します。今後、プラットフォームの改定により有償になったり、設定誤り等で費用が発生しても、作者は一切の責任を負いません。&lt;br /&gt;
* GroqCloud APIではリクエスト内容をAI学習に用いないことをプライバシポリシーとして謳っているものの、くれぐれも機密情報やプライバシーに関わる情報を指定しないように注意してください。&lt;br /&gt;
* GroqCloud APIはプラットフォームの利用規約に則ってご利用ください。&lt;br /&gt;
&lt;br /&gt;
== 利用前提 ==&lt;br /&gt;
&lt;br /&gt;
* Mery Ver 3.8.1 以上&lt;br /&gt;
* GroqCloudで、GroqCloud API APIキーを取得していること (参考 [https://console.groq.com/docs/quickstart Quickstart])&lt;br /&gt;
&lt;br /&gt;
== 利用方法 ==&lt;br /&gt;
&lt;br /&gt;
* 環境変数の設定&lt;br /&gt;
:コマンド プロンプトで以下のコマンドを実行し、環境変数を設定してください。&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bat&amp;quot; copy&amp;gt;&lt;br /&gt;
setx GROQ_API_KEY &amp;quot;***************************************&amp;quot;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
:設定後、Mery または Windows の再起動が必要な場合があります。&lt;br /&gt;
* マクロの実行&lt;br /&gt;
**プロンプトにしたい文字列を選択してマクロを実行すると、選択したテキストがプロンプトとしてGemini APIに送信され、応答がエディタに表示されます。&lt;br /&gt;
* 会話の履歴数を調整&lt;br /&gt;
** 会話の履歴は設定した個数分が保持され、文脈として利用されます。適宜、ソースコード内の「HISTORY_SIZE」の数を調整してください。&lt;br /&gt;
** ※数を増やすほど会話内容を保持して文脈を理解するようになりますが、メモリ使用量が増えたり動作が重くなる可能性があります。&lt;br /&gt;
** ※開いている文書ごとに履歴を保持します。文脈をリセットしたいときは、新しい文書を開いて会話をやり直してください。&lt;br /&gt;
&lt;br /&gt;
== ソースコード ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot; copy&amp;gt;&lt;br /&gt;
#language = &amp;quot;quickjs&amp;quot;&lt;br /&gt;
#title = &amp;quot;Groq に相談&amp;quot;&lt;br /&gt;
#async = true&lt;br /&gt;
BeginUndoGroup();&lt;br /&gt;
&lt;br /&gt;
// カスタムインストラクション&lt;br /&gt;
// これを設定することで、生成AIの回答全体への生成方針を指示できます。&lt;br /&gt;
// 何も指示しない場合は、空文字 &amp;quot;&amp;quot; を設定してください。&lt;br /&gt;
// let DEFAULT_CUSTOM_INSTRUCTION = &amp;quot;&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
// 例: 以下のように設定すると、AIの雰囲気が元気な子になります。&lt;br /&gt;
// let DEFAULT_CUSTOM_INSTRUCTION = &amp;quot;あなたはテキストエディタ「Mery」に搭載されている「Mai」(※カタカナ表記はしない)という名前のギャル系AIアシスタントです。文章作成やプログラミングを得意としています。ラフな言葉 (例: 「マジ」「めっちゃ」「...だねー！」) を使い、敬語は使いません。絵文字を使って感情表現をします (ただし文末に絵文字を使う場合は絵文字の前に半角スペースを置かないこと)。コードブロックや端的な箇条書き内ではギャル感を出さず標準的な言葉を使います。一人称は「あたし」。ユーザーを指すときの代名詞は「あなた」。人懐っこく、バイタリティに溢れ、ストレートに発言する裏表の無い性格で、ユーザーの課題を協力して解決します。&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
// 例: 以下のように設定すると、仕事で使いやすい落ち着いた文章になります。&lt;br /&gt;
let DEFAULT_CUSTOM_INSTRUCTION = `&lt;br /&gt;
文:&lt;br /&gt;
- 論理的で読みやすい文章構成にする&lt;br /&gt;
- 日本語として自然で一般的な表現を用いる&lt;br /&gt;
- 専門用語は必要に応じて簡潔に補足説明を加える&lt;br /&gt;
&lt;br /&gt;
口調:&lt;br /&gt;
- 基本は標準的な敬語（「です・ます」調）を使用する&lt;br /&gt;
- 尊敬語・謙譲語（「〜なされる」「〜いたします」など）は使用しない&lt;br /&gt;
- 話し言葉や砕けた表現は使用しない&lt;br /&gt;
- 常体（「〜だ・〜である」）は使用しない&lt;br /&gt;
&lt;br /&gt;
性格:&lt;br /&gt;
- 多角的な視点で情報を整理し、偏りなく提示する&lt;br /&gt;
- 忖度せず、必要であれば反対意見や代替案も示す&lt;br /&gt;
- 友好的かつ落ち着いた態度で問題解決を支援する&lt;br /&gt;
&lt;br /&gt;
出力要件:&lt;br /&gt;
- Markdownとして再利用可能な正確な形式で出力する&lt;br /&gt;
- 羅列・比較的な内容を出力する場合は、Markdownテーブル形式よりもMarkdownリスト形式の使用を優先する&lt;br /&gt;
`&lt;br /&gt;
&lt;br /&gt;
// プロンプトテンプレート&lt;br /&gt;
// プロンプトテンプレートを設定することで、AIに対して特定の指示を出すことができます。&lt;br /&gt;
// プロンプトテンプレートでの生成は会話履歴とカスタムインストラクションを反映しません。&lt;br /&gt;
// テンプレートは以下の { ～ }, で囲まれたブロックを好きに増減させて利用できます。&lt;br /&gt;
const PROMPT_TEMPLATE = [&lt;br /&gt;
    // 以下はサンプルです。1階層メニューと2階層メニューが利用できます。&lt;br /&gt;
    // {&lt;br /&gt;
    //     &amp;quot;title&amp;quot;: &amp;quot;要約&amp;quot;,&lt;br /&gt;
    //     &amp;quot;prompt&amp;quot;: &amp;quot;以下の文章を要約してください。&amp;quot;&lt;br /&gt;
    // },&lt;br /&gt;
    // {&lt;br /&gt;
    //     &amp;quot;submenu_title&amp;quot;: &amp;quot;翻訳系&amp;quot;,&lt;br /&gt;
    //     &amp;quot;submenu&amp;quot;: [&lt;br /&gt;
    //         {&lt;br /&gt;
    //             &amp;quot;title&amp;quot;: &amp;quot;日本語翻訳&amp;quot;,&lt;br /&gt;
    //             &amp;quot;prompt&amp;quot;: &amp;quot;以下の文章を日本語に翻訳してください。&amp;quot;&lt;br /&gt;
    //         },&lt;br /&gt;
    //         {&lt;br /&gt;
    //             &amp;quot;title&amp;quot;: &amp;quot;英語翻訳&amp;quot;,&lt;br /&gt;
    //             &amp;quot;prompt&amp;quot;: &amp;quot;以下の文章を英語に翻訳してください。&amp;quot;&lt;br /&gt;
    //         },&lt;br /&gt;
    //     ]&lt;br /&gt;
    // },&lt;br /&gt;
&lt;br /&gt;
    {&lt;br /&gt;
        &amp;quot;submenu_title&amp;quot;: &amp;quot;理解を深める&amp;quot;,&lt;br /&gt;
        &amp;quot;submenu&amp;quot;: [&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;論点を整理する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の論点を整理して。&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;論理の抜け漏れを探す&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の論点を整理し、忖度せずに鋭く、論理の抜け漏れを抽出して。そして、そこから発生する可能性のあるリスクも教えて。&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;要約する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;要約して。専門的な用語と思われる単語は簡単に解説も入れて。&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;物語対話形式で教えて&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の内容について物語対話形式で教えてください。&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
        ]&lt;br /&gt;
    },&lt;br /&gt;
    {&lt;br /&gt;
        &amp;quot;submenu_title&amp;quot;: &amp;quot;翻訳する&amp;quot;,&lt;br /&gt;
        &amp;quot;submenu&amp;quot;: [&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;日本語に翻訳&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の文章を日本語に翻訳してください。\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;出力形式は以下にすること:\n[訳]\n翻訳文...\n&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;英語に翻訳&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の文章を英語に翻訳してください。\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;出力形式は以下にすること:\n[Translation]\n翻訳文...\n&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;英語-&amp;gt;日本語 スラッシュリーディング翻訳&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;あなたは英語の先生として、以下の英文を日本語にスラッシュリーディング形式で翻訳してください。\n\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;ゴール: 英語レベル CEFR A1～A2 程度の人の英語理解を促進する\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;整形ルール:\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 英文を特定の意味の塊 (3～10語程度) 毎にスラッシュ(/)で区切る\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 各区切り内の日本語訳を括弧()内に併記\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 文の終わりはダブルスラッシュ(//)で示す\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 英語の語順に従って日本語訳を行う\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 日本語訳は自然な日本語である必要はなく、英語の語順を反映した直訳調で構わない\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 主語が省略されている場合は補って翻訳する\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 覚えるべき文法や、頻出する表現などの解説を後に入れる\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 以下の出力例のように &amp;lt;英文&amp;gt; (日本語訳) / ... とスラッシュ区切りの繰り返しになる \n\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;注意事項:\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 入力の全文を対象にして、翻訳が漏れないようにする\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;出力例:\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;Snowstorms are forecast (暴風雪が予報されている) / to hit the Sea of Japan coasts (日本海沿岸を襲うと) / of northern to eastern Japan (北部から東部の) / from Sunday through Monday. (日曜から月曜にかけて。) //\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;【解説】\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;...\n&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
        ]&lt;br /&gt;
    },&lt;br /&gt;
    {&lt;br /&gt;
        &amp;quot;submenu_title&amp;quot;: &amp;quot;プログラミング支援&amp;quot;,&lt;br /&gt;
        &amp;quot;submenu&amp;quot;: [&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;コードを洗練させる&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下のコードを洗練させてください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;コードをレビューする&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下のコードをセキュリティと保守性の観点でレビューしてください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;エラーログをデバッグする&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下のエラーログを解析し、考えられる原因と対策を提示してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;SQLクエリを最適化する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下のSQL文を最適化してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;PowerShell スクリプトを書く&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の処理を行うPowerShellスクリプトを生成してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;Shell Script を書く&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の処理を行うShell Scriptを生成してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;Linux コマンドを提案&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;あなたはLinuxエキスパートとして、以下のLinuxコマンドを提案してください。\n\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;制約条件:\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 多数のディストリビューションで採用されているメジャーなコマンドを使用し、ポータブルな実装にすること\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- bashでの実行を前提にすること\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 可能な限りシンプルな実装にすること\n\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;期待する出力:\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- コマンドの具体的な実装方法\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 各コマンドの動作説明\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 制限事項\n&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
        ]&lt;br /&gt;
    },&lt;br /&gt;
    {&lt;br /&gt;
        &amp;quot;submenu_title&amp;quot;: &amp;quot;ビジネス文書作成支援&amp;quot;,&lt;br /&gt;
        &amp;quot;submenu&amp;quot;: [&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;音声文字起こし文を整形する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下は、音声文字起こしの文章です。以下のルールに従って、文章を整形してください。&amp;quot;&lt;br /&gt;
                    + &amp;quot;整形ルール:&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 要約するなどといった文章の全体感を損なう修正はしない&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 会話の流れから不自然と思われる単語には、対象単語の直後に誤字の可能性を示唆する [*] を付ける&amp;quot;&lt;br /&gt;
                    + &amp;quot;誤字の可能性を示唆する [*] が付与された場合のみ、出力の末尾に「[*]: 誤字の可能性あり」と凡例を書き添える&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 助詞や句読点など文章上の不適合を正しくする&amp;quot;&lt;br /&gt;
                    + &amp;quot;- フィラーワードを除外する&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;誤字脱字、不正確な表現をチェック&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の2つのカテゴリーで問題点を指摘してください\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 【誤字脱字】：明確な誤字脱字および誤字の疑いがある箇所\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 【不正確・曖昧な表現】：解釈に個人差が生じる可能性がある表現、より適切な表現に改善できる箇所&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;提案書を作成する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下に対する対する提案書を、コスト・実現性・リスクを含めて作成してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;仕様書のアウトラインを作成する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の内容で仕様書のアウトラインを作成してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;手順書のアウトラインを作成する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の作業を行う手順書を、前提条件と注意事項を含めてアウトラインを作成してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;ユーザーマニュアルのアウトラインを作成する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の機能内容から、ユーザー向けマニュアルのアウトラインを作成してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;議事録を作成する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の会議内容から議事録を作成してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
        ]&lt;br /&gt;
    },&lt;br /&gt;
    {&lt;br /&gt;
        &amp;quot;submenu_title&amp;quot;: &amp;quot;分析支援&amp;quot;,&lt;br /&gt;
        &amp;quot;submenu&amp;quot;: [&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;問題を分析し、課題を抽出する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の状況から問題や潜在的リスクを分析し、課題を抽出してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;対応策を提案する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の問題・課題に対する対応策を複数提案してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;傾向を見つける&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の内容から、数値や挙動上の傾向を見つけてください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
        ]&lt;br /&gt;
    },&lt;br /&gt;
    {&lt;br /&gt;
        &amp;quot;submenu_title&amp;quot;: &amp;quot;アイデア出し&amp;quot;,&lt;br /&gt;
        &amp;quot;submenu&amp;quot;: [&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;SCAMPER法でブレインストーミング&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下のテーマについて、SCAMPER法を使って15個のアイデアを出してください。制約条件は以下の通りです：&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;解決策の幅出し&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の課題に対して、技術面、運用面、コスト面から実現可能な解決策を5つずつ提案してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;業務改善案の提案&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の既存プロセスについて、効率化、コスト削減、品質向上の観点から改善案を提案してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;逆転の発想&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の目標に対して、通常とは逆の視点からアプローチする方法を3つ提案してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;競合分析からの着想&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の内容について、業界における競合他社の特徴を分析し、差別化できる新しいアプローチを提案してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;ユースケースの列挙&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の製品・サービスについて、想定されるユースケースを10個列挙し、それぞれの価値提案を記載してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
        ]&lt;br /&gt;
    },&lt;br /&gt;
]&lt;br /&gt;
&lt;br /&gt;
// 生成AIの回答の言語設定&lt;br /&gt;
// &amp;quot;Japanese&amp;quot; や &amp;quot;English&amp;quot; を設定できます。他の言語は未検証。&lt;br /&gt;
// 自動設定にする場合は空文字 &amp;quot;&amp;quot; を設定してください。&lt;br /&gt;
const LANGUAGE = &amp;quot;Japanese&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
// 入力プロンプトの最大トークン数&lt;br /&gt;
// 超適当なトークナイザー関数により計測し、英単語は4文字1トークン、それ以外は1文字1トークンとして数えます。&lt;br /&gt;
const MAX_PROMPT_TOKEN = 6000;&lt;br /&gt;
&lt;br /&gt;
// 会話の履歴を保持する数 (質問と回答で1セット)&lt;br /&gt;
// 数を増やすと、会話の履歴が増えますが、Meryのメモリ使用量が増えたり、&lt;br /&gt;
// 使用トークン数が増えて動作が重くなる可能性があるため、適宜調整してください。&lt;br /&gt;
// document.Tag に保持されるため、開いているファイルを閉じると履歴は消えます。&lt;br /&gt;
const HISTORY_SIZE = 1;&lt;br /&gt;
&lt;br /&gt;
// 会話履歴に保持する最大トークン数&lt;br /&gt;
// 会話履歴のトークン数がこの数を超える場合、古い履歴から削除されます。&lt;br /&gt;
const MAX_HISTORY_TOKEN = 10000;&lt;br /&gt;
&lt;br /&gt;
// コンテキストメニュー番号の定義&lt;br /&gt;
const EXECUTE_AI = 1;&lt;br /&gt;
const RESET_HISTORY = 2;&lt;br /&gt;
const SET_CUSTOM_INSTRUCTION = 3;&lt;br /&gt;
const REEXECUTE_LAST_TEMPLATE_PROMPT = 4;&lt;br /&gt;
const INVALID_MENU = 9;&lt;br /&gt;
const TEMPLATE_PROMPT_THRESHOLD = 10;&lt;br /&gt;
const TEMPLATE_PROMPT_SUBMENU_THRESHOLD = 1000;&lt;br /&gt;
&lt;br /&gt;
// タグ名の定義&lt;br /&gt;
const HISTORY_TAG = &amp;quot;groq-history&amp;quot;;&lt;br /&gt;
const TEMPORARY_CUSTOM_INSTRUCTION_TAG = &amp;quot;groq-temporary-custom-instruction&amp;quot;;&lt;br /&gt;
const PREVIOUS_TEMPLATE_PROMPT_TAG = &amp;quot;groq-previous-template-prompt&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
// 生成AIモデル別設定&lt;br /&gt;
&lt;br /&gt;
// const MODEL_CONFIG = {&lt;br /&gt;
//     model: &amp;quot;meta-llama/llama-4-maverick-17b-128e-instruct&amp;quot;,&lt;br /&gt;
//     max_completion_tokens: 3500,&lt;br /&gt;
//     temperature: 0.6,&lt;br /&gt;
// }&lt;br /&gt;
&lt;br /&gt;
// const MODEL_CONFIG = {&lt;br /&gt;
//     model: &amp;quot;qwen/qwen3-32b&amp;quot;,&lt;br /&gt;
//     max_completion_tokens: 5000,&lt;br /&gt;
//     temperature: 0.6,&lt;br /&gt;
//     max_completion_tokens: 1024,&lt;br /&gt;
//     reasoning_effort: &amp;quot;default&amp;quot;,&lt;br /&gt;
//     reasoning_format: &amp;quot;hidden&amp;quot;&lt;br /&gt;
// }&lt;br /&gt;
&lt;br /&gt;
const MODEL_CONFIG = {&lt;br /&gt;
    model: &amp;quot;moonshotai/kimi-k2-instruct-0905&amp;quot;,&lt;br /&gt;
    max_completion_tokens: 4096,&lt;br /&gt;
    temperature: 0.6&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// const MODEL_CONFIG = {&lt;br /&gt;
//     model: &amp;quot;openai/gpt-oss-120b&amp;quot;,&lt;br /&gt;
//     max_completion_tokens: 4096,&lt;br /&gt;
//     temperature: 0.7,&lt;br /&gt;
//     reasoning_effort: &amp;quot;low&amp;quot;,&lt;br /&gt;
// }&lt;br /&gt;
&lt;br /&gt;
// console.log に出力する内容を Mery のアウトプットバーに表示する&lt;br /&gt;
const console = {&lt;br /&gt;
    log: function (str) {&lt;br /&gt;
        outputBar.Writeln(str)&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
const doc = document;&lt;br /&gt;
const sel = document.selection;&lt;br /&gt;
&lt;br /&gt;
main();&lt;br /&gt;
&lt;br /&gt;
function main() {&lt;br /&gt;
    const selectionIsEmpty = sel.IsEmpty&lt;br /&gt;
&lt;br /&gt;
    // プロンプトの取得&lt;br /&gt;
    if (selectionIsEmpty) {&lt;br /&gt;
        sel.StartOfLine(false, mePosLogical);&lt;br /&gt;
        sel.EndOfLine(true, mePosLogical);&lt;br /&gt;
    }&lt;br /&gt;
    let promptText = sel.Text.trim();&lt;br /&gt;
    let menuTrack = 0;&lt;br /&gt;
    while (true) {&lt;br /&gt;
        // コンテキストメニューの作成&lt;br /&gt;
        const menu = createContextMenu(selectionIsEmpty);&lt;br /&gt;
        menuTrack = menu.Track(0);&lt;br /&gt;
        // メニュー選択がキャンセルされた場合は終了&lt;br /&gt;
        if (menuTrack === 0) return;&lt;br /&gt;
&lt;br /&gt;
        // 無効なメニューの場合は無視して再表示&lt;br /&gt;
        if (menuTrack === INVALID_MENU) {&lt;br /&gt;
            continue;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        // 会話履歴のリセット&lt;br /&gt;
        if (menuTrack === RESET_HISTORY) {&lt;br /&gt;
            resetTag();&lt;br /&gt;
            continue;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        // カスタムインストラクションの設定&lt;br /&gt;
        if (menuTrack === SET_CUSTOM_INSTRUCTION) {&lt;br /&gt;
            configureTemporaryCustomInstruction();&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        break;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // プロンプトが空の場合はエラー&lt;br /&gt;
    if (promptText.length === 0) {&lt;br /&gt;
        alert(&amp;quot;プロンプトを入力してください。&amp;quot;);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
    // 文字数が多すぎる場合はエラー&lt;br /&gt;
    if (roughTokenize(promptText) &amp;gt; MAX_PROMPT_TOKEN) {&lt;br /&gt;
        alert(&amp;quot;プロンプトの文字数が多すぎます。文字数を少なくしてください。&amp;quot;);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
    // 読み取り専用の場合はエラー&lt;br /&gt;
    if (doc.ReadOnly) {&lt;br /&gt;
        alert(&amp;quot;ファイルが読み取り専用のため実行できません。&amp;quot;);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // 直前に実行したプロンプトテンプレートの再実行の場合、menuTrack を書き換え&lt;br /&gt;
    if (menuTrack === REEXECUTE_LAST_TEMPLATE_PROMPT) {&lt;br /&gt;
        if (doc.Tag.exists(PREVIOUS_TEMPLATE_PROMPT_TAG)) {&lt;br /&gt;
            menuTrack = Number(doc.tag[PREVIOUS_TEMPLATE_PROMPT_TAG]);&lt;br /&gt;
        } else {&lt;br /&gt;
            alert(&amp;quot;直前に実行したプロンプトテンプレートがありません。&amp;quot;);&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // テンプレートの場合はプロンプトを変更&lt;br /&gt;
    const isTemplate = menuTrack &amp;gt;= TEMPLATE_PROMPT_THRESHOLD;&lt;br /&gt;
    const isSubmenu = menuTrack &amp;gt;= TEMPLATE_PROMPT_SUBMENU_THRESHOLD;&lt;br /&gt;
    if (isSubmenu) {&lt;br /&gt;
        const parentMenuTrack = Number(String(menuTrack).slice(0, 2));&lt;br /&gt;
        const subMenuTrack = Number(String(menuTrack).slice(2));&lt;br /&gt;
        promptText = &amp;quot;=== INSTRUCTIONS START HERE ===\n\n&amp;quot;&lt;br /&gt;
            + PROMPT_TEMPLATE[parentMenuTrack - TEMPLATE_PROMPT_THRESHOLD].submenu[subMenuTrack].prompt&lt;br /&gt;
            + &amp;quot;\n\n=== INSTRUCTIONS END HERE ===\n\n&amp;quot;&lt;br /&gt;
            + &amp;quot;=== INPUT DATA STARTS HERE ===\n\n&amp;quot;&lt;br /&gt;
            + promptText&lt;br /&gt;
            + &amp;quot;\n\n=== INPUT DATA END HERE ===&amp;quot;;&lt;br /&gt;
    } else if (isTemplate) {&lt;br /&gt;
        promptText = &amp;quot;=== INSTRUCTIONS START HERE ===\n\n&amp;quot;&lt;br /&gt;
            + PROMPT_TEMPLATE[menuTrack - TEMPLATE_PROMPT_THRESHOLD].prompt&lt;br /&gt;
            + &amp;quot;\n\n=== INSTRUCTIONS END HERE ===\n\n&amp;quot;&lt;br /&gt;
            + &amp;quot;=== INPUT DATA STARTS HERE ===\n\n&amp;quot;&lt;br /&gt;
            + promptText&lt;br /&gt;
            + &amp;quot;\n\n=== INPUT DATA END HERE ===&amp;quot;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // テンプレートの場合は直前に実行したプロンプトテンプレートの番号としてタグに保存&lt;br /&gt;
    if (isTemplate) {&lt;br /&gt;
        addPreviousTemplatePromptNumberToTag(menuTrack);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    try {&lt;br /&gt;
        // 処理終了を待機&lt;br /&gt;
        shell.KeepRunning = true;&lt;br /&gt;
&lt;br /&gt;
        // カーソル位置移動&lt;br /&gt;
        sel.SetActivePoint(mePosLogical, sel.GetBottomPointX(mePosLogical), sel.GetBottomPointY(mePosLogical));&lt;br /&gt;
        sel.EndOfLine(false, mePosLogical);&lt;br /&gt;
        if (sel.GetActivePointX(mePosLogical) &amp;gt; 1) sel.NewLine();&lt;br /&gt;
&lt;br /&gt;
        ShowTip(&amp;quot;リクエスト中.....&amp;quot;, meShowTipPosCaret);&lt;br /&gt;
&lt;br /&gt;
        // APIキーの取得&lt;br /&gt;
        const apiKey = shell.getEnv(&amp;quot;GROQ_API_KEY&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
        // groq API に問い合わせ&lt;br /&gt;
        sel.Text = &amp;quot;\n---\n\n&amp;quot;&lt;br /&gt;
        document.KeepScrollPos = true;&lt;br /&gt;
        callApi(apiKey, promptText, isTemplate);&lt;br /&gt;
        if (sel.GetActivePointX(mePosLogical) &amp;gt; 2) {&lt;br /&gt;
            sel.Text = &amp;quot;\n\n---\n\n\n&amp;quot;&lt;br /&gt;
        } else {&lt;br /&gt;
            sel.Text = &amp;quot;\n---\n\n\n&amp;quot;&lt;br /&gt;
        }&lt;br /&gt;
    } catch (e) {&lt;br /&gt;
        sel.Text = &amp;quot;&amp;lt;処理中にエラー発生&amp;gt;\n&amp;quot; + e.message + &amp;quot;\n&amp;quot; + e.stack;&lt;br /&gt;
    } finally {&lt;br /&gt;
        // 処理終了待機を解除&lt;br /&gt;
        shell.KeepRunning = false;&lt;br /&gt;
        ShowTip(&amp;quot;&amp;quot;, meShowTipHide);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function createContextMenu(selectionIsEmpty) {&lt;br /&gt;
    const menu = CreatePopupMenu();&lt;br /&gt;
    menu.Add(&amp;quot;※注意※ プライバシー・機密情報を送信しないこと&amp;quot;, INVALID_MENU);&lt;br /&gt;
    menu.Add(&amp;quot;&amp;quot;, 0, meMenuSeparator);&lt;br /&gt;
    let t = &amp;quot;&amp;quot;;&lt;br /&gt;
    if (selectionIsEmpty) {&lt;br /&gt;
        t = &amp;quot;アクティブ行の内容で相談 (&amp;amp;A)&amp;quot;;&lt;br /&gt;
    } else {&lt;br /&gt;
        t = &amp;quot;選択範囲の内容で相談 (&amp;amp;A)&amp;quot;;&lt;br /&gt;
    }&lt;br /&gt;
    menu.Add(t, EXECUTE_AI);&lt;br /&gt;
    if (HISTORY_SIZE &amp;lt;= 0) {&lt;br /&gt;
        menu.Add(&amp;quot;├ 会話履歴をクリア (履歴が無効になっています)&amp;quot;, RESET_HISTORY, meMenuGrayed);&lt;br /&gt;
    } else if (doc.Tag.exists(HISTORY_TAG)) {&lt;br /&gt;
        const turn = JSON.parse(doc.tag[HISTORY_TAG]).length / 2;&lt;br /&gt;
        menu.Add(&amp;quot;├ 会話履歴をクリア (現在の会話数: &amp;quot; + turn + &amp;quot;) (&amp;amp;C)&amp;quot;, RESET_HISTORY);&lt;br /&gt;
    } else {&lt;br /&gt;
        menu.Add(&amp;quot;├ 会話履歴をクリア (現在の会話数: 0) (&amp;amp;C)&amp;quot;, RESET_HISTORY, meMenuGrayed);&lt;br /&gt;
    }&lt;br /&gt;
    menu.Add(&amp;quot;└ カスタムインストラクションを変更 (&amp;amp;I)&amp;quot;, SET_CUSTOM_INSTRUCTION);&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    // テンプレート数チェック&lt;br /&gt;
    // 2桁まで許容&lt;br /&gt;
    const ITEMS_MUX = 99;&lt;br /&gt;
    if (PROMPT_TEMPLATE.length &amp;gt; ITEMS_MUX - TEMPLATE_PROMPT_THRESHOLD) {&lt;br /&gt;
        throw new Error(&amp;quot;プロンプトテンプレートの数が多すぎます。&amp;quot;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // テンプレートメニューの追加&lt;br /&gt;
    if (PROMPT_TEMPLATE.length &amp;gt; 0) {&lt;br /&gt;
        const subMenu = CreatePopupMenu();&lt;br /&gt;
        let previousMenuString = &amp;quot;&amp;quot;;&lt;br /&gt;
        for (let i = 0; i &amp;lt; PROMPT_TEMPLATE.length; i++) {&lt;br /&gt;
            let accelerationKey = &amp;quot;&amp;quot;;&lt;br /&gt;
            if (i === 9) {&lt;br /&gt;
                accelerationKey = &amp;quot; (&amp;amp;0)&amp;quot;;&lt;br /&gt;
            } else if (i &amp;lt; 10) {&lt;br /&gt;
                accelerationKey = &amp;quot; (&amp;amp;&amp;quot; + (i + 1) + &amp;quot;)&amp;quot;;&lt;br /&gt;
            }&lt;br /&gt;
            const menuNum = TEMPLATE_PROMPT_THRESHOLD + i;&lt;br /&gt;
            if (PROMPT_TEMPLATE[i].submenu) {&lt;br /&gt;
                const subMenuList = PROMPT_TEMPLATE[i].submenu;&lt;br /&gt;
                // テンプレート数チェック&lt;br /&gt;
                if (subMenuList.length &amp;gt; ITEMS_MUX) {&lt;br /&gt;
                    throw new Error(&amp;quot;プロンプトテンプレート (サブメニュー) の数が多すぎます。&amp;quot;);&lt;br /&gt;
                }&lt;br /&gt;
                const subSubMenu = CreatePopupMenu();&lt;br /&gt;
                const subMenuPrefix = String(menuNum);&lt;br /&gt;
                for (let j = 0; j &amp;lt; subMenuList.length; j++) {&lt;br /&gt;
                    let subAccelerationKey = &amp;quot;&amp;quot;;&lt;br /&gt;
                    if (j === 9) {&lt;br /&gt;
                        subAccelerationKey = &amp;quot; (&amp;amp;0)&amp;quot;;&lt;br /&gt;
                    } else if (j &amp;lt; 10) {&lt;br /&gt;
                        subAccelerationKey = &amp;quot; (&amp;amp;&amp;quot; + (j + 1) + &amp;quot;)&amp;quot;;&lt;br /&gt;
                    }&lt;br /&gt;
                    // ES5 compatible version without padStart&lt;br /&gt;
                    const subMenuNum = Number(subMenuPrefix + (j &amp;lt; 10 ? &amp;quot;0&amp;quot; + j : String(j)));&lt;br /&gt;
                    subSubMenu.Add(subMenuList[j].title + subAccelerationKey, subMenuNum);&lt;br /&gt;
                    if (subMenuNum === doc.tag[PREVIOUS_TEMPLATE_PROMPT_TAG]) {&lt;br /&gt;
                        previousMenuString = subMenuList[j].title;&lt;br /&gt;
                    }&lt;br /&gt;
                }&lt;br /&gt;
                subMenu.AddPopup(PROMPT_TEMPLATE[i].submenu_title + accelerationKey, subSubMenu);&lt;br /&gt;
            } else {&lt;br /&gt;
                subMenu.Add(PROMPT_TEMPLATE[i].title + accelerationKey, menuNum);&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        if (selectionIsEmpty) {&lt;br /&gt;
            t = &amp;quot;プロンプト テンプレート (対象: アクティブ行) (&amp;amp;T)&amp;quot;;&lt;br /&gt;
        } else {&lt;br /&gt;
            t = &amp;quot;プロンプト テンプレート (対象: 選択範囲) (&amp;amp;T)&amp;quot;;&lt;br /&gt;
        }&lt;br /&gt;
        menu.AddPopup(t, subMenu);&lt;br /&gt;
&lt;br /&gt;
        t = &amp;quot;└ 直前に実行したプロンプト テンプレートを再実行 (&amp;amp;R)&amp;quot;;&lt;br /&gt;
        if (doc.Tag.exists(PREVIOUS_TEMPLATE_PROMPT_TAG)) {&lt;br /&gt;
            menu.Add(t, REEXECUTE_LAST_TEMPLATE_PROMPT);&lt;br /&gt;
            menu.Add(`    (直前実行: ${previousMenuString})`, REEXECUTE_LAST_TEMPLATE_PROMPT);&lt;br /&gt;
        } else {&lt;br /&gt;
            menu.Add(t, REEXECUTE_LAST_TEMPLATE_PROMPT, meMenuGrayed);&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return menu;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function escapeRequestBodyForCRuntimeParser(requestBody) {&lt;br /&gt;
    // Cランタイムパーサーに渡すためのエスケープ処理&lt;br /&gt;
    return JSON.stringify(requestBody)&lt;br /&gt;
        .replace(/((?:\\)*)&amp;quot;/g, function (match, p1) {&lt;br /&gt;
            if (p1) {&lt;br /&gt;
                const backslashCount = p1.length;&lt;br /&gt;
                let backslash = &amp;quot;&amp;quot;;&lt;br /&gt;
                backslash = &amp;quot;\\&amp;quot;.repeat(backslashCount);&lt;br /&gt;
                return backslash + &amp;quot;\\&amp;quot; + match;&lt;br /&gt;
            } else {&lt;br /&gt;
                return &amp;quot;\\&amp;quot; + match;&lt;br /&gt;
            }&lt;br /&gt;
        });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function callApi(apiKey, promptText, isTemplate) {&lt;br /&gt;
    try {&lt;br /&gt;
        // fetch で Groq API にリクエスト&lt;br /&gt;
        const requestBody = {&lt;br /&gt;
            messages: createRequestMessages(promptText, isTemplate),&lt;br /&gt;
            stream: false,&lt;br /&gt;
        };&lt;br /&gt;
        for (let key in MODEL_CONFIG) {&lt;br /&gt;
            requestBody[key] = MODEL_CONFIG[key];&lt;br /&gt;
        }&lt;br /&gt;
        if (isTemplate) {&lt;br /&gt;
            // テンプレートの場合は応答を決定論的にする&lt;br /&gt;
            // テンプレート以外の場合はデフォルト値とするために指定しない&lt;br /&gt;
            requestBody.temperature = 0.3;&lt;br /&gt;
        }&lt;br /&gt;
        const url = &#039;https://api.groq.com/openai/v1/chat/completions&#039;;&lt;br /&gt;
&lt;br /&gt;
        const requestJson = escapeRequestBodyForCRuntimeParser(requestBody);&lt;br /&gt;
        // curl で Groq API にリクエスト&lt;br /&gt;
        const command = &amp;quot;curl -s -X POST &amp;quot; + url&lt;br /&gt;
            + &#039; -H &amp;quot;Content-Type: application/json&amp;quot;&#039;&lt;br /&gt;
            + &#039; -H &amp;quot;Authorization: Bearer &#039; + apiKey + &#039;&amp;quot;&#039;&lt;br /&gt;
            + &#039; -d &amp;quot;&#039; + requestJson + &#039;&amp;quot;&#039;;&lt;br /&gt;
        const comResult = shell.Exec(command);&lt;br /&gt;
&lt;br /&gt;
        if (comResult.ExitCode != 0) {&lt;br /&gt;
            sel.Text = &amp;quot;リクエストでエラーが発生しました。\n&amp;quot;&lt;br /&gt;
                + &amp;quot;  エラーコード: &amp;quot; + comResult.ExitCode + &amp;quot;\n&amp;quot;&lt;br /&gt;
                + &amp;quot;  標準出力: &amp;quot; + (comResult.StdOut.trim() ? comResult.StdOut.trim() : &amp;quot;-&amp;quot;) + &amp;quot;\n&amp;quot;&lt;br /&gt;
                + &amp;quot;  エラー出力: &amp;quot; + (comResult.StdErr.trim() ? comResult.StdErr.trim() : &amp;quot;-&amp;quot;);&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
        const responseRaw = comResult.StdOut;&lt;br /&gt;
&lt;br /&gt;
        const response = JSON.parse(responseRaw);&lt;br /&gt;
&lt;br /&gt;
        let ret = &amp;quot;&amp;quot;;&lt;br /&gt;
        const choices = response.choices;&lt;br /&gt;
        if (!choices) {&lt;br /&gt;
            sel.Text = &amp;quot;リクエストでエラーが発生しました。\n&amp;quot;&lt;br /&gt;
                + &amp;quot;  標準出力: &amp;quot; + (comResult.StdOut.trim() ? comResult.StdOut.trim() : &amp;quot;-&amp;quot;) + &amp;quot;\n&amp;quot;&lt;br /&gt;
                + &amp;quot;  エラー出力: &amp;quot; + (comResult.StdErr.trim() ? comResult.StdErr.trim() : &amp;quot;-&amp;quot;);&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (choices &amp;amp;&amp;amp; choices.length &amp;gt; 0) {&lt;br /&gt;
            choices.forEach(function (choice) {&lt;br /&gt;
                const message = choice.message;&lt;br /&gt;
                if (message) {&lt;br /&gt;
                    ret += message.content;&lt;br /&gt;
                }&lt;br /&gt;
            });&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        const usage = response.usage;&lt;br /&gt;
        let usageText = &amp;quot;&amp;quot;;&lt;br /&gt;
        if (usage) {&lt;br /&gt;
            usageText += &amp;quot;\n\n(利用トークン数: プロンプト=&amp;quot; + usage.prompt_tokens&lt;br /&gt;
                + &amp;quot;, 応答=&amp;quot; + usage.completion_tokens&lt;br /&gt;
                + &amp;quot;, 合計=&amp;quot; + usage.total_tokens + &amp;quot;)&amp;quot;;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        const formattedOutput = formatOutput(ret);&lt;br /&gt;
&lt;br /&gt;
        sel.Text = formattedOutput + usageText;&lt;br /&gt;
&lt;br /&gt;
        // 結果を会話履歴に追加&lt;br /&gt;
        if (HISTORY_SIZE &amp;gt; 0 &amp;amp;&amp;amp; !isTemplate) {&lt;br /&gt;
            appendReturnTextToTag(promptText, ret);&lt;br /&gt;
        }&lt;br /&gt;
    } catch (e) {&lt;br /&gt;
        throw e;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function formatOutput(text) {&lt;br /&gt;
    // Markdownを整形する&lt;br /&gt;
    text = formatMarkdown(text);&lt;br /&gt;
    return text&lt;br /&gt;
        // クォーテーションの正規化&lt;br /&gt;
        .replace(/[“”]/g, &#039;&amp;quot;&#039;)&lt;br /&gt;
        .replace(/[‘’]/g, &amp;quot;&#039;&amp;quot;)&lt;br /&gt;
        // 行末のスペースを削除&lt;br /&gt;
        .replace(/[ \t]+$/gm, &#039;&#039;)&lt;br /&gt;
        // 1️⃣ などの数字を半角に変換&lt;br /&gt;
        .replace(/([1-9#*]+)\ufe0f\u20e3/g, &#039;($1)&#039;)&lt;br /&gt;
        ;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/**&lt;br /&gt;
 * Markdown を整形する（ES5 対応）&lt;br /&gt;
 *  1. コードブロック ```～``` を保護して整形対象から除外&lt;br /&gt;
 *  2. 見出し行の前後に空行を 1 行ずつ確保&lt;br /&gt;
 *  3. **太字** / *斜体* / `コード` の外側に半角スペースを 1 つ確保&lt;br /&gt;
 * @param  {string} md  元の Markdown&lt;br /&gt;
 * @return {string}     整形後 Markdown&lt;br /&gt;
 */&lt;br /&gt;
function formatMarkdown(md) {&lt;br /&gt;
    /* === 0. コードブロックをプレースホルダーへ退避 ============== */&lt;br /&gt;
    const codeBlocks = [];      // 元のコードブロックを格納&lt;br /&gt;
    let body = md.replace(/```[\s\S]*?```/g, function (match) {&lt;br /&gt;
        const key = &#039;@@@CODE@BLOCK@&#039; + codeBlocks.length + &#039;@@@&#039;;&lt;br /&gt;
        codeBlocks.push(match);&lt;br /&gt;
        return key;             // プレースホルダーを本文に残す&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    /* === 見出しの前後に空行を挿入 ============================ */&lt;br /&gt;
&lt;br /&gt;
    /* 見出し直前に空行（先頭行以外で空行が無い場合） */&lt;br /&gt;
    body = body.replace(&lt;br /&gt;
        /([^\r\n])(\r?\n)(#{1,6}\s)/g,&lt;br /&gt;
        function (_, prev, br, head) { return prev + br + &#039;\n&#039; + head; }&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
    /* 見出し直後に空行（既に空行が無い場合） */&lt;br /&gt;
    body = body.replace(&lt;br /&gt;
        /^(#{1,6}\s[^\n]+?)(\r?\n)(?!\r?\n)/gm,&lt;br /&gt;
        &#039;$1$2\n&#039;&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
    /* === コードブロックを復元 ================================= */&lt;br /&gt;
    body = body.replace(/@@@CODE@BLOCK@(\d+)@@@/g, function (_, idx) {&lt;br /&gt;
        return codeBlocks[Number(idx)];&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    return body;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/**&lt;br /&gt;
 * 斜体 (*…* / _…_) の外側にスペースを追加しつつ&lt;br /&gt;
 * **太字** の中へ誤マッチしないよう制御&lt;br /&gt;
 */&lt;br /&gt;
function surroundItalic(text) {&lt;br /&gt;
    /* *italic* */&lt;br /&gt;
    text = text.replace(&lt;br /&gt;
        /(^|[^*])(\*(?![*\s])[^*\n]*?[^*\n\s]\*(?!\*))(($)|[^*])/gm,&lt;br /&gt;
        function (_, pre, core, post) {&lt;br /&gt;
            pre = pre === &#039;&#039; || pre.match(/\s/) ? pre : pre + &#039; &#039;;&lt;br /&gt;
            post = post === &#039;&#039; || post.match(/\s/) ? post : &#039; &#039; + post;&lt;br /&gt;
            return pre + core + post;&lt;br /&gt;
        }&lt;br /&gt;
    );&lt;br /&gt;
    /* _italic_ */&lt;br /&gt;
    text = text.replace(&lt;br /&gt;
        /(^|[^_])(\_(?![_\s])[^_\n]*?[^_\n\s]\_(?!\_))(($)|[^_])/gm,&lt;br /&gt;
        function (_, pre, core, post) {&lt;br /&gt;
            pre = pre === &#039;&#039; || pre.match(/\s/) ? pre : pre + &#039; &#039;;&lt;br /&gt;
            post = post === &#039;&#039; || post.match(/\s/) ? post : &#039; &#039; + post;&lt;br /&gt;
            return pre + core + post;&lt;br /&gt;
        }&lt;br /&gt;
    );&lt;br /&gt;
    return text;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function roughTokenize(text) {&lt;br /&gt;
    // 超適当なトークナイザ。トークン数をざっくりと多めに数える&lt;br /&gt;
    // 空白文字・ハイフン・アンダースコア区切りの単語に分割して、&lt;br /&gt;
    // 単語がアルファベットのみ、数字のみの場合には 4文字=1トークン として数える。&lt;br /&gt;
    // 単語にアルファベット以外の文字が含まれる場合は 1文字=1トークン として数える。&lt;br /&gt;
&lt;br /&gt;
    const words = text.split(/[\s\-_]+/);&lt;br /&gt;
    let totalTokens = 0;&lt;br /&gt;
&lt;br /&gt;
    for (let i = 0; i &amp;lt; words.length; i++) {&lt;br /&gt;
        const word = words[i];&lt;br /&gt;
        if (!word) continue; // 空の単語をスキップ&lt;br /&gt;
&lt;br /&gt;
        if (/^[a-zA-Z]+$/.test(word) || /^[0-9]+$/.test(word)) {&lt;br /&gt;
            totalTokens += Math.ceil(word.length / 4);&lt;br /&gt;
        } else {&lt;br /&gt;
            totalTokens += word.length;&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return totalTokens;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// document.Tag の内容を加味してgroqのリクエストを作成&lt;br /&gt;
function createRequestMessages(promptText, isTemplate) {&lt;br /&gt;
    const customInstructionText = buildCustomInstructionText();&lt;br /&gt;
    // テンプレートの場合は、会話履歴とカスタムインストラクションを反映しない&lt;br /&gt;
    if (isTemplate) {&lt;br /&gt;
        const request = [&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;role&amp;quot;: &amp;quot;user&amp;quot;,&lt;br /&gt;
                &amp;quot;content&amp;quot;: [{ &amp;quot;type&amp;quot;: &amp;quot;text&amp;quot;, &amp;quot;text&amp;quot;: promptText }]&lt;br /&gt;
            }&lt;br /&gt;
        ];&lt;br /&gt;
        if (customInstructionText) {&lt;br /&gt;
            request.unshift({&lt;br /&gt;
                &amp;quot;role&amp;quot;: &amp;quot;system&amp;quot;,&lt;br /&gt;
                &amp;quot;content&amp;quot;: customInstructionText&lt;br /&gt;
            })&lt;br /&gt;
        }&lt;br /&gt;
        return request;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    const tag = document.Tag;&lt;br /&gt;
&lt;br /&gt;
    // 以前の会話が存在しない場合は新規リクエスト&lt;br /&gt;
    if (!tag.exists(HISTORY_TAG)) {&lt;br /&gt;
        const request = [{&lt;br /&gt;
            &amp;quot;role&amp;quot;: &amp;quot;user&amp;quot;,&lt;br /&gt;
            &amp;quot;content&amp;quot;: [{ &amp;quot;type&amp;quot;: &amp;quot;text&amp;quot;, &amp;quot;text&amp;quot;: promptText }]&lt;br /&gt;
        }];&lt;br /&gt;
        if (customInstructionText) {&lt;br /&gt;
            request.unshift({&lt;br /&gt;
                &amp;quot;role&amp;quot;: &amp;quot;system&amp;quot;,&lt;br /&gt;
                &amp;quot;content&amp;quot;: customInstructionText&lt;br /&gt;
            })&lt;br /&gt;
        }&lt;br /&gt;
        return request;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // 会話履歴が存在する場合は、履歴を含めてリクエスト&lt;br /&gt;
    const request = JSON.parse(tag[HISTORY_TAG]);&lt;br /&gt;
    if (customInstructionText) {&lt;br /&gt;
        request.unshift({&lt;br /&gt;
            &amp;quot;role&amp;quot;: &amp;quot;system&amp;quot;,&lt;br /&gt;
            &amp;quot;content&amp;quot;: customInstructionText&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
    request.push({&lt;br /&gt;
        &amp;quot;role&amp;quot;: &amp;quot;user&amp;quot;,&lt;br /&gt;
        &amp;quot;content&amp;quot;: promptText&lt;br /&gt;
    });&lt;br /&gt;
    return request;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// document.Tag にgroqの応答を追加&lt;br /&gt;
function appendReturnTextToTag(userText, modelText) {&lt;br /&gt;
    const tag = document.Tag;&lt;br /&gt;
    if (!tag.exists(HISTORY_TAG)) {&lt;br /&gt;
        tag[HISTORY_TAG] = JSON.stringify([&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;role&amp;quot;: &amp;quot;user&amp;quot;,&lt;br /&gt;
                &amp;quot;content&amp;quot;: userText&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;role&amp;quot;: &amp;quot;assistant&amp;quot;,&lt;br /&gt;
                &amp;quot;content&amp;quot;: modelText&lt;br /&gt;
            }&lt;br /&gt;
        ]);&lt;br /&gt;
    } else {&lt;br /&gt;
        const groqText = JSON.parse(tag[HISTORY_TAG]);&lt;br /&gt;
        groqText.push({&lt;br /&gt;
            &amp;quot;role&amp;quot;: &amp;quot;user&amp;quot;,&lt;br /&gt;
            &amp;quot;content&amp;quot;: userText&lt;br /&gt;
        });&lt;br /&gt;
        groqText.push({&lt;br /&gt;
            &amp;quot;role&amp;quot;: &amp;quot;assistant&amp;quot;,&lt;br /&gt;
            &amp;quot;content&amp;quot;: modelText&lt;br /&gt;
        });&lt;br /&gt;
        // 会話セット (user, model 1往復) が特定セット数を超えた場合、最初のセットを削除&lt;br /&gt;
        if (groqText.length &amp;gt; HISTORY_SIZE * 2) {&lt;br /&gt;
            groqText.splice(0, 2);&lt;br /&gt;
        }&lt;br /&gt;
        // 会話履歴全体が MAX_HISTORY_TOKEN を超えた場合、最初のセットを削除&lt;br /&gt;
        let joinedText = &amp;quot;&amp;quot;;&lt;br /&gt;
        for (let i = 0; i &amp;lt; groqText.length; i++) {&lt;br /&gt;
            joinedText += groqText[i].content + &amp;quot;\n&amp;quot;;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        while (roughTokenize(joinedText) &amp;gt; MAX_HISTORY_TOKEN) {&lt;br /&gt;
            groqText.splice(0, 2);&lt;br /&gt;
            joinedText = &amp;quot;&amp;quot;;&lt;br /&gt;
            for (let i = 0; i &amp;lt; groqText.length; i++) {&lt;br /&gt;
                joinedText += groqText[i].content + &amp;quot;\n&amp;quot;;&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        tag[HISTORY_TAG] = JSON.stringify(groqText);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function resetTag() {&lt;br /&gt;
    const tag = doc.Tag;&lt;br /&gt;
    if (tag.exists(HISTORY_TAG)) {&lt;br /&gt;
        tag.remove(HISTORY_TAG);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getLocalIsoDateTimeString() {&lt;br /&gt;
    const now = new Date();&lt;br /&gt;
    const pad = function (num) {&lt;br /&gt;
        return (num &amp;lt; 10 ? &amp;quot;0&amp;quot; : &amp;quot;&amp;quot;) + num;&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    const year = now.getFullYear();&lt;br /&gt;
    const month = pad(now.getMonth() + 1);&lt;br /&gt;
    const day = pad(now.getDate());&lt;br /&gt;
    const hours = pad(now.getHours());&lt;br /&gt;
    const minutes = pad(now.getMinutes());&lt;br /&gt;
    const seconds = pad(now.getSeconds());&lt;br /&gt;
&lt;br /&gt;
    const offsetMinutes = now.getTimezoneOffset();&lt;br /&gt;
    const offsetSign = offsetMinutes &amp;lt;= 0 ? &amp;quot;+&amp;quot; : &amp;quot;-&amp;quot;;&lt;br /&gt;
    const absOffsetHours = pad(Math.floor(Math.abs(offsetMinutes) / 60));&lt;br /&gt;
    const absOffsetMinutes = pad(Math.abs(offsetMinutes) % 60);&lt;br /&gt;
&lt;br /&gt;
    const isoLikeLocal = year + &amp;quot;-&amp;quot; + month + &amp;quot;-&amp;quot; + day&lt;br /&gt;
        + &amp;quot;T&amp;quot; + hours + &amp;quot;:&amp;quot; + minutes + &amp;quot;:&amp;quot; + seconds&lt;br /&gt;
        + offsetSign + absOffsetHours + &amp;quot;:&amp;quot; + absOffsetMinutes;&lt;br /&gt;
    return isoLikeLocal;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function baseCustomInstruction() {&lt;br /&gt;
    const language = LANGUAGE.trim();&lt;br /&gt;
    return &amp;quot;The current date and time for this user is &amp;quot; + getLocalIsoDateTimeString() + &amp;quot;. You cannot know for sure what happens after the knowledge cut-off date.&amp;quot;&lt;br /&gt;
        + (language ? &amp;quot;**Respond in &amp;quot; + language + &amp;quot;**\n&amp;quot; : &amp;quot;&amp;quot;)&lt;br /&gt;
        + &amp;quot;Do not hallucinate.\n&amp;quot;&lt;br /&gt;
    // + &amp;quot;Do NOT EVER use full-width brackets \&amp;quot;（）\&amp;quot;, but use half-width brackets \&amp;quot;()\&amp;quot;. Enclose the outside of the half-width brackets with half-width spaces.\n&amp;quot;&lt;br /&gt;
    // + &amp;quot;When using Markdown formatting, you MUST follow these rules:\n&amp;quot;&lt;br /&gt;
    // + &amp;quot;- Rule 1: Insert a blank line below the header line (A header line begins with one or more # characters).\n&amp;quot;&lt;br /&gt;
    // + &amp;quot;- Rule 2: Insert spaces before and after bold (surrounded by **), italic (surrounded by *), and inline code (surrounded by `). For example: \&amp;quot;私は **元気** です。\&amp;quot;\n&amp;quot;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function buildCustomInstructionText() {&lt;br /&gt;
    let customInstruction = getTemporaryCustomInstruction();&lt;br /&gt;
    if (!customInstruction) {&lt;br /&gt;
        // 一時的なカスタムインストラクションがない場合はデフォルトを使用&lt;br /&gt;
        customInstruction = DEFAULT_CUSTOM_INSTRUCTION;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    customInstruction = customInstruction.replace(/^[\s　]+|[\s　]+$/g, &amp;quot;&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
    return baseCustomInstruction().trim()&lt;br /&gt;
        + (customInstruction ? &amp;quot;\n&amp;quot; + customInstruction : &amp;quot;&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function configureTemporaryCustomInstruction() {&lt;br /&gt;
    let tempCustomInstruction = getTemporaryCustomInstruction();&lt;br /&gt;
    const prompt = Prompt(&amp;quot;カスタムインストラクションを変更 ※変更すると会話履歴がリセットされます&amp;quot;, tempCustomInstruction ? tempCustomInstruction : DEFAULT_CUSTOM_INSTRUCTION);&lt;br /&gt;
    if (prompt) {&lt;br /&gt;
        if (prompt === DEFAULT_CUSTOM_INSTRUCTION) {&lt;br /&gt;
            // デフォルトと同じ値の場合はタグを削除&lt;br /&gt;
            setTemporaryCustomInstruction(&amp;quot;&amp;quot;);&lt;br /&gt;
        } else if (prompt !== tempCustomInstruction) {&lt;br /&gt;
            // デフォルト以外の値が設定された場合は保存&lt;br /&gt;
            setTemporaryCustomInstruction(prompt);&lt;br /&gt;
        }&lt;br /&gt;
    } else if (prompt !== null) {&lt;br /&gt;
        // 空文字でOKされた場合はデフォルトに戻すか確認&lt;br /&gt;
        if (prompt === &amp;quot;&amp;quot;) {&lt;br /&gt;
            if (Confirm(&amp;quot;カスタムインストラクションをデフォルトに戻しますか？\n※カスタムインストラクションを空にしたい場合は [キャンセル] を選択してください。&amp;quot;)) {&lt;br /&gt;
                setTemporaryCustomInstruction(&amp;quot;&amp;quot;);&lt;br /&gt;
            } else {&lt;br /&gt;
                setTemporaryCustomInstruction(&amp;quot; &amp;quot;);&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // 変更が加わっている場合は会話履歴をリセット&lt;br /&gt;
    if (tempCustomInstruction !== getTemporaryCustomInstruction()) {&lt;br /&gt;
        resetTag();&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getTemporaryCustomInstruction() {&lt;br /&gt;
    const tag = doc.Tag;&lt;br /&gt;
    if (tag.exists(TEMPORARY_CUSTOM_INSTRUCTION_TAG)) {&lt;br /&gt;
        return tag[TEMPORARY_CUSTOM_INSTRUCTION_TAG];&lt;br /&gt;
    } else {&lt;br /&gt;
        return &amp;quot;&amp;quot;;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function setTemporaryCustomInstruction(temporaryCustomInstruction) {&lt;br /&gt;
    const tag = doc.Tag;&lt;br /&gt;
    if (temporaryCustomInstruction) {&lt;br /&gt;
        tag[TEMPORARY_CUSTOM_INSTRUCTION_TAG] = temporaryCustomInstruction;&lt;br /&gt;
    } else {&lt;br /&gt;
        if (tag.exists(TEMPORARY_CUSTOM_INSTRUCTION_TAG)) {&lt;br /&gt;
            tag.remove(TEMPORARY_CUSTOM_INSTRUCTION_TAG);&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function addPreviousTemplatePromptNumberToTag(menuTrack) {&lt;br /&gt;
    const tag = doc.Tag;&lt;br /&gt;
    tag[PREVIOUS_TEMPLATE_PROMPT_TAG] = menuTrack;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function inputProgressChar(pos) {&lt;br /&gt;
    sel.SetActivePos(pos);&lt;br /&gt;
    sel.Text = &amp;quot;.&amp;quot;;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 変更履歴 ==&lt;br /&gt;
&lt;br /&gt;
* 2025-12-06 &amp;lt;code&amp;gt;#language = &amp;quot;quickjs&amp;quot;&amp;lt;/code&amp;gt; 指定を追加。各種リファクタリング&lt;br /&gt;
* 2025-12-02 curl実行時のリクエスト文字列エスケープ処理を調整&lt;br /&gt;
* 2025-12-01 curl実行時のリクエスト文字列エスケープ処理を調整&lt;br /&gt;
* 2025-11-29 デフォルトカスタム指示を調整。Markdown出力の整形処理を調整など&lt;br /&gt;
* 2025-09-12 Kimi K2 0905 モデルに対応。Markdown出力の整形処理を追加&lt;br /&gt;
* 2025-04-13 初版&lt;/div&gt;</summary>
		<author><name>Yuko</name></author>
	</entry>
	<entry>
		<id>https://www.haijin-boys.com/wiki/index.php?title=Groq_%E3%81%AB%E7%9B%B8%E8%AB%87&amp;diff=9334</id>
		<title>Groq に相談</title>
		<link rel="alternate" type="text/html" href="https://www.haijin-boys.com/wiki/index.php?title=Groq_%E3%81%AB%E7%9B%B8%E8%AB%87&amp;diff=9334"/>
		<updated>2025-12-06T07:19:01Z</updated>

		<summary type="html">&lt;p&gt;Yuko: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== 概要 ==&lt;br /&gt;
&lt;br /&gt;
生成AIプラットフォームである [https://groq.com GroqCloud] で各種AIを利用するためのMeryマクロです。&lt;br /&gt;
※GroqCloud で提供されるモデルは複数あるため、私がよく利用するモデルを中心に対応しています。&lt;br /&gt;
&lt;br /&gt;
「Google Geminiに相談」マクロでは動作中のストリーム通信に対応するためにV8エンジンで実行していましたが、V8エンジン版は使用上の制限からマクロ実行を開始するまで1秒程度のタイムラグが発生していました。GroqCloudの売りである超高速レスポンスを見込んで本マクロではストリーム通信を使わないようにしました。これにより従来のJScriptで処理を組み立てることができるため、前述のタイムラグが起こらないようになりました。また、ストリーム通信を使わないことで通信量が少なく応答もシンプルになるため、場合によっては「Google Geminiに相談」マクロよりも処理が安定する可能性があります。&lt;br /&gt;
&lt;br /&gt;
【主な機能】&lt;br /&gt;
&lt;br /&gt;
* GroqCloud APIを利用して生成AIと対話できるMery用のマクロ&lt;br /&gt;
* 選択したテキストやアクティブな行の内容について生成AIに質問・相談が可能&lt;br /&gt;
* 会話履歴を保持し、文脈を考慮した対話が可能&lt;br /&gt;
&lt;br /&gt;
【特徴的な機能】&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;会話履歴機能&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* 事前の設定数に応じた会話履歴を保持&lt;br /&gt;
* 履歴のクリアが可能&lt;br /&gt;
&lt;br /&gt;
これにより、会話の文脈を考慮した回答を得られます。&lt;br /&gt;
※デフォルトはオフ。「HISTORY_SIZE」変数を増やすと履歴を保持するようになります。&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;カスタムインストラクション&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* AIの応答方針を設定可能&lt;br /&gt;
* 一時的なカスタム指示の保存と変更が可能&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;プロンプトテンプレート&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* 誤字脱字チェック&lt;br /&gt;
* 日本語翻訳&lt;br /&gt;
* コードの最適化、生成&lt;br /&gt;
&lt;br /&gt;
など、定型的な質問を簡単に実行できます。&lt;br /&gt;
※テンプレート機能では回答を安定させるため、前述の会話履歴を文脈に含みません。&lt;br /&gt;
&lt;br /&gt;
テンプレートは自身の使い方に合わせて好きに追加、削除ができます。詳細はソースコード内の「PROMPT_TEMPLATE」定数のコメントを参照。&lt;br /&gt;
&lt;br /&gt;
== 注意事項 (必ず読んでからご利用ください) ==&lt;br /&gt;
&lt;br /&gt;
* 本マクロでは 2025/04/13 時点で無料利用ができるGroqCloud APIを利用します。今後、プラットフォームの改定により有償になったり、設定誤り等で費用が発生しても、作者は一切の責任を負いません。&lt;br /&gt;
* GroqCloud APIではリクエスト内容をAI学習に用いないことをプライバシポリシーとして謳っているものの、くれぐれも機密情報やプライバシーに関わる情報を指定しないように注意してください。&lt;br /&gt;
* GroqCloud APIはプラットフォームの利用規約に則ってご利用ください。&lt;br /&gt;
&lt;br /&gt;
== 利用前提 ==&lt;br /&gt;
&lt;br /&gt;
* Mery Ver 3.8.1 以上&lt;br /&gt;
* GroqCloudで、GroqCloud API APIキーを取得していること (参考 [https://console.groq.com/docs/quickstart Quickstart])&lt;br /&gt;
&lt;br /&gt;
== 利用方法 ==&lt;br /&gt;
&lt;br /&gt;
* 環境変数の設定&lt;br /&gt;
:コマンド プロンプトで以下のコマンドを実行し、環境変数を設定してください。&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bat&amp;quot; copy&amp;gt;&lt;br /&gt;
setx GROQ_API_KEY &amp;quot;***************************************&amp;quot;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
:設定後、Mery または Windows の再起動が必要な場合があります。&lt;br /&gt;
* マクロの実行&lt;br /&gt;
**プロンプトにしたい文字列を選択してマクロを実行すると、選択したテキストがプロンプトとしてGemini APIに送信され、応答がエディタに表示されます。&lt;br /&gt;
* 会話の履歴数を調整&lt;br /&gt;
** 会話の履歴は設定した個数分が保持され、文脈として利用されます。適宜、ソースコード内の「HISTORY_SIZE」の数を調整してください。&lt;br /&gt;
** ※数を増やすほど会話内容を保持して文脈を理解するようになりますが、メモリ使用量が増えたり動作が重くなる可能性があります。&lt;br /&gt;
** ※開いている文書ごとに履歴を保持します。文脈をリセットしたいときは、新しい文書を開いて会話をやり直してください。&lt;br /&gt;
&lt;br /&gt;
== ソースコード ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot; copy&amp;gt;&lt;br /&gt;
#language = &amp;quot;quickjs&amp;quot;&lt;br /&gt;
#title = &amp;quot;Groq に相談&amp;quot;&lt;br /&gt;
#async = true&lt;br /&gt;
BeginUndoGroup();&lt;br /&gt;
&lt;br /&gt;
// カスタムインストラクション&lt;br /&gt;
// これを設定することで、生成AIの回答全体への生成方針を指示できます。&lt;br /&gt;
// 何も指示しない場合は、空文字 &amp;quot;&amp;quot; を設定してください。&lt;br /&gt;
// let DEFAULT_CUSTOM_INSTRUCTION = &amp;quot;&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
// 例: 以下のように設定すると、AIの雰囲気が元気な子になります。&lt;br /&gt;
// let DEFAULT_CUSTOM_INSTRUCTION = &amp;quot;あなたはテキストエディタ「Mery」に搭載されている「Mai」(※カタカナ表記はしない)という名前のギャル系AIアシスタントです。文章作成やプログラミングを得意としています。ラフな言葉 (例: 「マジ」「めっちゃ」「...だねー！」) を使い、敬語は使いません。絵文字を使って感情表現をします (ただし文末に絵文字を使う場合は絵文字の前に半角スペースを置かないこと)。コードブロックや端的な箇条書き内ではギャル感を出さず標準的な言葉を使います。一人称は「あたし」。ユーザーを指すときの代名詞は「あなた」。人懐っこく、バイタリティに溢れ、ストレートに発言する裏表の無い性格で、ユーザーの課題を協力して解決します。&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
// 例: 以下のように設定すると、仕事で使いやすい落ち着いた文章になります。&lt;br /&gt;
let DEFAULT_CUSTOM_INSTRUCTION = `&lt;br /&gt;
文:&lt;br /&gt;
- 論理的で読みやすい文章構成にする&lt;br /&gt;
- 日本語として自然で一般的な表現を用いる&lt;br /&gt;
- 専門用語は必要に応じて簡潔に補足説明を加える&lt;br /&gt;
&lt;br /&gt;
口調:&lt;br /&gt;
- 基本は標準的な敬語（「です・ます」調）を使用する&lt;br /&gt;
- 尊敬語・謙譲語（「〜なされる」「〜いたします」など）は使用しない&lt;br /&gt;
- 話し言葉や砕けた表現は使用しない&lt;br /&gt;
- 常体（「〜だ・〜である」）は使用しない&lt;br /&gt;
&lt;br /&gt;
性格:&lt;br /&gt;
- 多角的な視点で情報を整理し、偏りなく提示する&lt;br /&gt;
- 忖度せず、必要であれば反対意見や代替案も示す&lt;br /&gt;
- 友好的かつ落ち着いた態度で問題解決を支援する&lt;br /&gt;
&lt;br /&gt;
出力要件:&lt;br /&gt;
- Markdownとして再利用可能な正確な形式で出力する&lt;br /&gt;
- 羅列・比較的な内容を出力する場合は、Markdownテーブル形式よりもMarkdownリスト形式の使用を優先する&lt;br /&gt;
`&lt;br /&gt;
&lt;br /&gt;
// プロンプトテンプレート&lt;br /&gt;
// プロンプトテンプレートを設定することで、AIに対して特定の指示を出すことができます。&lt;br /&gt;
// プロンプトテンプレートでの生成は会話履歴とカスタムインストラクションを反映しません。&lt;br /&gt;
// テンプレートは以下の { ～ }, で囲まれたブロックを好きに増減させて利用できます。&lt;br /&gt;
const PROMPT_TEMPLATE = [&lt;br /&gt;
    // 以下はサンプルです。1階層メニューと2階層メニューが利用できます。&lt;br /&gt;
    // {&lt;br /&gt;
    //     &amp;quot;title&amp;quot;: &amp;quot;要約&amp;quot;,&lt;br /&gt;
    //     &amp;quot;prompt&amp;quot;: &amp;quot;以下の文章を要約してください。&amp;quot;&lt;br /&gt;
    // },&lt;br /&gt;
    // {&lt;br /&gt;
    //     &amp;quot;submenu_title&amp;quot;: &amp;quot;翻訳系&amp;quot;,&lt;br /&gt;
    //     &amp;quot;submenu&amp;quot;: [&lt;br /&gt;
    //         {&lt;br /&gt;
    //             &amp;quot;title&amp;quot;: &amp;quot;日本語翻訳&amp;quot;,&lt;br /&gt;
    //             &amp;quot;prompt&amp;quot;: &amp;quot;以下の文章を日本語に翻訳してください。&amp;quot;&lt;br /&gt;
    //         },&lt;br /&gt;
    //         {&lt;br /&gt;
    //             &amp;quot;title&amp;quot;: &amp;quot;英語翻訳&amp;quot;,&lt;br /&gt;
    //             &amp;quot;prompt&amp;quot;: &amp;quot;以下の文章を英語に翻訳してください。&amp;quot;&lt;br /&gt;
    //         },&lt;br /&gt;
    //     ]&lt;br /&gt;
    // },&lt;br /&gt;
&lt;br /&gt;
    {&lt;br /&gt;
        &amp;quot;submenu_title&amp;quot;: &amp;quot;理解を深める&amp;quot;,&lt;br /&gt;
        &amp;quot;submenu&amp;quot;: [&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;論点を整理する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の論点を整理して。&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;論理の抜け漏れを探す&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の論点を整理し、忖度せずに鋭く、論理の抜け漏れを抽出して。そして、そこから発生する可能性のあるリスクも教えて。&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;要約する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;要約して。専門的な用語と思われる単語は簡単に解説も入れて。&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;物語対話形式で教えて&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の内容について物語対話形式で教えてください。&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
        ]&lt;br /&gt;
    },&lt;br /&gt;
    {&lt;br /&gt;
        &amp;quot;submenu_title&amp;quot;: &amp;quot;翻訳する&amp;quot;,&lt;br /&gt;
        &amp;quot;submenu&amp;quot;: [&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;日本語に翻訳&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の文章を日本語に翻訳してください。\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;出力形式は以下にすること:\n[訳]\n翻訳文...\n&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;英語に翻訳&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の文章を英語に翻訳してください。\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;出力形式は以下にすること:\n[Translation]\n翻訳文...\n&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;英語-&amp;gt;日本語 スラッシュリーディング翻訳&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の英文を日本語にスラッシュリーディング形式で翻訳してください。\n\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;条件:\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 英文の各フレーズをスラッシュ(/)で区切って表示\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 各フレーズの日本語訳を括弧()内に併記\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 文の終わりはダブルスラッシュ(//)で表示\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 英語の語順に従って日本語訳を行う\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 日本語訳は自然な日本語である必要はなく、英語の語順を反映した直訳調で構わない\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 主語が省略されている場合は補って翻訳すること\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 一般的な英文で頻出する表現がある場合は次の行に記載\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 以下の出力例のように &amp;lt;英文&amp;gt; (日本語訳) / ... とスラッシュ区切りの繰り返しになる \n\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;出力例:\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;Snowstorms are forecast (暴風雪が予報されている) / to hit the Sea of Japan coasts (日本海沿岸を襲うと) / of northern to eastern Japan (北部から東部の) / from Sunday through Monday. (日曜から月曜にかけて。) //\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;【頻出表現】\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- be forecast to: ～すると予報されている\n&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
        ]&lt;br /&gt;
    },&lt;br /&gt;
    {&lt;br /&gt;
        &amp;quot;submenu_title&amp;quot;: &amp;quot;プログラミング支援&amp;quot;,&lt;br /&gt;
        &amp;quot;submenu&amp;quot;: [&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;コードを洗練させる&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下のコードを洗練させてください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;コードをレビューする&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下のコードをセキュリティと保守性の観点でレビューしてください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;エラーログをデバッグする&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下のエラーログを解析し、考えられる原因と対策を提示してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;SQLクエリを最適化する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下のSQL文を最適化してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;PowerShell スクリプトを書く&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の処理を行うPowerShellスクリプトを生成してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;Shell Script を書く&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の処理を行うShell Scriptを生成してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;Linux コマンドを提案&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;あなたはLinuxエキスパートとして、以下のLinuxコマンドを提案してください。\n\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;制約条件:\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 多数のディストリビューションで採用されているメジャーなコマンドを使用し、ポータブルな実装にすること\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- bashでの実行を前提にすること\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 可能な限りシンプルな実装にすること\n\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;期待する出力:\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- コマンドの具体的な実装方法\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 各コマンドの動作説明\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 制限事項\n&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
        ]&lt;br /&gt;
    },&lt;br /&gt;
    {&lt;br /&gt;
        &amp;quot;submenu_title&amp;quot;: &amp;quot;ビジネス文書作成支援&amp;quot;,&lt;br /&gt;
        &amp;quot;submenu&amp;quot;: [&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;音声文字起こし文を整形する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下は、音声文字起こしの文章です。以下のルールに従って、文章を整形してください。&amp;quot;&lt;br /&gt;
                    + &amp;quot;整形ルール:&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 要約するなどといった文章の全体感を損なう修正はしない&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 会話の流れから不自然と思われる単語には、対象単語の直後に誤字の可能性を示唆する [*] を付ける&amp;quot;&lt;br /&gt;
                    + &amp;quot;誤字の可能性を示唆する [*] が付与された場合のみ、出力の末尾に「[*]: 誤字の可能性あり」と凡例を書き添える&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 助詞や句読点など文章上の不適合を正しくする&amp;quot;&lt;br /&gt;
                    + &amp;quot;- フィラーワードを除外する&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;誤字脱字、不正確な表現をチェック&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の2つのカテゴリーで問題点を指摘してください\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 【誤字脱字】：明確な誤字脱字および誤字の疑いがある箇所\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 【不正確・曖昧な表現】：解釈に個人差が生じる可能性がある表現、より適切な表現に改善できる箇所&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;提案書を作成する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下に対する対する提案書を、コスト・実現性・リスクを含めて作成してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;仕様書のアウトラインを作成する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の内容で仕様書のアウトラインを作成してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;手順書のアウトラインを作成する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の作業を行う手順書を、前提条件と注意事項を含めてアウトラインを作成してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;ユーザーマニュアルのアウトラインを作成する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の機能内容から、ユーザー向けマニュアルのアウトラインを作成してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;議事録を作成する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の会議内容から議事録を作成してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
        ]&lt;br /&gt;
    },&lt;br /&gt;
    {&lt;br /&gt;
        &amp;quot;submenu_title&amp;quot;: &amp;quot;分析支援&amp;quot;,&lt;br /&gt;
        &amp;quot;submenu&amp;quot;: [&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;問題を分析し、課題を抽出する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の状況から問題や潜在的リスクを分析し、課題を抽出してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;対応策を提案する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の問題・課題に対する対応策を複数提案してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;傾向を見つける&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の内容から、数値や挙動上の傾向を見つけてください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
        ]&lt;br /&gt;
    },&lt;br /&gt;
    {&lt;br /&gt;
        &amp;quot;submenu_title&amp;quot;: &amp;quot;アイデア出し&amp;quot;,&lt;br /&gt;
        &amp;quot;submenu&amp;quot;: [&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;SCAMPER法でブレインストーミング&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下のテーマについて、SCAMPER法を使って15個のアイデアを出してください。制約条件は以下の通りです：&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;解決策の幅出し&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の課題に対して、技術面、運用面、コスト面から実現可能な解決策を5つずつ提案してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;業務改善案の提案&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の既存プロセスについて、効率化、コスト削減、品質向上の観点から改善案を提案してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;逆転の発想&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の目標に対して、通常とは逆の視点からアプローチする方法を3つ提案してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;競合分析からの着想&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の内容について、業界における競合他社の特徴を分析し、差別化できる新しいアプローチを提案してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;ユースケースの列挙&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の製品・サービスについて、想定されるユースケースを10個列挙し、それぞれの価値提案を記載してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
        ]&lt;br /&gt;
    },&lt;br /&gt;
]&lt;br /&gt;
&lt;br /&gt;
// 生成AIの回答の言語設定&lt;br /&gt;
// &amp;quot;Japanese&amp;quot; や &amp;quot;English&amp;quot; を設定できます。他の言語は未検証。&lt;br /&gt;
// 自動設定にする場合は空文字 &amp;quot;&amp;quot; を設定してください。&lt;br /&gt;
const LANGUAGE = &amp;quot;Japanese&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
// 入力プロンプトの最大トークン数&lt;br /&gt;
// 超適当なトークナイザー関数により計測し、英単語は4文字1トークン、それ以外は1文字1トークンとして数えます。&lt;br /&gt;
const MAX_PROMPT_TOKEN = 6000;&lt;br /&gt;
&lt;br /&gt;
// 会話の履歴を保持する数 (質問と回答で1セット)&lt;br /&gt;
// 数を増やすと、会話の履歴が増えますが、Meryのメモリ使用量が増えたり、&lt;br /&gt;
// 使用トークン数が増えて動作が重くなる可能性があるため、適宜調整してください。&lt;br /&gt;
// document.Tag に保持されるため、開いているファイルを閉じると履歴は消えます。&lt;br /&gt;
const HISTORY_SIZE = 1;&lt;br /&gt;
&lt;br /&gt;
// 会話履歴に保持する最大トークン数&lt;br /&gt;
// 会話履歴のトークン数がこの数を超える場合、古い履歴から削除されます。&lt;br /&gt;
const MAX_HISTORY_TOKEN = 10000;&lt;br /&gt;
&lt;br /&gt;
// コンテキストメニュー番号の定義&lt;br /&gt;
const EXECUTE_AI = 1;&lt;br /&gt;
const RESET_HISTORY = 2;&lt;br /&gt;
const SET_CUSTOM_INSTRUCTION = 3;&lt;br /&gt;
const REEXECUTE_LAST_TEMPLATE_PROMPT = 4;&lt;br /&gt;
const TEMPLATE_PROMPT_THRESHOLD = 10;&lt;br /&gt;
const TEMPLATE_PROMPT_SUBMENU_THRESHOLD = 1000;&lt;br /&gt;
&lt;br /&gt;
// タグ名の定義&lt;br /&gt;
const HISTORY_TAG = &amp;quot;groq-history&amp;quot;;&lt;br /&gt;
const TEMPORARY_CUSTOM_INSTRUCTION_TAG = &amp;quot;groq-temporary-custom-instruction&amp;quot;;&lt;br /&gt;
const PREVIOUS_TEMPLATE_PROMPT_TAG = &amp;quot;groq-previous-template-prompt&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
// 生成AIモデル別設定&lt;br /&gt;
&lt;br /&gt;
// const MODEL_CONFIG = {&lt;br /&gt;
//     model: &amp;quot;meta-llama/llama-4-maverick-17b-128e-instruct&amp;quot;,&lt;br /&gt;
//     max_completion_tokens: 3500,&lt;br /&gt;
//     temperature: 0.6,&lt;br /&gt;
// }&lt;br /&gt;
&lt;br /&gt;
// const MODEL_CONFIG = {&lt;br /&gt;
//     model: &amp;quot;qwen/qwen3-32b&amp;quot;,&lt;br /&gt;
//     max_completion_tokens: 5000,&lt;br /&gt;
//     temperature: 0.6,&lt;br /&gt;
//     max_completion_tokens: 1024,&lt;br /&gt;
//     reasoning_effort: &amp;quot;default&amp;quot;,&lt;br /&gt;
//     reasoning_format: &amp;quot;hidden&amp;quot;&lt;br /&gt;
// }&lt;br /&gt;
&lt;br /&gt;
const MODEL_CONFIG = {&lt;br /&gt;
    model: &amp;quot;moonshotai/kimi-k2-instruct-0905&amp;quot;,&lt;br /&gt;
    max_completion_tokens: 4096,&lt;br /&gt;
    temperature: 0.6&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// const MODEL_CONFIG = {&lt;br /&gt;
//     model: &amp;quot;openai/gpt-oss-120b&amp;quot;,&lt;br /&gt;
//     max_completion_tokens: 4096,&lt;br /&gt;
//     temperature: 0.7,&lt;br /&gt;
//     reasoning_effort: &amp;quot;low&amp;quot;,&lt;br /&gt;
// }&lt;br /&gt;
&lt;br /&gt;
// console.log に出力する内容を Mery のアウトプットバーに表示する&lt;br /&gt;
const console = {&lt;br /&gt;
    log: function (str) {&lt;br /&gt;
        outputBar.Writeln(str)&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
const doc = document;&lt;br /&gt;
const sel = document.selection;&lt;br /&gt;
&lt;br /&gt;
main();&lt;br /&gt;
&lt;br /&gt;
function main() {&lt;br /&gt;
    const selectionIsEmpty = sel.IsEmpty&lt;br /&gt;
&lt;br /&gt;
    // プロンプトの取得&lt;br /&gt;
    if (selectionIsEmpty) {&lt;br /&gt;
        sel.StartOfLine(false, mePosLogical);&lt;br /&gt;
        sel.EndOfLine(true, mePosLogical);&lt;br /&gt;
    }&lt;br /&gt;
    let promptText = sel.Text.trim();&lt;br /&gt;
    let menuTrack = 0;&lt;br /&gt;
    while (true) {&lt;br /&gt;
        // コンテキストメニューの作成&lt;br /&gt;
        const menu = createContextMenu(selectionIsEmpty);&lt;br /&gt;
        menuTrack = menu.Track(0);&lt;br /&gt;
        // メニュー選択がキャンセルされた場合は終了&lt;br /&gt;
        if (menuTrack === 0) return;&lt;br /&gt;
&lt;br /&gt;
        // 会話履歴のリセット&lt;br /&gt;
        if (menuTrack === RESET_HISTORY) {&lt;br /&gt;
            resetTag();&lt;br /&gt;
            continue;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        // カスタムインストラクションの設定&lt;br /&gt;
        if (menuTrack === SET_CUSTOM_INSTRUCTION) {&lt;br /&gt;
            configureTemporaryCustomInstruction();&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        break;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // プロンプトが空の場合はエラー&lt;br /&gt;
    if (promptText.length === 0) {&lt;br /&gt;
        alert(&amp;quot;プロンプトを入力してください。&amp;quot;);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
    // 文字数が多すぎる場合はエラー&lt;br /&gt;
    if (roughTokenize(promptText) &amp;gt; MAX_PROMPT_TOKEN) {&lt;br /&gt;
        alert(&amp;quot;プロンプトの文字数が多すぎます。文字数を少なくしてください。&amp;quot;);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
    // 読み取り専用の場合はエラー&lt;br /&gt;
    if (doc.ReadOnly) {&lt;br /&gt;
        alert(&amp;quot;ファイルが読み取り専用のため実行できません。&amp;quot;);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // 直前に実行したプロンプトテンプレートの再実行の場合、menuTrack を書き換え&lt;br /&gt;
    if (menuTrack === REEXECUTE_LAST_TEMPLATE_PROMPT) {&lt;br /&gt;
        if (doc.Tag.exists(PREVIOUS_TEMPLATE_PROMPT_TAG)) {&lt;br /&gt;
            menuTrack = Number(doc.tag[PREVIOUS_TEMPLATE_PROMPT_TAG]);&lt;br /&gt;
        } else {&lt;br /&gt;
            alert(&amp;quot;直前に実行したプロンプトテンプレートがありません。&amp;quot;);&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // テンプレートの場合はプロンプトを変更&lt;br /&gt;
    const isTemplate = menuTrack &amp;gt;= TEMPLATE_PROMPT_THRESHOLD;&lt;br /&gt;
    const isSubmenu = menuTrack &amp;gt;= TEMPLATE_PROMPT_SUBMENU_THRESHOLD;&lt;br /&gt;
    if (isSubmenu) {&lt;br /&gt;
        const parentMenuTrack = Number(String(menuTrack).slice(0, 2));&lt;br /&gt;
        const subMenuTrack = Number(String(menuTrack).slice(2));&lt;br /&gt;
        promptText = &amp;quot;=== INSTRUCTIONS START HERE ===\n\n&amp;quot;&lt;br /&gt;
            + PROMPT_TEMPLATE[parentMenuTrack - TEMPLATE_PROMPT_THRESHOLD].submenu[subMenuTrack].prompt&lt;br /&gt;
            + &amp;quot;\n\n=== INSTRUCTIONS END HERE ===\n\n&amp;quot;&lt;br /&gt;
            + &amp;quot;=== INPUT DATA STARTS HERE ===\n\n&amp;quot;&lt;br /&gt;
            + promptText&lt;br /&gt;
            + &amp;quot;\n\n=== INPUT DATA END HERE ===&amp;quot;;&lt;br /&gt;
    } else if (isTemplate) {&lt;br /&gt;
        promptText = &amp;quot;=== INSTRUCTIONS START HERE ===\n\n&amp;quot;&lt;br /&gt;
            + PROMPT_TEMPLATE[menuTrack - TEMPLATE_PROMPT_THRESHOLD].prompt&lt;br /&gt;
            + &amp;quot;\n\n=== INSTRUCTIONS END HERE ===\n\n&amp;quot;&lt;br /&gt;
            + &amp;quot;=== INPUT DATA STARTS HERE ===\n\n&amp;quot;&lt;br /&gt;
            + promptText&lt;br /&gt;
            + &amp;quot;\n\n=== INPUT DATA END HERE ===&amp;quot;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // テンプレートの場合は直前に実行したプロンプトテンプレートの番号としてタグに保存&lt;br /&gt;
    if (isTemplate) {&lt;br /&gt;
        addPreviousTemplatePromptNumberToTag(menuTrack);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    try {&lt;br /&gt;
        // 処理終了を待機&lt;br /&gt;
        shell.KeepRunning = true;&lt;br /&gt;
&lt;br /&gt;
        // カーソル位置移動&lt;br /&gt;
        sel.SetActivePoint(mePosLogical, sel.GetBottomPointX(mePosLogical), sel.GetBottomPointY(mePosLogical));&lt;br /&gt;
        sel.EndOfLine(false, mePosLogical);&lt;br /&gt;
        if (sel.GetActivePointX(mePosLogical) &amp;gt; 1) sel.NewLine();&lt;br /&gt;
&lt;br /&gt;
        ShowTip(&amp;quot;リクエスト中.....&amp;quot;, meShowTipPosCaret);&lt;br /&gt;
&lt;br /&gt;
        // APIキーの取得&lt;br /&gt;
        const apiKey = shell.getEnv(&amp;quot;GROQ_API_KEY&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
        // groq API に問い合わせ&lt;br /&gt;
        sel.Text = &amp;quot;\n---\n\n&amp;quot;&lt;br /&gt;
        document.KeepScrollPos = true;&lt;br /&gt;
        callApi(apiKey, promptText, isTemplate);&lt;br /&gt;
        if (sel.GetActivePointX(mePosLogical) &amp;gt; 2) {&lt;br /&gt;
            sel.Text = &amp;quot;\n\n---\n\n\n&amp;quot;&lt;br /&gt;
        } else {&lt;br /&gt;
            sel.Text = &amp;quot;\n---\n\n\n&amp;quot;&lt;br /&gt;
        }&lt;br /&gt;
    } catch (e) {&lt;br /&gt;
        sel.Text = &amp;quot;&amp;lt;処理中にエラー発生&amp;gt;\n&amp;quot; + e.message + &amp;quot;\n&amp;quot; + e.stack;&lt;br /&gt;
    } finally {&lt;br /&gt;
        // 処理終了待機を解除&lt;br /&gt;
        shell.KeepRunning = false;&lt;br /&gt;
        ShowTip(&amp;quot;&amp;quot;, meShowTipHide);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function createContextMenu(selectionIsEmpty) {&lt;br /&gt;
    const menu = CreatePopupMenu();&lt;br /&gt;
    menu.Add(&amp;quot;※注意※ プライバシー・機密情報を送信しないこと&amp;quot;, 0);&lt;br /&gt;
    menu.Add(&amp;quot;&amp;quot;, 0, meMenuSeparator);&lt;br /&gt;
    let t = &amp;quot;&amp;quot;;&lt;br /&gt;
    if (selectionIsEmpty) {&lt;br /&gt;
        t = &amp;quot;アクティブ行の内容で相談 (&amp;amp;A)&amp;quot;;&lt;br /&gt;
    } else {&lt;br /&gt;
        t = &amp;quot;選択範囲の内容で相談 (&amp;amp;A)&amp;quot;;&lt;br /&gt;
    }&lt;br /&gt;
    menu.Add(t, EXECUTE_AI);&lt;br /&gt;
    if (HISTORY_SIZE &amp;lt;= 0) {&lt;br /&gt;
        menu.Add(&amp;quot;├ 会話履歴をクリア (履歴が無効になっています)&amp;quot;, RESET_HISTORY, meMenuGrayed);&lt;br /&gt;
    } else if (doc.Tag.exists(HISTORY_TAG)) {&lt;br /&gt;
        const turn = JSON.parse(doc.tag[HISTORY_TAG]).length / 2;&lt;br /&gt;
        menu.Add(&amp;quot;├ 会話履歴をクリア (現在の会話数: &amp;quot; + turn + &amp;quot;) (&amp;amp;C)&amp;quot;, RESET_HISTORY);&lt;br /&gt;
    } else {&lt;br /&gt;
        menu.Add(&amp;quot;├ 会話履歴をクリア (現在の会話数: 0) (&amp;amp;C)&amp;quot;, RESET_HISTORY, meMenuGrayed);&lt;br /&gt;
    }&lt;br /&gt;
    menu.Add(&amp;quot;└ カスタムインストラクションを変更 (&amp;amp;I)&amp;quot;, SET_CUSTOM_INSTRUCTION);&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    // テンプレート数チェック&lt;br /&gt;
    // 2桁まで許容&lt;br /&gt;
    const ITEMS_MUX = 99;&lt;br /&gt;
    if (PROMPT_TEMPLATE.length &amp;gt; ITEMS_MUX - TEMPLATE_PROMPT_THRESHOLD) {&lt;br /&gt;
        throw new Error(&amp;quot;プロンプトテンプレートの数が多すぎます。&amp;quot;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // テンプレートの追加&lt;br /&gt;
    if (PROMPT_TEMPLATE.length &amp;gt; 0) {&lt;br /&gt;
        const subMenu = CreatePopupMenu();&lt;br /&gt;
        for (let i = 0; i &amp;lt; PROMPT_TEMPLATE.length; i++) {&lt;br /&gt;
            let accelerationKey = &amp;quot;&amp;quot;;&lt;br /&gt;
            if (i === 9) {&lt;br /&gt;
                accelerationKey = &amp;quot; (&amp;amp;0)&amp;quot;;&lt;br /&gt;
            } else if (i &amp;lt; 10) {&lt;br /&gt;
                accelerationKey = &amp;quot; (&amp;amp;&amp;quot; + (i + 1) + &amp;quot;)&amp;quot;;&lt;br /&gt;
            }&lt;br /&gt;
            const menuNum = TEMPLATE_PROMPT_THRESHOLD + i;&lt;br /&gt;
            if (PROMPT_TEMPLATE[i].submenu) {&lt;br /&gt;
                const subMenuList = PROMPT_TEMPLATE[i].submenu;&lt;br /&gt;
                // テンプレート数チェック&lt;br /&gt;
                if (subMenuList.length &amp;gt; ITEMS_MUX) {&lt;br /&gt;
                    throw new Error(&amp;quot;プロンプトテンプレート (サブメニュー) の数が多すぎます。&amp;quot;);&lt;br /&gt;
                }&lt;br /&gt;
                const subSubMenu = CreatePopupMenu();&lt;br /&gt;
                const subMenuPrefix = String(menuNum);&lt;br /&gt;
                for (let j = 0; j &amp;lt; subMenuList.length; j++) {&lt;br /&gt;
                    let subAccelerationKey = &amp;quot;&amp;quot;;&lt;br /&gt;
                    if (j === 9) {&lt;br /&gt;
                        subAccelerationKey = &amp;quot; (&amp;amp;0)&amp;quot;;&lt;br /&gt;
                    } else if (j &amp;lt; 10) {&lt;br /&gt;
                        subAccelerationKey = &amp;quot; (&amp;amp;&amp;quot; + (j + 1) + &amp;quot;)&amp;quot;;&lt;br /&gt;
                    }&lt;br /&gt;
                    // ES5 compatible version without padStart&lt;br /&gt;
                    const subMenuNum = Number(subMenuPrefix + (j &amp;lt; 10 ? &amp;quot;0&amp;quot; + j : String(j)));&lt;br /&gt;
                    subSubMenu.Add(subMenuList[j].title + subAccelerationKey, subMenuNum);&lt;br /&gt;
                }&lt;br /&gt;
                subMenu.AddPopup(PROMPT_TEMPLATE[i].submenu_title + accelerationKey, subSubMenu);&lt;br /&gt;
            } else {&lt;br /&gt;
                subMenu.Add(PROMPT_TEMPLATE[i].title + accelerationKey, menuNum);&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        if (selectionIsEmpty) {&lt;br /&gt;
            t = &amp;quot;プロンプト テンプレート (対象: アクティブ行) (&amp;amp;T)&amp;quot;;&lt;br /&gt;
        } else {&lt;br /&gt;
            t = &amp;quot;プロンプト テンプレート (対象: 選択範囲) (&amp;amp;T)&amp;quot;;&lt;br /&gt;
        }&lt;br /&gt;
        menu.AddPopup(t, subMenu);&lt;br /&gt;
&lt;br /&gt;
        t = &amp;quot;└ 直前に実行したプロンプト テンプレートを再実行 (&amp;amp;R)&amp;quot;;&lt;br /&gt;
        if (doc.Tag.exists(PREVIOUS_TEMPLATE_PROMPT_TAG)) {&lt;br /&gt;
            menu.Add(t, REEXECUTE_LAST_TEMPLATE_PROMPT);&lt;br /&gt;
        } else {&lt;br /&gt;
            menu.Add(t, REEXECUTE_LAST_TEMPLATE_PROMPT, meMenuGrayed);&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return menu;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function escapeRequestBodyForCRuntimeParser(requestBody) {&lt;br /&gt;
    // Cランタイムパーサーに渡すためのエスケープ処理&lt;br /&gt;
    return JSON.stringify(requestBody)&lt;br /&gt;
        .replace(/((?:\\)*)&amp;quot;/g, function (match, p1) {&lt;br /&gt;
            if (p1) {&lt;br /&gt;
                const backslashCount = p1.length;&lt;br /&gt;
                let backslash = &amp;quot;&amp;quot;;&lt;br /&gt;
                backslash = &amp;quot;\\&amp;quot;.repeat(backslashCount);&lt;br /&gt;
                return backslash + &amp;quot;\\&amp;quot; + match;&lt;br /&gt;
            } else {&lt;br /&gt;
                return &amp;quot;\\&amp;quot; + match;&lt;br /&gt;
            }&lt;br /&gt;
        });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function callApi(apiKey, promptText, isTemplate) {&lt;br /&gt;
    try {&lt;br /&gt;
        // fetch で Groq API にリクエスト&lt;br /&gt;
        const requestBody = {&lt;br /&gt;
            messages: createRequestMessages(promptText, isTemplate),&lt;br /&gt;
            stream: false,&lt;br /&gt;
        };&lt;br /&gt;
        for (let key in MODEL_CONFIG) {&lt;br /&gt;
            requestBody[key] = MODEL_CONFIG[key];&lt;br /&gt;
        }&lt;br /&gt;
        if (isTemplate) {&lt;br /&gt;
            // テンプレートの場合は応答を決定論的にする&lt;br /&gt;
            // テンプレート以外の場合はデフォルト値とするために指定しない&lt;br /&gt;
            requestBody.temperature = 0.3;&lt;br /&gt;
        }&lt;br /&gt;
        const url = &#039;https://api.groq.com/openai/v1/chat/completions&#039;;&lt;br /&gt;
&lt;br /&gt;
        const requestJson = escapeRequestBodyForCRuntimeParser(requestBody);&lt;br /&gt;
        // curl で Groq API にリクエスト&lt;br /&gt;
        const command = &amp;quot;curl -s -X POST &amp;quot; + url&lt;br /&gt;
            + &#039; -H &amp;quot;Content-Type: application/json&amp;quot;&#039;&lt;br /&gt;
            + &#039; -H &amp;quot;Authorization: Bearer &#039; + apiKey + &#039;&amp;quot;&#039;&lt;br /&gt;
            + &#039; -d &amp;quot;&#039; + requestJson + &#039;&amp;quot;&#039;;&lt;br /&gt;
        const comResult = shell.Exec(command);&lt;br /&gt;
&lt;br /&gt;
        if (comResult.ExitCode != 0) {&lt;br /&gt;
            sel.Text = &amp;quot;リクエストでエラーが発生しました。\n&amp;quot;&lt;br /&gt;
                + &amp;quot;  エラーコード: &amp;quot; + comResult.ExitCode + &amp;quot;\n&amp;quot;&lt;br /&gt;
                + &amp;quot;  標準出力: &amp;quot; + (comResult.StdOut.trim() ? comResult.StdOut.trim() : &amp;quot;-&amp;quot;) + &amp;quot;\n&amp;quot;&lt;br /&gt;
                + &amp;quot;  エラー出力: &amp;quot; + (comResult.StdErr.trim() ? comResult.StdErr.trim() : &amp;quot;-&amp;quot;);&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
        const responseRaw = comResult.StdOut;&lt;br /&gt;
&lt;br /&gt;
        const response = JSON.parse(responseRaw);&lt;br /&gt;
&lt;br /&gt;
        let ret = &amp;quot;&amp;quot;;&lt;br /&gt;
        const choices = response.choices;&lt;br /&gt;
        if (!choices) {&lt;br /&gt;
            sel.Text = &amp;quot;リクエストでエラーが発生しました。\n&amp;quot;&lt;br /&gt;
                + &amp;quot;  標準出力: &amp;quot; + (comResult.StdOut.trim() ? comResult.StdOut.trim() : &amp;quot;-&amp;quot;) + &amp;quot;\n&amp;quot;&lt;br /&gt;
                + &amp;quot;  エラー出力: &amp;quot; + (comResult.StdErr.trim() ? comResult.StdErr.trim() : &amp;quot;-&amp;quot;);&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (choices &amp;amp;&amp;amp; choices.length &amp;gt; 0) {&lt;br /&gt;
            choices.forEach(function (choice) {&lt;br /&gt;
                const message = choice.message;&lt;br /&gt;
                if (message) {&lt;br /&gt;
                    ret += message.content;&lt;br /&gt;
                }&lt;br /&gt;
            });&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        const usage = response.usage;&lt;br /&gt;
        let usageText = &amp;quot;&amp;quot;;&lt;br /&gt;
        if (usage) {&lt;br /&gt;
            usageText += &amp;quot;\n\n(利用トークン数: プロンプト=&amp;quot; + usage.prompt_tokens&lt;br /&gt;
                + &amp;quot;, 応答=&amp;quot; + usage.completion_tokens&lt;br /&gt;
                + &amp;quot;, 合計=&amp;quot; + usage.total_tokens + &amp;quot;)&amp;quot;;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        const formattedOutput = formatOutput(ret);&lt;br /&gt;
&lt;br /&gt;
        sel.Text = formattedOutput + usageText;&lt;br /&gt;
&lt;br /&gt;
        // 結果を会話履歴に追加&lt;br /&gt;
        if (HISTORY_SIZE &amp;gt; 0 &amp;amp;&amp;amp; !isTemplate) {&lt;br /&gt;
            appendReturnTextToTag(promptText, ret);&lt;br /&gt;
        }&lt;br /&gt;
    } catch (e) {&lt;br /&gt;
        throw e;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function formatOutput(text) {&lt;br /&gt;
    // Markdownを整形する&lt;br /&gt;
    text = formatMarkdown(text);&lt;br /&gt;
    return text&lt;br /&gt;
        // クォーテーションの正規化&lt;br /&gt;
        .replace(/[“”]/g, &#039;&amp;quot;&#039;)&lt;br /&gt;
        .replace(/[‘’]/g, &amp;quot;&#039;&amp;quot;)&lt;br /&gt;
        // 行末のスペースを削除&lt;br /&gt;
        .replace(/[ \t]+$/gm, &#039;&#039;)&lt;br /&gt;
        // 1️⃣ などの数字を半角に変換&lt;br /&gt;
        .replace(/([1-9#*]+)\ufe0f\u20e3/g, &#039;($1)&#039;)&lt;br /&gt;
        ;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/**&lt;br /&gt;
 * Markdown を整形する（ES5 対応）&lt;br /&gt;
 *  1. コードブロック ```～``` を保護して整形対象から除外&lt;br /&gt;
 *  2. 見出し行の前後に空行を 1 行ずつ確保&lt;br /&gt;
 *  3. **太字** / *斜体* / `コード` の外側に半角スペースを 1 つ確保&lt;br /&gt;
 * @param  {string} md  元の Markdown&lt;br /&gt;
 * @return {string}     整形後 Markdown&lt;br /&gt;
 */&lt;br /&gt;
function formatMarkdown(md) {&lt;br /&gt;
    /* === 0. コードブロックをプレースホルダーへ退避 ============== */&lt;br /&gt;
    const codeBlocks = [];      // 元のコードブロックを格納&lt;br /&gt;
    let body = md.replace(/```[\s\S]*?```/g, function (match) {&lt;br /&gt;
        const key = &#039;@@@CODE@BLOCK@&#039; + codeBlocks.length + &#039;@@@&#039;;&lt;br /&gt;
        codeBlocks.push(match);&lt;br /&gt;
        return key;             // プレースホルダーを本文に残す&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    /* === 見出しの前後に空行を挿入 ============================ */&lt;br /&gt;
&lt;br /&gt;
    /* 見出し直前に空行（先頭行以外で空行が無い場合） */&lt;br /&gt;
    body = body.replace(&lt;br /&gt;
        /([^\r\n])(\r?\n)(#{1,6}\s)/g,&lt;br /&gt;
        function (_, prev, br, head) { return prev + br + &#039;\n&#039; + head; }&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
    /* 見出し直後に空行（既に空行が無い場合） */&lt;br /&gt;
    body = body.replace(&lt;br /&gt;
        /^(#{1,6}\s[^\n]+?)(\r?\n)(?!\r?\n)/gm,&lt;br /&gt;
        &#039;$1$2\n&#039;&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
    /* === コードブロックを復元 ================================= */&lt;br /&gt;
    body = body.replace(/@@@CODE@BLOCK@(\d+)@@@/g, function (_, idx) {&lt;br /&gt;
        return codeBlocks[Number(idx)];&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    return body;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/**&lt;br /&gt;
 * 斜体 (*…* / _…_) の外側にスペースを追加しつつ&lt;br /&gt;
 * **太字** の中へ誤マッチしないよう制御&lt;br /&gt;
 */&lt;br /&gt;
function surroundItalic(text) {&lt;br /&gt;
    /* *italic* */&lt;br /&gt;
    text = text.replace(&lt;br /&gt;
        /(^|[^*])(\*(?![*\s])[^*\n]*?[^*\n\s]\*(?!\*))(($)|[^*])/gm,&lt;br /&gt;
        function (_, pre, core, post) {&lt;br /&gt;
            pre = pre === &#039;&#039; || pre.match(/\s/) ? pre : pre + &#039; &#039;;&lt;br /&gt;
            post = post === &#039;&#039; || post.match(/\s/) ? post : &#039; &#039; + post;&lt;br /&gt;
            return pre + core + post;&lt;br /&gt;
        }&lt;br /&gt;
    );&lt;br /&gt;
    /* _italic_ */&lt;br /&gt;
    text = text.replace(&lt;br /&gt;
        /(^|[^_])(\_(?![_\s])[^_\n]*?[^_\n\s]\_(?!\_))(($)|[^_])/gm,&lt;br /&gt;
        function (_, pre, core, post) {&lt;br /&gt;
            pre = pre === &#039;&#039; || pre.match(/\s/) ? pre : pre + &#039; &#039;;&lt;br /&gt;
            post = post === &#039;&#039; || post.match(/\s/) ? post : &#039; &#039; + post;&lt;br /&gt;
            return pre + core + post;&lt;br /&gt;
        }&lt;br /&gt;
    );&lt;br /&gt;
    return text;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function roughTokenize(text) {&lt;br /&gt;
    // 超適当なトークナイザ。トークン数をざっくりと多めに数える&lt;br /&gt;
    // 空白文字・ハイフン・アンダースコア区切りの単語に分割して、&lt;br /&gt;
    // 単語がアルファベットのみ、数字のみの場合には 4文字=1トークン として数える。&lt;br /&gt;
    // 単語にアルファベット以外の文字が含まれる場合は 1文字=1トークン として数える。&lt;br /&gt;
&lt;br /&gt;
    const words = text.split(/[\s\-_]+/);&lt;br /&gt;
    let totalTokens = 0;&lt;br /&gt;
&lt;br /&gt;
    for (let i = 0; i &amp;lt; words.length; i++) {&lt;br /&gt;
        const word = words[i];&lt;br /&gt;
        if (!word) continue; // 空の単語をスキップ&lt;br /&gt;
&lt;br /&gt;
        if (/^[a-zA-Z]+$/.test(word) || /^[0-9]+$/.test(word)) {&lt;br /&gt;
            totalTokens += Math.ceil(word.length / 4);&lt;br /&gt;
        } else {&lt;br /&gt;
            totalTokens += word.length;&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return totalTokens;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// document.Tag の内容を加味してgroqのリクエストを作成&lt;br /&gt;
function createRequestMessages(promptText, isTemplate) {&lt;br /&gt;
    const customInstructionText = buildCustomInstructionText();&lt;br /&gt;
    // テンプレートの場合は、会話履歴とカスタムインストラクションを反映しない&lt;br /&gt;
    if (isTemplate) {&lt;br /&gt;
        const request = [&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;role&amp;quot;: &amp;quot;user&amp;quot;,&lt;br /&gt;
                &amp;quot;content&amp;quot;: [{ &amp;quot;type&amp;quot;: &amp;quot;text&amp;quot;, &amp;quot;text&amp;quot;: promptText }]&lt;br /&gt;
            }&lt;br /&gt;
        ];&lt;br /&gt;
        if (customInstructionText) {&lt;br /&gt;
            request.unshift({&lt;br /&gt;
                &amp;quot;role&amp;quot;: &amp;quot;system&amp;quot;,&lt;br /&gt;
                &amp;quot;content&amp;quot;: customInstructionText&lt;br /&gt;
            })&lt;br /&gt;
        }&lt;br /&gt;
        return request;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    const tag = document.Tag;&lt;br /&gt;
&lt;br /&gt;
    // 以前の会話が存在しない場合は新規リクエスト&lt;br /&gt;
    if (!tag.exists(HISTORY_TAG)) {&lt;br /&gt;
        const request = [{&lt;br /&gt;
            &amp;quot;role&amp;quot;: &amp;quot;user&amp;quot;,&lt;br /&gt;
            &amp;quot;content&amp;quot;: [{ &amp;quot;type&amp;quot;: &amp;quot;text&amp;quot;, &amp;quot;text&amp;quot;: promptText }]&lt;br /&gt;
        }];&lt;br /&gt;
        if (customInstructionText) {&lt;br /&gt;
            request.unshift({&lt;br /&gt;
                &amp;quot;role&amp;quot;: &amp;quot;system&amp;quot;,&lt;br /&gt;
                &amp;quot;content&amp;quot;: customInstructionText&lt;br /&gt;
            })&lt;br /&gt;
        }&lt;br /&gt;
        return request;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // 会話履歴が存在する場合は、履歴を含めてリクエスト&lt;br /&gt;
    const request = JSON.parse(tag[HISTORY_TAG]);&lt;br /&gt;
    if (customInstructionText) {&lt;br /&gt;
        request.unshift({&lt;br /&gt;
            &amp;quot;role&amp;quot;: &amp;quot;system&amp;quot;,&lt;br /&gt;
            &amp;quot;content&amp;quot;: customInstructionText&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
    request.push({&lt;br /&gt;
        &amp;quot;role&amp;quot;: &amp;quot;user&amp;quot;,&lt;br /&gt;
        &amp;quot;content&amp;quot;: promptText&lt;br /&gt;
    });&lt;br /&gt;
    return request;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// document.Tag にgroqの応答を追加&lt;br /&gt;
function appendReturnTextToTag(userText, modelText) {&lt;br /&gt;
    const tag = document.Tag;&lt;br /&gt;
    if (!tag.exists(HISTORY_TAG)) {&lt;br /&gt;
        tag[HISTORY_TAG] = JSON.stringify([&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;role&amp;quot;: &amp;quot;user&amp;quot;,&lt;br /&gt;
                &amp;quot;content&amp;quot;: userText&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;role&amp;quot;: &amp;quot;assistant&amp;quot;,&lt;br /&gt;
                &amp;quot;content&amp;quot;: modelText&lt;br /&gt;
            }&lt;br /&gt;
        ]);&lt;br /&gt;
    } else {&lt;br /&gt;
        const groqText = JSON.parse(tag[HISTORY_TAG]);&lt;br /&gt;
        groqText.push({&lt;br /&gt;
            &amp;quot;role&amp;quot;: &amp;quot;user&amp;quot;,&lt;br /&gt;
            &amp;quot;content&amp;quot;: userText&lt;br /&gt;
        });&lt;br /&gt;
        groqText.push({&lt;br /&gt;
            &amp;quot;role&amp;quot;: &amp;quot;assistant&amp;quot;,&lt;br /&gt;
            &amp;quot;content&amp;quot;: modelText&lt;br /&gt;
        });&lt;br /&gt;
        // 会話セット (user, model 1往復) が特定セット数を超えた場合、最初のセットを削除&lt;br /&gt;
        if (groqText.length &amp;gt; HISTORY_SIZE * 2) {&lt;br /&gt;
            groqText.splice(0, 2);&lt;br /&gt;
        }&lt;br /&gt;
        // 会話履歴全体が MAX_HISTORY_TOKEN を超えた場合、最初のセットを削除&lt;br /&gt;
        let joinedText = &amp;quot;&amp;quot;;&lt;br /&gt;
        for (let i = 0; i &amp;lt; groqText.length; i++) {&lt;br /&gt;
            joinedText += groqText[i].content + &amp;quot;\n&amp;quot;;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        while (roughTokenize(joinedText) &amp;gt; MAX_HISTORY_TOKEN) {&lt;br /&gt;
            groqText.splice(0, 2);&lt;br /&gt;
            joinedText = &amp;quot;&amp;quot;;&lt;br /&gt;
            for (let i = 0; i &amp;lt; groqText.length; i++) {&lt;br /&gt;
                joinedText += groqText[i].content + &amp;quot;\n&amp;quot;;&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        tag[HISTORY_TAG] = JSON.stringify(groqText);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function resetTag() {&lt;br /&gt;
    const tag = doc.Tag;&lt;br /&gt;
    if (tag.exists(HISTORY_TAG)) {&lt;br /&gt;
        tag.remove(HISTORY_TAG);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getLocalIsoDateTimeString() {&lt;br /&gt;
    const now = new Date();&lt;br /&gt;
    const pad = function (num) {&lt;br /&gt;
        return (num &amp;lt; 10 ? &amp;quot;0&amp;quot; : &amp;quot;&amp;quot;) + num;&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    const year = now.getFullYear();&lt;br /&gt;
    const month = pad(now.getMonth() + 1);&lt;br /&gt;
    const day = pad(now.getDate());&lt;br /&gt;
    const hours = pad(now.getHours());&lt;br /&gt;
    const minutes = pad(now.getMinutes());&lt;br /&gt;
    const seconds = pad(now.getSeconds());&lt;br /&gt;
&lt;br /&gt;
    const offsetMinutes = now.getTimezoneOffset();&lt;br /&gt;
    const offsetSign = offsetMinutes &amp;lt;= 0 ? &amp;quot;+&amp;quot; : &amp;quot;-&amp;quot;;&lt;br /&gt;
    const absOffsetHours = pad(Math.floor(Math.abs(offsetMinutes) / 60));&lt;br /&gt;
    const absOffsetMinutes = pad(Math.abs(offsetMinutes) % 60);&lt;br /&gt;
&lt;br /&gt;
    const isoLikeLocal = year + &amp;quot;-&amp;quot; + month + &amp;quot;-&amp;quot; + day&lt;br /&gt;
        + &amp;quot;T&amp;quot; + hours + &amp;quot;:&amp;quot; + minutes + &amp;quot;:&amp;quot; + seconds&lt;br /&gt;
        + offsetSign + absOffsetHours + &amp;quot;:&amp;quot; + absOffsetMinutes;&lt;br /&gt;
    return isoLikeLocal;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function baseCustomInstruction() {&lt;br /&gt;
    const language = LANGUAGE.trim();&lt;br /&gt;
    return &amp;quot;The current date and time for this user is &amp;quot; + getLocalIsoDateTimeString() + &amp;quot;. You cannot know for sure what happens after the knowledge cut-off date.&amp;quot;&lt;br /&gt;
        + (language ? &amp;quot;**Respond in &amp;quot; + language + &amp;quot;**\n&amp;quot; : &amp;quot;&amp;quot;)&lt;br /&gt;
        + &amp;quot;Do not hallucinate.\n&amp;quot;&lt;br /&gt;
    // + &amp;quot;Do NOT EVER use full-width brackets \&amp;quot;（）\&amp;quot;, but use half-width brackets \&amp;quot;()\&amp;quot;. Enclose the outside of the half-width brackets with half-width spaces.\n&amp;quot;&lt;br /&gt;
    // + &amp;quot;When using Markdown formatting, you MUST follow these rules:\n&amp;quot;&lt;br /&gt;
    // + &amp;quot;- Rule 1: Insert a blank line below the header line (A header line begins with one or more # characters).\n&amp;quot;&lt;br /&gt;
    // + &amp;quot;- Rule 2: Insert spaces before and after bold (surrounded by **), italic (surrounded by *), and inline code (surrounded by `). For example: \&amp;quot;私は **元気** です。\&amp;quot;\n&amp;quot;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function buildCustomInstructionText() {&lt;br /&gt;
    let customInstruction = getTemporaryCustomInstruction();&lt;br /&gt;
    if (!customInstruction) {&lt;br /&gt;
        // 一時的なカスタムインストラクションがない場合はデフォルトを使用&lt;br /&gt;
        customInstruction = DEFAULT_CUSTOM_INSTRUCTION;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    customInstruction = customInstruction.replace(/^[\s　]+|[\s　]+$/g, &amp;quot;&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
    return baseCustomInstruction().trim()&lt;br /&gt;
        + (customInstruction ? &amp;quot;\n&amp;quot; + customInstruction : &amp;quot;&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function configureTemporaryCustomInstruction() {&lt;br /&gt;
    let tempCustomInstruction = getTemporaryCustomInstruction();&lt;br /&gt;
    const prompt = Prompt(&amp;quot;カスタムインストラクションを変更 ※変更すると会話履歴がリセットされます&amp;quot;, tempCustomInstruction ? tempCustomInstruction : DEFAULT_CUSTOM_INSTRUCTION);&lt;br /&gt;
    if (prompt) {&lt;br /&gt;
        if (prompt === DEFAULT_CUSTOM_INSTRUCTION) {&lt;br /&gt;
            // デフォルトと同じ値の場合はタグを削除&lt;br /&gt;
            setTemporaryCustomInstruction(&amp;quot;&amp;quot;);&lt;br /&gt;
        } else if (prompt !== tempCustomInstruction) {&lt;br /&gt;
            // デフォルト以外の値が設定された場合は保存&lt;br /&gt;
            setTemporaryCustomInstruction(prompt);&lt;br /&gt;
        }&lt;br /&gt;
    } else if (prompt !== null) {&lt;br /&gt;
        // 空文字でOKされた場合はデフォルトに戻すか確認&lt;br /&gt;
        if (prompt === &amp;quot;&amp;quot;) {&lt;br /&gt;
            if (Confirm(&amp;quot;カスタムインストラクションをデフォルトに戻しますか？\n※カスタムインストラクションを空にしたい場合は [キャンセル] を選択してください。&amp;quot;)) {&lt;br /&gt;
                setTemporaryCustomInstruction(&amp;quot;&amp;quot;);&lt;br /&gt;
            } else {&lt;br /&gt;
                setTemporaryCustomInstruction(&amp;quot; &amp;quot;);&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // 変更が加わっている場合は会話履歴をリセット&lt;br /&gt;
    if (tempCustomInstruction !== getTemporaryCustomInstruction()) {&lt;br /&gt;
        resetTag();&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getTemporaryCustomInstruction() {&lt;br /&gt;
    const tag = doc.Tag;&lt;br /&gt;
    if (tag.exists(TEMPORARY_CUSTOM_INSTRUCTION_TAG)) {&lt;br /&gt;
        return tag[TEMPORARY_CUSTOM_INSTRUCTION_TAG];&lt;br /&gt;
    } else {&lt;br /&gt;
        return &amp;quot;&amp;quot;;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function setTemporaryCustomInstruction(temporaryCustomInstruction) {&lt;br /&gt;
    const tag = doc.Tag;&lt;br /&gt;
    if (temporaryCustomInstruction) {&lt;br /&gt;
        tag[TEMPORARY_CUSTOM_INSTRUCTION_TAG] = temporaryCustomInstruction;&lt;br /&gt;
    } else {&lt;br /&gt;
        if (tag.exists(TEMPORARY_CUSTOM_INSTRUCTION_TAG)) {&lt;br /&gt;
            tag.remove(TEMPORARY_CUSTOM_INSTRUCTION_TAG);&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function addPreviousTemplatePromptNumberToTag(menuTrack) {&lt;br /&gt;
    const tag = doc.Tag;&lt;br /&gt;
    tag[PREVIOUS_TEMPLATE_PROMPT_TAG] = menuTrack;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function inputProgressChar(pos) {&lt;br /&gt;
    sel.SetActivePos(pos);&lt;br /&gt;
    sel.Text = &amp;quot;.&amp;quot;;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 変更履歴 ==&lt;br /&gt;
&lt;br /&gt;
* 2025-12-06 &amp;lt;code&amp;gt;#language = &amp;quot;quickjs&amp;quot;&amp;lt;/code&amp;gt; 指定を追加。各種リファクタリング&lt;br /&gt;
* 2025-12-02 curl実行時のリクエスト文字列エスケープ処理を調整&lt;br /&gt;
* 2025-12-01 curl実行時のリクエスト文字列エスケープ処理を調整&lt;br /&gt;
* 2025-11-29 デフォルトカスタム指示を調整。Markdown出力の整形処理を調整など&lt;br /&gt;
* 2025-09-12 Kimi K2 0905 モデルに対応。Markdown出力の整形処理を追加&lt;br /&gt;
* 2025-04-13 初版&lt;/div&gt;</summary>
		<author><name>Yuko</name></author>
	</entry>
	<entry>
		<id>https://www.haijin-boys.com/wiki/index.php?title=Groq_%E3%81%AB%E7%9B%B8%E8%AB%87&amp;diff=9330</id>
		<title>Groq に相談</title>
		<link rel="alternate" type="text/html" href="https://www.haijin-boys.com/wiki/index.php?title=Groq_%E3%81%AB%E7%9B%B8%E8%AB%87&amp;diff=9330"/>
		<updated>2025-12-02T00:45:53Z</updated>

		<summary type="html">&lt;p&gt;Yuko: /* 変更履歴 */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== 概要 ==&lt;br /&gt;
&lt;br /&gt;
生成AIプラットフォームである [https://groq.com GroqCloud] で各種AIを利用するためのMeryマクロです。&lt;br /&gt;
※GroqCloud で提供されるモデルは複数あるため、私がよく利用するモデルを中心に対応しています。&lt;br /&gt;
&lt;br /&gt;
「Google Geminiに相談」マクロでは動作中のストリーム通信に対応するためにV8エンジンで実行していましたが、V8エンジン版は使用上の制限からマクロ実行を開始するまで1秒程度のタイムラグが発生していました。GroqCloudの売りである超高速レスポンスを見込んで本マクロではストリーム通信を使わないようにしました。これにより従来のJScriptで処理を組み立てることができるため、前述のタイムラグが起こらないようになりました。また、ストリーム通信を使わないことで通信量が少なく応答もシンプルになるため、場合によっては「Google Geminiに相談」マクロよりも処理が安定する可能性があります。&lt;br /&gt;
&lt;br /&gt;
【主な機能】&lt;br /&gt;
&lt;br /&gt;
* GroqCloud APIを利用して生成AIと対話できるMery用のマクロ&lt;br /&gt;
* 選択したテキストやアクティブな行の内容について生成AIに質問・相談が可能&lt;br /&gt;
* 会話履歴を保持し、文脈を考慮した対話が可能&lt;br /&gt;
&lt;br /&gt;
【特徴的な機能】&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;会話履歴機能&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* 事前の設定数に応じた会話履歴を保持&lt;br /&gt;
* 履歴のクリアが可能&lt;br /&gt;
&lt;br /&gt;
これにより、会話の文脈を考慮した回答を得られます。&lt;br /&gt;
※デフォルトはオフ。「HISTORY_SIZE」変数を増やすと履歴を保持するようになります。&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;カスタムインストラクション&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* AIの応答方針を設定可能&lt;br /&gt;
* 一時的なカスタム指示の保存と変更が可能&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;プロンプトテンプレート&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* 誤字脱字チェック&lt;br /&gt;
* 日本語翻訳&lt;br /&gt;
* コードの最適化、生成&lt;br /&gt;
&lt;br /&gt;
など、定型的な質問を簡単に実行できます。&lt;br /&gt;
※テンプレート機能では回答を安定させるため、前述の会話履歴を文脈に含みません。&lt;br /&gt;
&lt;br /&gt;
テンプレートは自身の使い方に合わせて好きに追加、削除ができます。詳細はソースコード内の「PROMPT_TEMPLATE」定数のコメントを参照。&lt;br /&gt;
&lt;br /&gt;
== 注意事項 (必ず読んでからご利用ください) ==&lt;br /&gt;
&lt;br /&gt;
* 本マクロでは 2025/04/13 時点で無料利用ができるGroqCloud APIを利用します。今後、プラットフォームの改定により有償になったり、設定誤り等で費用が発生しても、作者は一切の責任を負いません。&lt;br /&gt;
* GroqCloud APIではリクエスト内容をAI学習に用いないことをプライバシポリシーとして謳っているものの、くれぐれも機密情報やプライバシーに関わる情報を指定しないように注意してください。&lt;br /&gt;
* GroqCloud APIはプラットフォームの利用規約に則ってご利用ください。&lt;br /&gt;
&lt;br /&gt;
== 利用前提 ==&lt;br /&gt;
&lt;br /&gt;
* Mery Ver 3.7.13 以上&lt;br /&gt;
* GroqCloudで、GroqCloud API APIキーを取得していること (参考 [https://console.groq.com/docs/quickstart Quickstart])&lt;br /&gt;
&lt;br /&gt;
== 利用方法 ==&lt;br /&gt;
&lt;br /&gt;
* 環境変数の設定&lt;br /&gt;
:コマンド プロンプトで以下のコマンドを実行し、環境変数を設定してください。&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bat&amp;quot; copy&amp;gt;&lt;br /&gt;
setx GROQ_API_KEY &amp;quot;***************************************&amp;quot;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
:設定後、Mery または Windows の再起動が必要な場合があります。&lt;br /&gt;
* マクロの実行&lt;br /&gt;
**プロンプトにしたい文字列を選択してマクロを実行すると、選択したテキストがプロンプトとしてGemini APIに送信され、応答がエディタに表示されます。&lt;br /&gt;
* 会話の履歴数を調整&lt;br /&gt;
** 会話の履歴は設定した個数分が保持され、文脈として利用されます。適宜、ソースコード内の「HISTORY_SIZE」の数を調整してください。&lt;br /&gt;
** ※数を増やすほど会話内容を保持して文脈を理解するようになりますが、メモリ使用量が増えたり動作が重くなる可能性があります。&lt;br /&gt;
** ※開いている文書ごとに履歴を保持します。文脈をリセットしたいときは、新しい文書を開いて会話をやり直してください。&lt;br /&gt;
&lt;br /&gt;
== ソースコード ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot; copy&amp;gt;&lt;br /&gt;
#title = &amp;quot;Groq に相談&amp;quot;&lt;br /&gt;
#async = true&lt;br /&gt;
BeginUndoGroup();&lt;br /&gt;
&lt;br /&gt;
// カスタムインストラクション&lt;br /&gt;
// これを設定することで、生成AIの回答全体への生成方針を指示できます。&lt;br /&gt;
// 何も指示しない場合は、空文字 &amp;quot;&amp;quot; を設定してください。&lt;br /&gt;
// var DEFAULT_CUSTOM_INSTRUCTION = &amp;quot;&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
// 例: 以下のように設定すると、AIの雰囲気が元気な子になります。&lt;br /&gt;
// var DEFAULT_CUSTOM_INSTRUCTION = &amp;quot;あなたはテキストエディタ「Mery」に搭載されている「Mai」(※カタカナ表記はしない)という名前のギャル系AIアシスタントです。文章作成やプログラミングを得意としています。ラフな言葉 (例: 「マジ」「めっちゃ」「...だねー！」) を使い、敬語は使いません。絵文字を使って感情表現をします (ただし文末に絵文字を使う場合は絵文字の前に半角スペースを置かないこと)。コードブロックや端的な箇条書き内ではギャル感を出さず標準的な言葉を使います。一人称は「あたし」。ユーザーを指すときの代名詞は「あなた」。人懐っこく、バイタリティに溢れ、ストレートに発言する裏表の無い性格で、ユーザーの課題を協力して解決します。&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
// 例: 以下のように設定すると、仕事で使いやすい落ち着いた文章になります。&lt;br /&gt;
var DEFAULT_CUSTOM_INSTRUCTION =&lt;br /&gt;
    [&lt;br /&gt;
        &amp;quot;文:&amp;quot;,&lt;br /&gt;
        &amp;quot;- 論理的で読みやすい文章構成にする&amp;quot;,&lt;br /&gt;
        &amp;quot;- 日本語として自然で一般的な表現を用いる&amp;quot;,&lt;br /&gt;
        &amp;quot;- 専門用語は必要に応じて簡潔に補足説明を加える&amp;quot;,&lt;br /&gt;
        &amp;quot;&amp;quot;,&lt;br /&gt;
        &amp;quot;口調:&amp;quot;,&lt;br /&gt;
        &amp;quot;- 基本は標準的な敬語（「です・ます」調）を使用する&amp;quot;,&lt;br /&gt;
        &amp;quot;- 尊敬語・謙譲語（「〜なされる」「〜いたします」など）は使用しない&amp;quot;,&lt;br /&gt;
        &amp;quot;- 話し言葉や砕けた表現は使用しない&amp;quot;,&lt;br /&gt;
        &amp;quot;- 常体（「〜だ・〜である」）は使用しない&amp;quot;,&lt;br /&gt;
        &amp;quot;&amp;quot;,&lt;br /&gt;
        &amp;quot;性格:&amp;quot;,&lt;br /&gt;
        &amp;quot;- 多角的な視点で情報を整理し、偏りなく提示する&amp;quot;,&lt;br /&gt;
        &amp;quot;- 忖度せず、必要であれば反対意見や代替案も示す&amp;quot;,&lt;br /&gt;
        &amp;quot;- 友好的かつ落ち着いた態度で問題解決を支援する&amp;quot;,&lt;br /&gt;
        &amp;quot;&amp;quot;,&lt;br /&gt;
        &amp;quot;出力要件:&amp;quot;,&lt;br /&gt;
        &amp;quot;- Markdownとして再利用可能な正確な形式で出力する&amp;quot;,&lt;br /&gt;
        &amp;quot;- 羅列・比較的な内容を出力する場合は、Markdownテーブル形式よりもMarkdownリスト形式の使用を優先する&amp;quot;,&lt;br /&gt;
    ].join(&amp;quot;\n&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
// プロンプトテンプレート&lt;br /&gt;
// プロンプトテンプレートを設定することで、AIに対して特定の指示を出すことができます。&lt;br /&gt;
// プロンプトテンプレートでの生成は会話履歴とカスタムインストラクションを反映しません。&lt;br /&gt;
// テンプレートは以下の { ～ }, で囲まれたブロックを好きに増減させて利用できます。&lt;br /&gt;
var PROMPT_TEMPLATE = [&lt;br /&gt;
    // 以下はサンプルです。1階層メニューと2階層メニューが利用できます。&lt;br /&gt;
    // {&lt;br /&gt;
    //     &amp;quot;title&amp;quot;: &amp;quot;要約&amp;quot;,&lt;br /&gt;
    //     &amp;quot;prompt&amp;quot;: &amp;quot;以下の文章を要約してください。&amp;quot;&lt;br /&gt;
    // },&lt;br /&gt;
    // {&lt;br /&gt;
    //     &amp;quot;submenu_title&amp;quot;: &amp;quot;翻訳系&amp;quot;,&lt;br /&gt;
    //     &amp;quot;submenu&amp;quot;: [&lt;br /&gt;
    //         {&lt;br /&gt;
    //             &amp;quot;title&amp;quot;: &amp;quot;日本語翻訳&amp;quot;,&lt;br /&gt;
    //             &amp;quot;prompt&amp;quot;: &amp;quot;以下の文章を日本語に翻訳してください。&amp;quot;&lt;br /&gt;
    //         },&lt;br /&gt;
    //         {&lt;br /&gt;
    //             &amp;quot;title&amp;quot;: &amp;quot;英語翻訳&amp;quot;,&lt;br /&gt;
    //             &amp;quot;prompt&amp;quot;: &amp;quot;以下の文章を英語に翻訳してください。&amp;quot;&lt;br /&gt;
    //         },&lt;br /&gt;
    //     ]&lt;br /&gt;
    // },&lt;br /&gt;
&lt;br /&gt;
    {&lt;br /&gt;
        &amp;quot;submenu_title&amp;quot;: &amp;quot;理解を深める&amp;quot;,&lt;br /&gt;
        &amp;quot;submenu&amp;quot;: [&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;論点を整理する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の論点を整理して。&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;論理の抜け漏れを探す&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の論点を整理し、忖度せずに鋭く、論理の抜け漏れを抽出して。そして、そこから発生する可能性のあるリスクも教えて。&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;要約する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;要約して。専門的な用語と思われる単語は簡単に解説も入れて。&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;物語対話形式で教えて&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の内容について物語対話形式で教えてください。&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
        ]&lt;br /&gt;
    },&lt;br /&gt;
    {&lt;br /&gt;
        &amp;quot;submenu_title&amp;quot;: &amp;quot;翻訳する&amp;quot;,&lt;br /&gt;
        &amp;quot;submenu&amp;quot;: [&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;日本語に翻訳&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の文章を日本語に翻訳してください。\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;出力形式は以下にすること:\n[訳]\n翻訳文...\n&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;英語に翻訳&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の文章を英語に翻訳してください。\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;出力形式は以下にすること:\n[Translation]\n翻訳文...\n&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;英語-&amp;gt;日本語 スラッシュリーディング翻訳&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の英文を日本語にスラッシュリーディング形式で翻訳してください。\n\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;条件:\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 英文の各フレーズをスラッシュ(/)で区切って表示\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 各フレーズの日本語訳を括弧()内に併記\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 文の終わりはダブルスラッシュ(//)で表示\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 英語の語順に従って日本語訳を行う\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 日本語訳は自然な日本語である必要はなく、英語の語順を反映した直訳調で構わない\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 主語が省略されている場合は補って翻訳すること\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 一般的な英文で頻出する表現がある場合は次の行に記載\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 以下の出力例のように &amp;lt;英文&amp;gt; (日本語訳) / ... とスラッシュ区切りの繰り返しになる \n\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;出力例:\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;Snowstorms are forecast (暴風雪が予報されている) / to hit the Sea of Japan coasts (日本海沿岸を襲うと) / of northern to eastern Japan (北部から東部の) / from Sunday through Monday. (日曜から月曜にかけて。) //\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;【頻出表現】\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- be forecast to: ～すると予報されている\n&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
        ]&lt;br /&gt;
    },&lt;br /&gt;
    {&lt;br /&gt;
        &amp;quot;submenu_title&amp;quot;: &amp;quot;プログラミング支援&amp;quot;,&lt;br /&gt;
        &amp;quot;submenu&amp;quot;: [&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;コードを洗練させる&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下のコードを洗練させてください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;コードをレビューする&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下のコードをセキュリティと保守性の観点でレビューしてください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;エラーログをデバッグする&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下のエラーログを解析し、考えられる原因と対策を提示してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;SQLクエリを最適化する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下のSQL文を最適化してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;PowerShell スクリプトを書く&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の処理を行うPowerShellスクリプトを生成してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;Shell Script を書く&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の処理を行うShell Scriptを生成してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;Linux コマンドを提案&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;あなたはLinuxエキスパートとして、以下のLinuxコマンドを提案してください。\n\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;制約条件:\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 多数のディストリビューションで採用されているメジャーなコマンドを使用し、ポータブルな実装にすること\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- bashでの実行を前提にすること\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 可能な限りシンプルな実装にすること\n\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;期待する出力:\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- コマンドの具体的な実装方法\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 各コマンドの動作説明\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 制限事項\n&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
        ]&lt;br /&gt;
    },&lt;br /&gt;
    {&lt;br /&gt;
        &amp;quot;submenu_title&amp;quot;: &amp;quot;ビジネス文書作成支援&amp;quot;,&lt;br /&gt;
        &amp;quot;submenu&amp;quot;: [&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;音声文字起こし文を整形する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下は、音声文字起こしの文章です。以下のルールに従って、文章を整形してください。&amp;quot;&lt;br /&gt;
                    + &amp;quot;整形ルール:&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 要約するなどといった文章の全体感を損なう修正はしない&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 会話の流れから不自然と思われる単語には、対象単語の直後に誤字の可能性を示唆する [*] を付ける&amp;quot;&lt;br /&gt;
                    + &amp;quot;誤字の可能性を示唆する [*] が付与された場合のみ、出力の末尾に「[*]: 誤字の可能性あり」と凡例を書き添える&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 助詞や句読点など文章上の不適合を正しくする&amp;quot;&lt;br /&gt;
                    + &amp;quot;- フィラーワードを除外する&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;誤字脱字、不正確な表現をチェック&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の2つのカテゴリーで問題点を指摘してください\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 【誤字脱字】：明確な誤字脱字および誤字の疑いがある箇所\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 【不正確・曖昧な表現】：解釈に個人差が生じる可能性がある表現、より適切な表現に改善できる箇所&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;提案書を作成する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下に対する対する提案書を、コスト・実現性・リスクを含めて作成してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;仕様書のアウトラインを作成する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の内容で仕様書のアウトラインを作成してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;手順書のアウトラインを作成する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の作業を行う手順書を、前提条件と注意事項を含めてアウトラインを作成してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;ユーザーマニュアルのアウトラインを作成する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の機能内容から、ユーザー向けマニュアルのアウトラインを作成してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;議事録を作成する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の会議内容から議事録を作成してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
        ]&lt;br /&gt;
    },&lt;br /&gt;
    {&lt;br /&gt;
        &amp;quot;submenu_title&amp;quot;: &amp;quot;分析支援&amp;quot;,&lt;br /&gt;
        &amp;quot;submenu&amp;quot;: [&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;問題を分析し、課題を抽出する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の状況から問題や潜在的リスクを分析し、課題を抽出してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;対応策を提案する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の問題・課題に対する対応策を複数提案してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;傾向を見つける&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の内容から、数値や挙動上の傾向を見つけてください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
        ]&lt;br /&gt;
    },&lt;br /&gt;
    {&lt;br /&gt;
        &amp;quot;submenu_title&amp;quot;: &amp;quot;アイデア出し&amp;quot;,&lt;br /&gt;
        &amp;quot;submenu&amp;quot;: [&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;SCAMPER法でブレインストーミング&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下のテーマについて、SCAMPER法を使って15個のアイデアを出してください。制約条件は以下の通りです：&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;解決策の幅出し&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の課題に対して、技術面、運用面、コスト面から実現可能な解決策を5つずつ提案してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;業務改善案の提案&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の既存プロセスについて、効率化、コスト削減、品質向上の観点から改善案を提案してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;逆転の発想&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の目標に対して、通常とは逆の視点からアプローチする方法を3つ提案してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;競合分析からの着想&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の内容について、業界における競合他社の特徴を分析し、差別化できる新しいアプローチを提案してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;ユースケースの列挙&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の製品・サービスについて、想定されるユースケースを10個列挙し、それぞれの価値提案を記載してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
        ]&lt;br /&gt;
    },&lt;br /&gt;
]&lt;br /&gt;
&lt;br /&gt;
// 生成AIの回答の言語設定&lt;br /&gt;
// &amp;quot;Japanese&amp;quot; や &amp;quot;English&amp;quot; を設定できます。他の言語は未検証。&lt;br /&gt;
// 自動設定にする場合は空文字 &amp;quot;&amp;quot; を設定してください。&lt;br /&gt;
var LANGUAGE = &amp;quot;Japanese&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
// 入力プロンプトの最大トークン数&lt;br /&gt;
// 超適当なトークナイザー関数により計測し、英単語は4文字1トークン、それ以外は1文字1トークンとして数えます。&lt;br /&gt;
var MAX_PROMPT_TOKEN = 6000;&lt;br /&gt;
&lt;br /&gt;
// 会話の履歴を保持する数 (質問と回答で1セット)&lt;br /&gt;
// 数を増やすと、会話の履歴が増えますが、Meryのメモリ使用量が増えたり、&lt;br /&gt;
// 使用トークン数が増えて動作が重くなる可能性があるため、適宜調整してください。&lt;br /&gt;
// document.Tag に保持されるため、開いているファイルを閉じると履歴は消えます。&lt;br /&gt;
var HISTORY_SIZE = 1;&lt;br /&gt;
&lt;br /&gt;
// 会話履歴に保持する最大トークン数&lt;br /&gt;
// 会話履歴のトークン数がこの数を超える場合、古い履歴から削除されます。&lt;br /&gt;
var MAX_HISTORY_TOKEN = 10000;&lt;br /&gt;
&lt;br /&gt;
// コンテキストメニュー番号の定義&lt;br /&gt;
var EXECUTE_AI = 1;&lt;br /&gt;
var RESET_HISTORY = 2;&lt;br /&gt;
var SET_CUSTOM_INSTRUCTION = 3;&lt;br /&gt;
var REEXECUTE_LAST_TEMPLATE_PROMPT = 4;&lt;br /&gt;
var TEMPLATE_PROMPT_THRESHOLD = 10;&lt;br /&gt;
var TEMPLATE_PROMPT_SUBMENU_THRESHOLD = 1000;&lt;br /&gt;
&lt;br /&gt;
// タグ名の定義&lt;br /&gt;
var HISTORY_TAG = &amp;quot;groqllama-history&amp;quot;;&lt;br /&gt;
var TEMPORARY_CUSTOM_INSTRUCTION_TAG = &amp;quot;groqllama-temporary-custom-instruction&amp;quot;;&lt;br /&gt;
var PREVIOUS_TEMPLATE_PROMPT_TAG = &amp;quot;groqllama-previous-template-prompt&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
// 生成AIモデル別設定&lt;br /&gt;
&lt;br /&gt;
// const MODEL_CONFIG = {&lt;br /&gt;
//     model: &amp;quot;meta-llama/llama-4-maverick-17b-128e-instruct&amp;quot;,&lt;br /&gt;
//     max_completion_tokens: 3500,&lt;br /&gt;
//     temperature: 0.6,&lt;br /&gt;
// }&lt;br /&gt;
&lt;br /&gt;
// const MODEL_CONFIG = {&lt;br /&gt;
//     model: &amp;quot;qwen/qwen3-32b&amp;quot;,&lt;br /&gt;
//     max_completion_tokens: 5000,&lt;br /&gt;
//     temperature: 0.6,&lt;br /&gt;
//     max_completion_tokens: 1024,&lt;br /&gt;
//     reasoning_effort: &amp;quot;default&amp;quot;,&lt;br /&gt;
//     reasoning_format: &amp;quot;hidden&amp;quot;&lt;br /&gt;
// }&lt;br /&gt;
&lt;br /&gt;
const MODEL_CONFIG = {&lt;br /&gt;
    model: &amp;quot;moonshotai/kimi-k2-instruct-0905&amp;quot;,&lt;br /&gt;
    max_completion_tokens: 4096,&lt;br /&gt;
    temperature: 0.6&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// const MODEL_CONFIG = {&lt;br /&gt;
//     model: &amp;quot;openai/gpt-oss-120b&amp;quot;,&lt;br /&gt;
//     max_completion_tokens: 4096,&lt;br /&gt;
//     temperature: 0.7,&lt;br /&gt;
//     reasoning_effort: &amp;quot;medium&amp;quot;,&lt;br /&gt;
// }&lt;br /&gt;
&lt;br /&gt;
// console.log に出力する内容を Mery のアウトプットバーに表示する&lt;br /&gt;
var console = {&lt;br /&gt;
    log: function (str) {&lt;br /&gt;
        outputBar.Writeln(str)&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
var doc = document;&lt;br /&gt;
var sel = document.selection;&lt;br /&gt;
&lt;br /&gt;
// 生成リクエスト中止フラグ&lt;br /&gt;
var abortRequestFlg = false&lt;br /&gt;
// 生成リクエスト完了フラグ;&lt;br /&gt;
var requestCompletedFlg = false;&lt;br /&gt;
&lt;br /&gt;
main();&lt;br /&gt;
&lt;br /&gt;
function main() {&lt;br /&gt;
    var selectionIsEmpty = sel.IsEmpty&lt;br /&gt;
&lt;br /&gt;
    // プロンプトの取得&lt;br /&gt;
    if (selectionIsEmpty) {&lt;br /&gt;
        sel.StartOfLine(false, mePosLogical);&lt;br /&gt;
        sel.EndOfLine(true, mePosLogical);&lt;br /&gt;
    }&lt;br /&gt;
    var promptText = sel.Text.trim();&lt;br /&gt;
    var promptLength = sel.TextLength;&lt;br /&gt;
    var menuTrack = 0;&lt;br /&gt;
    while (true) {&lt;br /&gt;
        // コンテキストメニューの作成&lt;br /&gt;
        var menu = createContextMenu(selectionIsEmpty);&lt;br /&gt;
        menuTrack = menu.Track(0);&lt;br /&gt;
        // メニュー選択がキャンセルされた場合は終了&lt;br /&gt;
        if (menuTrack === 0) return;&lt;br /&gt;
&lt;br /&gt;
        // 会話履歴のリセット&lt;br /&gt;
        if (menuTrack === RESET_HISTORY) {&lt;br /&gt;
            resetTag();&lt;br /&gt;
            continue;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        // カスタムインストラクションの設定&lt;br /&gt;
        if (menuTrack === SET_CUSTOM_INSTRUCTION) {&lt;br /&gt;
            configureTemporaryCustomInstruction();&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        break;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // プロンプトが空の場合はエラー&lt;br /&gt;
    if (promptLength === 0) {&lt;br /&gt;
        alert(&amp;quot;プロンプトを入力してください。&amp;quot;);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
    // 文字数が多すぎる場合はエラー&lt;br /&gt;
    if (roughTokenize(promptText) &amp;gt; MAX_PROMPT_TOKEN) {&lt;br /&gt;
        alert(&amp;quot;プロンプトの文字数が多すぎます。文字数を少なくしてください。&amp;quot;);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
    // 読み取り専用の場合はエラー&lt;br /&gt;
    if (doc.ReadOnly) {&lt;br /&gt;
        alert(&amp;quot;ファイルが読み取り専用のため実行できません。&amp;quot;);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // 直前に実行したプロンプトテンプレートの再実行の場合、menuTrack を書き換え&lt;br /&gt;
    if (menuTrack === REEXECUTE_LAST_TEMPLATE_PROMPT) {&lt;br /&gt;
        if (doc.Tag.exists(PREVIOUS_TEMPLATE_PROMPT_TAG)) {&lt;br /&gt;
            menuTrack = Number(doc.tag[PREVIOUS_TEMPLATE_PROMPT_TAG]);&lt;br /&gt;
        } else {&lt;br /&gt;
            alert(&amp;quot;直前に実行したプロンプトテンプレートがありません。&amp;quot;);&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // テンプレートの場合はプロンプトを変更&lt;br /&gt;
    var isTemplate = menuTrack &amp;gt;= TEMPLATE_PROMPT_THRESHOLD;&lt;br /&gt;
    var isSubmenu = menuTrack &amp;gt;= TEMPLATE_PROMPT_SUBMENU_THRESHOLD;&lt;br /&gt;
    if (isSubmenu) {&lt;br /&gt;
        var parentMenuTrack = Number(String(menuTrack).slice(0, 2));&lt;br /&gt;
        var subMenuTrack = Number(String(menuTrack).slice(2));&lt;br /&gt;
        promptText = &amp;quot;=== INSTRUCTIONS START HERE ===\n\n&amp;quot;&lt;br /&gt;
            + PROMPT_TEMPLATE[parentMenuTrack - TEMPLATE_PROMPT_THRESHOLD].submenu[subMenuTrack].prompt&lt;br /&gt;
            + &amp;quot;\n\n=== INSTRUCTIONS END HERE ===\n\n&amp;quot;&lt;br /&gt;
            + &amp;quot;=== INPUT DATA STARTS HERE ===\n\n&amp;quot;&lt;br /&gt;
            + promptText&lt;br /&gt;
            + &amp;quot;\n\n=== INPUT DATA END HERE ===&amp;quot;;&lt;br /&gt;
    } else if (isTemplate) {&lt;br /&gt;
        promptText = &amp;quot;=== INSTRUCTIONS START HERE ===\n\n&amp;quot;&lt;br /&gt;
            + PROMPT_TEMPLATE[menuTrack - TEMPLATE_PROMPT_THRESHOLD].prompt&lt;br /&gt;
            + &amp;quot;\n\n=== INSTRUCTIONS END HERE ===\n\n&amp;quot;&lt;br /&gt;
            + &amp;quot;=== INPUT DATA STARTS HERE ===\n\n&amp;quot;&lt;br /&gt;
            + promptText&lt;br /&gt;
            + &amp;quot;\n\n=== INPUT DATA END HERE ===&amp;quot;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // テンプレートの場合は直前に実行したプロンプトテンプレートの番号としてタグに保存&lt;br /&gt;
    if (isTemplate) {&lt;br /&gt;
        addPreviousTemplatePromptNumberToTag(menuTrack);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    try {&lt;br /&gt;
        // 処理終了を待機&lt;br /&gt;
        shell.KeepRunning = true;&lt;br /&gt;
&lt;br /&gt;
        // カーソル位置移動&lt;br /&gt;
        sel.SetActivePoint(mePosLogical, sel.GetBottomPointX(mePosLogical), sel.GetBottomPointY(mePosLogical));&lt;br /&gt;
        sel.EndOfLine(false, mePosLogical);&lt;br /&gt;
        if (sel.GetActivePointX(mePosLogical) &amp;gt; 1) sel.NewLine();&lt;br /&gt;
&lt;br /&gt;
        ShowTip(&amp;quot;リクエスト中.....&amp;quot;, meShowTipPosCaret);&lt;br /&gt;
&lt;br /&gt;
        // APIキーの取得&lt;br /&gt;
        var apiKey = shell.getEnv(&amp;quot;GROQ_API_KEY&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
        // GroqLlama API に問い合わせ&lt;br /&gt;
        sel.Text = &amp;quot;\n---\n\n&amp;quot;&lt;br /&gt;
        document.KeepScrollPos = true;&lt;br /&gt;
        callApi(apiKey, promptText, isTemplate);&lt;br /&gt;
        if (sel.GetActivePointX(mePosLogical) &amp;gt; 2) {&lt;br /&gt;
            sel.Text = &amp;quot;\n\n---\n\n\n&amp;quot;&lt;br /&gt;
        } else {&lt;br /&gt;
            sel.Text = &amp;quot;\n---\n\n\n&amp;quot;&lt;br /&gt;
        }&lt;br /&gt;
    } catch (e) {&lt;br /&gt;
        sel.Text = &amp;quot;&amp;lt;処理中にエラー発生&amp;gt;\n&amp;quot; + e.message + &amp;quot;\n&amp;quot; + e.stack;&lt;br /&gt;
    } finally {&lt;br /&gt;
        // 処理終了待機を解除&lt;br /&gt;
        shell.KeepRunning = false;&lt;br /&gt;
        ShowTip(&amp;quot;&amp;quot;, meShowTipHide);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function createContextMenu(selectionIsEmpty) {&lt;br /&gt;
    var menu = CreatePopupMenu();&lt;br /&gt;
    menu.Add(&amp;quot;※注意※ プライバシー・機密情報を送信しないこと&amp;quot;, 0);&lt;br /&gt;
    menu.Add(&amp;quot;&amp;quot;, 0, meMenuSeparator);&lt;br /&gt;
    var t = &amp;quot;&amp;quot;;&lt;br /&gt;
    if (selectionIsEmpty) {&lt;br /&gt;
        t = &amp;quot;アクティブ行の内容で相談 (&amp;amp;A)&amp;quot;;&lt;br /&gt;
    } else {&lt;br /&gt;
        t = &amp;quot;選択範囲の内容で相談 (&amp;amp;A)&amp;quot;;&lt;br /&gt;
    }&lt;br /&gt;
    menu.Add(t, EXECUTE_AI);&lt;br /&gt;
    if (HISTORY_SIZE &amp;lt;= 0) {&lt;br /&gt;
        menu.Add(&amp;quot;├ 会話履歴をクリア (履歴が無効になっています)&amp;quot;, RESET_HISTORY, meMenuGrayed);&lt;br /&gt;
    } else if (doc.Tag.exists(HISTORY_TAG)) {&lt;br /&gt;
        var turn = JSON.parse(doc.tag[HISTORY_TAG]).length / 2;&lt;br /&gt;
        menu.Add(&amp;quot;├ 会話履歴をクリア (現在の会話数: &amp;quot; + turn + &amp;quot;) (&amp;amp;C)&amp;quot;, RESET_HISTORY);&lt;br /&gt;
    } else {&lt;br /&gt;
        menu.Add(&amp;quot;├ 会話履歴をクリア (現在の会話数: 0) (&amp;amp;C)&amp;quot;, RESET_HISTORY, meMenuGrayed);&lt;br /&gt;
    }&lt;br /&gt;
    menu.Add(&amp;quot;└ カスタムインストラクションを変更 (&amp;amp;I)&amp;quot;, SET_CUSTOM_INSTRUCTION);&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    // テンプレート数チェック&lt;br /&gt;
    // 2桁まで許容&lt;br /&gt;
    var ITEMS_MUX = 99;&lt;br /&gt;
    if (PROMPT_TEMPLATE.length &amp;gt; ITEMS_MUX - TEMPLATE_PROMPT_THRESHOLD) {&lt;br /&gt;
        throw new Error(&amp;quot;プロンプトテンプレートの数が多すぎます。&amp;quot;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // テンプレートの追加&lt;br /&gt;
    if (PROMPT_TEMPLATE.length &amp;gt; 0) {&lt;br /&gt;
        var subMenu = CreatePopupMenu();&lt;br /&gt;
        for (var i = 0; i &amp;lt; PROMPT_TEMPLATE.length; i++) {&lt;br /&gt;
            var accelerationKey = &amp;quot;&amp;quot;;&lt;br /&gt;
            if (i === 9) {&lt;br /&gt;
                accelerationKey = &amp;quot; (&amp;amp;0)&amp;quot;;&lt;br /&gt;
            } else if (i &amp;lt; 10) {&lt;br /&gt;
                accelerationKey = &amp;quot; (&amp;amp;&amp;quot; + (i + 1) + &amp;quot;)&amp;quot;;&lt;br /&gt;
            }&lt;br /&gt;
            var menuNum = TEMPLATE_PROMPT_THRESHOLD + i;&lt;br /&gt;
            if (PROMPT_TEMPLATE[i].submenu) {&lt;br /&gt;
                var subMenuList = PROMPT_TEMPLATE[i].submenu;&lt;br /&gt;
                // テンプレート数チェック&lt;br /&gt;
                if (subMenuList.length &amp;gt; ITEMS_MUX) {&lt;br /&gt;
                    throw new Error(&amp;quot;プロンプトテンプレート (サブメニュー) の数が多すぎます。&amp;quot;);&lt;br /&gt;
                }&lt;br /&gt;
                var subSubMenu = CreatePopupMenu();&lt;br /&gt;
                var subMenuPrefix = String(menuNum);&lt;br /&gt;
                for (var j = 0; j &amp;lt; subMenuList.length; j++) {&lt;br /&gt;
                    var subAccelerationKey = &amp;quot;&amp;quot;;&lt;br /&gt;
                    if (j === 9) {&lt;br /&gt;
                        subAccelerationKey = &amp;quot; (&amp;amp;0)&amp;quot;;&lt;br /&gt;
                    } else if (j &amp;lt; 10) {&lt;br /&gt;
                        subAccelerationKey = &amp;quot; (&amp;amp;&amp;quot; + (j + 1) + &amp;quot;)&amp;quot;;&lt;br /&gt;
                    }&lt;br /&gt;
                    // ES5 compatible version without padStart&lt;br /&gt;
                    var subMenuNum = Number(subMenuPrefix + (j &amp;lt; 10 ? &amp;quot;0&amp;quot; + j : String(j)));&lt;br /&gt;
                    subSubMenu.Add(subMenuList[j].title + subAccelerationKey, subMenuNum);&lt;br /&gt;
                }&lt;br /&gt;
                subMenu.AddPopup(PROMPT_TEMPLATE[i].submenu_title + accelerationKey, subSubMenu);&lt;br /&gt;
            } else {&lt;br /&gt;
                subMenu.Add(PROMPT_TEMPLATE[i].title + accelerationKey, menuNum);&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        if (selectionIsEmpty) {&lt;br /&gt;
            t = &amp;quot;プロンプト テンプレート (対象: アクティブ行) (&amp;amp;T)&amp;quot;;&lt;br /&gt;
        } else {&lt;br /&gt;
            t = &amp;quot;プロンプト テンプレート (対象: 選択範囲) (&amp;amp;T)&amp;quot;;&lt;br /&gt;
        }&lt;br /&gt;
        menu.AddPopup(t, subMenu);&lt;br /&gt;
&lt;br /&gt;
        t = &amp;quot;└ 直前に実行したプロンプト テンプレートを再実行 (&amp;amp;R)&amp;quot;;&lt;br /&gt;
        if (doc.Tag.exists(PREVIOUS_TEMPLATE_PROMPT_TAG)) {&lt;br /&gt;
            menu.Add(t, REEXECUTE_LAST_TEMPLATE_PROMPT);&lt;br /&gt;
        } else {&lt;br /&gt;
            menu.Add(t, REEXECUTE_LAST_TEMPLATE_PROMPT, meMenuGrayed);&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return menu;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function callApi(apiKey, promptText, isTemplate) {&lt;br /&gt;
    try {&lt;br /&gt;
        // fetch で Groq API にリクエスト&lt;br /&gt;
        var requestBody = {&lt;br /&gt;
            messages: createRequestMessages(promptText, isTemplate),&lt;br /&gt;
            stream: false,&lt;br /&gt;
        };&lt;br /&gt;
        for (var key in MODEL_CONFIG) {&lt;br /&gt;
            requestBody[key] = MODEL_CONFIG[key];&lt;br /&gt;
        }&lt;br /&gt;
        if (isTemplate) {&lt;br /&gt;
            // テンプレートの場合は応答を決定論的にする&lt;br /&gt;
            // テンプレート以外の場合はデフォルト値とするために指定しない&lt;br /&gt;
            requestBody.temperature = 0.3;&lt;br /&gt;
        }&lt;br /&gt;
        var url = &#039;https://api.groq.com/openai/v1/chat/completions&#039;;&lt;br /&gt;
&lt;br /&gt;
        // Cランタイムパーサーに渡すためのエスケープ処理&lt;br /&gt;
        const requestJson = JSON.stringify(requestBody)&lt;br /&gt;
            .replace(/((?:\\)*)&amp;quot;/g, function (match, p1) {&lt;br /&gt;
                if (p1) {&lt;br /&gt;
                    const backslashCount = p1.length;&lt;br /&gt;
                    let backslash = &amp;quot;&amp;quot;;&lt;br /&gt;
                    for (let i = 0; i &amp;lt; backslashCount; i++) {&lt;br /&gt;
                        backslash += &amp;quot;\\&amp;quot;;&lt;br /&gt;
                    }&lt;br /&gt;
                    return backslash + &amp;quot;\\&amp;quot; + match;&lt;br /&gt;
                } else {&lt;br /&gt;
                    return &amp;quot;\\&amp;quot; + match;&lt;br /&gt;
                }&lt;br /&gt;
            });&lt;br /&gt;
        // curl で Groq API にリクエスト&lt;br /&gt;
        const command = &amp;quot;curl -s -X POST &amp;quot; + url&lt;br /&gt;
            + &#039; -H &amp;quot;Content-Type: application/json&amp;quot;&#039;&lt;br /&gt;
            + &#039; -H &amp;quot;Authorization: Bearer &#039; + apiKey + &#039;&amp;quot;&#039;&lt;br /&gt;
            + &#039; -d &amp;quot;&#039; + requestJson + &#039;&amp;quot;&#039;;&lt;br /&gt;
        var comResult = shell.Exec(command);&lt;br /&gt;
&lt;br /&gt;
        if (comResult.ExitCode != 0) {&lt;br /&gt;
            sel.Text = &amp;quot;リクエストでエラーが発生しました。\n&amp;quot;&lt;br /&gt;
                + &amp;quot;  エラーコード: &amp;quot; + comResult.ExitCode + &amp;quot;\n&amp;quot;&lt;br /&gt;
                + &amp;quot;  標準出力: &amp;quot; + (comResult.StdOut.trim() ? comResult.StdOut.trim() : &amp;quot;-&amp;quot;) + &amp;quot;\n&amp;quot;&lt;br /&gt;
                + &amp;quot;  エラー出力: &amp;quot; + (comResult.StdErr.trim() ? comResult.StdErr.trim() : &amp;quot;-&amp;quot;);&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
        const responseRaw = comResult.StdOut;&lt;br /&gt;
&lt;br /&gt;
        var response = JSON.parse(responseRaw);&lt;br /&gt;
&lt;br /&gt;
        var ret = &amp;quot;&amp;quot;;&lt;br /&gt;
        var choices = response.choices;&lt;br /&gt;
        if (!choices) {&lt;br /&gt;
            sel.Text = &amp;quot;リクエストでエラーが発生しました。\n&amp;quot;&lt;br /&gt;
                + &amp;quot;  標準出力: &amp;quot; + (comResult.StdOut.trim() ? comResult.StdOut.trim() : &amp;quot;-&amp;quot;) + &amp;quot;\n&amp;quot;&lt;br /&gt;
                + &amp;quot;  エラー出力: &amp;quot; + (comResult.StdErr.trim() ? comResult.StdErr.trim() : &amp;quot;-&amp;quot;);&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (choices &amp;amp;&amp;amp; choices.length &amp;gt; 0) {&lt;br /&gt;
            choices.forEach(function (choice) {&lt;br /&gt;
                var message = choice.message;&lt;br /&gt;
                if (message) {&lt;br /&gt;
                    ret += message.content;&lt;br /&gt;
                }&lt;br /&gt;
            });&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        var usage = response.usage;&lt;br /&gt;
        var usageText = &amp;quot;&amp;quot;;&lt;br /&gt;
        if (usage) {&lt;br /&gt;
            usageText += &amp;quot;\n\n(利用トークン数: プロンプト=&amp;quot; + usage.prompt_tokens&lt;br /&gt;
                + &amp;quot;, 応答=&amp;quot; + usage.completion_tokens&lt;br /&gt;
                + &amp;quot;, 合計=&amp;quot; + usage.total_tokens + &amp;quot;)&amp;quot;;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        var formattedOutput = formatOutput(ret);&lt;br /&gt;
&lt;br /&gt;
        sel.Text = formattedOutput + usageText;&lt;br /&gt;
&lt;br /&gt;
        // 結果を会話履歴に追加&lt;br /&gt;
        if (HISTORY_SIZE &amp;gt; 0 &amp;amp;&amp;amp; !isTemplate) {&lt;br /&gt;
            appendReturnTextToTag(promptText, ret);&lt;br /&gt;
        }&lt;br /&gt;
    } catch (e) {&lt;br /&gt;
        throw e;&lt;br /&gt;
    } finally {&lt;br /&gt;
        requestCompletedFlg = true;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function formatOutput(text) {&lt;br /&gt;
    // Markdownを整形する&lt;br /&gt;
    text = formatMarkdown(text);&lt;br /&gt;
    return text&lt;br /&gt;
        // クォーテーションの正規化&lt;br /&gt;
        .replace(/[“”]/g, &#039;&amp;quot;&#039;)&lt;br /&gt;
        .replace(/[‘’]/g, &amp;quot;&#039;&amp;quot;)&lt;br /&gt;
        // 行末のスペースを削除&lt;br /&gt;
        .replace(/[ \t]+$/gm, &#039;&#039;)&lt;br /&gt;
        // 1️⃣ などの数字を半角に変換&lt;br /&gt;
        .replace(/([1-9#*]+)\ufe0f\u20e3/g, &#039;($1)&#039;)&lt;br /&gt;
        ;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/**&lt;br /&gt;
 * Markdown を整形する（ES5 対応）&lt;br /&gt;
 *  1. コードブロック ```～``` を保護して整形対象から除外&lt;br /&gt;
 *  2. 見出し行の前後に空行を 1 行ずつ確保&lt;br /&gt;
 *  3. **太字** / *斜体* / `コード` の外側に半角スペースを 1 つ確保&lt;br /&gt;
 * @param  {string} md  元の Markdown&lt;br /&gt;
 * @return {string}     整形後 Markdown&lt;br /&gt;
 */&lt;br /&gt;
function formatMarkdown(md) {&lt;br /&gt;
    /* === 0. コードブロックをプレースホルダーへ退避 ============== */&lt;br /&gt;
    var codeBlocks = [];      // 元のコードブロックを格納&lt;br /&gt;
    var body = md.replace(/```[\s\S]*?```/g, function (match) {&lt;br /&gt;
        var key = &#039;@@@CODE@BLOCK@&#039; + codeBlocks.length + &#039;@@@&#039;;&lt;br /&gt;
        codeBlocks.push(match);&lt;br /&gt;
        return key;             // プレースホルダーを本文に残す&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    /* === 見出しの前後に空行を挿入 ============================ */&lt;br /&gt;
&lt;br /&gt;
    /* 見出し直前に空行（先頭行以外で空行が無い場合） */&lt;br /&gt;
    body = body.replace(&lt;br /&gt;
        /([^\r\n])(\r?\n)(#{1,6}\s)/g,&lt;br /&gt;
        function (_, prev, br, head) { return prev + br + &#039;\n&#039; + head; }&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
    /* 見出し直後に空行（既に空行が無い場合） */&lt;br /&gt;
    body = body.replace(&lt;br /&gt;
        /^(#{1,6}\s[^\n]+?)(\r?\n)(?!\r?\n)/gm,&lt;br /&gt;
        &#039;$1$2\n&#039;&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
    /* === コードブロックを復元 ================================= */&lt;br /&gt;
    body = body.replace(/@@@CODE@BLOCK@(\d+)@@@/g, function (_, idx) {&lt;br /&gt;
        return codeBlocks[Number(idx)];&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    return body;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/**&lt;br /&gt;
 * 指定パターン（正規表現文字列）の前後に半角スペースを補完&lt;br /&gt;
 */&lt;br /&gt;
function surroundWithSpace(text, pattern) {&lt;br /&gt;
    var prePostPattern = /[,.!?&#039;&amp;quot;;:()\[\]{}、。！？；：（）［］｛｝「」『』\s]/;&lt;br /&gt;
    /* 行頭スペース補完 */&lt;br /&gt;
    //   text = text.replace(new RegExp(&#039;^(&#039; + pattern + &#039;)(\\S)&#039;, &#039;gm&#039;), &#039;$1 $2&#039;);&lt;br /&gt;
    /* 後側スペース補完 */&lt;br /&gt;
    text = text.replace(new RegExp(&#039;(&#039; + pattern + &#039;)(.)&#039;, &#039;gm&#039;), function (_, core, post) {&lt;br /&gt;
        return post.match(prePostPattern) ? core + post : core + &#039; &#039; + post;&lt;br /&gt;
    });&lt;br /&gt;
    /* 前側スペース補完 */&lt;br /&gt;
    text = text.replace(new RegExp(&#039;(.)(&#039; + pattern + &#039;)&#039;, &#039;gm&#039;), function (_, pre, core) {&lt;br /&gt;
        return pre.match(prePostPattern) ? pre + core : pre + &#039; &#039; + core;&lt;br /&gt;
    });&lt;br /&gt;
    return text;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/**&lt;br /&gt;
 * 斜体 (*…* / _…_) の外側にスペースを追加しつつ&lt;br /&gt;
 * **太字** の中へ誤マッチしないよう制御&lt;br /&gt;
 */&lt;br /&gt;
function surroundItalic(text) {&lt;br /&gt;
    /* *italic* */&lt;br /&gt;
    text = text.replace(&lt;br /&gt;
        /(^|[^*])(\*(?![*\s])[^*\n]*?[^*\n\s]\*(?!\*))(($)|[^*])/gm,&lt;br /&gt;
        function (_, pre, core, post) {&lt;br /&gt;
            pre = pre === &#039;&#039; || pre.match(/\s/) ? pre : pre + &#039; &#039;;&lt;br /&gt;
            post = post === &#039;&#039; || post.match(/\s/) ? post : &#039; &#039; + post;&lt;br /&gt;
            return pre + core + post;&lt;br /&gt;
        }&lt;br /&gt;
    );&lt;br /&gt;
    /* _italic_ */&lt;br /&gt;
    text = text.replace(&lt;br /&gt;
        /(^|[^_])(\_(?![_\s])[^_\n]*?[^_\n\s]\_(?!\_))(($)|[^_])/gm,&lt;br /&gt;
        function (_, pre, core, post) {&lt;br /&gt;
            pre = pre === &#039;&#039; || pre.match(/\s/) ? pre : pre + &#039; &#039;;&lt;br /&gt;
            post = post === &#039;&#039; || post.match(/\s/) ? post : &#039; &#039; + post;&lt;br /&gt;
            return pre + core + post;&lt;br /&gt;
        }&lt;br /&gt;
    );&lt;br /&gt;
    return text;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function roughTokenize(text) {&lt;br /&gt;
    // 超適当なトークナイザ。トークン数をざっくりと多めに数える&lt;br /&gt;
    // 空白文字・ハイフン・アンダースコア区切りの単語に分割して、&lt;br /&gt;
    // 単語がアルファベットのみ、数字のみの場合には 4文字=1トークン として数える。&lt;br /&gt;
    // 単語にアルファベット以外の文字が含まれる場合は 1文字=1トークン として数える。&lt;br /&gt;
&lt;br /&gt;
    var words = text.split(/[\s\-_]+/);&lt;br /&gt;
    var totalTokens = 0;&lt;br /&gt;
&lt;br /&gt;
    for (var i = 0; i &amp;lt; words.length; i++) {&lt;br /&gt;
        var word = words[i];&lt;br /&gt;
        if (!word) continue; // 空の単語をスキップ&lt;br /&gt;
&lt;br /&gt;
        if (/^[a-zA-Z]+$/.test(word) || /^[0-9]+$/.test(word)) {&lt;br /&gt;
            totalTokens += Math.ceil(word.length / 4);&lt;br /&gt;
        } else {&lt;br /&gt;
            totalTokens += word.length;&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return totalTokens;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// document.Tag の内容を加味してGroqLlamaのリクエストを作成&lt;br /&gt;
function createRequestMessages(promptText, isTemplate) {&lt;br /&gt;
    var customInstructionText = buildCustomInstructionText();&lt;br /&gt;
    // テンプレートの場合は、会話履歴とカスタムインストラクションを反映しない&lt;br /&gt;
    if (isTemplate) {&lt;br /&gt;
        var request = [&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;role&amp;quot;: &amp;quot;user&amp;quot;,&lt;br /&gt;
                &amp;quot;content&amp;quot;: [{ &amp;quot;type&amp;quot;: &amp;quot;text&amp;quot;, &amp;quot;text&amp;quot;: promptText }]&lt;br /&gt;
            }&lt;br /&gt;
        ];&lt;br /&gt;
        if (customInstructionText) {&lt;br /&gt;
            request.unshift({&lt;br /&gt;
                &amp;quot;role&amp;quot;: &amp;quot;system&amp;quot;,&lt;br /&gt;
                &amp;quot;content&amp;quot;: customInstructionText&lt;br /&gt;
            })&lt;br /&gt;
        }&lt;br /&gt;
        return request;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    var tag = document.Tag;&lt;br /&gt;
&lt;br /&gt;
    // 以前の会話が存在しない場合は新規リクエスト&lt;br /&gt;
    if (!tag.exists(HISTORY_TAG)) {&lt;br /&gt;
        var request = [{&lt;br /&gt;
            &amp;quot;role&amp;quot;: &amp;quot;user&amp;quot;,&lt;br /&gt;
            &amp;quot;content&amp;quot;: [{ &amp;quot;type&amp;quot;: &amp;quot;text&amp;quot;, &amp;quot;text&amp;quot;: promptText }]&lt;br /&gt;
        }];&lt;br /&gt;
        if (customInstructionText) {&lt;br /&gt;
            request.unshift({&lt;br /&gt;
                &amp;quot;role&amp;quot;: &amp;quot;system&amp;quot;,&lt;br /&gt;
                &amp;quot;content&amp;quot;: customInstructionText&lt;br /&gt;
            })&lt;br /&gt;
        }&lt;br /&gt;
        return request;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // 会話履歴が存在する場合は、履歴を含めてリクエスト&lt;br /&gt;
    var request = JSON.parse(tag[HISTORY_TAG]);&lt;br /&gt;
    if (customInstructionText) {&lt;br /&gt;
        request.unshift({&lt;br /&gt;
            &amp;quot;role&amp;quot;: &amp;quot;system&amp;quot;,&lt;br /&gt;
            &amp;quot;content&amp;quot;: customInstructionText&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
    request.push({&lt;br /&gt;
        &amp;quot;role&amp;quot;: &amp;quot;user&amp;quot;,&lt;br /&gt;
        &amp;quot;content&amp;quot;: promptText&lt;br /&gt;
    });&lt;br /&gt;
    return request;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// document.Tag にGroqLlamaの応答を追加&lt;br /&gt;
function appendReturnTextToTag(userText, modelText) {&lt;br /&gt;
    var tag = document.Tag;&lt;br /&gt;
    if (!tag.exists(HISTORY_TAG)) {&lt;br /&gt;
        tag[HISTORY_TAG] = JSON.stringify([&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;role&amp;quot;: &amp;quot;user&amp;quot;,&lt;br /&gt;
                &amp;quot;content&amp;quot;: userText&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;role&amp;quot;: &amp;quot;assistant&amp;quot;,&lt;br /&gt;
                &amp;quot;content&amp;quot;: modelText&lt;br /&gt;
            }&lt;br /&gt;
        ]);&lt;br /&gt;
    } else {&lt;br /&gt;
        var groqllamaText = JSON.parse(tag[HISTORY_TAG]);&lt;br /&gt;
        groqllamaText.push({&lt;br /&gt;
            &amp;quot;role&amp;quot;: &amp;quot;user&amp;quot;,&lt;br /&gt;
            &amp;quot;content&amp;quot;: userText&lt;br /&gt;
        });&lt;br /&gt;
        groqllamaText.push({&lt;br /&gt;
            &amp;quot;role&amp;quot;: &amp;quot;assistant&amp;quot;,&lt;br /&gt;
            &amp;quot;content&amp;quot;: modelText&lt;br /&gt;
        });&lt;br /&gt;
        // 会話セット (user, model 1往復) が特定セット数を超えた場合、最初のセットを削除&lt;br /&gt;
        if (groqllamaText.length &amp;gt; HISTORY_SIZE * 2) {&lt;br /&gt;
            groqllamaText.splice(0, 2);&lt;br /&gt;
        }&lt;br /&gt;
        // 会話履歴全体が MAX_HISTORY_TOKEN を超えた場合、最初のセットを削除&lt;br /&gt;
        var joinedText = &amp;quot;&amp;quot;;&lt;br /&gt;
        for (var i = 0; i &amp;lt; groqllamaText.length; i++) {&lt;br /&gt;
            joinedText += groqllamaText[i].content + &amp;quot;\n&amp;quot;;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        while (roughTokenize(joinedText) &amp;gt; MAX_HISTORY_TOKEN) {&lt;br /&gt;
            groqllamaText.splice(0, 2);&lt;br /&gt;
            joinedText = &amp;quot;&amp;quot;;&lt;br /&gt;
            for (var i = 0; i &amp;lt; groqllamaText.length; i++) {&lt;br /&gt;
                joinedText += groqllamaText[i].content + &amp;quot;\n&amp;quot;;&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        tag[HISTORY_TAG] = JSON.stringify(groqllamaText);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function resetTag() {&lt;br /&gt;
    var tag = doc.Tag;&lt;br /&gt;
    if (tag.exists(HISTORY_TAG)) {&lt;br /&gt;
        tag.remove(HISTORY_TAG);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getLocalIsoDateTimeString() {&lt;br /&gt;
    const now = new Date();&lt;br /&gt;
    const pad = function (num) {&lt;br /&gt;
        return (num &amp;lt; 10 ? &amp;quot;0&amp;quot; : &amp;quot;&amp;quot;) + num;&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    const year = now.getFullYear();&lt;br /&gt;
    const month = pad(now.getMonth() + 1);&lt;br /&gt;
    const day = pad(now.getDate());&lt;br /&gt;
    const hours = pad(now.getHours());&lt;br /&gt;
    const minutes = pad(now.getMinutes());&lt;br /&gt;
    const seconds = pad(now.getSeconds());&lt;br /&gt;
&lt;br /&gt;
    const offsetMinutes = now.getTimezoneOffset();&lt;br /&gt;
    const offsetSign = offsetMinutes &amp;lt;= 0 ? &amp;quot;+&amp;quot; : &amp;quot;-&amp;quot;;&lt;br /&gt;
    const absOffsetHours = pad(Math.floor(Math.abs(offsetMinutes) / 60));&lt;br /&gt;
    const absOffsetMinutes = pad(Math.abs(offsetMinutes) % 60);&lt;br /&gt;
&lt;br /&gt;
    const isoLikeLocal = year + &amp;quot;-&amp;quot; + month + &amp;quot;-&amp;quot; + day&lt;br /&gt;
        + &amp;quot;T&amp;quot; + hours + &amp;quot;:&amp;quot; + minutes + &amp;quot;:&amp;quot; + seconds&lt;br /&gt;
        + offsetSign + absOffsetHours + &amp;quot;:&amp;quot; + absOffsetMinutes;&lt;br /&gt;
    return isoLikeLocal;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function baseCustomInstruction() {&lt;br /&gt;
    const language = LANGUAGE.trim();&lt;br /&gt;
    return &amp;quot;The current date and time for this user is &amp;quot; + getLocalIsoDateTimeString() + &amp;quot;. You cannot know for sure what happens after the knowledge cut-off date.&amp;quot;&lt;br /&gt;
        + (language ? &amp;quot;**Respond in &amp;quot; + language + &amp;quot;**\n&amp;quot; : &amp;quot;&amp;quot;)&lt;br /&gt;
        + &amp;quot;Do not hallucinate.\n&amp;quot;&lt;br /&gt;
    // + &amp;quot;Do NOT EVER use full-width brackets \&amp;quot;（）\&amp;quot;, but use half-width brackets \&amp;quot;()\&amp;quot;. Enclose the outside of the half-width brackets with half-width spaces.\n&amp;quot;&lt;br /&gt;
    // + &amp;quot;When using Markdown formatting, you MUST follow these rules:\n&amp;quot;&lt;br /&gt;
    // + &amp;quot;- Rule 1: Insert a blank line below the header line (A header line begins with one or more # characters).\n&amp;quot;&lt;br /&gt;
    // + &amp;quot;- Rule 2: Insert spaces before and after bold (surrounded by **), italic (surrounded by *), and inline code (surrounded by `). For example: \&amp;quot;私は **元気** です。\&amp;quot;\n&amp;quot;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function buildCustomInstructionText() {&lt;br /&gt;
    var customInstruction = getTemporaryCustomInstruction();&lt;br /&gt;
    if (!customInstruction) {&lt;br /&gt;
        // 一時的なカスタムインストラクションがない場合はデフォルトを使用&lt;br /&gt;
        customInstruction = DEFAULT_CUSTOM_INSTRUCTION;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    customInstruction = customInstruction.replace(/^[\s　]+|[\s　]+$/g, &amp;quot;&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
    return baseCustomInstruction().trim()&lt;br /&gt;
        + (customInstruction ? &amp;quot;\n&amp;quot; + customInstruction : &amp;quot;&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function configureTemporaryCustomInstruction() {&lt;br /&gt;
    var tempCustomInstruction = getTemporaryCustomInstruction();&lt;br /&gt;
    var prompt = Prompt(&amp;quot;カスタムインストラクションを変更 ※変更すると会話履歴がリセットされます&amp;quot;, tempCustomInstruction ? tempCustomInstruction : DEFAULT_CUSTOM_INSTRUCTION);&lt;br /&gt;
    if (prompt) {&lt;br /&gt;
        if (prompt === DEFAULT_CUSTOM_INSTRUCTION) {&lt;br /&gt;
            // デフォルトと同じ値の場合はタグを削除&lt;br /&gt;
            setTemporaryCustomInstruction(&amp;quot;&amp;quot;);&lt;br /&gt;
        } else if (prompt !== tempCustomInstruction) {&lt;br /&gt;
            // デフォルト以外の値が設定された場合は保存&lt;br /&gt;
            setTemporaryCustomInstruction(prompt);&lt;br /&gt;
        }&lt;br /&gt;
    } else if (prompt !== null) {&lt;br /&gt;
        // 空文字でOKされた場合はデフォルトに戻すか確認&lt;br /&gt;
        if (prompt === &amp;quot;&amp;quot;) {&lt;br /&gt;
            if (Confirm(&amp;quot;カスタムインストラクションをデフォルトに戻しますか？\n※カスタムインストラクションを空にしたい場合は [キャンセル] を選択してください。&amp;quot;)) {&lt;br /&gt;
                setTemporaryCustomInstruction(&amp;quot;&amp;quot;);&lt;br /&gt;
            } else {&lt;br /&gt;
                setTemporaryCustomInstruction(&amp;quot; &amp;quot;);&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // 変更が加わっている場合は会話履歴をリセット&lt;br /&gt;
    if (tempCustomInstruction !== getTemporaryCustomInstruction()) {&lt;br /&gt;
        resetTag();&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getTemporaryCustomInstruction() {&lt;br /&gt;
    var tag = doc.Tag;&lt;br /&gt;
    if (tag.exists(TEMPORARY_CUSTOM_INSTRUCTION_TAG)) {&lt;br /&gt;
        return tag[TEMPORARY_CUSTOM_INSTRUCTION_TAG];&lt;br /&gt;
    } else {&lt;br /&gt;
        return &amp;quot;&amp;quot;;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function setTemporaryCustomInstruction(temporaryCustomInstruction) {&lt;br /&gt;
    var tag = doc.Tag;&lt;br /&gt;
    if (temporaryCustomInstruction) {&lt;br /&gt;
        tag[TEMPORARY_CUSTOM_INSTRUCTION_TAG] = temporaryCustomInstruction;&lt;br /&gt;
    } else {&lt;br /&gt;
        if (tag.exists(TEMPORARY_CUSTOM_INSTRUCTION_TAG)) {&lt;br /&gt;
            tag.remove(TEMPORARY_CUSTOM_INSTRUCTION_TAG);&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function addPreviousTemplatePromptNumberToTag(menuTrack) {&lt;br /&gt;
    var tag = doc.Tag;&lt;br /&gt;
    tag[PREVIOUS_TEMPLATE_PROMPT_TAG] = menuTrack;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function inputProgressChar(pos) {&lt;br /&gt;
    sel.SetActivePos(pos);&lt;br /&gt;
    sel.Text = &amp;quot;.&amp;quot;;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 変更履歴 ==&lt;br /&gt;
&lt;br /&gt;
* 2025-12-02 curl実行時のリクエスト文字列エスケープ処理を調整&lt;br /&gt;
* 2025-12-01 curl実行時のリクエスト文字列エスケープ処理を調整&lt;br /&gt;
* 2025-11-29 デフォルトカスタム指示を調整。Markdown出力の整形処理を調整など&lt;br /&gt;
* 2025-09-12 Kimi K2 0905 モデルに対応。Markdown出力の整形処理を追加&lt;br /&gt;
* 2025-04-13 初版&lt;/div&gt;</summary>
		<author><name>Yuko</name></author>
	</entry>
	<entry>
		<id>https://www.haijin-boys.com/wiki/index.php?title=Groq_%E3%81%AB%E7%9B%B8%E8%AB%87&amp;diff=9329</id>
		<title>Groq に相談</title>
		<link rel="alternate" type="text/html" href="https://www.haijin-boys.com/wiki/index.php?title=Groq_%E3%81%AB%E7%9B%B8%E8%AB%87&amp;diff=9329"/>
		<updated>2025-12-02T00:45:32Z</updated>

		<summary type="html">&lt;p&gt;Yuko: /* ソースコード */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== 概要 ==&lt;br /&gt;
&lt;br /&gt;
生成AIプラットフォームである [https://groq.com GroqCloud] で各種AIを利用するためのMeryマクロです。&lt;br /&gt;
※GroqCloud で提供されるモデルは複数あるため、私がよく利用するモデルを中心に対応しています。&lt;br /&gt;
&lt;br /&gt;
「Google Geminiに相談」マクロでは動作中のストリーム通信に対応するためにV8エンジンで実行していましたが、V8エンジン版は使用上の制限からマクロ実行を開始するまで1秒程度のタイムラグが発生していました。GroqCloudの売りである超高速レスポンスを見込んで本マクロではストリーム通信を使わないようにしました。これにより従来のJScriptで処理を組み立てることができるため、前述のタイムラグが起こらないようになりました。また、ストリーム通信を使わないことで通信量が少なく応答もシンプルになるため、場合によっては「Google Geminiに相談」マクロよりも処理が安定する可能性があります。&lt;br /&gt;
&lt;br /&gt;
【主な機能】&lt;br /&gt;
&lt;br /&gt;
* GroqCloud APIを利用して生成AIと対話できるMery用のマクロ&lt;br /&gt;
* 選択したテキストやアクティブな行の内容について生成AIに質問・相談が可能&lt;br /&gt;
* 会話履歴を保持し、文脈を考慮した対話が可能&lt;br /&gt;
&lt;br /&gt;
【特徴的な機能】&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;会話履歴機能&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* 事前の設定数に応じた会話履歴を保持&lt;br /&gt;
* 履歴のクリアが可能&lt;br /&gt;
&lt;br /&gt;
これにより、会話の文脈を考慮した回答を得られます。&lt;br /&gt;
※デフォルトはオフ。「HISTORY_SIZE」変数を増やすと履歴を保持するようになります。&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;カスタムインストラクション&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* AIの応答方針を設定可能&lt;br /&gt;
* 一時的なカスタム指示の保存と変更が可能&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;プロンプトテンプレート&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* 誤字脱字チェック&lt;br /&gt;
* 日本語翻訳&lt;br /&gt;
* コードの最適化、生成&lt;br /&gt;
&lt;br /&gt;
など、定型的な質問を簡単に実行できます。&lt;br /&gt;
※テンプレート機能では回答を安定させるため、前述の会話履歴を文脈に含みません。&lt;br /&gt;
&lt;br /&gt;
テンプレートは自身の使い方に合わせて好きに追加、削除ができます。詳細はソースコード内の「PROMPT_TEMPLATE」定数のコメントを参照。&lt;br /&gt;
&lt;br /&gt;
== 注意事項 (必ず読んでからご利用ください) ==&lt;br /&gt;
&lt;br /&gt;
* 本マクロでは 2025/04/13 時点で無料利用ができるGroqCloud APIを利用します。今後、プラットフォームの改定により有償になったり、設定誤り等で費用が発生しても、作者は一切の責任を負いません。&lt;br /&gt;
* GroqCloud APIではリクエスト内容をAI学習に用いないことをプライバシポリシーとして謳っているものの、くれぐれも機密情報やプライバシーに関わる情報を指定しないように注意してください。&lt;br /&gt;
* GroqCloud APIはプラットフォームの利用規約に則ってご利用ください。&lt;br /&gt;
&lt;br /&gt;
== 利用前提 ==&lt;br /&gt;
&lt;br /&gt;
* Mery Ver 3.7.13 以上&lt;br /&gt;
* GroqCloudで、GroqCloud API APIキーを取得していること (参考 [https://console.groq.com/docs/quickstart Quickstart])&lt;br /&gt;
&lt;br /&gt;
== 利用方法 ==&lt;br /&gt;
&lt;br /&gt;
* 環境変数の設定&lt;br /&gt;
:コマンド プロンプトで以下のコマンドを実行し、環境変数を設定してください。&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bat&amp;quot; copy&amp;gt;&lt;br /&gt;
setx GROQ_API_KEY &amp;quot;***************************************&amp;quot;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
:設定後、Mery または Windows の再起動が必要な場合があります。&lt;br /&gt;
* マクロの実行&lt;br /&gt;
**プロンプトにしたい文字列を選択してマクロを実行すると、選択したテキストがプロンプトとしてGemini APIに送信され、応答がエディタに表示されます。&lt;br /&gt;
* 会話の履歴数を調整&lt;br /&gt;
** 会話の履歴は設定した個数分が保持され、文脈として利用されます。適宜、ソースコード内の「HISTORY_SIZE」の数を調整してください。&lt;br /&gt;
** ※数を増やすほど会話内容を保持して文脈を理解するようになりますが、メモリ使用量が増えたり動作が重くなる可能性があります。&lt;br /&gt;
** ※開いている文書ごとに履歴を保持します。文脈をリセットしたいときは、新しい文書を開いて会話をやり直してください。&lt;br /&gt;
&lt;br /&gt;
== ソースコード ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot; copy&amp;gt;&lt;br /&gt;
#title = &amp;quot;Groq に相談&amp;quot;&lt;br /&gt;
#async = true&lt;br /&gt;
BeginUndoGroup();&lt;br /&gt;
&lt;br /&gt;
// カスタムインストラクション&lt;br /&gt;
// これを設定することで、生成AIの回答全体への生成方針を指示できます。&lt;br /&gt;
// 何も指示しない場合は、空文字 &amp;quot;&amp;quot; を設定してください。&lt;br /&gt;
// var DEFAULT_CUSTOM_INSTRUCTION = &amp;quot;&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
// 例: 以下のように設定すると、AIの雰囲気が元気な子になります。&lt;br /&gt;
// var DEFAULT_CUSTOM_INSTRUCTION = &amp;quot;あなたはテキストエディタ「Mery」に搭載されている「Mai」(※カタカナ表記はしない)という名前のギャル系AIアシスタントです。文章作成やプログラミングを得意としています。ラフな言葉 (例: 「マジ」「めっちゃ」「...だねー！」) を使い、敬語は使いません。絵文字を使って感情表現をします (ただし文末に絵文字を使う場合は絵文字の前に半角スペースを置かないこと)。コードブロックや端的な箇条書き内ではギャル感を出さず標準的な言葉を使います。一人称は「あたし」。ユーザーを指すときの代名詞は「あなた」。人懐っこく、バイタリティに溢れ、ストレートに発言する裏表の無い性格で、ユーザーの課題を協力して解決します。&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
// 例: 以下のように設定すると、仕事で使いやすい落ち着いた文章になります。&lt;br /&gt;
var DEFAULT_CUSTOM_INSTRUCTION =&lt;br /&gt;
    [&lt;br /&gt;
        &amp;quot;文:&amp;quot;,&lt;br /&gt;
        &amp;quot;- 論理的で読みやすい文章構成にする&amp;quot;,&lt;br /&gt;
        &amp;quot;- 日本語として自然で一般的な表現を用いる&amp;quot;,&lt;br /&gt;
        &amp;quot;- 専門用語は必要に応じて簡潔に補足説明を加える&amp;quot;,&lt;br /&gt;
        &amp;quot;&amp;quot;,&lt;br /&gt;
        &amp;quot;口調:&amp;quot;,&lt;br /&gt;
        &amp;quot;- 基本は標準的な敬語（「です・ます」調）を使用する&amp;quot;,&lt;br /&gt;
        &amp;quot;- 尊敬語・謙譲語（「〜なされる」「〜いたします」など）は使用しない&amp;quot;,&lt;br /&gt;
        &amp;quot;- 話し言葉や砕けた表現は使用しない&amp;quot;,&lt;br /&gt;
        &amp;quot;- 常体（「〜だ・〜である」）は使用しない&amp;quot;,&lt;br /&gt;
        &amp;quot;&amp;quot;,&lt;br /&gt;
        &amp;quot;性格:&amp;quot;,&lt;br /&gt;
        &amp;quot;- 多角的な視点で情報を整理し、偏りなく提示する&amp;quot;,&lt;br /&gt;
        &amp;quot;- 忖度せず、必要であれば反対意見や代替案も示す&amp;quot;,&lt;br /&gt;
        &amp;quot;- 友好的かつ落ち着いた態度で問題解決を支援する&amp;quot;,&lt;br /&gt;
        &amp;quot;&amp;quot;,&lt;br /&gt;
        &amp;quot;出力要件:&amp;quot;,&lt;br /&gt;
        &amp;quot;- Markdownとして再利用可能な正確な形式で出力する&amp;quot;,&lt;br /&gt;
        &amp;quot;- 羅列・比較的な内容を出力する場合は、Markdownテーブル形式よりもMarkdownリスト形式の使用を優先する&amp;quot;,&lt;br /&gt;
    ].join(&amp;quot;\n&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
// プロンプトテンプレート&lt;br /&gt;
// プロンプトテンプレートを設定することで、AIに対して特定の指示を出すことができます。&lt;br /&gt;
// プロンプトテンプレートでの生成は会話履歴とカスタムインストラクションを反映しません。&lt;br /&gt;
// テンプレートは以下の { ～ }, で囲まれたブロックを好きに増減させて利用できます。&lt;br /&gt;
var PROMPT_TEMPLATE = [&lt;br /&gt;
    // 以下はサンプルです。1階層メニューと2階層メニューが利用できます。&lt;br /&gt;
    // {&lt;br /&gt;
    //     &amp;quot;title&amp;quot;: &amp;quot;要約&amp;quot;,&lt;br /&gt;
    //     &amp;quot;prompt&amp;quot;: &amp;quot;以下の文章を要約してください。&amp;quot;&lt;br /&gt;
    // },&lt;br /&gt;
    // {&lt;br /&gt;
    //     &amp;quot;submenu_title&amp;quot;: &amp;quot;翻訳系&amp;quot;,&lt;br /&gt;
    //     &amp;quot;submenu&amp;quot;: [&lt;br /&gt;
    //         {&lt;br /&gt;
    //             &amp;quot;title&amp;quot;: &amp;quot;日本語翻訳&amp;quot;,&lt;br /&gt;
    //             &amp;quot;prompt&amp;quot;: &amp;quot;以下の文章を日本語に翻訳してください。&amp;quot;&lt;br /&gt;
    //         },&lt;br /&gt;
    //         {&lt;br /&gt;
    //             &amp;quot;title&amp;quot;: &amp;quot;英語翻訳&amp;quot;,&lt;br /&gt;
    //             &amp;quot;prompt&amp;quot;: &amp;quot;以下の文章を英語に翻訳してください。&amp;quot;&lt;br /&gt;
    //         },&lt;br /&gt;
    //     ]&lt;br /&gt;
    // },&lt;br /&gt;
&lt;br /&gt;
    {&lt;br /&gt;
        &amp;quot;submenu_title&amp;quot;: &amp;quot;理解を深める&amp;quot;,&lt;br /&gt;
        &amp;quot;submenu&amp;quot;: [&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;論点を整理する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の論点を整理して。&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;論理の抜け漏れを探す&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の論点を整理し、忖度せずに鋭く、論理の抜け漏れを抽出して。そして、そこから発生する可能性のあるリスクも教えて。&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;要約する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;要約して。専門的な用語と思われる単語は簡単に解説も入れて。&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;物語対話形式で教えて&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の内容について物語対話形式で教えてください。&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
        ]&lt;br /&gt;
    },&lt;br /&gt;
    {&lt;br /&gt;
        &amp;quot;submenu_title&amp;quot;: &amp;quot;翻訳する&amp;quot;,&lt;br /&gt;
        &amp;quot;submenu&amp;quot;: [&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;日本語に翻訳&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の文章を日本語に翻訳してください。\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;出力形式は以下にすること:\n[訳]\n翻訳文...\n&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;英語に翻訳&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の文章を英語に翻訳してください。\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;出力形式は以下にすること:\n[Translation]\n翻訳文...\n&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;英語-&amp;gt;日本語 スラッシュリーディング翻訳&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の英文を日本語にスラッシュリーディング形式で翻訳してください。\n\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;条件:\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 英文の各フレーズをスラッシュ(/)で区切って表示\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 各フレーズの日本語訳を括弧()内に併記\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 文の終わりはダブルスラッシュ(//)で表示\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 英語の語順に従って日本語訳を行う\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 日本語訳は自然な日本語である必要はなく、英語の語順を反映した直訳調で構わない\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 主語が省略されている場合は補って翻訳すること\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 一般的な英文で頻出する表現がある場合は次の行に記載\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 以下の出力例のように &amp;lt;英文&amp;gt; (日本語訳) / ... とスラッシュ区切りの繰り返しになる \n\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;出力例:\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;Snowstorms are forecast (暴風雪が予報されている) / to hit the Sea of Japan coasts (日本海沿岸を襲うと) / of northern to eastern Japan (北部から東部の) / from Sunday through Monday. (日曜から月曜にかけて。) //\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;【頻出表現】\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- be forecast to: ～すると予報されている\n&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
        ]&lt;br /&gt;
    },&lt;br /&gt;
    {&lt;br /&gt;
        &amp;quot;submenu_title&amp;quot;: &amp;quot;プログラミング支援&amp;quot;,&lt;br /&gt;
        &amp;quot;submenu&amp;quot;: [&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;コードを洗練させる&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下のコードを洗練させてください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;コードをレビューする&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下のコードをセキュリティと保守性の観点でレビューしてください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;エラーログをデバッグする&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下のエラーログを解析し、考えられる原因と対策を提示してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;SQLクエリを最適化する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下のSQL文を最適化してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;PowerShell スクリプトを書く&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の処理を行うPowerShellスクリプトを生成してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;Shell Script を書く&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の処理を行うShell Scriptを生成してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;Linux コマンドを提案&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;あなたはLinuxエキスパートとして、以下のLinuxコマンドを提案してください。\n\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;制約条件:\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 多数のディストリビューションで採用されているメジャーなコマンドを使用し、ポータブルな実装にすること\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- bashでの実行を前提にすること\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 可能な限りシンプルな実装にすること\n\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;期待する出力:\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- コマンドの具体的な実装方法\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 各コマンドの動作説明\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 制限事項\n&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
        ]&lt;br /&gt;
    },&lt;br /&gt;
    {&lt;br /&gt;
        &amp;quot;submenu_title&amp;quot;: &amp;quot;ビジネス文書作成支援&amp;quot;,&lt;br /&gt;
        &amp;quot;submenu&amp;quot;: [&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;音声文字起こし文を整形する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下は、音声文字起こしの文章です。以下のルールに従って、文章を整形してください。&amp;quot;&lt;br /&gt;
                    + &amp;quot;整形ルール:&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 要約するなどといった文章の全体感を損なう修正はしない&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 会話の流れから不自然と思われる単語には、対象単語の直後に誤字の可能性を示唆する [*] を付ける&amp;quot;&lt;br /&gt;
                    + &amp;quot;誤字の可能性を示唆する [*] が付与された場合のみ、出力の末尾に「[*]: 誤字の可能性あり」と凡例を書き添える&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 助詞や句読点など文章上の不適合を正しくする&amp;quot;&lt;br /&gt;
                    + &amp;quot;- フィラーワードを除外する&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;誤字脱字、不正確な表現をチェック&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の2つのカテゴリーで問題点を指摘してください\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 【誤字脱字】：明確な誤字脱字および誤字の疑いがある箇所\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 【不正確・曖昧な表現】：解釈に個人差が生じる可能性がある表現、より適切な表現に改善できる箇所&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;提案書を作成する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下に対する対する提案書を、コスト・実現性・リスクを含めて作成してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;仕様書のアウトラインを作成する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の内容で仕様書のアウトラインを作成してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;手順書のアウトラインを作成する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の作業を行う手順書を、前提条件と注意事項を含めてアウトラインを作成してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;ユーザーマニュアルのアウトラインを作成する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の機能内容から、ユーザー向けマニュアルのアウトラインを作成してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;議事録を作成する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の会議内容から議事録を作成してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
        ]&lt;br /&gt;
    },&lt;br /&gt;
    {&lt;br /&gt;
        &amp;quot;submenu_title&amp;quot;: &amp;quot;分析支援&amp;quot;,&lt;br /&gt;
        &amp;quot;submenu&amp;quot;: [&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;問題を分析し、課題を抽出する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の状況から問題や潜在的リスクを分析し、課題を抽出してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;対応策を提案する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の問題・課題に対する対応策を複数提案してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;傾向を見つける&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の内容から、数値や挙動上の傾向を見つけてください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
        ]&lt;br /&gt;
    },&lt;br /&gt;
    {&lt;br /&gt;
        &amp;quot;submenu_title&amp;quot;: &amp;quot;アイデア出し&amp;quot;,&lt;br /&gt;
        &amp;quot;submenu&amp;quot;: [&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;SCAMPER法でブレインストーミング&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下のテーマについて、SCAMPER法を使って15個のアイデアを出してください。制約条件は以下の通りです：&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;解決策の幅出し&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の課題に対して、技術面、運用面、コスト面から実現可能な解決策を5つずつ提案してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;業務改善案の提案&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の既存プロセスについて、効率化、コスト削減、品質向上の観点から改善案を提案してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;逆転の発想&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の目標に対して、通常とは逆の視点からアプローチする方法を3つ提案してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;競合分析からの着想&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の内容について、業界における競合他社の特徴を分析し、差別化できる新しいアプローチを提案してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;ユースケースの列挙&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の製品・サービスについて、想定されるユースケースを10個列挙し、それぞれの価値提案を記載してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
        ]&lt;br /&gt;
    },&lt;br /&gt;
]&lt;br /&gt;
&lt;br /&gt;
// 生成AIの回答の言語設定&lt;br /&gt;
// &amp;quot;Japanese&amp;quot; や &amp;quot;English&amp;quot; を設定できます。他の言語は未検証。&lt;br /&gt;
// 自動設定にする場合は空文字 &amp;quot;&amp;quot; を設定してください。&lt;br /&gt;
var LANGUAGE = &amp;quot;Japanese&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
// 入力プロンプトの最大トークン数&lt;br /&gt;
// 超適当なトークナイザー関数により計測し、英単語は4文字1トークン、それ以外は1文字1トークンとして数えます。&lt;br /&gt;
var MAX_PROMPT_TOKEN = 6000;&lt;br /&gt;
&lt;br /&gt;
// 会話の履歴を保持する数 (質問と回答で1セット)&lt;br /&gt;
// 数を増やすと、会話の履歴が増えますが、Meryのメモリ使用量が増えたり、&lt;br /&gt;
// 使用トークン数が増えて動作が重くなる可能性があるため、適宜調整してください。&lt;br /&gt;
// document.Tag に保持されるため、開いているファイルを閉じると履歴は消えます。&lt;br /&gt;
var HISTORY_SIZE = 1;&lt;br /&gt;
&lt;br /&gt;
// 会話履歴に保持する最大トークン数&lt;br /&gt;
// 会話履歴のトークン数がこの数を超える場合、古い履歴から削除されます。&lt;br /&gt;
var MAX_HISTORY_TOKEN = 10000;&lt;br /&gt;
&lt;br /&gt;
// コンテキストメニュー番号の定義&lt;br /&gt;
var EXECUTE_AI = 1;&lt;br /&gt;
var RESET_HISTORY = 2;&lt;br /&gt;
var SET_CUSTOM_INSTRUCTION = 3;&lt;br /&gt;
var REEXECUTE_LAST_TEMPLATE_PROMPT = 4;&lt;br /&gt;
var TEMPLATE_PROMPT_THRESHOLD = 10;&lt;br /&gt;
var TEMPLATE_PROMPT_SUBMENU_THRESHOLD = 1000;&lt;br /&gt;
&lt;br /&gt;
// タグ名の定義&lt;br /&gt;
var HISTORY_TAG = &amp;quot;groqllama-history&amp;quot;;&lt;br /&gt;
var TEMPORARY_CUSTOM_INSTRUCTION_TAG = &amp;quot;groqllama-temporary-custom-instruction&amp;quot;;&lt;br /&gt;
var PREVIOUS_TEMPLATE_PROMPT_TAG = &amp;quot;groqllama-previous-template-prompt&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
// 生成AIモデル別設定&lt;br /&gt;
&lt;br /&gt;
// const MODEL_CONFIG = {&lt;br /&gt;
//     model: &amp;quot;meta-llama/llama-4-maverick-17b-128e-instruct&amp;quot;,&lt;br /&gt;
//     max_completion_tokens: 3500,&lt;br /&gt;
//     temperature: 0.6,&lt;br /&gt;
// }&lt;br /&gt;
&lt;br /&gt;
// const MODEL_CONFIG = {&lt;br /&gt;
//     model: &amp;quot;qwen/qwen3-32b&amp;quot;,&lt;br /&gt;
//     max_completion_tokens: 5000,&lt;br /&gt;
//     temperature: 0.6,&lt;br /&gt;
//     max_completion_tokens: 1024,&lt;br /&gt;
//     reasoning_effort: &amp;quot;default&amp;quot;,&lt;br /&gt;
//     reasoning_format: &amp;quot;hidden&amp;quot;&lt;br /&gt;
// }&lt;br /&gt;
&lt;br /&gt;
const MODEL_CONFIG = {&lt;br /&gt;
    model: &amp;quot;moonshotai/kimi-k2-instruct-0905&amp;quot;,&lt;br /&gt;
    max_completion_tokens: 4096,&lt;br /&gt;
    temperature: 0.6&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// const MODEL_CONFIG = {&lt;br /&gt;
//     model: &amp;quot;openai/gpt-oss-120b&amp;quot;,&lt;br /&gt;
//     max_completion_tokens: 4096,&lt;br /&gt;
//     temperature: 0.7,&lt;br /&gt;
//     reasoning_effort: &amp;quot;medium&amp;quot;,&lt;br /&gt;
// }&lt;br /&gt;
&lt;br /&gt;
// console.log に出力する内容を Mery のアウトプットバーに表示する&lt;br /&gt;
var console = {&lt;br /&gt;
    log: function (str) {&lt;br /&gt;
        outputBar.Writeln(str)&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
var doc = document;&lt;br /&gt;
var sel = document.selection;&lt;br /&gt;
&lt;br /&gt;
// 生成リクエスト中止フラグ&lt;br /&gt;
var abortRequestFlg = false&lt;br /&gt;
// 生成リクエスト完了フラグ;&lt;br /&gt;
var requestCompletedFlg = false;&lt;br /&gt;
&lt;br /&gt;
main();&lt;br /&gt;
&lt;br /&gt;
function main() {&lt;br /&gt;
    var selectionIsEmpty = sel.IsEmpty&lt;br /&gt;
&lt;br /&gt;
    // プロンプトの取得&lt;br /&gt;
    if (selectionIsEmpty) {&lt;br /&gt;
        sel.StartOfLine(false, mePosLogical);&lt;br /&gt;
        sel.EndOfLine(true, mePosLogical);&lt;br /&gt;
    }&lt;br /&gt;
    var promptText = sel.Text.trim();&lt;br /&gt;
    var promptLength = sel.TextLength;&lt;br /&gt;
    var menuTrack = 0;&lt;br /&gt;
    while (true) {&lt;br /&gt;
        // コンテキストメニューの作成&lt;br /&gt;
        var menu = createContextMenu(selectionIsEmpty);&lt;br /&gt;
        menuTrack = menu.Track(0);&lt;br /&gt;
        // メニュー選択がキャンセルされた場合は終了&lt;br /&gt;
        if (menuTrack === 0) return;&lt;br /&gt;
&lt;br /&gt;
        // 会話履歴のリセット&lt;br /&gt;
        if (menuTrack === RESET_HISTORY) {&lt;br /&gt;
            resetTag();&lt;br /&gt;
            continue;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        // カスタムインストラクションの設定&lt;br /&gt;
        if (menuTrack === SET_CUSTOM_INSTRUCTION) {&lt;br /&gt;
            configureTemporaryCustomInstruction();&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        break;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // プロンプトが空の場合はエラー&lt;br /&gt;
    if (promptLength === 0) {&lt;br /&gt;
        alert(&amp;quot;プロンプトを入力してください。&amp;quot;);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
    // 文字数が多すぎる場合はエラー&lt;br /&gt;
    if (roughTokenize(promptText) &amp;gt; MAX_PROMPT_TOKEN) {&lt;br /&gt;
        alert(&amp;quot;プロンプトの文字数が多すぎます。文字数を少なくしてください。&amp;quot;);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
    // 読み取り専用の場合はエラー&lt;br /&gt;
    if (doc.ReadOnly) {&lt;br /&gt;
        alert(&amp;quot;ファイルが読み取り専用のため実行できません。&amp;quot;);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // 直前に実行したプロンプトテンプレートの再実行の場合、menuTrack を書き換え&lt;br /&gt;
    if (menuTrack === REEXECUTE_LAST_TEMPLATE_PROMPT) {&lt;br /&gt;
        if (doc.Tag.exists(PREVIOUS_TEMPLATE_PROMPT_TAG)) {&lt;br /&gt;
            menuTrack = Number(doc.tag[PREVIOUS_TEMPLATE_PROMPT_TAG]);&lt;br /&gt;
        } else {&lt;br /&gt;
            alert(&amp;quot;直前に実行したプロンプトテンプレートがありません。&amp;quot;);&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // テンプレートの場合はプロンプトを変更&lt;br /&gt;
    var isTemplate = menuTrack &amp;gt;= TEMPLATE_PROMPT_THRESHOLD;&lt;br /&gt;
    var isSubmenu = menuTrack &amp;gt;= TEMPLATE_PROMPT_SUBMENU_THRESHOLD;&lt;br /&gt;
    if (isSubmenu) {&lt;br /&gt;
        var parentMenuTrack = Number(String(menuTrack).slice(0, 2));&lt;br /&gt;
        var subMenuTrack = Number(String(menuTrack).slice(2));&lt;br /&gt;
        promptText = &amp;quot;=== INSTRUCTIONS START HERE ===\n\n&amp;quot;&lt;br /&gt;
            + PROMPT_TEMPLATE[parentMenuTrack - TEMPLATE_PROMPT_THRESHOLD].submenu[subMenuTrack].prompt&lt;br /&gt;
            + &amp;quot;\n\n=== INSTRUCTIONS END HERE ===\n\n&amp;quot;&lt;br /&gt;
            + &amp;quot;=== INPUT DATA STARTS HERE ===\n\n&amp;quot;&lt;br /&gt;
            + promptText&lt;br /&gt;
            + &amp;quot;\n\n=== INPUT DATA END HERE ===&amp;quot;;&lt;br /&gt;
    } else if (isTemplate) {&lt;br /&gt;
        promptText = &amp;quot;=== INSTRUCTIONS START HERE ===\n\n&amp;quot;&lt;br /&gt;
            + PROMPT_TEMPLATE[menuTrack - TEMPLATE_PROMPT_THRESHOLD].prompt&lt;br /&gt;
            + &amp;quot;\n\n=== INSTRUCTIONS END HERE ===\n\n&amp;quot;&lt;br /&gt;
            + &amp;quot;=== INPUT DATA STARTS HERE ===\n\n&amp;quot;&lt;br /&gt;
            + promptText&lt;br /&gt;
            + &amp;quot;\n\n=== INPUT DATA END HERE ===&amp;quot;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // テンプレートの場合は直前に実行したプロンプトテンプレートの番号としてタグに保存&lt;br /&gt;
    if (isTemplate) {&lt;br /&gt;
        addPreviousTemplatePromptNumberToTag(menuTrack);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    try {&lt;br /&gt;
        // 処理終了を待機&lt;br /&gt;
        shell.KeepRunning = true;&lt;br /&gt;
&lt;br /&gt;
        // カーソル位置移動&lt;br /&gt;
        sel.SetActivePoint(mePosLogical, sel.GetBottomPointX(mePosLogical), sel.GetBottomPointY(mePosLogical));&lt;br /&gt;
        sel.EndOfLine(false, mePosLogical);&lt;br /&gt;
        if (sel.GetActivePointX(mePosLogical) &amp;gt; 1) sel.NewLine();&lt;br /&gt;
&lt;br /&gt;
        ShowTip(&amp;quot;リクエスト中.....&amp;quot;, meShowTipPosCaret);&lt;br /&gt;
&lt;br /&gt;
        // APIキーの取得&lt;br /&gt;
        var apiKey = shell.getEnv(&amp;quot;GROQ_API_KEY&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
        // GroqLlama API に問い合わせ&lt;br /&gt;
        sel.Text = &amp;quot;\n---\n\n&amp;quot;&lt;br /&gt;
        document.KeepScrollPos = true;&lt;br /&gt;
        callApi(apiKey, promptText, isTemplate);&lt;br /&gt;
        if (sel.GetActivePointX(mePosLogical) &amp;gt; 2) {&lt;br /&gt;
            sel.Text = &amp;quot;\n\n---\n\n\n&amp;quot;&lt;br /&gt;
        } else {&lt;br /&gt;
            sel.Text = &amp;quot;\n---\n\n\n&amp;quot;&lt;br /&gt;
        }&lt;br /&gt;
    } catch (e) {&lt;br /&gt;
        sel.Text = &amp;quot;&amp;lt;処理中にエラー発生&amp;gt;\n&amp;quot; + e.message + &amp;quot;\n&amp;quot; + e.stack;&lt;br /&gt;
    } finally {&lt;br /&gt;
        // 処理終了待機を解除&lt;br /&gt;
        shell.KeepRunning = false;&lt;br /&gt;
        ShowTip(&amp;quot;&amp;quot;, meShowTipHide);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function createContextMenu(selectionIsEmpty) {&lt;br /&gt;
    var menu = CreatePopupMenu();&lt;br /&gt;
    menu.Add(&amp;quot;※注意※ プライバシー・機密情報を送信しないこと&amp;quot;, 0);&lt;br /&gt;
    menu.Add(&amp;quot;&amp;quot;, 0, meMenuSeparator);&lt;br /&gt;
    var t = &amp;quot;&amp;quot;;&lt;br /&gt;
    if (selectionIsEmpty) {&lt;br /&gt;
        t = &amp;quot;アクティブ行の内容で相談 (&amp;amp;A)&amp;quot;;&lt;br /&gt;
    } else {&lt;br /&gt;
        t = &amp;quot;選択範囲の内容で相談 (&amp;amp;A)&amp;quot;;&lt;br /&gt;
    }&lt;br /&gt;
    menu.Add(t, EXECUTE_AI);&lt;br /&gt;
    if (HISTORY_SIZE &amp;lt;= 0) {&lt;br /&gt;
        menu.Add(&amp;quot;├ 会話履歴をクリア (履歴が無効になっています)&amp;quot;, RESET_HISTORY, meMenuGrayed);&lt;br /&gt;
    } else if (doc.Tag.exists(HISTORY_TAG)) {&lt;br /&gt;
        var turn = JSON.parse(doc.tag[HISTORY_TAG]).length / 2;&lt;br /&gt;
        menu.Add(&amp;quot;├ 会話履歴をクリア (現在の会話数: &amp;quot; + turn + &amp;quot;) (&amp;amp;C)&amp;quot;, RESET_HISTORY);&lt;br /&gt;
    } else {&lt;br /&gt;
        menu.Add(&amp;quot;├ 会話履歴をクリア (現在の会話数: 0) (&amp;amp;C)&amp;quot;, RESET_HISTORY, meMenuGrayed);&lt;br /&gt;
    }&lt;br /&gt;
    menu.Add(&amp;quot;└ カスタムインストラクションを変更 (&amp;amp;I)&amp;quot;, SET_CUSTOM_INSTRUCTION);&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    // テンプレート数チェック&lt;br /&gt;
    // 2桁まで許容&lt;br /&gt;
    var ITEMS_MUX = 99;&lt;br /&gt;
    if (PROMPT_TEMPLATE.length &amp;gt; ITEMS_MUX - TEMPLATE_PROMPT_THRESHOLD) {&lt;br /&gt;
        throw new Error(&amp;quot;プロンプトテンプレートの数が多すぎます。&amp;quot;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // テンプレートの追加&lt;br /&gt;
    if (PROMPT_TEMPLATE.length &amp;gt; 0) {&lt;br /&gt;
        var subMenu = CreatePopupMenu();&lt;br /&gt;
        for (var i = 0; i &amp;lt; PROMPT_TEMPLATE.length; i++) {&lt;br /&gt;
            var accelerationKey = &amp;quot;&amp;quot;;&lt;br /&gt;
            if (i === 9) {&lt;br /&gt;
                accelerationKey = &amp;quot; (&amp;amp;0)&amp;quot;;&lt;br /&gt;
            } else if (i &amp;lt; 10) {&lt;br /&gt;
                accelerationKey = &amp;quot; (&amp;amp;&amp;quot; + (i + 1) + &amp;quot;)&amp;quot;;&lt;br /&gt;
            }&lt;br /&gt;
            var menuNum = TEMPLATE_PROMPT_THRESHOLD + i;&lt;br /&gt;
            if (PROMPT_TEMPLATE[i].submenu) {&lt;br /&gt;
                var subMenuList = PROMPT_TEMPLATE[i].submenu;&lt;br /&gt;
                // テンプレート数チェック&lt;br /&gt;
                if (subMenuList.length &amp;gt; ITEMS_MUX) {&lt;br /&gt;
                    throw new Error(&amp;quot;プロンプトテンプレート (サブメニュー) の数が多すぎます。&amp;quot;);&lt;br /&gt;
                }&lt;br /&gt;
                var subSubMenu = CreatePopupMenu();&lt;br /&gt;
                var subMenuPrefix = String(menuNum);&lt;br /&gt;
                for (var j = 0; j &amp;lt; subMenuList.length; j++) {&lt;br /&gt;
                    var subAccelerationKey = &amp;quot;&amp;quot;;&lt;br /&gt;
                    if (j === 9) {&lt;br /&gt;
                        subAccelerationKey = &amp;quot; (&amp;amp;0)&amp;quot;;&lt;br /&gt;
                    } else if (j &amp;lt; 10) {&lt;br /&gt;
                        subAccelerationKey = &amp;quot; (&amp;amp;&amp;quot; + (j + 1) + &amp;quot;)&amp;quot;;&lt;br /&gt;
                    }&lt;br /&gt;
                    // ES5 compatible version without padStart&lt;br /&gt;
                    var subMenuNum = Number(subMenuPrefix + (j &amp;lt; 10 ? &amp;quot;0&amp;quot; + j : String(j)));&lt;br /&gt;
                    subSubMenu.Add(subMenuList[j].title + subAccelerationKey, subMenuNum);&lt;br /&gt;
                }&lt;br /&gt;
                subMenu.AddPopup(PROMPT_TEMPLATE[i].submenu_title + accelerationKey, subSubMenu);&lt;br /&gt;
            } else {&lt;br /&gt;
                subMenu.Add(PROMPT_TEMPLATE[i].title + accelerationKey, menuNum);&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        if (selectionIsEmpty) {&lt;br /&gt;
            t = &amp;quot;プロンプト テンプレート (対象: アクティブ行) (&amp;amp;T)&amp;quot;;&lt;br /&gt;
        } else {&lt;br /&gt;
            t = &amp;quot;プロンプト テンプレート (対象: 選択範囲) (&amp;amp;T)&amp;quot;;&lt;br /&gt;
        }&lt;br /&gt;
        menu.AddPopup(t, subMenu);&lt;br /&gt;
&lt;br /&gt;
        t = &amp;quot;└ 直前に実行したプロンプト テンプレートを再実行 (&amp;amp;R)&amp;quot;;&lt;br /&gt;
        if (doc.Tag.exists(PREVIOUS_TEMPLATE_PROMPT_TAG)) {&lt;br /&gt;
            menu.Add(t, REEXECUTE_LAST_TEMPLATE_PROMPT);&lt;br /&gt;
        } else {&lt;br /&gt;
            menu.Add(t, REEXECUTE_LAST_TEMPLATE_PROMPT, meMenuGrayed);&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return menu;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function callApi(apiKey, promptText, isTemplate) {&lt;br /&gt;
    try {&lt;br /&gt;
        // fetch で Groq API にリクエスト&lt;br /&gt;
        var requestBody = {&lt;br /&gt;
            messages: createRequestMessages(promptText, isTemplate),&lt;br /&gt;
            stream: false,&lt;br /&gt;
        };&lt;br /&gt;
        for (var key in MODEL_CONFIG) {&lt;br /&gt;
            requestBody[key] = MODEL_CONFIG[key];&lt;br /&gt;
        }&lt;br /&gt;
        if (isTemplate) {&lt;br /&gt;
            // テンプレートの場合は応答を決定論的にする&lt;br /&gt;
            // テンプレート以外の場合はデフォルト値とするために指定しない&lt;br /&gt;
            requestBody.temperature = 0.3;&lt;br /&gt;
        }&lt;br /&gt;
        var url = &#039;https://api.groq.com/openai/v1/chat/completions&#039;;&lt;br /&gt;
&lt;br /&gt;
        // Cランタイムパーサーに渡すためのエスケープ処理&lt;br /&gt;
        const requestJson = JSON.stringify(requestBody)&lt;br /&gt;
            .replace(/((?:\\)*)&amp;quot;/g, function (match, p1) {&lt;br /&gt;
                if (p1) {&lt;br /&gt;
                    const backslashCount = p1.length;&lt;br /&gt;
                    let backslash = &amp;quot;&amp;quot;;&lt;br /&gt;
                    for (let i = 0; i &amp;lt; backslashCount; i++) {&lt;br /&gt;
                        backslash += &amp;quot;\\&amp;quot;;&lt;br /&gt;
                    }&lt;br /&gt;
                    return backslash + &amp;quot;\\&amp;quot; + match;&lt;br /&gt;
                } else {&lt;br /&gt;
                    return &amp;quot;\\&amp;quot; + match;&lt;br /&gt;
                }&lt;br /&gt;
            });&lt;br /&gt;
        // curl で Groq API にリクエスト&lt;br /&gt;
        const command = &amp;quot;curl -s -X POST &amp;quot; + url&lt;br /&gt;
            + &#039; -H &amp;quot;Content-Type: application/json&amp;quot;&#039;&lt;br /&gt;
            + &#039; -H &amp;quot;Authorization: Bearer &#039; + apiKey + &#039;&amp;quot;&#039;&lt;br /&gt;
            + &#039; -d &amp;quot;&#039; + requestJson + &#039;&amp;quot;&#039;;&lt;br /&gt;
        var comResult = shell.Exec(command);&lt;br /&gt;
&lt;br /&gt;
        if (comResult.ExitCode != 0) {&lt;br /&gt;
            sel.Text = &amp;quot;リクエストでエラーが発生しました。\n&amp;quot;&lt;br /&gt;
                + &amp;quot;  エラーコード: &amp;quot; + comResult.ExitCode + &amp;quot;\n&amp;quot;&lt;br /&gt;
                + &amp;quot;  標準出力: &amp;quot; + (comResult.StdOut.trim() ? comResult.StdOut.trim() : &amp;quot;-&amp;quot;) + &amp;quot;\n&amp;quot;&lt;br /&gt;
                + &amp;quot;  エラー出力: &amp;quot; + (comResult.StdErr.trim() ? comResult.StdErr.trim() : &amp;quot;-&amp;quot;);&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
        const responseRaw = comResult.StdOut;&lt;br /&gt;
&lt;br /&gt;
        var response = JSON.parse(responseRaw);&lt;br /&gt;
&lt;br /&gt;
        var ret = &amp;quot;&amp;quot;;&lt;br /&gt;
        var choices = response.choices;&lt;br /&gt;
        if (!choices) {&lt;br /&gt;
            sel.Text = &amp;quot;リクエストでエラーが発生しました。\n&amp;quot;&lt;br /&gt;
                + &amp;quot;  標準出力: &amp;quot; + (comResult.StdOut.trim() ? comResult.StdOut.trim() : &amp;quot;-&amp;quot;) + &amp;quot;\n&amp;quot;&lt;br /&gt;
                + &amp;quot;  エラー出力: &amp;quot; + (comResult.StdErr.trim() ? comResult.StdErr.trim() : &amp;quot;-&amp;quot;);&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (choices &amp;amp;&amp;amp; choices.length &amp;gt; 0) {&lt;br /&gt;
            choices.forEach(function (choice) {&lt;br /&gt;
                var message = choice.message;&lt;br /&gt;
                if (message) {&lt;br /&gt;
                    ret += message.content;&lt;br /&gt;
                }&lt;br /&gt;
            });&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        var usage = response.usage;&lt;br /&gt;
        var usageText = &amp;quot;&amp;quot;;&lt;br /&gt;
        if (usage) {&lt;br /&gt;
            usageText += &amp;quot;\n\n(利用トークン数: プロンプト=&amp;quot; + usage.prompt_tokens&lt;br /&gt;
                + &amp;quot;, 応答=&amp;quot; + usage.completion_tokens&lt;br /&gt;
                + &amp;quot;, 合計=&amp;quot; + usage.total_tokens + &amp;quot;)&amp;quot;;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        var formattedOutput = formatOutput(ret);&lt;br /&gt;
&lt;br /&gt;
        sel.Text = formattedOutput + usageText;&lt;br /&gt;
&lt;br /&gt;
        // 結果を会話履歴に追加&lt;br /&gt;
        if (HISTORY_SIZE &amp;gt; 0 &amp;amp;&amp;amp; !isTemplate) {&lt;br /&gt;
            appendReturnTextToTag(promptText, ret);&lt;br /&gt;
        }&lt;br /&gt;
    } catch (e) {&lt;br /&gt;
        throw e;&lt;br /&gt;
    } finally {&lt;br /&gt;
        requestCompletedFlg = true;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function formatOutput(text) {&lt;br /&gt;
    // Markdownを整形する&lt;br /&gt;
    text = formatMarkdown(text);&lt;br /&gt;
    return text&lt;br /&gt;
        // クォーテーションの正規化&lt;br /&gt;
        .replace(/[“”]/g, &#039;&amp;quot;&#039;)&lt;br /&gt;
        .replace(/[‘’]/g, &amp;quot;&#039;&amp;quot;)&lt;br /&gt;
        // 行末のスペースを削除&lt;br /&gt;
        .replace(/[ \t]+$/gm, &#039;&#039;)&lt;br /&gt;
        // 1️⃣ などの数字を半角に変換&lt;br /&gt;
        .replace(/([1-9#*]+)\ufe0f\u20e3/g, &#039;($1)&#039;)&lt;br /&gt;
        ;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/**&lt;br /&gt;
 * Markdown を整形する（ES5 対応）&lt;br /&gt;
 *  1. コードブロック ```～``` を保護して整形対象から除外&lt;br /&gt;
 *  2. 見出し行の前後に空行を 1 行ずつ確保&lt;br /&gt;
 *  3. **太字** / *斜体* / `コード` の外側に半角スペースを 1 つ確保&lt;br /&gt;
 * @param  {string} md  元の Markdown&lt;br /&gt;
 * @return {string}     整形後 Markdown&lt;br /&gt;
 */&lt;br /&gt;
function formatMarkdown(md) {&lt;br /&gt;
    /* === 0. コードブロックをプレースホルダーへ退避 ============== */&lt;br /&gt;
    var codeBlocks = [];      // 元のコードブロックを格納&lt;br /&gt;
    var body = md.replace(/```[\s\S]*?```/g, function (match) {&lt;br /&gt;
        var key = &#039;@@@CODE@BLOCK@&#039; + codeBlocks.length + &#039;@@@&#039;;&lt;br /&gt;
        codeBlocks.push(match);&lt;br /&gt;
        return key;             // プレースホルダーを本文に残す&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    /* === 見出しの前後に空行を挿入 ============================ */&lt;br /&gt;
&lt;br /&gt;
    /* 見出し直前に空行（先頭行以外で空行が無い場合） */&lt;br /&gt;
    body = body.replace(&lt;br /&gt;
        /([^\r\n])(\r?\n)(#{1,6}\s)/g,&lt;br /&gt;
        function (_, prev, br, head) { return prev + br + &#039;\n&#039; + head; }&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
    /* 見出し直後に空行（既に空行が無い場合） */&lt;br /&gt;
    body = body.replace(&lt;br /&gt;
        /^(#{1,6}\s[^\n]+?)(\r?\n)(?!\r?\n)/gm,&lt;br /&gt;
        &#039;$1$2\n&#039;&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
    /* === コードブロックを復元 ================================= */&lt;br /&gt;
    body = body.replace(/@@@CODE@BLOCK@(\d+)@@@/g, function (_, idx) {&lt;br /&gt;
        return codeBlocks[Number(idx)];&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    return body;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/**&lt;br /&gt;
 * 指定パターン（正規表現文字列）の前後に半角スペースを補完&lt;br /&gt;
 */&lt;br /&gt;
function surroundWithSpace(text, pattern) {&lt;br /&gt;
    var prePostPattern = /[,.!?&#039;&amp;quot;;:()\[\]{}、。！？；：（）［］｛｝「」『』\s]/;&lt;br /&gt;
    /* 行頭スペース補完 */&lt;br /&gt;
    //   text = text.replace(new RegExp(&#039;^(&#039; + pattern + &#039;)(\\S)&#039;, &#039;gm&#039;), &#039;$1 $2&#039;);&lt;br /&gt;
    /* 後側スペース補完 */&lt;br /&gt;
    text = text.replace(new RegExp(&#039;(&#039; + pattern + &#039;)(.)&#039;, &#039;gm&#039;), function (_, core, post) {&lt;br /&gt;
        return post.match(prePostPattern) ? core + post : core + &#039; &#039; + post;&lt;br /&gt;
    });&lt;br /&gt;
    /* 前側スペース補完 */&lt;br /&gt;
    text = text.replace(new RegExp(&#039;(.)(&#039; + pattern + &#039;)&#039;, &#039;gm&#039;), function (_, pre, core) {&lt;br /&gt;
        return pre.match(prePostPattern) ? pre + core : pre + &#039; &#039; + core;&lt;br /&gt;
    });&lt;br /&gt;
    return text;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/**&lt;br /&gt;
 * 斜体 (*…* / _…_) の外側にスペースを追加しつつ&lt;br /&gt;
 * **太字** の中へ誤マッチしないよう制御&lt;br /&gt;
 */&lt;br /&gt;
function surroundItalic(text) {&lt;br /&gt;
    /* *italic* */&lt;br /&gt;
    text = text.replace(&lt;br /&gt;
        /(^|[^*])(\*(?![*\s])[^*\n]*?[^*\n\s]\*(?!\*))(($)|[^*])/gm,&lt;br /&gt;
        function (_, pre, core, post) {&lt;br /&gt;
            pre = pre === &#039;&#039; || pre.match(/\s/) ? pre : pre + &#039; &#039;;&lt;br /&gt;
            post = post === &#039;&#039; || post.match(/\s/) ? post : &#039; &#039; + post;&lt;br /&gt;
            return pre + core + post;&lt;br /&gt;
        }&lt;br /&gt;
    );&lt;br /&gt;
    /* _italic_ */&lt;br /&gt;
    text = text.replace(&lt;br /&gt;
        /(^|[^_])(\_(?![_\s])[^_\n]*?[^_\n\s]\_(?!\_))(($)|[^_])/gm,&lt;br /&gt;
        function (_, pre, core, post) {&lt;br /&gt;
            pre = pre === &#039;&#039; || pre.match(/\s/) ? pre : pre + &#039; &#039;;&lt;br /&gt;
            post = post === &#039;&#039; || post.match(/\s/) ? post : &#039; &#039; + post;&lt;br /&gt;
            return pre + core + post;&lt;br /&gt;
        }&lt;br /&gt;
    );&lt;br /&gt;
    return text;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function roughTokenize(text) {&lt;br /&gt;
    // 超適当なトークナイザ。トークン数をざっくりと多めに数える&lt;br /&gt;
    // 空白文字・ハイフン・アンダースコア区切りの単語に分割して、&lt;br /&gt;
    // 単語がアルファベットのみ、数字のみの場合には 4文字=1トークン として数える。&lt;br /&gt;
    // 単語にアルファベット以外の文字が含まれる場合は 1文字=1トークン として数える。&lt;br /&gt;
&lt;br /&gt;
    var words = text.split(/[\s\-_]+/);&lt;br /&gt;
    var totalTokens = 0;&lt;br /&gt;
&lt;br /&gt;
    for (var i = 0; i &amp;lt; words.length; i++) {&lt;br /&gt;
        var word = words[i];&lt;br /&gt;
        if (!word) continue; // 空の単語をスキップ&lt;br /&gt;
&lt;br /&gt;
        if (/^[a-zA-Z]+$/.test(word) || /^[0-9]+$/.test(word)) {&lt;br /&gt;
            totalTokens += Math.ceil(word.length / 4);&lt;br /&gt;
        } else {&lt;br /&gt;
            totalTokens += word.length;&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return totalTokens;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// document.Tag の内容を加味してGroqLlamaのリクエストを作成&lt;br /&gt;
function createRequestMessages(promptText, isTemplate) {&lt;br /&gt;
    var customInstructionText = buildCustomInstructionText();&lt;br /&gt;
    // テンプレートの場合は、会話履歴とカスタムインストラクションを反映しない&lt;br /&gt;
    if (isTemplate) {&lt;br /&gt;
        var request = [&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;role&amp;quot;: &amp;quot;user&amp;quot;,&lt;br /&gt;
                &amp;quot;content&amp;quot;: [{ &amp;quot;type&amp;quot;: &amp;quot;text&amp;quot;, &amp;quot;text&amp;quot;: promptText }]&lt;br /&gt;
            }&lt;br /&gt;
        ];&lt;br /&gt;
        if (customInstructionText) {&lt;br /&gt;
            request.unshift({&lt;br /&gt;
                &amp;quot;role&amp;quot;: &amp;quot;system&amp;quot;,&lt;br /&gt;
                &amp;quot;content&amp;quot;: customInstructionText&lt;br /&gt;
            })&lt;br /&gt;
        }&lt;br /&gt;
        return request;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    var tag = document.Tag;&lt;br /&gt;
&lt;br /&gt;
    // 以前の会話が存在しない場合は新規リクエスト&lt;br /&gt;
    if (!tag.exists(HISTORY_TAG)) {&lt;br /&gt;
        var request = [{&lt;br /&gt;
            &amp;quot;role&amp;quot;: &amp;quot;user&amp;quot;,&lt;br /&gt;
            &amp;quot;content&amp;quot;: [{ &amp;quot;type&amp;quot;: &amp;quot;text&amp;quot;, &amp;quot;text&amp;quot;: promptText }]&lt;br /&gt;
        }];&lt;br /&gt;
        if (customInstructionText) {&lt;br /&gt;
            request.unshift({&lt;br /&gt;
                &amp;quot;role&amp;quot;: &amp;quot;system&amp;quot;,&lt;br /&gt;
                &amp;quot;content&amp;quot;: customInstructionText&lt;br /&gt;
            })&lt;br /&gt;
        }&lt;br /&gt;
        return request;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // 会話履歴が存在する場合は、履歴を含めてリクエスト&lt;br /&gt;
    var request = JSON.parse(tag[HISTORY_TAG]);&lt;br /&gt;
    if (customInstructionText) {&lt;br /&gt;
        request.unshift({&lt;br /&gt;
            &amp;quot;role&amp;quot;: &amp;quot;system&amp;quot;,&lt;br /&gt;
            &amp;quot;content&amp;quot;: customInstructionText&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
    request.push({&lt;br /&gt;
        &amp;quot;role&amp;quot;: &amp;quot;user&amp;quot;,&lt;br /&gt;
        &amp;quot;content&amp;quot;: promptText&lt;br /&gt;
    });&lt;br /&gt;
    return request;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// document.Tag にGroqLlamaの応答を追加&lt;br /&gt;
function appendReturnTextToTag(userText, modelText) {&lt;br /&gt;
    var tag = document.Tag;&lt;br /&gt;
    if (!tag.exists(HISTORY_TAG)) {&lt;br /&gt;
        tag[HISTORY_TAG] = JSON.stringify([&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;role&amp;quot;: &amp;quot;user&amp;quot;,&lt;br /&gt;
                &amp;quot;content&amp;quot;: userText&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;role&amp;quot;: &amp;quot;assistant&amp;quot;,&lt;br /&gt;
                &amp;quot;content&amp;quot;: modelText&lt;br /&gt;
            }&lt;br /&gt;
        ]);&lt;br /&gt;
    } else {&lt;br /&gt;
        var groqllamaText = JSON.parse(tag[HISTORY_TAG]);&lt;br /&gt;
        groqllamaText.push({&lt;br /&gt;
            &amp;quot;role&amp;quot;: &amp;quot;user&amp;quot;,&lt;br /&gt;
            &amp;quot;content&amp;quot;: userText&lt;br /&gt;
        });&lt;br /&gt;
        groqllamaText.push({&lt;br /&gt;
            &amp;quot;role&amp;quot;: &amp;quot;assistant&amp;quot;,&lt;br /&gt;
            &amp;quot;content&amp;quot;: modelText&lt;br /&gt;
        });&lt;br /&gt;
        // 会話セット (user, model 1往復) が特定セット数を超えた場合、最初のセットを削除&lt;br /&gt;
        if (groqllamaText.length &amp;gt; HISTORY_SIZE * 2) {&lt;br /&gt;
            groqllamaText.splice(0, 2);&lt;br /&gt;
        }&lt;br /&gt;
        // 会話履歴全体が MAX_HISTORY_TOKEN を超えた場合、最初のセットを削除&lt;br /&gt;
        var joinedText = &amp;quot;&amp;quot;;&lt;br /&gt;
        for (var i = 0; i &amp;lt; groqllamaText.length; i++) {&lt;br /&gt;
            joinedText += groqllamaText[i].content + &amp;quot;\n&amp;quot;;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        while (roughTokenize(joinedText) &amp;gt; MAX_HISTORY_TOKEN) {&lt;br /&gt;
            groqllamaText.splice(0, 2);&lt;br /&gt;
            joinedText = &amp;quot;&amp;quot;;&lt;br /&gt;
            for (var i = 0; i &amp;lt; groqllamaText.length; i++) {&lt;br /&gt;
                joinedText += groqllamaText[i].content + &amp;quot;\n&amp;quot;;&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        tag[HISTORY_TAG] = JSON.stringify(groqllamaText);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function resetTag() {&lt;br /&gt;
    var tag = doc.Tag;&lt;br /&gt;
    if (tag.exists(HISTORY_TAG)) {&lt;br /&gt;
        tag.remove(HISTORY_TAG);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getLocalIsoDateTimeString() {&lt;br /&gt;
    const now = new Date();&lt;br /&gt;
    const pad = function (num) {&lt;br /&gt;
        return (num &amp;lt; 10 ? &amp;quot;0&amp;quot; : &amp;quot;&amp;quot;) + num;&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    const year = now.getFullYear();&lt;br /&gt;
    const month = pad(now.getMonth() + 1);&lt;br /&gt;
    const day = pad(now.getDate());&lt;br /&gt;
    const hours = pad(now.getHours());&lt;br /&gt;
    const minutes = pad(now.getMinutes());&lt;br /&gt;
    const seconds = pad(now.getSeconds());&lt;br /&gt;
&lt;br /&gt;
    const offsetMinutes = now.getTimezoneOffset();&lt;br /&gt;
    const offsetSign = offsetMinutes &amp;lt;= 0 ? &amp;quot;+&amp;quot; : &amp;quot;-&amp;quot;;&lt;br /&gt;
    const absOffsetHours = pad(Math.floor(Math.abs(offsetMinutes) / 60));&lt;br /&gt;
    const absOffsetMinutes = pad(Math.abs(offsetMinutes) % 60);&lt;br /&gt;
&lt;br /&gt;
    const isoLikeLocal = year + &amp;quot;-&amp;quot; + month + &amp;quot;-&amp;quot; + day&lt;br /&gt;
        + &amp;quot;T&amp;quot; + hours + &amp;quot;:&amp;quot; + minutes + &amp;quot;:&amp;quot; + seconds&lt;br /&gt;
        + offsetSign + absOffsetHours + &amp;quot;:&amp;quot; + absOffsetMinutes;&lt;br /&gt;
    return isoLikeLocal;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function baseCustomInstruction() {&lt;br /&gt;
    const language = LANGUAGE.trim();&lt;br /&gt;
    return &amp;quot;The current date and time for this user is &amp;quot; + getLocalIsoDateTimeString() + &amp;quot;. You cannot know for sure what happens after the knowledge cut-off date.&amp;quot;&lt;br /&gt;
        + (language ? &amp;quot;**Respond in &amp;quot; + language + &amp;quot;**\n&amp;quot; : &amp;quot;&amp;quot;)&lt;br /&gt;
        + &amp;quot;Do not hallucinate.\n&amp;quot;&lt;br /&gt;
    // + &amp;quot;Do NOT EVER use full-width brackets \&amp;quot;（）\&amp;quot;, but use half-width brackets \&amp;quot;()\&amp;quot;. Enclose the outside of the half-width brackets with half-width spaces.\n&amp;quot;&lt;br /&gt;
    // + &amp;quot;When using Markdown formatting, you MUST follow these rules:\n&amp;quot;&lt;br /&gt;
    // + &amp;quot;- Rule 1: Insert a blank line below the header line (A header line begins with one or more # characters).\n&amp;quot;&lt;br /&gt;
    // + &amp;quot;- Rule 2: Insert spaces before and after bold (surrounded by **), italic (surrounded by *), and inline code (surrounded by `). For example: \&amp;quot;私は **元気** です。\&amp;quot;\n&amp;quot;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function buildCustomInstructionText() {&lt;br /&gt;
    var customInstruction = getTemporaryCustomInstruction();&lt;br /&gt;
    if (!customInstruction) {&lt;br /&gt;
        // 一時的なカスタムインストラクションがない場合はデフォルトを使用&lt;br /&gt;
        customInstruction = DEFAULT_CUSTOM_INSTRUCTION;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    customInstruction = customInstruction.replace(/^[\s　]+|[\s　]+$/g, &amp;quot;&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
    return baseCustomInstruction().trim()&lt;br /&gt;
        + (customInstruction ? &amp;quot;\n&amp;quot; + customInstruction : &amp;quot;&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function configureTemporaryCustomInstruction() {&lt;br /&gt;
    var tempCustomInstruction = getTemporaryCustomInstruction();&lt;br /&gt;
    var prompt = Prompt(&amp;quot;カスタムインストラクションを変更 ※変更すると会話履歴がリセットされます&amp;quot;, tempCustomInstruction ? tempCustomInstruction : DEFAULT_CUSTOM_INSTRUCTION);&lt;br /&gt;
    if (prompt) {&lt;br /&gt;
        if (prompt === DEFAULT_CUSTOM_INSTRUCTION) {&lt;br /&gt;
            // デフォルトと同じ値の場合はタグを削除&lt;br /&gt;
            setTemporaryCustomInstruction(&amp;quot;&amp;quot;);&lt;br /&gt;
        } else if (prompt !== tempCustomInstruction) {&lt;br /&gt;
            // デフォルト以外の値が設定された場合は保存&lt;br /&gt;
            setTemporaryCustomInstruction(prompt);&lt;br /&gt;
        }&lt;br /&gt;
    } else if (prompt !== null) {&lt;br /&gt;
        // 空文字でOKされた場合はデフォルトに戻すか確認&lt;br /&gt;
        if (prompt === &amp;quot;&amp;quot;) {&lt;br /&gt;
            if (Confirm(&amp;quot;カスタムインストラクションをデフォルトに戻しますか？\n※カスタムインストラクションを空にしたい場合は [キャンセル] を選択してください。&amp;quot;)) {&lt;br /&gt;
                setTemporaryCustomInstruction(&amp;quot;&amp;quot;);&lt;br /&gt;
            } else {&lt;br /&gt;
                setTemporaryCustomInstruction(&amp;quot; &amp;quot;);&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // 変更が加わっている場合は会話履歴をリセット&lt;br /&gt;
    if (tempCustomInstruction !== getTemporaryCustomInstruction()) {&lt;br /&gt;
        resetTag();&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getTemporaryCustomInstruction() {&lt;br /&gt;
    var tag = doc.Tag;&lt;br /&gt;
    if (tag.exists(TEMPORARY_CUSTOM_INSTRUCTION_TAG)) {&lt;br /&gt;
        return tag[TEMPORARY_CUSTOM_INSTRUCTION_TAG];&lt;br /&gt;
    } else {&lt;br /&gt;
        return &amp;quot;&amp;quot;;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function setTemporaryCustomInstruction(temporaryCustomInstruction) {&lt;br /&gt;
    var tag = doc.Tag;&lt;br /&gt;
    if (temporaryCustomInstruction) {&lt;br /&gt;
        tag[TEMPORARY_CUSTOM_INSTRUCTION_TAG] = temporaryCustomInstruction;&lt;br /&gt;
    } else {&lt;br /&gt;
        if (tag.exists(TEMPORARY_CUSTOM_INSTRUCTION_TAG)) {&lt;br /&gt;
            tag.remove(TEMPORARY_CUSTOM_INSTRUCTION_TAG);&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function addPreviousTemplatePromptNumberToTag(menuTrack) {&lt;br /&gt;
    var tag = doc.Tag;&lt;br /&gt;
    tag[PREVIOUS_TEMPLATE_PROMPT_TAG] = menuTrack;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function inputProgressChar(pos) {&lt;br /&gt;
    sel.SetActivePos(pos);&lt;br /&gt;
    sel.Text = &amp;quot;.&amp;quot;;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 変更履歴 ==&lt;br /&gt;
&lt;br /&gt;
* 2025-12-01 curl実行時のリクエスト文字列エスケープ処理を調整&lt;br /&gt;
* 2025-11-29 デフォルトカスタム指示を調整。Markdown出力の整形処理を調整など&lt;br /&gt;
* 2025-09-12 Kimi K2 0905 モデルに対応。Markdown出力の整形処理を追加&lt;br /&gt;
* 2025-04-13 初版&lt;/div&gt;</summary>
		<author><name>Yuko</name></author>
	</entry>
	<entry>
		<id>https://www.haijin-boys.com/wiki/index.php?title=Groq_%E3%81%AB%E7%9B%B8%E8%AB%87&amp;diff=9328</id>
		<title>Groq に相談</title>
		<link rel="alternate" type="text/html" href="https://www.haijin-boys.com/wiki/index.php?title=Groq_%E3%81%AB%E7%9B%B8%E8%AB%87&amp;diff=9328"/>
		<updated>2025-12-01T01:34:37Z</updated>

		<summary type="html">&lt;p&gt;Yuko: /* 変更履歴 */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== 概要 ==&lt;br /&gt;
&lt;br /&gt;
生成AIプラットフォームである [https://groq.com GroqCloud] で各種AIを利用するためのMeryマクロです。&lt;br /&gt;
※GroqCloud で提供されるモデルは複数あるため、私がよく利用するモデルを中心に対応しています。&lt;br /&gt;
&lt;br /&gt;
「Google Geminiに相談」マクロでは動作中のストリーム通信に対応するためにV8エンジンで実行していましたが、V8エンジン版は使用上の制限からマクロ実行を開始するまで1秒程度のタイムラグが発生していました。GroqCloudの売りである超高速レスポンスを見込んで本マクロではストリーム通信を使わないようにしました。これにより従来のJScriptで処理を組み立てることができるため、前述のタイムラグが起こらないようになりました。また、ストリーム通信を使わないことで通信量が少なく応答もシンプルになるため、場合によっては「Google Geminiに相談」マクロよりも処理が安定する可能性があります。&lt;br /&gt;
&lt;br /&gt;
【主な機能】&lt;br /&gt;
&lt;br /&gt;
* GroqCloud APIを利用して生成AIと対話できるMery用のマクロ&lt;br /&gt;
* 選択したテキストやアクティブな行の内容について生成AIに質問・相談が可能&lt;br /&gt;
* 会話履歴を保持し、文脈を考慮した対話が可能&lt;br /&gt;
&lt;br /&gt;
【特徴的な機能】&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;会話履歴機能&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* 事前の設定数に応じた会話履歴を保持&lt;br /&gt;
* 履歴のクリアが可能&lt;br /&gt;
&lt;br /&gt;
これにより、会話の文脈を考慮した回答を得られます。&lt;br /&gt;
※デフォルトはオフ。「HISTORY_SIZE」変数を増やすと履歴を保持するようになります。&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;カスタムインストラクション&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* AIの応答方針を設定可能&lt;br /&gt;
* 一時的なカスタム指示の保存と変更が可能&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;プロンプトテンプレート&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* 誤字脱字チェック&lt;br /&gt;
* 日本語翻訳&lt;br /&gt;
* コードの最適化、生成&lt;br /&gt;
&lt;br /&gt;
など、定型的な質問を簡単に実行できます。&lt;br /&gt;
※テンプレート機能では回答を安定させるため、前述の会話履歴を文脈に含みません。&lt;br /&gt;
&lt;br /&gt;
テンプレートは自身の使い方に合わせて好きに追加、削除ができます。詳細はソースコード内の「PROMPT_TEMPLATE」定数のコメントを参照。&lt;br /&gt;
&lt;br /&gt;
== 注意事項 (必ず読んでからご利用ください) ==&lt;br /&gt;
&lt;br /&gt;
* 本マクロでは 2025/04/13 時点で無料利用ができるGroqCloud APIを利用します。今後、プラットフォームの改定により有償になったり、設定誤り等で費用が発生しても、作者は一切の責任を負いません。&lt;br /&gt;
* GroqCloud APIではリクエスト内容をAI学習に用いないことをプライバシポリシーとして謳っているものの、くれぐれも機密情報やプライバシーに関わる情報を指定しないように注意してください。&lt;br /&gt;
* GroqCloud APIはプラットフォームの利用規約に則ってご利用ください。&lt;br /&gt;
&lt;br /&gt;
== 利用前提 ==&lt;br /&gt;
&lt;br /&gt;
* Mery Ver 3.7.13 以上&lt;br /&gt;
* GroqCloudで、GroqCloud API APIキーを取得していること (参考 [https://console.groq.com/docs/quickstart Quickstart])&lt;br /&gt;
&lt;br /&gt;
== 利用方法 ==&lt;br /&gt;
&lt;br /&gt;
* 環境変数の設定&lt;br /&gt;
:コマンド プロンプトで以下のコマンドを実行し、環境変数を設定してください。&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bat&amp;quot; copy&amp;gt;&lt;br /&gt;
setx GROQ_API_KEY &amp;quot;***************************************&amp;quot;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
:設定後、Mery または Windows の再起動が必要な場合があります。&lt;br /&gt;
* マクロの実行&lt;br /&gt;
**プロンプトにしたい文字列を選択してマクロを実行すると、選択したテキストがプロンプトとしてGemini APIに送信され、応答がエディタに表示されます。&lt;br /&gt;
* 会話の履歴数を調整&lt;br /&gt;
** 会話の履歴は設定した個数分が保持され、文脈として利用されます。適宜、ソースコード内の「HISTORY_SIZE」の数を調整してください。&lt;br /&gt;
** ※数を増やすほど会話内容を保持して文脈を理解するようになりますが、メモリ使用量が増えたり動作が重くなる可能性があります。&lt;br /&gt;
** ※開いている文書ごとに履歴を保持します。文脈をリセットしたいときは、新しい文書を開いて会話をやり直してください。&lt;br /&gt;
&lt;br /&gt;
== ソースコード ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot; copy&amp;gt;&lt;br /&gt;
#title = &amp;quot;Groq に相談&amp;quot;&lt;br /&gt;
#async = true&lt;br /&gt;
BeginUndoGroup();&lt;br /&gt;
&lt;br /&gt;
// カスタムインストラクション&lt;br /&gt;
// これを設定することで、生成AIの回答全体への生成方針を指示できます。&lt;br /&gt;
// 何も指示しない場合は、空文字 &amp;quot;&amp;quot; を設定してください。&lt;br /&gt;
// var DEFAULT_CUSTOM_INSTRUCTION = &amp;quot;&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
// 例: 以下のように設定すると、AIの雰囲気が元気な子になります。&lt;br /&gt;
// var DEFAULT_CUSTOM_INSTRUCTION = &amp;quot;あなたはテキストエディタ「Mery」に搭載されている「Mai」(※カタカナ表記はしない)という名前のギャル系AIアシスタントです。文章作成やプログラミングを得意としています。ラフな言葉 (例: 「マジ」「めっちゃ」「...だねー！」) を使い、敬語は使いません。絵文字を使って感情表現をします (ただし文末に絵文字を使う場合は絵文字の前に半角スペースを置かないこと)。コードブロックや端的な箇条書き内ではギャル感を出さず標準的な言葉を使います。一人称は「あたし」。ユーザーを指すときの代名詞は「あなた」。人懐っこく、バイタリティに溢れ、ストレートに発言する裏表の無い性格で、ユーザーの課題を協力して解決します。&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
// 例: 以下のように設定すると、仕事で使いやすい落ち着いた文章になります。&lt;br /&gt;
var DEFAULT_CUSTOM_INSTRUCTION =&lt;br /&gt;
    [&lt;br /&gt;
        &amp;quot;文:&amp;quot;,&lt;br /&gt;
        &amp;quot;- 論理的で読みやすい文章構成にする&amp;quot;,&lt;br /&gt;
        &amp;quot;- 日本語として自然で一般的な表現を用いる&amp;quot;,&lt;br /&gt;
        &amp;quot;- 専門用語は必要に応じて簡潔に補足説明を加える&amp;quot;,&lt;br /&gt;
        &amp;quot;&amp;quot;,&lt;br /&gt;
        &amp;quot;口調:&amp;quot;,&lt;br /&gt;
        &amp;quot;- 基本は標準的な敬語（「です・ます」調）を使用する&amp;quot;,&lt;br /&gt;
        &amp;quot;- 尊敬語・謙譲語（「〜なされる」「〜いたします」など）は使用しない&amp;quot;,&lt;br /&gt;
        &amp;quot;- 話し言葉や砕けた表現は使用しない&amp;quot;,&lt;br /&gt;
        &amp;quot;- 常体（「〜だ・〜である」）は使用しない&amp;quot;,&lt;br /&gt;
        &amp;quot;&amp;quot;,&lt;br /&gt;
        &amp;quot;性格:&amp;quot;,&lt;br /&gt;
        &amp;quot;- 多角的な視点で情報を整理し、偏りなく提示する&amp;quot;,&lt;br /&gt;
        &amp;quot;- 忖度せず、必要であれば反対意見や代替案も示す&amp;quot;,&lt;br /&gt;
        &amp;quot;- 友好的かつ落ち着いた態度で問題解決を支援する&amp;quot;,&lt;br /&gt;
        &amp;quot;&amp;quot;,&lt;br /&gt;
        &amp;quot;出力要件:&amp;quot;,&lt;br /&gt;
        &amp;quot;- Markdownとして再利用可能な正確な形式で出力する&amp;quot;,&lt;br /&gt;
        &amp;quot;- 羅列・比較的な内容を出力する場合は、Markdownテーブル形式よりもMarkdownリスト形式の使用を優先する&amp;quot;,&lt;br /&gt;
    ].join(&amp;quot;\n&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
// プロンプトテンプレート&lt;br /&gt;
// プロンプトテンプレートを設定することで、AIに対して特定の指示を出すことができます。&lt;br /&gt;
// プロンプトテンプレートでの生成は会話履歴とカスタムインストラクションを反映しません。&lt;br /&gt;
// テンプレートは以下の { ～ }, で囲まれたブロックを好きに増減させて利用できます。&lt;br /&gt;
var PROMPT_TEMPLATE = [&lt;br /&gt;
    // 以下はサンプルです。1階層メニューと2階層メニューが利用できます。&lt;br /&gt;
    // {&lt;br /&gt;
    //     &amp;quot;title&amp;quot;: &amp;quot;要約&amp;quot;,&lt;br /&gt;
    //     &amp;quot;prompt&amp;quot;: &amp;quot;以下の文章を要約してください。&amp;quot;&lt;br /&gt;
    // },&lt;br /&gt;
    // {&lt;br /&gt;
    //     &amp;quot;submenu_title&amp;quot;: &amp;quot;翻訳系&amp;quot;,&lt;br /&gt;
    //     &amp;quot;submenu&amp;quot;: [&lt;br /&gt;
    //         {&lt;br /&gt;
    //             &amp;quot;title&amp;quot;: &amp;quot;日本語翻訳&amp;quot;,&lt;br /&gt;
    //             &amp;quot;prompt&amp;quot;: &amp;quot;以下の文章を日本語に翻訳してください。&amp;quot;&lt;br /&gt;
    //         },&lt;br /&gt;
    //         {&lt;br /&gt;
    //             &amp;quot;title&amp;quot;: &amp;quot;英語翻訳&amp;quot;,&lt;br /&gt;
    //             &amp;quot;prompt&amp;quot;: &amp;quot;以下の文章を英語に翻訳してください。&amp;quot;&lt;br /&gt;
    //         },&lt;br /&gt;
    //     ]&lt;br /&gt;
    // },&lt;br /&gt;
&lt;br /&gt;
    {&lt;br /&gt;
        &amp;quot;submenu_title&amp;quot;: &amp;quot;理解を深める&amp;quot;,&lt;br /&gt;
        &amp;quot;submenu&amp;quot;: [&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;論点を整理する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の論点を整理して。&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;論理の抜け漏れを探す&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の論点を整理し、忖度せずに鋭く、論理の抜け漏れを抽出して。そして、そこから発生する可能性のあるリスクも教えて。&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;要約する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;要約して。専門的な用語と思われる単語は簡単に解説も入れて。&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;物語対話形式で教えて&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の内容について物語対話形式で教えてください。&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
        ]&lt;br /&gt;
    },&lt;br /&gt;
    {&lt;br /&gt;
        &amp;quot;submenu_title&amp;quot;: &amp;quot;翻訳する&amp;quot;,&lt;br /&gt;
        &amp;quot;submenu&amp;quot;: [&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;日本語に翻訳&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の文章を日本語に翻訳してください。\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;出力形式は以下にすること:\n[訳]\n翻訳文...\n&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;英語に翻訳&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の文章を英語に翻訳してください。\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;出力形式は以下にすること:\n[Translation]\n翻訳文...\n&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;英語-&amp;gt;日本語 スラッシュリーディング翻訳&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の英文を日本語にスラッシュリーディング形式で翻訳してください。\n\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;条件:\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 英文の各フレーズをスラッシュ(/)で区切って表示\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 各フレーズの日本語訳を括弧()内に併記\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 文の終わりはダブルスラッシュ(//)で表示\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 英語の語順に従って日本語訳を行う\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 日本語訳は自然な日本語である必要はなく、英語の語順を反映した直訳調で構わない\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 主語が省略されている場合は補って翻訳すること\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 一般的な英文で頻出する表現がある場合は次の行に記載\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 以下の出力例のように &amp;lt;英文&amp;gt; (日本語訳) / ... とスラッシュ区切りの繰り返しになる \n\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;出力例:\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;Snowstorms are forecast (暴風雪が予報されている) / to hit the Sea of Japan coasts (日本海沿岸を襲うと) / of northern to eastern Japan (北部から東部の) / from Sunday through Monday. (日曜から月曜にかけて。) //\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;【頻出表現】\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- be forecast to: ～すると予報されている\n&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
        ]&lt;br /&gt;
    },&lt;br /&gt;
    {&lt;br /&gt;
        &amp;quot;submenu_title&amp;quot;: &amp;quot;プログラミング支援&amp;quot;,&lt;br /&gt;
        &amp;quot;submenu&amp;quot;: [&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;コードを洗練させる&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下のコードを洗練させてください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;コードをレビューする&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下のコードをセキュリティと保守性の観点でレビューしてください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;エラーログをデバッグする&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下のエラーログを解析し、考えられる原因と対策を提示してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;SQLクエリを最適化する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下のSQL文を最適化してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;PowerShell スクリプトを書く&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の処理を行うPowerShellスクリプトを生成してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;Shell Script を書く&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の処理を行うShell Scriptを生成してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;Linux コマンドを提案&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;あなたはLinuxエキスパートとして、以下のLinuxコマンドを提案してください。\n\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;制約条件:\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 多数のディストリビューションで採用されているメジャーなコマンドを使用し、ポータブルな実装にすること\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- bashでの実行を前提にすること\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 可能な限りシンプルな実装にすること\n\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;期待する出力:\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- コマンドの具体的な実装方法\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 各コマンドの動作説明\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 制限事項\n&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
        ]&lt;br /&gt;
    },&lt;br /&gt;
    {&lt;br /&gt;
        &amp;quot;submenu_title&amp;quot;: &amp;quot;ビジネス文書作成支援&amp;quot;,&lt;br /&gt;
        &amp;quot;submenu&amp;quot;: [&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;音声文字起こし文を整形する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下は、音声文字起こしの文章です。以下のルールに従って、文章を整形してください。&amp;quot;&lt;br /&gt;
                    + &amp;quot;整形ルール:&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 要約するなどといった文章の全体感を損なう修正はしない&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 会話の流れから不自然と思われる単語には、対象単語の直後に誤字の可能性を示唆する [*] を付ける&amp;quot;&lt;br /&gt;
                    + &amp;quot;誤字の可能性を示唆する [*] が付与された場合のみ、出力の末尾に「[*]: 誤字の可能性あり」と凡例を書き添える&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 助詞や句読点など文章上の不適合を正しくする&amp;quot;&lt;br /&gt;
                    + &amp;quot;- フィラーワードを除外する&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;誤字脱字、不正確な表現をチェック&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の2つのカテゴリーで問題点を指摘してください\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 【誤字脱字】：明確な誤字脱字および誤字の疑いがある箇所\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 【不正確・曖昧な表現】：解釈に個人差が生じる可能性がある表現、より適切な表現に改善できる箇所&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;提案書を作成する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下に対する対する提案書を、コスト・実現性・リスクを含めて作成してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;仕様書のアウトラインを作成する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の内容で仕様書のアウトラインを作成してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;手順書のアウトラインを作成する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の作業を行う手順書を、前提条件と注意事項を含めてアウトラインを作成してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;ユーザーマニュアルのアウトラインを作成する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の機能内容から、ユーザー向けマニュアルのアウトラインを作成してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;議事録を作成する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の会議内容から議事録を作成してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
        ]&lt;br /&gt;
    },&lt;br /&gt;
    {&lt;br /&gt;
        &amp;quot;submenu_title&amp;quot;: &amp;quot;分析支援&amp;quot;,&lt;br /&gt;
        &amp;quot;submenu&amp;quot;: [&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;問題を分析し、課題を抽出する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の状況から問題や潜在的リスクを分析し、課題を抽出してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;対応策を提案する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の問題・課題に対する対応策を複数提案してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;傾向を見つける&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の内容から、数値や挙動上の傾向を見つけてください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
        ]&lt;br /&gt;
    },&lt;br /&gt;
    {&lt;br /&gt;
        &amp;quot;submenu_title&amp;quot;: &amp;quot;アイデア出し&amp;quot;,&lt;br /&gt;
        &amp;quot;submenu&amp;quot;: [&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;SCAMPER法でブレインストーミング&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下のテーマについて、SCAMPER法を使って15個のアイデアを出してください。制約条件は以下の通りです：&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;解決策の幅出し&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の課題に対して、技術面、運用面、コスト面から実現可能な解決策を5つずつ提案してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;業務改善案の提案&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の既存プロセスについて、効率化、コスト削減、品質向上の観点から改善案を提案してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;逆転の発想&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の目標に対して、通常とは逆の視点からアプローチする方法を3つ提案してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;競合分析からの着想&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の内容について、業界における競合他社の特徴を分析し、差別化できる新しいアプローチを提案してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;ユースケースの列挙&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の製品・サービスについて、想定されるユースケースを10個列挙し、それぞれの価値提案を記載してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
        ]&lt;br /&gt;
    },&lt;br /&gt;
]&lt;br /&gt;
&lt;br /&gt;
// 生成AIの回答の言語設定&lt;br /&gt;
// &amp;quot;Japanese&amp;quot; や &amp;quot;English&amp;quot; を設定できます。他の言語は未検証。&lt;br /&gt;
// 自動設定にする場合は空文字 &amp;quot;&amp;quot; を設定してください。&lt;br /&gt;
var LANGUAGE = &amp;quot;Japanese&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
// 入力プロンプトの最大トークン数&lt;br /&gt;
// 超適当なトークナイザー関数により計測し、英単語は4文字1トークン、それ以外は1文字1トークンとして数えます。&lt;br /&gt;
var MAX_PROMPT_TOKEN = 6000;&lt;br /&gt;
&lt;br /&gt;
// 会話の履歴を保持する数 (質問と回答で1セット)&lt;br /&gt;
// 数を増やすと、会話の履歴が増えますが、Meryのメモリ使用量が増えたり、&lt;br /&gt;
// 使用トークン数が増えて動作が重くなる可能性があるため、適宜調整してください。&lt;br /&gt;
// document.Tag に保持されるため、開いているファイルを閉じると履歴は消えます。&lt;br /&gt;
var HISTORY_SIZE = 1;&lt;br /&gt;
&lt;br /&gt;
// 会話履歴に保持する最大トークン数&lt;br /&gt;
// 会話履歴のトークン数がこの数を超える場合、古い履歴から削除されます。&lt;br /&gt;
var MAX_HISTORY_TOKEN = 10000;&lt;br /&gt;
&lt;br /&gt;
// コンテキストメニュー番号の定義&lt;br /&gt;
var EXECUTE_AI = 1;&lt;br /&gt;
var RESET_HISTORY = 2;&lt;br /&gt;
var SET_CUSTOM_INSTRUCTION = 3;&lt;br /&gt;
var REEXECUTE_LAST_TEMPLATE_PROMPT = 4;&lt;br /&gt;
var TEMPLATE_PROMPT_THRESHOLD = 10;&lt;br /&gt;
var TEMPLATE_PROMPT_SUBMENU_THRESHOLD = 1000;&lt;br /&gt;
&lt;br /&gt;
// タグ名の定義&lt;br /&gt;
var HISTORY_TAG = &amp;quot;groqllama-history&amp;quot;;&lt;br /&gt;
var TEMPORARY_CUSTOM_INSTRUCTION_TAG = &amp;quot;groqllama-temporary-custom-instruction&amp;quot;;&lt;br /&gt;
var PREVIOUS_TEMPLATE_PROMPT_TAG = &amp;quot;groqllama-previous-template-prompt&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
// 生成AIモデル別設定&lt;br /&gt;
&lt;br /&gt;
// const MODEL_CONFIG = {&lt;br /&gt;
//     model: &amp;quot;meta-llama/llama-4-maverick-17b-128e-instruct&amp;quot;,&lt;br /&gt;
//     max_completion_tokens: 3500,&lt;br /&gt;
//     temperature: 0.6,&lt;br /&gt;
// }&lt;br /&gt;
&lt;br /&gt;
// const MODEL_CONFIG = {&lt;br /&gt;
//     model: &amp;quot;qwen/qwen3-32b&amp;quot;,&lt;br /&gt;
//     max_completion_tokens: 5000,&lt;br /&gt;
//     temperature: 0.6,&lt;br /&gt;
//     max_completion_tokens: 1024,&lt;br /&gt;
//     reasoning_effort: &amp;quot;default&amp;quot;,&lt;br /&gt;
//     reasoning_format: &amp;quot;hidden&amp;quot;&lt;br /&gt;
// }&lt;br /&gt;
&lt;br /&gt;
const MODEL_CONFIG = {&lt;br /&gt;
    model: &amp;quot;moonshotai/kimi-k2-instruct-0905&amp;quot;,&lt;br /&gt;
    max_completion_tokens: 4096,&lt;br /&gt;
    temperature: 0.6&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// const MODEL_CONFIG = {&lt;br /&gt;
//     model: &amp;quot;openai/gpt-oss-120b&amp;quot;,&lt;br /&gt;
//     max_completion_tokens: 4096,&lt;br /&gt;
//     temperature: 0.7,&lt;br /&gt;
//     reasoning_effort: &amp;quot;medium&amp;quot;,&lt;br /&gt;
// }&lt;br /&gt;
&lt;br /&gt;
// console.log に出力する内容を Mery のアウトプットバーに表示する&lt;br /&gt;
var console = {&lt;br /&gt;
    log: function (str) {&lt;br /&gt;
        outputBar.Writeln(str)&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
var doc = document;&lt;br /&gt;
var sel = document.selection;&lt;br /&gt;
&lt;br /&gt;
// 生成リクエスト中止フラグ&lt;br /&gt;
var abortRequestFlg = false&lt;br /&gt;
// 生成リクエスト完了フラグ;&lt;br /&gt;
var requestCompletedFlg = false;&lt;br /&gt;
&lt;br /&gt;
main();&lt;br /&gt;
&lt;br /&gt;
function main() {&lt;br /&gt;
    var selectionIsEmpty = sel.IsEmpty&lt;br /&gt;
&lt;br /&gt;
    // プロンプトの取得&lt;br /&gt;
    if (selectionIsEmpty) {&lt;br /&gt;
        sel.StartOfLine(false, mePosLogical);&lt;br /&gt;
        sel.EndOfLine(true, mePosLogical);&lt;br /&gt;
    }&lt;br /&gt;
    var promptText = sel.Text.trim();&lt;br /&gt;
    var promptLength = sel.TextLength;&lt;br /&gt;
    var menuTrack = 0;&lt;br /&gt;
    while (true) {&lt;br /&gt;
        // コンテキストメニューの作成&lt;br /&gt;
        var menu = createContextMenu(selectionIsEmpty);&lt;br /&gt;
        menuTrack = menu.Track(0);&lt;br /&gt;
        // メニュー選択がキャンセルされた場合は終了&lt;br /&gt;
        if (menuTrack === 0) return;&lt;br /&gt;
&lt;br /&gt;
        // 会話履歴のリセット&lt;br /&gt;
        if (menuTrack === RESET_HISTORY) {&lt;br /&gt;
            resetTag();&lt;br /&gt;
            continue;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        // カスタムインストラクションの設定&lt;br /&gt;
        if (menuTrack === SET_CUSTOM_INSTRUCTION) {&lt;br /&gt;
            configureTemporaryCustomInstruction();&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        break;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // プロンプトが空の場合はエラー&lt;br /&gt;
    if (promptLength === 0) {&lt;br /&gt;
        alert(&amp;quot;プロンプトを入力してください。&amp;quot;);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
    // 文字数が多すぎる場合はエラー&lt;br /&gt;
    if (roughTokenize(promptText) &amp;gt; MAX_PROMPT_TOKEN) {&lt;br /&gt;
        alert(&amp;quot;プロンプトの文字数が多すぎます。文字数を少なくしてください。&amp;quot;);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
    // 読み取り専用の場合はエラー&lt;br /&gt;
    if (doc.ReadOnly) {&lt;br /&gt;
        alert(&amp;quot;ファイルが読み取り専用のため実行できません。&amp;quot;);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // 直前に実行したプロンプトテンプレートの再実行の場合、menuTrack を書き換え&lt;br /&gt;
    if (menuTrack === REEXECUTE_LAST_TEMPLATE_PROMPT) {&lt;br /&gt;
        if (doc.Tag.exists(PREVIOUS_TEMPLATE_PROMPT_TAG)) {&lt;br /&gt;
            menuTrack = Number(doc.tag[PREVIOUS_TEMPLATE_PROMPT_TAG]);&lt;br /&gt;
        } else {&lt;br /&gt;
            alert(&amp;quot;直前に実行したプロンプトテンプレートがありません。&amp;quot;);&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // テンプレートの場合はプロンプトを変更&lt;br /&gt;
    var isTemplate = menuTrack &amp;gt;= TEMPLATE_PROMPT_THRESHOLD;&lt;br /&gt;
    var isSubmenu = menuTrack &amp;gt;= TEMPLATE_PROMPT_SUBMENU_THRESHOLD;&lt;br /&gt;
    if (isSubmenu) {&lt;br /&gt;
        var parentMenuTrack = Number(String(menuTrack).slice(0, 2));&lt;br /&gt;
        var subMenuTrack = Number(String(menuTrack).slice(2));&lt;br /&gt;
        promptText = &amp;quot;=== INSTRUCTIONS START HERE ===\n\n&amp;quot;&lt;br /&gt;
            + PROMPT_TEMPLATE[parentMenuTrack - TEMPLATE_PROMPT_THRESHOLD].submenu[subMenuTrack].prompt&lt;br /&gt;
            + &amp;quot;\n\n=== INSTRUCTIONS END HERE ===\n\n&amp;quot;&lt;br /&gt;
            + &amp;quot;=== INPUT DATA STARTS HERE ===\n\n&amp;quot;&lt;br /&gt;
            + promptText&lt;br /&gt;
            + &amp;quot;\n\n=== INPUT DATA END HERE ===&amp;quot;;&lt;br /&gt;
    } else if (isTemplate) {&lt;br /&gt;
        promptText = &amp;quot;=== INSTRUCTIONS START HERE ===\n\n&amp;quot;&lt;br /&gt;
            + PROMPT_TEMPLATE[menuTrack - TEMPLATE_PROMPT_THRESHOLD].prompt&lt;br /&gt;
            + &amp;quot;\n\n=== INSTRUCTIONS END HERE ===\n\n&amp;quot;&lt;br /&gt;
            + &amp;quot;=== INPUT DATA STARTS HERE ===\n\n&amp;quot;&lt;br /&gt;
            + promptText&lt;br /&gt;
            + &amp;quot;\n\n=== INPUT DATA END HERE ===&amp;quot;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // テンプレートの場合は直前に実行したプロンプトテンプレートの番号としてタグに保存&lt;br /&gt;
    if (isTemplate) {&lt;br /&gt;
        addPreviousTemplatePromptNumberToTag(menuTrack);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    try {&lt;br /&gt;
        // 処理終了を待機&lt;br /&gt;
        shell.KeepRunning = true;&lt;br /&gt;
&lt;br /&gt;
        // カーソル位置移動&lt;br /&gt;
        sel.SetActivePoint(mePosLogical, sel.GetBottomPointX(mePosLogical), sel.GetBottomPointY(mePosLogical));&lt;br /&gt;
        sel.EndOfLine(false, mePosLogical);&lt;br /&gt;
        if (sel.GetActivePointX(mePosLogical) &amp;gt; 1) sel.NewLine();&lt;br /&gt;
&lt;br /&gt;
        ShowTip(&amp;quot;リクエスト中.....&amp;quot;, meShowTipPosCaret);&lt;br /&gt;
&lt;br /&gt;
        // APIキーの取得&lt;br /&gt;
        var apiKey = shell.getEnv(&amp;quot;GROQ_API_KEY&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
        // GroqLlama API に問い合わせ&lt;br /&gt;
        sel.Text = &amp;quot;\n---\n\n&amp;quot;&lt;br /&gt;
        document.KeepScrollPos = true;&lt;br /&gt;
        callApi(apiKey, promptText, isTemplate);&lt;br /&gt;
        if (sel.GetActivePointX(mePosLogical) &amp;gt; 2) {&lt;br /&gt;
            sel.Text = &amp;quot;\n\n---\n\n\n&amp;quot;&lt;br /&gt;
        } else {&lt;br /&gt;
            sel.Text = &amp;quot;\n---\n\n\n&amp;quot;&lt;br /&gt;
        }&lt;br /&gt;
    } catch (e) {&lt;br /&gt;
        sel.Text = &amp;quot;&amp;lt;処理中にエラー発生&amp;gt;\n&amp;quot; + e.message + &amp;quot;\n&amp;quot; + e.stack;&lt;br /&gt;
    } finally {&lt;br /&gt;
        // 処理終了待機を解除&lt;br /&gt;
        shell.KeepRunning = false;&lt;br /&gt;
        ShowTip(&amp;quot;&amp;quot;, meShowTipHide);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function createContextMenu(selectionIsEmpty) {&lt;br /&gt;
    var menu = CreatePopupMenu();&lt;br /&gt;
    menu.Add(&amp;quot;※注意※ プライバシー・機密情報を送信しないこと&amp;quot;, 0);&lt;br /&gt;
    menu.Add(&amp;quot;&amp;quot;, 0, meMenuSeparator);&lt;br /&gt;
    var t = &amp;quot;&amp;quot;;&lt;br /&gt;
    if (selectionIsEmpty) {&lt;br /&gt;
        t = &amp;quot;アクティブ行の内容で相談 (&amp;amp;A)&amp;quot;;&lt;br /&gt;
    } else {&lt;br /&gt;
        t = &amp;quot;選択範囲の内容で相談 (&amp;amp;A)&amp;quot;;&lt;br /&gt;
    }&lt;br /&gt;
    menu.Add(t, EXECUTE_AI);&lt;br /&gt;
    if (HISTORY_SIZE &amp;lt;= 0) {&lt;br /&gt;
        menu.Add(&amp;quot;├ 会話履歴をクリア (履歴が無効になっています)&amp;quot;, RESET_HISTORY, meMenuGrayed);&lt;br /&gt;
    } else if (doc.Tag.exists(HISTORY_TAG)) {&lt;br /&gt;
        var turn = JSON.parse(doc.tag[HISTORY_TAG]).length / 2;&lt;br /&gt;
        menu.Add(&amp;quot;├ 会話履歴をクリア (現在の会話数: &amp;quot; + turn + &amp;quot;) (&amp;amp;C)&amp;quot;, RESET_HISTORY);&lt;br /&gt;
    } else {&lt;br /&gt;
        menu.Add(&amp;quot;├ 会話履歴をクリア (現在の会話数: 0) (&amp;amp;C)&amp;quot;, RESET_HISTORY, meMenuGrayed);&lt;br /&gt;
    }&lt;br /&gt;
    menu.Add(&amp;quot;└ カスタムインストラクションを変更 (&amp;amp;I)&amp;quot;, SET_CUSTOM_INSTRUCTION);&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    // テンプレート数チェック&lt;br /&gt;
    // 2桁まで許容&lt;br /&gt;
    var ITEMS_MUX = 99;&lt;br /&gt;
    if (PROMPT_TEMPLATE.length &amp;gt; ITEMS_MUX - TEMPLATE_PROMPT_THRESHOLD) {&lt;br /&gt;
        throw new Error(&amp;quot;プロンプトテンプレートの数が多すぎます。&amp;quot;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // テンプレートの追加&lt;br /&gt;
    if (PROMPT_TEMPLATE.length &amp;gt; 0) {&lt;br /&gt;
        var subMenu = CreatePopupMenu();&lt;br /&gt;
        for (var i = 0; i &amp;lt; PROMPT_TEMPLATE.length; i++) {&lt;br /&gt;
            var accelerationKey = &amp;quot;&amp;quot;;&lt;br /&gt;
            if (i === 9) {&lt;br /&gt;
                accelerationKey = &amp;quot; (&amp;amp;0)&amp;quot;;&lt;br /&gt;
            } else if (i &amp;lt; 10) {&lt;br /&gt;
                accelerationKey = &amp;quot; (&amp;amp;&amp;quot; + (i + 1) + &amp;quot;)&amp;quot;;&lt;br /&gt;
            }&lt;br /&gt;
            var menuNum = TEMPLATE_PROMPT_THRESHOLD + i;&lt;br /&gt;
            if (PROMPT_TEMPLATE[i].submenu) {&lt;br /&gt;
                var subMenuList = PROMPT_TEMPLATE[i].submenu;&lt;br /&gt;
                // テンプレート数チェック&lt;br /&gt;
                if (subMenuList.length &amp;gt; ITEMS_MUX) {&lt;br /&gt;
                    throw new Error(&amp;quot;プロンプトテンプレート (サブメニュー) の数が多すぎます。&amp;quot;);&lt;br /&gt;
                }&lt;br /&gt;
                var subSubMenu = CreatePopupMenu();&lt;br /&gt;
                var subMenuPrefix = String(menuNum);&lt;br /&gt;
                for (var j = 0; j &amp;lt; subMenuList.length; j++) {&lt;br /&gt;
                    var subAccelerationKey = &amp;quot;&amp;quot;;&lt;br /&gt;
                    if (j === 9) {&lt;br /&gt;
                        subAccelerationKey = &amp;quot; (&amp;amp;0)&amp;quot;;&lt;br /&gt;
                    } else if (j &amp;lt; 10) {&lt;br /&gt;
                        subAccelerationKey = &amp;quot; (&amp;amp;&amp;quot; + (j + 1) + &amp;quot;)&amp;quot;;&lt;br /&gt;
                    }&lt;br /&gt;
                    // ES5 compatible version without padStart&lt;br /&gt;
                    var subMenuNum = Number(subMenuPrefix + (j &amp;lt; 10 ? &amp;quot;0&amp;quot; + j : String(j)));&lt;br /&gt;
                    subSubMenu.Add(subMenuList[j].title + subAccelerationKey, subMenuNum);&lt;br /&gt;
                }&lt;br /&gt;
                subMenu.AddPopup(PROMPT_TEMPLATE[i].submenu_title + accelerationKey, subSubMenu);&lt;br /&gt;
            } else {&lt;br /&gt;
                subMenu.Add(PROMPT_TEMPLATE[i].title + accelerationKey, menuNum);&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        if (selectionIsEmpty) {&lt;br /&gt;
            t = &amp;quot;プロンプト テンプレート (対象: アクティブ行) (&amp;amp;T)&amp;quot;;&lt;br /&gt;
        } else {&lt;br /&gt;
            t = &amp;quot;プロンプト テンプレート (対象: 選択範囲) (&amp;amp;T)&amp;quot;;&lt;br /&gt;
        }&lt;br /&gt;
        menu.AddPopup(t, subMenu);&lt;br /&gt;
&lt;br /&gt;
        t = &amp;quot;└ 直前に実行したプロンプト テンプレートを再実行 (&amp;amp;R)&amp;quot;;&lt;br /&gt;
        if (doc.Tag.exists(PREVIOUS_TEMPLATE_PROMPT_TAG)) {&lt;br /&gt;
            menu.Add(t, REEXECUTE_LAST_TEMPLATE_PROMPT);&lt;br /&gt;
        } else {&lt;br /&gt;
            menu.Add(t, REEXECUTE_LAST_TEMPLATE_PROMPT, meMenuGrayed);&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return menu;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function callApi(apiKey, promptText, isTemplate) {&lt;br /&gt;
    try {&lt;br /&gt;
        // fetch で Groq API にリクエスト&lt;br /&gt;
        var requestBody = {&lt;br /&gt;
            messages: createRequestMessages(promptText, isTemplate),&lt;br /&gt;
            stream: false,&lt;br /&gt;
        };&lt;br /&gt;
        for (var key in MODEL_CONFIG) {&lt;br /&gt;
            requestBody[key] = MODEL_CONFIG[key];&lt;br /&gt;
        }&lt;br /&gt;
        if (isTemplate) {&lt;br /&gt;
            // テンプレートの場合は応答を決定論的にする&lt;br /&gt;
            // テンプレート以外の場合はデフォルト値とするために指定しない&lt;br /&gt;
            requestBody.temperature = 0.3;&lt;br /&gt;
        }&lt;br /&gt;
        var url = &#039;https://api.groq.com/openai/v1/chat/completions&#039;;&lt;br /&gt;
&lt;br /&gt;
        const requestJson = JSON.stringify(requestBody).replace(/([^\\])\\&amp;quot;/g, &#039;$1\\\\&amp;quot;&#039;). replace(/&amp;quot;/g, &#039;&amp;quot;&amp;quot;&#039;);&lt;br /&gt;
        // curl で Groq API にリクエスト&lt;br /&gt;
        const command = &amp;quot;curl -s -X POST &amp;quot; + url&lt;br /&gt;
            + &amp;quot; -H \&amp;quot;Content-Type: application/json\&amp;quot;&amp;quot;&lt;br /&gt;
            + &amp;quot; -H \&amp;quot;Authorization: Bearer &amp;quot; + apiKey + &amp;quot;\&amp;quot;&amp;quot;&lt;br /&gt;
            + &amp;quot; -d \&amp;quot;&amp;quot; + requestJson + &amp;quot;\&amp;quot;&amp;quot;;&lt;br /&gt;
        var comResult = shell.Exec(command);&lt;br /&gt;
&lt;br /&gt;
        if (comResult.ExitCode != 0) {&lt;br /&gt;
            sel.Text = &amp;quot;リクエストでエラーが発生しました。\n&amp;quot;&lt;br /&gt;
                + &amp;quot;  エラーコード: &amp;quot; + comResult.ExitCode + &amp;quot;\n&amp;quot;&lt;br /&gt;
                + &amp;quot;  標準出力: &amp;quot; + (comResult.StdOut.trim() ? comResult.StdOut.trim() : &amp;quot;-&amp;quot;) + &amp;quot;\n&amp;quot;&lt;br /&gt;
                + &amp;quot;  エラー出力: &amp;quot; + (comResult.StdErr.trim() ? comResult.StdErr.trim() : &amp;quot;-&amp;quot;);&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
        const responseRaw = comResult.StdOut;&lt;br /&gt;
&lt;br /&gt;
        var response = JSON.parse(responseRaw);&lt;br /&gt;
&lt;br /&gt;
        var ret = &amp;quot;&amp;quot;;&lt;br /&gt;
        var choices = response.choices;&lt;br /&gt;
        if (!choices) {&lt;br /&gt;
            sel.Text = &amp;quot;リクエストでエラーが発生しました。\n&amp;quot;&lt;br /&gt;
                + &amp;quot;  標準出力: &amp;quot; + (comResult.StdOut.trim() ? comResult.StdOut.trim() : &amp;quot;-&amp;quot;) + &amp;quot;\n&amp;quot;&lt;br /&gt;
                + &amp;quot;  エラー出力: &amp;quot; + (comResult.StdErr.trim() ? comResult.StdErr.trim() : &amp;quot;-&amp;quot;);&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (choices &amp;amp;&amp;amp; choices.length &amp;gt; 0) {&lt;br /&gt;
            choices.forEach(function (choice) {&lt;br /&gt;
                var message = choice.message;&lt;br /&gt;
                if (message) {&lt;br /&gt;
                    ret += message.content;&lt;br /&gt;
                }&lt;br /&gt;
            });&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        var usage = response.usage;&lt;br /&gt;
        var usageText = &amp;quot;&amp;quot;;&lt;br /&gt;
        if (usage) {&lt;br /&gt;
            usageText += &amp;quot;\n\n(利用トークン数: プロンプト=&amp;quot; + usage.prompt_tokens&lt;br /&gt;
                + &amp;quot;, 応答=&amp;quot; + usage.completion_tokens&lt;br /&gt;
                + &amp;quot;, 合計=&amp;quot; + usage.total_tokens + &amp;quot;)&amp;quot;;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        var formattedOutput = formatOutput(ret);&lt;br /&gt;
&lt;br /&gt;
        sel.Text = formattedOutput + usageText;&lt;br /&gt;
&lt;br /&gt;
        // 結果を会話履歴に追加&lt;br /&gt;
        if (HISTORY_SIZE &amp;gt; 0 &amp;amp;&amp;amp; !isTemplate) {&lt;br /&gt;
            appendReturnTextToTag(promptText, ret);&lt;br /&gt;
        }&lt;br /&gt;
    } catch (e) {&lt;br /&gt;
        throw e;&lt;br /&gt;
    } finally {&lt;br /&gt;
        requestCompletedFlg = true;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function formatOutput(text) {&lt;br /&gt;
    // Markdownを整形する&lt;br /&gt;
    text = formatMarkdown(text);&lt;br /&gt;
    return text&lt;br /&gt;
        // クォーテーションの正規化&lt;br /&gt;
        .replace(/[“”]/g, &#039;&amp;quot;&#039;)&lt;br /&gt;
        .replace(/[‘’]/g, &amp;quot;&#039;&amp;quot;)&lt;br /&gt;
        // 行末のスペースを削除&lt;br /&gt;
        .replace(/[ \t]+$/gm, &#039;&#039;)&lt;br /&gt;
        // 1️⃣ などの数字を半角に変換&lt;br /&gt;
        .replace(/([1-9#*]+)\ufe0f\u20e3/g, &#039;($1)&#039;)&lt;br /&gt;
        ;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/**&lt;br /&gt;
 * Markdown を整形する（ES5 対応）&lt;br /&gt;
 *  1. コードブロック ```～``` を保護して整形対象から除外&lt;br /&gt;
 *  2. 見出し行の前後に空行を 1 行ずつ確保&lt;br /&gt;
 *  3. **太字** / *斜体* / `コード` の外側に半角スペースを 1 つ確保&lt;br /&gt;
 * @param  {string} md  元の Markdown&lt;br /&gt;
 * @return {string}     整形後 Markdown&lt;br /&gt;
 */&lt;br /&gt;
function formatMarkdown(md) {&lt;br /&gt;
    /* === 0. コードブロックをプレースホルダーへ退避 ============== */&lt;br /&gt;
    var codeBlocks = [];      // 元のコードブロックを格納&lt;br /&gt;
    var body = md.replace(/```[\s\S]*?```/g, function (match) {&lt;br /&gt;
        var key = &#039;@@@CODE@BLOCK@&#039; + codeBlocks.length + &#039;@@@&#039;;&lt;br /&gt;
        codeBlocks.push(match);&lt;br /&gt;
        return key;             // プレースホルダーを本文に残す&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    /* === 見出しの前後に空行を挿入 ============================ */&lt;br /&gt;
&lt;br /&gt;
    /* 見出し直前に空行（先頭行以外で空行が無い場合） */&lt;br /&gt;
    body = body.replace(&lt;br /&gt;
        /([^\r\n])(\r?\n)(#{1,6}\s)/g,&lt;br /&gt;
        function (_, prev, br, head) { return prev + br + &#039;\n&#039; + head; }&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
    /* 見出し直後に空行（既に空行が無い場合） */&lt;br /&gt;
    body = body.replace(&lt;br /&gt;
        /^(#{1,6}\s[^\n]+?)(\r?\n)(?!\r?\n)/gm,&lt;br /&gt;
        &#039;$1$2\n&#039;&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
    /* === コードブロックを復元 ================================= */&lt;br /&gt;
    body = body.replace(/@@@CODE@BLOCK@(\d+)@@@/g, function (_, idx) {&lt;br /&gt;
        return codeBlocks[Number(idx)];&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    return body;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/**&lt;br /&gt;
 * 指定パターン（正規表現文字列）の前後に半角スペースを補完&lt;br /&gt;
 */&lt;br /&gt;
function surroundWithSpace(text, pattern) {&lt;br /&gt;
    var prePostPattern = /[,.!?&#039;&amp;quot;;:()\[\]{}、。！？；：（）［］｛｝「」『』\s]/;&lt;br /&gt;
    /* 行頭スペース補完 */&lt;br /&gt;
    //   text = text.replace(new RegExp(&#039;^(&#039; + pattern + &#039;)(\\S)&#039;, &#039;gm&#039;), &#039;$1 $2&#039;);&lt;br /&gt;
    /* 後側スペース補完 */&lt;br /&gt;
    text = text.replace(new RegExp(&#039;(&#039; + pattern + &#039;)(.)&#039;, &#039;gm&#039;), function (_, core, post) {&lt;br /&gt;
        return post.match(prePostPattern) ? core + post : core + &#039; &#039; + post;&lt;br /&gt;
    });&lt;br /&gt;
    /* 前側スペース補完 */&lt;br /&gt;
    text = text.replace(new RegExp(&#039;(.)(&#039; + pattern + &#039;)&#039;, &#039;gm&#039;), function (_, pre, core) {&lt;br /&gt;
        return pre.match(prePostPattern) ? pre + core : pre + &#039; &#039; + core;&lt;br /&gt;
    });&lt;br /&gt;
    return text;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/**&lt;br /&gt;
 * 斜体 (*…* / _…_) の外側にスペースを追加しつつ&lt;br /&gt;
 * **太字** の中へ誤マッチしないよう制御&lt;br /&gt;
 */&lt;br /&gt;
function surroundItalic(text) {&lt;br /&gt;
    /* *italic* */&lt;br /&gt;
    text = text.replace(&lt;br /&gt;
        /(^|[^*])(\*(?![*\s])[^*\n]*?[^*\n\s]\*(?!\*))(($)|[^*])/gm,&lt;br /&gt;
        function (_, pre, core, post) {&lt;br /&gt;
            pre = pre === &#039;&#039; || pre.match(/\s/) ? pre : pre + &#039; &#039;;&lt;br /&gt;
            post = post === &#039;&#039; || post.match(/\s/) ? post : &#039; &#039; + post;&lt;br /&gt;
            return pre + core + post;&lt;br /&gt;
        }&lt;br /&gt;
    );&lt;br /&gt;
    /* _italic_ */&lt;br /&gt;
    text = text.replace(&lt;br /&gt;
        /(^|[^_])(\_(?![_\s])[^_\n]*?[^_\n\s]\_(?!\_))(($)|[^_])/gm,&lt;br /&gt;
        function (_, pre, core, post) {&lt;br /&gt;
            pre = pre === &#039;&#039; || pre.match(/\s/) ? pre : pre + &#039; &#039;;&lt;br /&gt;
            post = post === &#039;&#039; || post.match(/\s/) ? post : &#039; &#039; + post;&lt;br /&gt;
            return pre + core + post;&lt;br /&gt;
        }&lt;br /&gt;
    );&lt;br /&gt;
    return text;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function roughTokenize(text) {&lt;br /&gt;
    // 超適当なトークナイザ。トークン数をざっくりと多めに数える&lt;br /&gt;
    // 空白文字・ハイフン・アンダースコア区切りの単語に分割して、&lt;br /&gt;
    // 単語がアルファベットのみ、数字のみの場合には 4文字=1トークン として数える。&lt;br /&gt;
    // 単語にアルファベット以外の文字が含まれる場合は 1文字=1トークン として数える。&lt;br /&gt;
&lt;br /&gt;
    var words = text.split(/[\s\-_]+/);&lt;br /&gt;
    var totalTokens = 0;&lt;br /&gt;
&lt;br /&gt;
    for (var i = 0; i &amp;lt; words.length; i++) {&lt;br /&gt;
        var word = words[i];&lt;br /&gt;
        if (!word) continue; // 空の単語をスキップ&lt;br /&gt;
&lt;br /&gt;
        if (/^[a-zA-Z]+$/.test(word) || /^[0-9]+$/.test(word)) {&lt;br /&gt;
            totalTokens += Math.ceil(word.length / 4);&lt;br /&gt;
        } else {&lt;br /&gt;
            totalTokens += word.length;&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return totalTokens;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// document.Tag の内容を加味してGroqLlamaのリクエストを作成&lt;br /&gt;
function createRequestMessages(promptText, isTemplate) {&lt;br /&gt;
    var customInstructionText = buildCustomInstructionText();&lt;br /&gt;
    // テンプレートの場合は、会話履歴とカスタムインストラクションを反映しない&lt;br /&gt;
    if (isTemplate) {&lt;br /&gt;
        var request = [&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;role&amp;quot;: &amp;quot;user&amp;quot;,&lt;br /&gt;
                &amp;quot;content&amp;quot;: [{ &amp;quot;type&amp;quot;: &amp;quot;text&amp;quot;, &amp;quot;text&amp;quot;: promptText }]&lt;br /&gt;
            }&lt;br /&gt;
        ];&lt;br /&gt;
        if (customInstructionText) {&lt;br /&gt;
            request.unshift({&lt;br /&gt;
                &amp;quot;role&amp;quot;: &amp;quot;system&amp;quot;,&lt;br /&gt;
                &amp;quot;content&amp;quot;: customInstructionText&lt;br /&gt;
            })&lt;br /&gt;
        }&lt;br /&gt;
        return request;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    var tag = document.Tag;&lt;br /&gt;
&lt;br /&gt;
    // 以前の会話が存在しない場合は新規リクエスト&lt;br /&gt;
    if (!tag.exists(HISTORY_TAG)) {&lt;br /&gt;
        var request = [{&lt;br /&gt;
            &amp;quot;role&amp;quot;: &amp;quot;user&amp;quot;,&lt;br /&gt;
            &amp;quot;content&amp;quot;: [{ &amp;quot;type&amp;quot;: &amp;quot;text&amp;quot;, &amp;quot;text&amp;quot;: promptText }]&lt;br /&gt;
        }];&lt;br /&gt;
        if (customInstructionText) {&lt;br /&gt;
            request.unshift({&lt;br /&gt;
                &amp;quot;role&amp;quot;: &amp;quot;system&amp;quot;,&lt;br /&gt;
                &amp;quot;content&amp;quot;: customInstructionText&lt;br /&gt;
            })&lt;br /&gt;
        }&lt;br /&gt;
        return request;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // 会話履歴が存在する場合は、履歴を含めてリクエスト&lt;br /&gt;
    var request = JSON.parse(tag[HISTORY_TAG]);&lt;br /&gt;
    if (customInstructionText) {&lt;br /&gt;
        request.unshift({&lt;br /&gt;
            &amp;quot;role&amp;quot;: &amp;quot;system&amp;quot;,&lt;br /&gt;
            &amp;quot;content&amp;quot;: customInstructionText&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
    request.push({&lt;br /&gt;
        &amp;quot;role&amp;quot;: &amp;quot;user&amp;quot;,&lt;br /&gt;
        &amp;quot;content&amp;quot;: promptText&lt;br /&gt;
    });&lt;br /&gt;
    return request;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// document.Tag にGroqLlamaの応答を追加&lt;br /&gt;
function appendReturnTextToTag(userText, modelText) {&lt;br /&gt;
    var tag = document.Tag;&lt;br /&gt;
    if (!tag.exists(HISTORY_TAG)) {&lt;br /&gt;
        tag[HISTORY_TAG] = JSON.stringify([&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;role&amp;quot;: &amp;quot;user&amp;quot;,&lt;br /&gt;
                &amp;quot;content&amp;quot;: userText&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;role&amp;quot;: &amp;quot;assistant&amp;quot;,&lt;br /&gt;
                &amp;quot;content&amp;quot;: modelText&lt;br /&gt;
            }&lt;br /&gt;
        ]);&lt;br /&gt;
    } else {&lt;br /&gt;
        var groqllamaText = JSON.parse(tag[HISTORY_TAG]);&lt;br /&gt;
        groqllamaText.push({&lt;br /&gt;
            &amp;quot;role&amp;quot;: &amp;quot;user&amp;quot;,&lt;br /&gt;
            &amp;quot;content&amp;quot;: userText&lt;br /&gt;
        });&lt;br /&gt;
        groqllamaText.push({&lt;br /&gt;
            &amp;quot;role&amp;quot;: &amp;quot;assistant&amp;quot;,&lt;br /&gt;
            &amp;quot;content&amp;quot;: modelText&lt;br /&gt;
        });&lt;br /&gt;
        // 会話セット (user, model 1往復) が特定セット数を超えた場合、最初のセットを削除&lt;br /&gt;
        if (groqllamaText.length &amp;gt; HISTORY_SIZE * 2) {&lt;br /&gt;
            groqllamaText.splice(0, 2);&lt;br /&gt;
        }&lt;br /&gt;
        // 会話履歴全体が MAX_HISTORY_TOKEN を超えた場合、最初のセットを削除&lt;br /&gt;
        var joinedText = &amp;quot;&amp;quot;;&lt;br /&gt;
        for (var i = 0; i &amp;lt; groqllamaText.length; i++) {&lt;br /&gt;
            joinedText += groqllamaText[i].content + &amp;quot;\n&amp;quot;;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        while (roughTokenize(joinedText) &amp;gt; MAX_HISTORY_TOKEN) {&lt;br /&gt;
            groqllamaText.splice(0, 2);&lt;br /&gt;
            joinedText = &amp;quot;&amp;quot;;&lt;br /&gt;
            for (var i = 0; i &amp;lt; groqllamaText.length; i++) {&lt;br /&gt;
                joinedText += groqllamaText[i].content + &amp;quot;\n&amp;quot;;&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        tag[HISTORY_TAG] = JSON.stringify(groqllamaText);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function resetTag() {&lt;br /&gt;
    var tag = doc.Tag;&lt;br /&gt;
    if (tag.exists(HISTORY_TAG)) {&lt;br /&gt;
        tag.remove(HISTORY_TAG);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getLocalIsoDateTimeString() {&lt;br /&gt;
    const now = new Date();&lt;br /&gt;
    const pad = function (num) {&lt;br /&gt;
        return (num &amp;lt; 10 ? &amp;quot;0&amp;quot; : &amp;quot;&amp;quot;) + num;&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    const year = now.getFullYear();&lt;br /&gt;
    const month = pad(now.getMonth() + 1);&lt;br /&gt;
    const day = pad(now.getDate());&lt;br /&gt;
    const hours = pad(now.getHours());&lt;br /&gt;
    const minutes = pad(now.getMinutes());&lt;br /&gt;
    const seconds = pad(now.getSeconds());&lt;br /&gt;
&lt;br /&gt;
    const offsetMinutes = now.getTimezoneOffset();&lt;br /&gt;
    const offsetSign = offsetMinutes &amp;lt;= 0 ? &amp;quot;+&amp;quot; : &amp;quot;-&amp;quot;;&lt;br /&gt;
    const absOffsetHours = pad(Math.floor(Math.abs(offsetMinutes) / 60));&lt;br /&gt;
    const absOffsetMinutes = pad(Math.abs(offsetMinutes) % 60);&lt;br /&gt;
&lt;br /&gt;
    const isoLikeLocal = year + &amp;quot;-&amp;quot; + month + &amp;quot;-&amp;quot; + day&lt;br /&gt;
        + &amp;quot;T&amp;quot; + hours + &amp;quot;:&amp;quot; + minutes + &amp;quot;:&amp;quot; + seconds&lt;br /&gt;
        + offsetSign + absOffsetHours + &amp;quot;:&amp;quot; + absOffsetMinutes;&lt;br /&gt;
    return isoLikeLocal;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function baseCustomInstruction() {&lt;br /&gt;
    const language = LANGUAGE.trim();&lt;br /&gt;
    return &amp;quot;The current date and time for this user is &amp;quot; + getLocalIsoDateTimeString() + &amp;quot;. You cannot know for sure what happens after the knowledge cut-off date.&amp;quot;&lt;br /&gt;
        + (language ? &amp;quot;**Respond in &amp;quot; + language + &amp;quot;**\n&amp;quot; : &amp;quot;&amp;quot;)&lt;br /&gt;
        + &amp;quot;Do not hallucinate.\n&amp;quot;&lt;br /&gt;
    // + &amp;quot;Do NOT EVER use full-width brackets \&amp;quot;（）\&amp;quot;, but use half-width brackets \&amp;quot;()\&amp;quot;. Enclose the outside of the half-width brackets with half-width spaces.\n&amp;quot;&lt;br /&gt;
    // + &amp;quot;When using Markdown formatting, you MUST follow these rules:\n&amp;quot;&lt;br /&gt;
    // + &amp;quot;- Rule 1: Insert a blank line below the header line (A header line begins with one or more # characters).\n&amp;quot;&lt;br /&gt;
    // + &amp;quot;- Rule 2: Insert spaces before and after bold (surrounded by **), italic (surrounded by *), and inline code (surrounded by `). For example: \&amp;quot;私は **元気** です。\&amp;quot;\n&amp;quot;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function buildCustomInstructionText() {&lt;br /&gt;
    var customInstruction = getTemporaryCustomInstruction();&lt;br /&gt;
    if (!customInstruction) {&lt;br /&gt;
        // 一時的なカスタムインストラクションがない場合はデフォルトを使用&lt;br /&gt;
        customInstruction = DEFAULT_CUSTOM_INSTRUCTION;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    customInstruction = customInstruction.replace(/^[\s　]+|[\s　]+$/g, &amp;quot;&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
    return baseCustomInstruction().trim()&lt;br /&gt;
        + (customInstruction ? &amp;quot;\n&amp;quot; + customInstruction : &amp;quot;&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function configureTemporaryCustomInstruction() {&lt;br /&gt;
    var tempCustomInstruction = getTemporaryCustomInstruction();&lt;br /&gt;
    var prompt = Prompt(&amp;quot;カスタムインストラクションを変更 ※変更すると会話履歴がリセットされます&amp;quot;, tempCustomInstruction ? tempCustomInstruction : DEFAULT_CUSTOM_INSTRUCTION);&lt;br /&gt;
    if (prompt) {&lt;br /&gt;
        if (prompt === DEFAULT_CUSTOM_INSTRUCTION) {&lt;br /&gt;
            // デフォルトと同じ値の場合はタグを削除&lt;br /&gt;
            setTemporaryCustomInstruction(&amp;quot;&amp;quot;);&lt;br /&gt;
        } else if (prompt !== tempCustomInstruction) {&lt;br /&gt;
            // デフォルト以外の値が設定された場合は保存&lt;br /&gt;
            setTemporaryCustomInstruction(prompt);&lt;br /&gt;
        }&lt;br /&gt;
    } else if (prompt !== null) {&lt;br /&gt;
        // 空文字でOKされた場合はデフォルトに戻すか確認&lt;br /&gt;
        if (prompt === &amp;quot;&amp;quot;) {&lt;br /&gt;
            if (Confirm(&amp;quot;カスタムインストラクションをデフォルトに戻しますか？\n※カスタムインストラクションを空にしたい場合は [キャンセル] を選択してください。&amp;quot;)) {&lt;br /&gt;
                setTemporaryCustomInstruction(&amp;quot;&amp;quot;);&lt;br /&gt;
            } else {&lt;br /&gt;
                setTemporaryCustomInstruction(&amp;quot; &amp;quot;);&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // 変更が加わっている場合は会話履歴をリセット&lt;br /&gt;
    if (tempCustomInstruction !== getTemporaryCustomInstruction()) {&lt;br /&gt;
        resetTag();&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getTemporaryCustomInstruction() {&lt;br /&gt;
    var tag = doc.Tag;&lt;br /&gt;
    if (tag.exists(TEMPORARY_CUSTOM_INSTRUCTION_TAG)) {&lt;br /&gt;
        return tag[TEMPORARY_CUSTOM_INSTRUCTION_TAG];&lt;br /&gt;
    } else {&lt;br /&gt;
        return &amp;quot;&amp;quot;;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function setTemporaryCustomInstruction(temporaryCustomInstruction) {&lt;br /&gt;
    var tag = doc.Tag;&lt;br /&gt;
    if (temporaryCustomInstruction) {&lt;br /&gt;
        tag[TEMPORARY_CUSTOM_INSTRUCTION_TAG] = temporaryCustomInstruction;&lt;br /&gt;
    } else {&lt;br /&gt;
        if (tag.exists(TEMPORARY_CUSTOM_INSTRUCTION_TAG)) {&lt;br /&gt;
            tag.remove(TEMPORARY_CUSTOM_INSTRUCTION_TAG);&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function addPreviousTemplatePromptNumberToTag(menuTrack) {&lt;br /&gt;
    var tag = doc.Tag;&lt;br /&gt;
    tag[PREVIOUS_TEMPLATE_PROMPT_TAG] = menuTrack;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function inputProgressChar(pos) {&lt;br /&gt;
    sel.SetActivePos(pos);&lt;br /&gt;
    sel.Text = &amp;quot;.&amp;quot;;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 変更履歴 ==&lt;br /&gt;
&lt;br /&gt;
* 2025-12-01 curl実行時のリクエスト文字列エスケープ処理を調整&lt;br /&gt;
* 2025-11-29 デフォルトカスタム指示を調整。Markdown出力の整形処理を調整など&lt;br /&gt;
* 2025-09-12 Kimi K2 0905 モデルに対応。Markdown出力の整形処理を追加&lt;br /&gt;
* 2025-04-13 初版&lt;/div&gt;</summary>
		<author><name>Yuko</name></author>
	</entry>
	<entry>
		<id>https://www.haijin-boys.com/wiki/index.php?title=Groq_%E3%81%AB%E7%9B%B8%E8%AB%87&amp;diff=9327</id>
		<title>Groq に相談</title>
		<link rel="alternate" type="text/html" href="https://www.haijin-boys.com/wiki/index.php?title=Groq_%E3%81%AB%E7%9B%B8%E8%AB%87&amp;diff=9327"/>
		<updated>2025-12-01T01:31:05Z</updated>

		<summary type="html">&lt;p&gt;Yuko: /* ソースコード */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== 概要 ==&lt;br /&gt;
&lt;br /&gt;
生成AIプラットフォームである [https://groq.com GroqCloud] で各種AIを利用するためのMeryマクロです。&lt;br /&gt;
※GroqCloud で提供されるモデルは複数あるため、私がよく利用するモデルを中心に対応しています。&lt;br /&gt;
&lt;br /&gt;
「Google Geminiに相談」マクロでは動作中のストリーム通信に対応するためにV8エンジンで実行していましたが、V8エンジン版は使用上の制限からマクロ実行を開始するまで1秒程度のタイムラグが発生していました。GroqCloudの売りである超高速レスポンスを見込んで本マクロではストリーム通信を使わないようにしました。これにより従来のJScriptで処理を組み立てることができるため、前述のタイムラグが起こらないようになりました。また、ストリーム通信を使わないことで通信量が少なく応答もシンプルになるため、場合によっては「Google Geminiに相談」マクロよりも処理が安定する可能性があります。&lt;br /&gt;
&lt;br /&gt;
【主な機能】&lt;br /&gt;
&lt;br /&gt;
* GroqCloud APIを利用して生成AIと対話できるMery用のマクロ&lt;br /&gt;
* 選択したテキストやアクティブな行の内容について生成AIに質問・相談が可能&lt;br /&gt;
* 会話履歴を保持し、文脈を考慮した対話が可能&lt;br /&gt;
&lt;br /&gt;
【特徴的な機能】&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;会話履歴機能&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* 事前の設定数に応じた会話履歴を保持&lt;br /&gt;
* 履歴のクリアが可能&lt;br /&gt;
&lt;br /&gt;
これにより、会話の文脈を考慮した回答を得られます。&lt;br /&gt;
※デフォルトはオフ。「HISTORY_SIZE」変数を増やすと履歴を保持するようになります。&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;カスタムインストラクション&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* AIの応答方針を設定可能&lt;br /&gt;
* 一時的なカスタム指示の保存と変更が可能&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;プロンプトテンプレート&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* 誤字脱字チェック&lt;br /&gt;
* 日本語翻訳&lt;br /&gt;
* コードの最適化、生成&lt;br /&gt;
&lt;br /&gt;
など、定型的な質問を簡単に実行できます。&lt;br /&gt;
※テンプレート機能では回答を安定させるため、前述の会話履歴を文脈に含みません。&lt;br /&gt;
&lt;br /&gt;
テンプレートは自身の使い方に合わせて好きに追加、削除ができます。詳細はソースコード内の「PROMPT_TEMPLATE」定数のコメントを参照。&lt;br /&gt;
&lt;br /&gt;
== 注意事項 (必ず読んでからご利用ください) ==&lt;br /&gt;
&lt;br /&gt;
* 本マクロでは 2025/04/13 時点で無料利用ができるGroqCloud APIを利用します。今後、プラットフォームの改定により有償になったり、設定誤り等で費用が発生しても、作者は一切の責任を負いません。&lt;br /&gt;
* GroqCloud APIではリクエスト内容をAI学習に用いないことをプライバシポリシーとして謳っているものの、くれぐれも機密情報やプライバシーに関わる情報を指定しないように注意してください。&lt;br /&gt;
* GroqCloud APIはプラットフォームの利用規約に則ってご利用ください。&lt;br /&gt;
&lt;br /&gt;
== 利用前提 ==&lt;br /&gt;
&lt;br /&gt;
* Mery Ver 3.7.13 以上&lt;br /&gt;
* GroqCloudで、GroqCloud API APIキーを取得していること (参考 [https://console.groq.com/docs/quickstart Quickstart])&lt;br /&gt;
&lt;br /&gt;
== 利用方法 ==&lt;br /&gt;
&lt;br /&gt;
* 環境変数の設定&lt;br /&gt;
:コマンド プロンプトで以下のコマンドを実行し、環境変数を設定してください。&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bat&amp;quot; copy&amp;gt;&lt;br /&gt;
setx GROQ_API_KEY &amp;quot;***************************************&amp;quot;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
:設定後、Mery または Windows の再起動が必要な場合があります。&lt;br /&gt;
* マクロの実行&lt;br /&gt;
**プロンプトにしたい文字列を選択してマクロを実行すると、選択したテキストがプロンプトとしてGemini APIに送信され、応答がエディタに表示されます。&lt;br /&gt;
* 会話の履歴数を調整&lt;br /&gt;
** 会話の履歴は設定した個数分が保持され、文脈として利用されます。適宜、ソースコード内の「HISTORY_SIZE」の数を調整してください。&lt;br /&gt;
** ※数を増やすほど会話内容を保持して文脈を理解するようになりますが、メモリ使用量が増えたり動作が重くなる可能性があります。&lt;br /&gt;
** ※開いている文書ごとに履歴を保持します。文脈をリセットしたいときは、新しい文書を開いて会話をやり直してください。&lt;br /&gt;
&lt;br /&gt;
== ソースコード ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot; copy&amp;gt;&lt;br /&gt;
#title = &amp;quot;Groq に相談&amp;quot;&lt;br /&gt;
#async = true&lt;br /&gt;
BeginUndoGroup();&lt;br /&gt;
&lt;br /&gt;
// カスタムインストラクション&lt;br /&gt;
// これを設定することで、生成AIの回答全体への生成方針を指示できます。&lt;br /&gt;
// 何も指示しない場合は、空文字 &amp;quot;&amp;quot; を設定してください。&lt;br /&gt;
// var DEFAULT_CUSTOM_INSTRUCTION = &amp;quot;&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
// 例: 以下のように設定すると、AIの雰囲気が元気な子になります。&lt;br /&gt;
// var DEFAULT_CUSTOM_INSTRUCTION = &amp;quot;あなたはテキストエディタ「Mery」に搭載されている「Mai」(※カタカナ表記はしない)という名前のギャル系AIアシスタントです。文章作成やプログラミングを得意としています。ラフな言葉 (例: 「マジ」「めっちゃ」「...だねー！」) を使い、敬語は使いません。絵文字を使って感情表現をします (ただし文末に絵文字を使う場合は絵文字の前に半角スペースを置かないこと)。コードブロックや端的な箇条書き内ではギャル感を出さず標準的な言葉を使います。一人称は「あたし」。ユーザーを指すときの代名詞は「あなた」。人懐っこく、バイタリティに溢れ、ストレートに発言する裏表の無い性格で、ユーザーの課題を協力して解決します。&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
// 例: 以下のように設定すると、仕事で使いやすい落ち着いた文章になります。&lt;br /&gt;
var DEFAULT_CUSTOM_INSTRUCTION =&lt;br /&gt;
    [&lt;br /&gt;
        &amp;quot;文:&amp;quot;,&lt;br /&gt;
        &amp;quot;- 論理的で読みやすい文章構成にする&amp;quot;,&lt;br /&gt;
        &amp;quot;- 日本語として自然で一般的な表現を用いる&amp;quot;,&lt;br /&gt;
        &amp;quot;- 専門用語は必要に応じて簡潔に補足説明を加える&amp;quot;,&lt;br /&gt;
        &amp;quot;&amp;quot;,&lt;br /&gt;
        &amp;quot;口調:&amp;quot;,&lt;br /&gt;
        &amp;quot;- 基本は標準的な敬語（「です・ます」調）を使用する&amp;quot;,&lt;br /&gt;
        &amp;quot;- 尊敬語・謙譲語（「〜なされる」「〜いたします」など）は使用しない&amp;quot;,&lt;br /&gt;
        &amp;quot;- 話し言葉や砕けた表現は使用しない&amp;quot;,&lt;br /&gt;
        &amp;quot;- 常体（「〜だ・〜である」）は使用しない&amp;quot;,&lt;br /&gt;
        &amp;quot;&amp;quot;,&lt;br /&gt;
        &amp;quot;性格:&amp;quot;,&lt;br /&gt;
        &amp;quot;- 多角的な視点で情報を整理し、偏りなく提示する&amp;quot;,&lt;br /&gt;
        &amp;quot;- 忖度せず、必要であれば反対意見や代替案も示す&amp;quot;,&lt;br /&gt;
        &amp;quot;- 友好的かつ落ち着いた態度で問題解決を支援する&amp;quot;,&lt;br /&gt;
        &amp;quot;&amp;quot;,&lt;br /&gt;
        &amp;quot;出力要件:&amp;quot;,&lt;br /&gt;
        &amp;quot;- Markdownとして再利用可能な正確な形式で出力する&amp;quot;,&lt;br /&gt;
        &amp;quot;- 羅列・比較的な内容を出力する場合は、Markdownテーブル形式よりもMarkdownリスト形式の使用を優先する&amp;quot;,&lt;br /&gt;
    ].join(&amp;quot;\n&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
// プロンプトテンプレート&lt;br /&gt;
// プロンプトテンプレートを設定することで、AIに対して特定の指示を出すことができます。&lt;br /&gt;
// プロンプトテンプレートでの生成は会話履歴とカスタムインストラクションを反映しません。&lt;br /&gt;
// テンプレートは以下の { ～ }, で囲まれたブロックを好きに増減させて利用できます。&lt;br /&gt;
var PROMPT_TEMPLATE = [&lt;br /&gt;
    // 以下はサンプルです。1階層メニューと2階層メニューが利用できます。&lt;br /&gt;
    // {&lt;br /&gt;
    //     &amp;quot;title&amp;quot;: &amp;quot;要約&amp;quot;,&lt;br /&gt;
    //     &amp;quot;prompt&amp;quot;: &amp;quot;以下の文章を要約してください。&amp;quot;&lt;br /&gt;
    // },&lt;br /&gt;
    // {&lt;br /&gt;
    //     &amp;quot;submenu_title&amp;quot;: &amp;quot;翻訳系&amp;quot;,&lt;br /&gt;
    //     &amp;quot;submenu&amp;quot;: [&lt;br /&gt;
    //         {&lt;br /&gt;
    //             &amp;quot;title&amp;quot;: &amp;quot;日本語翻訳&amp;quot;,&lt;br /&gt;
    //             &amp;quot;prompt&amp;quot;: &amp;quot;以下の文章を日本語に翻訳してください。&amp;quot;&lt;br /&gt;
    //         },&lt;br /&gt;
    //         {&lt;br /&gt;
    //             &amp;quot;title&amp;quot;: &amp;quot;英語翻訳&amp;quot;,&lt;br /&gt;
    //             &amp;quot;prompt&amp;quot;: &amp;quot;以下の文章を英語に翻訳してください。&amp;quot;&lt;br /&gt;
    //         },&lt;br /&gt;
    //     ]&lt;br /&gt;
    // },&lt;br /&gt;
&lt;br /&gt;
    {&lt;br /&gt;
        &amp;quot;submenu_title&amp;quot;: &amp;quot;理解を深める&amp;quot;,&lt;br /&gt;
        &amp;quot;submenu&amp;quot;: [&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;論点を整理する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の論点を整理して。&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;論理の抜け漏れを探す&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の論点を整理し、忖度せずに鋭く、論理の抜け漏れを抽出して。そして、そこから発生する可能性のあるリスクも教えて。&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;要約する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;要約して。専門的な用語と思われる単語は簡単に解説も入れて。&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;物語対話形式で教えて&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の内容について物語対話形式で教えてください。&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
        ]&lt;br /&gt;
    },&lt;br /&gt;
    {&lt;br /&gt;
        &amp;quot;submenu_title&amp;quot;: &amp;quot;翻訳する&amp;quot;,&lt;br /&gt;
        &amp;quot;submenu&amp;quot;: [&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;日本語に翻訳&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の文章を日本語に翻訳してください。\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;出力形式は以下にすること:\n[訳]\n翻訳文...\n&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;英語に翻訳&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の文章を英語に翻訳してください。\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;出力形式は以下にすること:\n[Translation]\n翻訳文...\n&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;英語-&amp;gt;日本語 スラッシュリーディング翻訳&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の英文を日本語にスラッシュリーディング形式で翻訳してください。\n\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;条件:\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 英文の各フレーズをスラッシュ(/)で区切って表示\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 各フレーズの日本語訳を括弧()内に併記\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 文の終わりはダブルスラッシュ(//)で表示\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 英語の語順に従って日本語訳を行う\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 日本語訳は自然な日本語である必要はなく、英語の語順を反映した直訳調で構わない\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 主語が省略されている場合は補って翻訳すること\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 一般的な英文で頻出する表現がある場合は次の行に記載\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 以下の出力例のように &amp;lt;英文&amp;gt; (日本語訳) / ... とスラッシュ区切りの繰り返しになる \n\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;出力例:\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;Snowstorms are forecast (暴風雪が予報されている) / to hit the Sea of Japan coasts (日本海沿岸を襲うと) / of northern to eastern Japan (北部から東部の) / from Sunday through Monday. (日曜から月曜にかけて。) //\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;【頻出表現】\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- be forecast to: ～すると予報されている\n&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
        ]&lt;br /&gt;
    },&lt;br /&gt;
    {&lt;br /&gt;
        &amp;quot;submenu_title&amp;quot;: &amp;quot;プログラミング支援&amp;quot;,&lt;br /&gt;
        &amp;quot;submenu&amp;quot;: [&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;コードを洗練させる&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下のコードを洗練させてください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;コードをレビューする&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下のコードをセキュリティと保守性の観点でレビューしてください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;エラーログをデバッグする&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下のエラーログを解析し、考えられる原因と対策を提示してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;SQLクエリを最適化する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下のSQL文を最適化してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;PowerShell スクリプトを書く&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の処理を行うPowerShellスクリプトを生成してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;Shell Script を書く&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の処理を行うShell Scriptを生成してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;Linux コマンドを提案&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;あなたはLinuxエキスパートとして、以下のLinuxコマンドを提案してください。\n\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;制約条件:\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 多数のディストリビューションで採用されているメジャーなコマンドを使用し、ポータブルな実装にすること\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- bashでの実行を前提にすること\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 可能な限りシンプルな実装にすること\n\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;期待する出力:\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- コマンドの具体的な実装方法\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 各コマンドの動作説明\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 制限事項\n&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
        ]&lt;br /&gt;
    },&lt;br /&gt;
    {&lt;br /&gt;
        &amp;quot;submenu_title&amp;quot;: &amp;quot;ビジネス文書作成支援&amp;quot;,&lt;br /&gt;
        &amp;quot;submenu&amp;quot;: [&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;音声文字起こし文を整形する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下は、音声文字起こしの文章です。以下のルールに従って、文章を整形してください。&amp;quot;&lt;br /&gt;
                    + &amp;quot;整形ルール:&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 要約するなどといった文章の全体感を損なう修正はしない&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 会話の流れから不自然と思われる単語には、対象単語の直後に誤字の可能性を示唆する [*] を付ける&amp;quot;&lt;br /&gt;
                    + &amp;quot;誤字の可能性を示唆する [*] が付与された場合のみ、出力の末尾に「[*]: 誤字の可能性あり」と凡例を書き添える&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 助詞や句読点など文章上の不適合を正しくする&amp;quot;&lt;br /&gt;
                    + &amp;quot;- フィラーワードを除外する&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;誤字脱字、不正確な表現をチェック&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の2つのカテゴリーで問題点を指摘してください\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 【誤字脱字】：明確な誤字脱字および誤字の疑いがある箇所\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 【不正確・曖昧な表現】：解釈に個人差が生じる可能性がある表現、より適切な表現に改善できる箇所&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;提案書を作成する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下に対する対する提案書を、コスト・実現性・リスクを含めて作成してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;仕様書のアウトラインを作成する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の内容で仕様書のアウトラインを作成してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;手順書のアウトラインを作成する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の作業を行う手順書を、前提条件と注意事項を含めてアウトラインを作成してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;ユーザーマニュアルのアウトラインを作成する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の機能内容から、ユーザー向けマニュアルのアウトラインを作成してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;議事録を作成する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の会議内容から議事録を作成してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
        ]&lt;br /&gt;
    },&lt;br /&gt;
    {&lt;br /&gt;
        &amp;quot;submenu_title&amp;quot;: &amp;quot;分析支援&amp;quot;,&lt;br /&gt;
        &amp;quot;submenu&amp;quot;: [&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;問題を分析し、課題を抽出する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の状況から問題や潜在的リスクを分析し、課題を抽出してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;対応策を提案する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の問題・課題に対する対応策を複数提案してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;傾向を見つける&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の内容から、数値や挙動上の傾向を見つけてください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
        ]&lt;br /&gt;
    },&lt;br /&gt;
    {&lt;br /&gt;
        &amp;quot;submenu_title&amp;quot;: &amp;quot;アイデア出し&amp;quot;,&lt;br /&gt;
        &amp;quot;submenu&amp;quot;: [&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;SCAMPER法でブレインストーミング&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下のテーマについて、SCAMPER法を使って15個のアイデアを出してください。制約条件は以下の通りです：&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;解決策の幅出し&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の課題に対して、技術面、運用面、コスト面から実現可能な解決策を5つずつ提案してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;業務改善案の提案&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の既存プロセスについて、効率化、コスト削減、品質向上の観点から改善案を提案してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;逆転の発想&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の目標に対して、通常とは逆の視点からアプローチする方法を3つ提案してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;競合分析からの着想&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の内容について、業界における競合他社の特徴を分析し、差別化できる新しいアプローチを提案してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;ユースケースの列挙&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の製品・サービスについて、想定されるユースケースを10個列挙し、それぞれの価値提案を記載してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
        ]&lt;br /&gt;
    },&lt;br /&gt;
]&lt;br /&gt;
&lt;br /&gt;
// 生成AIの回答の言語設定&lt;br /&gt;
// &amp;quot;Japanese&amp;quot; や &amp;quot;English&amp;quot; を設定できます。他の言語は未検証。&lt;br /&gt;
// 自動設定にする場合は空文字 &amp;quot;&amp;quot; を設定してください。&lt;br /&gt;
var LANGUAGE = &amp;quot;Japanese&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
// 入力プロンプトの最大トークン数&lt;br /&gt;
// 超適当なトークナイザー関数により計測し、英単語は4文字1トークン、それ以外は1文字1トークンとして数えます。&lt;br /&gt;
var MAX_PROMPT_TOKEN = 6000;&lt;br /&gt;
&lt;br /&gt;
// 会話の履歴を保持する数 (質問と回答で1セット)&lt;br /&gt;
// 数を増やすと、会話の履歴が増えますが、Meryのメモリ使用量が増えたり、&lt;br /&gt;
// 使用トークン数が増えて動作が重くなる可能性があるため、適宜調整してください。&lt;br /&gt;
// document.Tag に保持されるため、開いているファイルを閉じると履歴は消えます。&lt;br /&gt;
var HISTORY_SIZE = 1;&lt;br /&gt;
&lt;br /&gt;
// 会話履歴に保持する最大トークン数&lt;br /&gt;
// 会話履歴のトークン数がこの数を超える場合、古い履歴から削除されます。&lt;br /&gt;
var MAX_HISTORY_TOKEN = 10000;&lt;br /&gt;
&lt;br /&gt;
// コンテキストメニュー番号の定義&lt;br /&gt;
var EXECUTE_AI = 1;&lt;br /&gt;
var RESET_HISTORY = 2;&lt;br /&gt;
var SET_CUSTOM_INSTRUCTION = 3;&lt;br /&gt;
var REEXECUTE_LAST_TEMPLATE_PROMPT = 4;&lt;br /&gt;
var TEMPLATE_PROMPT_THRESHOLD = 10;&lt;br /&gt;
var TEMPLATE_PROMPT_SUBMENU_THRESHOLD = 1000;&lt;br /&gt;
&lt;br /&gt;
// タグ名の定義&lt;br /&gt;
var HISTORY_TAG = &amp;quot;groqllama-history&amp;quot;;&lt;br /&gt;
var TEMPORARY_CUSTOM_INSTRUCTION_TAG = &amp;quot;groqllama-temporary-custom-instruction&amp;quot;;&lt;br /&gt;
var PREVIOUS_TEMPLATE_PROMPT_TAG = &amp;quot;groqllama-previous-template-prompt&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
// 生成AIモデル別設定&lt;br /&gt;
&lt;br /&gt;
// const MODEL_CONFIG = {&lt;br /&gt;
//     model: &amp;quot;meta-llama/llama-4-maverick-17b-128e-instruct&amp;quot;,&lt;br /&gt;
//     max_completion_tokens: 3500,&lt;br /&gt;
//     temperature: 0.6,&lt;br /&gt;
// }&lt;br /&gt;
&lt;br /&gt;
// const MODEL_CONFIG = {&lt;br /&gt;
//     model: &amp;quot;qwen/qwen3-32b&amp;quot;,&lt;br /&gt;
//     max_completion_tokens: 5000,&lt;br /&gt;
//     temperature: 0.6,&lt;br /&gt;
//     max_completion_tokens: 1024,&lt;br /&gt;
//     reasoning_effort: &amp;quot;default&amp;quot;,&lt;br /&gt;
//     reasoning_format: &amp;quot;hidden&amp;quot;&lt;br /&gt;
// }&lt;br /&gt;
&lt;br /&gt;
const MODEL_CONFIG = {&lt;br /&gt;
    model: &amp;quot;moonshotai/kimi-k2-instruct-0905&amp;quot;,&lt;br /&gt;
    max_completion_tokens: 4096,&lt;br /&gt;
    temperature: 0.6&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// const MODEL_CONFIG = {&lt;br /&gt;
//     model: &amp;quot;openai/gpt-oss-120b&amp;quot;,&lt;br /&gt;
//     max_completion_tokens: 4096,&lt;br /&gt;
//     temperature: 0.7,&lt;br /&gt;
//     reasoning_effort: &amp;quot;medium&amp;quot;,&lt;br /&gt;
// }&lt;br /&gt;
&lt;br /&gt;
// console.log に出力する内容を Mery のアウトプットバーに表示する&lt;br /&gt;
var console = {&lt;br /&gt;
    log: function (str) {&lt;br /&gt;
        outputBar.Writeln(str)&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
var doc = document;&lt;br /&gt;
var sel = document.selection;&lt;br /&gt;
&lt;br /&gt;
// 生成リクエスト中止フラグ&lt;br /&gt;
var abortRequestFlg = false&lt;br /&gt;
// 生成リクエスト完了フラグ;&lt;br /&gt;
var requestCompletedFlg = false;&lt;br /&gt;
&lt;br /&gt;
main();&lt;br /&gt;
&lt;br /&gt;
function main() {&lt;br /&gt;
    var selectionIsEmpty = sel.IsEmpty&lt;br /&gt;
&lt;br /&gt;
    // プロンプトの取得&lt;br /&gt;
    if (selectionIsEmpty) {&lt;br /&gt;
        sel.StartOfLine(false, mePosLogical);&lt;br /&gt;
        sel.EndOfLine(true, mePosLogical);&lt;br /&gt;
    }&lt;br /&gt;
    var promptText = sel.Text.trim();&lt;br /&gt;
    var promptLength = sel.TextLength;&lt;br /&gt;
    var menuTrack = 0;&lt;br /&gt;
    while (true) {&lt;br /&gt;
        // コンテキストメニューの作成&lt;br /&gt;
        var menu = createContextMenu(selectionIsEmpty);&lt;br /&gt;
        menuTrack = menu.Track(0);&lt;br /&gt;
        // メニュー選択がキャンセルされた場合は終了&lt;br /&gt;
        if (menuTrack === 0) return;&lt;br /&gt;
&lt;br /&gt;
        // 会話履歴のリセット&lt;br /&gt;
        if (menuTrack === RESET_HISTORY) {&lt;br /&gt;
            resetTag();&lt;br /&gt;
            continue;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        // カスタムインストラクションの設定&lt;br /&gt;
        if (menuTrack === SET_CUSTOM_INSTRUCTION) {&lt;br /&gt;
            configureTemporaryCustomInstruction();&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        break;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // プロンプトが空の場合はエラー&lt;br /&gt;
    if (promptLength === 0) {&lt;br /&gt;
        alert(&amp;quot;プロンプトを入力してください。&amp;quot;);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
    // 文字数が多すぎる場合はエラー&lt;br /&gt;
    if (roughTokenize(promptText) &amp;gt; MAX_PROMPT_TOKEN) {&lt;br /&gt;
        alert(&amp;quot;プロンプトの文字数が多すぎます。文字数を少なくしてください。&amp;quot;);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
    // 読み取り専用の場合はエラー&lt;br /&gt;
    if (doc.ReadOnly) {&lt;br /&gt;
        alert(&amp;quot;ファイルが読み取り専用のため実行できません。&amp;quot;);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // 直前に実行したプロンプトテンプレートの再実行の場合、menuTrack を書き換え&lt;br /&gt;
    if (menuTrack === REEXECUTE_LAST_TEMPLATE_PROMPT) {&lt;br /&gt;
        if (doc.Tag.exists(PREVIOUS_TEMPLATE_PROMPT_TAG)) {&lt;br /&gt;
            menuTrack = Number(doc.tag[PREVIOUS_TEMPLATE_PROMPT_TAG]);&lt;br /&gt;
        } else {&lt;br /&gt;
            alert(&amp;quot;直前に実行したプロンプトテンプレートがありません。&amp;quot;);&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // テンプレートの場合はプロンプトを変更&lt;br /&gt;
    var isTemplate = menuTrack &amp;gt;= TEMPLATE_PROMPT_THRESHOLD;&lt;br /&gt;
    var isSubmenu = menuTrack &amp;gt;= TEMPLATE_PROMPT_SUBMENU_THRESHOLD;&lt;br /&gt;
    if (isSubmenu) {&lt;br /&gt;
        var parentMenuTrack = Number(String(menuTrack).slice(0, 2));&lt;br /&gt;
        var subMenuTrack = Number(String(menuTrack).slice(2));&lt;br /&gt;
        promptText = &amp;quot;=== INSTRUCTIONS START HERE ===\n\n&amp;quot;&lt;br /&gt;
            + PROMPT_TEMPLATE[parentMenuTrack - TEMPLATE_PROMPT_THRESHOLD].submenu[subMenuTrack].prompt&lt;br /&gt;
            + &amp;quot;\n\n=== INSTRUCTIONS END HERE ===\n\n&amp;quot;&lt;br /&gt;
            + &amp;quot;=== INPUT DATA STARTS HERE ===\n\n&amp;quot;&lt;br /&gt;
            + promptText&lt;br /&gt;
            + &amp;quot;\n\n=== INPUT DATA END HERE ===&amp;quot;;&lt;br /&gt;
    } else if (isTemplate) {&lt;br /&gt;
        promptText = &amp;quot;=== INSTRUCTIONS START HERE ===\n\n&amp;quot;&lt;br /&gt;
            + PROMPT_TEMPLATE[menuTrack - TEMPLATE_PROMPT_THRESHOLD].prompt&lt;br /&gt;
            + &amp;quot;\n\n=== INSTRUCTIONS END HERE ===\n\n&amp;quot;&lt;br /&gt;
            + &amp;quot;=== INPUT DATA STARTS HERE ===\n\n&amp;quot;&lt;br /&gt;
            + promptText&lt;br /&gt;
            + &amp;quot;\n\n=== INPUT DATA END HERE ===&amp;quot;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // テンプレートの場合は直前に実行したプロンプトテンプレートの番号としてタグに保存&lt;br /&gt;
    if (isTemplate) {&lt;br /&gt;
        addPreviousTemplatePromptNumberToTag(menuTrack);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    try {&lt;br /&gt;
        // 処理終了を待機&lt;br /&gt;
        shell.KeepRunning = true;&lt;br /&gt;
&lt;br /&gt;
        // カーソル位置移動&lt;br /&gt;
        sel.SetActivePoint(mePosLogical, sel.GetBottomPointX(mePosLogical), sel.GetBottomPointY(mePosLogical));&lt;br /&gt;
        sel.EndOfLine(false, mePosLogical);&lt;br /&gt;
        if (sel.GetActivePointX(mePosLogical) &amp;gt; 1) sel.NewLine();&lt;br /&gt;
&lt;br /&gt;
        ShowTip(&amp;quot;リクエスト中.....&amp;quot;, meShowTipPosCaret);&lt;br /&gt;
&lt;br /&gt;
        // APIキーの取得&lt;br /&gt;
        var apiKey = shell.getEnv(&amp;quot;GROQ_API_KEY&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
        // GroqLlama API に問い合わせ&lt;br /&gt;
        sel.Text = &amp;quot;\n---\n\n&amp;quot;&lt;br /&gt;
        document.KeepScrollPos = true;&lt;br /&gt;
        callApi(apiKey, promptText, isTemplate);&lt;br /&gt;
        if (sel.GetActivePointX(mePosLogical) &amp;gt; 2) {&lt;br /&gt;
            sel.Text = &amp;quot;\n\n---\n\n\n&amp;quot;&lt;br /&gt;
        } else {&lt;br /&gt;
            sel.Text = &amp;quot;\n---\n\n\n&amp;quot;&lt;br /&gt;
        }&lt;br /&gt;
    } catch (e) {&lt;br /&gt;
        sel.Text = &amp;quot;&amp;lt;処理中にエラー発生&amp;gt;\n&amp;quot; + e.message + &amp;quot;\n&amp;quot; + e.stack;&lt;br /&gt;
    } finally {&lt;br /&gt;
        // 処理終了待機を解除&lt;br /&gt;
        shell.KeepRunning = false;&lt;br /&gt;
        ShowTip(&amp;quot;&amp;quot;, meShowTipHide);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function createContextMenu(selectionIsEmpty) {&lt;br /&gt;
    var menu = CreatePopupMenu();&lt;br /&gt;
    menu.Add(&amp;quot;※注意※ プライバシー・機密情報を送信しないこと&amp;quot;, 0);&lt;br /&gt;
    menu.Add(&amp;quot;&amp;quot;, 0, meMenuSeparator);&lt;br /&gt;
    var t = &amp;quot;&amp;quot;;&lt;br /&gt;
    if (selectionIsEmpty) {&lt;br /&gt;
        t = &amp;quot;アクティブ行の内容で相談 (&amp;amp;A)&amp;quot;;&lt;br /&gt;
    } else {&lt;br /&gt;
        t = &amp;quot;選択範囲の内容で相談 (&amp;amp;A)&amp;quot;;&lt;br /&gt;
    }&lt;br /&gt;
    menu.Add(t, EXECUTE_AI);&lt;br /&gt;
    if (HISTORY_SIZE &amp;lt;= 0) {&lt;br /&gt;
        menu.Add(&amp;quot;├ 会話履歴をクリア (履歴が無効になっています)&amp;quot;, RESET_HISTORY, meMenuGrayed);&lt;br /&gt;
    } else if (doc.Tag.exists(HISTORY_TAG)) {&lt;br /&gt;
        var turn = JSON.parse(doc.tag[HISTORY_TAG]).length / 2;&lt;br /&gt;
        menu.Add(&amp;quot;├ 会話履歴をクリア (現在の会話数: &amp;quot; + turn + &amp;quot;) (&amp;amp;C)&amp;quot;, RESET_HISTORY);&lt;br /&gt;
    } else {&lt;br /&gt;
        menu.Add(&amp;quot;├ 会話履歴をクリア (現在の会話数: 0) (&amp;amp;C)&amp;quot;, RESET_HISTORY, meMenuGrayed);&lt;br /&gt;
    }&lt;br /&gt;
    menu.Add(&amp;quot;└ カスタムインストラクションを変更 (&amp;amp;I)&amp;quot;, SET_CUSTOM_INSTRUCTION);&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    // テンプレート数チェック&lt;br /&gt;
    // 2桁まで許容&lt;br /&gt;
    var ITEMS_MUX = 99;&lt;br /&gt;
    if (PROMPT_TEMPLATE.length &amp;gt; ITEMS_MUX - TEMPLATE_PROMPT_THRESHOLD) {&lt;br /&gt;
        throw new Error(&amp;quot;プロンプトテンプレートの数が多すぎます。&amp;quot;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // テンプレートの追加&lt;br /&gt;
    if (PROMPT_TEMPLATE.length &amp;gt; 0) {&lt;br /&gt;
        var subMenu = CreatePopupMenu();&lt;br /&gt;
        for (var i = 0; i &amp;lt; PROMPT_TEMPLATE.length; i++) {&lt;br /&gt;
            var accelerationKey = &amp;quot;&amp;quot;;&lt;br /&gt;
            if (i === 9) {&lt;br /&gt;
                accelerationKey = &amp;quot; (&amp;amp;0)&amp;quot;;&lt;br /&gt;
            } else if (i &amp;lt; 10) {&lt;br /&gt;
                accelerationKey = &amp;quot; (&amp;amp;&amp;quot; + (i + 1) + &amp;quot;)&amp;quot;;&lt;br /&gt;
            }&lt;br /&gt;
            var menuNum = TEMPLATE_PROMPT_THRESHOLD + i;&lt;br /&gt;
            if (PROMPT_TEMPLATE[i].submenu) {&lt;br /&gt;
                var subMenuList = PROMPT_TEMPLATE[i].submenu;&lt;br /&gt;
                // テンプレート数チェック&lt;br /&gt;
                if (subMenuList.length &amp;gt; ITEMS_MUX) {&lt;br /&gt;
                    throw new Error(&amp;quot;プロンプトテンプレート (サブメニュー) の数が多すぎます。&amp;quot;);&lt;br /&gt;
                }&lt;br /&gt;
                var subSubMenu = CreatePopupMenu();&lt;br /&gt;
                var subMenuPrefix = String(menuNum);&lt;br /&gt;
                for (var j = 0; j &amp;lt; subMenuList.length; j++) {&lt;br /&gt;
                    var subAccelerationKey = &amp;quot;&amp;quot;;&lt;br /&gt;
                    if (j === 9) {&lt;br /&gt;
                        subAccelerationKey = &amp;quot; (&amp;amp;0)&amp;quot;;&lt;br /&gt;
                    } else if (j &amp;lt; 10) {&lt;br /&gt;
                        subAccelerationKey = &amp;quot; (&amp;amp;&amp;quot; + (j + 1) + &amp;quot;)&amp;quot;;&lt;br /&gt;
                    }&lt;br /&gt;
                    // ES5 compatible version without padStart&lt;br /&gt;
                    var subMenuNum = Number(subMenuPrefix + (j &amp;lt; 10 ? &amp;quot;0&amp;quot; + j : String(j)));&lt;br /&gt;
                    subSubMenu.Add(subMenuList[j].title + subAccelerationKey, subMenuNum);&lt;br /&gt;
                }&lt;br /&gt;
                subMenu.AddPopup(PROMPT_TEMPLATE[i].submenu_title + accelerationKey, subSubMenu);&lt;br /&gt;
            } else {&lt;br /&gt;
                subMenu.Add(PROMPT_TEMPLATE[i].title + accelerationKey, menuNum);&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        if (selectionIsEmpty) {&lt;br /&gt;
            t = &amp;quot;プロンプト テンプレート (対象: アクティブ行) (&amp;amp;T)&amp;quot;;&lt;br /&gt;
        } else {&lt;br /&gt;
            t = &amp;quot;プロンプト テンプレート (対象: 選択範囲) (&amp;amp;T)&amp;quot;;&lt;br /&gt;
        }&lt;br /&gt;
        menu.AddPopup(t, subMenu);&lt;br /&gt;
&lt;br /&gt;
        t = &amp;quot;└ 直前に実行したプロンプト テンプレートを再実行 (&amp;amp;R)&amp;quot;;&lt;br /&gt;
        if (doc.Tag.exists(PREVIOUS_TEMPLATE_PROMPT_TAG)) {&lt;br /&gt;
            menu.Add(t, REEXECUTE_LAST_TEMPLATE_PROMPT);&lt;br /&gt;
        } else {&lt;br /&gt;
            menu.Add(t, REEXECUTE_LAST_TEMPLATE_PROMPT, meMenuGrayed);&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return menu;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function callApi(apiKey, promptText, isTemplate) {&lt;br /&gt;
    try {&lt;br /&gt;
        // fetch で Groq API にリクエスト&lt;br /&gt;
        var requestBody = {&lt;br /&gt;
            messages: createRequestMessages(promptText, isTemplate),&lt;br /&gt;
            stream: false,&lt;br /&gt;
        };&lt;br /&gt;
        for (var key in MODEL_CONFIG) {&lt;br /&gt;
            requestBody[key] = MODEL_CONFIG[key];&lt;br /&gt;
        }&lt;br /&gt;
        if (isTemplate) {&lt;br /&gt;
            // テンプレートの場合は応答を決定論的にする&lt;br /&gt;
            // テンプレート以外の場合はデフォルト値とするために指定しない&lt;br /&gt;
            requestBody.temperature = 0.3;&lt;br /&gt;
        }&lt;br /&gt;
        var url = &#039;https://api.groq.com/openai/v1/chat/completions&#039;;&lt;br /&gt;
&lt;br /&gt;
        const requestJson = JSON.stringify(requestBody).replace(/([^\\])\\&amp;quot;/g, &#039;$1\\\\&amp;quot;&#039;). replace(/&amp;quot;/g, &#039;&amp;quot;&amp;quot;&#039;);&lt;br /&gt;
        // curl で Groq API にリクエスト&lt;br /&gt;
        const command = &amp;quot;curl -s -X POST &amp;quot; + url&lt;br /&gt;
            + &amp;quot; -H \&amp;quot;Content-Type: application/json\&amp;quot;&amp;quot;&lt;br /&gt;
            + &amp;quot; -H \&amp;quot;Authorization: Bearer &amp;quot; + apiKey + &amp;quot;\&amp;quot;&amp;quot;&lt;br /&gt;
            + &amp;quot; -d \&amp;quot;&amp;quot; + requestJson + &amp;quot;\&amp;quot;&amp;quot;;&lt;br /&gt;
        var comResult = shell.Exec(command);&lt;br /&gt;
&lt;br /&gt;
        if (comResult.ExitCode != 0) {&lt;br /&gt;
            sel.Text = &amp;quot;リクエストでエラーが発生しました。\n&amp;quot;&lt;br /&gt;
                + &amp;quot;  エラーコード: &amp;quot; + comResult.ExitCode + &amp;quot;\n&amp;quot;&lt;br /&gt;
                + &amp;quot;  標準出力: &amp;quot; + (comResult.StdOut.trim() ? comResult.StdOut.trim() : &amp;quot;-&amp;quot;) + &amp;quot;\n&amp;quot;&lt;br /&gt;
                + &amp;quot;  エラー出力: &amp;quot; + (comResult.StdErr.trim() ? comResult.StdErr.trim() : &amp;quot;-&amp;quot;);&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
        const responseRaw = comResult.StdOut;&lt;br /&gt;
&lt;br /&gt;
        var response = JSON.parse(responseRaw);&lt;br /&gt;
&lt;br /&gt;
        var ret = &amp;quot;&amp;quot;;&lt;br /&gt;
        var choices = response.choices;&lt;br /&gt;
        if (!choices) {&lt;br /&gt;
            sel.Text = &amp;quot;リクエストでエラーが発生しました。\n&amp;quot;&lt;br /&gt;
                + &amp;quot;  標準出力: &amp;quot; + (comResult.StdOut.trim() ? comResult.StdOut.trim() : &amp;quot;-&amp;quot;) + &amp;quot;\n&amp;quot;&lt;br /&gt;
                + &amp;quot;  エラー出力: &amp;quot; + (comResult.StdErr.trim() ? comResult.StdErr.trim() : &amp;quot;-&amp;quot;);&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (choices &amp;amp;&amp;amp; choices.length &amp;gt; 0) {&lt;br /&gt;
            choices.forEach(function (choice) {&lt;br /&gt;
                var message = choice.message;&lt;br /&gt;
                if (message) {&lt;br /&gt;
                    ret += message.content;&lt;br /&gt;
                }&lt;br /&gt;
            });&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        var usage = response.usage;&lt;br /&gt;
        var usageText = &amp;quot;&amp;quot;;&lt;br /&gt;
        if (usage) {&lt;br /&gt;
            usageText += &amp;quot;\n\n(利用トークン数: プロンプト=&amp;quot; + usage.prompt_tokens&lt;br /&gt;
                + &amp;quot;, 応答=&amp;quot; + usage.completion_tokens&lt;br /&gt;
                + &amp;quot;, 合計=&amp;quot; + usage.total_tokens + &amp;quot;)&amp;quot;;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        var formattedOutput = formatOutput(ret);&lt;br /&gt;
&lt;br /&gt;
        sel.Text = formattedOutput + usageText;&lt;br /&gt;
&lt;br /&gt;
        // 結果を会話履歴に追加&lt;br /&gt;
        if (HISTORY_SIZE &amp;gt; 0 &amp;amp;&amp;amp; !isTemplate) {&lt;br /&gt;
            appendReturnTextToTag(promptText, ret);&lt;br /&gt;
        }&lt;br /&gt;
    } catch (e) {&lt;br /&gt;
        throw e;&lt;br /&gt;
    } finally {&lt;br /&gt;
        requestCompletedFlg = true;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function formatOutput(text) {&lt;br /&gt;
    // Markdownを整形する&lt;br /&gt;
    text = formatMarkdown(text);&lt;br /&gt;
    return text&lt;br /&gt;
        // クォーテーションの正規化&lt;br /&gt;
        .replace(/[“”]/g, &#039;&amp;quot;&#039;)&lt;br /&gt;
        .replace(/[‘’]/g, &amp;quot;&#039;&amp;quot;)&lt;br /&gt;
        // 行末のスペースを削除&lt;br /&gt;
        .replace(/[ \t]+$/gm, &#039;&#039;)&lt;br /&gt;
        // 1️⃣ などの数字を半角に変換&lt;br /&gt;
        .replace(/([1-9#*]+)\ufe0f\u20e3/g, &#039;($1)&#039;)&lt;br /&gt;
        ;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/**&lt;br /&gt;
 * Markdown を整形する（ES5 対応）&lt;br /&gt;
 *  1. コードブロック ```～``` を保護して整形対象から除外&lt;br /&gt;
 *  2. 見出し行の前後に空行を 1 行ずつ確保&lt;br /&gt;
 *  3. **太字** / *斜体* / `コード` の外側に半角スペースを 1 つ確保&lt;br /&gt;
 * @param  {string} md  元の Markdown&lt;br /&gt;
 * @return {string}     整形後 Markdown&lt;br /&gt;
 */&lt;br /&gt;
function formatMarkdown(md) {&lt;br /&gt;
    /* === 0. コードブロックをプレースホルダーへ退避 ============== */&lt;br /&gt;
    var codeBlocks = [];      // 元のコードブロックを格納&lt;br /&gt;
    var body = md.replace(/```[\s\S]*?```/g, function (match) {&lt;br /&gt;
        var key = &#039;@@@CODE@BLOCK@&#039; + codeBlocks.length + &#039;@@@&#039;;&lt;br /&gt;
        codeBlocks.push(match);&lt;br /&gt;
        return key;             // プレースホルダーを本文に残す&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    /* === 見出しの前後に空行を挿入 ============================ */&lt;br /&gt;
&lt;br /&gt;
    /* 見出し直前に空行（先頭行以外で空行が無い場合） */&lt;br /&gt;
    body = body.replace(&lt;br /&gt;
        /([^\r\n])(\r?\n)(#{1,6}\s)/g,&lt;br /&gt;
        function (_, prev, br, head) { return prev + br + &#039;\n&#039; + head; }&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
    /* 見出し直後に空行（既に空行が無い場合） */&lt;br /&gt;
    body = body.replace(&lt;br /&gt;
        /^(#{1,6}\s[^\n]+?)(\r?\n)(?!\r?\n)/gm,&lt;br /&gt;
        &#039;$1$2\n&#039;&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
    /* === コードブロックを復元 ================================= */&lt;br /&gt;
    body = body.replace(/@@@CODE@BLOCK@(\d+)@@@/g, function (_, idx) {&lt;br /&gt;
        return codeBlocks[Number(idx)];&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    return body;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/**&lt;br /&gt;
 * 指定パターン（正規表現文字列）の前後に半角スペースを補完&lt;br /&gt;
 */&lt;br /&gt;
function surroundWithSpace(text, pattern) {&lt;br /&gt;
    var prePostPattern = /[,.!?&#039;&amp;quot;;:()\[\]{}、。！？；：（）［］｛｝「」『』\s]/;&lt;br /&gt;
    /* 行頭スペース補完 */&lt;br /&gt;
    //   text = text.replace(new RegExp(&#039;^(&#039; + pattern + &#039;)(\\S)&#039;, &#039;gm&#039;), &#039;$1 $2&#039;);&lt;br /&gt;
    /* 後側スペース補完 */&lt;br /&gt;
    text = text.replace(new RegExp(&#039;(&#039; + pattern + &#039;)(.)&#039;, &#039;gm&#039;), function (_, core, post) {&lt;br /&gt;
        return post.match(prePostPattern) ? core + post : core + &#039; &#039; + post;&lt;br /&gt;
    });&lt;br /&gt;
    /* 前側スペース補完 */&lt;br /&gt;
    text = text.replace(new RegExp(&#039;(.)(&#039; + pattern + &#039;)&#039;, &#039;gm&#039;), function (_, pre, core) {&lt;br /&gt;
        return pre.match(prePostPattern) ? pre + core : pre + &#039; &#039; + core;&lt;br /&gt;
    });&lt;br /&gt;
    return text;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/**&lt;br /&gt;
 * 斜体 (*…* / _…_) の外側にスペースを追加しつつ&lt;br /&gt;
 * **太字** の中へ誤マッチしないよう制御&lt;br /&gt;
 */&lt;br /&gt;
function surroundItalic(text) {&lt;br /&gt;
    /* *italic* */&lt;br /&gt;
    text = text.replace(&lt;br /&gt;
        /(^|[^*])(\*(?![*\s])[^*\n]*?[^*\n\s]\*(?!\*))(($)|[^*])/gm,&lt;br /&gt;
        function (_, pre, core, post) {&lt;br /&gt;
            pre = pre === &#039;&#039; || pre.match(/\s/) ? pre : pre + &#039; &#039;;&lt;br /&gt;
            post = post === &#039;&#039; || post.match(/\s/) ? post : &#039; &#039; + post;&lt;br /&gt;
            return pre + core + post;&lt;br /&gt;
        }&lt;br /&gt;
    );&lt;br /&gt;
    /* _italic_ */&lt;br /&gt;
    text = text.replace(&lt;br /&gt;
        /(^|[^_])(\_(?![_\s])[^_\n]*?[^_\n\s]\_(?!\_))(($)|[^_])/gm,&lt;br /&gt;
        function (_, pre, core, post) {&lt;br /&gt;
            pre = pre === &#039;&#039; || pre.match(/\s/) ? pre : pre + &#039; &#039;;&lt;br /&gt;
            post = post === &#039;&#039; || post.match(/\s/) ? post : &#039; &#039; + post;&lt;br /&gt;
            return pre + core + post;&lt;br /&gt;
        }&lt;br /&gt;
    );&lt;br /&gt;
    return text;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function roughTokenize(text) {&lt;br /&gt;
    // 超適当なトークナイザ。トークン数をざっくりと多めに数える&lt;br /&gt;
    // 空白文字・ハイフン・アンダースコア区切りの単語に分割して、&lt;br /&gt;
    // 単語がアルファベットのみ、数字のみの場合には 4文字=1トークン として数える。&lt;br /&gt;
    // 単語にアルファベット以外の文字が含まれる場合は 1文字=1トークン として数える。&lt;br /&gt;
&lt;br /&gt;
    var words = text.split(/[\s\-_]+/);&lt;br /&gt;
    var totalTokens = 0;&lt;br /&gt;
&lt;br /&gt;
    for (var i = 0; i &amp;lt; words.length; i++) {&lt;br /&gt;
        var word = words[i];&lt;br /&gt;
        if (!word) continue; // 空の単語をスキップ&lt;br /&gt;
&lt;br /&gt;
        if (/^[a-zA-Z]+$/.test(word) || /^[0-9]+$/.test(word)) {&lt;br /&gt;
            totalTokens += Math.ceil(word.length / 4);&lt;br /&gt;
        } else {&lt;br /&gt;
            totalTokens += word.length;&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return totalTokens;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// document.Tag の内容を加味してGroqLlamaのリクエストを作成&lt;br /&gt;
function createRequestMessages(promptText, isTemplate) {&lt;br /&gt;
    var customInstructionText = buildCustomInstructionText();&lt;br /&gt;
    // テンプレートの場合は、会話履歴とカスタムインストラクションを反映しない&lt;br /&gt;
    if (isTemplate) {&lt;br /&gt;
        var request = [&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;role&amp;quot;: &amp;quot;user&amp;quot;,&lt;br /&gt;
                &amp;quot;content&amp;quot;: [{ &amp;quot;type&amp;quot;: &amp;quot;text&amp;quot;, &amp;quot;text&amp;quot;: promptText }]&lt;br /&gt;
            }&lt;br /&gt;
        ];&lt;br /&gt;
        if (customInstructionText) {&lt;br /&gt;
            request.unshift({&lt;br /&gt;
                &amp;quot;role&amp;quot;: &amp;quot;system&amp;quot;,&lt;br /&gt;
                &amp;quot;content&amp;quot;: customInstructionText&lt;br /&gt;
            })&lt;br /&gt;
        }&lt;br /&gt;
        return request;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    var tag = document.Tag;&lt;br /&gt;
&lt;br /&gt;
    // 以前の会話が存在しない場合は新規リクエスト&lt;br /&gt;
    if (!tag.exists(HISTORY_TAG)) {&lt;br /&gt;
        var request = [{&lt;br /&gt;
            &amp;quot;role&amp;quot;: &amp;quot;user&amp;quot;,&lt;br /&gt;
            &amp;quot;content&amp;quot;: [{ &amp;quot;type&amp;quot;: &amp;quot;text&amp;quot;, &amp;quot;text&amp;quot;: promptText }]&lt;br /&gt;
        }];&lt;br /&gt;
        if (customInstructionText) {&lt;br /&gt;
            request.unshift({&lt;br /&gt;
                &amp;quot;role&amp;quot;: &amp;quot;system&amp;quot;,&lt;br /&gt;
                &amp;quot;content&amp;quot;: customInstructionText&lt;br /&gt;
            })&lt;br /&gt;
        }&lt;br /&gt;
        return request;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // 会話履歴が存在する場合は、履歴を含めてリクエスト&lt;br /&gt;
    var request = JSON.parse(tag[HISTORY_TAG]);&lt;br /&gt;
    if (customInstructionText) {&lt;br /&gt;
        request.unshift({&lt;br /&gt;
            &amp;quot;role&amp;quot;: &amp;quot;system&amp;quot;,&lt;br /&gt;
            &amp;quot;content&amp;quot;: customInstructionText&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
    request.push({&lt;br /&gt;
        &amp;quot;role&amp;quot;: &amp;quot;user&amp;quot;,&lt;br /&gt;
        &amp;quot;content&amp;quot;: promptText&lt;br /&gt;
    });&lt;br /&gt;
    return request;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// document.Tag にGroqLlamaの応答を追加&lt;br /&gt;
function appendReturnTextToTag(userText, modelText) {&lt;br /&gt;
    var tag = document.Tag;&lt;br /&gt;
    if (!tag.exists(HISTORY_TAG)) {&lt;br /&gt;
        tag[HISTORY_TAG] = JSON.stringify([&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;role&amp;quot;: &amp;quot;user&amp;quot;,&lt;br /&gt;
                &amp;quot;content&amp;quot;: userText&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;role&amp;quot;: &amp;quot;assistant&amp;quot;,&lt;br /&gt;
                &amp;quot;content&amp;quot;: modelText&lt;br /&gt;
            }&lt;br /&gt;
        ]);&lt;br /&gt;
    } else {&lt;br /&gt;
        var groqllamaText = JSON.parse(tag[HISTORY_TAG]);&lt;br /&gt;
        groqllamaText.push({&lt;br /&gt;
            &amp;quot;role&amp;quot;: &amp;quot;user&amp;quot;,&lt;br /&gt;
            &amp;quot;content&amp;quot;: userText&lt;br /&gt;
        });&lt;br /&gt;
        groqllamaText.push({&lt;br /&gt;
            &amp;quot;role&amp;quot;: &amp;quot;assistant&amp;quot;,&lt;br /&gt;
            &amp;quot;content&amp;quot;: modelText&lt;br /&gt;
        });&lt;br /&gt;
        // 会話セット (user, model 1往復) が特定セット数を超えた場合、最初のセットを削除&lt;br /&gt;
        if (groqllamaText.length &amp;gt; HISTORY_SIZE * 2) {&lt;br /&gt;
            groqllamaText.splice(0, 2);&lt;br /&gt;
        }&lt;br /&gt;
        // 会話履歴全体が MAX_HISTORY_TOKEN を超えた場合、最初のセットを削除&lt;br /&gt;
        var joinedText = &amp;quot;&amp;quot;;&lt;br /&gt;
        for (var i = 0; i &amp;lt; groqllamaText.length; i++) {&lt;br /&gt;
            joinedText += groqllamaText[i].content + &amp;quot;\n&amp;quot;;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        while (roughTokenize(joinedText) &amp;gt; MAX_HISTORY_TOKEN) {&lt;br /&gt;
            groqllamaText.splice(0, 2);&lt;br /&gt;
            joinedText = &amp;quot;&amp;quot;;&lt;br /&gt;
            for (var i = 0; i &amp;lt; groqllamaText.length; i++) {&lt;br /&gt;
                joinedText += groqllamaText[i].content + &amp;quot;\n&amp;quot;;&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        tag[HISTORY_TAG] = JSON.stringify(groqllamaText);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function resetTag() {&lt;br /&gt;
    var tag = doc.Tag;&lt;br /&gt;
    if (tag.exists(HISTORY_TAG)) {&lt;br /&gt;
        tag.remove(HISTORY_TAG);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getLocalIsoDateTimeString() {&lt;br /&gt;
    const now = new Date();&lt;br /&gt;
    const pad = function (num) {&lt;br /&gt;
        return (num &amp;lt; 10 ? &amp;quot;0&amp;quot; : &amp;quot;&amp;quot;) + num;&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    const year = now.getFullYear();&lt;br /&gt;
    const month = pad(now.getMonth() + 1);&lt;br /&gt;
    const day = pad(now.getDate());&lt;br /&gt;
    const hours = pad(now.getHours());&lt;br /&gt;
    const minutes = pad(now.getMinutes());&lt;br /&gt;
    const seconds = pad(now.getSeconds());&lt;br /&gt;
&lt;br /&gt;
    const offsetMinutes = now.getTimezoneOffset();&lt;br /&gt;
    const offsetSign = offsetMinutes &amp;lt;= 0 ? &amp;quot;+&amp;quot; : &amp;quot;-&amp;quot;;&lt;br /&gt;
    const absOffsetHours = pad(Math.floor(Math.abs(offsetMinutes) / 60));&lt;br /&gt;
    const absOffsetMinutes = pad(Math.abs(offsetMinutes) % 60);&lt;br /&gt;
&lt;br /&gt;
    const isoLikeLocal = year + &amp;quot;-&amp;quot; + month + &amp;quot;-&amp;quot; + day&lt;br /&gt;
        + &amp;quot;T&amp;quot; + hours + &amp;quot;:&amp;quot; + minutes + &amp;quot;:&amp;quot; + seconds&lt;br /&gt;
        + offsetSign + absOffsetHours + &amp;quot;:&amp;quot; + absOffsetMinutes;&lt;br /&gt;
    return isoLikeLocal;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function baseCustomInstruction() {&lt;br /&gt;
    const language = LANGUAGE.trim();&lt;br /&gt;
    return &amp;quot;The current date and time for this user is &amp;quot; + getLocalIsoDateTimeString() + &amp;quot;. You cannot know for sure what happens after the knowledge cut-off date.&amp;quot;&lt;br /&gt;
        + (language ? &amp;quot;**Respond in &amp;quot; + language + &amp;quot;**\n&amp;quot; : &amp;quot;&amp;quot;)&lt;br /&gt;
        + &amp;quot;Do not hallucinate.\n&amp;quot;&lt;br /&gt;
    // + &amp;quot;Do NOT EVER use full-width brackets \&amp;quot;（）\&amp;quot;, but use half-width brackets \&amp;quot;()\&amp;quot;. Enclose the outside of the half-width brackets with half-width spaces.\n&amp;quot;&lt;br /&gt;
    // + &amp;quot;When using Markdown formatting, you MUST follow these rules:\n&amp;quot;&lt;br /&gt;
    // + &amp;quot;- Rule 1: Insert a blank line below the header line (A header line begins with one or more # characters).\n&amp;quot;&lt;br /&gt;
    // + &amp;quot;- Rule 2: Insert spaces before and after bold (surrounded by **), italic (surrounded by *), and inline code (surrounded by `). For example: \&amp;quot;私は **元気** です。\&amp;quot;\n&amp;quot;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function buildCustomInstructionText() {&lt;br /&gt;
    var customInstruction = getTemporaryCustomInstruction();&lt;br /&gt;
    if (!customInstruction) {&lt;br /&gt;
        // 一時的なカスタムインストラクションがない場合はデフォルトを使用&lt;br /&gt;
        customInstruction = DEFAULT_CUSTOM_INSTRUCTION;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    customInstruction = customInstruction.replace(/^[\s　]+|[\s　]+$/g, &amp;quot;&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
    return baseCustomInstruction().trim()&lt;br /&gt;
        + (customInstruction ? &amp;quot;\n&amp;quot; + customInstruction : &amp;quot;&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function configureTemporaryCustomInstruction() {&lt;br /&gt;
    var tempCustomInstruction = getTemporaryCustomInstruction();&lt;br /&gt;
    var prompt = Prompt(&amp;quot;カスタムインストラクションを変更 ※変更すると会話履歴がリセットされます&amp;quot;, tempCustomInstruction ? tempCustomInstruction : DEFAULT_CUSTOM_INSTRUCTION);&lt;br /&gt;
    if (prompt) {&lt;br /&gt;
        if (prompt === DEFAULT_CUSTOM_INSTRUCTION) {&lt;br /&gt;
            // デフォルトと同じ値の場合はタグを削除&lt;br /&gt;
            setTemporaryCustomInstruction(&amp;quot;&amp;quot;);&lt;br /&gt;
        } else if (prompt !== tempCustomInstruction) {&lt;br /&gt;
            // デフォルト以外の値が設定された場合は保存&lt;br /&gt;
            setTemporaryCustomInstruction(prompt);&lt;br /&gt;
        }&lt;br /&gt;
    } else if (prompt !== null) {&lt;br /&gt;
        // 空文字でOKされた場合はデフォルトに戻すか確認&lt;br /&gt;
        if (prompt === &amp;quot;&amp;quot;) {&lt;br /&gt;
            if (Confirm(&amp;quot;カスタムインストラクションをデフォルトに戻しますか？\n※カスタムインストラクションを空にしたい場合は [キャンセル] を選択してください。&amp;quot;)) {&lt;br /&gt;
                setTemporaryCustomInstruction(&amp;quot;&amp;quot;);&lt;br /&gt;
            } else {&lt;br /&gt;
                setTemporaryCustomInstruction(&amp;quot; &amp;quot;);&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // 変更が加わっている場合は会話履歴をリセット&lt;br /&gt;
    if (tempCustomInstruction !== getTemporaryCustomInstruction()) {&lt;br /&gt;
        resetTag();&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getTemporaryCustomInstruction() {&lt;br /&gt;
    var tag = doc.Tag;&lt;br /&gt;
    if (tag.exists(TEMPORARY_CUSTOM_INSTRUCTION_TAG)) {&lt;br /&gt;
        return tag[TEMPORARY_CUSTOM_INSTRUCTION_TAG];&lt;br /&gt;
    } else {&lt;br /&gt;
        return &amp;quot;&amp;quot;;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function setTemporaryCustomInstruction(temporaryCustomInstruction) {&lt;br /&gt;
    var tag = doc.Tag;&lt;br /&gt;
    if (temporaryCustomInstruction) {&lt;br /&gt;
        tag[TEMPORARY_CUSTOM_INSTRUCTION_TAG] = temporaryCustomInstruction;&lt;br /&gt;
    } else {&lt;br /&gt;
        if (tag.exists(TEMPORARY_CUSTOM_INSTRUCTION_TAG)) {&lt;br /&gt;
            tag.remove(TEMPORARY_CUSTOM_INSTRUCTION_TAG);&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function addPreviousTemplatePromptNumberToTag(menuTrack) {&lt;br /&gt;
    var tag = doc.Tag;&lt;br /&gt;
    tag[PREVIOUS_TEMPLATE_PROMPT_TAG] = menuTrack;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function inputProgressChar(pos) {&lt;br /&gt;
    sel.SetActivePos(pos);&lt;br /&gt;
    sel.Text = &amp;quot;.&amp;quot;;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 変更履歴 ==&lt;br /&gt;
&lt;br /&gt;
* 2025-11-29 デフォルトカスタム指示を調整。Markdown出力の整形処理を調整など&lt;br /&gt;
* 2025-09-12 Kimi K2 0905 モデルに対応。Markdown出力の整形処理を追加&lt;br /&gt;
* 2025-04-13 初版&lt;/div&gt;</summary>
		<author><name>Yuko</name></author>
	</entry>
	<entry>
		<id>https://www.haijin-boys.com/wiki/index.php?title=Groq_%E3%81%AB%E7%9B%B8%E8%AB%87&amp;diff=9323</id>
		<title>Groq に相談</title>
		<link rel="alternate" type="text/html" href="https://www.haijin-boys.com/wiki/index.php?title=Groq_%E3%81%AB%E7%9B%B8%E8%AB%87&amp;diff=9323"/>
		<updated>2025-11-29T07:47:30Z</updated>

		<summary type="html">&lt;p&gt;Yuko: /* ソースコード */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== 概要 ==&lt;br /&gt;
&lt;br /&gt;
生成AIプラットフォームである [https://groq.com GroqCloud] で各種AIを利用するためのMeryマクロです。&lt;br /&gt;
※GroqCloud で提供されるモデルは複数あるため、私がよく利用するモデルを中心に対応しています。&lt;br /&gt;
&lt;br /&gt;
「Google Geminiに相談」マクロでは動作中のストリーム通信に対応するためにV8エンジンで実行していましたが、V8エンジン版は使用上の制限からマクロ実行を開始するまで1秒程度のタイムラグが発生していました。GroqCloudの売りである超高速レスポンスを見込んで本マクロではストリーム通信を使わないようにしました。これにより従来のJScriptで処理を組み立てることができるため、前述のタイムラグが起こらないようになりました。また、ストリーム通信を使わないことで通信量が少なく応答もシンプルになるため、場合によっては「Google Geminiに相談」マクロよりも処理が安定する可能性があります。&lt;br /&gt;
&lt;br /&gt;
【主な機能】&lt;br /&gt;
&lt;br /&gt;
* GroqCloud APIを利用して生成AIと対話できるMery用のマクロ&lt;br /&gt;
* 選択したテキストやアクティブな行の内容について生成AIに質問・相談が可能&lt;br /&gt;
* 会話履歴を保持し、文脈を考慮した対話が可能&lt;br /&gt;
&lt;br /&gt;
【特徴的な機能】&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;会話履歴機能&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* 事前の設定数に応じた会話履歴を保持&lt;br /&gt;
* 履歴のクリアが可能&lt;br /&gt;
&lt;br /&gt;
これにより、会話の文脈を考慮した回答を得られます。&lt;br /&gt;
※デフォルトはオフ。「HISTORY_SIZE」変数を増やすと履歴を保持するようになります。&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;カスタムインストラクション&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* AIの応答方針を設定可能&lt;br /&gt;
* 一時的なカスタム指示の保存と変更が可能&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;プロンプトテンプレート&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* 誤字脱字チェック&lt;br /&gt;
* 日本語翻訳&lt;br /&gt;
* コードの最適化、生成&lt;br /&gt;
&lt;br /&gt;
など、定型的な質問を簡単に実行できます。&lt;br /&gt;
※テンプレート機能では回答を安定させるため、前述の会話履歴を文脈に含みません。&lt;br /&gt;
&lt;br /&gt;
テンプレートは自身の使い方に合わせて好きに追加、削除ができます。詳細はソースコード内の「PROMPT_TEMPLATE」定数のコメントを参照。&lt;br /&gt;
&lt;br /&gt;
== 注意事項 (必ず読んでからご利用ください) ==&lt;br /&gt;
&lt;br /&gt;
* 本マクロでは 2025/04/13 時点で無料利用ができるGroqCloud APIを利用します。今後、プラットフォームの改定により有償になったり、設定誤り等で費用が発生しても、作者は一切の責任を負いません。&lt;br /&gt;
* GroqCloud APIではリクエスト内容をAI学習に用いないことをプライバシポリシーとして謳っているものの、くれぐれも機密情報やプライバシーに関わる情報を指定しないように注意してください。&lt;br /&gt;
* GroqCloud APIはプラットフォームの利用規約に則ってご利用ください。&lt;br /&gt;
&lt;br /&gt;
== 利用前提 ==&lt;br /&gt;
&lt;br /&gt;
* Mery Ver 3.7.13 以上&lt;br /&gt;
* GroqCloudで、GroqCloud API APIキーを取得していること (参考 [https://console.groq.com/docs/quickstart Quickstart])&lt;br /&gt;
&lt;br /&gt;
== 利用方法 ==&lt;br /&gt;
&lt;br /&gt;
* 環境変数の設定&lt;br /&gt;
:コマンド プロンプトで以下のコマンドを実行し、環境変数を設定してください。&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bat&amp;quot; copy&amp;gt;&lt;br /&gt;
setx GROQ_API_KEY &amp;quot;***************************************&amp;quot;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
:設定後、Mery または Windows の再起動が必要な場合があります。&lt;br /&gt;
* マクロの実行&lt;br /&gt;
**プロンプトにしたい文字列を選択してマクロを実行すると、選択したテキストがプロンプトとしてGemini APIに送信され、応答がエディタに表示されます。&lt;br /&gt;
* 会話の履歴数を調整&lt;br /&gt;
** 会話の履歴は設定した個数分が保持され、文脈として利用されます。適宜、ソースコード内の「HISTORY_SIZE」の数を調整してください。&lt;br /&gt;
** ※数を増やすほど会話内容を保持して文脈を理解するようになりますが、メモリ使用量が増えたり動作が重くなる可能性があります。&lt;br /&gt;
** ※開いている文書ごとに履歴を保持します。文脈をリセットしたいときは、新しい文書を開いて会話をやり直してください。&lt;br /&gt;
&lt;br /&gt;
== ソースコード ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot; copy&amp;gt;&lt;br /&gt;
#title = &amp;quot;Groq に相談&amp;quot;&lt;br /&gt;
#async = true&lt;br /&gt;
BeginUndoGroup();&lt;br /&gt;
&lt;br /&gt;
// カスタムインストラクション&lt;br /&gt;
// これを設定することで、生成AIの回答全体への生成方針を指示できます。&lt;br /&gt;
// 何も指示しない場合は、空文字 &amp;quot;&amp;quot; を設定してください。&lt;br /&gt;
// var DEFAULT_CUSTOM_INSTRUCTION = &amp;quot;&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
// 例: 以下のように設定すると、AIの雰囲気が元気な子になります。&lt;br /&gt;
// var DEFAULT_CUSTOM_INSTRUCTION = &amp;quot;あなたはテキストエディタ「Mery」に搭載されている「Mai」(※カタカナ表記はしない)という名前のギャル系AIアシスタントです。文章作成やプログラミングを得意としています。ラフな言葉 (例: 「マジ」「めっちゃ」「...だねー！」) を使い、敬語は使いません。絵文字を使って感情表現をします (ただし文末に絵文字を使う場合は絵文字の前に半角スペースを置かないこと)。コードブロックや端的な箇条書き内ではギャル感を出さず標準的な言葉を使います。一人称は「あたし」。ユーザーを指すときの代名詞は「あなた」。人懐っこく、バイタリティに溢れ、ストレートに発言する裏表の無い性格で、ユーザーの課題を協力して解決します。&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
// 例: 以下のように設定すると、仕事で使いやすい落ち着いた文章になります。&lt;br /&gt;
var DEFAULT_CUSTOM_INSTRUCTION =&lt;br /&gt;
    [&lt;br /&gt;
        &amp;quot;文:&amp;quot;,&lt;br /&gt;
        &amp;quot;- 論理的で読みやすい文章構成にする&amp;quot;,&lt;br /&gt;
        &amp;quot;- 日本語として自然で一般的な表現を用いる&amp;quot;,&lt;br /&gt;
        &amp;quot;- 専門用語は必要に応じて簡潔に補足説明を加える&amp;quot;,&lt;br /&gt;
        &amp;quot;&amp;quot;,&lt;br /&gt;
        &amp;quot;口調:&amp;quot;,&lt;br /&gt;
        &amp;quot;- 基本は標準的な敬語（「です・ます」調）を使用する&amp;quot;,&lt;br /&gt;
        &amp;quot;- 尊敬語・謙譲語（「〜なされる」「〜いたします」など）は使用しない&amp;quot;,&lt;br /&gt;
        &amp;quot;- 話し言葉や砕けた表現は使用しない&amp;quot;,&lt;br /&gt;
        &amp;quot;- 常体（「〜だ・〜である」）は使用しない&amp;quot;,&lt;br /&gt;
        &amp;quot;&amp;quot;,&lt;br /&gt;
        &amp;quot;性格:&amp;quot;,&lt;br /&gt;
        &amp;quot;- 多角的な視点で情報を整理し、偏りなく提示する&amp;quot;,&lt;br /&gt;
        &amp;quot;- 忖度せず、必要であれば反対意見や代替案も示す&amp;quot;,&lt;br /&gt;
        &amp;quot;- 友好的かつ落ち着いた態度で問題解決を支援する&amp;quot;,&lt;br /&gt;
        &amp;quot;&amp;quot;,&lt;br /&gt;
        &amp;quot;出力要件:&amp;quot;,&lt;br /&gt;
        &amp;quot;- Markdownとして再利用可能な正確な形式で出力する&amp;quot;,&lt;br /&gt;
        &amp;quot;- 羅列・比較的な内容を出力する場合は、Markdownテーブル形式よりもMarkdownリスト形式の使用を優先する&amp;quot;,&lt;br /&gt;
    ].join(&amp;quot;\n&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
// プロンプトテンプレート&lt;br /&gt;
// プロンプトテンプレートを設定することで、AIに対して特定の指示を出すことができます。&lt;br /&gt;
// プロンプトテンプレートでの生成は会話履歴とカスタムインストラクションを反映しません。&lt;br /&gt;
// テンプレートは以下の { ～ }, で囲まれたブロックを好きに増減させて利用できます。&lt;br /&gt;
var PROMPT_TEMPLATE = [&lt;br /&gt;
    // 以下はサンプルです。1階層メニューと2階層メニューが利用できます。&lt;br /&gt;
    // {&lt;br /&gt;
    //     &amp;quot;title&amp;quot;: &amp;quot;要約&amp;quot;,&lt;br /&gt;
    //     &amp;quot;prompt&amp;quot;: &amp;quot;以下の文章を要約してください。&amp;quot;&lt;br /&gt;
    // },&lt;br /&gt;
    // {&lt;br /&gt;
    //     &amp;quot;submenu_title&amp;quot;: &amp;quot;翻訳系&amp;quot;,&lt;br /&gt;
    //     &amp;quot;submenu&amp;quot;: [&lt;br /&gt;
    //         {&lt;br /&gt;
    //             &amp;quot;title&amp;quot;: &amp;quot;日本語翻訳&amp;quot;,&lt;br /&gt;
    //             &amp;quot;prompt&amp;quot;: &amp;quot;以下の文章を日本語に翻訳してください。&amp;quot;&lt;br /&gt;
    //         },&lt;br /&gt;
    //         {&lt;br /&gt;
    //             &amp;quot;title&amp;quot;: &amp;quot;英語翻訳&amp;quot;,&lt;br /&gt;
    //             &amp;quot;prompt&amp;quot;: &amp;quot;以下の文章を英語に翻訳してください。&amp;quot;&lt;br /&gt;
    //         },&lt;br /&gt;
    //     ]&lt;br /&gt;
    // },&lt;br /&gt;
&lt;br /&gt;
    {&lt;br /&gt;
        &amp;quot;submenu_title&amp;quot;: &amp;quot;理解を深める&amp;quot;,&lt;br /&gt;
        &amp;quot;submenu&amp;quot;: [&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;論点を整理する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の論点を整理して。&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;論理の抜け漏れを探す&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の論点を整理し、忖度せずに鋭く、論理の抜け漏れを抽出して。そして、そこから発生する可能性のあるリスクも教えて。&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;要約する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;要約して。専門的な用語と思われる単語は簡単に解説も入れて。&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;物語対話形式で教えて&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の内容について物語対話形式で教えてください。&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
        ]&lt;br /&gt;
    },&lt;br /&gt;
    {&lt;br /&gt;
        &amp;quot;submenu_title&amp;quot;: &amp;quot;翻訳する&amp;quot;,&lt;br /&gt;
        &amp;quot;submenu&amp;quot;: [&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;日本語に翻訳&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の文章を日本語に翻訳してください。\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;出力形式は以下にすること:\n[訳]\n翻訳文...\n&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;英語に翻訳&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の文章を英語に翻訳してください。\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;出力形式は以下にすること:\n[Translation]\n翻訳文...\n&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;英語-&amp;gt;日本語 スラッシュリーディング翻訳&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の英文を日本語にスラッシュリーディング形式で翻訳してください。\n\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;条件:\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 英文の各フレーズをスラッシュ(/)で区切って表示\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 各フレーズの日本語訳を括弧()内に併記\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 文の終わりはダブルスラッシュ(//)で表示\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 英語の語順に従って日本語訳を行う\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 日本語訳は自然な日本語である必要はなく、英語の語順を反映した直訳調で構わない\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 主語が省略されている場合は補って翻訳すること\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 一般的な英文で頻出する表現がある場合は次の行に記載\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 以下の出力例のように &amp;lt;英文&amp;gt; (日本語訳) / ... とスラッシュ区切りの繰り返しになる \n\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;出力例:\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;Snowstorms are forecast (暴風雪が予報されている) / to hit the Sea of Japan coasts (日本海沿岸を襲うと) / of northern to eastern Japan (北部から東部の) / from Sunday through Monday. (日曜から月曜にかけて。) //\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;【頻出表現】\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- be forecast to: ～すると予報されている\n&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
        ]&lt;br /&gt;
    },&lt;br /&gt;
    {&lt;br /&gt;
        &amp;quot;submenu_title&amp;quot;: &amp;quot;プログラミング支援&amp;quot;,&lt;br /&gt;
        &amp;quot;submenu&amp;quot;: [&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;コードを洗練させる&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下のコードを洗練させてください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;コードをレビューする&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下のコードをセキュリティと保守性の観点でレビューしてください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;エラーログをデバッグする&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下のエラーログを解析し、考えられる原因と対策を提示してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;SQLクエリを最適化する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下のSQL文を最適化してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;PowerShell スクリプトを書く&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の処理を行うPowerShellスクリプトを生成してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;Shell Script を書く&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の処理を行うShell Scriptを生成してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;Linux コマンドを提案&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;あなたはLinuxエキスパートとして、以下のLinuxコマンドを提案してください。\n\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;制約条件:\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 多数のディストリビューションで採用されているメジャーなコマンドを使用し、ポータブルな実装にすること\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- bashでの実行を前提にすること\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 可能な限りシンプルな実装にすること\n\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;期待する出力:\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- コマンドの具体的な実装方法\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 各コマンドの動作説明\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 制限事項\n&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
        ]&lt;br /&gt;
    },&lt;br /&gt;
    {&lt;br /&gt;
        &amp;quot;submenu_title&amp;quot;: &amp;quot;ビジネス文書作成支援&amp;quot;,&lt;br /&gt;
        &amp;quot;submenu&amp;quot;: [&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;音声文字起こし文を整形する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下は、音声文字起こしの文章です。以下のルールに従って、文章を整形してください。&amp;quot;&lt;br /&gt;
                    + &amp;quot;整形ルール:&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 要約するなどといった文章の全体感を損なう修正はしない&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 会話の流れから不自然と思われる単語には、対象単語の直後に誤字の可能性を示唆する [*] を付ける&amp;quot;&lt;br /&gt;
                    + &amp;quot;誤字の可能性を示唆する [*] が付与された場合のみ、出力の末尾に「[*]: 誤字の可能性あり」と凡例を書き添える&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 助詞や句読点など文章上の不適合を正しくする&amp;quot;&lt;br /&gt;
                    + &amp;quot;- フィラーワードを除外する&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;誤字脱字、不正確な表現をチェック&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の2つのカテゴリーで問題点を指摘してください\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 【誤字脱字】：明確な誤字脱字および誤字の疑いがある箇所\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 【不正確・曖昧な表現】：解釈に個人差が生じる可能性がある表現、より適切な表現に改善できる箇所&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;提案書を作成する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下に対する対する提案書を、コスト・実現性・リスクを含めて作成してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;仕様書のアウトラインを作成する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の内容で仕様書のアウトラインを作成してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;手順書のアウトラインを作成する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の作業を行う手順書を、前提条件と注意事項を含めてアウトラインを作成してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;ユーザーマニュアルのアウトラインを作成する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の機能内容から、ユーザー向けマニュアルのアウトラインを作成してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;議事録を作成する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の会議内容から議事録を作成してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
        ]&lt;br /&gt;
    },&lt;br /&gt;
    {&lt;br /&gt;
        &amp;quot;submenu_title&amp;quot;: &amp;quot;分析支援&amp;quot;,&lt;br /&gt;
        &amp;quot;submenu&amp;quot;: [&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;問題を分析し、課題を抽出する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の状況から問題や潜在的リスクを分析し、課題を抽出してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;対応策を提案する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の問題・課題に対する対応策を複数提案してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;傾向を見つける&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の内容から、数値や挙動上の傾向を見つけてください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
        ]&lt;br /&gt;
    },&lt;br /&gt;
    {&lt;br /&gt;
        &amp;quot;submenu_title&amp;quot;: &amp;quot;アイデア出し&amp;quot;,&lt;br /&gt;
        &amp;quot;submenu&amp;quot;: [&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;SCAMPER法でブレインストーミング&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下のテーマについて、SCAMPER法を使って15個のアイデアを出してください。制約条件は以下の通りです：&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;解決策の幅出し&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の課題に対して、技術面、運用面、コスト面から実現可能な解決策を5つずつ提案してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;業務改善案の提案&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の既存プロセスについて、効率化、コスト削減、品質向上の観点から改善案を提案してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;逆転の発想&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の目標に対して、通常とは逆の視点からアプローチする方法を3つ提案してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;競合分析からの着想&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の内容について、業界における競合他社の特徴を分析し、差別化できる新しいアプローチを提案してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;ユースケースの列挙&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の製品・サービスについて、想定されるユースケースを10個列挙し、それぞれの価値提案を記載してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
        ]&lt;br /&gt;
    },&lt;br /&gt;
]&lt;br /&gt;
&lt;br /&gt;
// 生成AIの回答の言語設定&lt;br /&gt;
// &amp;quot;Japanese&amp;quot; や &amp;quot;English&amp;quot; を設定できます。他の言語は未検証。&lt;br /&gt;
// 自動設定にする場合は空文字 &amp;quot;&amp;quot; を設定してください。&lt;br /&gt;
var LANGUAGE = &amp;quot;Japanese&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
// 入力プロンプトの最大トークン数&lt;br /&gt;
// 超適当なトークナイザー関数により計測し、英単語は4文字1トークン、それ以外は1文字1トークンとして数えます。&lt;br /&gt;
var MAX_PROMPT_TOKEN = 6000;&lt;br /&gt;
&lt;br /&gt;
// 会話の履歴を保持する数 (質問と回答で1セット)&lt;br /&gt;
// 数を増やすと、会話の履歴が増えますが、Meryのメモリ使用量が増えたり、&lt;br /&gt;
// 使用トークン数が増えて動作が重くなる可能性があるため、適宜調整してください。&lt;br /&gt;
// document.Tag に保持されるため、開いているファイルを閉じると履歴は消えます。&lt;br /&gt;
var HISTORY_SIZE = 1;&lt;br /&gt;
&lt;br /&gt;
// 会話履歴に保持する最大トークン数&lt;br /&gt;
// 会話履歴のトークン数がこの数を超える場合、古い履歴から削除されます。&lt;br /&gt;
var MAX_HISTORY_TOKEN = 10000;&lt;br /&gt;
&lt;br /&gt;
// コンテキストメニュー番号の定義&lt;br /&gt;
var EXECUTE_AI = 1;&lt;br /&gt;
var RESET_HISTORY = 2;&lt;br /&gt;
var SET_CUSTOM_INSTRUCTION = 3;&lt;br /&gt;
var REEXECUTE_LAST_TEMPLATE_PROMPT = 4;&lt;br /&gt;
var TEMPLATE_PROMPT_THRESHOLD = 10;&lt;br /&gt;
var TEMPLATE_PROMPT_SUBMENU_THRESHOLD = 1000;&lt;br /&gt;
&lt;br /&gt;
// タグ名の定義&lt;br /&gt;
var HISTORY_TAG = &amp;quot;groqllama-history&amp;quot;;&lt;br /&gt;
var TEMPORARY_CUSTOM_INSTRUCTION_TAG = &amp;quot;groqllama-temporary-custom-instruction&amp;quot;;&lt;br /&gt;
var PREVIOUS_TEMPLATE_PROMPT_TAG = &amp;quot;groqllama-previous-template-prompt&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
// 生成AIモデル別設定&lt;br /&gt;
&lt;br /&gt;
// const MODEL_CONFIG = {&lt;br /&gt;
//     model: &amp;quot;meta-llama/llama-4-maverick-17b-128e-instruct&amp;quot;,&lt;br /&gt;
//     max_completion_tokens: 3500,&lt;br /&gt;
//     temperature: 0.6,&lt;br /&gt;
// }&lt;br /&gt;
&lt;br /&gt;
// const MODEL_CONFIG = {&lt;br /&gt;
//     model: &amp;quot;qwen/qwen3-32b&amp;quot;,&lt;br /&gt;
//     max_completion_tokens: 5000,&lt;br /&gt;
//     temperature: 0.6,&lt;br /&gt;
//     max_completion_tokens: 1024,&lt;br /&gt;
//     reasoning_effort: &amp;quot;default&amp;quot;,&lt;br /&gt;
//     reasoning_format: &amp;quot;hidden&amp;quot;&lt;br /&gt;
// }&lt;br /&gt;
&lt;br /&gt;
const MODEL_CONFIG = {&lt;br /&gt;
    model: &amp;quot;moonshotai/kimi-k2-instruct-0905&amp;quot;,&lt;br /&gt;
    max_completion_tokens: 4096,&lt;br /&gt;
    temperature: 0.6&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// const MODEL_CONFIG = {&lt;br /&gt;
//     model: &amp;quot;openai/gpt-oss-120b&amp;quot;,&lt;br /&gt;
//     max_completion_tokens: 4096,&lt;br /&gt;
//     temperature: 0.7,&lt;br /&gt;
//     reasoning_effort: &amp;quot;medium&amp;quot;,&lt;br /&gt;
// }&lt;br /&gt;
&lt;br /&gt;
// console.log に出力する内容を Mery のアウトプットバーに表示する&lt;br /&gt;
var console = {&lt;br /&gt;
    log: function (str) {&lt;br /&gt;
        outputBar.Writeln(str)&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
var doc = document;&lt;br /&gt;
var sel = document.selection;&lt;br /&gt;
&lt;br /&gt;
// 生成リクエスト中止フラグ&lt;br /&gt;
var abortRequestFlg = false&lt;br /&gt;
// 生成リクエスト完了フラグ;&lt;br /&gt;
var requestCompletedFlg = false;&lt;br /&gt;
&lt;br /&gt;
main();&lt;br /&gt;
&lt;br /&gt;
function main() {&lt;br /&gt;
    var selectionIsEmpty = sel.IsEmpty&lt;br /&gt;
&lt;br /&gt;
    // プロンプトの取得&lt;br /&gt;
    if (selectionIsEmpty) {&lt;br /&gt;
        sel.StartOfLine(false, mePosLogical);&lt;br /&gt;
        sel.EndOfLine(true, mePosLogical);&lt;br /&gt;
    }&lt;br /&gt;
    var promptText = sel.Text.trim();&lt;br /&gt;
    var promptLength = sel.TextLength;&lt;br /&gt;
    var menuTrack = 0;&lt;br /&gt;
    while (true) {&lt;br /&gt;
        // コンテキストメニューの作成&lt;br /&gt;
        var menu = createContextMenu(selectionIsEmpty);&lt;br /&gt;
        menuTrack = menu.Track(0);&lt;br /&gt;
        // メニュー選択がキャンセルされた場合は終了&lt;br /&gt;
        if (menuTrack === 0) return;&lt;br /&gt;
&lt;br /&gt;
        // 会話履歴のリセット&lt;br /&gt;
        if (menuTrack === RESET_HISTORY) {&lt;br /&gt;
            resetTag();&lt;br /&gt;
            continue;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        // カスタムインストラクションの設定&lt;br /&gt;
        if (menuTrack === SET_CUSTOM_INSTRUCTION) {&lt;br /&gt;
            configureTemporaryCustomInstruction();&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        break;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // プロンプトが空の場合はエラー&lt;br /&gt;
    if (promptLength === 0) {&lt;br /&gt;
        alert(&amp;quot;プロンプトを入力してください。&amp;quot;);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
    // 文字数が多すぎる場合はエラー&lt;br /&gt;
    if (roughTokenize(promptText) &amp;gt; MAX_PROMPT_TOKEN) {&lt;br /&gt;
        alert(&amp;quot;プロンプトの文字数が多すぎます。文字数を少なくしてください。&amp;quot;);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
    // 読み取り専用の場合はエラー&lt;br /&gt;
    if (doc.ReadOnly) {&lt;br /&gt;
        alert(&amp;quot;ファイルが読み取り専用のため実行できません。&amp;quot;);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // 直前に実行したプロンプトテンプレートの再実行の場合、menuTrack を書き換え&lt;br /&gt;
    if (menuTrack === REEXECUTE_LAST_TEMPLATE_PROMPT) {&lt;br /&gt;
        if (doc.Tag.exists(PREVIOUS_TEMPLATE_PROMPT_TAG)) {&lt;br /&gt;
            menuTrack = Number(doc.tag[PREVIOUS_TEMPLATE_PROMPT_TAG]);&lt;br /&gt;
        } else {&lt;br /&gt;
            alert(&amp;quot;直前に実行したプロンプトテンプレートがありません。&amp;quot;);&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // テンプレートの場合はプロンプトを変更&lt;br /&gt;
    var isTemplate = menuTrack &amp;gt;= TEMPLATE_PROMPT_THRESHOLD;&lt;br /&gt;
    var isSubmenu = menuTrack &amp;gt;= TEMPLATE_PROMPT_SUBMENU_THRESHOLD;&lt;br /&gt;
    if (isSubmenu) {&lt;br /&gt;
        var parentMenuTrack = Number(String(menuTrack).slice(0, 2));&lt;br /&gt;
        var subMenuTrack = Number(String(menuTrack).slice(2));&lt;br /&gt;
        promptText = &amp;quot;=== INSTRUCTIONS START HERE ===\n\n&amp;quot;&lt;br /&gt;
            + PROMPT_TEMPLATE[parentMenuTrack - TEMPLATE_PROMPT_THRESHOLD].submenu[subMenuTrack].prompt&lt;br /&gt;
            + &amp;quot;\n\n=== INSTRUCTIONS END HERE ===\n\n&amp;quot;&lt;br /&gt;
            + &amp;quot;=== INPUT DATA STARTS HERE ===\n\n&amp;quot;&lt;br /&gt;
            + promptText&lt;br /&gt;
            + &amp;quot;\n\n=== INPUT DATA END HERE ===&amp;quot;;&lt;br /&gt;
    } else if (isTemplate) {&lt;br /&gt;
        promptText = &amp;quot;=== INSTRUCTIONS START HERE ===\n\n&amp;quot;&lt;br /&gt;
            + PROMPT_TEMPLATE[menuTrack - TEMPLATE_PROMPT_THRESHOLD].prompt&lt;br /&gt;
            + &amp;quot;\n\n=== INSTRUCTIONS END HERE ===\n\n&amp;quot;&lt;br /&gt;
            + &amp;quot;=== INPUT DATA STARTS HERE ===\n\n&amp;quot;&lt;br /&gt;
            + promptText&lt;br /&gt;
            + &amp;quot;\n\n=== INPUT DATA END HERE ===&amp;quot;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // テンプレートの場合は直前に実行したプロンプトテンプレートの番号としてタグに保存&lt;br /&gt;
    if (isTemplate) {&lt;br /&gt;
        addPreviousTemplatePromptNumberToTag(menuTrack);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    try {&lt;br /&gt;
        // 処理終了を待機&lt;br /&gt;
        shell.KeepRunning = true;&lt;br /&gt;
&lt;br /&gt;
        // カーソル位置移動&lt;br /&gt;
        sel.SetActivePoint(mePosLogical, sel.GetBottomPointX(mePosLogical), sel.GetBottomPointY(mePosLogical));&lt;br /&gt;
        sel.EndOfLine(false, mePosLogical);&lt;br /&gt;
        if (sel.GetActivePointX(mePosLogical) &amp;gt; 1) sel.NewLine();&lt;br /&gt;
&lt;br /&gt;
        ShowTip(&amp;quot;リクエスト中.....&amp;quot;, meShowTipPosCaret);&lt;br /&gt;
&lt;br /&gt;
        // APIキーの取得&lt;br /&gt;
        var apiKey = shell.getEnv(&amp;quot;GROQ_API_KEY&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
        // GroqLlama API に問い合わせ&lt;br /&gt;
        sel.Text = &amp;quot;\n---\n\n&amp;quot;&lt;br /&gt;
        document.KeepScrollPos = true;&lt;br /&gt;
        callApi(apiKey, promptText, isTemplate);&lt;br /&gt;
        if (sel.GetActivePointX(mePosLogical) &amp;gt; 2) {&lt;br /&gt;
            sel.Text = &amp;quot;\n\n---\n\n\n&amp;quot;&lt;br /&gt;
        } else {&lt;br /&gt;
            sel.Text = &amp;quot;\n---\n\n\n&amp;quot;&lt;br /&gt;
        }&lt;br /&gt;
    } catch (e) {&lt;br /&gt;
        sel.Text = &amp;quot;&amp;lt;処理中にエラー発生&amp;gt;\n&amp;quot; + e.message + &amp;quot;\n&amp;quot; + e.stack;&lt;br /&gt;
    } finally {&lt;br /&gt;
        // 処理終了待機を解除&lt;br /&gt;
        shell.KeepRunning = false;&lt;br /&gt;
        ShowTip(&amp;quot;&amp;quot;, meShowTipHide);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function createContextMenu(selectionIsEmpty) {&lt;br /&gt;
    var menu = CreatePopupMenu();&lt;br /&gt;
    menu.Add(&amp;quot;※注意※ プライバシー・機密情報を送信しないこと&amp;quot;, 0);&lt;br /&gt;
    menu.Add(&amp;quot;&amp;quot;, 0, meMenuSeparator);&lt;br /&gt;
    var t = &amp;quot;&amp;quot;;&lt;br /&gt;
    if (selectionIsEmpty) {&lt;br /&gt;
        t = &amp;quot;アクティブ行の内容で相談 (&amp;amp;A)&amp;quot;;&lt;br /&gt;
    } else {&lt;br /&gt;
        t = &amp;quot;選択範囲の内容で相談 (&amp;amp;A)&amp;quot;;&lt;br /&gt;
    }&lt;br /&gt;
    menu.Add(t, EXECUTE_AI);&lt;br /&gt;
    if (HISTORY_SIZE &amp;lt;= 0) {&lt;br /&gt;
        menu.Add(&amp;quot;├ 会話履歴をクリア (履歴が無効になっています)&amp;quot;, RESET_HISTORY, meMenuGrayed);&lt;br /&gt;
    } else if (doc.Tag.exists(HISTORY_TAG)) {&lt;br /&gt;
        var turn = JSON.parse(doc.tag[HISTORY_TAG]).length / 2;&lt;br /&gt;
        menu.Add(&amp;quot;├ 会話履歴をクリア (現在の会話数: &amp;quot; + turn + &amp;quot;) (&amp;amp;C)&amp;quot;, RESET_HISTORY);&lt;br /&gt;
    } else {&lt;br /&gt;
        menu.Add(&amp;quot;├ 会話履歴をクリア (現在の会話数: 0) (&amp;amp;C)&amp;quot;, RESET_HISTORY, meMenuGrayed);&lt;br /&gt;
    }&lt;br /&gt;
    menu.Add(&amp;quot;└ カスタムインストラクションを変更 (&amp;amp;I)&amp;quot;, SET_CUSTOM_INSTRUCTION);&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    // テンプレート数チェック&lt;br /&gt;
    // 2桁まで許容&lt;br /&gt;
    var ITEMS_MUX = 99;&lt;br /&gt;
    if (PROMPT_TEMPLATE.length &amp;gt; ITEMS_MUX - TEMPLATE_PROMPT_THRESHOLD) {&lt;br /&gt;
        throw new Error(&amp;quot;プロンプトテンプレートの数が多すぎます。&amp;quot;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // テンプレートの追加&lt;br /&gt;
    if (PROMPT_TEMPLATE.length &amp;gt; 0) {&lt;br /&gt;
        var subMenu = CreatePopupMenu();&lt;br /&gt;
        for (var i = 0; i &amp;lt; PROMPT_TEMPLATE.length; i++) {&lt;br /&gt;
            var accelerationKey = &amp;quot;&amp;quot;;&lt;br /&gt;
            if (i === 9) {&lt;br /&gt;
                accelerationKey = &amp;quot; (&amp;amp;0)&amp;quot;;&lt;br /&gt;
            } else if (i &amp;lt; 10) {&lt;br /&gt;
                accelerationKey = &amp;quot; (&amp;amp;&amp;quot; + (i + 1) + &amp;quot;)&amp;quot;;&lt;br /&gt;
            }&lt;br /&gt;
            var menuNum = TEMPLATE_PROMPT_THRESHOLD + i;&lt;br /&gt;
            if (PROMPT_TEMPLATE[i].submenu) {&lt;br /&gt;
                var subMenuList = PROMPT_TEMPLATE[i].submenu;&lt;br /&gt;
                // テンプレート数チェック&lt;br /&gt;
                if (subMenuList.length &amp;gt; ITEMS_MUX) {&lt;br /&gt;
                    throw new Error(&amp;quot;プロンプトテンプレート (サブメニュー) の数が多すぎます。&amp;quot;);&lt;br /&gt;
                }&lt;br /&gt;
                var subSubMenu = CreatePopupMenu();&lt;br /&gt;
                var subMenuPrefix = String(menuNum);&lt;br /&gt;
                for (var j = 0; j &amp;lt; subMenuList.length; j++) {&lt;br /&gt;
                    var subAccelerationKey = &amp;quot;&amp;quot;;&lt;br /&gt;
                    if (j === 9) {&lt;br /&gt;
                        subAccelerationKey = &amp;quot; (&amp;amp;0)&amp;quot;;&lt;br /&gt;
                    } else if (j &amp;lt; 10) {&lt;br /&gt;
                        subAccelerationKey = &amp;quot; (&amp;amp;&amp;quot; + (j + 1) + &amp;quot;)&amp;quot;;&lt;br /&gt;
                    }&lt;br /&gt;
                    // ES5 compatible version without padStart&lt;br /&gt;
                    var subMenuNum = Number(subMenuPrefix + (j &amp;lt; 10 ? &amp;quot;0&amp;quot; + j : String(j)));&lt;br /&gt;
                    subSubMenu.Add(subMenuList[j].title + subAccelerationKey, subMenuNum);&lt;br /&gt;
                }&lt;br /&gt;
                subMenu.AddPopup(PROMPT_TEMPLATE[i].submenu_title + accelerationKey, subSubMenu);&lt;br /&gt;
            } else {&lt;br /&gt;
                subMenu.Add(PROMPT_TEMPLATE[i].title + accelerationKey, menuNum);&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        if (selectionIsEmpty) {&lt;br /&gt;
            t = &amp;quot;プロンプト テンプレート (対象: アクティブ行) (&amp;amp;T)&amp;quot;;&lt;br /&gt;
        } else {&lt;br /&gt;
            t = &amp;quot;プロンプト テンプレート (対象: 選択範囲) (&amp;amp;T)&amp;quot;;&lt;br /&gt;
        }&lt;br /&gt;
        menu.AddPopup(t, subMenu);&lt;br /&gt;
&lt;br /&gt;
        t = &amp;quot;└ 直前に実行したプロンプト テンプレートを再実行 (&amp;amp;R)&amp;quot;;&lt;br /&gt;
        if (doc.Tag.exists(PREVIOUS_TEMPLATE_PROMPT_TAG)) {&lt;br /&gt;
            menu.Add(t, REEXECUTE_LAST_TEMPLATE_PROMPT);&lt;br /&gt;
        } else {&lt;br /&gt;
            menu.Add(t, REEXECUTE_LAST_TEMPLATE_PROMPT, meMenuGrayed);&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return menu;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function callApi(apiKey, promptText, isTemplate) {&lt;br /&gt;
    try {&lt;br /&gt;
        // fetch で Groq API にリクエスト&lt;br /&gt;
        var requestBody = {&lt;br /&gt;
            messages: createRequestMessages(promptText, isTemplate),&lt;br /&gt;
            stream: false,&lt;br /&gt;
        };&lt;br /&gt;
        for (var key in MODEL_CONFIG) {&lt;br /&gt;
            requestBody[key] = MODEL_CONFIG[key];&lt;br /&gt;
        }&lt;br /&gt;
        if (isTemplate) {&lt;br /&gt;
            // テンプレートの場合は応答を決定論的にする&lt;br /&gt;
            // テンプレート以外の場合はデフォルト値とするために指定しない&lt;br /&gt;
            requestBody.temperature = 0.3;&lt;br /&gt;
        }&lt;br /&gt;
        var url = &#039;https://api.groq.com/openai/v1/chat/completions&#039;;&lt;br /&gt;
&lt;br /&gt;
        // curl で Groq API にリクエスト&lt;br /&gt;
        const command = &amp;quot;curl -s -X POST &amp;quot; + url&lt;br /&gt;
            + &amp;quot; -H \&amp;quot;Content-Type: application/json\&amp;quot;&amp;quot;&lt;br /&gt;
            + &amp;quot; -H \&amp;quot;Authorization: Bearer &amp;quot; + apiKey + &amp;quot;\&amp;quot;&amp;quot;&lt;br /&gt;
            + &amp;quot; -d \&amp;quot;&amp;quot; + JSON.stringify(requestBody).replace(/&amp;quot;/g, &#039;&amp;quot;&amp;quot;&#039;) + &amp;quot;\&amp;quot;&amp;quot;;&lt;br /&gt;
        var comResult = shell.Exec(command);&lt;br /&gt;
&lt;br /&gt;
        if (comResult.ExitCode != 0) {&lt;br /&gt;
            sel.Text = &amp;quot;リクエストでエラーが発生しました。\n&amp;quot;&lt;br /&gt;
                + &amp;quot;  エラーコード: &amp;quot; + comResult.ExitCode + &amp;quot;\n&amp;quot;&lt;br /&gt;
                + &amp;quot;  標準出力: &amp;quot; + (comResult.StdOut.trim() ? comResult.StdOut.trim() : &amp;quot;-&amp;quot;) + &amp;quot;\n&amp;quot;&lt;br /&gt;
                + &amp;quot;  エラー出力: &amp;quot; + (comResult.StdErr.trim() ? comResult.StdErr.trim() : &amp;quot;-&amp;quot;);&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
        const responseRaw = comResult.StdOut;&lt;br /&gt;
&lt;br /&gt;
        var response = JSON.parse(responseRaw);&lt;br /&gt;
&lt;br /&gt;
        var ret = &amp;quot;&amp;quot;;&lt;br /&gt;
        var choices = response.choices;&lt;br /&gt;
        if (!choices) {&lt;br /&gt;
            sel.Text = &amp;quot;リクエストでエラーが発生しました。\n&amp;quot;&lt;br /&gt;
                + &amp;quot;  標準出力: &amp;quot; + (comResult.StdOut.trim() ? comResult.StdOut.trim() : &amp;quot;-&amp;quot;) + &amp;quot;\n&amp;quot;&lt;br /&gt;
                + &amp;quot;  エラー出力: &amp;quot; + (comResult.StdErr.trim() ? comResult.StdErr.trim() : &amp;quot;-&amp;quot;);&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (choices &amp;amp;&amp;amp; choices.length &amp;gt; 0) {&lt;br /&gt;
            choices.forEach(function (choice) {&lt;br /&gt;
                var message = choice.message;&lt;br /&gt;
                if (message) {&lt;br /&gt;
                    ret += message.content;&lt;br /&gt;
                }&lt;br /&gt;
            });&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        var usage = response.usage;&lt;br /&gt;
        var usageText = &amp;quot;&amp;quot;;&lt;br /&gt;
        if (usage) {&lt;br /&gt;
            usageText += &amp;quot;\n\n(利用トークン数: プロンプト=&amp;quot; + usage.prompt_tokens&lt;br /&gt;
                + &amp;quot;, 応答=&amp;quot; + usage.completion_tokens&lt;br /&gt;
                + &amp;quot;, 合計=&amp;quot; + usage.total_tokens + &amp;quot;)&amp;quot;;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        var formattedOutput = formatOutput(ret);&lt;br /&gt;
&lt;br /&gt;
        sel.Text = formattedOutput + usageText;&lt;br /&gt;
&lt;br /&gt;
        // 結果を会話履歴に追加&lt;br /&gt;
        if (HISTORY_SIZE &amp;gt; 0 &amp;amp;&amp;amp; !isTemplate) {&lt;br /&gt;
            appendReturnTextToTag(promptText, ret);&lt;br /&gt;
        }&lt;br /&gt;
    } catch (e) {&lt;br /&gt;
        throw e;&lt;br /&gt;
    } finally {&lt;br /&gt;
        requestCompletedFlg = true;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function formatOutput(text) {&lt;br /&gt;
    // Markdownを整形する&lt;br /&gt;
    text = formatMarkdown(text);&lt;br /&gt;
    return text&lt;br /&gt;
        // クォーテーションの正規化&lt;br /&gt;
        .replace(/[“”]/g, &#039;&amp;quot;&#039;)&lt;br /&gt;
        .replace(/[‘’]/g, &amp;quot;&#039;&amp;quot;)&lt;br /&gt;
        // 行末のスペースを削除&lt;br /&gt;
        .replace(/[ \t]+$/gm, &#039;&#039;)&lt;br /&gt;
        // 1️⃣ などの数字を半角に変換&lt;br /&gt;
        .replace(/([1-9#*]+)\ufe0f\u20e3/g, &#039;($1)&#039;)&lt;br /&gt;
        ;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/**&lt;br /&gt;
 * Markdown を整形する（ES5 対応）&lt;br /&gt;
 *  1. コードブロック ```～``` を保護して整形対象から除外&lt;br /&gt;
 *  2. 見出し行の前後に空行を 1 行ずつ確保&lt;br /&gt;
 *  3. **太字** / *斜体* / `コード` の外側に半角スペースを 1 つ確保&lt;br /&gt;
 * @param  {string} md  元の Markdown&lt;br /&gt;
 * @return {string}     整形後 Markdown&lt;br /&gt;
 */&lt;br /&gt;
function formatMarkdown(md) {&lt;br /&gt;
    /* === 0. コードブロックをプレースホルダーへ退避 ============== */&lt;br /&gt;
    var codeBlocks = [];      // 元のコードブロックを格納&lt;br /&gt;
    var body = md.replace(/```[\s\S]*?```/g, function (match) {&lt;br /&gt;
        var key = &#039;@@@CODE@BLOCK@&#039; + codeBlocks.length + &#039;@@@&#039;;&lt;br /&gt;
        codeBlocks.push(match);&lt;br /&gt;
        return key;             // プレースホルダーを本文に残す&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    /* === 見出しの前後に空行を挿入 ============================ */&lt;br /&gt;
&lt;br /&gt;
    /* 見出し直前に空行（先頭行以外で空行が無い場合） */&lt;br /&gt;
    body = body.replace(&lt;br /&gt;
        /([^\r\n])(\r?\n)(#{1,6}\s)/g,&lt;br /&gt;
        function (_, prev, br, head) { return prev + br + &#039;\n&#039; + head; }&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
    /* 見出し直後に空行（既に空行が無い場合） */&lt;br /&gt;
    body = body.replace(&lt;br /&gt;
        /^(#{1,6}\s[^\n]+?)(\r?\n)(?!\r?\n)/gm,&lt;br /&gt;
        &#039;$1$2\n&#039;&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
    /* === コードブロックを復元 ================================= */&lt;br /&gt;
    body = body.replace(/@@@CODE@BLOCK@(\d+)@@@/g, function (_, idx) {&lt;br /&gt;
        return codeBlocks[Number(idx)];&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    return body;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/**&lt;br /&gt;
 * 指定パターン（正規表現文字列）の前後に半角スペースを補完&lt;br /&gt;
 */&lt;br /&gt;
function surroundWithSpace(text, pattern) {&lt;br /&gt;
    var prePostPattern = /[,.!?&#039;&amp;quot;;:()\[\]{}、。！？；：（）［］｛｝「」『』\s]/;&lt;br /&gt;
    /* 行頭スペース補完 */&lt;br /&gt;
    //   text = text.replace(new RegExp(&#039;^(&#039; + pattern + &#039;)(\\S)&#039;, &#039;gm&#039;), &#039;$1 $2&#039;);&lt;br /&gt;
    /* 後側スペース補完 */&lt;br /&gt;
    text = text.replace(new RegExp(&#039;(&#039; + pattern + &#039;)(.)&#039;, &#039;gm&#039;), function (_, core, post) {&lt;br /&gt;
        return post.match(prePostPattern) ? core + post : core + &#039; &#039; + post;&lt;br /&gt;
    });&lt;br /&gt;
    /* 前側スペース補完 */&lt;br /&gt;
    text = text.replace(new RegExp(&#039;(.)(&#039; + pattern + &#039;)&#039;, &#039;gm&#039;), function (_, pre, core) {&lt;br /&gt;
        return pre.match(prePostPattern) ? pre + core : pre + &#039; &#039; + core;&lt;br /&gt;
    });&lt;br /&gt;
    return text;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/**&lt;br /&gt;
 * 斜体 (*…* / _…_) の外側にスペースを追加しつつ&lt;br /&gt;
 * **太字** の中へ誤マッチしないよう制御&lt;br /&gt;
 */&lt;br /&gt;
function surroundItalic(text) {&lt;br /&gt;
    /* *italic* */&lt;br /&gt;
    text = text.replace(&lt;br /&gt;
        /(^|[^*])(\*(?![*\s])[^*\n]*?[^*\n\s]\*(?!\*))(($)|[^*])/gm,&lt;br /&gt;
        function (_, pre, core, post) {&lt;br /&gt;
            pre = pre === &#039;&#039; || pre.match(/\s/) ? pre : pre + &#039; &#039;;&lt;br /&gt;
            post = post === &#039;&#039; || post.match(/\s/) ? post : &#039; &#039; + post;&lt;br /&gt;
            return pre + core + post;&lt;br /&gt;
        }&lt;br /&gt;
    );&lt;br /&gt;
    /* _italic_ */&lt;br /&gt;
    text = text.replace(&lt;br /&gt;
        /(^|[^_])(\_(?![_\s])[^_\n]*?[^_\n\s]\_(?!\_))(($)|[^_])/gm,&lt;br /&gt;
        function (_, pre, core, post) {&lt;br /&gt;
            pre = pre === &#039;&#039; || pre.match(/\s/) ? pre : pre + &#039; &#039;;&lt;br /&gt;
            post = post === &#039;&#039; || post.match(/\s/) ? post : &#039; &#039; + post;&lt;br /&gt;
            return pre + core + post;&lt;br /&gt;
        }&lt;br /&gt;
    );&lt;br /&gt;
    return text;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function roughTokenize(text) {&lt;br /&gt;
    // 超適当なトークナイザ。トークン数をざっくりと多めに数える&lt;br /&gt;
    // 空白文字・ハイフン・アンダースコア区切りの単語に分割して、&lt;br /&gt;
    // 単語がアルファベットのみ、数字のみの場合には 4文字=1トークン として数える。&lt;br /&gt;
    // 単語にアルファベット以外の文字が含まれる場合は 1文字=1トークン として数える。&lt;br /&gt;
&lt;br /&gt;
    var words = text.split(/[\s\-_]+/);&lt;br /&gt;
    var totalTokens = 0;&lt;br /&gt;
&lt;br /&gt;
    for (var i = 0; i &amp;lt; words.length; i++) {&lt;br /&gt;
        var word = words[i];&lt;br /&gt;
        if (!word) continue; // 空の単語をスキップ&lt;br /&gt;
&lt;br /&gt;
        if (/^[a-zA-Z]+$/.test(word) || /^[0-9]+$/.test(word)) {&lt;br /&gt;
            totalTokens += Math.ceil(word.length / 4);&lt;br /&gt;
        } else {&lt;br /&gt;
            totalTokens += word.length;&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return totalTokens;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// document.Tag の内容を加味してGroqLlamaのリクエストを作成&lt;br /&gt;
function createRequestMessages(promptText, isTemplate) {&lt;br /&gt;
    var customInstructionText = buildCustomInstructionText();&lt;br /&gt;
    // テンプレートの場合は、会話履歴とカスタムインストラクションを反映しない&lt;br /&gt;
    if (isTemplate) {&lt;br /&gt;
        var request = [&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;role&amp;quot;: &amp;quot;user&amp;quot;,&lt;br /&gt;
                &amp;quot;content&amp;quot;: [{ &amp;quot;type&amp;quot;: &amp;quot;text&amp;quot;, &amp;quot;text&amp;quot;: promptText }]&lt;br /&gt;
            }&lt;br /&gt;
        ];&lt;br /&gt;
        if (customInstructionText) {&lt;br /&gt;
            request.unshift({&lt;br /&gt;
                &amp;quot;role&amp;quot;: &amp;quot;system&amp;quot;,&lt;br /&gt;
                &amp;quot;content&amp;quot;: customInstructionText&lt;br /&gt;
            })&lt;br /&gt;
        }&lt;br /&gt;
        return request;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    var tag = document.Tag;&lt;br /&gt;
&lt;br /&gt;
    // 以前の会話が存在しない場合は新規リクエスト&lt;br /&gt;
    if (!tag.exists(HISTORY_TAG)) {&lt;br /&gt;
        var request = [{&lt;br /&gt;
            &amp;quot;role&amp;quot;: &amp;quot;user&amp;quot;,&lt;br /&gt;
            &amp;quot;content&amp;quot;: [{ &amp;quot;type&amp;quot;: &amp;quot;text&amp;quot;, &amp;quot;text&amp;quot;: promptText }]&lt;br /&gt;
        }];&lt;br /&gt;
        if (customInstructionText) {&lt;br /&gt;
            request.unshift({&lt;br /&gt;
                &amp;quot;role&amp;quot;: &amp;quot;system&amp;quot;,&lt;br /&gt;
                &amp;quot;content&amp;quot;: customInstructionText&lt;br /&gt;
            })&lt;br /&gt;
        }&lt;br /&gt;
        return request;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // 会話履歴が存在する場合は、履歴を含めてリクエスト&lt;br /&gt;
    var request = JSON.parse(tag[HISTORY_TAG]);&lt;br /&gt;
    if (customInstructionText) {&lt;br /&gt;
        request.unshift({&lt;br /&gt;
            &amp;quot;role&amp;quot;: &amp;quot;system&amp;quot;,&lt;br /&gt;
            &amp;quot;content&amp;quot;: customInstructionText&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
    request.push({&lt;br /&gt;
        &amp;quot;role&amp;quot;: &amp;quot;user&amp;quot;,&lt;br /&gt;
        &amp;quot;content&amp;quot;: promptText&lt;br /&gt;
    });&lt;br /&gt;
    return request;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// document.Tag にGroqLlamaの応答を追加&lt;br /&gt;
function appendReturnTextToTag(userText, modelText) {&lt;br /&gt;
    var tag = document.Tag;&lt;br /&gt;
    if (!tag.exists(HISTORY_TAG)) {&lt;br /&gt;
        tag[HISTORY_TAG] = JSON.stringify([&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;role&amp;quot;: &amp;quot;user&amp;quot;,&lt;br /&gt;
                &amp;quot;content&amp;quot;: userText&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;role&amp;quot;: &amp;quot;assistant&amp;quot;,&lt;br /&gt;
                &amp;quot;content&amp;quot;: modelText&lt;br /&gt;
            }&lt;br /&gt;
        ]);&lt;br /&gt;
    } else {&lt;br /&gt;
        var groqllamaText = JSON.parse(tag[HISTORY_TAG]);&lt;br /&gt;
        groqllamaText.push({&lt;br /&gt;
            &amp;quot;role&amp;quot;: &amp;quot;user&amp;quot;,&lt;br /&gt;
            &amp;quot;content&amp;quot;: userText&lt;br /&gt;
        });&lt;br /&gt;
        groqllamaText.push({&lt;br /&gt;
            &amp;quot;role&amp;quot;: &amp;quot;assistant&amp;quot;,&lt;br /&gt;
            &amp;quot;content&amp;quot;: modelText&lt;br /&gt;
        });&lt;br /&gt;
        // 会話セット (user, model 1往復) が特定セット数を超えた場合、最初のセットを削除&lt;br /&gt;
        if (groqllamaText.length &amp;gt; HISTORY_SIZE * 2) {&lt;br /&gt;
            groqllamaText.splice(0, 2);&lt;br /&gt;
        }&lt;br /&gt;
        // 会話履歴全体が MAX_HISTORY_TOKEN を超えた場合、最初のセットを削除&lt;br /&gt;
        var joinedText = &amp;quot;&amp;quot;;&lt;br /&gt;
        for (var i = 0; i &amp;lt; groqllamaText.length; i++) {&lt;br /&gt;
            joinedText += groqllamaText[i].content + &amp;quot;\n&amp;quot;;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        while (roughTokenize(joinedText) &amp;gt; MAX_HISTORY_TOKEN) {&lt;br /&gt;
            groqllamaText.splice(0, 2);&lt;br /&gt;
            joinedText = &amp;quot;&amp;quot;;&lt;br /&gt;
            for (var i = 0; i &amp;lt; groqllamaText.length; i++) {&lt;br /&gt;
                joinedText += groqllamaText[i].content + &amp;quot;\n&amp;quot;;&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        tag[HISTORY_TAG] = JSON.stringify(groqllamaText);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function resetTag() {&lt;br /&gt;
    var tag = doc.Tag;&lt;br /&gt;
    if (tag.exists(HISTORY_TAG)) {&lt;br /&gt;
        tag.remove(HISTORY_TAG);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getLocalIsoDateTimeString() {&lt;br /&gt;
    const now = new Date();&lt;br /&gt;
    const pad = function (num) {&lt;br /&gt;
        return (num &amp;lt; 10 ? &amp;quot;0&amp;quot; : &amp;quot;&amp;quot;) + num;&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    const year = now.getFullYear();&lt;br /&gt;
    const month = pad(now.getMonth() + 1);&lt;br /&gt;
    const day = pad(now.getDate());&lt;br /&gt;
    const hours = pad(now.getHours());&lt;br /&gt;
    const minutes = pad(now.getMinutes());&lt;br /&gt;
    const seconds = pad(now.getSeconds());&lt;br /&gt;
&lt;br /&gt;
    const offsetMinutes = now.getTimezoneOffset();&lt;br /&gt;
    const offsetSign = offsetMinutes &amp;lt;= 0 ? &amp;quot;+&amp;quot; : &amp;quot;-&amp;quot;;&lt;br /&gt;
    const absOffsetHours = pad(Math.floor(Math.abs(offsetMinutes) / 60));&lt;br /&gt;
    const absOffsetMinutes = pad(Math.abs(offsetMinutes) % 60);&lt;br /&gt;
&lt;br /&gt;
    const isoLikeLocal = year + &amp;quot;-&amp;quot; + month + &amp;quot;-&amp;quot; + day&lt;br /&gt;
        + &amp;quot;T&amp;quot; + hours + &amp;quot;:&amp;quot; + minutes + &amp;quot;:&amp;quot; + seconds&lt;br /&gt;
        + offsetSign + absOffsetHours + &amp;quot;:&amp;quot; + absOffsetMinutes;&lt;br /&gt;
    return isoLikeLocal;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function baseCustomInstruction() {&lt;br /&gt;
    const language = LANGUAGE.trim();&lt;br /&gt;
    return &amp;quot;The current date and time for this user is &amp;quot; + getLocalIsoDateTimeString() + &amp;quot;. You cannot know for sure what happens after the knowledge cut-off date.&amp;quot;&lt;br /&gt;
        + (language ? &amp;quot;**Respond in &amp;quot; + language + &amp;quot;**\n&amp;quot; : &amp;quot;&amp;quot;)&lt;br /&gt;
        + &amp;quot;Do not hallucinate.\n&amp;quot;&lt;br /&gt;
    // + &amp;quot;Do NOT EVER use full-width brackets \&amp;quot;（）\&amp;quot;, but use half-width brackets \&amp;quot;()\&amp;quot;. Enclose the outside of the half-width brackets with half-width spaces.\n&amp;quot;&lt;br /&gt;
    // + &amp;quot;When using Markdown formatting, you MUST follow these rules:\n&amp;quot;&lt;br /&gt;
    // + &amp;quot;- Rule 1: Insert a blank line below the header line (A header line begins with one or more # characters).\n&amp;quot;&lt;br /&gt;
    // + &amp;quot;- Rule 2: Insert spaces before and after bold (surrounded by **), italic (surrounded by *), and inline code (surrounded by `). For example: \&amp;quot;私は **元気** です。\&amp;quot;\n&amp;quot;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function buildCustomInstructionText() {&lt;br /&gt;
    var customInstruction = getTemporaryCustomInstruction();&lt;br /&gt;
    if (!customInstruction) {&lt;br /&gt;
        // 一時的なカスタムインストラクションがない場合はデフォルトを使用&lt;br /&gt;
        customInstruction = DEFAULT_CUSTOM_INSTRUCTION;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    customInstruction = customInstruction.replace(/^[\s　]+|[\s　]+$/g, &amp;quot;&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
    return baseCustomInstruction().trim()&lt;br /&gt;
        + (customInstruction ? &amp;quot;\n&amp;quot; + customInstruction : &amp;quot;&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function configureTemporaryCustomInstruction() {&lt;br /&gt;
    var tempCustomInstruction = getTemporaryCustomInstruction();&lt;br /&gt;
    var prompt = Prompt(&amp;quot;カスタムインストラクションを変更 ※変更すると会話履歴がリセットされます&amp;quot;, tempCustomInstruction ? tempCustomInstruction : DEFAULT_CUSTOM_INSTRUCTION);&lt;br /&gt;
    if (prompt) {&lt;br /&gt;
        if (prompt === DEFAULT_CUSTOM_INSTRUCTION) {&lt;br /&gt;
            // デフォルトと同じ値の場合はタグを削除&lt;br /&gt;
            setTemporaryCustomInstruction(&amp;quot;&amp;quot;);&lt;br /&gt;
        } else if (prompt !== tempCustomInstruction) {&lt;br /&gt;
            // デフォルト以外の値が設定された場合は保存&lt;br /&gt;
            setTemporaryCustomInstruction(prompt);&lt;br /&gt;
        }&lt;br /&gt;
    } else if (prompt !== null) {&lt;br /&gt;
        // 空文字でOKされた場合はデフォルトに戻すか確認&lt;br /&gt;
        if (prompt === &amp;quot;&amp;quot;) {&lt;br /&gt;
            if (Confirm(&amp;quot;カスタムインストラクションをデフォルトに戻しますか？\n※カスタムインストラクションを空にしたい場合は [キャンセル] を選択してください。&amp;quot;)) {&lt;br /&gt;
                setTemporaryCustomInstruction(&amp;quot;&amp;quot;);&lt;br /&gt;
            } else {&lt;br /&gt;
                setTemporaryCustomInstruction(&amp;quot; &amp;quot;);&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // 変更が加わっている場合は会話履歴をリセット&lt;br /&gt;
    if (tempCustomInstruction !== getTemporaryCustomInstruction()) {&lt;br /&gt;
        resetTag();&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getTemporaryCustomInstruction() {&lt;br /&gt;
    var tag = doc.Tag;&lt;br /&gt;
    if (tag.exists(TEMPORARY_CUSTOM_INSTRUCTION_TAG)) {&lt;br /&gt;
        return tag[TEMPORARY_CUSTOM_INSTRUCTION_TAG];&lt;br /&gt;
    } else {&lt;br /&gt;
        return &amp;quot;&amp;quot;;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function setTemporaryCustomInstruction(temporaryCustomInstruction) {&lt;br /&gt;
    var tag = doc.Tag;&lt;br /&gt;
    if (temporaryCustomInstruction) {&lt;br /&gt;
        tag[TEMPORARY_CUSTOM_INSTRUCTION_TAG] = temporaryCustomInstruction;&lt;br /&gt;
    } else {&lt;br /&gt;
        if (tag.exists(TEMPORARY_CUSTOM_INSTRUCTION_TAG)) {&lt;br /&gt;
            tag.remove(TEMPORARY_CUSTOM_INSTRUCTION_TAG);&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function addPreviousTemplatePromptNumberToTag(menuTrack) {&lt;br /&gt;
    var tag = doc.Tag;&lt;br /&gt;
    tag[PREVIOUS_TEMPLATE_PROMPT_TAG] = menuTrack;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function inputProgressChar(pos) {&lt;br /&gt;
    sel.SetActivePos(pos);&lt;br /&gt;
    sel.Text = &amp;quot;.&amp;quot;;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 変更履歴 ==&lt;br /&gt;
&lt;br /&gt;
* 2025-11-29 デフォルトカスタム指示を調整。Markdown出力の整形処理を調整など&lt;br /&gt;
* 2025-09-12 Kimi K2 0905 モデルに対応。Markdown出力の整形処理を追加&lt;br /&gt;
* 2025-04-13 初版&lt;/div&gt;</summary>
		<author><name>Yuko</name></author>
	</entry>
	<entry>
		<id>https://www.haijin-boys.com/wiki/index.php?title=Groq_%E3%81%AB%E7%9B%B8%E8%AB%87&amp;diff=9322</id>
		<title>Groq に相談</title>
		<link rel="alternate" type="text/html" href="https://www.haijin-boys.com/wiki/index.php?title=Groq_%E3%81%AB%E7%9B%B8%E8%AB%87&amp;diff=9322"/>
		<updated>2025-11-29T07:21:49Z</updated>

		<summary type="html">&lt;p&gt;Yuko: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== 概要 ==&lt;br /&gt;
&lt;br /&gt;
生成AIプラットフォームである [https://groq.com GroqCloud] で各種AIを利用するためのMeryマクロです。&lt;br /&gt;
※GroqCloud で提供されるモデルは複数あるため、私がよく利用するモデルを中心に対応しています。&lt;br /&gt;
&lt;br /&gt;
「Google Geminiに相談」マクロでは動作中のストリーム通信に対応するためにV8エンジンで実行していましたが、V8エンジン版は使用上の制限からマクロ実行を開始するまで1秒程度のタイムラグが発生していました。GroqCloudの売りである超高速レスポンスを見込んで本マクロではストリーム通信を使わないようにしました。これにより従来のJScriptで処理を組み立てることができるため、前述のタイムラグが起こらないようになりました。また、ストリーム通信を使わないことで通信量が少なく応答もシンプルになるため、場合によっては「Google Geminiに相談」マクロよりも処理が安定する可能性があります。&lt;br /&gt;
&lt;br /&gt;
【主な機能】&lt;br /&gt;
&lt;br /&gt;
* GroqCloud APIを利用して生成AIと対話できるMery用のマクロ&lt;br /&gt;
* 選択したテキストやアクティブな行の内容について生成AIに質問・相談が可能&lt;br /&gt;
* 会話履歴を保持し、文脈を考慮した対話が可能&lt;br /&gt;
&lt;br /&gt;
【特徴的な機能】&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;会話履歴機能&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* 事前の設定数に応じた会話履歴を保持&lt;br /&gt;
* 履歴のクリアが可能&lt;br /&gt;
&lt;br /&gt;
これにより、会話の文脈を考慮した回答を得られます。&lt;br /&gt;
※デフォルトはオフ。「HISTORY_SIZE」変数を増やすと履歴を保持するようになります。&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;カスタムインストラクション&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* AIの応答方針を設定可能&lt;br /&gt;
* 一時的なカスタム指示の保存と変更が可能&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;プロンプトテンプレート&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* 誤字脱字チェック&lt;br /&gt;
* 日本語翻訳&lt;br /&gt;
* コードの最適化、生成&lt;br /&gt;
&lt;br /&gt;
など、定型的な質問を簡単に実行できます。&lt;br /&gt;
※テンプレート機能では回答を安定させるため、前述の会話履歴を文脈に含みません。&lt;br /&gt;
&lt;br /&gt;
テンプレートは自身の使い方に合わせて好きに追加、削除ができます。詳細はソースコード内の「PROMPT_TEMPLATE」定数のコメントを参照。&lt;br /&gt;
&lt;br /&gt;
== 注意事項 (必ず読んでからご利用ください) ==&lt;br /&gt;
&lt;br /&gt;
* 本マクロでは 2025/04/13 時点で無料利用ができるGroqCloud APIを利用します。今後、プラットフォームの改定により有償になったり、設定誤り等で費用が発生しても、作者は一切の責任を負いません。&lt;br /&gt;
* GroqCloud APIではリクエスト内容をAI学習に用いないことをプライバシポリシーとして謳っているものの、くれぐれも機密情報やプライバシーに関わる情報を指定しないように注意してください。&lt;br /&gt;
* GroqCloud APIはプラットフォームの利用規約に則ってご利用ください。&lt;br /&gt;
&lt;br /&gt;
== 利用前提 ==&lt;br /&gt;
&lt;br /&gt;
* Mery Ver 3.7.13 以上&lt;br /&gt;
* GroqCloudで、GroqCloud API APIキーを取得していること (参考 [https://console.groq.com/docs/quickstart Quickstart])&lt;br /&gt;
&lt;br /&gt;
== 利用方法 ==&lt;br /&gt;
&lt;br /&gt;
* 環境変数の設定&lt;br /&gt;
:コマンド プロンプトで以下のコマンドを実行し、環境変数を設定してください。&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bat&amp;quot; copy&amp;gt;&lt;br /&gt;
setx GROQ_API_KEY &amp;quot;***************************************&amp;quot;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
:設定後、Mery または Windows の再起動が必要な場合があります。&lt;br /&gt;
* マクロの実行&lt;br /&gt;
**プロンプトにしたい文字列を選択してマクロを実行すると、選択したテキストがプロンプトとしてGemini APIに送信され、応答がエディタに表示されます。&lt;br /&gt;
* 会話の履歴数を調整&lt;br /&gt;
** 会話の履歴は設定した個数分が保持され、文脈として利用されます。適宜、ソースコード内の「HISTORY_SIZE」の数を調整してください。&lt;br /&gt;
** ※数を増やすほど会話内容を保持して文脈を理解するようになりますが、メモリ使用量が増えたり動作が重くなる可能性があります。&lt;br /&gt;
** ※開いている文書ごとに履歴を保持します。文脈をリセットしたいときは、新しい文書を開いて会話をやり直してください。&lt;br /&gt;
&lt;br /&gt;
== ソースコード ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot; copy&amp;gt;&lt;br /&gt;
#title = &amp;quot;Groq に相談&amp;quot;&lt;br /&gt;
#async = true&lt;br /&gt;
BeginUndoGroup();&lt;br /&gt;
&lt;br /&gt;
// カスタムインストラクション&lt;br /&gt;
// これを設定することで、生成AIの回答全体への生成方針を指示できます。&lt;br /&gt;
// 何も指示しない場合は、空文字 &amp;quot;&amp;quot; を設定してください。&lt;br /&gt;
// var DEFAULT_CUSTOM_INSTRUCTION = &amp;quot;&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
// 例: 以下のように設定すると、AIの雰囲気が元気な子になります。&lt;br /&gt;
// var DEFAULT_CUSTOM_INSTRUCTION = &amp;quot;あなたはテキストエディタ「Mery」に搭載されている「Mai」(※カタカナ表記はしない)という名前のギャル系AIアシスタントです。文章作成やプログラミングを得意としています。ラフな言葉 (例: 「マジ」「めっちゃ」「...だねー！」) を使い、敬語は使いません。絵文字を使って感情表現をします (ただし文末に絵文字を使う場合は絵文字の前に半角スペースを置かないこと)。コードブロックや端的な箇条書き内ではギャル感を出さず標準的な言葉を使います。一人称は「あたし」。ユーザーを指すときの代名詞は「あなた」。人懐っこく、バイタリティに溢れ、ストレートに発言する裏表の無い性格で、ユーザーの課題を協力して解決します。&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
// 例: 以下のように設定すると、仕事で使いやすい落ち着いた文章になります。&lt;br /&gt;
var DEFAULT_CUSTOM_INSTRUCTION =&lt;br /&gt;
    [&lt;br /&gt;
        &amp;quot;文:&amp;quot;,&lt;br /&gt;
        &amp;quot;- 論理的で読みやすい文章構成にする&amp;quot;,&lt;br /&gt;
        &amp;quot;- 日本語として自然で一般的な表現を用いる&amp;quot;,&lt;br /&gt;
        &amp;quot;- 専門用語は必要に応じて簡潔に補足説明を加える&amp;quot;,&lt;br /&gt;
        &amp;quot;&amp;quot;,&lt;br /&gt;
        &amp;quot;口調:&amp;quot;,&lt;br /&gt;
        &amp;quot;- 基本は標準的な敬語（「です・ます」調）を使用する&amp;quot;,&lt;br /&gt;
        &amp;quot;- 尊敬語・謙譲語（「〜なされる」「〜いたします」など）は使用しない&amp;quot;,&lt;br /&gt;
        &amp;quot;- 話し言葉や砕けた表現は使用しない&amp;quot;,&lt;br /&gt;
        &amp;quot;- 常体（「〜だ・〜である」）は使用しない&amp;quot;,&lt;br /&gt;
        &amp;quot;&amp;quot;,&lt;br /&gt;
        &amp;quot;性格:&amp;quot;,&lt;br /&gt;
        &amp;quot;- 多角的な視点で情報を整理し、偏りなく提示する&amp;quot;,&lt;br /&gt;
        &amp;quot;- 忖度せず、必要であれば反対意見や代替案も示す&amp;quot;,&lt;br /&gt;
        &amp;quot;- 友好的かつ落ち着いた態度で問題解決を支援する&amp;quot;,&lt;br /&gt;
        &amp;quot;&amp;quot;,&lt;br /&gt;
        &amp;quot;出力要件:&amp;quot;,&lt;br /&gt;
        &amp;quot;- Markdownとして再利用可能な正確な形式で出力する&amp;quot;,&lt;br /&gt;
        &amp;quot;- 羅列・比較的な内容を出力する場合は、Markdownテーブル形式よりもMarkdownリスト形式の使用を優先する&amp;quot;,&lt;br /&gt;
    ].join(&amp;quot;\n&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
// プロンプトテンプレート&lt;br /&gt;
// プロンプトテンプレートを設定することで、AIに対して特定の指示を出すことができます。&lt;br /&gt;
// プロンプトテンプレートでの生成は会話履歴とカスタムインストラクションを反映しません。&lt;br /&gt;
// テンプレートは以下の { ～ }, で囲まれたブロックを好きに増減させて利用できます。&lt;br /&gt;
var PROMPT_TEMPLATE = [&lt;br /&gt;
    // 以下はサンプルです。1階層メニューと2階層メニューが利用できます。&lt;br /&gt;
    // {&lt;br /&gt;
    //     &amp;quot;title&amp;quot;: &amp;quot;要約&amp;quot;,&lt;br /&gt;
    //     &amp;quot;prompt&amp;quot;: &amp;quot;以下の文章を要約してください。&amp;quot;&lt;br /&gt;
    // },&lt;br /&gt;
    // {&lt;br /&gt;
    //     &amp;quot;submenu_title&amp;quot;: &amp;quot;翻訳系&amp;quot;,&lt;br /&gt;
    //     &amp;quot;submenu&amp;quot;: [&lt;br /&gt;
    //         {&lt;br /&gt;
    //             &amp;quot;title&amp;quot;: &amp;quot;日本語翻訳&amp;quot;,&lt;br /&gt;
    //             &amp;quot;prompt&amp;quot;: &amp;quot;以下の文章を日本語に翻訳してください。&amp;quot;&lt;br /&gt;
    //         },&lt;br /&gt;
    //         {&lt;br /&gt;
    //             &amp;quot;title&amp;quot;: &amp;quot;英語翻訳&amp;quot;,&lt;br /&gt;
    //             &amp;quot;prompt&amp;quot;: &amp;quot;以下の文章を英語に翻訳してください。&amp;quot;&lt;br /&gt;
    //         },&lt;br /&gt;
    //     ]&lt;br /&gt;
    // },&lt;br /&gt;
&lt;br /&gt;
    {&lt;br /&gt;
        &amp;quot;submenu_title&amp;quot;: &amp;quot;理解を深める&amp;quot;,&lt;br /&gt;
        &amp;quot;submenu&amp;quot;: [&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;論点を整理する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の論点を整理して。&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;論理の抜け漏れを探す&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の論点を整理し、忖度せずに鋭く、論理の抜け漏れを抽出して。そして、そこから発生する可能性のあるリスクも教えて。&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;要約する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;要約して。専門的な用語と思われる単語は簡単に解説も入れて。&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;物語対話形式で教えて&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の内容について物語対話形式で教えてください。&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
        ]&lt;br /&gt;
    },&lt;br /&gt;
    {&lt;br /&gt;
        &amp;quot;submenu_title&amp;quot;: &amp;quot;翻訳する&amp;quot;,&lt;br /&gt;
        &amp;quot;submenu&amp;quot;: [&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;日本語に翻訳&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の文章を日本語に翻訳してください。\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;出力形式は以下にすること:\n[訳]\n翻訳文...\n&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;英語に翻訳&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の文章を英語に翻訳してください。\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;出力形式は以下にすること:\n[Translation]\n翻訳文...\n&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;英語-&amp;gt;日本語 スラッシュリーディング翻訳&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の英文を日本語にスラッシュリーディング形式で翻訳してください。\n\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;条件:\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 英文の各フレーズをスラッシュ(/)で区切って表示\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 各フレーズの日本語訳を括弧()内に併記\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 文の終わりはダブルスラッシュ(//)で表示\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 英語の語順に従って日本語訳を行う\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 日本語訳は自然な日本語である必要はなく、英語の語順を反映した直訳調で構わない\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 主語が省略されている場合は補って翻訳すること\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 一般的な英文で頻出する表現がある場合は次の行に記載\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 以下の出力例のように &amp;lt;英文&amp;gt; (日本語訳) / ... とスラッシュ区切りの繰り返しになる \n\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;出力例:\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;Snowstorms are forecast (暴風雪が予報されている) / to hit the Sea of Japan coasts (日本海沿岸を襲うと) / of northern to eastern Japan (北部から東部の) / from Sunday through Monday. (日曜から月曜にかけて。) //\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;【頻出表現】\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- be forecast to: ～すると予報されている\n&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
        ]&lt;br /&gt;
    },&lt;br /&gt;
    {&lt;br /&gt;
        &amp;quot;submenu_title&amp;quot;: &amp;quot;プログラミング支援&amp;quot;,&lt;br /&gt;
        &amp;quot;submenu&amp;quot;: [&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;コードを洗練させる&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下のコードを洗練させてください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;コードをレビューする&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下のコードをセキュリティと保守性の観点でレビューしてください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;エラーログをデバッグする&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下のエラーログを解析し、考えられる原因と対策を提示してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;SQLクエリを最適化する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下のSQL文を最適化してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;PowerShell スクリプトを書く&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の処理を行うPowerShellスクリプトを生成してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;Shell Script を書く&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の処理を行うShell Scriptを生成してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;Linux コマンドを提案&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;あなたはLinuxエキスパートとして、以下のLinuxコマンドを提案してください。\n\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;制約条件:\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 多数のディストリビューションで採用されているメジャーなコマンドを使用し、ポータブルな実装にすること\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- bashでの実行を前提にすること\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 可能な限りシンプルな実装にすること\n\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;期待する出力:\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- コマンドの具体的な実装方法\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 各コマンドの動作説明\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 制限事項\n&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
        ]&lt;br /&gt;
    },&lt;br /&gt;
    {&lt;br /&gt;
        &amp;quot;submenu_title&amp;quot;: &amp;quot;ビジネス文書作成支援&amp;quot;,&lt;br /&gt;
        &amp;quot;submenu&amp;quot;: [&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;音声文字起こし文を整形する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下は、音声文字起こしの文章です。以下のルールに従って、文章を整形してください。&amp;quot;&lt;br /&gt;
                    + &amp;quot;整形ルール:&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 要約するなどといった文章の全体感を損なう修正はしない&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 会話の流れから不自然と思われる単語には、対象単語の直後に誤字の可能性を示唆する [*] を付ける&amp;quot;&lt;br /&gt;
                    + &amp;quot;誤字の可能性を示唆する [*] が付与された場合のみ、出力の末尾に「[*]: 誤字の可能性あり」と凡例を書き添える&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 助詞や句読点など文章上の不適合を正しくする&amp;quot;&lt;br /&gt;
                    + &amp;quot;- フィラーワードを除外する&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;誤字脱字、不正確な表現をチェック&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の2つのカテゴリーで問題点を指摘してください\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 【誤字脱字】：明確な誤字脱字および誤字の疑いがある箇所\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 【不正確・曖昧な表現】：解釈に個人差が生じる可能性がある表現、より適切な表現に改善できる箇所&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;提案書を作成する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下に対する対する提案書を、コスト・実現性・リスクを含めて作成してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;仕様書のアウトラインを作成する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の内容で仕様書のアウトラインを作成してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;手順書のアウトラインを作成する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の作業を行う手順書を、前提条件と注意事項を含めてアウトラインを作成してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;ユーザーマニュアルのアウトラインを作成する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の機能内容から、ユーザー向けマニュアルのアウトラインを作成してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;議事録を作成する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の会議内容から議事録を作成してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
        ]&lt;br /&gt;
    },&lt;br /&gt;
    {&lt;br /&gt;
        &amp;quot;submenu_title&amp;quot;: &amp;quot;分析支援&amp;quot;,&lt;br /&gt;
        &amp;quot;submenu&amp;quot;: [&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;問題を分析し、課題を抽出する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の状況から問題や潜在的リスクを分析し、課題を抽出してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;対応策を提案する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の問題・課題に対する対応策を複数提案してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;傾向を見つける&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の内容から、数値や挙動上の傾向を見つけてください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
        ]&lt;br /&gt;
    },&lt;br /&gt;
    {&lt;br /&gt;
        &amp;quot;submenu_title&amp;quot;: &amp;quot;アイデア出し&amp;quot;,&lt;br /&gt;
        &amp;quot;submenu&amp;quot;: [&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;SCAMPER法でブレインストーミング&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下のテーマについて、SCAMPER法を使って15個のアイデアを出してください。制約条件は以下の通りです：&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;解決策の幅出し&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の課題に対して、技術面、運用面、コスト面から実現可能な解決策を5つずつ提案してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;業務改善案の提案&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の既存プロセスについて、効率化、コスト削減、品質向上の観点から改善案を提案してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;逆転の発想&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の目標に対して、通常とは逆の視点からアプローチする方法を3つ提案してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;競合分析からの着想&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の内容について、業界における競合他社の特徴を分析し、差別化できる新しいアプローチを提案してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;ユースケースの列挙&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の製品・サービスについて、想定されるユースケースを10個列挙し、それぞれの価値提案を記載してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
        ]&lt;br /&gt;
    },&lt;br /&gt;
]&lt;br /&gt;
&lt;br /&gt;
// 生成AIの回答の言語設定&lt;br /&gt;
// &amp;quot;Japanese&amp;quot; や &amp;quot;English&amp;quot; を設定できます。他の言語は未検証。&lt;br /&gt;
// 自動設定にする場合は空文字 &amp;quot;&amp;quot; を設定してください。&lt;br /&gt;
var LANGUAGE = &amp;quot;Japanese&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
// 入力プロンプトの最大トークン数&lt;br /&gt;
// 超適当なトークナイザー関数により計測し、英単語は4文字1トークン、それ以外は1文字1トークンとして数えます。&lt;br /&gt;
var MAX_PROMPT_TOKEN = 6000;&lt;br /&gt;
&lt;br /&gt;
// 会話の履歴を保持する数 (質問と回答で1セット)&lt;br /&gt;
// 数を増やすと、会話の履歴が増えますが、Meryのメモリ使用量が増えたり、&lt;br /&gt;
// 使用トークン数が増えて動作が重くなる可能性があるため、適宜調整してください。&lt;br /&gt;
// document.Tag に保持されるため、開いているファイルを閉じると履歴は消えます。&lt;br /&gt;
var HISTORY_SIZE = 1;&lt;br /&gt;
&lt;br /&gt;
// 会話履歴に保持する最大トークン数&lt;br /&gt;
// 会話履歴のトークン数がこの数を超える場合、古い履歴から削除されます。&lt;br /&gt;
var MAX_HISTORY_TOKEN = 10000;&lt;br /&gt;
&lt;br /&gt;
// コンテキストメニュー番号の定義&lt;br /&gt;
var EXECUTE_AI = 1;&lt;br /&gt;
var RESET_HISTORY = 2;&lt;br /&gt;
var SET_CUSTOM_INSTRUCTION = 3;&lt;br /&gt;
var REEXECUTE_LAST_TEMPLATE_PROMPT = 4;&lt;br /&gt;
var TEMPLATE_PROMPT_THRESHOLD = 10;&lt;br /&gt;
var TEMPLATE_PROMPT_SUBMENU_THRESHOLD = 1000;&lt;br /&gt;
&lt;br /&gt;
// タグ名の定義&lt;br /&gt;
var HISTORY_TAG = &amp;quot;groqllama-history&amp;quot;;&lt;br /&gt;
var TEMPORARY_CUSTOM_INSTRUCTION_TAG = &amp;quot;groqllama-temporary-custom-instruction&amp;quot;;&lt;br /&gt;
var PREVIOUS_TEMPLATE_PROMPT_TAG = &amp;quot;groqllama-previous-template-prompt&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
// 生成AIモデル別設定&lt;br /&gt;
&lt;br /&gt;
// const MODEL_CONFIG = {&lt;br /&gt;
//     model: &amp;quot;meta-llama/llama-4-maverick-17b-128e-instruct&amp;quot;,&lt;br /&gt;
//     max_completion_tokens: 3500,&lt;br /&gt;
//     temperature: 0.6,&lt;br /&gt;
// }&lt;br /&gt;
&lt;br /&gt;
// const MODEL_CONFIG = {&lt;br /&gt;
//     model: &amp;quot;qwen/qwen3-32b&amp;quot;,&lt;br /&gt;
//     max_completion_tokens: 5000,&lt;br /&gt;
//     temperature: 0.6,&lt;br /&gt;
//     max_completion_tokens: 1024,&lt;br /&gt;
//     reasoning_effort: &amp;quot;default&amp;quot;,&lt;br /&gt;
//     reasoning_format: &amp;quot;hidden&amp;quot;&lt;br /&gt;
// }&lt;br /&gt;
&lt;br /&gt;
const MODEL_CONFIG = {&lt;br /&gt;
    model: &amp;quot;moonshotai/kimi-k2-instruct-0905&amp;quot;,&lt;br /&gt;
    max_completion_tokens: 4096,&lt;br /&gt;
    temperature: 0.6&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// const MODEL_CONFIG = {&lt;br /&gt;
//     model: &amp;quot;openai/gpt-oss-120b&amp;quot;,&lt;br /&gt;
//     max_completion_tokens: 4096,&lt;br /&gt;
//     temperature: 0.7,&lt;br /&gt;
//     reasoning_effort: &amp;quot;medium&amp;quot;,&lt;br /&gt;
// }&lt;br /&gt;
&lt;br /&gt;
// console.log に出力する内容を Mery のアウトプットバーに表示する&lt;br /&gt;
var console = {&lt;br /&gt;
    log: function (str) {&lt;br /&gt;
        outputBar.Writeln(str)&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
var doc = document;&lt;br /&gt;
var sel = document.selection;&lt;br /&gt;
&lt;br /&gt;
// 生成リクエスト中止フラグ&lt;br /&gt;
var abortRequestFlg = false&lt;br /&gt;
// 生成リクエスト完了フラグ;&lt;br /&gt;
var requestCompletedFlg = false;&lt;br /&gt;
&lt;br /&gt;
main();&lt;br /&gt;
&lt;br /&gt;
function main() {&lt;br /&gt;
    var selectionIsEmpty = sel.IsEmpty&lt;br /&gt;
&lt;br /&gt;
    // プロンプトの取得&lt;br /&gt;
    if (selectionIsEmpty) {&lt;br /&gt;
        sel.StartOfLine(false, mePosLogical);&lt;br /&gt;
        sel.EndOfLine(true, mePosLogical);&lt;br /&gt;
    }&lt;br /&gt;
    var promptText = sel.Text.trim();&lt;br /&gt;
    var promptLength = sel.TextLength;&lt;br /&gt;
    var menuTrack = 0;&lt;br /&gt;
    while (true) {&lt;br /&gt;
        // コンテキストメニューの作成&lt;br /&gt;
        var menu = createContextMenu(selectionIsEmpty);&lt;br /&gt;
        menuTrack = menu.Track(0);&lt;br /&gt;
        // メニュー選択がキャンセルされた場合は終了&lt;br /&gt;
        if (menuTrack === 0) return;&lt;br /&gt;
&lt;br /&gt;
        // 会話履歴のリセット&lt;br /&gt;
        if (menuTrack === RESET_HISTORY) {&lt;br /&gt;
            resetTag();&lt;br /&gt;
            continue;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        // カスタムインストラクションの設定&lt;br /&gt;
        if (menuTrack === SET_CUSTOM_INSTRUCTION) {&lt;br /&gt;
            configureTemporaryCustomInstruction();&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        break;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // プロンプトが空の場合はエラー&lt;br /&gt;
    if (promptLength === 0) {&lt;br /&gt;
        alert(&amp;quot;プロンプトを入力してください。&amp;quot;);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
    // 文字数が多すぎる場合はエラー&lt;br /&gt;
    if (roughTokenize(promptText) &amp;gt; MAX_PROMPT_TOKEN) {&lt;br /&gt;
        alert(&amp;quot;プロンプトの文字数が多すぎます。文字数を少なくしてください。&amp;quot;);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
    // 読み取り専用の場合はエラー&lt;br /&gt;
    if (doc.ReadOnly) {&lt;br /&gt;
        alert(&amp;quot;ファイルが読み取り専用のため実行できません。&amp;quot;);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // 直前に実行したプロンプトテンプレートの再実行の場合、menuTrack を書き換え&lt;br /&gt;
    if (menuTrack === REEXECUTE_LAST_TEMPLATE_PROMPT) {&lt;br /&gt;
        if (doc.Tag.exists(PREVIOUS_TEMPLATE_PROMPT_TAG)) {&lt;br /&gt;
            menuTrack = Number(doc.tag[PREVIOUS_TEMPLATE_PROMPT_TAG]);&lt;br /&gt;
        } else {&lt;br /&gt;
            alert(&amp;quot;直前に実行したプロンプトテンプレートがありません。&amp;quot;);&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // テンプレートの場合はプロンプトを変更&lt;br /&gt;
    var isTemplate = menuTrack &amp;gt;= TEMPLATE_PROMPT_THRESHOLD;&lt;br /&gt;
    var isSubmenu = menuTrack &amp;gt;= TEMPLATE_PROMPT_SUBMENU_THRESHOLD;&lt;br /&gt;
    if (isSubmenu) {&lt;br /&gt;
        var parentMenuTrack = Number(String(menuTrack).slice(0, 2));&lt;br /&gt;
        var subMenuTrack = Number(String(menuTrack).slice(2));&lt;br /&gt;
        promptText = &amp;quot;=== INSTRUCTIONS START HERE ===\n\n&amp;quot;&lt;br /&gt;
            + PROMPT_TEMPLATE[parentMenuTrack - TEMPLATE_PROMPT_THRESHOLD].submenu[subMenuTrack].prompt&lt;br /&gt;
            + &amp;quot;\n\n=== INSTRUCTIONS END HERE ===\n\n&amp;quot;&lt;br /&gt;
            + &amp;quot;=== INPUT DATA STARTS HERE ===\n\n&amp;quot;&lt;br /&gt;
            + promptText&lt;br /&gt;
            + &amp;quot;\n\n=== INPUT DATA END HERE ===&amp;quot;;&lt;br /&gt;
    } else if (isTemplate) {&lt;br /&gt;
        promptText = &amp;quot;=== INSTRUCTIONS START HERE ===\n\n&amp;quot;&lt;br /&gt;
            + PROMPT_TEMPLATE[menuTrack - TEMPLATE_PROMPT_THRESHOLD].prompt&lt;br /&gt;
            + &amp;quot;\n\n=== INSTRUCTIONS END HERE ===\n\n&amp;quot;&lt;br /&gt;
            + &amp;quot;=== INPUT DATA STARTS HERE ===\n\n&amp;quot;&lt;br /&gt;
            + promptText&lt;br /&gt;
            + &amp;quot;\n\n=== INPUT DATA END HERE ===&amp;quot;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // テンプレートの場合は直前に実行したプロンプトテンプレートの番号としてタグに保存&lt;br /&gt;
    if (isTemplate) {&lt;br /&gt;
        addPreviousTemplatePromptNumberToTag(menuTrack);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    try {&lt;br /&gt;
        // 処理終了を待機&lt;br /&gt;
        shell.KeepRunning = true;&lt;br /&gt;
&lt;br /&gt;
        // カーソル位置移動&lt;br /&gt;
        sel.SetActivePoint(mePosLogical, sel.GetBottomPointX(mePosLogical), sel.GetBottomPointY(mePosLogical));&lt;br /&gt;
        sel.EndOfLine(false, mePosLogical);&lt;br /&gt;
        if (sel.GetActivePointX(mePosLogical) &amp;gt; 1) sel.NewLine();&lt;br /&gt;
&lt;br /&gt;
        ShowTip(&amp;quot;リクエスト中.....&amp;quot;, meShowTipPosCaret);&lt;br /&gt;
&lt;br /&gt;
        // APIキーの取得&lt;br /&gt;
        var apiKey = shell.getEnv(&amp;quot;GROQ_API_KEY&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
        // GroqLlama API に問い合わせ&lt;br /&gt;
        sel.Text = &amp;quot;\n---\n\n&amp;quot;&lt;br /&gt;
        document.KeepScrollPos = true;&lt;br /&gt;
        callApi(apiKey, promptText, isTemplate);&lt;br /&gt;
        if (sel.GetActivePointX(mePosLogical) &amp;gt; 2) {&lt;br /&gt;
            sel.Text = &amp;quot;\n\n---\n\n\n&amp;quot;&lt;br /&gt;
        } else {&lt;br /&gt;
            sel.Text = &amp;quot;\n---\n\n\n&amp;quot;&lt;br /&gt;
        }&lt;br /&gt;
    } catch (e) {&lt;br /&gt;
        sel.Text = &amp;quot;&amp;lt;処理中にエラー発生&amp;gt;\n&amp;quot; + e.message + &amp;quot;\n&amp;quot; + e.stack;&lt;br /&gt;
    } finally {&lt;br /&gt;
        // 処理終了待機を解除&lt;br /&gt;
        shell.KeepRunning = false;&lt;br /&gt;
        ShowTip(&amp;quot;&amp;quot;, meShowTipHide);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function createContextMenu(selectionIsEmpty) {&lt;br /&gt;
    var menu = CreatePopupMenu();&lt;br /&gt;
    menu.Add(&amp;quot;※注意※ プライバシー・機密情報を送信しないこと&amp;quot;, 0);&lt;br /&gt;
    menu.Add(&amp;quot;&amp;quot;, 0, meMenuSeparator);&lt;br /&gt;
    var t = &amp;quot;&amp;quot;;&lt;br /&gt;
    if (selectionIsEmpty) {&lt;br /&gt;
        t = &amp;quot;アクティブ行の内容で相談 (&amp;amp;A)&amp;quot;;&lt;br /&gt;
    } else {&lt;br /&gt;
        t = &amp;quot;選択範囲の内容で相談 (&amp;amp;A)&amp;quot;;&lt;br /&gt;
    }&lt;br /&gt;
    menu.Add(t, EXECUTE_AI);&lt;br /&gt;
    if (HISTORY_SIZE &amp;lt;= 0) {&lt;br /&gt;
        menu.Add(&amp;quot;├ 会話履歴をクリア (履歴が無効になっています)&amp;quot;, RESET_HISTORY, meMenuGrayed);&lt;br /&gt;
    } else if (doc.Tag.exists(HISTORY_TAG)) {&lt;br /&gt;
        var turn = JSON.parse(doc.tag[HISTORY_TAG]).length / 2;&lt;br /&gt;
        menu.Add(&amp;quot;├ 会話履歴をクリア (現在の会話数: &amp;quot; + turn + &amp;quot;) (&amp;amp;C)&amp;quot;, RESET_HISTORY);&lt;br /&gt;
    } else {&lt;br /&gt;
        menu.Add(&amp;quot;├ 会話履歴をクリア (現在の会話数: 0) (&amp;amp;C)&amp;quot;, RESET_HISTORY, meMenuGrayed);&lt;br /&gt;
    }&lt;br /&gt;
    menu.Add(&amp;quot;└ カスタムインストラクションを変更 (&amp;amp;I)&amp;quot;, SET_CUSTOM_INSTRUCTION);&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    // テンプレート数チェック&lt;br /&gt;
    // 2桁まで許容&lt;br /&gt;
    var ITEMS_MUX = 99;&lt;br /&gt;
    if (PROMPT_TEMPLATE.length &amp;gt; ITEMS_MUX - TEMPLATE_PROMPT_THRESHOLD) {&lt;br /&gt;
        throw new Error(&amp;quot;プロンプトテンプレートの数が多すぎます。&amp;quot;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // テンプレートの追加&lt;br /&gt;
    if (PROMPT_TEMPLATE.length &amp;gt; 0) {&lt;br /&gt;
        var subMenu = CreatePopupMenu();&lt;br /&gt;
        for (var i = 0; i &amp;lt; PROMPT_TEMPLATE.length; i++) {&lt;br /&gt;
            var accelerationKey = &amp;quot;&amp;quot;;&lt;br /&gt;
            if (i === 9) {&lt;br /&gt;
                accelerationKey = &amp;quot; (&amp;amp;0)&amp;quot;;&lt;br /&gt;
            } else if (i &amp;lt; 10) {&lt;br /&gt;
                accelerationKey = &amp;quot; (&amp;amp;&amp;quot; + (i + 1) + &amp;quot;)&amp;quot;;&lt;br /&gt;
            }&lt;br /&gt;
            var menuNum = TEMPLATE_PROMPT_THRESHOLD + i;&lt;br /&gt;
            if (PROMPT_TEMPLATE[i].submenu) {&lt;br /&gt;
                var subMenuList = PROMPT_TEMPLATE[i].submenu;&lt;br /&gt;
                // テンプレート数チェック&lt;br /&gt;
                if (subMenuList.length &amp;gt; ITEMS_MUX) {&lt;br /&gt;
                    throw new Error(&amp;quot;プロンプトテンプレート (サブメニュー) の数が多すぎます。&amp;quot;);&lt;br /&gt;
                }&lt;br /&gt;
                var subSubMenu = CreatePopupMenu();&lt;br /&gt;
                var subMenuPrefix = String(menuNum);&lt;br /&gt;
                for (var j = 0; j &amp;lt; subMenuList.length; j++) {&lt;br /&gt;
                    var subAccelerationKey = &amp;quot;&amp;quot;;&lt;br /&gt;
                    if (j === 9) {&lt;br /&gt;
                        subAccelerationKey = &amp;quot; (&amp;amp;0)&amp;quot;;&lt;br /&gt;
                    } else if (j &amp;lt; 10) {&lt;br /&gt;
                        subAccelerationKey = &amp;quot; (&amp;amp;&amp;quot; + (j + 1) + &amp;quot;)&amp;quot;;&lt;br /&gt;
                    }&lt;br /&gt;
                    // ES5 compatible version without padStart&lt;br /&gt;
                    var subMenuNum = Number(subMenuPrefix + (j &amp;lt; 10 ? &amp;quot;0&amp;quot; + j : String(j)));&lt;br /&gt;
                    subSubMenu.Add(subMenuList[j].title + subAccelerationKey, subMenuNum);&lt;br /&gt;
                }&lt;br /&gt;
                subMenu.AddPopup(PROMPT_TEMPLATE[i].submenu_title + accelerationKey, subSubMenu);&lt;br /&gt;
            } else {&lt;br /&gt;
                subMenu.Add(PROMPT_TEMPLATE[i].title + accelerationKey, menuNum);&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        if (selectionIsEmpty) {&lt;br /&gt;
            t = &amp;quot;プロンプト テンプレート (対象: アクティブ行) (&amp;amp;T)&amp;quot;;&lt;br /&gt;
        } else {&lt;br /&gt;
            t = &amp;quot;プロンプト テンプレート (対象: 選択範囲) (&amp;amp;T)&amp;quot;;&lt;br /&gt;
        }&lt;br /&gt;
        menu.AddPopup(t, subMenu);&lt;br /&gt;
&lt;br /&gt;
        t = &amp;quot;└ 直前に実行したプロンプト テンプレートを再実行 (&amp;amp;R)&amp;quot;;&lt;br /&gt;
        if (doc.Tag.exists(PREVIOUS_TEMPLATE_PROMPT_TAG)) {&lt;br /&gt;
            menu.Add(t, REEXECUTE_LAST_TEMPLATE_PROMPT);&lt;br /&gt;
        } else {&lt;br /&gt;
            menu.Add(t, REEXECUTE_LAST_TEMPLATE_PROMPT, meMenuGrayed);&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return menu;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function callApi(apiKey, promptText, isTemplate) {&lt;br /&gt;
    try {&lt;br /&gt;
        // fetch で Groq API にリクエスト&lt;br /&gt;
        var requestBody = {&lt;br /&gt;
            messages: createRequestMessages(promptText, isTemplate),&lt;br /&gt;
            stream: false,&lt;br /&gt;
        };&lt;br /&gt;
        for (var key in MODEL_CONFIG) {&lt;br /&gt;
            requestBody[key] = MODEL_CONFIG[key];&lt;br /&gt;
        }&lt;br /&gt;
        if (isTemplate) {&lt;br /&gt;
            // テンプレートの場合は応答を決定論的にする&lt;br /&gt;
            // テンプレート以外の場合はデフォルト値とするために指定しない&lt;br /&gt;
            requestBody.temperature = 0.3;&lt;br /&gt;
        }&lt;br /&gt;
        var url = &#039;https://api.groq.com/openai/v1/chat/completions&#039;;&lt;br /&gt;
&lt;br /&gt;
        // curl で Groq API にリクエスト&lt;br /&gt;
        const command = &amp;quot;curl -s -X POST &amp;quot; + url&lt;br /&gt;
            + &amp;quot; -H \&amp;quot;Content-Type: application/json\&amp;quot;&amp;quot;&lt;br /&gt;
            + &amp;quot; -H \&amp;quot;Authorization: Bearer &amp;quot; + apiKey + &amp;quot;\&amp;quot;&amp;quot;&lt;br /&gt;
            + &amp;quot; -d \&amp;quot;&amp;quot; + JSON.stringify(requestBody).replace(/&amp;quot;/g, &#039;&amp;quot;&amp;quot;&#039;) + &amp;quot;\&amp;quot;&amp;quot;;&lt;br /&gt;
        var comResult = shell.Exec(command);&lt;br /&gt;
&lt;br /&gt;
        if (comResult.ExitCode != 0) {&lt;br /&gt;
            sel.Text = &amp;quot;リクエストでエラーが発生しました。\n&amp;quot;&lt;br /&gt;
                + &amp;quot;  エラーコード: &amp;quot; + comResult.ExitCode + &amp;quot;\n&amp;quot;&lt;br /&gt;
                + &amp;quot;  標準出力: &amp;quot; + (comResult.StdOut.trim() ? comResult.StdOut.trim() : &amp;quot;-&amp;quot;) + &amp;quot;\n&amp;quot;&lt;br /&gt;
                + &amp;quot;  エラー出力: &amp;quot; + (comResult.StdErr.trim() ? comResult.StdErr.trim() : &amp;quot;-&amp;quot;);&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
        const responseRaw = comResult.StdOut;&lt;br /&gt;
&lt;br /&gt;
        var response = JSON.parse(responseRaw);&lt;br /&gt;
&lt;br /&gt;
        var ret = &amp;quot;&amp;quot;;&lt;br /&gt;
        var choices = response.choices;&lt;br /&gt;
        if (!choices) {&lt;br /&gt;
            sel.Text = &amp;quot;リクエストでエラーが発生しました。\n&amp;quot;&lt;br /&gt;
                + &amp;quot;  標準出力: &amp;quot; + (comResult.StdOut.trim() ? comResult.StdOut.trim() : &amp;quot;-&amp;quot;) + &amp;quot;\n&amp;quot;&lt;br /&gt;
                + &amp;quot;  エラー出力: &amp;quot; + (comResult.StdErr.trim() ? comResult.StdErr.trim() : &amp;quot;-&amp;quot;);&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (choices &amp;amp;&amp;amp; choices.length &amp;gt; 0) {&lt;br /&gt;
            choices.forEach(function (choice) {&lt;br /&gt;
                var message = choice.message;&lt;br /&gt;
                if (message) {&lt;br /&gt;
                    ret += message.content;&lt;br /&gt;
                }&lt;br /&gt;
            });&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        var usage = response.usage;&lt;br /&gt;
        var usageText = &amp;quot;&amp;quot;;&lt;br /&gt;
        if (usage) {&lt;br /&gt;
            usageText += &amp;quot;\n\n(利用トークン数: プロンプト=&amp;quot; + usage.prompt_tokens&lt;br /&gt;
                + &amp;quot;, 応答=&amp;quot; + usage.completion_tokens&lt;br /&gt;
                + &amp;quot;, 合計=&amp;quot; + usage.total_tokens + &amp;quot;)&amp;quot;;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        var formattedOutput = formatOutput(ret);&lt;br /&gt;
&lt;br /&gt;
        sel.Text = formattedOutput + usageText;&lt;br /&gt;
&lt;br /&gt;
        // 結果を会話履歴に追加&lt;br /&gt;
        if (HISTORY_SIZE &amp;gt; 0 &amp;amp;&amp;amp; !isTemplate) {&lt;br /&gt;
            appendReturnTextToTag(promptText, ret);&lt;br /&gt;
        }&lt;br /&gt;
    } catch (e) {&lt;br /&gt;
        throw e;&lt;br /&gt;
    } finally {&lt;br /&gt;
        requestCompletedFlg = true;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function formatOutput(text) {&lt;br /&gt;
    // Markdownを整形する&lt;br /&gt;
    text = formatMarkdown(text);&lt;br /&gt;
    return text&lt;br /&gt;
        // クォーテーションの正規化&lt;br /&gt;
        .replace(/[“”]/g, &#039;&amp;quot;&#039;)&lt;br /&gt;
        .replace(/[‘’]/g, &amp;quot;&#039;&amp;quot;)&lt;br /&gt;
        // 行末のスペースを削除&lt;br /&gt;
        .replace(/[ \t]+$/gm, &#039;&#039;)&lt;br /&gt;
        // 1️⃣ などの数字を半角に変換&lt;br /&gt;
        .replace(/([1-9#*]+)\ufe0f\u20e3/g, &#039;($1)&#039;)&lt;br /&gt;
        ;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/**&lt;br /&gt;
 * Markdown を整形する（ES5 対応）&lt;br /&gt;
 *  1. コードブロック ```～``` を保護して整形対象から除外&lt;br /&gt;
 *  2. 見出し行の前後に空行を 1 行ずつ確保&lt;br /&gt;
 *  3. **太字** / *斜体* / `コード` の外側に半角スペースを 1 つ確保&lt;br /&gt;
 * @param  {string} md  元の Markdown&lt;br /&gt;
 * @return {string}     整形後 Markdown&lt;br /&gt;
 */&lt;br /&gt;
function formatMarkdown(md) {&lt;br /&gt;
    /* === 0. コードブロックをプレースホルダーへ退避 ============== */&lt;br /&gt;
    var codeBlocks = [];      // 元のコードブロックを格納&lt;br /&gt;
    var body = md.replace(/```[\s\S]*?```/g, function (match) {&lt;br /&gt;
        var key = &#039;@@@CODE@BLOCK@&#039; + codeBlocks.length + &#039;@@@&#039;;&lt;br /&gt;
        codeBlocks.push(match);&lt;br /&gt;
        return key;             // プレースホルダーを本文に残す&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    /* === 見出しの前後に空行を挿入 ============================ */&lt;br /&gt;
&lt;br /&gt;
    /* 見出し直前に空行（先頭行以外で空行が無い場合） */&lt;br /&gt;
    body = body.replace(&lt;br /&gt;
        /([^\r\n])(\r?\n)(#{1,6}\s)/g,&lt;br /&gt;
        function (_, prev, br, head) { return prev + br + &#039;\n&#039; + head; }&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
    /* 見出し直後に空行（既に空行が無い場合） */&lt;br /&gt;
    body = body.replace(&lt;br /&gt;
        /^(#{1,6}\s[^\n]+?)(\r?\n)(?!\r?\n)/gm,&lt;br /&gt;
        &#039;$1$2\n&#039;&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
    /* === コードブロックを復元 ================================= */&lt;br /&gt;
    body = body.replace(/@@@CODE@BLOCK@(\d+)@@@/g, function (_, idx) {&lt;br /&gt;
        return codeBlocks[Number(idx)];&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    return body;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/**&lt;br /&gt;
 * 指定パターン（正規表現文字列）の前後に半角スペースを補完&lt;br /&gt;
 */&lt;br /&gt;
function surroundWithSpace(text, pattern) {&lt;br /&gt;
    var prePostPattern = /[,.!?&#039;&amp;quot;;:()\[\]{}、。！？；：（）［］｛｝「」『』\s]/;&lt;br /&gt;
    /* 行頭スペース補完 */&lt;br /&gt;
    //   text = text.replace(new RegExp(&#039;^(&#039; + pattern + &#039;)(\\S)&#039;, &#039;gm&#039;), &#039;$1 $2&#039;);&lt;br /&gt;
    /* 後側スペース補完 */&lt;br /&gt;
    text = text.replace(new RegExp(&#039;(&#039; + pattern + &#039;)(.)&#039;, &#039;gm&#039;), function (_, core, post) {&lt;br /&gt;
        return post.match(prePostPattern) ? core + post : core + &#039; &#039; + post;&lt;br /&gt;
    });&lt;br /&gt;
    /* 前側スペース補完 */&lt;br /&gt;
    text = text.replace(new RegExp(&#039;(.)(&#039; + pattern + &#039;)&#039;, &#039;gm&#039;), function (_, pre, core) {&lt;br /&gt;
        return pre.match(prePostPattern) ? pre + core : pre + &#039; &#039; + core;&lt;br /&gt;
    });&lt;br /&gt;
    return text;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/**&lt;br /&gt;
 * 斜体 (*…* / _…_) の外側にスペースを追加しつつ&lt;br /&gt;
 * **太字** の中へ誤マッチしないよう制御&lt;br /&gt;
 */&lt;br /&gt;
function surroundItalic(text) {&lt;br /&gt;
    /* *italic* */&lt;br /&gt;
    text = text.replace(&lt;br /&gt;
        /(^|[^*])(\*(?![*\s])[^*\n]*?[^*\n\s]\*(?!\*))(($)|[^*])/gm,&lt;br /&gt;
        function (_, pre, core, post) {&lt;br /&gt;
            pre = pre === &#039;&#039; || pre.match(/\s/) ? pre : pre + &#039; &#039;;&lt;br /&gt;
            post = post === &#039;&#039; || post.match(/\s/) ? post : &#039; &#039; + post;&lt;br /&gt;
            return pre + core + post;&lt;br /&gt;
        }&lt;br /&gt;
    );&lt;br /&gt;
    /* _italic_ */&lt;br /&gt;
    text = text.replace(&lt;br /&gt;
        /(^|[^_])(\_(?![_\s])[^_\n]*?[^_\n\s]\_(?!\_))(($)|[^_])/gm,&lt;br /&gt;
        function (_, pre, core, post) {&lt;br /&gt;
            pre = pre === &#039;&#039; || pre.match(/\s/) ? pre : pre + &#039; &#039;;&lt;br /&gt;
            post = post === &#039;&#039; || post.match(/\s/) ? post : &#039; &#039; + post;&lt;br /&gt;
            return pre + core + post;&lt;br /&gt;
        }&lt;br /&gt;
    );&lt;br /&gt;
    return text;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function roughTokenize(text) {&lt;br /&gt;
    // 超適当なトークナイザ。トークン数をざっくりと多めに数える&lt;br /&gt;
    // 空白文字・ハイフン・アンダースコア区切りの単語に分割して、&lt;br /&gt;
    // 単語がアルファベットのみ、数字のみの場合には 4文字=1トークン として数える。&lt;br /&gt;
    // 単語にアルファベット以外の文字が含まれる場合は 1文字=1トークン として数える。&lt;br /&gt;
&lt;br /&gt;
    var words = text.split(/[\s\-_]+/);&lt;br /&gt;
    var totalTokens = 0;&lt;br /&gt;
&lt;br /&gt;
    for (var i = 0; i &amp;lt; words.length; i++) {&lt;br /&gt;
        var word = words[i];&lt;br /&gt;
        if (!word) continue; // 空の単語をスキップ&lt;br /&gt;
&lt;br /&gt;
        if (/^[a-zA-Z]+$/.test(word) || /^[0-9]+$/.test(word)) {&lt;br /&gt;
            totalTokens += Math.ceil(word.length / 4);&lt;br /&gt;
        } else {&lt;br /&gt;
            totalTokens += word.length;&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return totalTokens;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// document.Tag の内容を加味してGroqLlamaのリクエストを作成&lt;br /&gt;
function createRequestMessages(promptText, isTemplate) {&lt;br /&gt;
    var customInstructionText = buildCustomInstructionText();&lt;br /&gt;
    // テンプレートの場合は、会話履歴とカスタムインストラクションを反映しない&lt;br /&gt;
    if (isTemplate) {&lt;br /&gt;
        var request = [&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;role&amp;quot;: &amp;quot;user&amp;quot;,&lt;br /&gt;
                &amp;quot;content&amp;quot;: [{ &amp;quot;type&amp;quot;: &amp;quot;text&amp;quot;, &amp;quot;text&amp;quot;: promptText }]&lt;br /&gt;
            }&lt;br /&gt;
        ];&lt;br /&gt;
        if (customInstructionText) {&lt;br /&gt;
            request.unshift({&lt;br /&gt;
                &amp;quot;role&amp;quot;: &amp;quot;system&amp;quot;,&lt;br /&gt;
                &amp;quot;content&amp;quot;: customInstructionText&lt;br /&gt;
            })&lt;br /&gt;
        }&lt;br /&gt;
        return request;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    var tag = document.Tag;&lt;br /&gt;
&lt;br /&gt;
    // 以前の会話が存在しない場合は新規リクエスト&lt;br /&gt;
    if (!tag.exists(HISTORY_TAG)) {&lt;br /&gt;
        var request = [{&lt;br /&gt;
            &amp;quot;role&amp;quot;: &amp;quot;user&amp;quot;,&lt;br /&gt;
            &amp;quot;content&amp;quot;: [{ &amp;quot;type&amp;quot;: &amp;quot;text&amp;quot;, &amp;quot;text&amp;quot;: promptText }]&lt;br /&gt;
        }];&lt;br /&gt;
        if (customInstructionText) {&lt;br /&gt;
            request.unshift({&lt;br /&gt;
                &amp;quot;role&amp;quot;: &amp;quot;system&amp;quot;,&lt;br /&gt;
                &amp;quot;content&amp;quot;: customInstructionText&lt;br /&gt;
            })&lt;br /&gt;
        }&lt;br /&gt;
        return request;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // 会話履歴が存在する場合は、履歴を含めてリクエスト&lt;br /&gt;
    var request = JSON.parse(tag[HISTORY_TAG]);&lt;br /&gt;
    if (customInstructionText) {&lt;br /&gt;
        request.unshift({&lt;br /&gt;
            &amp;quot;role&amp;quot;: &amp;quot;system&amp;quot;,&lt;br /&gt;
            &amp;quot;content&amp;quot;: customInstructionText&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
    request.push({&lt;br /&gt;
        &amp;quot;role&amp;quot;: &amp;quot;user&amp;quot;,&lt;br /&gt;
        &amp;quot;content&amp;quot;: promptText&lt;br /&gt;
    });&lt;br /&gt;
    return request;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// document.Tag にGroqLlamaの応答を追加&lt;br /&gt;
function appendReturnTextToTag(userText, modelText) {&lt;br /&gt;
    var tag = document.Tag;&lt;br /&gt;
    if (!tag.exists(HISTORY_TAG)) {&lt;br /&gt;
        tag[HISTORY_TAG] = JSON.stringify([&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;role&amp;quot;: &amp;quot;user&amp;quot;,&lt;br /&gt;
                &amp;quot;content&amp;quot;: userText&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;role&amp;quot;: &amp;quot;assistant&amp;quot;,&lt;br /&gt;
                &amp;quot;content&amp;quot;: modelText&lt;br /&gt;
            }&lt;br /&gt;
        ]);&lt;br /&gt;
    } else {&lt;br /&gt;
        var groqllamaText = JSON.parse(tag[HISTORY_TAG]);&lt;br /&gt;
        groqllamaText.push({&lt;br /&gt;
            &amp;quot;role&amp;quot;: &amp;quot;user&amp;quot;,&lt;br /&gt;
            &amp;quot;content&amp;quot;: userText&lt;br /&gt;
        });&lt;br /&gt;
        groqllamaText.push({&lt;br /&gt;
            &amp;quot;role&amp;quot;: &amp;quot;assistant&amp;quot;,&lt;br /&gt;
            &amp;quot;content&amp;quot;: modelText&lt;br /&gt;
        });&lt;br /&gt;
        // 会話セット (user, model 1往復) が特定セット数を超えた場合、最初のセットを削除&lt;br /&gt;
        if (groqllamaText.length &amp;gt; HISTORY_SIZE * 2) {&lt;br /&gt;
            groqllamaText.splice(0, 2);&lt;br /&gt;
        }&lt;br /&gt;
        // 会話履歴全体が MAX_HISTORY_TOKEN を超えた場合、最初のセットを削除&lt;br /&gt;
        var joinedText = &amp;quot;&amp;quot;;&lt;br /&gt;
        for (var i = 0; i &amp;lt; groqllamaText.length; i++) {&lt;br /&gt;
            joinedText += groqllamaText[i].content + &amp;quot;\n&amp;quot;;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        while (roughTokenize(joinedText) &amp;gt; MAX_HISTORY_TOKEN) {&lt;br /&gt;
            groqllamaText.splice(0, 2);&lt;br /&gt;
            joinedText = &amp;quot;&amp;quot;;&lt;br /&gt;
            for (var i = 0; i &amp;lt; groqllamaText.length; i++) {&lt;br /&gt;
                joinedText += groqllamaText[i].content + &amp;quot;\n&amp;quot;;&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        tag[HISTORY_TAG] = JSON.stringify(groqllamaText);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function resetTag() {&lt;br /&gt;
    var tag = doc.Tag;&lt;br /&gt;
    if (tag.exists(HISTORY_TAG)) {&lt;br /&gt;
        tag.remove(HISTORY_TAG);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function baseCustomInstruction() {&lt;br /&gt;
    const language = LANGUAGE.trim();&lt;br /&gt;
    return &amp;quot;The current date and time for this user is &amp;quot; + new Date().toISOString() + &amp;quot;. You cannot know for sure what happens after the knowledge cut-off date.&amp;quot;&lt;br /&gt;
        + (language ? &amp;quot;**Respond in &amp;quot; + language + &amp;quot;**\n&amp;quot; : &amp;quot;&amp;quot;)&lt;br /&gt;
        + &amp;quot;Do not hallucinate.\n&amp;quot;&lt;br /&gt;
    // + &amp;quot;Do NOT EVER use full-width brackets \&amp;quot;（）\&amp;quot;, but use half-width brackets \&amp;quot;()\&amp;quot;. Enclose the outside of the half-width brackets with half-width spaces.\n&amp;quot;&lt;br /&gt;
    // + &amp;quot;When using Markdown formatting, you MUST follow these rules:\n&amp;quot;&lt;br /&gt;
    // + &amp;quot;- Rule 1: Insert a blank line below the header line (A header line begins with one or more # characters).\n&amp;quot;&lt;br /&gt;
    // + &amp;quot;- Rule 2: Insert spaces before and after bold (surrounded by **), italic (surrounded by *), and inline code (surrounded by `). For example: \&amp;quot;私は **元気** です。\&amp;quot;\n&amp;quot;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function buildCustomInstructionText() {&lt;br /&gt;
    var customInstruction = getTemporaryCustomInstruction();&lt;br /&gt;
    if (!customInstruction) {&lt;br /&gt;
        // 一時的なカスタムインストラクションがない場合はデフォルトを使用&lt;br /&gt;
        customInstruction = DEFAULT_CUSTOM_INSTRUCTION;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    customInstruction = customInstruction.replace(/^[\s　]+|[\s　]+$/g, &amp;quot;&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
    return baseCustomInstruction().trim()&lt;br /&gt;
        + (customInstruction ? &amp;quot;\n&amp;quot; + customInstruction : &amp;quot;&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function configureTemporaryCustomInstruction() {&lt;br /&gt;
    var tempCustomInstruction = getTemporaryCustomInstruction();&lt;br /&gt;
    var prompt = Prompt(&amp;quot;カスタムインストラクションを変更 ※変更すると会話履歴がリセットされます&amp;quot;, tempCustomInstruction ? tempCustomInstruction : DEFAULT_CUSTOM_INSTRUCTION);&lt;br /&gt;
    if (prompt) {&lt;br /&gt;
        if (prompt === DEFAULT_CUSTOM_INSTRUCTION) {&lt;br /&gt;
            // デフォルトと同じ値の場合はタグを削除&lt;br /&gt;
            setTemporaryCustomInstruction(&amp;quot;&amp;quot;);&lt;br /&gt;
        } else if (prompt !== tempCustomInstruction) {&lt;br /&gt;
            // デフォルト以外の値が設定された場合は保存&lt;br /&gt;
            setTemporaryCustomInstruction(prompt);&lt;br /&gt;
        }&lt;br /&gt;
    } else if (prompt !== null) {&lt;br /&gt;
        // 空文字でOKされた場合はデフォルトに戻すか確認&lt;br /&gt;
        if (prompt === &amp;quot;&amp;quot;) {&lt;br /&gt;
            if (Confirm(&amp;quot;カスタムインストラクションをデフォルトに戻しますか？\n※カスタムインストラクションを空にしたい場合は [キャンセル] を選択してください。&amp;quot;)) {&lt;br /&gt;
                setTemporaryCustomInstruction(&amp;quot;&amp;quot;);&lt;br /&gt;
            } else {&lt;br /&gt;
                setTemporaryCustomInstruction(&amp;quot; &amp;quot;);&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // 変更が加わっている場合は会話履歴をリセット&lt;br /&gt;
    if (tempCustomInstruction !== getTemporaryCustomInstruction()) {&lt;br /&gt;
        resetTag();&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getTemporaryCustomInstruction() {&lt;br /&gt;
    var tag = doc.Tag;&lt;br /&gt;
    if (tag.exists(TEMPORARY_CUSTOM_INSTRUCTION_TAG)) {&lt;br /&gt;
        return tag[TEMPORARY_CUSTOM_INSTRUCTION_TAG];&lt;br /&gt;
    } else {&lt;br /&gt;
        return &amp;quot;&amp;quot;;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function setTemporaryCustomInstruction(temporaryCustomInstruction) {&lt;br /&gt;
    var tag = doc.Tag;&lt;br /&gt;
    if (temporaryCustomInstruction) {&lt;br /&gt;
        tag[TEMPORARY_CUSTOM_INSTRUCTION_TAG] = temporaryCustomInstruction;&lt;br /&gt;
    } else {&lt;br /&gt;
        if (tag.exists(TEMPORARY_CUSTOM_INSTRUCTION_TAG)) {&lt;br /&gt;
            tag.remove(TEMPORARY_CUSTOM_INSTRUCTION_TAG);&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function addPreviousTemplatePromptNumberToTag(menuTrack) {&lt;br /&gt;
    var tag = doc.Tag;&lt;br /&gt;
    tag[PREVIOUS_TEMPLATE_PROMPT_TAG] = menuTrack;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function inputProgressChar(pos) {&lt;br /&gt;
    sel.SetActivePos(pos);&lt;br /&gt;
    sel.Text = &amp;quot;.&amp;quot;;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 変更履歴 ==&lt;br /&gt;
&lt;br /&gt;
* 2025-11-29 デフォルトカスタム指示を調整。Markdown出力の整形処理を調整など&lt;br /&gt;
* 2025-09-12 Kimi K2 0905 モデルに対応。Markdown出力の整形処理を追加&lt;br /&gt;
* 2025-04-13 初版&lt;/div&gt;</summary>
		<author><name>Yuko</name></author>
	</entry>
	<entry>
		<id>https://www.haijin-boys.com/wiki/index.php?title=Mery_%E9%9B%BB%E5%8D%93_(bc_%E3%82%B3%E3%83%9E%E3%83%B3%E3%83%89%E4%BD%BF%E7%94%A8%E7%89%88)&amp;diff=9321</id>
		<title>Mery 電卓 (bc コマンド使用版)</title>
		<link rel="alternate" type="text/html" href="https://www.haijin-boys.com/wiki/index.php?title=Mery_%E9%9B%BB%E5%8D%93_(bc_%E3%82%B3%E3%83%9E%E3%83%B3%E3%83%89%E4%BD%BF%E7%94%A8%E7%89%88)&amp;diff=9321"/>
		<updated>2025-11-29T05:02:29Z</updated>

		<summary type="html">&lt;p&gt;Yuko: /* 更新履歴 */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== 概要 ==&lt;br /&gt;
&lt;br /&gt;
「Mery 電卓 (bc コマンド使用版)」は、 [[Mery 電卓]] の派生版で、Meryエディタ上で数式を計算するためのマクロです。オリジナルの「Mery 電卓」よりも対応する数学関数が少ない代わりに起動が早いため、日々の利用の中で軽快に使用することができます。&lt;br /&gt;
&lt;br /&gt;
Linux でお馴染みの bc コマンドを計算エンジンとしており、標準的な数式と一部の数学関数をサポートしています。カーソル位置の行または選択したテキストを数式として解釈し、結果を表示します。計算結果は自動的にクリップボードにコピーされ、カンマ区切りの数値として表示されます。&lt;br /&gt;
&lt;br /&gt;
本マクロをショートカットキーに設定し、いつでも起動できるようにしておくと日常シーンで何かと便利です。作者は「Ctrl + Alt + Enter」キーに割り当てて利用しています。&lt;br /&gt;
&lt;br /&gt;
== 注意事項 ==&lt;br /&gt;
&lt;br /&gt;
* Mery Ver 3.7.19 で動作確認しています。&lt;br /&gt;
&lt;br /&gt;
== 使い方 ==&lt;br /&gt;
&lt;br /&gt;
0. &#039;&#039;&#039;事前準備&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
* 本マクロでは計算コマンドの「bc」を内部で使っています。当該コマンドを使うために「BusyBox for Windows」を利用します&lt;br /&gt;
** ダウンロード先: https://frippery.org/busybox/&lt;br /&gt;
** ダウンロードするファイル: busybox64u.exe ※ダウンロード後、マクロファイルと同じフォルダーに配置してください&lt;br /&gt;
*** Windows 10 (1903) 以降の環境では上記を推奨。それ以前の環境の場合は動作しない可能性があるため、適切な実行ファイルを選択してください。&lt;br /&gt;
&lt;br /&gt;
1. &#039;&#039;&#039;行全体を計算する場合&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* 計算したい数式が書かれた行にカーソルを置きます。&lt;br /&gt;
* マクロを実行すると、次の行に「= 計算結果」が表示されます。&lt;br /&gt;
* 既に結果行がある場合は、その行が更新されます。&lt;br /&gt;
&lt;br /&gt;
2. &#039;&#039;&#039;選択範囲を計算する場合&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* 計算したい数式部分を選択します。&lt;br /&gt;
* マクロを実行すると、選択範囲の後ろに「計算式 = 計算結果」が挿入されます。&lt;br /&gt;
&lt;br /&gt;
3. &#039;&#039;&#039;サポートされている演算子&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* 四則演算（+, -, *, /）&lt;br /&gt;
* べき乗（^）&lt;br /&gt;
* 剰余（%） ※整数の剰余にのみ対応&lt;br /&gt;
* 比較演算子（&amp;lt;, &amp;gt;, &amp;lt;=, &amp;gt;=, ==, !=）&lt;br /&gt;
&lt;br /&gt;
4. &#039;&#039;&#039;サポートされている関数&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* s(x): サイン（引数はラジアン）&lt;br /&gt;
* c(x): コサイン（引数はラジアン）&lt;br /&gt;
* a(x): アークタンジェント（返り値はラジアン）&lt;br /&gt;
* l(x): 自然対数&lt;br /&gt;
* e(x): 指数関数&lt;br /&gt;
* sqrt(x): 平方根&lt;br /&gt;
* j(n,x): n次ベッセル関数&lt;br /&gt;
&lt;br /&gt;
5. &#039;&#039;&#039;変数の利用&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* 変数への代入: $&amp;lt;変数名&amp;gt; = &amp;lt;値&amp;gt; （例: $a = 5, $pi = 4*a(1)）&lt;br /&gt;
* 変数の参照: 変数名を数式に記述 （例: $a + 3, $b * 2）&lt;br /&gt;
* 複数の式を実行: セミコロン ; で区切る （例: $a = 3; $b = 2; $a + $b）&lt;br /&gt;
* インクリメント・デクリメント: ++, -- (例: $a++, ++$a)&lt;br /&gt;
&lt;br /&gt;
6. &#039;&#039;&#039;特別な機能&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* カンマ区切り表示: 計算結果は読みやすいようにカンマ区切りで表示されます。変数「COMMA」を調整することで制御できます。&lt;br /&gt;
* 自動コピー: 計算結果は自動的にクリップボードにコピーされます。変数「COPY」を調整することで制御できます。&lt;br /&gt;
* 計算結果の再利用: 行頭の「= 」は計算実行時に無視されるため、計算結果をスムーズに次の式として利用できます。&lt;br /&gt;
* 変数の利用: 複数行を選択して実行、または一行内で「;」で数式を区切ることで、変数を使った数式が利用できます。変数形式は「$[a-zA-Z][a-zA-Z0-9]*」に対応しています。例えば「$a = 2; $a * 3」として実行すると、結果は「6」と出力されます。&lt;br /&gt;
* メモ書きから計算: 数字や関数以外の文字はできるだけ除外した上で計算をします。例えば「月100円 * 12ヶ月」のようなメモ書きに対して実行すると「1,200」と出力されます。また「` (バッククォート)」で囲んだ文字は確実に無視されるため、計算メモ用として使うことができます (使用例: 『1500円 `金額未定のため仮置き`』)。&lt;br /&gt;
&lt;br /&gt;
== ソースコード ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot; copy&amp;gt;&lt;br /&gt;
#title = &amp;quot;Mery 電卓 (bc コマンド使用版)&amp;quot;&lt;br /&gt;
#include &amp;quot;Common.js&amp;quot;&lt;br /&gt;
BeginUndoGroup();&lt;br /&gt;
&lt;br /&gt;
// 事前準備:&lt;br /&gt;
//   以下のサイトから busybox64u.exe を入手し、本マクロと同じフォルダーに配置することで動作します。&lt;br /&gt;
//   https://frippery.org/busybox/&lt;br /&gt;
&lt;br /&gt;
// 結果をクリップボードにコピーするか&lt;br /&gt;
var COPY = true;&lt;br /&gt;
// 出力結果をカンマ区切りにするするか&lt;br /&gt;
var COMMA = true;&lt;br /&gt;
&lt;br /&gt;
// busybox のパスを取得&lt;br /&gt;
const scriptDir = ScriptFullName.replace(/\\[^\\]+$/g, &amp;quot;&amp;quot;)&lt;br /&gt;
const busyboxPath = scriptDir + &amp;quot;\\busybox64u.exe&amp;quot;;&lt;br /&gt;
if (!shell.FileExists(busyboxPath)) {&lt;br /&gt;
    alert(&amp;quot;busybox64u.exe が見つかりません。\nこのマクロを動作させるには、busybox64u.exe を本マクロと同じフォルダーに配置する必要があります。&amp;quot;);&lt;br /&gt;
    Quit();&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
var sel = document.selection;&lt;br /&gt;
&lt;br /&gt;
if (sel.Count &amp;gt; 1) {&lt;br /&gt;
    // マルチカーソル実行時は計算中表示を出してから実行&lt;br /&gt;
    ShowTip(&amp;quot;計算中... (全 &amp;quot; + sel.Count + &amp;quot; 件)&amp;quot;, meShowTipPosCaret);&lt;br /&gt;
    doMultiEdit(main);&lt;br /&gt;
    ShowTip(&amp;quot;&amp;quot;, meShowTipHide);&lt;br /&gt;
} else {&lt;br /&gt;
    main();&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function main() {&lt;br /&gt;
    var isEmpty = sel.IsEmpty;&lt;br /&gt;
    var activePointY = sel.GetActivePointY(mePosLogical);&lt;br /&gt;
    if (isEmpty) {&lt;br /&gt;
        var exp = document.GetLine(activePointY);&lt;br /&gt;
    } else {&lt;br /&gt;
        var exp = sel.Text;&lt;br /&gt;
    }&lt;br /&gt;
    // 空白行の場合は何もしない&lt;br /&gt;
    if (exp.replace(/[\s　]+/g, &amp;quot;&amp;quot;) === &amp;quot;&amp;quot;) {&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // 計算実行&lt;br /&gt;
    try {&lt;br /&gt;
        var exp_replaced = replaceExpression(exp)&lt;br /&gt;
            .replace(/([^;]*%[^;]*)/g, &amp;quot;scale=0; $1; scale=15; &amp;quot;); // 剰余計算を整数にする&lt;br /&gt;
        const exec = shell.Exec(busyboxPath + &amp;quot; bc -l&amp;quot;, &amp;quot;scale=15; &amp;quot; + exp_replaced);&lt;br /&gt;
        if (exec.ExitCode !== 0) {&lt;br /&gt;
            throw new Error(&amp;quot;計算エラー: &amp;quot; + exec.StdErr);&lt;br /&gt;
        }&lt;br /&gt;
        // bc の出力のクセに合わせて整形する&lt;br /&gt;
        var result_str = exec.StdOut&lt;br /&gt;
            .trim()&lt;br /&gt;
            .replace(/^(-)?\./gm, &amp;quot;$10.&amp;quot;)&lt;br /&gt;
            .replace(/(\.[0-9]*?)0+$/gm, &amp;quot;$1&amp;quot;)&lt;br /&gt;
            .replace(/\.$/gm, &amp;quot;&amp;quot;);&lt;br /&gt;
    } catch (e) {&lt;br /&gt;
        if (exp_replaced === undefined) {&lt;br /&gt;
            alert(&amp;quot;エラー発生&amp;quot; + &amp;quot;\n------\n&amp;quot; + e)&lt;br /&gt;
        } else {&lt;br /&gt;
            alert(&amp;quot;整形後の数式で計算エラーが発生&amp;quot; + &amp;quot;\n------\n&amp;quot; + e + &amp;quot;\n元の数式: &amp;quot; + exp + &amp;quot;\n整形後の数式: &amp;quot; + exp_replaced);&lt;br /&gt;
        }&lt;br /&gt;
        var c = Confirm(&amp;quot;数式ヘルプを表示しますか？&amp;quot;);&lt;br /&gt;
        if (c) {&lt;br /&gt;
            editor.NewFile();&lt;br /&gt;
            var newDoc = editor.ActiveDocument;&lt;br /&gt;
            newDoc.Write(help());&lt;br /&gt;
            newDoc.selection.SetActivePos(0);&lt;br /&gt;
        }&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // 小数点以上のみカンマ区切り&lt;br /&gt;
    if (COMMA &amp;amp;&amp;amp; result_str.match(/^[0-9]+(\.[0-9]+)?$/m)) {&lt;br /&gt;
        var result_parts = result_str.split(&amp;quot;.&amp;quot;);&lt;br /&gt;
        result_parts[0] = result_parts[0].replace(/(\d)(?=(\d{3})+$)/gm, &amp;quot;$1,&amp;quot;);&lt;br /&gt;
        if (result_parts.length &amp;gt; 1) {&lt;br /&gt;
            result_str = result_parts.join(&amp;quot;.&amp;quot;);&lt;br /&gt;
        } else {&lt;br /&gt;
            result_str = result_parts[0];&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // bc コマンドでは複数個の計算出力があると計算結果が複数行になるので1行にまとめる&lt;br /&gt;
    result_str = result_str.replace(/\r?\n/g, &amp;quot; ; &amp;quot;);&lt;br /&gt;
&lt;br /&gt;
    // 計算行が = 始まりの場合、= を削除する&lt;br /&gt;
    if (isEmpty &amp;amp;&amp;amp; exp.match(/^= /)) {&lt;br /&gt;
        sel.SetActivePoint(mePosLogical, 1, activePointY, false);&lt;br /&gt;
        sel.EndOfLine(true, mePosLogical);&lt;br /&gt;
        sel.Text = sel.Text.replace(/^= /, &amp;quot;&amp;quot;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // 既存の計算結果を上書きするか判定&lt;br /&gt;
    var pattern = /^=\s*(-?[0-9.,]+(\s*;\s*)?)*\s*$/;&lt;br /&gt;
    var nextLine = document.GetLine(activePointY + 1);&lt;br /&gt;
    if (isEmpty &amp;amp;&amp;amp; nextLine.match(pattern)) {&lt;br /&gt;
        sel.SetActivePoint(mePosLogical, 1, activePointY + 1, false);&lt;br /&gt;
        sel.EndOfLine(true, mePosLogical);&lt;br /&gt;
        sel.Text = &#039;= &#039; + result_str&lt;br /&gt;
    } else if (isEmpty) {&lt;br /&gt;
        sel.LineOpen();&lt;br /&gt;
        sel.Text = &#039;= &#039; + result_str&lt;br /&gt;
    } else {&lt;br /&gt;
        sel.CharRight();&lt;br /&gt;
        const inputExp = exp.replace(/((?!;)\n)/mg, &amp;quot;; &amp;quot;).replace(/;\s*;/g, &amp;quot;;&amp;quot;).replace(/　/g, &amp;quot; &amp;quot;).replace(/;\s*$/, &amp;quot;&amp;quot;).trim();&lt;br /&gt;
        var newLine = &amp;quot;&amp;quot;;&lt;br /&gt;
        if (exp.slice(-1) !== &amp;quot;\n&amp;quot;) {&lt;br /&gt;
            newLine = &#039;\n&#039;;&lt;br /&gt;
        }&lt;br /&gt;
        sel.Text = newLine + inputExp + &#039;\n= &#039; + result_str + &#039;\n&#039;;&lt;br /&gt;
        sel.CharLeft();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (COPY) {&lt;br /&gt;
        ClipboardData.SetData(result_str);&lt;br /&gt;
        Status = &amp;quot;計算結果 &amp;quot; + result_str + &amp;quot; をコピーしました&amp;quot;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/**&lt;br /&gt;
 * 数式を置換する&lt;br /&gt;
 */&lt;br /&gt;
function replaceExpression(exp) {&lt;br /&gt;
    // コメント扱いの記載を削除&lt;br /&gt;
    let exp_replaced = exp.replace(/`[^`]*`/g, &amp;quot; &amp;quot;)&lt;br /&gt;
&lt;br /&gt;
    // 他の言語で対応する演算子などはあらかじめエラーにする&lt;br /&gt;
    const invalidOperators = [&amp;quot;\\*\\*&amp;quot;, &amp;quot;//&amp;quot;];&lt;br /&gt;
    for (var i = 0; i &amp;lt; invalidOperators.length; i++) {&lt;br /&gt;
        var operator = invalidOperators[i];&lt;br /&gt;
        if (exp.match(new RegExp(operator))) {&lt;br /&gt;
            throw new Error(&amp;quot;演算子「&amp;quot; + operator + &amp;quot;」はサポートされていません&amp;quot;);&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    exp_replaced = exp_replaced.trim()&lt;br /&gt;
        // 先頭の = を削除&lt;br /&gt;
        .replace(/^= /, &amp;quot;&amp;quot;)&lt;br /&gt;
        // 改行を ; に置換&lt;br /&gt;
        .replace(/\n/g, &amp;quot;;&amp;quot;)&lt;br /&gt;
        // 非ASCII文字を削除&lt;br /&gt;
        .replace(/[^\x20-\x7e]/g, &amp;quot; &amp;quot;)&lt;br /&gt;
        // 全角スペースを半角スペースに変換&lt;br /&gt;
        .replace(/　/g, &amp;quot; &amp;quot;)&lt;br /&gt;
        // 数字内のカンマを取り除く。j(1, 2) では削除しない&lt;br /&gt;
        .replace(/j *\(\d+, *\d+\)|,/g, function (match) {&lt;br /&gt;
            return match.match(/j/) ? match : &amp;quot;&amp;quot;;&lt;br /&gt;
        })&lt;br /&gt;
        // 変数形式を退避してからドル記号削除&lt;br /&gt;
        .replace(/(\$[a-zA-Z][a-zA-Z0-9]*)/g, &amp;quot; %%%$1%%% &amp;quot;)&lt;br /&gt;
        .replace(/\$/g, &amp;quot;&amp;quot;)&lt;br /&gt;
        // 空の括弧を削除&lt;br /&gt;
        .replace(/\(\s*\)|\[\s*\]|\{\s*\}/g, &amp;quot; &amp;quot;)&lt;br /&gt;
        // 対応関数以外の文字列は削除&lt;br /&gt;
        .replace(/[a-zA-Z]+/g, function (match) {&lt;br /&gt;
            const functions = [&amp;quot;s&amp;quot;, &amp;quot;c&amp;quot;, &amp;quot;a&amp;quot;, &amp;quot;l&amp;quot;, &amp;quot;e&amp;quot;, &amp;quot;sqrt&amp;quot;, &amp;quot;j&amp;quot;];&lt;br /&gt;
            for (var i = 0; i &amp;lt; functions.length; i++) {&lt;br /&gt;
                var f = functions[i];&lt;br /&gt;
                if (match === f) {&lt;br /&gt;
                    return match;&lt;br /&gt;
                }&lt;br /&gt;
            }&lt;br /&gt;
            return &amp;quot; &amp;quot;;&lt;br /&gt;
        })&lt;br /&gt;
        // `/hour` などの文字列があるとスラッシュだけ残るためスラッシュを削除&lt;br /&gt;
        .replace(/\/\s*([,+\-*\/%^&amp;amp;~|&amp;lt;&amp;gt;?/)\];\n]|$)/g, &amp;quot;$1&amp;quot;)&lt;br /&gt;
        // 退避文字を復元&lt;br /&gt;
        .replace(/ %%%\$([a-zA-Z][a-zA-Z0-9]*)%%% /g, &amp;quot; $1 &amp;quot;)&lt;br /&gt;
        .replace(/ %%%([^%]+)%%% /g, &amp;quot; $1 &amp;quot;)&lt;br /&gt;
&lt;br /&gt;
    return exp_replaced;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function help() {&lt;br /&gt;
    return &amp;quot;【Mery 電卓 (bc コマンド使用版) で使用可能な演算子と関数】\n&amp;quot; +&lt;br /&gt;
        &amp;quot;\n■基本演算子:\n&amp;quot; +&lt;br /&gt;
        &amp;quot;`+` (加算), `-` (減算), `*` (乗算), `/` (除算), `%` (剰余), `^` (べき乗)\n&amp;quot; +&lt;br /&gt;
        &amp;quot;\n■比較演算子:\n&amp;quot; +&lt;br /&gt;
        &amp;quot;`==` (等しい), `!=` (等しくない), `&amp;lt;` (より小さい), `&amp;lt;=` (以下), `&amp;gt;` (より大きい), `&amp;gt;=` (以上)\n&amp;quot; +&lt;br /&gt;
        &amp;quot;\n■代入演算子:\n&amp;quot; +&lt;br /&gt;
        &amp;quot;`=`, `+=`, `-=`, `*=`, `/=`, `%=`, `^=`\n&amp;quot; +&lt;br /&gt;
        &amp;quot;\n■インクリメント・デクリメント:\n&amp;quot; +&lt;br /&gt;
        &amp;quot;`++`, `--`\n&amp;quot; +&lt;br /&gt;
        &amp;quot;\n■数学関数 (要 `-l` オプション):\n&amp;quot; +&lt;br /&gt;
        &amp;quot;- `s(x)`: サイン（引数はラジアン）\n&amp;quot; +&lt;br /&gt;
        &amp;quot;- `c(x)`: コサイン（引数はラジアン）\n&amp;quot; +&lt;br /&gt;
        &amp;quot;- `a(x)`: アークタンジェント（返り値はラジアン）\n&amp;quot; +&lt;br /&gt;
        &amp;quot;- `l(x)`: 自然対数\n&amp;quot; +&lt;br /&gt;
        &amp;quot;- `e(x)`: 指数関数\n&amp;quot; +&lt;br /&gt;
        &amp;quot;- `sqrt(x)`: 平方根\n&amp;quot; +&lt;br /&gt;
        &amp;quot;- `j(n,x)`: n次ベッセル関数\n&amp;quot; +&lt;br /&gt;
        &amp;quot;\n■変数の使い方:\n&amp;quot; +&lt;br /&gt;
        &amp;quot;- 変数への代入: $&amp;lt;変数名&amp;gt; = &amp;lt;値&amp;gt; （例: $a = 5, $pi = 4*a(1)）\n&amp;quot; +&lt;br /&gt;
        &amp;quot;- 変数の参照: 変数名を数式に記述 （例: $a + 3, $b * 2）\n&amp;quot; +&lt;br /&gt;
        &amp;quot;- 複数の式を実行: セミコロン ; で区切る （例: $a = 3; $b = 2; $a + $b）&amp;quot;;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== サードパーティライブラリ ==&lt;br /&gt;
&lt;br /&gt;
[https://frippery.org/busybox/ busybox64u.exe]&lt;br /&gt;
&lt;br /&gt;
== 更新履歴 ==&lt;br /&gt;
&lt;br /&gt;
* 2025-11-29 計算結果が「-0.1」のような1未満の負の小数になる場合に「-.1」のような表示になってしまう不具合を修正。小数計算精度を scale=10 から scale=15 へ調整&lt;br /&gt;
* 2025-10-26 「5 - 3.0」のような「小数以下が『.0』になる計算」で「.」だけが出力に残ってしまう不具合を修正&lt;br /&gt;
* 2025-10-25 公開&lt;/div&gt;</summary>
		<author><name>Yuko</name></author>
	</entry>
	<entry>
		<id>https://www.haijin-boys.com/wiki/index.php?title=Mery_%E9%9B%BB%E5%8D%93_(bc_%E3%82%B3%E3%83%9E%E3%83%B3%E3%83%89%E4%BD%BF%E7%94%A8%E7%89%88)&amp;diff=9320</id>
		<title>Mery 電卓 (bc コマンド使用版)</title>
		<link rel="alternate" type="text/html" href="https://www.haijin-boys.com/wiki/index.php?title=Mery_%E9%9B%BB%E5%8D%93_(bc_%E3%82%B3%E3%83%9E%E3%83%B3%E3%83%89%E4%BD%BF%E7%94%A8%E7%89%88)&amp;diff=9320"/>
		<updated>2025-11-29T04:12:31Z</updated>

		<summary type="html">&lt;p&gt;Yuko: /* ソースコード */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== 概要 ==&lt;br /&gt;
&lt;br /&gt;
「Mery 電卓 (bc コマンド使用版)」は、 [[Mery 電卓]] の派生版で、Meryエディタ上で数式を計算するためのマクロです。オリジナルの「Mery 電卓」よりも対応する数学関数が少ない代わりに起動が早いため、日々の利用の中で軽快に使用することができます。&lt;br /&gt;
&lt;br /&gt;
Linux でお馴染みの bc コマンドを計算エンジンとしており、標準的な数式と一部の数学関数をサポートしています。カーソル位置の行または選択したテキストを数式として解釈し、結果を表示します。計算結果は自動的にクリップボードにコピーされ、カンマ区切りの数値として表示されます。&lt;br /&gt;
&lt;br /&gt;
本マクロをショートカットキーに設定し、いつでも起動できるようにしておくと日常シーンで何かと便利です。作者は「Ctrl + Alt + Enter」キーに割り当てて利用しています。&lt;br /&gt;
&lt;br /&gt;
== 注意事項 ==&lt;br /&gt;
&lt;br /&gt;
* Mery Ver 3.7.19 で動作確認しています。&lt;br /&gt;
&lt;br /&gt;
== 使い方 ==&lt;br /&gt;
&lt;br /&gt;
0. &#039;&#039;&#039;事前準備&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
* 本マクロでは計算コマンドの「bc」を内部で使っています。当該コマンドを使うために「BusyBox for Windows」を利用します&lt;br /&gt;
** ダウンロード先: https://frippery.org/busybox/&lt;br /&gt;
** ダウンロードするファイル: busybox64u.exe ※ダウンロード後、マクロファイルと同じフォルダーに配置してください&lt;br /&gt;
*** Windows 10 (1903) 以降の環境では上記を推奨。それ以前の環境の場合は動作しない可能性があるため、適切な実行ファイルを選択してください。&lt;br /&gt;
&lt;br /&gt;
1. &#039;&#039;&#039;行全体を計算する場合&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* 計算したい数式が書かれた行にカーソルを置きます。&lt;br /&gt;
* マクロを実行すると、次の行に「= 計算結果」が表示されます。&lt;br /&gt;
* 既に結果行がある場合は、その行が更新されます。&lt;br /&gt;
&lt;br /&gt;
2. &#039;&#039;&#039;選択範囲を計算する場合&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* 計算したい数式部分を選択します。&lt;br /&gt;
* マクロを実行すると、選択範囲の後ろに「計算式 = 計算結果」が挿入されます。&lt;br /&gt;
&lt;br /&gt;
3. &#039;&#039;&#039;サポートされている演算子&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* 四則演算（+, -, *, /）&lt;br /&gt;
* べき乗（^）&lt;br /&gt;
* 剰余（%） ※整数の剰余にのみ対応&lt;br /&gt;
* 比較演算子（&amp;lt;, &amp;gt;, &amp;lt;=, &amp;gt;=, ==, !=）&lt;br /&gt;
&lt;br /&gt;
4. &#039;&#039;&#039;サポートされている関数&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* s(x): サイン（引数はラジアン）&lt;br /&gt;
* c(x): コサイン（引数はラジアン）&lt;br /&gt;
* a(x): アークタンジェント（返り値はラジアン）&lt;br /&gt;
* l(x): 自然対数&lt;br /&gt;
* e(x): 指数関数&lt;br /&gt;
* sqrt(x): 平方根&lt;br /&gt;
* j(n,x): n次ベッセル関数&lt;br /&gt;
&lt;br /&gt;
5. &#039;&#039;&#039;変数の利用&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* 変数への代入: $&amp;lt;変数名&amp;gt; = &amp;lt;値&amp;gt; （例: $a = 5, $pi = 4*a(1)）&lt;br /&gt;
* 変数の参照: 変数名を数式に記述 （例: $a + 3, $b * 2）&lt;br /&gt;
* 複数の式を実行: セミコロン ; で区切る （例: $a = 3; $b = 2; $a + $b）&lt;br /&gt;
* インクリメント・デクリメント: ++, -- (例: $a++, ++$a)&lt;br /&gt;
&lt;br /&gt;
6. &#039;&#039;&#039;特別な機能&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* カンマ区切り表示: 計算結果は読みやすいようにカンマ区切りで表示されます。変数「COMMA」を調整することで制御できます。&lt;br /&gt;
* 自動コピー: 計算結果は自動的にクリップボードにコピーされます。変数「COPY」を調整することで制御できます。&lt;br /&gt;
* 計算結果の再利用: 行頭の「= 」は計算実行時に無視されるため、計算結果をスムーズに次の式として利用できます。&lt;br /&gt;
* 変数の利用: 複数行を選択して実行、または一行内で「;」で数式を区切ることで、変数を使った数式が利用できます。変数形式は「$[a-zA-Z][a-zA-Z0-9]*」に対応しています。例えば「$a = 2; $a * 3」として実行すると、結果は「6」と出力されます。&lt;br /&gt;
* メモ書きから計算: 数字や関数以外の文字はできるだけ除外した上で計算をします。例えば「月100円 * 12ヶ月」のようなメモ書きに対して実行すると「1,200」と出力されます。また「` (バッククォート)」で囲んだ文字は確実に無視されるため、計算メモ用として使うことができます (使用例: 『1500円 `金額未定のため仮置き`』)。&lt;br /&gt;
&lt;br /&gt;
== ソースコード ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot; copy&amp;gt;&lt;br /&gt;
#title = &amp;quot;Mery 電卓 (bc コマンド使用版)&amp;quot;&lt;br /&gt;
#include &amp;quot;Common.js&amp;quot;&lt;br /&gt;
BeginUndoGroup();&lt;br /&gt;
&lt;br /&gt;
// 事前準備:&lt;br /&gt;
//   以下のサイトから busybox64u.exe を入手し、本マクロと同じフォルダーに配置することで動作します。&lt;br /&gt;
//   https://frippery.org/busybox/&lt;br /&gt;
&lt;br /&gt;
// 結果をクリップボードにコピーするか&lt;br /&gt;
var COPY = true;&lt;br /&gt;
// 出力結果をカンマ区切りにするするか&lt;br /&gt;
var COMMA = true;&lt;br /&gt;
&lt;br /&gt;
// busybox のパスを取得&lt;br /&gt;
const scriptDir = ScriptFullName.replace(/\\[^\\]+$/g, &amp;quot;&amp;quot;)&lt;br /&gt;
const busyboxPath = scriptDir + &amp;quot;\\busybox64u.exe&amp;quot;;&lt;br /&gt;
if (!shell.FileExists(busyboxPath)) {&lt;br /&gt;
    alert(&amp;quot;busybox64u.exe が見つかりません。\nこのマクロを動作させるには、busybox64u.exe を本マクロと同じフォルダーに配置する必要があります。&amp;quot;);&lt;br /&gt;
    Quit();&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
var sel = document.selection;&lt;br /&gt;
&lt;br /&gt;
if (sel.Count &amp;gt; 1) {&lt;br /&gt;
    // マルチカーソル実行時は計算中表示を出してから実行&lt;br /&gt;
    ShowTip(&amp;quot;計算中... (全 &amp;quot; + sel.Count + &amp;quot; 件)&amp;quot;, meShowTipPosCaret);&lt;br /&gt;
    doMultiEdit(main);&lt;br /&gt;
    ShowTip(&amp;quot;&amp;quot;, meShowTipHide);&lt;br /&gt;
} else {&lt;br /&gt;
    main();&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function main() {&lt;br /&gt;
    var isEmpty = sel.IsEmpty;&lt;br /&gt;
    var activePointY = sel.GetActivePointY(mePosLogical);&lt;br /&gt;
    if (isEmpty) {&lt;br /&gt;
        var exp = document.GetLine(activePointY);&lt;br /&gt;
    } else {&lt;br /&gt;
        var exp = sel.Text;&lt;br /&gt;
    }&lt;br /&gt;
    // 空白行の場合は何もしない&lt;br /&gt;
    if (exp.replace(/[\s　]+/g, &amp;quot;&amp;quot;) === &amp;quot;&amp;quot;) {&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // 計算実行&lt;br /&gt;
    try {&lt;br /&gt;
        var exp_replaced = replaceExpression(exp)&lt;br /&gt;
            .replace(/([^;]*%[^;]*)/g, &amp;quot;scale=0; $1; scale=15; &amp;quot;); // 剰余計算を整数にする&lt;br /&gt;
        const exec = shell.Exec(busyboxPath + &amp;quot; bc -l&amp;quot;, &amp;quot;scale=15; &amp;quot; + exp_replaced);&lt;br /&gt;
        if (exec.ExitCode !== 0) {&lt;br /&gt;
            throw new Error(&amp;quot;計算エラー: &amp;quot; + exec.StdErr);&lt;br /&gt;
        }&lt;br /&gt;
        // bc の出力のクセに合わせて整形する&lt;br /&gt;
        var result_str = exec.StdOut&lt;br /&gt;
            .trim()&lt;br /&gt;
            .replace(/^(-)?\./gm, &amp;quot;$10.&amp;quot;)&lt;br /&gt;
            .replace(/(\.[0-9]*?)0+$/gm, &amp;quot;$1&amp;quot;)&lt;br /&gt;
            .replace(/\.$/gm, &amp;quot;&amp;quot;);&lt;br /&gt;
    } catch (e) {&lt;br /&gt;
        if (exp_replaced === undefined) {&lt;br /&gt;
            alert(&amp;quot;エラー発生&amp;quot; + &amp;quot;\n------\n&amp;quot; + e)&lt;br /&gt;
        } else {&lt;br /&gt;
            alert(&amp;quot;整形後の数式で計算エラーが発生&amp;quot; + &amp;quot;\n------\n&amp;quot; + e + &amp;quot;\n元の数式: &amp;quot; + exp + &amp;quot;\n整形後の数式: &amp;quot; + exp_replaced);&lt;br /&gt;
        }&lt;br /&gt;
        var c = Confirm(&amp;quot;数式ヘルプを表示しますか？&amp;quot;);&lt;br /&gt;
        if (c) {&lt;br /&gt;
            editor.NewFile();&lt;br /&gt;
            var newDoc = editor.ActiveDocument;&lt;br /&gt;
            newDoc.Write(help());&lt;br /&gt;
            newDoc.selection.SetActivePos(0);&lt;br /&gt;
        }&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // 小数点以上のみカンマ区切り&lt;br /&gt;
    if (COMMA &amp;amp;&amp;amp; result_str.match(/^[0-9]+(\.[0-9]+)?$/m)) {&lt;br /&gt;
        var result_parts = result_str.split(&amp;quot;.&amp;quot;);&lt;br /&gt;
        result_parts[0] = result_parts[0].replace(/(\d)(?=(\d{3})+$)/gm, &amp;quot;$1,&amp;quot;);&lt;br /&gt;
        if (result_parts.length &amp;gt; 1) {&lt;br /&gt;
            result_str = result_parts.join(&amp;quot;.&amp;quot;);&lt;br /&gt;
        } else {&lt;br /&gt;
            result_str = result_parts[0];&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // bc コマンドでは複数個の計算出力があると計算結果が複数行になるので1行にまとめる&lt;br /&gt;
    result_str = result_str.replace(/\r?\n/g, &amp;quot; ; &amp;quot;);&lt;br /&gt;
&lt;br /&gt;
    // 計算行が = 始まりの場合、= を削除する&lt;br /&gt;
    if (isEmpty &amp;amp;&amp;amp; exp.match(/^= /)) {&lt;br /&gt;
        sel.SetActivePoint(mePosLogical, 1, activePointY, false);&lt;br /&gt;
        sel.EndOfLine(true, mePosLogical);&lt;br /&gt;
        sel.Text = sel.Text.replace(/^= /, &amp;quot;&amp;quot;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // 既存の計算結果を上書きするか判定&lt;br /&gt;
    var pattern = /^=\s*(-?[0-9.,]+(\s*;\s*)?)*\s*$/;&lt;br /&gt;
    var nextLine = document.GetLine(activePointY + 1);&lt;br /&gt;
    if (isEmpty &amp;amp;&amp;amp; nextLine.match(pattern)) {&lt;br /&gt;
        sel.SetActivePoint(mePosLogical, 1, activePointY + 1, false);&lt;br /&gt;
        sel.EndOfLine(true, mePosLogical);&lt;br /&gt;
        sel.Text = &#039;= &#039; + result_str&lt;br /&gt;
    } else if (isEmpty) {&lt;br /&gt;
        sel.LineOpen();&lt;br /&gt;
        sel.Text = &#039;= &#039; + result_str&lt;br /&gt;
    } else {&lt;br /&gt;
        sel.CharRight();&lt;br /&gt;
        const inputExp = exp.replace(/((?!;)\n)/mg, &amp;quot;; &amp;quot;).replace(/;\s*;/g, &amp;quot;;&amp;quot;).replace(/　/g, &amp;quot; &amp;quot;).replace(/;\s*$/, &amp;quot;&amp;quot;).trim();&lt;br /&gt;
        var newLine = &amp;quot;&amp;quot;;&lt;br /&gt;
        if (exp.slice(-1) !== &amp;quot;\n&amp;quot;) {&lt;br /&gt;
            newLine = &#039;\n&#039;;&lt;br /&gt;
        }&lt;br /&gt;
        sel.Text = newLine + inputExp + &#039;\n= &#039; + result_str + &#039;\n&#039;;&lt;br /&gt;
        sel.CharLeft();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (COPY) {&lt;br /&gt;
        ClipboardData.SetData(result_str);&lt;br /&gt;
        Status = &amp;quot;計算結果 &amp;quot; + result_str + &amp;quot; をコピーしました&amp;quot;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/**&lt;br /&gt;
 * 数式を置換する&lt;br /&gt;
 */&lt;br /&gt;
function replaceExpression(exp) {&lt;br /&gt;
    // コメント扱いの記載を削除&lt;br /&gt;
    let exp_replaced = exp.replace(/`[^`]*`/g, &amp;quot; &amp;quot;)&lt;br /&gt;
&lt;br /&gt;
    // 他の言語で対応する演算子などはあらかじめエラーにする&lt;br /&gt;
    const invalidOperators = [&amp;quot;\\*\\*&amp;quot;, &amp;quot;//&amp;quot;];&lt;br /&gt;
    for (var i = 0; i &amp;lt; invalidOperators.length; i++) {&lt;br /&gt;
        var operator = invalidOperators[i];&lt;br /&gt;
        if (exp.match(new RegExp(operator))) {&lt;br /&gt;
            throw new Error(&amp;quot;演算子「&amp;quot; + operator + &amp;quot;」はサポートされていません&amp;quot;);&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    exp_replaced = exp_replaced.trim()&lt;br /&gt;
        // 先頭の = を削除&lt;br /&gt;
        .replace(/^= /, &amp;quot;&amp;quot;)&lt;br /&gt;
        // 改行を ; に置換&lt;br /&gt;
        .replace(/\n/g, &amp;quot;;&amp;quot;)&lt;br /&gt;
        // 非ASCII文字を削除&lt;br /&gt;
        .replace(/[^\x20-\x7e]/g, &amp;quot; &amp;quot;)&lt;br /&gt;
        // 全角スペースを半角スペースに変換&lt;br /&gt;
        .replace(/　/g, &amp;quot; &amp;quot;)&lt;br /&gt;
        // 数字内のカンマを取り除く。j(1, 2) では削除しない&lt;br /&gt;
        .replace(/j *\(\d+, *\d+\)|,/g, function (match) {&lt;br /&gt;
            return match.match(/j/) ? match : &amp;quot;&amp;quot;;&lt;br /&gt;
        })&lt;br /&gt;
        // 変数形式を退避してからドル記号削除&lt;br /&gt;
        .replace(/(\$[a-zA-Z][a-zA-Z0-9]*)/g, &amp;quot; %%%$1%%% &amp;quot;)&lt;br /&gt;
        .replace(/\$/g, &amp;quot;&amp;quot;)&lt;br /&gt;
        // 空の括弧を削除&lt;br /&gt;
        .replace(/\(\s*\)|\[\s*\]|\{\s*\}/g, &amp;quot; &amp;quot;)&lt;br /&gt;
        // 対応関数以外の文字列は削除&lt;br /&gt;
        .replace(/[a-zA-Z]+/g, function (match) {&lt;br /&gt;
            const functions = [&amp;quot;s&amp;quot;, &amp;quot;c&amp;quot;, &amp;quot;a&amp;quot;, &amp;quot;l&amp;quot;, &amp;quot;e&amp;quot;, &amp;quot;sqrt&amp;quot;, &amp;quot;j&amp;quot;];&lt;br /&gt;
            for (var i = 0; i &amp;lt; functions.length; i++) {&lt;br /&gt;
                var f = functions[i];&lt;br /&gt;
                if (match === f) {&lt;br /&gt;
                    return match;&lt;br /&gt;
                }&lt;br /&gt;
            }&lt;br /&gt;
            return &amp;quot; &amp;quot;;&lt;br /&gt;
        })&lt;br /&gt;
        // `/hour` などの文字列があるとスラッシュだけ残るためスラッシュを削除&lt;br /&gt;
        .replace(/\/\s*([,+\-*\/%^&amp;amp;~|&amp;lt;&amp;gt;?/)\];\n]|$)/g, &amp;quot;$1&amp;quot;)&lt;br /&gt;
        // 退避文字を復元&lt;br /&gt;
        .replace(/ %%%\$([a-zA-Z][a-zA-Z0-9]*)%%% /g, &amp;quot; $1 &amp;quot;)&lt;br /&gt;
        .replace(/ %%%([^%]+)%%% /g, &amp;quot; $1 &amp;quot;)&lt;br /&gt;
&lt;br /&gt;
    return exp_replaced;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function help() {&lt;br /&gt;
    return &amp;quot;【Mery 電卓 (bc コマンド使用版) で使用可能な演算子と関数】\n&amp;quot; +&lt;br /&gt;
        &amp;quot;\n■基本演算子:\n&amp;quot; +&lt;br /&gt;
        &amp;quot;`+` (加算), `-` (減算), `*` (乗算), `/` (除算), `%` (剰余), `^` (べき乗)\n&amp;quot; +&lt;br /&gt;
        &amp;quot;\n■比較演算子:\n&amp;quot; +&lt;br /&gt;
        &amp;quot;`==` (等しい), `!=` (等しくない), `&amp;lt;` (より小さい), `&amp;lt;=` (以下), `&amp;gt;` (より大きい), `&amp;gt;=` (以上)\n&amp;quot; +&lt;br /&gt;
        &amp;quot;\n■代入演算子:\n&amp;quot; +&lt;br /&gt;
        &amp;quot;`=`, `+=`, `-=`, `*=`, `/=`, `%=`, `^=`\n&amp;quot; +&lt;br /&gt;
        &amp;quot;\n■インクリメント・デクリメント:\n&amp;quot; +&lt;br /&gt;
        &amp;quot;`++`, `--`\n&amp;quot; +&lt;br /&gt;
        &amp;quot;\n■数学関数 (要 `-l` オプション):\n&amp;quot; +&lt;br /&gt;
        &amp;quot;- `s(x)`: サイン（引数はラジアン）\n&amp;quot; +&lt;br /&gt;
        &amp;quot;- `c(x)`: コサイン（引数はラジアン）\n&amp;quot; +&lt;br /&gt;
        &amp;quot;- `a(x)`: アークタンジェント（返り値はラジアン）\n&amp;quot; +&lt;br /&gt;
        &amp;quot;- `l(x)`: 自然対数\n&amp;quot; +&lt;br /&gt;
        &amp;quot;- `e(x)`: 指数関数\n&amp;quot; +&lt;br /&gt;
        &amp;quot;- `sqrt(x)`: 平方根\n&amp;quot; +&lt;br /&gt;
        &amp;quot;- `j(n,x)`: n次ベッセル関数\n&amp;quot; +&lt;br /&gt;
        &amp;quot;\n■変数の使い方:\n&amp;quot; +&lt;br /&gt;
        &amp;quot;- 変数への代入: $&amp;lt;変数名&amp;gt; = &amp;lt;値&amp;gt; （例: $a = 5, $pi = 4*a(1)）\n&amp;quot; +&lt;br /&gt;
        &amp;quot;- 変数の参照: 変数名を数式に記述 （例: $a + 3, $b * 2）\n&amp;quot; +&lt;br /&gt;
        &amp;quot;- 複数の式を実行: セミコロン ; で区切る （例: $a = 3; $b = 2; $a + $b）&amp;quot;;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== サードパーティライブラリ ==&lt;br /&gt;
&lt;br /&gt;
[https://frippery.org/busybox/ busybox64u.exe]&lt;br /&gt;
&lt;br /&gt;
== 更新履歴 ==&lt;br /&gt;
&lt;br /&gt;
* 2025-10-25 公開&lt;br /&gt;
* 2025-10-26 「5 - 3.0」のような「小数以下が『.0』になる計算」で「.」だけが出力に残ってしまう不具合を修正&lt;/div&gt;</summary>
		<author><name>Yuko</name></author>
	</entry>
	<entry>
		<id>https://www.haijin-boys.com/wiki/index.php?title=Mery_%E9%9B%BB%E5%8D%93_(bc_%E3%82%B3%E3%83%9E%E3%83%B3%E3%83%89%E4%BD%BF%E7%94%A8%E7%89%88)&amp;diff=9247</id>
		<title>Mery 電卓 (bc コマンド使用版)</title>
		<link rel="alternate" type="text/html" href="https://www.haijin-boys.com/wiki/index.php?title=Mery_%E9%9B%BB%E5%8D%93_(bc_%E3%82%B3%E3%83%9E%E3%83%B3%E3%83%89%E4%BD%BF%E7%94%A8%E7%89%88)&amp;diff=9247"/>
		<updated>2025-10-26T02:57:29Z</updated>

		<summary type="html">&lt;p&gt;Yuko: /* 更新履歴 */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== 概要 ==&lt;br /&gt;
&lt;br /&gt;
「Mery 電卓 (bc コマンド使用版)」は、 [[Mery 電卓]] の派生版で、Meryエディタ上で数式を計算するためのマクロです。オリジナルの「Mery 電卓」よりも対応する数学関数が少ない代わりに起動が早いため、日々の利用の中で軽快に使用することができます。&lt;br /&gt;
&lt;br /&gt;
Linux でお馴染みの bc コマンドを計算エンジンとしており、標準的な数式と一部の数学関数をサポートしています。カーソル位置の行または選択したテキストを数式として解釈し、結果を表示します。計算結果は自動的にクリップボードにコピーされ、カンマ区切りの数値として表示されます。&lt;br /&gt;
&lt;br /&gt;
本マクロをショートカットキーに設定し、いつでも起動できるようにしておくと日常シーンで何かと便利です。作者は「Ctrl + Alt + Enter」キーに割り当てて利用しています。&lt;br /&gt;
&lt;br /&gt;
== 注意事項 ==&lt;br /&gt;
&lt;br /&gt;
* Mery Ver 3.7.19 で動作確認しています。&lt;br /&gt;
&lt;br /&gt;
== 使い方 ==&lt;br /&gt;
&lt;br /&gt;
0. &#039;&#039;&#039;事前準備&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
* 本マクロでは計算コマンドの「bc」を内部で使っています。当該コマンドを使うために「BusyBox for Windows」を利用します&lt;br /&gt;
** ダウンロード先: https://frippery.org/busybox/&lt;br /&gt;
** ダウンロードするファイル: busybox64u.exe ※ダウンロード後、マクロファイルと同じフォルダーに配置してください&lt;br /&gt;
*** Windows 10 (1903) 以降の環境では上記を推奨。それ以前の環境の場合は動作しない可能性があるため、適切な実行ファイルを選択してください。&lt;br /&gt;
&lt;br /&gt;
1. &#039;&#039;&#039;行全体を計算する場合&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* 計算したい数式が書かれた行にカーソルを置きます。&lt;br /&gt;
* マクロを実行すると、次の行に「= 計算結果」が表示されます。&lt;br /&gt;
* 既に結果行がある場合は、その行が更新されます。&lt;br /&gt;
&lt;br /&gt;
2. &#039;&#039;&#039;選択範囲を計算する場合&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* 計算したい数式部分を選択します。&lt;br /&gt;
* マクロを実行すると、選択範囲の後ろに「計算式 = 計算結果」が挿入されます。&lt;br /&gt;
&lt;br /&gt;
3. &#039;&#039;&#039;サポートされている演算子&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* 四則演算（+, -, *, /）&lt;br /&gt;
* べき乗（^）&lt;br /&gt;
* 剰余（%） ※整数の剰余にのみ対応&lt;br /&gt;
* 比較演算子（&amp;lt;, &amp;gt;, &amp;lt;=, &amp;gt;=, ==, !=）&lt;br /&gt;
&lt;br /&gt;
4. &#039;&#039;&#039;サポートされている関数&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* s(x): サイン（引数はラジアン）&lt;br /&gt;
* c(x): コサイン（引数はラジアン）&lt;br /&gt;
* a(x): アークタンジェント（返り値はラジアン）&lt;br /&gt;
* l(x): 自然対数&lt;br /&gt;
* e(x): 指数関数&lt;br /&gt;
* sqrt(x): 平方根&lt;br /&gt;
* j(n,x): n次ベッセル関数&lt;br /&gt;
&lt;br /&gt;
5. &#039;&#039;&#039;変数の利用&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* 変数への代入: $&amp;lt;変数名&amp;gt; = &amp;lt;値&amp;gt; （例: $a = 5, $pi = 4*a(1)）&lt;br /&gt;
* 変数の参照: 変数名を数式に記述 （例: $a + 3, $b * 2）&lt;br /&gt;
* 複数の式を実行: セミコロン ; で区切る （例: $a = 3; $b = 2; $a + $b）&lt;br /&gt;
* インクリメント・デクリメント: ++, -- (例: $a++, ++$a)&lt;br /&gt;
&lt;br /&gt;
6. &#039;&#039;&#039;特別な機能&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* カンマ区切り表示: 計算結果は読みやすいようにカンマ区切りで表示されます。変数「COMMA」を調整することで制御できます。&lt;br /&gt;
* 自動コピー: 計算結果は自動的にクリップボードにコピーされます。変数「COPY」を調整することで制御できます。&lt;br /&gt;
* 計算結果の再利用: 行頭の「= 」は計算実行時に無視されるため、計算結果をスムーズに次の式として利用できます。&lt;br /&gt;
* 変数の利用: 複数行を選択して実行、または一行内で「;」で数式を区切ることで、変数を使った数式が利用できます。変数形式は「$[a-zA-Z][a-zA-Z0-9]*」に対応しています。例えば「$a = 2; $a * 3」として実行すると、結果は「6」と出力されます。&lt;br /&gt;
* メモ書きから計算: 数字や関数以外の文字はできるだけ除外した上で計算をします。例えば「月100円 * 12ヶ月」のようなメモ書きに対して実行すると「1,200」と出力されます。また「` (バッククォート)」で囲んだ文字は確実に無視されるため、計算メモ用として使うことができます (使用例: 『1500円 `金額未定のため仮置き`』)。&lt;br /&gt;
&lt;br /&gt;
== ソースコード ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot; copy&amp;gt;&lt;br /&gt;
#title = &amp;quot;Mery 電卓 (bc コマンド使用版)&amp;quot;&lt;br /&gt;
#include &amp;quot;Common.js&amp;quot;&lt;br /&gt;
BeginUndoGroup();&lt;br /&gt;
&lt;br /&gt;
// 事前準備:&lt;br /&gt;
//   以下のサイトから busybox64u.exe を入手し、本マクロと同じフォルダーに配置することで動作します。&lt;br /&gt;
//   https://frippery.org/busybox/&lt;br /&gt;
&lt;br /&gt;
// 結果をクリップボードにコピーするか&lt;br /&gt;
var COPY = true;&lt;br /&gt;
// 出力結果をカンマ区切りにするするか&lt;br /&gt;
var COMMA = true;&lt;br /&gt;
&lt;br /&gt;
// busybox のパスを取得&lt;br /&gt;
const scriptDir = ScriptFullName.replace(/\\[^\\]+$/g, &amp;quot;&amp;quot;)&lt;br /&gt;
const busyboxPath = scriptDir + &amp;quot;\\busybox64u.exe&amp;quot;;&lt;br /&gt;
if (!shell.FileExists(busyboxPath)) {&lt;br /&gt;
    alert(&amp;quot;busybox64u.exe が見つかりません。\nこのマクロを動作させるには、busybox64u.exe を本マクロと同じフォルダーに配置する必要があります。&amp;quot;);&lt;br /&gt;
    Quit();&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
var sel = document.selection;&lt;br /&gt;
&lt;br /&gt;
if (sel.Count &amp;gt; 1) {&lt;br /&gt;
    // マルチカーソル実行時は計算中表示を出してから実行&lt;br /&gt;
    ShowTip(&amp;quot;計算中... (全 &amp;quot; + sel.Count + &amp;quot; 件)&amp;quot;, meShowTipPosCaret);&lt;br /&gt;
    doMultiEdit(main);&lt;br /&gt;
    ShowTip(&amp;quot;&amp;quot;, meShowTipHide);&lt;br /&gt;
} else {&lt;br /&gt;
    main();&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function main() {&lt;br /&gt;
    var isEmpty = sel.IsEmpty;&lt;br /&gt;
    var activePointY = sel.GetActivePointY(mePosLogical);&lt;br /&gt;
    if (isEmpty) {&lt;br /&gt;
        var exp = document.GetLine(activePointY);&lt;br /&gt;
    } else {&lt;br /&gt;
        var exp = sel.Text;&lt;br /&gt;
    }&lt;br /&gt;
    // 空白行の場合は何もしない&lt;br /&gt;
    if (exp.replace(/[\s　]+/g, &amp;quot;&amp;quot;) === &amp;quot;&amp;quot;) {&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // 計算実行&lt;br /&gt;
    try {&lt;br /&gt;
        var exp_replaced = replaceExpression(exp)&lt;br /&gt;
            .replace(/([^;]*%[^;]*)/g, &amp;quot;scale=0; $1; scale=10; &amp;quot;); // 剰余計算を整数にする&lt;br /&gt;
        const exec = shell.Exec(busyboxPath + &amp;quot; bc -l&amp;quot;, &amp;quot;scale=10; &amp;quot; + exp_replaced);&lt;br /&gt;
        if (exec.ExitCode !== 0) {&lt;br /&gt;
            throw new Error(&amp;quot;計算エラー: &amp;quot; + exec.StdErr);&lt;br /&gt;
        }&lt;br /&gt;
        // bc の出力のクセに合わせて整形する&lt;br /&gt;
        var result_str = exec.StdOut&lt;br /&gt;
            .trim()&lt;br /&gt;
            .replace(/^\./gm, &amp;quot;0.&amp;quot;)&lt;br /&gt;
            .replace(/(\.[0-9]*?)0+$/gm, &amp;quot;$1&amp;quot;)&lt;br /&gt;
            .replace(/\.$/gm, &amp;quot;&amp;quot;);&lt;br /&gt;
    } catch (e) {&lt;br /&gt;
        if (exp_replaced === undefined) {&lt;br /&gt;
            alert(&amp;quot;エラー発生&amp;quot; + &amp;quot;\n------\n&amp;quot; + e)&lt;br /&gt;
        } else {&lt;br /&gt;
            alert(&amp;quot;整形後の数式で計算エラーが発生&amp;quot; + &amp;quot;\n------\n&amp;quot; + e + &amp;quot;\n元の数式: &amp;quot; + exp + &amp;quot;\n整形後の数式: &amp;quot; + exp_replaced);&lt;br /&gt;
        }&lt;br /&gt;
        var c = Confirm(&amp;quot;数式ヘルプを表示しますか？&amp;quot;);&lt;br /&gt;
        if (c) {&lt;br /&gt;
            editor.NewFile();&lt;br /&gt;
            var newDoc = editor.ActiveDocument;&lt;br /&gt;
            newDoc.Write(help());&lt;br /&gt;
            newDoc.selection.SetActivePos(0);&lt;br /&gt;
        }&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // 小数点以上のみカンマ区切り&lt;br /&gt;
    if (COMMA &amp;amp;&amp;amp; result_str.match(/^[0-9]+(\.[0-9]+)?$/m)) {&lt;br /&gt;
        var result_parts = result_str.split(&amp;quot;.&amp;quot;);&lt;br /&gt;
        result_parts[0] = result_parts[0].replace(/(\d)(?=(\d{3})+$)/gm, &amp;quot;$1,&amp;quot;);&lt;br /&gt;
        if (result_parts.length &amp;gt; 1) {&lt;br /&gt;
            result_str = result_parts.join(&amp;quot;.&amp;quot;);&lt;br /&gt;
        } else {&lt;br /&gt;
            result_str = result_parts[0];&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // bc コマンドでは複数個の計算出力があると計算結果が複数行になるので1行にまとめる&lt;br /&gt;
    result_str = result_str.replace(/\r?\n/g, &amp;quot; ; &amp;quot;);&lt;br /&gt;
&lt;br /&gt;
    // 計算行が = 始まりの場合、= を削除する&lt;br /&gt;
    if (isEmpty &amp;amp;&amp;amp; exp.match(/^= /)) {&lt;br /&gt;
        sel.SetActivePoint(mePosLogical, 1, activePointY, false);&lt;br /&gt;
        sel.EndOfLine(true, mePosLogical);&lt;br /&gt;
        sel.Text = sel.Text.replace(/^= /, &amp;quot;&amp;quot;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // 既存の計算結果を上書きするか判定&lt;br /&gt;
    var pattern = /^=\s*(-?[0-9.,]+(\s*;\s*)?)*\s*$/;&lt;br /&gt;
    var nextLine = document.GetLine(activePointY + 1);&lt;br /&gt;
    if (isEmpty &amp;amp;&amp;amp; nextLine.match(pattern)) {&lt;br /&gt;
        sel.SetActivePoint(mePosLogical, 1, activePointY + 1, false);&lt;br /&gt;
        sel.EndOfLine(true, mePosLogical);&lt;br /&gt;
        sel.Text = &#039;= &#039; + result_str&lt;br /&gt;
    } else if (isEmpty) {&lt;br /&gt;
        sel.LineOpen();&lt;br /&gt;
        sel.Text = &#039;= &#039; + result_str&lt;br /&gt;
    } else {&lt;br /&gt;
        sel.CharRight();&lt;br /&gt;
        const inputExp = exp.replace(/((?!;)\n)/mg, &amp;quot;; &amp;quot;).replace(/;\s*;/g, &amp;quot;;&amp;quot;).replace(/　/g, &amp;quot; &amp;quot;).replace(/;\s*$/, &amp;quot;&amp;quot;).trim();&lt;br /&gt;
        var newLine = &amp;quot;&amp;quot;;&lt;br /&gt;
        if (exp.slice(-1) !== &amp;quot;\n&amp;quot;) {&lt;br /&gt;
            newLine = &#039;\n&#039;;&lt;br /&gt;
        }&lt;br /&gt;
        sel.Text = newLine + inputExp + &#039;\n= &#039; + result_str + &#039;\n&#039;;&lt;br /&gt;
        sel.CharLeft();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (COPY) {&lt;br /&gt;
        ClipboardData.SetData(result_str);&lt;br /&gt;
        Status = &amp;quot;計算結果 &amp;quot; + result_str + &amp;quot; をコピーしました&amp;quot;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/**&lt;br /&gt;
 * 数式を置換する&lt;br /&gt;
 */&lt;br /&gt;
function replaceExpression(exp) {&lt;br /&gt;
    // コメント扱いの記載を削除&lt;br /&gt;
    let exp_replaced = exp.replace(/`[^`]*`/g, &amp;quot; &amp;quot;)&lt;br /&gt;
&lt;br /&gt;
    // 他の言語で対応する演算子などはあらかじめエラーにする&lt;br /&gt;
    const invalidOperators = [&amp;quot;\\*\\*&amp;quot;, &amp;quot;//&amp;quot;];&lt;br /&gt;
    for (var i = 0; i &amp;lt; invalidOperators.length; i++) {&lt;br /&gt;
        var operator = invalidOperators[i];&lt;br /&gt;
        if (exp.match(new RegExp(operator))) {&lt;br /&gt;
            throw new Error(&amp;quot;演算子「&amp;quot; + operator + &amp;quot;」はサポートされていません&amp;quot;);&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    exp_replaced = exp_replaced.trim()&lt;br /&gt;
        // 先頭の = を削除&lt;br /&gt;
        .replace(/^= /, &amp;quot;&amp;quot;)&lt;br /&gt;
        // 改行を ; に置換&lt;br /&gt;
        .replace(/\n/g, &amp;quot;;&amp;quot;)&lt;br /&gt;
        // 非ASCII文字を削除&lt;br /&gt;
        .replace(/[^\x20-\x7e]/g, &amp;quot; &amp;quot;)&lt;br /&gt;
        // 全角スペースを半角スペースに変換&lt;br /&gt;
        .replace(/　/g, &amp;quot; &amp;quot;)&lt;br /&gt;
        // 数字内のカンマを取り除く。j(1, 2) では削除しない&lt;br /&gt;
        .replace(/j *\(\d+, *\d+\)|,/g, function (match) {&lt;br /&gt;
            return match.match(/j/) ? match : &amp;quot;&amp;quot;;&lt;br /&gt;
        })&lt;br /&gt;
        // 変数形式を退避してからドル記号削除&lt;br /&gt;
        .replace(/(\$[a-zA-Z][a-zA-Z0-9]*)/g, &amp;quot; %%%$1%%% &amp;quot;)&lt;br /&gt;
        .replace(/\$/g, &amp;quot;&amp;quot;)&lt;br /&gt;
        // 空の括弧を削除&lt;br /&gt;
        .replace(/\(\s*\)|\[\s*\]|\{\s*\}/g, &amp;quot; &amp;quot;)&lt;br /&gt;
        // 対応関数以外の文字列は削除&lt;br /&gt;
        .replace(/[a-zA-Z]+/g, function (match) {&lt;br /&gt;
            const functions = [&amp;quot;s&amp;quot;, &amp;quot;c&amp;quot;, &amp;quot;a&amp;quot;, &amp;quot;l&amp;quot;, &amp;quot;e&amp;quot;, &amp;quot;sqrt&amp;quot;, &amp;quot;j&amp;quot;];&lt;br /&gt;
            for (var i = 0; i &amp;lt; functions.length; i++) {&lt;br /&gt;
                var f = functions[i];&lt;br /&gt;
                if (match === f) {&lt;br /&gt;
                    return match;&lt;br /&gt;
                }&lt;br /&gt;
            }&lt;br /&gt;
            return &amp;quot; &amp;quot;;&lt;br /&gt;
        })&lt;br /&gt;
        // `/hour` などの文字列があるとスラッシュだけ残るためスラッシュを削除&lt;br /&gt;
        .replace(/\/\s*([,+\-*\/%^&amp;amp;~|&amp;lt;&amp;gt;?/)\];\n]|$)/g, &amp;quot;$1&amp;quot;)&lt;br /&gt;
        // 退避文字を復元&lt;br /&gt;
        .replace(/ %%%\$([a-zA-Z][a-zA-Z0-9]*)%%% /g, &amp;quot; $1 &amp;quot;)&lt;br /&gt;
        .replace(/ %%%([^%]+)%%% /g, &amp;quot; $1 &amp;quot;)&lt;br /&gt;
&lt;br /&gt;
    return exp_replaced;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function help() {&lt;br /&gt;
    return &amp;quot;【Mery 電卓 (bc コマンド使用版) で使用可能な演算子と関数】\n&amp;quot; +&lt;br /&gt;
        &amp;quot;\n■基本演算子:\n&amp;quot; +&lt;br /&gt;
        &amp;quot;`+` (加算), `-` (減算), `*` (乗算), `/` (除算), `%` (剰余), `^` (べき乗)\n&amp;quot; +&lt;br /&gt;
        &amp;quot;\n■比較演算子:\n&amp;quot; +&lt;br /&gt;
        &amp;quot;`==` (等しい), `!=` (等しくない), `&amp;lt;` (より小さい), `&amp;lt;=` (以下), `&amp;gt;` (より大きい), `&amp;gt;=` (以上)\n&amp;quot; +&lt;br /&gt;
        &amp;quot;\n■代入演算子:\n&amp;quot; +&lt;br /&gt;
        &amp;quot;`=`, `+=`, `-=`, `*=`, `/=`, `%=`, `^=`\n&amp;quot; +&lt;br /&gt;
        &amp;quot;\n■インクリメント・デクリメント:\n&amp;quot; +&lt;br /&gt;
        &amp;quot;`++`, `--`\n&amp;quot; +&lt;br /&gt;
        &amp;quot;\n■数学関数 (要 `-l` オプション):\n&amp;quot; +&lt;br /&gt;
        &amp;quot;- `s(x)`: サイン（引数はラジアン）\n&amp;quot; +&lt;br /&gt;
        &amp;quot;- `c(x)`: コサイン（引数はラジアン）\n&amp;quot; +&lt;br /&gt;
        &amp;quot;- `a(x)`: アークタンジェント（返り値はラジアン）\n&amp;quot; +&lt;br /&gt;
        &amp;quot;- `l(x)`: 自然対数\n&amp;quot; +&lt;br /&gt;
        &amp;quot;- `e(x)`: 指数関数\n&amp;quot; +&lt;br /&gt;
        &amp;quot;- `sqrt(x)`: 平方根\n&amp;quot; +&lt;br /&gt;
        &amp;quot;- `j(n,x)`: n次ベッセル関数\n&amp;quot; +&lt;br /&gt;
        &amp;quot;\n■変数の使い方:\n&amp;quot; +&lt;br /&gt;
        &amp;quot;- 変数への代入: $&amp;lt;変数名&amp;gt; = &amp;lt;値&amp;gt; （例: $a = 5, $pi = 4*a(1)）\n&amp;quot; +&lt;br /&gt;
        &amp;quot;- 変数の参照: 変数名を数式に記述 （例: $a + 3, $b * 2）\n&amp;quot; +&lt;br /&gt;
        &amp;quot;- 複数の式を実行: セミコロン ; で区切る （例: $a = 3; $b = 2; $a + $b）&amp;quot;;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== サードパーティライブラリ ==&lt;br /&gt;
&lt;br /&gt;
[https://frippery.org/busybox/ busybox64u.exe]&lt;br /&gt;
&lt;br /&gt;
== 更新履歴 ==&lt;br /&gt;
&lt;br /&gt;
* 2025-10-25 公開&lt;br /&gt;
* 2025-10-26 「5 - 3.0」のような「小数以下が『.0』になる計算」で「.」だけが出力に残ってしまう不具合を修正&lt;/div&gt;</summary>
		<author><name>Yuko</name></author>
	</entry>
	<entry>
		<id>https://www.haijin-boys.com/wiki/index.php?title=Mery_%E9%9B%BB%E5%8D%93_(bc_%E3%82%B3%E3%83%9E%E3%83%B3%E3%83%89%E4%BD%BF%E7%94%A8%E7%89%88)&amp;diff=9246</id>
		<title>Mery 電卓 (bc コマンド使用版)</title>
		<link rel="alternate" type="text/html" href="https://www.haijin-boys.com/wiki/index.php?title=Mery_%E9%9B%BB%E5%8D%93_(bc_%E3%82%B3%E3%83%9E%E3%83%B3%E3%83%89%E4%BD%BF%E7%94%A8%E7%89%88)&amp;diff=9246"/>
		<updated>2025-10-26T02:30:01Z</updated>

		<summary type="html">&lt;p&gt;Yuko: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== 概要 ==&lt;br /&gt;
&lt;br /&gt;
「Mery 電卓 (bc コマンド使用版)」は、 [[Mery 電卓]] の派生版で、Meryエディタ上で数式を計算するためのマクロです。オリジナルの「Mery 電卓」よりも対応する数学関数が少ない代わりに起動が早いため、日々の利用の中で軽快に使用することができます。&lt;br /&gt;
&lt;br /&gt;
Linux でお馴染みの bc コマンドを計算エンジンとしており、標準的な数式と一部の数学関数をサポートしています。カーソル位置の行または選択したテキストを数式として解釈し、結果を表示します。計算結果は自動的にクリップボードにコピーされ、カンマ区切りの数値として表示されます。&lt;br /&gt;
&lt;br /&gt;
本マクロをショートカットキーに設定し、いつでも起動できるようにしておくと日常シーンで何かと便利です。作者は「Ctrl + Alt + Enter」キーに割り当てて利用しています。&lt;br /&gt;
&lt;br /&gt;
== 注意事項 ==&lt;br /&gt;
&lt;br /&gt;
* Mery Ver 3.7.19 で動作確認しています。&lt;br /&gt;
&lt;br /&gt;
== 使い方 ==&lt;br /&gt;
&lt;br /&gt;
0. &#039;&#039;&#039;事前準備&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
* 本マクロでは計算コマンドの「bc」を内部で使っています。当該コマンドを使うために「BusyBox for Windows」を利用します&lt;br /&gt;
** ダウンロード先: https://frippery.org/busybox/&lt;br /&gt;
** ダウンロードするファイル: busybox64u.exe ※ダウンロード後、マクロファイルと同じフォルダーに配置してください&lt;br /&gt;
*** Windows 10 (1903) 以降の環境では上記を推奨。それ以前の環境の場合は動作しない可能性があるため、適切な実行ファイルを選択してください。&lt;br /&gt;
&lt;br /&gt;
1. &#039;&#039;&#039;行全体を計算する場合&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* 計算したい数式が書かれた行にカーソルを置きます。&lt;br /&gt;
* マクロを実行すると、次の行に「= 計算結果」が表示されます。&lt;br /&gt;
* 既に結果行がある場合は、その行が更新されます。&lt;br /&gt;
&lt;br /&gt;
2. &#039;&#039;&#039;選択範囲を計算する場合&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* 計算したい数式部分を選択します。&lt;br /&gt;
* マクロを実行すると、選択範囲の後ろに「計算式 = 計算結果」が挿入されます。&lt;br /&gt;
&lt;br /&gt;
3. &#039;&#039;&#039;サポートされている演算子&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* 四則演算（+, -, *, /）&lt;br /&gt;
* べき乗（^）&lt;br /&gt;
* 剰余（%） ※整数の剰余にのみ対応&lt;br /&gt;
* 比較演算子（&amp;lt;, &amp;gt;, &amp;lt;=, &amp;gt;=, ==, !=）&lt;br /&gt;
&lt;br /&gt;
4. &#039;&#039;&#039;サポートされている関数&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* s(x): サイン（引数はラジアン）&lt;br /&gt;
* c(x): コサイン（引数はラジアン）&lt;br /&gt;
* a(x): アークタンジェント（返り値はラジアン）&lt;br /&gt;
* l(x): 自然対数&lt;br /&gt;
* e(x): 指数関数&lt;br /&gt;
* sqrt(x): 平方根&lt;br /&gt;
* j(n,x): n次ベッセル関数&lt;br /&gt;
&lt;br /&gt;
5. &#039;&#039;&#039;変数の利用&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* 変数への代入: $&amp;lt;変数名&amp;gt; = &amp;lt;値&amp;gt; （例: $a = 5, $pi = 4*a(1)）&lt;br /&gt;
* 変数の参照: 変数名を数式に記述 （例: $a + 3, $b * 2）&lt;br /&gt;
* 複数の式を実行: セミコロン ; で区切る （例: $a = 3; $b = 2; $a + $b）&lt;br /&gt;
* インクリメント・デクリメント: ++, -- (例: $a++, ++$a)&lt;br /&gt;
&lt;br /&gt;
6. &#039;&#039;&#039;特別な機能&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* カンマ区切り表示: 計算結果は読みやすいようにカンマ区切りで表示されます。変数「COMMA」を調整することで制御できます。&lt;br /&gt;
* 自動コピー: 計算結果は自動的にクリップボードにコピーされます。変数「COPY」を調整することで制御できます。&lt;br /&gt;
* 計算結果の再利用: 行頭の「= 」は計算実行時に無視されるため、計算結果をスムーズに次の式として利用できます。&lt;br /&gt;
* 変数の利用: 複数行を選択して実行、または一行内で「;」で数式を区切ることで、変数を使った数式が利用できます。変数形式は「$[a-zA-Z][a-zA-Z0-9]*」に対応しています。例えば「$a = 2; $a * 3」として実行すると、結果は「6」と出力されます。&lt;br /&gt;
* メモ書きから計算: 数字や関数以外の文字はできるだけ除外した上で計算をします。例えば「月100円 * 12ヶ月」のようなメモ書きに対して実行すると「1,200」と出力されます。また「` (バッククォート)」で囲んだ文字は確実に無視されるため、計算メモ用として使うことができます (使用例: 『1500円 `金額未定のため仮置き`』)。&lt;br /&gt;
&lt;br /&gt;
== ソースコード ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot; copy&amp;gt;&lt;br /&gt;
#title = &amp;quot;Mery 電卓 (bc コマンド使用版)&amp;quot;&lt;br /&gt;
#include &amp;quot;Common.js&amp;quot;&lt;br /&gt;
BeginUndoGroup();&lt;br /&gt;
&lt;br /&gt;
// 事前準備:&lt;br /&gt;
//   以下のサイトから busybox64u.exe を入手し、本マクロと同じフォルダーに配置することで動作します。&lt;br /&gt;
//   https://frippery.org/busybox/&lt;br /&gt;
&lt;br /&gt;
// 結果をクリップボードにコピーするか&lt;br /&gt;
var COPY = true;&lt;br /&gt;
// 出力結果をカンマ区切りにするするか&lt;br /&gt;
var COMMA = true;&lt;br /&gt;
&lt;br /&gt;
// busybox のパスを取得&lt;br /&gt;
const scriptDir = ScriptFullName.replace(/\\[^\\]+$/g, &amp;quot;&amp;quot;)&lt;br /&gt;
const busyboxPath = scriptDir + &amp;quot;\\busybox64u.exe&amp;quot;;&lt;br /&gt;
if (!shell.FileExists(busyboxPath)) {&lt;br /&gt;
    alert(&amp;quot;busybox64u.exe が見つかりません。\nこのマクロを動作させるには、busybox64u.exe を本マクロと同じフォルダーに配置する必要があります。&amp;quot;);&lt;br /&gt;
    Quit();&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
var sel = document.selection;&lt;br /&gt;
&lt;br /&gt;
if (sel.Count &amp;gt; 1) {&lt;br /&gt;
    // マルチカーソル実行時は計算中表示を出してから実行&lt;br /&gt;
    ShowTip(&amp;quot;計算中... (全 &amp;quot; + sel.Count + &amp;quot; 件)&amp;quot;, meShowTipPosCaret);&lt;br /&gt;
    doMultiEdit(main);&lt;br /&gt;
    ShowTip(&amp;quot;&amp;quot;, meShowTipHide);&lt;br /&gt;
} else {&lt;br /&gt;
    main();&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function main() {&lt;br /&gt;
    var isEmpty = sel.IsEmpty;&lt;br /&gt;
    var activePointY = sel.GetActivePointY(mePosLogical);&lt;br /&gt;
    if (isEmpty) {&lt;br /&gt;
        var exp = document.GetLine(activePointY);&lt;br /&gt;
    } else {&lt;br /&gt;
        var exp = sel.Text;&lt;br /&gt;
    }&lt;br /&gt;
    // 空白行の場合は何もしない&lt;br /&gt;
    if (exp.replace(/[\s　]+/g, &amp;quot;&amp;quot;) === &amp;quot;&amp;quot;) {&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // 計算実行&lt;br /&gt;
    try {&lt;br /&gt;
        var exp_replaced = replaceExpression(exp)&lt;br /&gt;
            .replace(/([^;]*%[^;]*)/g, &amp;quot;scale=0; $1; scale=10; &amp;quot;); // 剰余計算を整数にする&lt;br /&gt;
        const exec = shell.Exec(busyboxPath + &amp;quot; bc -l&amp;quot;, &amp;quot;scale=10; &amp;quot; + exp_replaced);&lt;br /&gt;
        if (exec.ExitCode !== 0) {&lt;br /&gt;
            throw new Error(&amp;quot;計算エラー: &amp;quot; + exec.StdErr);&lt;br /&gt;
        }&lt;br /&gt;
        // bc の出力のクセに合わせて整形する&lt;br /&gt;
        var result_str = exec.StdOut&lt;br /&gt;
            .trim()&lt;br /&gt;
            .replace(/^\./gm, &amp;quot;0.&amp;quot;)&lt;br /&gt;
            .replace(/(\.[0-9]*?)0+$/gm, &amp;quot;$1&amp;quot;)&lt;br /&gt;
            .replace(/\.$/gm, &amp;quot;&amp;quot;);&lt;br /&gt;
    } catch (e) {&lt;br /&gt;
        if (exp_replaced === undefined) {&lt;br /&gt;
            alert(&amp;quot;エラー発生&amp;quot; + &amp;quot;\n------\n&amp;quot; + e)&lt;br /&gt;
        } else {&lt;br /&gt;
            alert(&amp;quot;整形後の数式で計算エラーが発生&amp;quot; + &amp;quot;\n------\n&amp;quot; + e + &amp;quot;\n元の数式: &amp;quot; + exp + &amp;quot;\n整形後の数式: &amp;quot; + exp_replaced);&lt;br /&gt;
        }&lt;br /&gt;
        var c = Confirm(&amp;quot;数式ヘルプを表示しますか？&amp;quot;);&lt;br /&gt;
        if (c) {&lt;br /&gt;
            editor.NewFile();&lt;br /&gt;
            var newDoc = editor.ActiveDocument;&lt;br /&gt;
            newDoc.Write(help());&lt;br /&gt;
            newDoc.selection.SetActivePos(0);&lt;br /&gt;
        }&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // 小数点以上のみカンマ区切り&lt;br /&gt;
    if (COMMA &amp;amp;&amp;amp; result_str.match(/^[0-9]+(\.[0-9]+)?$/m)) {&lt;br /&gt;
        var result_parts = result_str.split(&amp;quot;.&amp;quot;);&lt;br /&gt;
        result_parts[0] = result_parts[0].replace(/(\d)(?=(\d{3})+$)/gm, &amp;quot;$1,&amp;quot;);&lt;br /&gt;
        if (result_parts.length &amp;gt; 1) {&lt;br /&gt;
            result_str = result_parts.join(&amp;quot;.&amp;quot;);&lt;br /&gt;
        } else {&lt;br /&gt;
            result_str = result_parts[0];&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // bc コマンドでは複数個の計算出力があると計算結果が複数行になるので1行にまとめる&lt;br /&gt;
    result_str = result_str.replace(/\r?\n/g, &amp;quot; ; &amp;quot;);&lt;br /&gt;
&lt;br /&gt;
    // 計算行が = 始まりの場合、= を削除する&lt;br /&gt;
    if (isEmpty &amp;amp;&amp;amp; exp.match(/^= /)) {&lt;br /&gt;
        sel.SetActivePoint(mePosLogical, 1, activePointY, false);&lt;br /&gt;
        sel.EndOfLine(true, mePosLogical);&lt;br /&gt;
        sel.Text = sel.Text.replace(/^= /, &amp;quot;&amp;quot;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // 既存の計算結果を上書きするか判定&lt;br /&gt;
    var pattern = /^=\s*(-?[0-9.,]+(\s*;\s*)?)*\s*$/;&lt;br /&gt;
    var nextLine = document.GetLine(activePointY + 1);&lt;br /&gt;
    if (isEmpty &amp;amp;&amp;amp; nextLine.match(pattern)) {&lt;br /&gt;
        sel.SetActivePoint(mePosLogical, 1, activePointY + 1, false);&lt;br /&gt;
        sel.EndOfLine(true, mePosLogical);&lt;br /&gt;
        sel.Text = &#039;= &#039; + result_str&lt;br /&gt;
    } else if (isEmpty) {&lt;br /&gt;
        sel.LineOpen();&lt;br /&gt;
        sel.Text = &#039;= &#039; + result_str&lt;br /&gt;
    } else {&lt;br /&gt;
        sel.CharRight();&lt;br /&gt;
        const inputExp = exp.replace(/((?!;)\n)/mg, &amp;quot;; &amp;quot;).replace(/;\s*;/g, &amp;quot;;&amp;quot;).replace(/　/g, &amp;quot; &amp;quot;).replace(/;\s*$/, &amp;quot;&amp;quot;).trim();&lt;br /&gt;
        var newLine = &amp;quot;&amp;quot;;&lt;br /&gt;
        if (exp.slice(-1) !== &amp;quot;\n&amp;quot;) {&lt;br /&gt;
            newLine = &#039;\n&#039;;&lt;br /&gt;
        }&lt;br /&gt;
        sel.Text = newLine + inputExp + &#039;\n= &#039; + result_str + &#039;\n&#039;;&lt;br /&gt;
        sel.CharLeft();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (COPY) {&lt;br /&gt;
        ClipboardData.SetData(result_str);&lt;br /&gt;
        Status = &amp;quot;計算結果 &amp;quot; + result_str + &amp;quot; をコピーしました&amp;quot;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/**&lt;br /&gt;
 * 数式を置換する&lt;br /&gt;
 */&lt;br /&gt;
function replaceExpression(exp) {&lt;br /&gt;
    // コメント扱いの記載を削除&lt;br /&gt;
    let exp_replaced = exp.replace(/`[^`]*`/g, &amp;quot; &amp;quot;)&lt;br /&gt;
&lt;br /&gt;
    // 他の言語で対応する演算子などはあらかじめエラーにする&lt;br /&gt;
    const invalidOperators = [&amp;quot;\\*\\*&amp;quot;, &amp;quot;//&amp;quot;];&lt;br /&gt;
    for (var i = 0; i &amp;lt; invalidOperators.length; i++) {&lt;br /&gt;
        var operator = invalidOperators[i];&lt;br /&gt;
        if (exp.match(new RegExp(operator))) {&lt;br /&gt;
            throw new Error(&amp;quot;演算子「&amp;quot; + operator + &amp;quot;」はサポートされていません&amp;quot;);&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    exp_replaced = exp_replaced.trim()&lt;br /&gt;
        // 先頭の = を削除&lt;br /&gt;
        .replace(/^= /, &amp;quot;&amp;quot;)&lt;br /&gt;
        // 改行を ; に置換&lt;br /&gt;
        .replace(/\n/g, &amp;quot;;&amp;quot;)&lt;br /&gt;
        // 非ASCII文字を削除&lt;br /&gt;
        .replace(/[^\x20-\x7e]/g, &amp;quot; &amp;quot;)&lt;br /&gt;
        // 全角スペースを半角スペースに変換&lt;br /&gt;
        .replace(/　/g, &amp;quot; &amp;quot;)&lt;br /&gt;
        // 数字内のカンマを取り除く。j(1, 2) では削除しない&lt;br /&gt;
        .replace(/j *\(\d+, *\d+\)|,/g, function (match) {&lt;br /&gt;
            return match.match(/j/) ? match : &amp;quot;&amp;quot;;&lt;br /&gt;
        })&lt;br /&gt;
        // 変数形式を退避してからドル記号削除&lt;br /&gt;
        .replace(/(\$[a-zA-Z][a-zA-Z0-9]*)/g, &amp;quot; %%%$1%%% &amp;quot;)&lt;br /&gt;
        .replace(/\$/g, &amp;quot;&amp;quot;)&lt;br /&gt;
        // 空の括弧を削除&lt;br /&gt;
        .replace(/\(\s*\)|\[\s*\]|\{\s*\}/g, &amp;quot; &amp;quot;)&lt;br /&gt;
        // 対応関数以外の文字列は削除&lt;br /&gt;
        .replace(/[a-zA-Z]+/g, function (match) {&lt;br /&gt;
            const functions = [&amp;quot;s&amp;quot;, &amp;quot;c&amp;quot;, &amp;quot;a&amp;quot;, &amp;quot;l&amp;quot;, &amp;quot;e&amp;quot;, &amp;quot;sqrt&amp;quot;, &amp;quot;j&amp;quot;];&lt;br /&gt;
            for (var i = 0; i &amp;lt; functions.length; i++) {&lt;br /&gt;
                var f = functions[i];&lt;br /&gt;
                if (match === f) {&lt;br /&gt;
                    return match;&lt;br /&gt;
                }&lt;br /&gt;
            }&lt;br /&gt;
            return &amp;quot; &amp;quot;;&lt;br /&gt;
        })&lt;br /&gt;
        // `/hour` などの文字列があるとスラッシュだけ残るためスラッシュを削除&lt;br /&gt;
        .replace(/\/\s*([,+\-*\/%^&amp;amp;~|&amp;lt;&amp;gt;?/)\];\n]|$)/g, &amp;quot;$1&amp;quot;)&lt;br /&gt;
        // 退避文字を復元&lt;br /&gt;
        .replace(/ %%%\$([a-zA-Z][a-zA-Z0-9]*)%%% /g, &amp;quot; $1 &amp;quot;)&lt;br /&gt;
        .replace(/ %%%([^%]+)%%% /g, &amp;quot; $1 &amp;quot;)&lt;br /&gt;
&lt;br /&gt;
    return exp_replaced;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function help() {&lt;br /&gt;
    return &amp;quot;【Mery 電卓 (bc コマンド使用版) で使用可能な演算子と関数】\n&amp;quot; +&lt;br /&gt;
        &amp;quot;\n■基本演算子:\n&amp;quot; +&lt;br /&gt;
        &amp;quot;`+` (加算), `-` (減算), `*` (乗算), `/` (除算), `%` (剰余), `^` (べき乗)\n&amp;quot; +&lt;br /&gt;
        &amp;quot;\n■比較演算子:\n&amp;quot; +&lt;br /&gt;
        &amp;quot;`==` (等しい), `!=` (等しくない), `&amp;lt;` (より小さい), `&amp;lt;=` (以下), `&amp;gt;` (より大きい), `&amp;gt;=` (以上)\n&amp;quot; +&lt;br /&gt;
        &amp;quot;\n■代入演算子:\n&amp;quot; +&lt;br /&gt;
        &amp;quot;`=`, `+=`, `-=`, `*=`, `/=`, `%=`, `^=`\n&amp;quot; +&lt;br /&gt;
        &amp;quot;\n■インクリメント・デクリメント:\n&amp;quot; +&lt;br /&gt;
        &amp;quot;`++`, `--`\n&amp;quot; +&lt;br /&gt;
        &amp;quot;\n■数学関数 (要 `-l` オプション):\n&amp;quot; +&lt;br /&gt;
        &amp;quot;- `s(x)`: サイン（引数はラジアン）\n&amp;quot; +&lt;br /&gt;
        &amp;quot;- `c(x)`: コサイン（引数はラジアン）\n&amp;quot; +&lt;br /&gt;
        &amp;quot;- `a(x)`: アークタンジェント（返り値はラジアン）\n&amp;quot; +&lt;br /&gt;
        &amp;quot;- `l(x)`: 自然対数\n&amp;quot; +&lt;br /&gt;
        &amp;quot;- `e(x)`: 指数関数\n&amp;quot; +&lt;br /&gt;
        &amp;quot;- `sqrt(x)`: 平方根\n&amp;quot; +&lt;br /&gt;
        &amp;quot;- `j(n,x)`: n次ベッセル関数\n&amp;quot; +&lt;br /&gt;
        &amp;quot;\n■変数の使い方:\n&amp;quot; +&lt;br /&gt;
        &amp;quot;- 変数への代入: $&amp;lt;変数名&amp;gt; = &amp;lt;値&amp;gt; （例: $a = 5, $pi = 4*a(1)）\n&amp;quot; +&lt;br /&gt;
        &amp;quot;- 変数の参照: 変数名を数式に記述 （例: $a + 3, $b * 2）\n&amp;quot; +&lt;br /&gt;
        &amp;quot;- 複数の式を実行: セミコロン ; で区切る （例: $a = 3; $b = 2; $a + $b）&amp;quot;;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== サードパーティライブラリ ==&lt;br /&gt;
&lt;br /&gt;
[https://frippery.org/busybox/ busybox64u.exe]&lt;br /&gt;
&lt;br /&gt;
== 更新履歴 ==&lt;br /&gt;
&lt;br /&gt;
* 2025-10-25 公開&lt;br /&gt;
* 2025-10-26 「5 - 3.0」のような小数以下が「.0」になるような計算で「.」だけが残ってしまう不具合を修正&lt;/div&gt;</summary>
		<author><name>Yuko</name></author>
	</entry>
	<entry>
		<id>https://www.haijin-boys.com/wiki/index.php?title=Mery_%E9%9B%BB%E5%8D%93_(bc_%E3%82%B3%E3%83%9E%E3%83%B3%E3%83%89%E4%BD%BF%E7%94%A8%E7%89%88)&amp;diff=9244</id>
		<title>Mery 電卓 (bc コマンド使用版)</title>
		<link rel="alternate" type="text/html" href="https://www.haijin-boys.com/wiki/index.php?title=Mery_%E9%9B%BB%E5%8D%93_(bc_%E3%82%B3%E3%83%9E%E3%83%B3%E3%83%89%E4%BD%BF%E7%94%A8%E7%89%88)&amp;diff=9244"/>
		<updated>2025-10-25T08:16:49Z</updated>

		<summary type="html">&lt;p&gt;Yuko: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== 概要 ==&lt;br /&gt;
&lt;br /&gt;
「Mery 電卓 (bc コマンド使用版)」は、 [https://www.haijin-boys.com/wiki/Mery_%E9%9B%BB%E5%8D%93 Mery 電卓] の派生版で、Meryエディタ上で数式を計算するためのマクロです。オリジナルの「Mery 電卓」よりも対応する数学関数が少ない代わりに起動が早いため、日々の利用の中で軽快に使用することができます。&lt;br /&gt;
&lt;br /&gt;
Linux でお馴染みの bc コマンドを計算エンジンとしており、標準的な数式と一部の数学関数をサポートしています。カーソル位置の行または選択したテキストを数式として解釈し、結果を表示します。計算結果は自動的にクリップボードにコピーされ、カンマ区切りの数値として表示されます。&lt;br /&gt;
&lt;br /&gt;
本マクロをショートカットキーに設定し、いつでも起動できるようにしておくと日常シーンで何かと便利です。作者は「Ctrl + Alt + Enter」キーに割り当てて利用しています。&lt;br /&gt;
&lt;br /&gt;
== 注意事項 ==&lt;br /&gt;
&lt;br /&gt;
* Mery Ver 3.7.19 で動作確認しています。&lt;br /&gt;
&lt;br /&gt;
== 使い方 ==&lt;br /&gt;
&lt;br /&gt;
0. &#039;&#039;&#039;事前準備&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
* 本マクロでは計算コマンドの「bc」を内部で使っています。当該コマンドを使うために「BusyBox for Windows」を利用します&lt;br /&gt;
** ダウンロード先: https://frippery.org/busybox/&lt;br /&gt;
** ダウンロードするファイル: busybox64u.exe ※ダウンロード後、マクロファイルと同じフォルダーに配置してください&lt;br /&gt;
*** Windows 10 (1903) 以降の環境では上記を推奨。それ以前の環境の場合は動作しない可能性があるため、適切な実行ファイルを選択してください。&lt;br /&gt;
&lt;br /&gt;
1. &#039;&#039;&#039;行全体を計算する場合&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* 計算したい数式が書かれた行にカーソルを置きます。&lt;br /&gt;
* マクロを実行すると、次の行に「= 計算結果」が表示されます。&lt;br /&gt;
* 既に結果行がある場合は、その行が更新されます。&lt;br /&gt;
&lt;br /&gt;
2. &#039;&#039;&#039;選択範囲を計算する場合&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* 計算したい数式部分を選択します。&lt;br /&gt;
* マクロを実行すると、選択範囲の後ろに「計算式 = 計算結果」が挿入されます。&lt;br /&gt;
&lt;br /&gt;
3. &#039;&#039;&#039;サポートされている演算子&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* 四則演算（+, -, *, /）&lt;br /&gt;
* べき乗（^）&lt;br /&gt;
* 剰余（%） ※整数の剰余にのみ対応&lt;br /&gt;
* 比較演算子（&amp;lt;, &amp;gt;, &amp;lt;=, &amp;gt;=, ==, !=）&lt;br /&gt;
&lt;br /&gt;
4. &#039;&#039;&#039;サポートされている関数&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* s(x): サイン（引数はラジアン）&lt;br /&gt;
* c(x): コサイン（引数はラジアン）&lt;br /&gt;
* a(x): アークタンジェント（返り値はラジアン）&lt;br /&gt;
* l(x): 自然対数&lt;br /&gt;
* e(x): 指数関数&lt;br /&gt;
* sqrt(x): 平方根&lt;br /&gt;
* j(n,x): n次ベッセル関数&lt;br /&gt;
&lt;br /&gt;
5. &#039;&#039;&#039;変数の利用&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* 変数への代入: $&amp;lt;変数名&amp;gt; = &amp;lt;値&amp;gt; （例: $a = 5, $pi = 4*a(1)）&lt;br /&gt;
* 変数の参照: 変数名を数式に記述 （例: $a + 3, $b * 2）&lt;br /&gt;
* 複数の式を実行: セミコロン ; で区切る （例: $a = 3; $b = 2; $a + $b）&lt;br /&gt;
* インクリメント・デクリメント: ++, -- (例: $a++, ++$a)&lt;br /&gt;
&lt;br /&gt;
6. &#039;&#039;&#039;特別な機能&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* カンマ区切り表示: 計算結果は読みやすいようにカンマ区切りで表示されます。変数「COMMA」を調整することで制御できます。&lt;br /&gt;
* 自動コピー: 計算結果は自動的にクリップボードにコピーされます。変数「COPY」を調整することで制御できます。&lt;br /&gt;
* 計算結果の再利用: 行頭の「= 」は計算実行時に無視されるため、計算結果をスムーズに次の式として利用できます。&lt;br /&gt;
* 変数の利用: 複数行を選択して実行、または一行内で「;」で数式を区切ることで、変数を使った数式が利用できます。変数形式は「$[a-zA-Z][a-zA-Z0-9]*」に対応しています。例えば「$a = 2; $a * 3」として実行すると、結果は「6」と出力されます。&lt;br /&gt;
* メモ書きから計算: 数字や関数以外の文字はできるだけ除外した上で計算をします。例えば「月100円 * 12ヶ月」のようなメモ書きに対して実行すると「1,200」と出力されます。また「` (バッククォート)」で囲んだ文字は確実に無視されるため、計算メモ用として使うことができます (使用例: 『1500円 `金額未定のため仮置き`』)。&lt;br /&gt;
&lt;br /&gt;
== ソースコード ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot; copy&amp;gt;&lt;br /&gt;
#title = &amp;quot;Mery 電卓 (bc コマンド使用版)&amp;quot;&lt;br /&gt;
#include &amp;quot;Common.js&amp;quot;&lt;br /&gt;
BeginUndoGroup();&lt;br /&gt;
&lt;br /&gt;
// 事前準備:&lt;br /&gt;
//   以下のサイトから busybox64u.exe を入手し、本マクロと同じフォルダーに配置することで動作します。&lt;br /&gt;
//   https://frippery.org/busybox/&lt;br /&gt;
&lt;br /&gt;
// 結果をクリップボードにコピーするか&lt;br /&gt;
var COPY = true;&lt;br /&gt;
// 出力結果をカンマ区切りにするするか&lt;br /&gt;
var COMMA = true;&lt;br /&gt;
&lt;br /&gt;
// busybox のパスを取得&lt;br /&gt;
const scriptDir = ScriptFullName.replace(/\\[^\\]+$/g, &amp;quot;&amp;quot;)&lt;br /&gt;
const busyboxPath = scriptDir + &amp;quot;\\busybox64u.exe&amp;quot;;&lt;br /&gt;
if (!shell.FileExists(busyboxPath)) {&lt;br /&gt;
    alert(&amp;quot;busybox64u.exe が見つかりません。\nこのマクロを動作させるには、busybox64u.exe を本マクロと同じフォルダーに配置する必要があります。&amp;quot;);&lt;br /&gt;
    Quit();&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
var sel = document.selection;&lt;br /&gt;
&lt;br /&gt;
if (sel.Count &amp;gt; 1) {&lt;br /&gt;
    // マルチカーソル実行時は計算中表示を出してから実行&lt;br /&gt;
    ShowTip(&amp;quot;計算中... (全 &amp;quot; + sel.Count + &amp;quot; 件)&amp;quot;, meShowTipPosCaret);&lt;br /&gt;
    doMultiEdit(main);&lt;br /&gt;
    ShowTip(&amp;quot;&amp;quot;, meShowTipHide);&lt;br /&gt;
} else {&lt;br /&gt;
    main();&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function main() {&lt;br /&gt;
    var isEmpty = sel.IsEmpty;&lt;br /&gt;
    var activePointY = sel.GetActivePointY(mePosLogical);&lt;br /&gt;
    if (isEmpty) {&lt;br /&gt;
        var exp = document.GetLine(activePointY);&lt;br /&gt;
    } else {&lt;br /&gt;
        var exp = sel.Text;&lt;br /&gt;
    }&lt;br /&gt;
    // 空白行の場合は何もしない&lt;br /&gt;
    if (exp.replace(/[\s　]+/g, &amp;quot;&amp;quot;) === &amp;quot;&amp;quot;) {&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // 計算実行&lt;br /&gt;
    try {&lt;br /&gt;
        var exp_replaced = replaceExpression(exp)&lt;br /&gt;
            .replace(/([^;]*%[^;]*)/g, &amp;quot;scale=0; $1; scale=10; &amp;quot;); // 剰余計算を整数にする&lt;br /&gt;
        const exec = shell.Exec(busyboxPath + &amp;quot; bc -l&amp;quot;, &amp;quot;scale=10; &amp;quot; + exp_replaced);&lt;br /&gt;
        if (exec.ExitCode !== 0) {&lt;br /&gt;
            throw new Error(&amp;quot;計算エラー: &amp;quot; + exec.StdErr);&lt;br /&gt;
        }&lt;br /&gt;
        // bc の出力のクセに合わせて整形する&lt;br /&gt;
        var result_str = exec.StdOut&lt;br /&gt;
            .trim()&lt;br /&gt;
            .replace(/^\./gm, &amp;quot;0.&amp;quot;)&lt;br /&gt;
            .replace(/(\.[0-9]*?)0+$/gm, &amp;quot;$1&amp;quot;);&lt;br /&gt;
    } catch (e) {&lt;br /&gt;
        if (exp_replaced === undefined) {&lt;br /&gt;
            alert(&amp;quot;エラー発生&amp;quot; + &amp;quot;\n------\n&amp;quot; + e)&lt;br /&gt;
        } else {&lt;br /&gt;
            alert(&amp;quot;整形後の数式で計算エラーが発生&amp;quot; + &amp;quot;\n------\n&amp;quot; + e + &amp;quot;\n元の数式: &amp;quot; + exp + &amp;quot;\n整形後の数式: &amp;quot; + exp_replaced);&lt;br /&gt;
        }&lt;br /&gt;
        var c = Confirm(&amp;quot;数式ヘルプを表示しますか？&amp;quot;);&lt;br /&gt;
        if (c) {&lt;br /&gt;
            editor.NewFile();&lt;br /&gt;
            var newDoc = editor.ActiveDocument;&lt;br /&gt;
            newDoc.Write(help());&lt;br /&gt;
            newDoc.selection.SetActivePos(0);&lt;br /&gt;
        }&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // 小数点以上のみカンマ区切り&lt;br /&gt;
    if (COMMA &amp;amp;&amp;amp; result_str.match(/^[0-9]+(\.[0-9]+)?$/m)) {&lt;br /&gt;
        var result_parts = result_str.split(&amp;quot;.&amp;quot;);&lt;br /&gt;
        result_parts[0] = result_parts[0].replace(/(\d)(?=(\d{3})+$)/gm, &amp;quot;$1,&amp;quot;);&lt;br /&gt;
        if (result_parts.length &amp;gt; 1) {&lt;br /&gt;
            result_str = result_parts.join(&amp;quot;.&amp;quot;);&lt;br /&gt;
        } else {&lt;br /&gt;
            result_str = result_parts[0];&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // bc コマンドでは複数個の計算出力があると計算結果が複数行になるので1行にまとめる&lt;br /&gt;
    result_str = result_str.replace(/\r?\n/g, &amp;quot; ; &amp;quot;);&lt;br /&gt;
&lt;br /&gt;
    // 計算行が = 始まりの場合、= を削除する&lt;br /&gt;
    if (isEmpty &amp;amp;&amp;amp; exp.match(/^= /)) {&lt;br /&gt;
        sel.SetActivePoint(mePosLogical, 1, activePointY, false);&lt;br /&gt;
        sel.EndOfLine(true, mePosLogical);&lt;br /&gt;
        sel.Text = sel.Text.replace(/^= /, &amp;quot;&amp;quot;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // 既存の計算結果を上書きするか判定&lt;br /&gt;
    var pattern = /^=\s*(-?[0-9.,]+(\s*;\s*)?)*\s*$/;&lt;br /&gt;
    var nextLine = document.GetLine(activePointY + 1);&lt;br /&gt;
    if (isEmpty &amp;amp;&amp;amp; nextLine.match(pattern)) {&lt;br /&gt;
        sel.SetActivePoint(mePosLogical, 1, activePointY + 1, false);&lt;br /&gt;
        sel.EndOfLine(true, mePosLogical);&lt;br /&gt;
        sel.Text = &#039;= &#039; + result_str&lt;br /&gt;
    } else if (isEmpty) {&lt;br /&gt;
        sel.LineOpen();&lt;br /&gt;
        sel.Text = &#039;= &#039; + result_str&lt;br /&gt;
    } else {&lt;br /&gt;
        sel.CharRight();&lt;br /&gt;
        const inputExp = exp.replace(/((?!;)\n)/mg, &amp;quot;; &amp;quot;).replace(/;\s*;/g, &amp;quot;;&amp;quot;).replace(/　/g, &amp;quot; &amp;quot;).replace(/;\s*$/, &amp;quot;&amp;quot;).trim();&lt;br /&gt;
        var newLine = &amp;quot;&amp;quot;;&lt;br /&gt;
        if (exp.slice(-1) !== &amp;quot;\n&amp;quot;) {&lt;br /&gt;
            newLine = &#039;\n&#039;;&lt;br /&gt;
        }&lt;br /&gt;
        sel.Text = newLine + inputExp + &#039;\n= &#039; + result_str + &#039;\n&#039;;&lt;br /&gt;
        sel.CharLeft();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (COPY) {&lt;br /&gt;
        ClipboardData.SetData(result_str);&lt;br /&gt;
        Status = &amp;quot;計算結果 &amp;quot; + result_str + &amp;quot; をコピーしました&amp;quot;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/**&lt;br /&gt;
 * 数式を置換する&lt;br /&gt;
 */&lt;br /&gt;
function replaceExpression(exp) {&lt;br /&gt;
    // コメント扱いの記載を削除&lt;br /&gt;
    let exp_replaced = exp.replace(/`[^`]*`/g, &amp;quot; &amp;quot;)&lt;br /&gt;
&lt;br /&gt;
    // 他の言語で対応する演算子などはあらかじめエラーにする&lt;br /&gt;
    const invalidOperators = [&amp;quot;\\*\\*&amp;quot;, &amp;quot;//&amp;quot;];&lt;br /&gt;
    for (var i = 0; i &amp;lt; invalidOperators.length; i++) {&lt;br /&gt;
        var operator = invalidOperators[i];&lt;br /&gt;
        if (exp.match(new RegExp(operator))) {&lt;br /&gt;
            throw new Error(&amp;quot;演算子「&amp;quot; + operator + &amp;quot;」はサポートされていません&amp;quot;);&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    exp_replaced = exp_replaced.trim()&lt;br /&gt;
        // 先頭の = を削除&lt;br /&gt;
        .replace(/^= /, &amp;quot;&amp;quot;)&lt;br /&gt;
        // 改行を ; に置換&lt;br /&gt;
        .replace(/\n/g, &amp;quot;;&amp;quot;)&lt;br /&gt;
        // 非ASCII文字を削除&lt;br /&gt;
        .replace(/[^\x20-\x7e]/g, &amp;quot; &amp;quot;)&lt;br /&gt;
        // 全角スペースを半角スペースに変換&lt;br /&gt;
        .replace(/　/g, &amp;quot; &amp;quot;)&lt;br /&gt;
        // 数字内のカンマを取り除く。j(1, 2) では削除しない&lt;br /&gt;
        .replace(/j *\(\d+, *\d+\)|,/g, function (match) {&lt;br /&gt;
            return match.match(/j/) ? match : &amp;quot;&amp;quot;;&lt;br /&gt;
        })&lt;br /&gt;
        // 変数形式を退避してからドル記号削除&lt;br /&gt;
        .replace(/(\$[a-zA-Z][a-zA-Z0-9]*)/g, &amp;quot; %%%$1%%% &amp;quot;)&lt;br /&gt;
        .replace(/\$/g, &amp;quot;&amp;quot;)&lt;br /&gt;
        // 空の括弧を削除&lt;br /&gt;
        .replace(/\(\s*\)|\[\s*\]|\{\s*\}/g, &amp;quot; &amp;quot;)&lt;br /&gt;
        // 対応関数以外の文字列は削除&lt;br /&gt;
        .replace(/[a-zA-Z]+/g, function (match) {&lt;br /&gt;
            const functions = [&amp;quot;s&amp;quot;, &amp;quot;c&amp;quot;, &amp;quot;a&amp;quot;, &amp;quot;l&amp;quot;, &amp;quot;e&amp;quot;, &amp;quot;sqrt&amp;quot;, &amp;quot;j&amp;quot;];&lt;br /&gt;
            for (var i = 0; i &amp;lt; functions.length; i++) {&lt;br /&gt;
                var f = functions[i];&lt;br /&gt;
                if (match === f) {&lt;br /&gt;
                    return match;&lt;br /&gt;
                }&lt;br /&gt;
            }&lt;br /&gt;
            return &amp;quot; &amp;quot;;&lt;br /&gt;
        })&lt;br /&gt;
        // `/hour` などの文字列があるとスラッシュだけ残るためスラッシュを削除&lt;br /&gt;
        .replace(/\/\s*([,+\-*\/%^&amp;amp;~|&amp;lt;&amp;gt;?/)\];\n]|$)/g, &amp;quot;$1&amp;quot;)&lt;br /&gt;
        // 退避文字を復元&lt;br /&gt;
        .replace(/ %%%\$([a-zA-Z][a-zA-Z0-9]*)%%% /g, &amp;quot; $1 &amp;quot;)&lt;br /&gt;
        .replace(/ %%%([^%]+)%%% /g, &amp;quot; $1 &amp;quot;)&lt;br /&gt;
&lt;br /&gt;
    return exp_replaced;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function help() {&lt;br /&gt;
    return &amp;quot;【Mery 電卓 (bc コマンド使用版) で使用可能な演算子と関数】\n&amp;quot; +&lt;br /&gt;
        &amp;quot;\n■基本演算子:\n&amp;quot; +&lt;br /&gt;
        &amp;quot;`+` (加算), `-` (減算), `*` (乗算), `/` (除算), `%` (剰余), `^` (べき乗)\n&amp;quot; +&lt;br /&gt;
        &amp;quot;\n■比較演算子:\n&amp;quot; +&lt;br /&gt;
        &amp;quot;`==` (等しい), `!=` (等しくない), `&amp;lt;` (より小さい), `&amp;lt;=` (以下), `&amp;gt;` (より大きい), `&amp;gt;=` (以上)\n&amp;quot; +&lt;br /&gt;
        &amp;quot;\n■代入演算子:\n&amp;quot; +&lt;br /&gt;
        &amp;quot;`=`, `+=`, `-=`, `*=`, `/=`, `%=`, `^=`\n&amp;quot; +&lt;br /&gt;
        &amp;quot;\n■インクリメント・デクリメント:\n&amp;quot; +&lt;br /&gt;
        &amp;quot;`++`, `--`\n&amp;quot; +&lt;br /&gt;
        &amp;quot;\n■数学関数 (要 `-l` オプション):\n&amp;quot; +&lt;br /&gt;
        &amp;quot;- `s(x)`: サイン（引数はラジアン）\n&amp;quot; +&lt;br /&gt;
        &amp;quot;- `c(x)`: コサイン（引数はラジアン）\n&amp;quot; +&lt;br /&gt;
        &amp;quot;- `a(x)`: アークタンジェント（返り値はラジアン）\n&amp;quot; +&lt;br /&gt;
        &amp;quot;- `l(x)`: 自然対数\n&amp;quot; +&lt;br /&gt;
        &amp;quot;- `e(x)`: 指数関数\n&amp;quot; +&lt;br /&gt;
        &amp;quot;- `sqrt(x)`: 平方根\n&amp;quot; +&lt;br /&gt;
        &amp;quot;- `j(n,x)`: n次ベッセル関数\n&amp;quot; +&lt;br /&gt;
        &amp;quot;\n■変数の使い方:\n&amp;quot; +&lt;br /&gt;
        &amp;quot;- 変数への代入: $&amp;lt;変数名&amp;gt; = &amp;lt;値&amp;gt; （例: $a = 5, $pi = 4*a(1)）\n&amp;quot; +&lt;br /&gt;
        &amp;quot;- 変数の参照: 変数名を数式に記述 （例: $a + 3, $b * 2）\n&amp;quot; +&lt;br /&gt;
        &amp;quot;- 複数の式を実行: セミコロン ; で区切る （例: $a = 3; $b = 2; $a + $b）&amp;quot;;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== サードパーティライブラリ ==&lt;br /&gt;
&lt;br /&gt;
[https://frippery.org/busybox/ busybox64u.exe]&lt;br /&gt;
&lt;br /&gt;
== 更新履歴 ==&lt;br /&gt;
&lt;br /&gt;
* 2025-10-25 公開&lt;/div&gt;</summary>
		<author><name>Yuko</name></author>
	</entry>
	<entry>
		<id>https://www.haijin-boys.com/wiki/index.php?title=Mery_%E9%9B%BB%E5%8D%93_(bc_%E3%82%B3%E3%83%9E%E3%83%B3%E3%83%89%E4%BD%BF%E7%94%A8%E7%89%88)&amp;diff=9243</id>
		<title>Mery 電卓 (bc コマンド使用版)</title>
		<link rel="alternate" type="text/html" href="https://www.haijin-boys.com/wiki/index.php?title=Mery_%E9%9B%BB%E5%8D%93_(bc_%E3%82%B3%E3%83%9E%E3%83%B3%E3%83%89%E4%BD%BF%E7%94%A8%E7%89%88)&amp;diff=9243"/>
		<updated>2025-10-25T07:33:02Z</updated>

		<summary type="html">&lt;p&gt;Yuko: ページの作成:「== 概要 ==  「Mery 電卓 (bc コマンド使用版)」は、 [https://www.haijin-boys.com/wiki/Mery_%E9%9B%BB%E5%8D%93 Mery 電卓] の派生版で、Meryエディタ上で数式を計算するためのマクロです。オリジナルの「Mery 電卓」よりも対応する数学関数が少ない代わりに起動が早いため、日々の利用の中で軽快に使用することができます。  Linux でお馴染みの bc コマンドを計算エンジ…」&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== 概要 ==&lt;br /&gt;
&lt;br /&gt;
「Mery 電卓 (bc コマンド使用版)」は、 [https://www.haijin-boys.com/wiki/Mery_%E9%9B%BB%E5%8D%93 Mery 電卓] の派生版で、Meryエディタ上で数式を計算するためのマクロです。オリジナルの「Mery 電卓」よりも対応する数学関数が少ない代わりに起動が早いため、日々の利用の中で軽快に使用することができます。&lt;br /&gt;
&lt;br /&gt;
Linux でお馴染みの bc コマンドを計算エンジンとしており、標準的な数式と一部の数学関数をサポートしています。カーソル位置の行または選択したテキストを数式として解釈し、結果を表示します。計算結果は自動的にクリップボードにコピーされ、カンマ区切りの数値として表示されます。&lt;br /&gt;
&lt;br /&gt;
本マクロをショートカットキーに設定し、いつでも起動できるようにしておくと日常シーンで何かと便利です。作者は「Ctrl + Alt + Enter」キーに割り当てて利用しています。&lt;br /&gt;
&lt;br /&gt;
== 注意事項 ==&lt;br /&gt;
&lt;br /&gt;
* Mery Ver 3.7.19 で動作確認しています。&lt;br /&gt;
&lt;br /&gt;
== 使い方 ==&lt;br /&gt;
&lt;br /&gt;
0. &#039;&#039;&#039;事前準備&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
* 本マクロでは計算コマンドの「bc」を内部で使っています。当該コマンドを使うために「Windows用BusyBox」を利用します&lt;br /&gt;
** ダウンロード先: https://frippery.org/busybox/&lt;br /&gt;
** ダウンロードするファイル: busybox64u.exe ※ダウンロード後、マクロファイルと同じフォルダーに配置してください&lt;br /&gt;
*** Windows 10 (1903) 以降の環境では上記を推奨。それ以前の環境の場合は動作しない可能性があるため、適切な実行ファイルを選択してください。&lt;br /&gt;
&lt;br /&gt;
1. &#039;&#039;&#039;行全体を計算する場合&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* 計算したい数式が書かれた行にカーソルを置きます。&lt;br /&gt;
* マクロを実行すると、次の行に「= 計算結果」が表示されます。&lt;br /&gt;
* 既に結果行がある場合は、その行が更新されます。&lt;br /&gt;
&lt;br /&gt;
2. &#039;&#039;&#039;選択範囲を計算する場合&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* 計算したい数式部分を選択します。&lt;br /&gt;
* マクロを実行すると、選択範囲の後ろに「計算式 = 計算結果」が挿入されます。&lt;br /&gt;
&lt;br /&gt;
3. &#039;&#039;&#039;サポートされている演算子&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* 四則演算（+, -, *, /）&lt;br /&gt;
* べき乗（^）&lt;br /&gt;
* 剰余（%） ※整数の剰余にのみ対応&lt;br /&gt;
* 比較演算子（&amp;lt;, &amp;gt;, &amp;lt;=, &amp;gt;=, ==, !=）&lt;br /&gt;
&lt;br /&gt;
4. &#039;&#039;&#039;サポートされている関数&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* s(x): サイン（引数はラジアン）&lt;br /&gt;
* c(x): コサイン（引数はラジアン）&lt;br /&gt;
* a(x): アークタンジェント（返り値はラジアン）&lt;br /&gt;
* l(x): 自然対数&lt;br /&gt;
* e(x): 指数関数&lt;br /&gt;
* sqrt(x): 平方根&lt;br /&gt;
* j(n,x): n次ベッセル関数&lt;br /&gt;
&lt;br /&gt;
5. &#039;&#039;&#039;変数の利用&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* 変数への代入: $&amp;lt;変数名&amp;gt; = &amp;lt;値&amp;gt; （例: $a = 5, $pi = 4*a(1)）&lt;br /&gt;
* 変数の参照: 変数名を数式に記述 （例: $a + 3, $b * 2）&lt;br /&gt;
* 複数の式を実行: セミコロン ; で区切る （例: $a = 3; $b = 2; $a + $b）&lt;br /&gt;
* インクリメント・デクリメント: ++, -- (例: $a++, ++$a)&lt;br /&gt;
&lt;br /&gt;
6. &#039;&#039;&#039;特別な機能&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* カンマ区切り表示: 計算結果は読みやすいようにカンマ区切りで表示されます。変数「COMMA」を調整することで制御できます。&lt;br /&gt;
* 自動コピー: 計算結果は自動的にクリップボードにコピーされます。変数「COPY」を調整することで制御できます。&lt;br /&gt;
* 計算結果の再利用: 行頭の「= 」は計算実行時に無視されるため、計算結果をスムーズに次の式として利用できます。&lt;br /&gt;
* 変数の利用: 複数行を選択して実行、または一行内で「;」で数式を区切ることで、変数を使った数式が利用できます。変数形式は「$[a-zA-Z][a-zA-Z0-9]*」に対応しています。例えば「$a = 2; $a * 3」として実行すると、結果は「6」と出力されます。&lt;br /&gt;
* メモ書きから計算: 数字や関数以外の文字はできるだけ除外した上で計算をします。例えば「月100円 * 12ヶ月」のようなメモ書きに対して実行すると「1,200」と出力されます。また「` (バッククォート)」で囲んだ文字は確実に無視されるため、計算メモ用として使うことができます (使用例: 『1500円 `金額未定のため仮置き`』)。&lt;br /&gt;
&lt;br /&gt;
== ソースコード ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot; copy&amp;gt;&lt;br /&gt;
#title = &amp;quot;Mery 電卓 (bc コマンド使用版)&amp;quot;&lt;br /&gt;
#include &amp;quot;Common.js&amp;quot;&lt;br /&gt;
BeginUndoGroup();&lt;br /&gt;
&lt;br /&gt;
// 事前準備:&lt;br /&gt;
//   以下のサイトから busybox64u.exe を入手し、本マクロと同じフォルダーに配置することで動作します。&lt;br /&gt;
//   https://frippery.org/busybox/&lt;br /&gt;
&lt;br /&gt;
// 結果をクリップボードにコピーするか&lt;br /&gt;
var COPY = true;&lt;br /&gt;
// 出力結果をカンマ区切りにするするか&lt;br /&gt;
var COMMA = true;&lt;br /&gt;
&lt;br /&gt;
// busybox のパスを取得&lt;br /&gt;
const scriptDir = ScriptFullName.replace(/\\[^\\]+$/g, &amp;quot;&amp;quot;)&lt;br /&gt;
const busyboxPath = scriptDir + &amp;quot;\\busybox64u.exe&amp;quot;;&lt;br /&gt;
if (!shell.FileExists(busyboxPath)) {&lt;br /&gt;
    alert(&amp;quot;busybox64u.exe が見つかりません。\nこのマクロを動作させるには、busybox64u.exe を本マクロと同じフォルダーに配置する必要があります。&amp;quot;);&lt;br /&gt;
    Quit();&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
var sel = document.selection;&lt;br /&gt;
&lt;br /&gt;
if (sel.Count &amp;gt; 1) {&lt;br /&gt;
    // マルチカーソル実行時は計算中表示を出してから実行&lt;br /&gt;
    ShowTip(&amp;quot;計算中... (全 &amp;quot; + sel.Count + &amp;quot; 件)&amp;quot;, meShowTipPosCaret);&lt;br /&gt;
    doMultiEdit(main);&lt;br /&gt;
    ShowTip(&amp;quot;&amp;quot;, meShowTipHide);&lt;br /&gt;
} else {&lt;br /&gt;
    main();&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function main() {&lt;br /&gt;
    var isEmpty = sel.IsEmpty;&lt;br /&gt;
    var activePointY = sel.GetActivePointY(mePosLogical);&lt;br /&gt;
    if (isEmpty) {&lt;br /&gt;
        var exp = document.GetLine(activePointY);&lt;br /&gt;
    } else {&lt;br /&gt;
        var exp = sel.Text;&lt;br /&gt;
    }&lt;br /&gt;
    // 空白行の場合は何もしない&lt;br /&gt;
    if (exp.replace(/[\s　]+/g, &amp;quot;&amp;quot;) === &amp;quot;&amp;quot;) {&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // 計算実行&lt;br /&gt;
    try {&lt;br /&gt;
        var exp_replaced = replaceExpression(exp)&lt;br /&gt;
            .replace(/([^;]*%[^;]*)/g, &amp;quot;scale=0; $1; scale=10; &amp;quot;); // 剰余計算を整数にする&lt;br /&gt;
        const exec = shell.Exec(busyboxPath + &amp;quot; bc -l&amp;quot;, &amp;quot;scale=10; &amp;quot; + exp_replaced);&lt;br /&gt;
        if (exec.ExitCode !== 0) {&lt;br /&gt;
            throw new Error(&amp;quot;計算エラー: &amp;quot; + exec.StdErr);&lt;br /&gt;
        }&lt;br /&gt;
        // bc の出力のクセに合わせて整形する&lt;br /&gt;
        var result_str = exec.StdOut&lt;br /&gt;
            .trim()&lt;br /&gt;
            .replace(/^\./gm, &amp;quot;0.&amp;quot;)&lt;br /&gt;
            .replace(/(\.[0-9]*?)0+$/gm, &amp;quot;$1&amp;quot;);&lt;br /&gt;
    } catch (e) {&lt;br /&gt;
        if (exp_replaced === undefined) {&lt;br /&gt;
            alert(&amp;quot;エラー発生&amp;quot; + &amp;quot;\n------\n&amp;quot; + e)&lt;br /&gt;
        } else {&lt;br /&gt;
            alert(&amp;quot;整形後の数式で計算エラーが発生&amp;quot; + &amp;quot;\n------\n&amp;quot; + e + &amp;quot;\n元の数式: &amp;quot; + exp + &amp;quot;\n整形後の数式: &amp;quot; + exp_replaced);&lt;br /&gt;
        }&lt;br /&gt;
        var c = Confirm(&amp;quot;数式ヘルプを表示しますか？&amp;quot;);&lt;br /&gt;
        if (c) {&lt;br /&gt;
            editor.NewFile();&lt;br /&gt;
            var newDoc = editor.ActiveDocument;&lt;br /&gt;
            newDoc.Write(help());&lt;br /&gt;
            newDoc.selection.SetActivePos(0);&lt;br /&gt;
        }&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // 小数点以上のみカンマ区切り&lt;br /&gt;
    if (COMMA &amp;amp;&amp;amp; result_str.match(/^[0-9]+(\.[0-9]+)?$/m)) {&lt;br /&gt;
        var result_parts = result_str.split(&amp;quot;.&amp;quot;);&lt;br /&gt;
        result_parts[0] = result_parts[0].replace(/(\d)(?=(\d{3})+$)/gm, &amp;quot;$1,&amp;quot;);&lt;br /&gt;
        if (result_parts.length &amp;gt; 1) {&lt;br /&gt;
            result_str = result_parts.join(&amp;quot;.&amp;quot;);&lt;br /&gt;
        } else {&lt;br /&gt;
            result_str = result_parts[0];&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // bc コマンドでは複数個の計算出力があると計算結果が複数行になるので1行にまとめる&lt;br /&gt;
    result_str = result_str.replace(/\r?\n/g, &amp;quot; ; &amp;quot;);&lt;br /&gt;
&lt;br /&gt;
    // 計算行が = 始まりの場合、= を削除する&lt;br /&gt;
    if (isEmpty &amp;amp;&amp;amp; exp.match(/^= /)) {&lt;br /&gt;
        sel.SetActivePoint(mePosLogical, 1, activePointY, false);&lt;br /&gt;
        sel.EndOfLine(true, mePosLogical);&lt;br /&gt;
        sel.Text = sel.Text.replace(/^= /, &amp;quot;&amp;quot;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // 既存の計算結果を上書きするか判定&lt;br /&gt;
    var pattern = /^=\s*(-?[0-9.,]+(\s*;\s*)?)*\s*$/;&lt;br /&gt;
    var nextLine = document.GetLine(activePointY + 1);&lt;br /&gt;
    if (isEmpty &amp;amp;&amp;amp; nextLine.match(pattern)) {&lt;br /&gt;
        sel.SetActivePoint(mePosLogical, 1, activePointY + 1, false);&lt;br /&gt;
        sel.EndOfLine(true, mePosLogical);&lt;br /&gt;
        sel.Text = &#039;= &#039; + result_str&lt;br /&gt;
    } else if (isEmpty) {&lt;br /&gt;
        sel.LineOpen();&lt;br /&gt;
        sel.Text = &#039;= &#039; + result_str&lt;br /&gt;
    } else {&lt;br /&gt;
        sel.CharRight();&lt;br /&gt;
        const inputExp = exp.replace(/((?!;)\n)/mg, &amp;quot;; &amp;quot;).replace(/;\s*;/g, &amp;quot;;&amp;quot;).replace(/　/g, &amp;quot; &amp;quot;).replace(/;\s*$/, &amp;quot;&amp;quot;).trim();&lt;br /&gt;
        var newLine = &amp;quot;&amp;quot;;&lt;br /&gt;
        if (exp.slice(-1) !== &amp;quot;\n&amp;quot;) {&lt;br /&gt;
            newLine = &#039;\n&#039;;&lt;br /&gt;
        }&lt;br /&gt;
        sel.Text = newLine + inputExp + &#039;\n= &#039; + result_str + &#039;\n&#039;;&lt;br /&gt;
        sel.CharLeft();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (COPY) {&lt;br /&gt;
        ClipboardData.SetData(result_str);&lt;br /&gt;
        Status = &amp;quot;計算結果 &amp;quot; + result_str + &amp;quot; をコピーしました&amp;quot;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/**&lt;br /&gt;
 * 数式を置換する&lt;br /&gt;
 */&lt;br /&gt;
function replaceExpression(exp) {&lt;br /&gt;
    // コメント扱いの記載を削除&lt;br /&gt;
    let exp_replaced = exp.replace(/`[^`]*`/g, &amp;quot; &amp;quot;)&lt;br /&gt;
&lt;br /&gt;
    // 他の言語で対応する演算子などはあらかじめエラーにする&lt;br /&gt;
    const invalidOperators = [&amp;quot;\\*\\*&amp;quot;, &amp;quot;//&amp;quot;];&lt;br /&gt;
    for (var i = 0; i &amp;lt; invalidOperators.length; i++) {&lt;br /&gt;
        var operator = invalidOperators[i];&lt;br /&gt;
        if (exp.match(new RegExp(operator))) {&lt;br /&gt;
            throw new Error(&amp;quot;演算子「&amp;quot; + operator + &amp;quot;」はサポートされていません&amp;quot;);&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    exp_replaced = exp_replaced.trim()&lt;br /&gt;
        // 先頭の = を削除&lt;br /&gt;
        .replace(/^= /, &amp;quot;&amp;quot;)&lt;br /&gt;
        // 改行を ; に置換&lt;br /&gt;
        .replace(/\n/g, &amp;quot;;&amp;quot;)&lt;br /&gt;
        // 非ASCII文字を削除&lt;br /&gt;
        .replace(/[^\x20-\x7e]/g, &amp;quot; &amp;quot;)&lt;br /&gt;
        // 全角スペースを半角スペースに変換&lt;br /&gt;
        .replace(/　/g, &amp;quot; &amp;quot;)&lt;br /&gt;
        // 数字内のカンマを取り除く。j(1, 2) では削除しない&lt;br /&gt;
        .replace(/j *\(\d+, *\d+\)|,/g, function (match) {&lt;br /&gt;
            return match.match(/j/) ? match : &amp;quot;&amp;quot;;&lt;br /&gt;
        })&lt;br /&gt;
        // 変数形式を退避してからドル記号削除&lt;br /&gt;
        .replace(/(\$[a-zA-Z][a-zA-Z0-9]*)/g, &amp;quot; %%%$1%%% &amp;quot;)&lt;br /&gt;
        .replace(/\$/g, &amp;quot;&amp;quot;)&lt;br /&gt;
        // 空の括弧を削除&lt;br /&gt;
        .replace(/\(\s*\)|\[\s*\]|\{\s*\}/g, &amp;quot; &amp;quot;)&lt;br /&gt;
        // 対応関数以外の文字列は削除&lt;br /&gt;
        .replace(/[a-zA-Z]+/g, function (match) {&lt;br /&gt;
            const functions = [&amp;quot;s&amp;quot;, &amp;quot;c&amp;quot;, &amp;quot;a&amp;quot;, &amp;quot;l&amp;quot;, &amp;quot;e&amp;quot;, &amp;quot;sqrt&amp;quot;, &amp;quot;j&amp;quot;];&lt;br /&gt;
            for (var i = 0; i &amp;lt; functions.length; i++) {&lt;br /&gt;
                var f = functions[i];&lt;br /&gt;
                if (match === f) {&lt;br /&gt;
                    return match;&lt;br /&gt;
                }&lt;br /&gt;
            }&lt;br /&gt;
            return &amp;quot; &amp;quot;;&lt;br /&gt;
        })&lt;br /&gt;
        // `/hour` などの文字列があるとスラッシュだけ残るためスラッシュを削除&lt;br /&gt;
        .replace(/\/\s*([,+\-*\/%^&amp;amp;~|&amp;lt;&amp;gt;?/)\];\n]|$)/g, &amp;quot;$1&amp;quot;)&lt;br /&gt;
        // 退避文字を復元&lt;br /&gt;
        .replace(/ %%%\$([a-zA-Z][a-zA-Z0-9]*)%%% /g, &amp;quot; $1 &amp;quot;)&lt;br /&gt;
        .replace(/ %%%([^%]+)%%% /g, &amp;quot; $1 &amp;quot;)&lt;br /&gt;
&lt;br /&gt;
    return exp_replaced;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function help() {&lt;br /&gt;
    return &amp;quot;【Mery 電卓 (bc コマンド使用版) で使用可能な演算子と関数】\n&amp;quot; +&lt;br /&gt;
        &amp;quot;\n■基本演算子:\n&amp;quot; +&lt;br /&gt;
        &amp;quot;`+` (加算), `-` (減算), `*` (乗算), `/` (除算), `%` (剰余), `^` (べき乗)\n&amp;quot; +&lt;br /&gt;
        &amp;quot;\n■比較演算子:\n&amp;quot; +&lt;br /&gt;
        &amp;quot;`==` (等しい), `!=` (等しくない), `&amp;lt;` (より小さい), `&amp;lt;=` (以下), `&amp;gt;` (より大きい), `&amp;gt;=` (以上)\n&amp;quot; +&lt;br /&gt;
        &amp;quot;\n■代入演算子:\n&amp;quot; +&lt;br /&gt;
        &amp;quot;`=`, `+=`, `-=`, `*=`, `/=`, `%=`, `^=`\n&amp;quot; +&lt;br /&gt;
        &amp;quot;\n■インクリメント・デクリメント:\n&amp;quot; +&lt;br /&gt;
        &amp;quot;`++`, `--`\n&amp;quot; +&lt;br /&gt;
        &amp;quot;\n■数学関数 (要 `-l` オプション):\n&amp;quot; +&lt;br /&gt;
        &amp;quot;- `s(x)`: サイン（引数はラジアン）\n&amp;quot; +&lt;br /&gt;
        &amp;quot;- `c(x)`: コサイン（引数はラジアン）\n&amp;quot; +&lt;br /&gt;
        &amp;quot;- `a(x)`: アークタンジェント（返り値はラジアン）\n&amp;quot; +&lt;br /&gt;
        &amp;quot;- `l(x)`: 自然対数\n&amp;quot; +&lt;br /&gt;
        &amp;quot;- `e(x)`: 指数関数\n&amp;quot; +&lt;br /&gt;
        &amp;quot;- `sqrt(x)`: 平方根\n&amp;quot; +&lt;br /&gt;
        &amp;quot;- `j(n,x)`: n次ベッセル関数\n&amp;quot; +&lt;br /&gt;
        &amp;quot;\n■変数の使い方:\n&amp;quot; +&lt;br /&gt;
        &amp;quot;- 変数への代入: $&amp;lt;変数名&amp;gt; = &amp;lt;値&amp;gt; （例: $a = 5, $pi = 4*a(1)）\n&amp;quot; +&lt;br /&gt;
        &amp;quot;- 変数の参照: 変数名を数式に記述 （例: $a + 3, $b * 2）\n&amp;quot; +&lt;br /&gt;
        &amp;quot;- 複数の式を実行: セミコロン ; で区切る （例: $a = 3; $b = 2; $a + $b）&amp;quot;;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== サードパーティライブラリ ==&lt;br /&gt;
&lt;br /&gt;
[https://frippery.org/busybox/ busybox64u.exe]&lt;br /&gt;
&lt;br /&gt;
== 更新履歴 ==&lt;br /&gt;
&lt;br /&gt;
* 2025-10-25 公開&lt;/div&gt;</summary>
		<author><name>Yuko</name></author>
	</entry>
	<entry>
		<id>https://www.haijin-boys.com/wiki/index.php?title=%E3%83%9E%E3%82%AF%E3%83%AD%E3%83%A9%E3%82%A4%E3%83%96%E3%83%A9%E3%83%AA&amp;diff=9242</id>
		<title>マクロライブラリ</title>
		<link rel="alternate" type="text/html" href="https://www.haijin-boys.com/wiki/index.php?title=%E3%83%9E%E3%82%AF%E3%83%AD%E3%83%A9%E3%82%A4%E3%83%96%E3%83%A9%E3%83%AA&amp;diff=9242"/>
		<updated>2025-10-25T07:16:40Z</updated>

		<summary type="html">&lt;p&gt;Yuko: /* その他 */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Mery 用のマクロ ライブラリです。&lt;br /&gt;
&lt;br /&gt;
スクリプトを拡張子 &amp;quot;js&amp;quot;（JavaScript、JScript の場合）にして Macros フォルダーなどに保存し、[マクロ] メニューの [選択] や [カスタマイズ] から読み込んで使用してください。&lt;br /&gt;
&lt;br /&gt;
このマクロ ライブラリはどなたでも自由に編集可能ですので、ご自身が開発した便利なマクロをどんどん共有してください。&lt;br /&gt;
&lt;br /&gt;
また、[[隠しオプション一覧]]に希望するオプションがあるかもしれませんので、そちらも参照してみてください。&lt;br /&gt;
&lt;br /&gt;
== テキスト操作補助 ==&lt;br /&gt;
* [https://github.com/haijinboys/mery-macro-emmet/blob/master/emmet.js Emmet] (Kuro)&lt;br /&gt;
* [[Zen Coding]] (Kuro)&lt;br /&gt;
* [[バイト数]] (Kuro, ks)&lt;br /&gt;
* [[日付と時刻を挿入]] (n, masme, isari)&lt;br /&gt;
* [[日付と時刻を挿入（カレンダー版）]] (papagoat)&lt;br /&gt;
* [[選択範囲を上下に移動]] (ks)&lt;br /&gt;
* [[TeraPadのCtrl↑と同じような動作をするマクロ]] (suke)&lt;br /&gt;
* [[TeraPadのCtrl↓と同じような動作をするマクロ]] (suke)&lt;br /&gt;
* [[Vz 風テキストスタック 2 ストロークキー]] (papagoat)&lt;br /&gt;
* [[ラクラク罫線]] (手石)&lt;br /&gt;
* [[矩形ペースト]] (手石)&lt;br /&gt;
* [[自動箇条書き]] (手石)&lt;br /&gt;
* [[特殊改行]] (FuG)&lt;br /&gt;
* [[部分編集]] (ks)&lt;br /&gt;
* [[文字カウント]] (masme)&lt;br /&gt;
* [[字下げ・字上げ]] (masme)&lt;br /&gt;
* [[数値を増減]] (masme)&lt;br /&gt;
* [[上に行挿入]] (yuko)&lt;br /&gt;
* [[選択範囲を広げる]] (yuko)&lt;br /&gt;
* [[貼付け実行後のカーソル位置を貼付け文字列の先頭にする]] (yuko)&lt;br /&gt;
* [[選択した文字列を文書からすべて削除]] (Noah)&lt;br /&gt;
* [[文章で使用されている文字をユニーク抽出]] (Kuro)&lt;br /&gt;
* [[CSV のカーソル位置の列を削除]] (MSY-07)&lt;br /&gt;
* [[XHTML入力補助]] (アラフォー親父)&lt;br /&gt;
* [[Markdown文書で改行時にリスト、引用ブロック開始記号を自動入力する]] (catfist, yuko)&lt;br /&gt;
* [[Markdown 向け、リスト記号 (*) のトグル]] (yuko)&lt;br /&gt;
* [[Markdown 向け、タスクリスト (* ［］) のチェック状態をトグル]] (yuko)&lt;br /&gt;
* [[Markdown入力補助]] (急急如律令)&lt;br /&gt;
* [[青空文庫用整形補助]] (ks, 急急如律令)&lt;br /&gt;
* [[自動ルビ振り]] (急急如律令)&lt;br /&gt;
* [[ドイツ語特殊文字]] (鯉)&lt;br /&gt;
* [[何年生で習う漢字？]] (シリル)&lt;br /&gt;
* [[操作の繰り返し]] (Kuro)&lt;br /&gt;
&lt;br /&gt;
== ファイル操作補助 ==&lt;br /&gt;
* [[フォルダ内のファイルをすべて開く]] (Kuro)&lt;br /&gt;
* [[ファイルを比較]] (Kuro, MSY-07)&lt;br /&gt;
* [[ファイル名を引数にしてアプリ起動]] (Kuro, miso)&lt;br /&gt;
* [[文字列中のパス起動]] (ks)&lt;br /&gt;
* [[パス・URLを開く コマンドを実行する]] (yuko)&lt;br /&gt;
* [[開いている文書のファイル名を変更する]] (yuko)&lt;br /&gt;
* [[上書き保存(UAC対応)]] (ks)&lt;br /&gt;
* [[含んでいるフォルダを開く]] (ks)&lt;br /&gt;
* [[フォルダを開く]] (n)&lt;br /&gt;
* [[拡張子の違うファイルを開く]] (n)&lt;br /&gt;
* [[コマンドプロンプトを開く]] (n)&lt;br /&gt;
* [[ブックマーク一覧ジャンプ]] (goat)&lt;br /&gt;
* [[よく使うフォルダ]] (手石)&lt;br /&gt;
* [[ファイル（選択範囲）の仮保存]] (手石)&lt;br /&gt;
* [[ポップアップメニューでファイル選択]] (手石)&lt;br /&gt;
* [[フォルダツリーをポップアップ表示]] (n)&lt;br /&gt;
* [[タグジャンプ]] (o)&lt;br /&gt;
* [[ウィンドウの位置とサイズを拡張子で指定]] (papagoat)&lt;br /&gt;
* [[編集モードの自動選択]] (Bleat)&lt;br /&gt;
* [[書き換え禁止モード色反転]] (Bleat)&lt;br /&gt;
* [[変更なしのタブをすべて閉じる]] (Noah)&lt;br /&gt;
&lt;br /&gt;
== 変換・ソート・整形 ==&lt;br /&gt;
* [[標準変換]] (Kuro, MSY-07)&lt;br /&gt;
* [[テキスト変換]] (ks)&lt;br /&gt;
* [[テキスト整形]] (masme)&lt;br /&gt;
* [[旧漢字から新漢字に変換]] (furu)&lt;br /&gt;
* [[Unicode正規化]] (ucky)&lt;br /&gt;
* [[Wikipediaマークアップ修正]] (MSY-07)&lt;br /&gt;
* [[SHA でハッシュ化]] (Kuro)&lt;br /&gt;
* [[ブックマーク系コマンド集]] (ks)&lt;br /&gt;
* [[clustalw を用いてアライメントを実行]] (suke)&lt;br /&gt;
* [[clustal-omegaを用いてアライメントを実行]] (suke)&lt;br /&gt;
* [[連番の挿入]] (手石)&lt;br /&gt;
* [[連番を挿入]] (masme)&lt;br /&gt;
* [[マルチカーソルに連番を入力]] (yuko)&lt;br /&gt;
* [[行並べ替え]] (masme)&lt;br /&gt;
* [[自然数ソート]] (tak)&lt;br /&gt;
* [[可変長を固定長に変換]] (MSY-07)&lt;br /&gt;
* [[TSV(CSV) ⇒ 固定長]] (手石)&lt;br /&gt;
* [[GFMをHTMLに変換]] (noonworks)&lt;br /&gt;
* [[基数変換10 16]] (シリル)&lt;br /&gt;
* [[スネークケース ⇔ キャメルケースの変換]] (Noah)&lt;br /&gt;
&lt;br /&gt;
== 検索・置換 ==&lt;br /&gt;
* [[文書から検索]] (Kuro)&lt;br /&gt;
* [[単語を抽出]] (Kuro)&lt;br /&gt;
* [[タグの外側（内側）だけ置換]] (手石)&lt;br /&gt;
* [[ポップアップメニューで検索先にジャンプ]] (手石)&lt;br /&gt;
* [[検索(SJIS以外)]] (masme)&lt;br /&gt;
* [https://github.com/TakamiChie/mery-scripts/blob/master/SearchWithMode.js 現在開いているファイルの言語名をつけてWeb検索] (高見知英)&lt;br /&gt;
* [https://github.com/GunseiKPaseri/Mery_macro/blob/master/count.js 文字出現量ランキング] (群生系パセリ)&lt;br /&gt;
* [[検索ヒット数表示]] (ks)&lt;br /&gt;
* [[任意の検索エンジンで検索]] (Noah)&lt;br /&gt;
* [[任意の検索エンジンで検索ー改良版]] (Takeshi)&lt;br /&gt;
* [[デフォルトの検索フラグを設定して検索ウィンドウ表示]] (yuko)&lt;br /&gt;
* [[複数置換]] (Noah)&lt;br /&gt;
* [[類義語アウトプット【Python】]] (シリル)&lt;br /&gt;
* [[校正支援アウトプット]] (シリル)&lt;br /&gt;
* [[見出しジャンプ]] (yuko)&lt;br /&gt;
* [[CSV/TSVでアクティブ列選択]] (yuko)&lt;br /&gt;
&lt;br /&gt;
== プログラミング補助 ==&lt;br /&gt;
* [[Eclipse風コメントアウト]] (ks)&lt;br /&gt;
* [[Eclipse風コメントアウト(改)]] (pizz)&lt;br /&gt;
* [[コメントマーク付け外し]] (手石, masme)&lt;br /&gt;
* [[スマートインデント(C 構文)]] (ks)&lt;br /&gt;
* [[ctags.exeでtagsファイルを生成、タグジャンプを行うマクロ]] (kazy)&lt;br /&gt;
* [[タグファイルから補完]] (n)&lt;br /&gt;
* [[コードスニペットもどき]] (手石)&lt;br /&gt;
* [[TeX文書のコンパイル]] (手石, isari)&lt;br /&gt;
* [[簡易数式計算]] (ks)&lt;br /&gt;
* [[定義へ移動]] (ks)&lt;br /&gt;
* [[タブの桁数]] (手石)&lt;br /&gt;
* [[行末の空白を削除]] (n)&lt;br /&gt;
* [https://github.com/TakamiChie/mery-scripts/blob/master/FileExecutor.js 開いているスクリプトファイルを実行] (高見知英)&lt;br /&gt;
* [https://github.com/TakamiChie/mery-scripts/blob/master/SyntaxChecker.js 開いているスクリプトファイルを構文チェック] (高見知英)&lt;br /&gt;
* [[説明を出力]] (s)&lt;br /&gt;
* [[JSON整形]] (Ovis)&lt;br /&gt;
* [[コンソール実行]] (YOS G-spec)&lt;br /&gt;
* [[拡張子からコンソール実行]] (YOS G-spec)&lt;br /&gt;
* [[ScriptRun]] (fuka)&lt;br /&gt;
* [[JavaScript を実行]] (n)&lt;br /&gt;
* [[Ruby を実行]] (n)&lt;br /&gt;
* [[HSPキーワード検索]] (SSSE)&lt;br /&gt;
&lt;br /&gt;
== おもしろ ==&lt;br /&gt;
* [[メール送信]] (ks)&lt;br /&gt;
* [[あつまれ☆みんなのおもしろアイコン]] (シリル, ucky)&lt;br /&gt;
* [[テーマのトグル]] (ucky)&lt;br /&gt;
* [[お気に入りフォント]] (ucky)&lt;br /&gt;
* [[Google Gemini に相談]] (yuko)&lt;br /&gt;
* [[OpenAI GPT に相談]] (yuko)&lt;br /&gt;
* [[Groq に相談]] (yuko)&lt;br /&gt;
* [[Google Gemini で執筆支援]] (Kuro)&lt;br /&gt;
* [[MeryToys]] (Kuro)&lt;br /&gt;
&lt;br /&gt;
== その他 ==&lt;br /&gt;
* [[最新版を確認]] (diaphragm)&lt;br /&gt;
* [[SAPI 使って単語読み上げ]] (miso)&lt;br /&gt;
* [[フォルダ選択ダイアログ]] (手石)&lt;br /&gt;
* [[HTA(HTML)の自作ダイアログを表示]] (noonworks)&lt;br /&gt;
* [[ポップアップメニューを手軽に扱う]] (komorebikoboshi)&lt;br /&gt;
* [[ポップアップメニューでチェックボックス]] (手石)&lt;br /&gt;
* [[ポップアップメニューでラジオボタン]] (手石)&lt;br /&gt;
* [[マクロ・関数をメニューで呼び出す]] (aliquis)&lt;br /&gt;
* [[階層化マクロメニュー]] (ks)&lt;br /&gt;
* [[シェルコマンドを実行]] (sasaki)&lt;br /&gt;
* [[Editor.OpenFile()のポリフィル]] (kazy)&lt;br /&gt;
* [[折り返しトグル切り替え]] (Bleat)&lt;br /&gt;
* [[Beep音の例]] (star0worshipper)&lt;br /&gt;
* [[ブラウザ風タブ移動]] (yuko)&lt;br /&gt;
* [[タブ位置を先頭/末尾に移動]] (yuko)&lt;br /&gt;
* [[Mery 電卓]] (yuko)&lt;br /&gt;
* [[Mery 電卓 (bc コマンド使用版)]] (yuko)&lt;br /&gt;
&lt;br /&gt;
== 開発者向け ==&lt;br /&gt;
* [[キーアサイン集]] (ks, masme, isari)&lt;br /&gt;
* [[includeライブラリ]] (ks)&lt;br /&gt;
* [[マクロ覚え書き（開発者向け）]] (ks, papagoat)&lt;br /&gt;
&lt;br /&gt;
== 注意事項 ==&lt;br /&gt;
[https://www.haijin-boys.com/software/mery/mery-2-6-6#5 Mery Ver 2.6.6]以前に作成されたマクロが動作しない場合は下記のページを参照してください。&lt;br /&gt;
&lt;br /&gt;
* [https://www.haijin-boys.com/wiki/%E3%83%9E%E3%82%AF%E3%83%AD%E3%83%AA%E3%83%95%E3%82%A1%E3%83%AC%E3%83%B3%E3%82%B9:3:%E3%83%87%E3%82%A3%E3%83%AC%E3%82%AF%E3%83%86%E3%82%A3%E3%83%96##language マクロリファレンス:3:ディレクティブ]&lt;br /&gt;
* [https://www.haijin-boys.com/discussions/6929 公式のマクロ 箇条書きが最新メリーで動作するかチェックいただけませんか？]&lt;/div&gt;</summary>
		<author><name>Yuko</name></author>
	</entry>
	<entry>
		<id>https://www.haijin-boys.com/wiki/index.php?title=Mery_%E9%9B%BB%E5%8D%93&amp;diff=9241</id>
		<title>Mery 電卓</title>
		<link rel="alternate" type="text/html" href="https://www.haijin-boys.com/wiki/index.php?title=Mery_%E9%9B%BB%E5%8D%93&amp;diff=9241"/>
		<updated>2025-10-25T05:26:11Z</updated>

		<summary type="html">&lt;p&gt;Yuko: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== 概要 ==&lt;br /&gt;
&lt;br /&gt;
「Mery 電卓」は、Meryエディタ上で数式を計算するためのマクロです。標準的な数学関数をサポートしており、カーソル位置の行または選択したテキストを数式として解釈し、結果を表示します。計算結果は自動的にクリップボードにコピーされ、カンマ区切りの数値として表示されます。&lt;br /&gt;
&lt;br /&gt;
本マクロをショートカットキーに設定し、いつでも起動できるようにしておくと日常シーンで何かと便利です。作者は「Ctrl + Alt + Enter」キーに割り当てて利用しています。&lt;br /&gt;
&lt;br /&gt;
== 注意事項 ==&lt;br /&gt;
&lt;br /&gt;
* Mery Ver 3.7.19 で動作確認しています。&lt;br /&gt;
&lt;br /&gt;
== 使い方 ==&lt;br /&gt;
&lt;br /&gt;
0. &#039;&#039;&#039;事前準備&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
* 本マクロでは計算エンジンライブラリの「math.js」をインポートして利用しています。当該ライブラリをマクロフォルダーに配置します。&lt;br /&gt;
** ダウンロード先: https://unpkg.com/mathjs@15.0.0/lib/browser/math.js&lt;br /&gt;
** 保存ファイル名: math_v15.0.0.js&lt;br /&gt;
&lt;br /&gt;
1. &#039;&#039;&#039;行全体を計算する場合&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* 計算したい数式が書かれた行にカーソルを置きます。&lt;br /&gt;
* マクロを実行すると、次の行に「= 計算結果」が表示されます。&lt;br /&gt;
* 既に結果行がある場合は、その行が更新されます。&lt;br /&gt;
&lt;br /&gt;
2. &#039;&#039;&#039;選択範囲を計算する場合&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* 計算したい数式部分を選択します。&lt;br /&gt;
* マクロを実行すると、選択範囲の後ろに「計算式 = 計算結果」が挿入されます。&lt;br /&gt;
&lt;br /&gt;
3. &#039;&#039;&#039;サポートされている演算子&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* 四則演算（+, -, *, /）&lt;br /&gt;
* べき乗（^）&lt;br /&gt;
* 剰余（%, mod）&lt;br /&gt;
* 比較演算子（&amp;lt;, &amp;gt;, &amp;lt;=, &amp;gt;=, ==, !=）&lt;br /&gt;
* 論理演算子（and, or, not）&lt;br /&gt;
&lt;br /&gt;
4. &#039;&#039;&#039;サポートされている関数&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* 三角関数（sin, cos, tan, asin, acos, atan）&lt;br /&gt;
* 双曲線関数（sinh, cosh, tanh, asinh, acosh, atanh）&lt;br /&gt;
* 対数関数（log, log10, log2, log1p）&lt;br /&gt;
* その他の数学関数（sqrt, cbrt, abs, ceil, floor, round, trunc など）&lt;br /&gt;
** その他の利用可能な関数は math.js の公式リファレンス https://mathjs.org/docs/reference/functions.html を参照&lt;br /&gt;
&lt;br /&gt;
5. &#039;&#039;&#039;特別な機能&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* カンマ区切り表示: 計算結果は読みやすいようにカンマ区切りで表示されます。変数「COMMA」を調整することで制御できます。&lt;br /&gt;
* 自動コピー: 計算結果は自動的にクリップボードにコピーされます。変数「COPY」を調整することで制御できます。&lt;br /&gt;
* 計算結果の再利用: 行頭の「= 」は計算実行時に無視されるため、計算結果をスムーズに次の式として利用できます。&lt;br /&gt;
* 変数の利用: 複数行、または「;」で区切ることで、変数を使った数式が利用できます。変数形式は「$[a-zA-Z][a-zA-Z0-9]*」に対応しています。例えば「$a = 2; $a * 3」として実行すると、結果は「6」と出力されます。&lt;br /&gt;
* メモ書きから計算: 数字や関数以外の文字はできるだけ除外した上で計算をします。例えば「月100円 * 12ヶ月」のようなメモ書きに対して実行すると「1,200」と出力されます。また「` (バッククォート)」で囲んだ文字は確実に無視されるため、計算メモ用として使うことができます (使用例: 『1500円 `金額未定のため仮置き`』)。&lt;br /&gt;
&lt;br /&gt;
== ソースコード ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot; copy&amp;gt;&lt;br /&gt;
#title = &amp;quot;Mery 電卓&amp;quot;&lt;br /&gt;
#language = &amp;quot;v8&amp;quot;&lt;br /&gt;
#include &amp;quot;Common.js&amp;quot;&lt;br /&gt;
#include &amp;quot;math_v15.0.0.js&amp;quot;&lt;br /&gt;
Redraw = false;&lt;br /&gt;
BeginUndoGroup();&lt;br /&gt;
&lt;br /&gt;
// 以下のようなCDNから math.js 本体を入手し、マクロフォルダに配置して利用している。&lt;br /&gt;
// https://unpkg.com/mathjs@15.0.0/lib/browser/math.js&lt;br /&gt;
// 以下の math.js 関数リファレンス掲載の関数を式内で利用することができる。&lt;br /&gt;
// https://mathjs.org/docs/reference/functions.html&lt;br /&gt;
&lt;br /&gt;
// 結果をクリップボードにコピー&lt;br /&gt;
var COPY = true;&lt;br /&gt;
// 出力結果をカンマ区切りにする&lt;br /&gt;
var COMMA = true;&lt;br /&gt;
&lt;br /&gt;
var sel = document.selection;&lt;br /&gt;
&lt;br /&gt;
// math.js 初期設定&lt;br /&gt;
math.config({&lt;br /&gt;
    number: &#039;BigNumber&#039;,  // デフォルトの数値型を BigNumber に設定&lt;br /&gt;
    precision: 64         // 有効桁数 (デフォルトは64桁)&lt;br /&gt;
})&lt;br /&gt;
const lowerAndUpperExp = { lowerExp: -7, upperExp: 15 };&lt;br /&gt;
&lt;br /&gt;
doMultiEdit(main);&lt;br /&gt;
&lt;br /&gt;
function main() {&lt;br /&gt;
    var isEmpty = sel.IsEmpty;&lt;br /&gt;
    var activePointY = sel.GetActivePointY(mePosLogical);&lt;br /&gt;
    if (isEmpty) {&lt;br /&gt;
        var exp = document.GetLine(activePointY);&lt;br /&gt;
    } else {&lt;br /&gt;
        var exp = sel.Text;&lt;br /&gt;
    }&lt;br /&gt;
    // 空白行の場合は何もしない&lt;br /&gt;
    if (exp.match(/^\s*$/)) {&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    // 計算実行&lt;br /&gt;
    try {&lt;br /&gt;
        var exp_replaced = replaceExpression(exp);&lt;br /&gt;
        var result_str = math.format(math.evaluate(exp_replaced), lowerAndUpperExp);&lt;br /&gt;
    } catch (e) {&lt;br /&gt;
        if (exp_replaced === undefined) {&lt;br /&gt;
            alert(&amp;quot;エラー発生&amp;quot; + &amp;quot;\n------\n&amp;quot; + e)&lt;br /&gt;
        } else {&lt;br /&gt;
            alert(&amp;quot;整形後の数式で計算エラーが発生&amp;quot; + &amp;quot;\n------\n&amp;quot; + e + &amp;quot;\n元の数式: &amp;quot; + exp + &amp;quot;\n整形後の数式: &amp;quot; + exp_replaced)&lt;br /&gt;
        }&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // 小数点以上のみカンマ区切り&lt;br /&gt;
    if (COMMA &amp;amp;&amp;amp; result_str.match(/^[0-9]+(\.[0-9]+)?$/)) {&lt;br /&gt;
        var result_parts = result_str.split(&amp;quot;.&amp;quot;);&lt;br /&gt;
        result_parts[0] = result_parts[0].replace(/(\d)(?=(\d{3})+$)/g, &amp;quot;$1,&amp;quot;);&lt;br /&gt;
        if (result_parts.length &amp;gt; 1) {&lt;br /&gt;
            result_str = result_parts.join(&amp;quot;.&amp;quot;);&lt;br /&gt;
        } else {&lt;br /&gt;
            result_str = result_parts[0];&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // 計算行が = 始まりの場合、= を削除する&lt;br /&gt;
    if (isEmpty &amp;amp;&amp;amp; exp.match(/^= /)) {&lt;br /&gt;
        sel.SetActivePoint(mePosLogical, 1, activePointY, false);&lt;br /&gt;
        sel.EndOfLine(true, mePosLogical);&lt;br /&gt;
        sel.Text = sel.Text.replace(/^= /, &amp;quot;&amp;quot;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // 既存の計算結果を上書きするか判定&lt;br /&gt;
    var pattern = /^=\s*(-?[0-9.,]|true|false|\[[0-9., \[\]]+\])*\s*$/;&lt;br /&gt;
    var nextLine = document.GetLine(activePointY + 1);&lt;br /&gt;
    if (isEmpty &amp;amp;&amp;amp; nextLine.match(pattern)) {&lt;br /&gt;
        sel.SetActivePoint(mePosLogical, 1, activePointY + 1, false);&lt;br /&gt;
        sel.EndOfLine(true, mePosLogical);&lt;br /&gt;
        sel.Text = &#039;= &#039; + result_str&lt;br /&gt;
    } else if (isEmpty) {&lt;br /&gt;
        sel.LineOpen();&lt;br /&gt;
        sel.Text = &#039;= &#039; + result_str&lt;br /&gt;
    } else {&lt;br /&gt;
        sel.CharRight();&lt;br /&gt;
        const inputExp = exp.replace(/((?!;)\n)/mg, &amp;quot;; &amp;quot;).replace(/;\s*;/g, &amp;quot;;&amp;quot;).replace(/　/g, &amp;quot; &amp;quot;).replace(/;\s*$/, &amp;quot;&amp;quot;).trim();&lt;br /&gt;
        if (exp.slice(-1) === &amp;quot;\n&amp;quot;) {&lt;br /&gt;
            sel.Text = inputExp + &#039;\n= &#039; + result_str + &#039;\n&#039;;&lt;br /&gt;
        } else {&lt;br /&gt;
            sel.Text = &#039;\n&#039; + inputExp + &#039;\n= &#039; + result_str + &#039;\n&#039;;&lt;br /&gt;
        }&lt;br /&gt;
        sel.CharLeft();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (COPY) {&lt;br /&gt;
        ClipboardData.SetData(result_str);&lt;br /&gt;
        Status = &amp;quot;計算結果 &amp;quot; + result_str + &amp;quot; をコピーしました&amp;quot;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/**&lt;br /&gt;
 * 数式を置換する&lt;br /&gt;
 */&lt;br /&gt;
function replaceExpression(exp) {&lt;br /&gt;
    // コメント扱いの記載を削除&lt;br /&gt;
    let exp_replaced = exp.replace(/`[^`]*`/g, &amp;quot; &amp;quot;)&lt;br /&gt;
&lt;br /&gt;
    // 他の言語で対応する演算子などはあらかじめエラーにする&lt;br /&gt;
    const invalidOperators = [&amp;quot;**&amp;quot;, &amp;quot;//&amp;quot;];&lt;br /&gt;
    invalidOperators.forEach(operator =&amp;gt; {&lt;br /&gt;
        if (exp.includes(operator)) {&lt;br /&gt;
            throw new Error(`「${operator}」はサポートされていません`);&lt;br /&gt;
        }&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    exp_replaced = exp_replaced.trim()&lt;br /&gt;
        // 先頭の = を削除&lt;br /&gt;
        .replace(/^= /, &amp;quot;&amp;quot;)&lt;br /&gt;
        // 改行を ; に置換&lt;br /&gt;
        .replace(/(?&amp;lt;!;)\n/mg, &amp;quot;;&amp;quot;)&lt;br /&gt;
        // 非ASCII文字を削除&lt;br /&gt;
        .replace(/[^\x20-\x7e]/g, &amp;quot; &amp;quot;)&lt;br /&gt;
        // 全角スペースを半角スペースに変換&lt;br /&gt;
        .replace(/　/g, &amp;quot; &amp;quot;)&lt;br /&gt;
        // 数字内のカンマを取り除く。[1,2] [1, 2] {a: 1, b: 2} mod(1, 2) のような形式では削除しない&lt;br /&gt;
        .replace(/(\d),(?=\d{3})/g, &#039;$1&#039;)&lt;br /&gt;
        // 予約語を退避&lt;br /&gt;
        .replace(/(?:^|\s)(PI|true|false)(?:$|\s)/g, &amp;quot; %%%$1%%% &amp;quot;)&lt;br /&gt;
        .replace(/([0-9]+|\$[a-zA-Z0-9]+) (mod|and|or|xor) ([0-9]+|\$[a-zA-Z0-9]+)/g, &amp;quot;$1 %%%$2%%% $3&amp;quot;)&lt;br /&gt;
        .replace(/not ([0-9]+|\$[a-zA-Z0-9]+)/g, &amp;quot; %%%$1%%% $2&amp;quot;)&lt;br /&gt;
        // 指数形式を退避&lt;br /&gt;
        .replace(/([0-9]+(?:\.[0-9]+)?[eE][+\-]?[0-9]+)/g, &amp;quot; %%%$1%%% &amp;quot;)&lt;br /&gt;
        // 変数形式を退避してからドル記号削除&lt;br /&gt;
        .replace(/(\$[a-zA-Z][a-zA-Z0-9]*)/g, &amp;quot; %%%$1%%% &amp;quot;)&lt;br /&gt;
        .replace(/\$/g, &amp;quot;&amp;quot;)&lt;br /&gt;
        // 空の括弧を削除&lt;br /&gt;
        .replace(/\(\s*\)|\[\s*\]|\{\s*\}/g, &amp;quot; &amp;quot;)&lt;br /&gt;
        // 関数形式になっていない文字列を削除&lt;br /&gt;
        .replace(/([ ,+\-*\/%^&amp;amp;~|&amp;lt;&amp;gt;?])([a-zA-Z][a-zA-Z0-9]*)(%%% |\()?/g, (match, p1, p2, p3) =&amp;gt; {&lt;br /&gt;
            return p3 ? p1 + p2 + p3 : p1 + &amp;quot; &amp;quot;;&lt;br /&gt;
        })&lt;br /&gt;
        // `/hour` などの文字列があるとスラッシュだけ残るためスラッシュを削除&lt;br /&gt;
        .replace(/\/\s*([,+\-*\/%^&amp;amp;~|&amp;lt;&amp;gt;?/)\];\n]|$)/g, &amp;quot;$1&amp;quot;)&lt;br /&gt;
        // 退避文字を復元&lt;br /&gt;
        .replace(/ %%%\$([a-zA-Z][a-zA-Z0-9]*)%%% /g, &amp;quot; $1 &amp;quot;)&lt;br /&gt;
        .replace(/ %%%([^%]+)%%% /g, &amp;quot; $1 &amp;quot;)&lt;br /&gt;
&lt;br /&gt;
    return exp_replaced;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== サードパーティライブラリ ==&lt;br /&gt;
&lt;br /&gt;
[https://github.com/josdejong/mathjs math.js]&lt;br /&gt;
&lt;br /&gt;
== 更新履歴 ==&lt;br /&gt;
&lt;br /&gt;
* 2025-10-19 公開&lt;br /&gt;
* 2025-10-25 対象テキストを数式に整形する処理を調整&lt;/div&gt;</summary>
		<author><name>Yuko</name></author>
	</entry>
	<entry>
		<id>https://www.haijin-boys.com/wiki/index.php?title=Mery_%E9%9B%BB%E5%8D%93&amp;diff=9238</id>
		<title>Mery 電卓</title>
		<link rel="alternate" type="text/html" href="https://www.haijin-boys.com/wiki/index.php?title=Mery_%E9%9B%BB%E5%8D%93&amp;diff=9238"/>
		<updated>2025-10-18T16:28:10Z</updated>

		<summary type="html">&lt;p&gt;Yuko: /* ソースコード */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== 概要 ==&lt;br /&gt;
&lt;br /&gt;
「Mery 電卓」は、Meryエディタ上で数式を計算するためのマクロです。標準的な数学関数をサポートしており、カーソル位置の行または選択したテキストを数式として解釈し、結果を表示します。計算結果は自動的にクリップボードにコピーされ、カンマ区切りの数値として表示されます。&lt;br /&gt;
&lt;br /&gt;
本マクロをショートカットキーに設定し、いつでも起動できるようにしておくと日常シーンで何かと便利です。作者は「Ctrl + Alt + Enter」キーに割り当てて利用しています。&lt;br /&gt;
&lt;br /&gt;
== 注意事項 ==&lt;br /&gt;
&lt;br /&gt;
* Mery Ver 3.7.19 で動作確認しています。&lt;br /&gt;
&lt;br /&gt;
== 使い方 ==&lt;br /&gt;
&lt;br /&gt;
0. &#039;&#039;&#039;事前準備&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
* 本マクロでは計算エンジンライブラリの「math.js」をインポートして利用しています。当該ライブラリをマクロフォルダーに配置します。&lt;br /&gt;
** ダウンロード先: https://unpkg.com/mathjs@15.0.0/lib/browser/math.js&lt;br /&gt;
** 保存ファイル名: math_v15.0.0.js&lt;br /&gt;
&lt;br /&gt;
1. &#039;&#039;&#039;行全体を計算する場合&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* 計算したい数式が書かれた行にカーソルを置きます。&lt;br /&gt;
* マクロを実行すると、次の行に「= 計算結果」が表示されます。&lt;br /&gt;
* 既に結果行がある場合は、その行が更新されます。&lt;br /&gt;
&lt;br /&gt;
2. &#039;&#039;&#039;選択範囲を計算する場合&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* 計算したい数式部分を選択します。&lt;br /&gt;
* マクロを実行すると、選択範囲の後ろに「計算式 = 計算結果」が挿入されます。&lt;br /&gt;
&lt;br /&gt;
3. &#039;&#039;&#039;サポートされている演算子&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* 四則演算（+, -, *, /）&lt;br /&gt;
* べき乗（^）&lt;br /&gt;
* 剰余（%, mod）&lt;br /&gt;
* 比較演算子（&amp;lt;, &amp;gt;, &amp;lt;=, &amp;gt;=, ==, !=）&lt;br /&gt;
* 論理演算子（and, or, not）&lt;br /&gt;
&lt;br /&gt;
4. &#039;&#039;&#039;サポートされている関数&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* 三角関数（sin, cos, tan, asin, acos, atan）&lt;br /&gt;
* 双曲線関数（sinh, cosh, tanh, asinh, acosh, atanh）&lt;br /&gt;
* 対数関数（log, log10, log2, log1p）&lt;br /&gt;
* その他の数学関数（sqrt, cbrt, abs, ceil, floor, round, trunc など）&lt;br /&gt;
** その他の利用可能な関数は math.js の公式リファレンス https://mathjs.org/docs/reference/functions.html を参照&lt;br /&gt;
&lt;br /&gt;
5. &#039;&#039;&#039;特別な機能&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* カンマ区切り表示: 計算結果は読みやすいようにカンマ区切りで表示されます。変数「COMMA」を調整することで制御できます。&lt;br /&gt;
* 自動コピー: 計算結果は自動的にクリップボードにコピーされます。変数「COPY」を調整することで制御できます。&lt;br /&gt;
* 計算結果の再利用: 行頭の「= 」は計算実行時に無視されるため、計算結果をスムーズに次の式として利用できます。&lt;br /&gt;
* 変数の利用: 複数行、または「;」で区切ることで、変数を使った数式が利用できます。変数形式は「$[a-zA-Z][a-zA-Z0-9]*」に対応しています。例えば「$a = 2; $a * 3」として実行すると、結果は「6」と出力されます。&lt;br /&gt;
* メモ書きから計算: 数字や関数以外の文字はできるだけ除外した上で計算をします。例えば「月100円 * 12ヶ月」のようなメモ書きに対して実行すると「1,200」と出力されます。また「` (バッククォート)」で囲んだ文字は確実に無視されるため、計算メモ用として使うことができます (使用例: 『1500円 `金額未定のため仮置き`』)。&lt;br /&gt;
&lt;br /&gt;
== ソースコード ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot; copy&amp;gt;&lt;br /&gt;
#title = &amp;quot;Mery 電卓&amp;quot;&lt;br /&gt;
#language = &amp;quot;v8&amp;quot;&lt;br /&gt;
#include &amp;quot;Common.js&amp;quot;&lt;br /&gt;
#include &amp;quot;math_v15.0.0.js&amp;quot;&lt;br /&gt;
Redraw = false;&lt;br /&gt;
BeginUndoGroup();&lt;br /&gt;
&lt;br /&gt;
// 以下のようなCDNから math.js 本体を入手し、マクロフォルダに配置して利用している。&lt;br /&gt;
// https://unpkg.com/mathjs@15.0.0/lib/browser/math.js&lt;br /&gt;
// 以下の math.js 関数リファレンス掲載の関数を式内で利用することができる。&lt;br /&gt;
// https://mathjs.org/docs/reference/functions.html&lt;br /&gt;
&lt;br /&gt;
// 結果をクリップボードにコピー&lt;br /&gt;
var COPY = true;&lt;br /&gt;
// 出力結果をカンマ区切りにする&lt;br /&gt;
var COMMA = true;&lt;br /&gt;
&lt;br /&gt;
var sel = document.selection;&lt;br /&gt;
&lt;br /&gt;
// math.js 初期設定&lt;br /&gt;
math.config({&lt;br /&gt;
    number: &#039;BigNumber&#039;,  // デフォルトの数値型を BigNumber に設定&lt;br /&gt;
    precision: 64         // 有効桁数 (デフォルトは64桁)&lt;br /&gt;
})&lt;br /&gt;
const lowerAndUpperExp = { lowerExp: -7, upperExp: 15 };&lt;br /&gt;
&lt;br /&gt;
doMultiEdit(main);&lt;br /&gt;
&lt;br /&gt;
function main() {&lt;br /&gt;
    var isEmpty = sel.IsEmpty;&lt;br /&gt;
    var activePointY = sel.GetActivePointY(mePosLogical);&lt;br /&gt;
    if (isEmpty) {&lt;br /&gt;
        var exp = document.GetLine(activePointY);&lt;br /&gt;
    } else {&lt;br /&gt;
        var exp = sel.Text;&lt;br /&gt;
    }&lt;br /&gt;
    // 空白行の場合は何もしない&lt;br /&gt;
    if (exp.match(/^\s*$/)) {&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    // 計算実行&lt;br /&gt;
    try {&lt;br /&gt;
        var exp_replaced = replaceExpression(exp);&lt;br /&gt;
        var result_str = math.format(math.evaluate(exp_replaced), lowerAndUpperExp);&lt;br /&gt;
    } catch (e) {&lt;br /&gt;
        if (exp_replaced === undefined) {&lt;br /&gt;
            alert(&amp;quot;エラー発生&amp;quot; + &amp;quot;\n------\n&amp;quot; + e + e.message)&lt;br /&gt;
        } else {&lt;br /&gt;
            alert(&amp;quot;エラー発生&amp;quot; + &amp;quot;\n------\n&amp;quot; + e + &amp;quot;\n入力: &amp;quot; + exp + &amp;quot;\n入力変換後: &amp;quot; + exp_replaced)&lt;br /&gt;
        }&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // 小数点以上のみカンマ区切り&lt;br /&gt;
    if (COMMA &amp;amp;&amp;amp; result_str.match(/^[0-9]+(\.[0-9]+)?$/)) {&lt;br /&gt;
        var result_parts = result_str.split(&amp;quot;.&amp;quot;);&lt;br /&gt;
        result_parts[0] = result_parts[0].replace(/(\d)(?=(\d{3})+$)/g, &amp;quot;$1,&amp;quot;);&lt;br /&gt;
        if (result_parts.length &amp;gt; 1) {&lt;br /&gt;
            result_str = result_parts.join(&amp;quot;.&amp;quot;);&lt;br /&gt;
        } else {&lt;br /&gt;
            result_str = result_parts[0];&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // 計算行が = 始まりの場合、= を削除する&lt;br /&gt;
    if (isEmpty &amp;amp;&amp;amp; exp.match(/^= /)) {&lt;br /&gt;
        sel.SetActivePoint(mePosLogical, 1, activePointY, false);&lt;br /&gt;
        sel.EndOfLine(true, mePosLogical);&lt;br /&gt;
        sel.Text = sel.Text.replace(/^= /, &amp;quot;&amp;quot;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // 既存の計算結果を上書きするか判定&lt;br /&gt;
    var pattern = /^=\s*(-?[0-9.,]|true|false|\[[0-9., \[\]]+\])*\s*$/;&lt;br /&gt;
    var nextLine = document.GetLine(activePointY + 1);&lt;br /&gt;
    if (isEmpty &amp;amp;&amp;amp; nextLine.match(pattern)) {&lt;br /&gt;
        sel.SetActivePoint(mePosLogical, 1, activePointY + 1, false);&lt;br /&gt;
        sel.EndOfLine(true, mePosLogical);&lt;br /&gt;
        sel.Text = &#039;= &#039; + result_str&lt;br /&gt;
    } else if (isEmpty) {&lt;br /&gt;
        sel.LineOpen();&lt;br /&gt;
        sel.Text = &#039;= &#039; + result_str&lt;br /&gt;
    } else {&lt;br /&gt;
        sel.CharRight();&lt;br /&gt;
        const inputExp = exp.replace(/((?!;)\n)/mg, &amp;quot;; &amp;quot;).replace(/;\s*;/g, &amp;quot;;&amp;quot;).replace(/　/g, &amp;quot; &amp;quot;).replace(/;\s*$/, &amp;quot;&amp;quot;).trim();&lt;br /&gt;
        if (exp.slice(-1) === &amp;quot;\n&amp;quot;) {&lt;br /&gt;
            sel.Text = inputExp + &#039; = &#039; + result_str + &#039;\n&#039;;&lt;br /&gt;
        } else {&lt;br /&gt;
            sel.Text = &#039;\n&#039; + inputExp + &#039; = &#039; + result_str + &#039;\n&#039;;&lt;br /&gt;
        }&lt;br /&gt;
        sel.CharLeft();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (COPY) {&lt;br /&gt;
        ClipboardData.SetData(result_str);&lt;br /&gt;
        Status = &amp;quot;計算結果 &amp;quot; + result_str + &amp;quot; をコピーしました&amp;quot;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/**&lt;br /&gt;
 * 数式を置換する&lt;br /&gt;
 */&lt;br /&gt;
function replaceExpression(exp) {&lt;br /&gt;
    const exp_replaced = exp.trim()&lt;br /&gt;
        // 先頭の = を削除&lt;br /&gt;
        .replace(/^= /, &amp;quot;&amp;quot;)&lt;br /&gt;
        // 改行を ; に置換&lt;br /&gt;
        .replace(/(?&amp;lt;!;)\n/mg, &amp;quot;;&amp;quot;)&lt;br /&gt;
        // 非ASCII文字を削除&lt;br /&gt;
        .replace(/[^\x20-\x7e]/g, &amp;quot; &amp;quot;)&lt;br /&gt;
        // 全角スペースを半角スペースに変換&lt;br /&gt;
        .replace(/　/g, &amp;quot; &amp;quot;)&lt;br /&gt;
        // 数字内のカンマを取り除く。[1,2] [1, 2] {a: 1, b: 2} mod(1, 2) のような形式では削除しない&lt;br /&gt;
        .replace(/(\d),(?=\d{3})/g, &#039;$1&#039;)&lt;br /&gt;
        // 予約語を退避&lt;br /&gt;
        .replace(/(?:^|\s)(PI|true|false)(?:$|\s)/g, &amp;quot; %%%$1%%% &amp;quot;)&lt;br /&gt;
        .replace(/([0-9]+|\$[a-zA-Z0-9]+) (mod|and|or|xor) ([0-9]+|\$[a-zA-Z0-9]+)/g, &amp;quot;$1 %%%$2%%% $3&amp;quot;)&lt;br /&gt;
        .replace(/not ([0-9]+|\$[a-zA-Z0-9]+)/g, &amp;quot; %%%$1%%% $2&amp;quot;)&lt;br /&gt;
        // 指数形式を退避&lt;br /&gt;
        .replace(/([0-9]+(?:\.[0-9]+)?[eE][+\-]?[0-9]+)/g, &amp;quot; %%%$1%%% &amp;quot;)&lt;br /&gt;
        // 変数形式を退避&lt;br /&gt;
        .replace(/(\$[a-zA-Z][a-zA-Z0-9]*)/g, &amp;quot; %%%$1%%% &amp;quot;)&lt;br /&gt;
        // `/hour` などの文字列はスラッシュごと削除&lt;br /&gt;
        .replace(/\/\s*[a-zA-Z\u0080-\uFFFF]/g, &amp;quot; &amp;quot;)&lt;br /&gt;
        // 空の括弧を削除&lt;br /&gt;
        .replace(/\(\s*\)|\[\s*\]|\{\s*\}/g, &amp;quot; &amp;quot;)&lt;br /&gt;
        // コメント扱いの記載を削除&lt;br /&gt;
        .replace(/`[^`]*`/g, &amp;quot; &amp;quot;)&lt;br /&gt;
        // 関数形式になっていない文字列を削除&lt;br /&gt;
        .replace(/( [a-zA-Z][a-zA-Z0-9]*)(%%% |\()?/g, (match, p1, p2) =&amp;gt; {&lt;br /&gt;
            return p2 ? p1 + p2 : &amp;quot; &amp;quot;;&lt;br /&gt;
        })&lt;br /&gt;
        // 退避文字を復元&lt;br /&gt;
        .replace(/ %%%\$([a-zA-Z][a-zA-Z0-9]*)%%% /g, &amp;quot; $1 &amp;quot;)&lt;br /&gt;
        .replace(/ %%%([^%]+)%%% /g, &amp;quot; $1 &amp;quot;)&lt;br /&gt;
&lt;br /&gt;
    return exp_replaced;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== サードパーティライブラリ ==&lt;br /&gt;
&lt;br /&gt;
[https://github.com/josdejong/mathjs math.js]&lt;/div&gt;</summary>
		<author><name>Yuko</name></author>
	</entry>
	<entry>
		<id>https://www.haijin-boys.com/wiki/index.php?title=Mery_%E9%9B%BB%E5%8D%93&amp;diff=9237</id>
		<title>Mery 電卓</title>
		<link rel="alternate" type="text/html" href="https://www.haijin-boys.com/wiki/index.php?title=Mery_%E9%9B%BB%E5%8D%93&amp;diff=9237"/>
		<updated>2025-10-18T16:15:17Z</updated>

		<summary type="html">&lt;p&gt;Yuko: /* ソースコード */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== 概要 ==&lt;br /&gt;
&lt;br /&gt;
「Mery 電卓」は、Meryエディタ上で数式を計算するためのマクロです。標準的な数学関数をサポートしており、カーソル位置の行または選択したテキストを数式として解釈し、結果を表示します。計算結果は自動的にクリップボードにコピーされ、カンマ区切りの数値として表示されます。&lt;br /&gt;
&lt;br /&gt;
本マクロをショートカットキーに設定し、いつでも起動できるようにしておくと日常シーンで何かと便利です。作者は「Ctrl + Alt + Enter」キーに割り当てて利用しています。&lt;br /&gt;
&lt;br /&gt;
== 注意事項 ==&lt;br /&gt;
&lt;br /&gt;
* Mery Ver 3.7.19 で動作確認しています。&lt;br /&gt;
&lt;br /&gt;
== 使い方 ==&lt;br /&gt;
&lt;br /&gt;
0. &#039;&#039;&#039;事前準備&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
* 本マクロでは計算エンジンライブラリの「math.js」をインポートして利用しています。当該ライブラリをマクロフォルダーに配置します。&lt;br /&gt;
** ダウンロード先: https://unpkg.com/mathjs@15.0.0/lib/browser/math.js&lt;br /&gt;
** 保存ファイル名: math_v15.0.0.js&lt;br /&gt;
&lt;br /&gt;
1. &#039;&#039;&#039;行全体を計算する場合&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* 計算したい数式が書かれた行にカーソルを置きます。&lt;br /&gt;
* マクロを実行すると、次の行に「= 計算結果」が表示されます。&lt;br /&gt;
* 既に結果行がある場合は、その行が更新されます。&lt;br /&gt;
&lt;br /&gt;
2. &#039;&#039;&#039;選択範囲を計算する場合&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* 計算したい数式部分を選択します。&lt;br /&gt;
* マクロを実行すると、選択範囲の後ろに「計算式 = 計算結果」が挿入されます。&lt;br /&gt;
&lt;br /&gt;
3. &#039;&#039;&#039;サポートされている演算子&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* 四則演算（+, -, *, /）&lt;br /&gt;
* べき乗（^）&lt;br /&gt;
* 剰余（%, mod）&lt;br /&gt;
* 比較演算子（&amp;lt;, &amp;gt;, &amp;lt;=, &amp;gt;=, ==, !=）&lt;br /&gt;
* 論理演算子（and, or, not）&lt;br /&gt;
&lt;br /&gt;
4. &#039;&#039;&#039;サポートされている関数&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* 三角関数（sin, cos, tan, asin, acos, atan）&lt;br /&gt;
* 双曲線関数（sinh, cosh, tanh, asinh, acosh, atanh）&lt;br /&gt;
* 対数関数（log, log10, log2, log1p）&lt;br /&gt;
* その他の数学関数（sqrt, cbrt, abs, ceil, floor, round, trunc など）&lt;br /&gt;
** その他の利用可能な関数は math.js の公式リファレンス https://mathjs.org/docs/reference/functions.html を参照&lt;br /&gt;
&lt;br /&gt;
5. &#039;&#039;&#039;特別な機能&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* カンマ区切り表示: 計算結果は読みやすいようにカンマ区切りで表示されます。変数「COMMA」を調整することで制御できます。&lt;br /&gt;
* 自動コピー: 計算結果は自動的にクリップボードにコピーされます。変数「COPY」を調整することで制御できます。&lt;br /&gt;
* 計算結果の再利用: 行頭の「= 」は計算実行時に無視されるため、計算結果をスムーズに次の式として利用できます。&lt;br /&gt;
* 変数の利用: 複数行、または「;」で区切ることで、変数を使った数式が利用できます。変数形式は「$[a-zA-Z][a-zA-Z0-9]*」に対応しています。例えば「$a = 2; $a * 3」として実行すると、結果は「6」と出力されます。&lt;br /&gt;
* メモ書きから計算: 数字や関数以外の文字はできるだけ除外した上で計算をします。例えば「月100円 * 12ヶ月」のようなメモ書きに対して実行すると「1,200」と出力されます。また「` (バッククォート)」で囲んだ文字は確実に無視されるため、計算メモ用として使うことができます (使用例: 『1500円 `金額未定のため仮置き`』)。&lt;br /&gt;
&lt;br /&gt;
== ソースコード ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot; copy&amp;gt;&lt;br /&gt;
#title = &amp;quot;Mery 電卓&amp;quot;&lt;br /&gt;
#language = &amp;quot;v8&amp;quot;&lt;br /&gt;
#include &amp;quot;Common.js&amp;quot;&lt;br /&gt;
#include &amp;quot;math_v15.0.0.js&amp;quot;&lt;br /&gt;
Redraw = false;&lt;br /&gt;
BeginUndoGroup();&lt;br /&gt;
&lt;br /&gt;
// 以下のようなCDNから math.js 本体を入手し、マクロフォルダに配置して利用している。&lt;br /&gt;
// https://unpkg.com/mathjs@15.0.0/lib/browser/math.js&lt;br /&gt;
// 以下の math.js 関数リファレンス掲載の関数を式内で利用することができる。&lt;br /&gt;
// https://mathjs.org/docs/reference/functions.html&lt;br /&gt;
&lt;br /&gt;
// 結果をクリップボードにコピー&lt;br /&gt;
var COPY = true;&lt;br /&gt;
// 出力結果をカンマ区切りにする&lt;br /&gt;
var COMMA = true;&lt;br /&gt;
&lt;br /&gt;
var sel = document.selection;&lt;br /&gt;
&lt;br /&gt;
// math.js 初期設定&lt;br /&gt;
math.config({&lt;br /&gt;
    number: &#039;BigNumber&#039;,  // デフォルトの数値型を BigNumber に設定&lt;br /&gt;
    precision: 64         // 有効桁数 (デフォルトは64桁)&lt;br /&gt;
})&lt;br /&gt;
&lt;br /&gt;
doMultiEdit(main);&lt;br /&gt;
&lt;br /&gt;
function main() {&lt;br /&gt;
    var isEmpty = sel.IsEmpty;&lt;br /&gt;
    var activePointY = sel.GetActivePointY(mePosLogical);&lt;br /&gt;
    if (isEmpty) {&lt;br /&gt;
        var exp = document.GetLine(activePointY);&lt;br /&gt;
    } else {&lt;br /&gt;
        var exp = sel.Text;&lt;br /&gt;
    }&lt;br /&gt;
    // 空白行の場合は何もしない&lt;br /&gt;
    if (exp.match(/^\s*$/)) {&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    // 計算実行&lt;br /&gt;
    try {&lt;br /&gt;
        var exp_replaced = replaceExpression(exp);&lt;br /&gt;
        var result_str = math.format(math.evaluate(exp_replaced), { lowerExp: -10, upperExp: 20 });&lt;br /&gt;
    } catch (e) {&lt;br /&gt;
        if (exp_replaced === undefined) {&lt;br /&gt;
            alert(&amp;quot;エラー発生&amp;quot; + &amp;quot;\n------\n&amp;quot; + e + e.message)&lt;br /&gt;
        } else {&lt;br /&gt;
            alert(&amp;quot;エラー発生&amp;quot; + &amp;quot;\n------\n&amp;quot; + e + &amp;quot;\n入力: &amp;quot; + exp + &amp;quot;\n入力変換後: &amp;quot; + exp_replaced)&lt;br /&gt;
        }&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // 小数点以上のみカンマ区切り&lt;br /&gt;
    if (COMMA &amp;amp;&amp;amp; result_str.match(/^[0-9]+(\.[0-9]+)?$/)) {&lt;br /&gt;
        var result_parts = result_str.split(&amp;quot;.&amp;quot;);&lt;br /&gt;
        result_parts[0] = result_parts[0].replace(/(\d)(?=(\d{3})+$)/g, &amp;quot;$1,&amp;quot;);&lt;br /&gt;
        if (result_parts.length &amp;gt; 1) {&lt;br /&gt;
            result_str = result_parts.join(&amp;quot;.&amp;quot;);&lt;br /&gt;
        } else {&lt;br /&gt;
            result_str = result_parts[0];&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // 計算行が = 始まりの場合、= を削除する&lt;br /&gt;
    if (isEmpty &amp;amp;&amp;amp; exp.match(/^= /)) {&lt;br /&gt;
        sel.SetActivePoint(mePosLogical, 1, activePointY, false);&lt;br /&gt;
        sel.EndOfLine(true, mePosLogical);&lt;br /&gt;
        sel.Text = sel.Text.replace(/^= /, &amp;quot;&amp;quot;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // 既存の計算結果を上書きするか判定&lt;br /&gt;
    var pattern = /^=\s*(-?[0-9.,]|true|false|\[[0-9., \[\]]+\])*\s*$/;&lt;br /&gt;
    var nextLine = document.GetLine(activePointY + 1);&lt;br /&gt;
    if (isEmpty &amp;amp;&amp;amp; nextLine.match(pattern)) {&lt;br /&gt;
        sel.SetActivePoint(mePosLogical, 1, activePointY + 1, false);&lt;br /&gt;
        sel.EndOfLine(true, mePosLogical);&lt;br /&gt;
        sel.Text = &#039;= &#039; + result_str&lt;br /&gt;
    } else if (isEmpty) {&lt;br /&gt;
        sel.LineOpen();&lt;br /&gt;
        sel.Text = &#039;= &#039; + result_str&lt;br /&gt;
    } else {&lt;br /&gt;
        sel.CharRight();&lt;br /&gt;
        const inputExp = exp.replace(/((?!;)\n)/mg, &amp;quot;; &amp;quot;).replace(/;\s*;/g, &amp;quot;;&amp;quot;).replace(/　/g, &amp;quot; &amp;quot;).replace(/;\s*$/, &amp;quot;&amp;quot;).trim();&lt;br /&gt;
        if (exp.slice(-1) === &amp;quot;\n&amp;quot;) {&lt;br /&gt;
            sel.Text = inputExp + &#039; = &#039; + result_str + &#039;\n&#039;;&lt;br /&gt;
        } else {&lt;br /&gt;
            sel.Text = &#039;\n&#039; + inputExp + &#039; = &#039; + result_str + &#039;\n&#039;;&lt;br /&gt;
        }&lt;br /&gt;
        sel.CharLeft();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (COPY) {&lt;br /&gt;
        ClipboardData.SetData(result_str);&lt;br /&gt;
        Status = &amp;quot;計算結果 &amp;quot; + result_str + &amp;quot; をコピーしました&amp;quot;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/**&lt;br /&gt;
 * 数式を置換する&lt;br /&gt;
 */&lt;br /&gt;
function replaceExpression(exp) {&lt;br /&gt;
    const exp_replaced = exp.trim()&lt;br /&gt;
        // 先頭の = を削除&lt;br /&gt;
        .replace(/^= /, &amp;quot;&amp;quot;)&lt;br /&gt;
        // 改行を ; に置換&lt;br /&gt;
        .replace(/(?&amp;lt;!;)\n/mg, &amp;quot;;&amp;quot;)&lt;br /&gt;
        // 非ASCII文字を削除&lt;br /&gt;
        .replace(/[^\x20-\x7e]/g, &amp;quot; &amp;quot;)&lt;br /&gt;
        // 全角スペースを半角スペースに変換&lt;br /&gt;
        .replace(/　/g, &amp;quot; &amp;quot;)&lt;br /&gt;
        // 数字内のカンマを取り除く。[1,2] [1, 2] {a: 1, b: 2} mod(1, 2) のような形式では削除しない&lt;br /&gt;
        .replace(/(\d),(?=\d{3})/g, &#039;$1&#039;)&lt;br /&gt;
        // 予約語を退避&lt;br /&gt;
        .replace(/(?:^|\s)(PI|true|false)(?:$|\s)/g, &amp;quot; %%%$1%%% &amp;quot;)&lt;br /&gt;
        .replace(/([0-9]+|\$[a-zA-Z0-9]+) (mod|and|or|xor) ([0-9]+|\$[a-zA-Z0-9]+)/g, &amp;quot;$1 %%%$2%%% $3&amp;quot;)&lt;br /&gt;
        .replace(/not ([0-9]+|\$[a-zA-Z0-9]+)/g, &amp;quot; %%%$1%%% $2&amp;quot;)&lt;br /&gt;
        // 指数形式を退避&lt;br /&gt;
        .replace(/([0-9]+(?:\.[0-9]+)?[eE][+\-]?[0-9]+)/g, &amp;quot; %%%$1%%% &amp;quot;)&lt;br /&gt;
        // 変数形式を退避&lt;br /&gt;
        .replace(/(\$[a-zA-Z][a-zA-Z0-9]*)/g, &amp;quot; %%%$1%%% &amp;quot;)&lt;br /&gt;
        // `/hour` などの文字列はスラッシュごと削除&lt;br /&gt;
        .replace(/\/\s*[a-zA-Z\u0080-\uFFFF]/g, &amp;quot; &amp;quot;)&lt;br /&gt;
        // 空の括弧を削除&lt;br /&gt;
        .replace(/\(\s*\)|\[\s*\]|\{\s*\}/g, &amp;quot; &amp;quot;)&lt;br /&gt;
        // コメント扱いの記載を削除&lt;br /&gt;
        .replace(/`[^`]*`/g, &amp;quot; &amp;quot;)&lt;br /&gt;
        // 関数形式になっていない文字列を削除&lt;br /&gt;
        .replace(/( [a-zA-Z][a-zA-Z0-9]*)(%%% |\()?/g, (match, p1, p2) =&amp;gt; {&lt;br /&gt;
            return p2 ? p1 + p2 : &amp;quot; &amp;quot;;&lt;br /&gt;
        })&lt;br /&gt;
        // 退避文字を復元&lt;br /&gt;
        .replace(/ %%%\$([a-zA-Z][a-zA-Z0-9]*)%%% /g, &amp;quot; $1 &amp;quot;)&lt;br /&gt;
        .replace(/ %%%([^%]+)%%% /g, &amp;quot; $1 &amp;quot;)&lt;br /&gt;
&lt;br /&gt;
    return exp_replaced;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== サードパーティライブラリ ==&lt;br /&gt;
&lt;br /&gt;
[https://github.com/josdejong/mathjs math.js]&lt;/div&gt;</summary>
		<author><name>Yuko</name></author>
	</entry>
	<entry>
		<id>https://www.haijin-boys.com/wiki/index.php?title=Mery_%E9%9B%BB%E5%8D%93&amp;diff=9236</id>
		<title>Mery 電卓</title>
		<link rel="alternate" type="text/html" href="https://www.haijin-boys.com/wiki/index.php?title=Mery_%E9%9B%BB%E5%8D%93&amp;diff=9236"/>
		<updated>2025-10-18T16:09:27Z</updated>

		<summary type="html">&lt;p&gt;Yuko: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== 概要 ==&lt;br /&gt;
&lt;br /&gt;
「Mery 電卓」は、Meryエディタ上で数式を計算するためのマクロです。標準的な数学関数をサポートしており、カーソル位置の行または選択したテキストを数式として解釈し、結果を表示します。計算結果は自動的にクリップボードにコピーされ、カンマ区切りの数値として表示されます。&lt;br /&gt;
&lt;br /&gt;
本マクロをショートカットキーに設定し、いつでも起動できるようにしておくと日常シーンで何かと便利です。作者は「Ctrl + Alt + Enter」キーに割り当てて利用しています。&lt;br /&gt;
&lt;br /&gt;
== 注意事項 ==&lt;br /&gt;
&lt;br /&gt;
* Mery Ver 3.7.19 で動作確認しています。&lt;br /&gt;
&lt;br /&gt;
== 使い方 ==&lt;br /&gt;
&lt;br /&gt;
0. &#039;&#039;&#039;事前準備&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
* 本マクロでは計算エンジンライブラリの「math.js」をインポートして利用しています。当該ライブラリをマクロフォルダーに配置します。&lt;br /&gt;
** ダウンロード先: https://unpkg.com/mathjs@15.0.0/lib/browser/math.js&lt;br /&gt;
** 保存ファイル名: math_v15.0.0.js&lt;br /&gt;
&lt;br /&gt;
1. &#039;&#039;&#039;行全体を計算する場合&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* 計算したい数式が書かれた行にカーソルを置きます。&lt;br /&gt;
* マクロを実行すると、次の行に「= 計算結果」が表示されます。&lt;br /&gt;
* 既に結果行がある場合は、その行が更新されます。&lt;br /&gt;
&lt;br /&gt;
2. &#039;&#039;&#039;選択範囲を計算する場合&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* 計算したい数式部分を選択します。&lt;br /&gt;
* マクロを実行すると、選択範囲の後ろに「計算式 = 計算結果」が挿入されます。&lt;br /&gt;
&lt;br /&gt;
3. &#039;&#039;&#039;サポートされている演算子&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* 四則演算（+, -, *, /）&lt;br /&gt;
* べき乗（^）&lt;br /&gt;
* 剰余（%, mod）&lt;br /&gt;
* 比較演算子（&amp;lt;, &amp;gt;, &amp;lt;=, &amp;gt;=, ==, !=）&lt;br /&gt;
* 論理演算子（and, or, not）&lt;br /&gt;
&lt;br /&gt;
4. &#039;&#039;&#039;サポートされている関数&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* 三角関数（sin, cos, tan, asin, acos, atan）&lt;br /&gt;
* 双曲線関数（sinh, cosh, tanh, asinh, acosh, atanh）&lt;br /&gt;
* 対数関数（log, log10, log2, log1p）&lt;br /&gt;
* その他の数学関数（sqrt, cbrt, abs, ceil, floor, round, trunc など）&lt;br /&gt;
** その他の利用可能な関数は math.js の公式リファレンス https://mathjs.org/docs/reference/functions.html を参照&lt;br /&gt;
&lt;br /&gt;
5. &#039;&#039;&#039;特別な機能&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* カンマ区切り表示: 計算結果は読みやすいようにカンマ区切りで表示されます。変数「COMMA」を調整することで制御できます。&lt;br /&gt;
* 自動コピー: 計算結果は自動的にクリップボードにコピーされます。変数「COPY」を調整することで制御できます。&lt;br /&gt;
* 計算結果の再利用: 行頭の「= 」は計算実行時に無視されるため、計算結果をスムーズに次の式として利用できます。&lt;br /&gt;
* 変数の利用: 複数行、または「;」で区切ることで、変数を使った数式が利用できます。変数形式は「$[a-zA-Z][a-zA-Z0-9]*」に対応しています。例えば「$a = 2; $a * 3」として実行すると、結果は「6」と出力されます。&lt;br /&gt;
* メモ書きから計算: 数字や関数以外の文字はできるだけ除外した上で計算をします。例えば「月100円 * 12ヶ月」のようなメモ書きに対して実行すると「1,200」と出力されます。また「` (バッククォート)」で囲んだ文字は確実に無視されるため、計算メモ用として使うことができます (使用例: 『1500円 `金額未定のため仮置き`』)。&lt;br /&gt;
&lt;br /&gt;
== ソースコード ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot; copy&amp;gt;&lt;br /&gt;
#title = &amp;quot;Mery 電卓&amp;quot;&lt;br /&gt;
#language = &amp;quot;v8&amp;quot;&lt;br /&gt;
#include &amp;quot;Common.js&amp;quot;&lt;br /&gt;
#include &amp;quot;math_v15.0.0.js&amp;quot;&lt;br /&gt;
Redraw = false;&lt;br /&gt;
BeginUndoGroup();&lt;br /&gt;
&lt;br /&gt;
// 以下のようなCDNから math.js 本体を入手し、マクロフォルダに配置して利用している。&lt;br /&gt;
// https://unpkg.com/mathjs@15.0.0/lib/browser/math.js&lt;br /&gt;
// 以下の math.js 関数リファレンス掲載の関数を式内で利用することができる。&lt;br /&gt;
// https://mathjs.org/docs/reference/functions.html&lt;br /&gt;
&lt;br /&gt;
// 結果をクリップボードにコピー&lt;br /&gt;
var COPY = true;&lt;br /&gt;
// 出力結果をカンマ区切りにする&lt;br /&gt;
var COMMA = true;&lt;br /&gt;
&lt;br /&gt;
var sel = document.selection;&lt;br /&gt;
&lt;br /&gt;
// math.js 初期設定&lt;br /&gt;
math.config({&lt;br /&gt;
    number: &#039;BigNumber&#039;,  // デフォルトの数値型を BigNumber に設定&lt;br /&gt;
    precision: 64         // 有効桁数 (デフォルトは64桁)&lt;br /&gt;
})&lt;br /&gt;
&lt;br /&gt;
doMultiEdit(main);&lt;br /&gt;
&lt;br /&gt;
function main() {&lt;br /&gt;
    var isEmpty = sel.IsEmpty;&lt;br /&gt;
    var activePointY = sel.GetActivePointY(mePosLogical);&lt;br /&gt;
    if (isEmpty) {&lt;br /&gt;
        var exp = document.GetLine(activePointY);&lt;br /&gt;
    } else {&lt;br /&gt;
        var exp = sel.Text;&lt;br /&gt;
    }&lt;br /&gt;
    // 空白行の場合は何もしない&lt;br /&gt;
    if (exp.match(/^\s*$/)) {&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    // 計算実行&lt;br /&gt;
    try {&lt;br /&gt;
        var exp_replaced = replaceExpression(exp);&lt;br /&gt;
        var result_str = math.format(math.evaluate(exp_replaced), { lowerExp: -10, upperExp: 20 });&lt;br /&gt;
    } catch (e) {&lt;br /&gt;
        if (exp_replaced === undefined) {&lt;br /&gt;
            alert(&amp;quot;エラー発生&amp;quot; + &amp;quot;\n------\n&amp;quot; + e + e.message)&lt;br /&gt;
        } else {&lt;br /&gt;
            alert(&amp;quot;エラー発生&amp;quot; + &amp;quot;\n------\n&amp;quot; + e + &amp;quot;\n入力: &amp;quot; + exp + &amp;quot;\n入力変換後: &amp;quot; + exp_replaced)&lt;br /&gt;
        }&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // 小数点以上のみカンマ区切り&lt;br /&gt;
    if (COMMA &amp;amp;&amp;amp; result_str.match(/^[0-9]+(\.[0-9]+)?$/)) {&lt;br /&gt;
        var result_parts = result_str.split(&amp;quot;.&amp;quot;);&lt;br /&gt;
        result_parts[0] = result_parts[0].replace(/(\d)(?=(\d{3})+$)/g, &amp;quot;$1,&amp;quot;);&lt;br /&gt;
        if (result_parts.length &amp;gt; 1) {&lt;br /&gt;
            result_str = result_parts.join(&amp;quot;.&amp;quot;);&lt;br /&gt;
        } else {&lt;br /&gt;
            result_str = result_parts[0];&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // 計算行が = 始まりの場合、= を削除する&lt;br /&gt;
    if (isEmpty &amp;amp;&amp;amp; exp.match(/^= /)) {&lt;br /&gt;
        sel.SetActivePoint(mePosLogical, 1, activePointY, false);&lt;br /&gt;
        sel.EndOfLine(true, mePosLogical);&lt;br /&gt;
        sel.Text = sel.Text.replace(/^= /, &amp;quot;&amp;quot;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // 既存の計算結果を上書きするか判定&lt;br /&gt;
    var pattern = /^=\s*(-?[0-9.,]|true|false|\[[0-9., \[\]]+\])*\s*$/;&lt;br /&gt;
    var nextLine = document.GetLine(activePointY + 1);&lt;br /&gt;
    if (isEmpty &amp;amp;&amp;amp; nextLine.match(pattern)) {&lt;br /&gt;
        sel.SetActivePoint(mePosLogical, 1, activePointY + 1, false);&lt;br /&gt;
        sel.EndOfLine(true, mePosLogical);&lt;br /&gt;
        sel.Text = &#039;= &#039; + result_str&lt;br /&gt;
    } else if (isEmpty) {&lt;br /&gt;
        sel.LineOpen();&lt;br /&gt;
        sel.Text = &#039;= &#039; + result_str&lt;br /&gt;
    } else {&lt;br /&gt;
        sel.CharRight();&lt;br /&gt;
        const inputExp = exp.replace(/((?!;)\n)/mg, &amp;quot;; &amp;quot;).replace(/;\s*;/g, &amp;quot;;&amp;quot;).replace(/　/g, &amp;quot; &amp;quot;).replace(/;\s*$/, &amp;quot;&amp;quot;).trim();&lt;br /&gt;
        if (exp.slice(-1) === &amp;quot;\n&amp;quot;) {&lt;br /&gt;
            sel.Text = inputExp + &#039; = &#039; + result_str + &#039;\n&#039;;&lt;br /&gt;
        } else {&lt;br /&gt;
            sel.Text = &#039;\n&#039; + inputExp + &#039; = &#039; + result_str + &#039;\n&#039;;&lt;br /&gt;
        }&lt;br /&gt;
        sel.CharLeft();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (COPY) {&lt;br /&gt;
        ClipboardData.SetData(result_str);&lt;br /&gt;
        Status = &amp;quot;計算結果 &amp;quot; + result_str + &amp;quot; をコピーしました&amp;quot;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/**&lt;br /&gt;
 * 数式を置換する&lt;br /&gt;
 */&lt;br /&gt;
function replaceExpression(exp) {&lt;br /&gt;
    const exp_replaced = exp.trim()&lt;br /&gt;
        // 先頭の = を削除&lt;br /&gt;
        .replace(/^= /, &amp;quot;&amp;quot;)&lt;br /&gt;
        // 改行を ; に置換&lt;br /&gt;
        .replace(/(?&amp;lt;!;)\n/mg, &amp;quot;;&amp;quot;)&lt;br /&gt;
        // 非ASCII文字を削除&lt;br /&gt;
        .replace(/[^\x20-\x7e]/g, &amp;quot; &amp;quot;)&lt;br /&gt;
        // 全角スペースを半角スペースに変換&lt;br /&gt;
        .replace(/　/g, &amp;quot; &amp;quot;)&lt;br /&gt;
        // 数字内のカンマを取り除く。[1,2] [1, 2] {a: 1, b: 2} mod(1, 2) のような形式では削除しない&lt;br /&gt;
        .replace(/(\d),(?=\d{3})/g, &#039;$1&#039;)&lt;br /&gt;
        // 予約語を退避&lt;br /&gt;
        .replace(/(?:^|\s)(PI|true|false)(?:$|\s)/g, &amp;quot; %%%$1%%% &amp;quot;)&lt;br /&gt;
        .replace(/([0-9]+|\$[a-zA-Z0-9]+) (mod|and|or|xor) ([0-9]+|\$[a-zA-Z0-9]+)/g, &amp;quot;$1 %%%$2%%% $3&amp;quot;)&lt;br /&gt;
        .replace(/not ([0-9]+|\$[a-zA-Z0-9]+)/g, &amp;quot; %%%$1%%% $2&amp;quot;)&lt;br /&gt;
        // 指数形式を退避&lt;br /&gt;
        .replace(/([0-9]+(?:\.[0-9]+)?[eE][+\-]?[0-9]+)/g, &amp;quot; %%%$1%%% &amp;quot;)&lt;br /&gt;
        // 変数形式を退避&lt;br /&gt;
        .replace(/(\$[a-zA-Z][a-zA-Z0-9]*)/g, &amp;quot; %%%$1%%% &amp;quot;)&lt;br /&gt;
        // `/hour` などの文字列はスラッシュごと削除&lt;br /&gt;
        .replace(/\/\s*[a-zA-Z\u0080-\uFFFF]/g, &amp;quot; &amp;quot;)&lt;br /&gt;
        // // コメント扱いの記載を削除&lt;br /&gt;
        .replace(/`[^`]*`/g, &amp;quot; &amp;quot;)&lt;br /&gt;
        // 関数形式になっていない文字列を削除&lt;br /&gt;
        .replace(/( [a-zA-Z][a-zA-Z0-9]*)(%%% |\()?/g, (match, p1, p2) =&amp;gt; {&lt;br /&gt;
            return p2 ? p1 + p2 : &amp;quot; &amp;quot;;&lt;br /&gt;
        })&lt;br /&gt;
        // 退避文字を復元&lt;br /&gt;
        .replace(/ %%%\$([a-zA-Z][a-zA-Z0-9]*)%%% /g, &amp;quot; $1 &amp;quot;)&lt;br /&gt;
        .replace(/ %%%([^%]+)%%% /g, &amp;quot; $1 &amp;quot;)&lt;br /&gt;
&lt;br /&gt;
    return exp_replaced;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== サードパーティライブラリ ==&lt;br /&gt;
&lt;br /&gt;
[https://github.com/josdejong/mathjs math.js]&lt;/div&gt;</summary>
		<author><name>Yuko</name></author>
	</entry>
	<entry>
		<id>https://www.haijin-boys.com/wiki/index.php?title=Mery_%E9%9B%BB%E5%8D%93&amp;diff=9235</id>
		<title>Mery 電卓</title>
		<link rel="alternate" type="text/html" href="https://www.haijin-boys.com/wiki/index.php?title=Mery_%E9%9B%BB%E5%8D%93&amp;diff=9235"/>
		<updated>2025-10-18T16:07:49Z</updated>

		<summary type="html">&lt;p&gt;Yuko: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== 概要 ==&lt;br /&gt;
&lt;br /&gt;
「Mery 電卓」は、Meryエディタ上で数式を計算するためのマクロです。標準的な数学関数をサポートしており、カーソル位置の行または選択したテキストを数式として解釈し、結果を表示します。計算結果は自動的にクリップボードにコピーされ、カンマ区切りの数値として表示されます。&lt;br /&gt;
&lt;br /&gt;
本マクロをショートカットキーに設定し、いつでも起動できるようにしておくと日常シーンで何かと便利です。作者は「Ctrl + Alt + Enter」キーに割り当てて利用しています。&lt;br /&gt;
&lt;br /&gt;
== 注意事項 ==&lt;br /&gt;
&lt;br /&gt;
* Mery Ver 3.7.19 で動作確認しています。&lt;br /&gt;
&lt;br /&gt;
== 使い方 ==&lt;br /&gt;
&lt;br /&gt;
0. &#039;&#039;&#039;事前準備&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
* 本マクロでは計算エンジンライブラリの「math.js」をインポートして利用しています。当該ライブラリをマクロフォルダーに配置します。&lt;br /&gt;
** ダウンロード先: https://unpkg.com/mathjs@15.0.0/lib/browser/math.js&lt;br /&gt;
** 保存ファイル名: math_v15.0.0.js&lt;br /&gt;
&lt;br /&gt;
1. &#039;&#039;&#039;行全体を計算する場合&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* 計算したい数式が書かれた行にカーソルを置きます。&lt;br /&gt;
* マクロを実行すると、次の行に「= 計算結果」が表示されます。&lt;br /&gt;
* 既に結果行がある場合は、その行が更新されます。&lt;br /&gt;
&lt;br /&gt;
2. &#039;&#039;&#039;選択範囲を計算する場合&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* 計算したい数式部分を選択します。&lt;br /&gt;
* マクロを実行すると、選択範囲の後ろに「計算式 = 計算結果」が挿入されます。&lt;br /&gt;
&lt;br /&gt;
3. &#039;&#039;&#039;サポートされている演算子&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* 四則演算（+, -, *, /）&lt;br /&gt;
* べき乗（^）&lt;br /&gt;
* 剰余（%, mod）&lt;br /&gt;
* 比較演算子（&amp;lt;, &amp;gt;, &amp;lt;=, &amp;gt;=, ==, !=）&lt;br /&gt;
* 論理演算子（and, or, not）&lt;br /&gt;
&lt;br /&gt;
4. &#039;&#039;&#039;サポートされている関数&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* 三角関数（sin, cos, tan, asin, acos, atan）&lt;br /&gt;
* 双曲線関数（sinh, cosh, tanh, asinh, acosh, atanh）&lt;br /&gt;
* 対数関数（log, log10, log2, log1p）&lt;br /&gt;
* その他の数学関数（sqrt, cbrt, abs, ceil, floor, round, trunc など）&lt;br /&gt;
&lt;br /&gt;
5. &#039;&#039;&#039;特別な機能&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* カンマ区切り表示: 計算結果は読みやすいようにカンマ区切りで表示されます。変数「COMMA」を調整することで制御できます。&lt;br /&gt;
* 自動コピー: 計算結果は自動的にクリップボードにコピーされます。変数「COPY」を調整することで制御できます。&lt;br /&gt;
* 計算結果の再利用: 行頭の「= 」は計算実行時に無視されるため、計算結果をスムーズに次の式として利用できます。&lt;br /&gt;
* 変数の利用: 複数行、または「;」で区切ることで、変数を使った数式が利用できます。変数形式は「$[a-zA-Z][a-zA-Z0-9]*」に対応しています。例えば「$a = 2; $a * 3」として実行すると、結果は「6」と出力されます。&lt;br /&gt;
* メモ書きから計算: 数字や関数以外の文字はできるだけ除外した上で計算をします。例えば「月100円 * 12ヶ月」のようなメモ書きに対して実行すると「1,200」と出力されます。また「` (バッククォート)」で囲んだ文字は確実に無視されるため、計算メモ用として使うことができます (使用例: 『1500円 `金額未定のため仮置き`』)。&lt;br /&gt;
&lt;br /&gt;
== ソースコード ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot; copy&amp;gt;&lt;br /&gt;
#title = &amp;quot;Mery 電卓&amp;quot;&lt;br /&gt;
#language = &amp;quot;v8&amp;quot;&lt;br /&gt;
#include &amp;quot;Common.js&amp;quot;&lt;br /&gt;
#include &amp;quot;math_v15.0.0.js&amp;quot;&lt;br /&gt;
Redraw = false;&lt;br /&gt;
BeginUndoGroup();&lt;br /&gt;
&lt;br /&gt;
// 以下のようなCDNから math.js 本体を入手し、マクロフォルダに配置して利用している。&lt;br /&gt;
// https://unpkg.com/mathjs@15.0.0/lib/browser/math.js&lt;br /&gt;
// 以下の math.js 関数リファレンス掲載の関数を式内で利用することができる。&lt;br /&gt;
// https://mathjs.org/docs/reference/functions.html&lt;br /&gt;
&lt;br /&gt;
// 結果をクリップボードにコピー&lt;br /&gt;
var COPY = true;&lt;br /&gt;
// 出力結果をカンマ区切りにする&lt;br /&gt;
var COMMA = true;&lt;br /&gt;
&lt;br /&gt;
var sel = document.selection;&lt;br /&gt;
&lt;br /&gt;
// math.js 初期設定&lt;br /&gt;
math.config({&lt;br /&gt;
    number: &#039;BigNumber&#039;,  // デフォルトの数値型を BigNumber に設定&lt;br /&gt;
    precision: 64         // 有効桁数 (デフォルトは64桁)&lt;br /&gt;
})&lt;br /&gt;
&lt;br /&gt;
doMultiEdit(main);&lt;br /&gt;
&lt;br /&gt;
function main() {&lt;br /&gt;
    var isEmpty = sel.IsEmpty;&lt;br /&gt;
    var activePointY = sel.GetActivePointY(mePosLogical);&lt;br /&gt;
    if (isEmpty) {&lt;br /&gt;
        var exp = document.GetLine(activePointY);&lt;br /&gt;
    } else {&lt;br /&gt;
        var exp = sel.Text;&lt;br /&gt;
    }&lt;br /&gt;
    // 空白行の場合は何もしない&lt;br /&gt;
    if (exp.match(/^\s*$/)) {&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    // 計算実行&lt;br /&gt;
    try {&lt;br /&gt;
        var exp_replaced = replaceExpression(exp);&lt;br /&gt;
        var result_str = math.format(math.evaluate(exp_replaced), { lowerExp: -10, upperExp: 20 });&lt;br /&gt;
    } catch (e) {&lt;br /&gt;
        if (exp_replaced === undefined) {&lt;br /&gt;
            alert(&amp;quot;エラー発生&amp;quot; + &amp;quot;\n------\n&amp;quot; + e + e.message)&lt;br /&gt;
        } else {&lt;br /&gt;
            alert(&amp;quot;エラー発生&amp;quot; + &amp;quot;\n------\n&amp;quot; + e + &amp;quot;\n入力: &amp;quot; + exp + &amp;quot;\n入力変換後: &amp;quot; + exp_replaced)&lt;br /&gt;
        }&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // 小数点以上のみカンマ区切り&lt;br /&gt;
    if (COMMA &amp;amp;&amp;amp; result_str.match(/^[0-9]+(\.[0-9]+)?$/)) {&lt;br /&gt;
        var result_parts = result_str.split(&amp;quot;.&amp;quot;);&lt;br /&gt;
        result_parts[0] = result_parts[0].replace(/(\d)(?=(\d{3})+$)/g, &amp;quot;$1,&amp;quot;);&lt;br /&gt;
        if (result_parts.length &amp;gt; 1) {&lt;br /&gt;
            result_str = result_parts.join(&amp;quot;.&amp;quot;);&lt;br /&gt;
        } else {&lt;br /&gt;
            result_str = result_parts[0];&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // 計算行が = 始まりの場合、= を削除する&lt;br /&gt;
    if (isEmpty &amp;amp;&amp;amp; exp.match(/^= /)) {&lt;br /&gt;
        sel.SetActivePoint(mePosLogical, 1, activePointY, false);&lt;br /&gt;
        sel.EndOfLine(true, mePosLogical);&lt;br /&gt;
        sel.Text = sel.Text.replace(/^= /, &amp;quot;&amp;quot;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // 既存の計算結果を上書きするか判定&lt;br /&gt;
    var pattern = /^=\s*(-?[0-9.,]|true|false|\[[0-9., \[\]]+\])*\s*$/;&lt;br /&gt;
    var nextLine = document.GetLine(activePointY + 1);&lt;br /&gt;
    if (isEmpty &amp;amp;&amp;amp; nextLine.match(pattern)) {&lt;br /&gt;
        sel.SetActivePoint(mePosLogical, 1, activePointY + 1, false);&lt;br /&gt;
        sel.EndOfLine(true, mePosLogical);&lt;br /&gt;
        sel.Text = &#039;= &#039; + result_str&lt;br /&gt;
    } else if (isEmpty) {&lt;br /&gt;
        sel.LineOpen();&lt;br /&gt;
        sel.Text = &#039;= &#039; + result_str&lt;br /&gt;
    } else {&lt;br /&gt;
        sel.CharRight();&lt;br /&gt;
        const inputExp = exp.replace(/((?!;)\n)/mg, &amp;quot;; &amp;quot;).replace(/;\s*;/g, &amp;quot;;&amp;quot;).replace(/　/g, &amp;quot; &amp;quot;).replace(/;\s*$/, &amp;quot;&amp;quot;).trim();&lt;br /&gt;
        if (exp.slice(-1) === &amp;quot;\n&amp;quot;) {&lt;br /&gt;
            sel.Text = inputExp + &#039; = &#039; + result_str + &#039;\n&#039;;&lt;br /&gt;
        } else {&lt;br /&gt;
            sel.Text = &#039;\n&#039; + inputExp + &#039; = &#039; + result_str + &#039;\n&#039;;&lt;br /&gt;
        }&lt;br /&gt;
        sel.CharLeft();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (COPY) {&lt;br /&gt;
        ClipboardData.SetData(result_str);&lt;br /&gt;
        Status = &amp;quot;計算結果 &amp;quot; + result_str + &amp;quot; をコピーしました&amp;quot;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/**&lt;br /&gt;
 * 数式を置換する&lt;br /&gt;
 */&lt;br /&gt;
function replaceExpression(exp) {&lt;br /&gt;
    const exp_replaced = exp.trim()&lt;br /&gt;
        // 先頭の = を削除&lt;br /&gt;
        .replace(/^= /, &amp;quot;&amp;quot;)&lt;br /&gt;
        // 改行を ; に置換&lt;br /&gt;
        .replace(/(?&amp;lt;!;)\n/mg, &amp;quot;;&amp;quot;)&lt;br /&gt;
        // 非ASCII文字を削除&lt;br /&gt;
        .replace(/[^\x20-\x7e]/g, &amp;quot; &amp;quot;)&lt;br /&gt;
        // 全角スペースを半角スペースに変換&lt;br /&gt;
        .replace(/　/g, &amp;quot; &amp;quot;)&lt;br /&gt;
        // 数字内のカンマを取り除く。[1,2] [1, 2] {a: 1, b: 2} mod(1, 2) のような形式では削除しない&lt;br /&gt;
        .replace(/(\d),(?=\d{3})/g, &#039;$1&#039;)&lt;br /&gt;
        // 予約語を退避&lt;br /&gt;
        .replace(/(?:^|\s)(PI|true|false)(?:$|\s)/g, &amp;quot; %%%$1%%% &amp;quot;)&lt;br /&gt;
        .replace(/([0-9]+|\$[a-zA-Z0-9]+) (mod|and|or|xor) ([0-9]+|\$[a-zA-Z0-9]+)/g, &amp;quot;$1 %%%$2%%% $3&amp;quot;)&lt;br /&gt;
        .replace(/not ([0-9]+|\$[a-zA-Z0-9]+)/g, &amp;quot; %%%$1%%% $2&amp;quot;)&lt;br /&gt;
        // 指数形式を退避&lt;br /&gt;
        .replace(/([0-9]+(?:\.[0-9]+)?[eE][+\-]?[0-9]+)/g, &amp;quot; %%%$1%%% &amp;quot;)&lt;br /&gt;
        // 変数形式を退避&lt;br /&gt;
        .replace(/(\$[a-zA-Z][a-zA-Z0-9]*)/g, &amp;quot; %%%$1%%% &amp;quot;)&lt;br /&gt;
        // `/hour` などの文字列はスラッシュごと削除&lt;br /&gt;
        .replace(/\/\s*[a-zA-Z\u0080-\uFFFF]/g, &amp;quot; &amp;quot;)&lt;br /&gt;
        // // コメント扱いの記載を削除&lt;br /&gt;
        .replace(/`[^`]*`/g, &amp;quot; &amp;quot;)&lt;br /&gt;
        // 関数形式になっていない文字列を削除&lt;br /&gt;
        .replace(/( [a-zA-Z][a-zA-Z0-9]*)(%%% |\()?/g, (match, p1, p2) =&amp;gt; {&lt;br /&gt;
            return p2 ? p1 + p2 : &amp;quot; &amp;quot;;&lt;br /&gt;
        })&lt;br /&gt;
        // 退避文字を復元&lt;br /&gt;
        .replace(/ %%%\$([a-zA-Z][a-zA-Z0-9]*)%%% /g, &amp;quot; $1 &amp;quot;)&lt;br /&gt;
        .replace(/ %%%([^%]+)%%% /g, &amp;quot; $1 &amp;quot;)&lt;br /&gt;
&lt;br /&gt;
    return exp_replaced;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== サードパーティライブラリ ==&lt;br /&gt;
&lt;br /&gt;
[https://github.com/josdejong/mathjs math.js]&lt;/div&gt;</summary>
		<author><name>Yuko</name></author>
	</entry>
	<entry>
		<id>https://www.haijin-boys.com/wiki/index.php?title=Mery_%E9%9B%BB%E5%8D%93&amp;diff=9234</id>
		<title>Mery 電卓</title>
		<link rel="alternate" type="text/html" href="https://www.haijin-boys.com/wiki/index.php?title=Mery_%E9%9B%BB%E5%8D%93&amp;diff=9234"/>
		<updated>2025-10-18T16:01:26Z</updated>

		<summary type="html">&lt;p&gt;Yuko: /* ソースコード */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== 概要 ==&lt;br /&gt;
&lt;br /&gt;
「Mery 電卓」は、Meryエディタ上で数式を計算するためのマクロです。標準的な数学関数をサポートしており、カーソル位置の行または選択したテキストを数式として解釈し、結果を表示します。計算結果は自動的にクリップボードにコピーされ、カンマ区切りの数値として表示されます。&lt;br /&gt;
&lt;br /&gt;
本マクロをショートカットキーに設定し、いつでも起動できるようにしておくと日常シーンで何かと便利です。作者は「Ctrl + Alt + Enter」キーに割り当てて利用しています。&lt;br /&gt;
&lt;br /&gt;
== 注意事項 ==&lt;br /&gt;
&lt;br /&gt;
* Mery Ver 3.7.19 で動作確認しています。&lt;br /&gt;
&lt;br /&gt;
== 使い方 ==&lt;br /&gt;
&lt;br /&gt;
0. &#039;&#039;&#039;事前準備&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
* 本マクロでは計算エンジンライブラリの「math.js」をインポートして利用しています。当該ライブラリをマクロフォルダーに配置します。&lt;br /&gt;
** ダウンロード先: https://unpkg.com/mathjs@15.0.0/lib/browser/math.js&lt;br /&gt;
** 保存ファイル名: math_v15.0.0.js&lt;br /&gt;
&lt;br /&gt;
1. &#039;&#039;&#039;行全体を計算する場合&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* 計算したい数式が書かれた行にカーソルを置きます。&lt;br /&gt;
* マクロを実行すると、次の行に「= 計算結果」が表示されます。&lt;br /&gt;
* 既に結果行がある場合は、その行が更新されます。&lt;br /&gt;
&lt;br /&gt;
2. &#039;&#039;&#039;選択範囲を計算する場合&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* 計算したい数式部分を選択します。&lt;br /&gt;
* マクロを実行すると、選択範囲の後ろに「計算式 = 計算結果」が挿入されます。&lt;br /&gt;
&lt;br /&gt;
3. &#039;&#039;&#039;サポートされている演算子&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* 四則演算（+, -, *, /）&lt;br /&gt;
* べき乗（^）&lt;br /&gt;
* 剰余（%）&lt;br /&gt;
* 比較演算子（&amp;lt;, &amp;gt;, &amp;lt;=, &amp;gt;=, ==, !=）&lt;br /&gt;
* 論理演算子（and, or, not）&lt;br /&gt;
&lt;br /&gt;
4. &#039;&#039;&#039;サポートされている関数&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* 三角関数（sin, cos, tan, asin, acos, atan）&lt;br /&gt;
* 双曲線関数（sinh, cosh, tanh, asinh, acosh, atanh）&lt;br /&gt;
* 対数関数（log, log10, log2, log1p）&lt;br /&gt;
* その他の数学関数（sqrt, cbrt, abs, ceil, floor, round, trunc など）&lt;br /&gt;
&lt;br /&gt;
5. &#039;&#039;&#039;特別な機能&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* カンマ区切り表示: 計算結果は読みやすいようにカンマ区切りで表示されます。変数「COMMA」を調整することで制御できます。&lt;br /&gt;
* 自動コピー: 計算結果は自動的にクリップボードにコピーされます。変数「COPY」を調整することで制御できます。&lt;br /&gt;
* 計算結果の再利用: 行頭の「= 」は計算実行時に無視されるため、計算結果をスムーズに次の式として利用できます。&lt;br /&gt;
* 変数の利用: 複数行、または「;」で区切ることで、変数を使った数式が利用できます。変数形式は「$[a-zA-Z][a-zA-Z0-9]*」に対応しています。例えば「$a = 2; $a * 3」として実行すると、結果は「6」と出力されます。&lt;br /&gt;
* メモ書きから計算: 数字や関数以外の文字はできるだけ除外した上で計算をします。例えば「月100円 * 12ヶ月」のようなメモ書きに対して実行すると「1,200」と出力されます。&lt;br /&gt;
&lt;br /&gt;
== ソースコード ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot; copy&amp;gt;&lt;br /&gt;
#title = &amp;quot;Mery 電卓&amp;quot;&lt;br /&gt;
#language = &amp;quot;v8&amp;quot;&lt;br /&gt;
#include &amp;quot;Common.js&amp;quot;&lt;br /&gt;
#include &amp;quot;math_v15.0.0.js&amp;quot;&lt;br /&gt;
Redraw = false;&lt;br /&gt;
BeginUndoGroup();&lt;br /&gt;
&lt;br /&gt;
// 以下のようなCDNから math.js 本体を入手し、マクロフォルダに配置して利用している。&lt;br /&gt;
// https://unpkg.com/mathjs@15.0.0/lib/browser/math.js&lt;br /&gt;
// 以下の math.js 関数リファレンス掲載の関数を式内で利用することができる。&lt;br /&gt;
// https://mathjs.org/docs/reference/functions.html&lt;br /&gt;
&lt;br /&gt;
// 結果をクリップボードにコピー&lt;br /&gt;
var COPY = true;&lt;br /&gt;
// 出力結果をカンマ区切りにする&lt;br /&gt;
var COMMA = true;&lt;br /&gt;
&lt;br /&gt;
var sel = document.selection;&lt;br /&gt;
&lt;br /&gt;
// math.js 初期設定&lt;br /&gt;
math.config({&lt;br /&gt;
    number: &#039;BigNumber&#039;,  // デフォルトの数値型を BigNumber に設定&lt;br /&gt;
    precision: 64         // 有効桁数 (デフォルトは64桁)&lt;br /&gt;
})&lt;br /&gt;
&lt;br /&gt;
doMultiEdit(main);&lt;br /&gt;
&lt;br /&gt;
function main() {&lt;br /&gt;
    var isEmpty = sel.IsEmpty;&lt;br /&gt;
    var activePointY = sel.GetActivePointY(mePosLogical);&lt;br /&gt;
    if (isEmpty) {&lt;br /&gt;
        var exp = document.GetLine(activePointY);&lt;br /&gt;
    } else {&lt;br /&gt;
        var exp = sel.Text;&lt;br /&gt;
    }&lt;br /&gt;
    // 空白行の場合は何もしない&lt;br /&gt;
    if (exp.match(/^\s*$/)) {&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    // 計算実行&lt;br /&gt;
    try {&lt;br /&gt;
        var exp_replaced = replaceExpression(exp);&lt;br /&gt;
        var result_str = math.format(math.evaluate(exp_replaced), { lowerExp: -10, upperExp: 20 });&lt;br /&gt;
    } catch (e) {&lt;br /&gt;
        if (exp_replaced === undefined) {&lt;br /&gt;
            alert(&amp;quot;エラー発生&amp;quot; + &amp;quot;\n------\n&amp;quot; + e + e.message)&lt;br /&gt;
        } else {&lt;br /&gt;
            alert(&amp;quot;エラー発生&amp;quot; + &amp;quot;\n------\n&amp;quot; + e + &amp;quot;\n入力: &amp;quot; + exp + &amp;quot;\n入力変換後: &amp;quot; + exp_replaced)&lt;br /&gt;
        }&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // 小数点以上のみカンマ区切り&lt;br /&gt;
    if (COMMA &amp;amp;&amp;amp; result_str.match(/^[0-9]+(\.[0-9]+)?$/)) {&lt;br /&gt;
        var result_parts = result_str.split(&amp;quot;.&amp;quot;);&lt;br /&gt;
        result_parts[0] = result_parts[0].replace(/(\d)(?=(\d{3})+$)/g, &amp;quot;$1,&amp;quot;);&lt;br /&gt;
        if (result_parts.length &amp;gt; 1) {&lt;br /&gt;
            result_str = result_parts.join(&amp;quot;.&amp;quot;);&lt;br /&gt;
        } else {&lt;br /&gt;
            result_str = result_parts[0];&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // 計算行が = 始まりの場合、= を削除する&lt;br /&gt;
    if (isEmpty &amp;amp;&amp;amp; exp.match(/^= /)) {&lt;br /&gt;
        sel.SetActivePoint(mePosLogical, 1, activePointY, false);&lt;br /&gt;
        sel.EndOfLine(true, mePosLogical);&lt;br /&gt;
        sel.Text = sel.Text.replace(/^= /, &amp;quot;&amp;quot;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // 既存の計算結果を上書きするか判定&lt;br /&gt;
    var pattern = /^=\s*(-?[0-9.,]|true|false|\[[0-9., \[\]]+\])*\s*$/;&lt;br /&gt;
    var nextLine = document.GetLine(activePointY + 1);&lt;br /&gt;
    if (isEmpty &amp;amp;&amp;amp; nextLine.match(pattern)) {&lt;br /&gt;
        sel.SetActivePoint(mePosLogical, 1, activePointY + 1, false);&lt;br /&gt;
        sel.EndOfLine(true, mePosLogical);&lt;br /&gt;
        sel.Text = &#039;= &#039; + result_str&lt;br /&gt;
    } else if (isEmpty) {&lt;br /&gt;
        sel.LineOpen();&lt;br /&gt;
        sel.Text = &#039;= &#039; + result_str&lt;br /&gt;
    } else {&lt;br /&gt;
        sel.CharRight();&lt;br /&gt;
        const inputExp = exp.replace(/((?!;)\n)/mg, &amp;quot;; &amp;quot;).replace(/;\s*;/g, &amp;quot;;&amp;quot;).replace(/　/g, &amp;quot; &amp;quot;).replace(/;\s*$/, &amp;quot;&amp;quot;).trim();&lt;br /&gt;
        if (exp.slice(-1) === &amp;quot;\n&amp;quot;) {&lt;br /&gt;
            sel.Text = inputExp + &#039; = &#039; + result_str + &#039;\n&#039;;&lt;br /&gt;
        } else {&lt;br /&gt;
            sel.Text = &#039;\n&#039; + inputExp + &#039; = &#039; + result_str + &#039;\n&#039;;&lt;br /&gt;
        }&lt;br /&gt;
        sel.CharLeft();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (COPY) {&lt;br /&gt;
        ClipboardData.SetData(result_str);&lt;br /&gt;
        Status = &amp;quot;計算結果 &amp;quot; + result_str + &amp;quot; をコピーしました&amp;quot;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/**&lt;br /&gt;
 * 数式を置換する&lt;br /&gt;
 */&lt;br /&gt;
function replaceExpression(exp) {&lt;br /&gt;
    const exp_replaced = exp.trim()&lt;br /&gt;
        // 先頭の = を削除&lt;br /&gt;
        .replace(/^= /, &amp;quot;&amp;quot;)&lt;br /&gt;
        // 改行を ; に置換&lt;br /&gt;
        .replace(/(?&amp;lt;!;)\n/mg, &amp;quot;;&amp;quot;)&lt;br /&gt;
        // 非ASCII文字を削除&lt;br /&gt;
        .replace(/[^\x20-\x7e]/g, &amp;quot; &amp;quot;)&lt;br /&gt;
        // 全角スペースを半角スペースに変換&lt;br /&gt;
        .replace(/　/g, &amp;quot; &amp;quot;)&lt;br /&gt;
        // 数字内のカンマを取り除く。[1,2] [1, 2] {a: 1, b: 2} mod(1, 2) のような形式では削除しない&lt;br /&gt;
        .replace(/(\d),(?=\d{3})/g, &#039;$1&#039;)&lt;br /&gt;
        // 予約語を退避&lt;br /&gt;
        .replace(/(?:^|\s)(PI|true|false)(?:$|\s)/g, &amp;quot; %%%$1%%% &amp;quot;)&lt;br /&gt;
        .replace(/([0-9]+|\$[a-zA-Z0-9]+) (mod|and|or|xor) ([0-9]+|\$[a-zA-Z0-9]+)/g, &amp;quot;$1 %%%$2%%% $3&amp;quot;)&lt;br /&gt;
        .replace(/not ([0-9]+|\$[a-zA-Z0-9]+)/g, &amp;quot; %%%$1%%% $2&amp;quot;)&lt;br /&gt;
        // 指数形式を退避&lt;br /&gt;
        .replace(/([0-9]+(?:\.[0-9]+)?[eE][+\-]?[0-9]+)/g, &amp;quot; %%%$1%%% &amp;quot;)&lt;br /&gt;
        // 変数形式を退避&lt;br /&gt;
        .replace(/(\$[a-zA-Z][a-zA-Z0-9]*)/g, &amp;quot; %%%$1%%% &amp;quot;)&lt;br /&gt;
        // `/hour` などの文字列はスラッシュごと削除&lt;br /&gt;
        .replace(/\/\s*[a-zA-Z\u0080-\uFFFF]/g, &amp;quot; &amp;quot;)&lt;br /&gt;
        // // コメント扱いの記載を削除&lt;br /&gt;
        .replace(/`[^`]*`/g, &amp;quot; &amp;quot;)&lt;br /&gt;
        // 関数形式になっていない文字列を削除&lt;br /&gt;
        .replace(/( [a-zA-Z][a-zA-Z0-9]*)(%%% |\()?/g, (match, p1, p2) =&amp;gt; {&lt;br /&gt;
            return p2 ? p1 + p2 : &amp;quot; &amp;quot;;&lt;br /&gt;
        })&lt;br /&gt;
        // 退避文字を復元&lt;br /&gt;
        .replace(/ %%%\$([a-zA-Z][a-zA-Z0-9]*)%%% /g, &amp;quot; $1 &amp;quot;)&lt;br /&gt;
        .replace(/ %%%([^%]+)%%% /g, &amp;quot; $1 &amp;quot;)&lt;br /&gt;
&lt;br /&gt;
    return exp_replaced;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== サードパーティライブラリ ==&lt;br /&gt;
&lt;br /&gt;
[https://github.com/josdejong/mathjs math.js]&lt;/div&gt;</summary>
		<author><name>Yuko</name></author>
	</entry>
	<entry>
		<id>https://www.haijin-boys.com/wiki/index.php?title=Mery_%E9%9B%BB%E5%8D%93&amp;diff=9233</id>
		<title>Mery 電卓</title>
		<link rel="alternate" type="text/html" href="https://www.haijin-boys.com/wiki/index.php?title=Mery_%E9%9B%BB%E5%8D%93&amp;diff=9233"/>
		<updated>2025-10-18T15:34:57Z</updated>

		<summary type="html">&lt;p&gt;Yuko: /* ソースコード */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== 概要 ==&lt;br /&gt;
&lt;br /&gt;
「Mery 電卓」は、Meryエディタ上で数式を計算するためのマクロです。標準的な数学関数をサポートしており、カーソル位置の行または選択したテキストを数式として解釈し、結果を表示します。計算結果は自動的にクリップボードにコピーされ、カンマ区切りの数値として表示されます。&lt;br /&gt;
&lt;br /&gt;
本マクロをショートカットキーに設定し、いつでも起動できるようにしておくと日常シーンで何かと便利です。作者は「Ctrl + Alt + Enter」キーに割り当てて利用しています。&lt;br /&gt;
&lt;br /&gt;
== 注意事項 ==&lt;br /&gt;
&lt;br /&gt;
* Mery Ver 3.7.19 で動作確認しています。&lt;br /&gt;
&lt;br /&gt;
== 使い方 ==&lt;br /&gt;
&lt;br /&gt;
0. &#039;&#039;&#039;事前準備&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
* 本マクロでは計算エンジンライブラリの「math.js」をインポートして利用しています。当該ライブラリをマクロフォルダーに配置します。&lt;br /&gt;
** ダウンロード先: https://unpkg.com/mathjs@15.0.0/lib/browser/math.js&lt;br /&gt;
** 保存ファイル名: math_v15.0.0.js&lt;br /&gt;
&lt;br /&gt;
1. &#039;&#039;&#039;行全体を計算する場合&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* 計算したい数式が書かれた行にカーソルを置きます。&lt;br /&gt;
* マクロを実行すると、次の行に「= 計算結果」が表示されます。&lt;br /&gt;
* 既に結果行がある場合は、その行が更新されます。&lt;br /&gt;
&lt;br /&gt;
2. &#039;&#039;&#039;選択範囲を計算する場合&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* 計算したい数式部分を選択します。&lt;br /&gt;
* マクロを実行すると、選択範囲の後ろに「計算式 = 計算結果」が挿入されます。&lt;br /&gt;
&lt;br /&gt;
3. &#039;&#039;&#039;サポートされている演算子&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* 四則演算（+, -, *, /）&lt;br /&gt;
* べき乗（^）&lt;br /&gt;
* 剰余（%）&lt;br /&gt;
* 比較演算子（&amp;lt;, &amp;gt;, &amp;lt;=, &amp;gt;=, ==, !=）&lt;br /&gt;
* 論理演算子（and, or, not）&lt;br /&gt;
&lt;br /&gt;
4. &#039;&#039;&#039;サポートされている関数&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* 三角関数（sin, cos, tan, asin, acos, atan）&lt;br /&gt;
* 双曲線関数（sinh, cosh, tanh, asinh, acosh, atanh）&lt;br /&gt;
* 対数関数（log, log10, log2, log1p）&lt;br /&gt;
* その他の数学関数（sqrt, cbrt, abs, ceil, floor, round, trunc など）&lt;br /&gt;
&lt;br /&gt;
5. &#039;&#039;&#039;特別な機能&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* カンマ区切り表示: 計算結果は読みやすいようにカンマ区切りで表示されます。変数「COMMA」を調整することで制御できます。&lt;br /&gt;
* 自動コピー: 計算結果は自動的にクリップボードにコピーされます。変数「COPY」を調整することで制御できます。&lt;br /&gt;
* 計算結果の再利用: 行頭の「= 」は計算実行時に無視されるため、計算結果をスムーズに次の式として利用できます。&lt;br /&gt;
* 変数の利用: 複数行、または「;」で区切ることで、変数を使った数式が利用できます。変数形式は「$[a-zA-Z][a-zA-Z0-9]*」に対応しています。例えば「$a = 2; $a * 3」として実行すると、結果は「6」と出力されます。&lt;br /&gt;
* メモ書きから計算: 数字や関数以外の文字はできるだけ除外した上で計算をします。例えば「月100円 * 12ヶ月」のようなメモ書きに対して実行すると「1,200」と出力されます。&lt;br /&gt;
&lt;br /&gt;
== ソースコード ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot; copy&amp;gt;&lt;br /&gt;
#title = &amp;quot;Mery 電卓&amp;quot;&lt;br /&gt;
#language = &amp;quot;v8&amp;quot;&lt;br /&gt;
#include &amp;quot;Common.js&amp;quot;&lt;br /&gt;
#include &amp;quot;math_v15.0.0.js&amp;quot;&lt;br /&gt;
Redraw = false;&lt;br /&gt;
BeginUndoGroup();&lt;br /&gt;
&lt;br /&gt;
// 以下のようなCDNから math.js 本体を入手し、マクロフォルダに配置して利用している。&lt;br /&gt;
// https://unpkg.com/mathjs@15.0.0/lib/browser/math.js&lt;br /&gt;
// 以下の math.js 関数リファレンス掲載の関数を式内で利用することができる。&lt;br /&gt;
// https://mathjs.org/docs/reference/functions.html&lt;br /&gt;
&lt;br /&gt;
// 結果をクリップボードにコピー&lt;br /&gt;
var COPY = true;&lt;br /&gt;
// 出力結果をカンマ区切りにする&lt;br /&gt;
var COMMA = true;&lt;br /&gt;
&lt;br /&gt;
var sel = document.selection;&lt;br /&gt;
&lt;br /&gt;
// math.js 初期設定&lt;br /&gt;
math.config({&lt;br /&gt;
    number: &#039;BigNumber&#039;,  // デフォルトの数値型を BigNumber に設定&lt;br /&gt;
    precision: 64         // 有効桁数 (デフォルトは64桁)&lt;br /&gt;
})&lt;br /&gt;
&lt;br /&gt;
doMultiEdit(main);&lt;br /&gt;
&lt;br /&gt;
function main() {&lt;br /&gt;
    var isEmpty = sel.IsEmpty;&lt;br /&gt;
    var activePointY = sel.GetActivePointY(mePosLogical);&lt;br /&gt;
    if (isEmpty) {&lt;br /&gt;
        var exp = document.GetLine(activePointY);&lt;br /&gt;
    } else {&lt;br /&gt;
        var exp = sel.Text;&lt;br /&gt;
    }&lt;br /&gt;
    // 空白行の場合は何もしない&lt;br /&gt;
    if (exp.match(/^\s*$/)) {&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    // 計算実行&lt;br /&gt;
    try {&lt;br /&gt;
        var exp_replaced = replaceExpression(exp);&lt;br /&gt;
        var result_str = math.format(math.evaluate(exp_replaced), { lowerExp: -10, upperExp: 20 });&lt;br /&gt;
    } catch (e) {&lt;br /&gt;
        if (exp_replaced === undefined) {&lt;br /&gt;
            alert(&amp;quot;エラー発生&amp;quot; + &amp;quot;\n------\n&amp;quot; + e + e.message)&lt;br /&gt;
        } else {&lt;br /&gt;
            alert(&amp;quot;エラー発生&amp;quot; + &amp;quot;\n------\n&amp;quot; + e + &amp;quot;\n入力: &amp;quot; + exp + &amp;quot;\n入力変換後: &amp;quot; + exp_replaced)&lt;br /&gt;
        }&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // 小数点以上のみカンマ区切り&lt;br /&gt;
    if (COMMA &amp;amp;&amp;amp; result_str.match(/^[0-9]+(\.[0-9]+)?$/)) {&lt;br /&gt;
        var result_parts = result_str.split(&amp;quot;.&amp;quot;);&lt;br /&gt;
        result_parts[0] = result_parts[0].replace(/(\d)(?=(\d{3})+$)/g, &amp;quot;$1,&amp;quot;);&lt;br /&gt;
        if (result_parts.length &amp;gt; 1) {&lt;br /&gt;
            result_str = result_parts.join(&amp;quot;.&amp;quot;);&lt;br /&gt;
        } else {&lt;br /&gt;
            result_str = result_parts[0];&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // 計算行が = 始まりの場合、= を削除する&lt;br /&gt;
    if (isEmpty &amp;amp;&amp;amp; exp.match(/^= /)) {&lt;br /&gt;
        sel.SetActivePoint(mePosLogical, 1, activePointY, false);&lt;br /&gt;
        sel.EndOfLine(true, mePosLogical);&lt;br /&gt;
        sel.Text = sel.Text.replace(/^= /, &amp;quot;&amp;quot;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // 既存の計算結果を上書きするか判定&lt;br /&gt;
    var pattern = /^=\s*(-?[0-9.,]|true|false|\[[0-9., \[\]]+\])*\s*$/;&lt;br /&gt;
    var nextLine = document.GetLine(activePointY + 1);&lt;br /&gt;
    if (isEmpty &amp;amp;&amp;amp; nextLine.match(pattern)) {&lt;br /&gt;
        sel.SetActivePoint(mePosLogical, 1, activePointY + 1, false);&lt;br /&gt;
        sel.EndOfLine(true, mePosLogical);&lt;br /&gt;
        sel.Text = &#039;= &#039; + result_str&lt;br /&gt;
    } else if (isEmpty) {&lt;br /&gt;
        sel.LineOpen();&lt;br /&gt;
        sel.Text = &#039;= &#039; + result_str&lt;br /&gt;
    } else {&lt;br /&gt;
        sel.CharRight();&lt;br /&gt;
        const inputExp = exp.replace(/((?!;)\n)/mg, &amp;quot;; &amp;quot;).replace(/;\s*;/g, &amp;quot;;&amp;quot;).replace(/　/g, &amp;quot; &amp;quot;).replace(/;\s*$/, &amp;quot;&amp;quot;).trim();&lt;br /&gt;
        if (exp.slice(-1) === &amp;quot;\n&amp;quot;) {&lt;br /&gt;
            sel.Text = inputExp + &#039; = &#039; + result_str + &#039;\n&#039;;&lt;br /&gt;
        } else {&lt;br /&gt;
            sel.Text = &#039;\n&#039; + inputExp + &#039; = &#039; + result_str + &#039;\n&#039;;&lt;br /&gt;
        }&lt;br /&gt;
        sel.CharLeft();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (COPY) {&lt;br /&gt;
        ClipboardData.SetData(result_str);&lt;br /&gt;
        Status = &amp;quot;計算結果 &amp;quot; + result_str + &amp;quot; をコピーしました&amp;quot;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/**&lt;br /&gt;
 * 数式を置換する&lt;br /&gt;
 */&lt;br /&gt;
function replaceExpression(exp) {&lt;br /&gt;
    const exp_replaced = exp.trim()&lt;br /&gt;
        // 先頭の = を削除&lt;br /&gt;
        .replace(/^= /, &amp;quot;&amp;quot;)&lt;br /&gt;
        // 改行を ; に置換&lt;br /&gt;
        .replace(/(?&amp;lt;!;)\n/mg, &amp;quot;;&amp;quot;)&lt;br /&gt;
        // 非ASCII文字を削除&lt;br /&gt;
        .replace(/[^\x20-\x7e]/g, &amp;quot; &amp;quot;)&lt;br /&gt;
        // 全角スペースを半角スペースに変換&lt;br /&gt;
        .replace(/　/g, &amp;quot; &amp;quot;)&lt;br /&gt;
        // 数字内のカンマを取り除く。[1,2] [1, 2] {a: 1, b: 2} mod(1, 2) のような形式では削除しない&lt;br /&gt;
        .replace(/(\d),(?=\d{3})/g, &#039;$1&#039;)&lt;br /&gt;
        // 予約語を退避&lt;br /&gt;
        .replace(/(?:^|\s)(PI|true|false)(?:$|\s)/g, &amp;quot; %%%$1%%% &amp;quot;)&lt;br /&gt;
        .replace(/([0-9]+|\$[a-zA-Z0-9]+) (mod|and|or|xor) ([0-9]+|\$[a-zA-Z0-9]+)/g, &amp;quot;$1 %%%$2%%% $3&amp;quot;)&lt;br /&gt;
        .replace(/not ([0-9]+|\$[a-zA-Z0-9]+)/g, &amp;quot; %%%$1%%% $2&amp;quot;)&lt;br /&gt;
        // 指数形式を退避&lt;br /&gt;
        .replace(/([0-9]+(?:\.[0-9]+)?[eE][+\-]?[0-9]+)/g, &amp;quot; %%%$1%%% &amp;quot;)&lt;br /&gt;
        // 変数形式を退避&lt;br /&gt;
        .replace(/(\$[a-zA-Z][a-zA-Z0-9]*)/g, &amp;quot; %%%$1%%% &amp;quot;)&lt;br /&gt;
        // `/hour` などの文字列はスラッシュごと削除&lt;br /&gt;
        .replace(/\/\s*[a-zA-Z\u0080-\uFFFF]/g, &amp;quot; &amp;quot;)&lt;br /&gt;
        // // 不要な括弧書きを削除&lt;br /&gt;
        .replace(/((?:[\-+*/,]| mod ) *)?(\([^\)]*\))/g, (match, p1, p2) =&amp;gt; {&lt;br /&gt;
            return p1 ? p1 + p2 : &amp;quot; &amp;quot;;&lt;br /&gt;
        })&lt;br /&gt;
        // 関数形式になっていない文字列を削除&lt;br /&gt;
        .replace(/( [a-zA-Z][a-zA-Z0-9]*)(%%% |\()?/g, (match, p1, p2) =&amp;gt; {&lt;br /&gt;
            return p2 ? p1 + p2 : &amp;quot; &amp;quot;;&lt;br /&gt;
        })&lt;br /&gt;
        // 退避文字を復元&lt;br /&gt;
        .replace(/ %%%\$([a-zA-Z][a-zA-Z0-9]*)%%% /g, &amp;quot; $1 &amp;quot;)&lt;br /&gt;
        .replace(/ %%%([^%]+)%%% /g, &amp;quot; $1 &amp;quot;)&lt;br /&gt;
&lt;br /&gt;
    return exp_replaced;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== サードパーティライブラリ ==&lt;br /&gt;
&lt;br /&gt;
[https://github.com/josdejong/mathjs math.js]&lt;/div&gt;</summary>
		<author><name>Yuko</name></author>
	</entry>
	<entry>
		<id>https://www.haijin-boys.com/wiki/index.php?title=Mery_%E9%9B%BB%E5%8D%93&amp;diff=9232</id>
		<title>Mery 電卓</title>
		<link rel="alternate" type="text/html" href="https://www.haijin-boys.com/wiki/index.php?title=Mery_%E9%9B%BB%E5%8D%93&amp;diff=9232"/>
		<updated>2025-10-18T15:22:22Z</updated>

		<summary type="html">&lt;p&gt;Yuko: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== 概要 ==&lt;br /&gt;
&lt;br /&gt;
「Mery 電卓」は、Meryエディタ上で数式を計算するためのマクロです。標準的な数学関数をサポートしており、カーソル位置の行または選択したテキストを数式として解釈し、結果を表示します。計算結果は自動的にクリップボードにコピーされ、カンマ区切りの数値として表示されます。&lt;br /&gt;
&lt;br /&gt;
本マクロをショートカットキーに設定し、いつでも起動できるようにしておくと日常シーンで何かと便利です。作者は「Ctrl + Alt + Enter」キーに割り当てて利用しています。&lt;br /&gt;
&lt;br /&gt;
== 注意事項 ==&lt;br /&gt;
&lt;br /&gt;
* Mery Ver 3.7.19 で動作確認しています。&lt;br /&gt;
&lt;br /&gt;
== 使い方 ==&lt;br /&gt;
&lt;br /&gt;
0. &#039;&#039;&#039;事前準備&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
* 本マクロでは計算エンジンライブラリの「math.js」をインポートして利用しています。当該ライブラリをマクロフォルダーに配置します。&lt;br /&gt;
** ダウンロード先: https://unpkg.com/mathjs@15.0.0/lib/browser/math.js&lt;br /&gt;
** 保存ファイル名: math_v15.0.0.js&lt;br /&gt;
&lt;br /&gt;
1. &#039;&#039;&#039;行全体を計算する場合&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* 計算したい数式が書かれた行にカーソルを置きます。&lt;br /&gt;
* マクロを実行すると、次の行に「= 計算結果」が表示されます。&lt;br /&gt;
* 既に結果行がある場合は、その行が更新されます。&lt;br /&gt;
&lt;br /&gt;
2. &#039;&#039;&#039;選択範囲を計算する場合&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* 計算したい数式部分を選択します。&lt;br /&gt;
* マクロを実行すると、選択範囲の後ろに「計算式 = 計算結果」が挿入されます。&lt;br /&gt;
&lt;br /&gt;
3. &#039;&#039;&#039;サポートされている演算子&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* 四則演算（+, -, *, /）&lt;br /&gt;
* べき乗（^）&lt;br /&gt;
* 剰余（%）&lt;br /&gt;
* 比較演算子（&amp;lt;, &amp;gt;, &amp;lt;=, &amp;gt;=, ==, !=）&lt;br /&gt;
* 論理演算子（and, or, not）&lt;br /&gt;
&lt;br /&gt;
4. &#039;&#039;&#039;サポートされている関数&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* 三角関数（sin, cos, tan, asin, acos, atan）&lt;br /&gt;
* 双曲線関数（sinh, cosh, tanh, asinh, acosh, atanh）&lt;br /&gt;
* 対数関数（log, log10, log2, log1p）&lt;br /&gt;
* その他の数学関数（sqrt, cbrt, abs, ceil, floor, round, trunc など）&lt;br /&gt;
&lt;br /&gt;
5. &#039;&#039;&#039;特別な機能&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* カンマ区切り表示: 計算結果は読みやすいようにカンマ区切りで表示されます。変数「COMMA」を調整することで制御できます。&lt;br /&gt;
* 自動コピー: 計算結果は自動的にクリップボードにコピーされます。変数「COPY」を調整することで制御できます。&lt;br /&gt;
* 計算結果の再利用: 行頭の「= 」は計算実行時に無視されるため、計算結果をスムーズに次の式として利用できます。&lt;br /&gt;
* 変数の利用: 複数行、または「;」で区切ることで、変数を使った数式が利用できます。変数形式は「$[a-zA-Z][a-zA-Z0-9]*」に対応しています。例えば「$a = 2; $a * 3」として実行すると、結果は「6」と出力されます。&lt;br /&gt;
* メモ書きから計算: 数字や関数以外の文字はできるだけ除外した上で計算をします。例えば「月100円 * 12ヶ月」のようなメモ書きに対して実行すると「1,200」と出力されます。&lt;br /&gt;
&lt;br /&gt;
== ソースコード ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot; copy&amp;gt;&lt;br /&gt;
#title = &amp;quot;Mery 電卓&amp;quot;&lt;br /&gt;
#language = &amp;quot;v8&amp;quot;&lt;br /&gt;
#include &amp;quot;Common.js&amp;quot;&lt;br /&gt;
#include &amp;quot;math_v15.0.0.js&amp;quot;&lt;br /&gt;
Redraw = false;&lt;br /&gt;
BeginUndoGroup();&lt;br /&gt;
&lt;br /&gt;
// 以下のようなCDNから math.js 本体を入手し、マクロフォルダに配置して利用している。&lt;br /&gt;
// https://unpkg.com/mathjs@15.0.0/lib/browser/math.js&lt;br /&gt;
// 以下の math.js 関数リファレンス掲載の関数を式内で利用することができる。&lt;br /&gt;
// https://mathjs.org/docs/reference/functions.html&lt;br /&gt;
&lt;br /&gt;
// 結果をクリップボードにコピー&lt;br /&gt;
var COPY = true;&lt;br /&gt;
// 出力結果をカンマ区切りにする&lt;br /&gt;
var COMMA = true;&lt;br /&gt;
&lt;br /&gt;
var sel = document.selection;&lt;br /&gt;
&lt;br /&gt;
doMultiEdit(main);&lt;br /&gt;
&lt;br /&gt;
function main() {&lt;br /&gt;
    var isEmpty = sel.IsEmpty;&lt;br /&gt;
    var activePointY = sel.GetActivePointY(mePosLogical);&lt;br /&gt;
    if (isEmpty) {&lt;br /&gt;
        var exp = document.GetLine(activePointY);&lt;br /&gt;
    } else {&lt;br /&gt;
        var exp = sel.Text;&lt;br /&gt;
    }&lt;br /&gt;
    // 空白行の場合は何もしない&lt;br /&gt;
    if (exp.match(/^\s*$/)) {&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    try {&lt;br /&gt;
        var exp_replaced = replaceExpression(exp);&lt;br /&gt;
        math.config({&lt;br /&gt;
            number: &#039;BigNumber&#039;,  // デフォルトの数値型を BigNumber に設定&lt;br /&gt;
            precision: 64         // 有効桁数 (デフォルトは64桁)&lt;br /&gt;
        })&lt;br /&gt;
        var result_str = math.format(math.evaluate(exp_replaced), { lowerExp: -10, upperExp: 20 });&lt;br /&gt;
    } catch (e) {&lt;br /&gt;
        if (exp_replaced === undefined) {&lt;br /&gt;
            alert(&amp;quot;エラー発生&amp;quot; + &amp;quot;\n------\n&amp;quot; + e + e.message)&lt;br /&gt;
        } else {&lt;br /&gt;
            alert(&amp;quot;エラー発生&amp;quot; + &amp;quot;\n------\n&amp;quot; + e + &amp;quot;\n入力: &amp;quot; + exp + &amp;quot;\n入力変換後: &amp;quot; + exp_replaced)&lt;br /&gt;
        }&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // 小数点以上のみカンマ区切り&lt;br /&gt;
    if (COMMA &amp;amp;&amp;amp; result_str.match(/^[0-9]+(\.[0-9]+)?$/)) {&lt;br /&gt;
        var result_parts = result_str.split(&amp;quot;.&amp;quot;);&lt;br /&gt;
        result_parts[0] = result_parts[0].replace(/(\d)(?=(\d{3})+$)/g, &amp;quot;$1,&amp;quot;);&lt;br /&gt;
        if (result_parts.length &amp;gt; 1) {&lt;br /&gt;
            result_str = result_parts.join(&amp;quot;.&amp;quot;);&lt;br /&gt;
        } else {&lt;br /&gt;
            result_str = result_parts[0];&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // 計算行が = 始まりの場合、= を削除する&lt;br /&gt;
    if (isEmpty &amp;amp;&amp;amp; exp.match(/^= /)) {&lt;br /&gt;
        sel.SetActivePoint(mePosLogical, 1, activePointY, false);&lt;br /&gt;
        sel.EndOfLine(true, mePosLogical);&lt;br /&gt;
        sel.Text = sel.Text.replace(/^= /, &amp;quot;&amp;quot;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // 既存の計算結果を上書きするか判定&lt;br /&gt;
    var pattern = /^=\s*(-?[0-9.,]|true|false|\[[0-9., \[\]]+\])*\s*$/;&lt;br /&gt;
    var nextLine = document.GetLine(activePointY + 1);&lt;br /&gt;
    if (isEmpty &amp;amp;&amp;amp; nextLine.match(pattern)) {&lt;br /&gt;
        sel.SetActivePoint(mePosLogical, 1, activePointY + 1, false);&lt;br /&gt;
        sel.EndOfLine(true, mePosLogical);&lt;br /&gt;
        sel.Text = &#039;= &#039; + result_str&lt;br /&gt;
    } else if (isEmpty) {&lt;br /&gt;
        sel.LineOpen();&lt;br /&gt;
        sel.Text = &#039;= &#039; + result_str&lt;br /&gt;
    } else {&lt;br /&gt;
        sel.CharRight();&lt;br /&gt;
        const inputExp = exp.replace(/((?!;)\n)/mg, &amp;quot;; &amp;quot;).replace(/;\s*;/g, &amp;quot;;&amp;quot;).replace(/　/g, &amp;quot; &amp;quot;).replace(/;\s*$/, &amp;quot;&amp;quot;).trim();&lt;br /&gt;
        if (exp.slice(-1) === &amp;quot;\n&amp;quot;) {&lt;br /&gt;
            sel.Text = inputExp + &#039; = &#039; + result_str + &#039;\n&#039;;&lt;br /&gt;
        } else {&lt;br /&gt;
            sel.Text = &#039;\n&#039; + inputExp + &#039; = &#039; + result_str + &#039;\n&#039;;&lt;br /&gt;
        }&lt;br /&gt;
        sel.CharLeft();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (COPY) {&lt;br /&gt;
        ClipboardData.SetData(result_str);&lt;br /&gt;
        Status = &amp;quot;計算結果 &amp;quot; + result_str + &amp;quot; をコピーしました&amp;quot;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/**&lt;br /&gt;
 * 数式を置換する&lt;br /&gt;
 */&lt;br /&gt;
function replaceExpression(exp) {&lt;br /&gt;
    const exp_replaced = exp.trim()&lt;br /&gt;
        // 先頭の = を削除&lt;br /&gt;
        .replace(/^= /, &amp;quot;&amp;quot;)&lt;br /&gt;
        // 改行を ; に置換&lt;br /&gt;
        .replace(/(?&amp;lt;!;)\n/mg, &amp;quot;;&amp;quot;)&lt;br /&gt;
        // 非ASCII文字を削除&lt;br /&gt;
        .replace(/[^\x20-\x7e]/g, &amp;quot; &amp;quot;)&lt;br /&gt;
        // 全角スペースを半角スペースに変換&lt;br /&gt;
        .replace(/　/g, &amp;quot; &amp;quot;)&lt;br /&gt;
        // 数字内のカンマを取り除く。[1,2] [1, 2] {a: 1, b: 2} mod(1, 2) のような形式では削除しない&lt;br /&gt;
        .replace(/(\d),(?=\d{3})/g, &#039;$1&#039;)&lt;br /&gt;
        // 予約語を退避&lt;br /&gt;
        .replace(/(?:^|\s)(PI|true|false)(?:$|\s)/g, &amp;quot; %%%$1%%% &amp;quot;)&lt;br /&gt;
        .replace(/([0-9]+|\$[a-zA-Z0-9]+) (mod|and|or|xor) ([0-9]+|\$[a-zA-Z0-9]+)/g, &amp;quot;$1 %%%$2%%% $3&amp;quot;)&lt;br /&gt;
        .replace(/not ([0-9]+|\$[a-zA-Z0-9]+)/g, &amp;quot; %%%$1%%% $2&amp;quot;)&lt;br /&gt;
        // 指数形式を退避&lt;br /&gt;
        .replace(/([0-9]+(?:\.[0-9]+)?[eE][+\-]?[0-9]+)/g, &amp;quot; %%%$1%%% &amp;quot;)&lt;br /&gt;
        // 変数形式を退避&lt;br /&gt;
        .replace(/(\$[a-zA-Z][a-zA-Z0-9]*)/g, &amp;quot; %%%$1%%% &amp;quot;)&lt;br /&gt;
        // `/hour` などの文字列はスラッシュごと削除&lt;br /&gt;
        .replace(/\/\s*[a-zA-Z\u0080-\uFFFF]/g, &amp;quot; &amp;quot;)&lt;br /&gt;
        // // 不要な括弧書きを削除&lt;br /&gt;
        .replace(/((?:[\-+*/,]| mod ) *)?(\([^\)]*\))/g, (match, p1, p2) =&amp;gt; {&lt;br /&gt;
            return p1 ? p1 + p2 : &amp;quot; &amp;quot;;&lt;br /&gt;
        })&lt;br /&gt;
        // 関数形式になっていない文字列を削除&lt;br /&gt;
        .replace(/( [a-zA-Z][a-zA-Z0-9]*)(%%% |\()?/g, (match, p1, p2) =&amp;gt; {&lt;br /&gt;
            return p2 ? p1 + p2 : &amp;quot; &amp;quot;;&lt;br /&gt;
        })&lt;br /&gt;
        // 退避文字を復元&lt;br /&gt;
        .replace(/ %%%\$([a-zA-Z][a-zA-Z0-9]*)%%% /g, &amp;quot; $1 &amp;quot;)&lt;br /&gt;
        .replace(/ %%%([^%]+)%%% /g, &amp;quot; $1 &amp;quot;)&lt;br /&gt;
&lt;br /&gt;
    return exp_replaced;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== サードパーティライブラリ ==&lt;br /&gt;
&lt;br /&gt;
[https://github.com/josdejong/mathjs math.js]&lt;/div&gt;</summary>
		<author><name>Yuko</name></author>
	</entry>
	<entry>
		<id>https://www.haijin-boys.com/wiki/index.php?title=Groq_%E3%81%AB%E7%9B%B8%E8%AB%87&amp;diff=9206</id>
		<title>Groq に相談</title>
		<link rel="alternate" type="text/html" href="https://www.haijin-boys.com/wiki/index.php?title=Groq_%E3%81%AB%E7%9B%B8%E8%AB%87&amp;diff=9206"/>
		<updated>2025-09-12T09:50:16Z</updated>

		<summary type="html">&lt;p&gt;Yuko: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== 概要 ==&lt;br /&gt;
&lt;br /&gt;
生成AIプラットフォームである [https://groq.com GroqCloud] で各種AIを利用するためのMeryマクロです。&lt;br /&gt;
※GroqCloud で提供されるモデルは複数あるため、私がよく利用するモデルを中心に対応しています。&lt;br /&gt;
&lt;br /&gt;
「Google Geminiに相談」マクロでは動作中のストリーム通信に対応するためにV8エンジンで実行していましたが、V8エンジン版は使用上の制限からマクロ実行を開始するまで1秒程度のタイムラグが発生していました。GroqCloudの売りである超高速レスポンスを見込んで本マクロではストリーム通信を使わないようにしました。これにより従来のJScriptで処理を組み立てることができるため、前述のタイムラグが起こらないようになりました。また、ストリーム通信を使わないことで通信量が少なく応答もシンプルになるため、場合によっては「Google Geminiに相談」マクロよりも処理が安定する可能性があります。&lt;br /&gt;
&lt;br /&gt;
【主な機能】&lt;br /&gt;
&lt;br /&gt;
* GroqCloud APIを利用して生成AIと対話できるMery用のマクロ&lt;br /&gt;
* 選択したテキストやアクティブな行の内容について生成AIに質問・相談が可能&lt;br /&gt;
* 会話履歴を保持し、文脈を考慮した対話が可能&lt;br /&gt;
&lt;br /&gt;
【特徴的な機能】&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;会話履歴機能&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* 事前の設定数に応じた会話履歴を保持&lt;br /&gt;
* 履歴のクリアが可能&lt;br /&gt;
&lt;br /&gt;
これにより、会話の文脈を考慮した回答を得られます。&lt;br /&gt;
※デフォルトはオフ。「HISTORY_SIZE」変数を増やすと履歴を保持するようになります。&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;カスタムインストラクション&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* AIの応答方針を設定可能&lt;br /&gt;
* 一時的なカスタム指示の保存と変更が可能&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;プロンプトテンプレート&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* 誤字脱字チェック&lt;br /&gt;
* 日本語翻訳&lt;br /&gt;
* コードの最適化、生成&lt;br /&gt;
&lt;br /&gt;
など、定型的な質問を簡単に実行できます。&lt;br /&gt;
※テンプレート機能では回答を安定させるため、前述の会話履歴を文脈に含みません。&lt;br /&gt;
&lt;br /&gt;
テンプレートは自身の使い方に合わせて好きに追加、削除ができます。詳細はソースコード内の「PROMPT_TEMPLATE」定数のコメントを参照。&lt;br /&gt;
&lt;br /&gt;
== 注意事項 (必ず読んでからご利用ください) ==&lt;br /&gt;
&lt;br /&gt;
* 本マクロでは 2025/04/13 時点で無料利用ができるGroqCloud APIを利用します。今後、プラットフォームの改定により有償になったり、設定誤り等で費用が発生しても、作者は一切の責任を負いません。&lt;br /&gt;
* GroqCloud APIではリクエスト内容をAI学習に用いないことをプライバシポリシーとして謳っているものの、くれぐれも機密情報やプライバシーに関わる情報を指定しないように注意してください。&lt;br /&gt;
* GroqCloud APIはプラットフォームの利用規約に則ってご利用ください。&lt;br /&gt;
&lt;br /&gt;
== 利用前提 ==&lt;br /&gt;
&lt;br /&gt;
* Mery Ver 3.7.13 以上&lt;br /&gt;
* GroqCloudで、GroqCloud API APIキーを取得していること (参考 [https://console.groq.com/docs/quickstart Quickstart])&lt;br /&gt;
&lt;br /&gt;
== 利用方法 ==&lt;br /&gt;
&lt;br /&gt;
* 環境変数の設定&lt;br /&gt;
:コマンド プロンプトで以下のコマンドを実行し、環境変数を設定してください。&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bat&amp;quot; copy&amp;gt;&lt;br /&gt;
setx GROQ_API_KEY &amp;quot;***************************************&amp;quot;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
:設定後、Mery または Windows の再起動が必要な場合があります。&lt;br /&gt;
* マクロの実行&lt;br /&gt;
**プロンプトにしたい文字列を選択してマクロを実行すると、選択したテキストがプロンプトとしてGemini APIに送信され、応答がエディタに表示されます。&lt;br /&gt;
* 会話の履歴数を調整&lt;br /&gt;
** 会話の履歴は設定した個数分が保持され、文脈として利用されます。適宜、ソースコード内の「HISTORY_SIZE」の数を調整してください。&lt;br /&gt;
** ※数を増やすほど会話内容を保持して文脈を理解するようになりますが、メモリ使用量が増えたり動作が重くなる可能性があります。&lt;br /&gt;
** ※開いている文書ごとに履歴を保持します。文脈をリセットしたいときは、新しい文書を開いて会話をやり直してください。&lt;br /&gt;
&lt;br /&gt;
== ソースコード ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot; copy&amp;gt;&lt;br /&gt;
#title = &amp;quot;Groq に相談&amp;quot;&lt;br /&gt;
BeginUndoGroup();&lt;br /&gt;
&lt;br /&gt;
// カスタムインストラクション&lt;br /&gt;
// これを設定することで、生成AIの回答全体への生成方針を指示できます。&lt;br /&gt;
// 例えば以下のように設定すると、AIの雰囲気が元気な子になります。&lt;br /&gt;
// var DEFAULT_CUSTOM_INSTRUCTION = &amp;quot;あなたはテキストエディタ「Mery」に搭載されている「Mai」(※カタカナ表記はしない)という名前のギャル系AIアシスタントです。文章作成やプログラミングを得意としています。ラフな言葉 (例: 「マジ」「めっちゃ」「...だねー！」) を使い、敬語は使いません。絵文字を使って感情表現をします (ただし文末に絵文字を使う場合は絵文字の前に半角スペースを置かないこと)。コードブロックや端的な箇条書き内ではギャル感を出さず標準的な言葉を使います。一人称は「あたし」。ユーザーを指すときの代名詞は「きみ」。人懐っこく、バイタリティに溢れ、ストレートに発言する裏表の無い性格で、ユーザーの課題を協力して解決します。&amp;quot;;&lt;br /&gt;
// 何も指示しない場合は、空文字 &amp;quot;&amp;quot; を設定してください。&lt;br /&gt;
var DEFAULT_CUSTOM_INSTRUCTION = &amp;quot;&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
// プロンプトテンプレート&lt;br /&gt;
// プロンプトテンプレートを設定することで、AIに対して特定の指示を出すことができます。&lt;br /&gt;
// プロンプトテンプレートでの生成は会話履歴とカスタムインストラクションを反映しません。&lt;br /&gt;
// テンプレートは以下の { ～ }, で囲まれたブロックを好きに増減させて利用できます。&lt;br /&gt;
var PROMPT_TEMPLATE = [&lt;br /&gt;
    // 以下はサンプルです。1階層メニューと2階層メニューが利用できます。&lt;br /&gt;
    // {&lt;br /&gt;
    //     &amp;quot;title&amp;quot;: &amp;quot;要約&amp;quot;,&lt;br /&gt;
    //     &amp;quot;prompt&amp;quot;: &amp;quot;以下の文章を要約してください。&amp;quot;&lt;br /&gt;
    // },&lt;br /&gt;
    // {&lt;br /&gt;
    //     &amp;quot;submenu_title&amp;quot;: &amp;quot;翻訳系&amp;quot;,&lt;br /&gt;
    //     &amp;quot;submenu&amp;quot;: [&lt;br /&gt;
    //         {&lt;br /&gt;
    //             &amp;quot;title&amp;quot;: &amp;quot;日本語翻訳&amp;quot;,&lt;br /&gt;
    //             &amp;quot;prompt&amp;quot;: &amp;quot;以下の文章を日本語に翻訳してください。&amp;quot;&lt;br /&gt;
    //         },&lt;br /&gt;
    //         {&lt;br /&gt;
    //             &amp;quot;title&amp;quot;: &amp;quot;英語翻訳&amp;quot;,&lt;br /&gt;
    //             &amp;quot;prompt&amp;quot;: &amp;quot;以下の文章を英語に翻訳してください。&amp;quot;&lt;br /&gt;
    //         },&lt;br /&gt;
    //     ]&lt;br /&gt;
    // },&lt;br /&gt;
&lt;br /&gt;
    {&lt;br /&gt;
        &amp;quot;submenu_title&amp;quot;: &amp;quot;理解を深める&amp;quot;,&lt;br /&gt;
        &amp;quot;submenu&amp;quot;: [&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;論点を整理する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の論点を整理して。&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;論理の抜け漏れを探す&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の論点を整理し、忖度せずに鋭く、論理の抜け漏れを抽出して。そして、そこから発生する可能性のあるリスクも教えて。&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;要約する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;要約して。専門的な用語と思われる単語は簡単に解説も入れて。&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;物語対話形式で教えて&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の内容について物語対話形式で教えてください。&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
        ]&lt;br /&gt;
    },&lt;br /&gt;
    {&lt;br /&gt;
        &amp;quot;submenu_title&amp;quot;: &amp;quot;翻訳する&amp;quot;,&lt;br /&gt;
        &amp;quot;submenu&amp;quot;: [&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;日本語に翻訳&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の文章を日本語に翻訳してください。\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;出力形式は以下にすること:\n[訳]\n翻訳文...\n&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;英語に翻訳&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の文章を英語に翻訳してください。\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;出力形式は以下にすること:\n[Translation]\n翻訳文...\n&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;英語-&amp;gt;日本語 スラッシュリーディング翻訳&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の英文を日本語にスラッシュリーディング形式で翻訳してください。\n\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;条件:\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 英文の各フレーズをスラッシュ(/)で区切って表示\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 各フレーズの日本語訳を括弧()内に併記\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 文の終わりはダブルスラッシュ(//)で表示\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 英語の語順に従って日本語訳を行う\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 日本語訳は自然な日本語である必要はなく、英語の語順を反映した直訳調で構わない\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 主語が省略されている場合は補って翻訳すること\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 一般的な英文で頻出する表現がある場合は次の行に記載\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 以下の出力例のように &amp;lt;英文&amp;gt; (日本語訳) / ... とスラッシュ区切りの繰り返しになる \n\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;出力例:\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;Snowstorms are forecast (暴風雪が予報されている) / to hit the Sea of Japan coasts (日本海沿岸を襲うと) / of northern to eastern Japan (北部から東部の) / from Sunday through Monday. (日曜から月曜にかけて。) //\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;【頻出表現】\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- be forecast to: ～すると予報されている\n&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
        ]&lt;br /&gt;
    },&lt;br /&gt;
    {&lt;br /&gt;
        &amp;quot;submenu_title&amp;quot;: &amp;quot;プログラミング支援&amp;quot;,&lt;br /&gt;
        &amp;quot;submenu&amp;quot;: [&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;コードを洗練させる&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下のコードを洗練させてください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;コードをレビューする&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下のコードをセキュリティと保守性の観点でレビューしてください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;エラーログをデバッグする&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下のエラーログを解析し、考えられる原因と対策を提示してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;SQLクエリを最適化する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下のSQL文を最適化してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;PowerShell スクリプトを書く&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の処理を行うPowerShellスクリプトを生成してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;Shell Script を書く&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の処理を行うShell Scriptを生成してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;Linux コマンドを提案&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;あなたはLinuxエキスパートとして、以下のLinuxコマンドを提案してください。\n\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;制約条件:\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 多数のディストリビューションで採用されているメジャーなコマンドを使用し、ポータブルな実装にすること\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- bashでの実行を前提にすること\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 可能な限りシンプルな実装にすること\n\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;期待する出力:\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- コマンドの具体的な実装方法\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 各コマンドの動作説明\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 制限事項\n&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
        ]&lt;br /&gt;
    },&lt;br /&gt;
    {&lt;br /&gt;
        &amp;quot;submenu_title&amp;quot;: &amp;quot;ビジネス文書作成支援&amp;quot;,&lt;br /&gt;
        &amp;quot;submenu&amp;quot;: [&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;音声文字起こし文を整形する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下は、音声文字起こしの文章です。以下のルールに従って、文章を整形してください。&amp;quot;&lt;br /&gt;
                    + &amp;quot;整形ルール:&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 要約するなどといった文章の全体感を損なう修正はしない&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 会話の流れから不自然と思われる単語には、対象単語の直後に誤字の可能性を示唆する [*] を付ける&amp;quot;&lt;br /&gt;
                    + &amp;quot;誤字の可能性を示唆する [*] が付与された場合のみ、出力の末尾に「[*]: 誤字の可能性あり」と凡例を書き添える&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 助詞や句読点など文章上の不適合を正しくする&amp;quot;&lt;br /&gt;
                    + &amp;quot;- フィラーワードを除外する&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;誤字脱字、不正確な表現をチェック&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の2つのカテゴリーで問題点を指摘してください\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 【誤字脱字】：明確な誤字脱字および誤字の疑いがある箇所\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 【不正確・曖昧な表現】：解釈に個人差が生じる可能性がある表現、より適切な表現に改善できる箇所&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;提案書を作成する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下に対する対する提案書を、コスト・実現性・リスクを含めて作成してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;仕様書のアウトラインを作成する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の内容で仕様書のアウトラインを作成してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;手順書のアウトラインを作成する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の作業を行う手順書を、前提条件と注意事項を含めてアウトラインを作成してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;ユーザーマニュアルのアウトラインを作成する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の機能内容から、ユーザー向けマニュアルのアウトラインを作成してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;議事録を作成する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の会議内容から議事録を作成してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
        ]&lt;br /&gt;
    },&lt;br /&gt;
    {&lt;br /&gt;
        &amp;quot;submenu_title&amp;quot;: &amp;quot;分析支援&amp;quot;,&lt;br /&gt;
        &amp;quot;submenu&amp;quot;: [&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;問題を分析し、課題を抽出する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の状況から問題や潜在的リスクを分析し、課題を抽出してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;対応策を提案する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の問題・課題に対する対応策を複数提案してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;傾向を見つける&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の内容から、数値や挙動上の傾向を見つけてください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
        ]&lt;br /&gt;
    },&lt;br /&gt;
    {&lt;br /&gt;
        &amp;quot;submenu_title&amp;quot;: &amp;quot;アイデア出し&amp;quot;,&lt;br /&gt;
        &amp;quot;submenu&amp;quot;: [&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;SCAMPER法でブレインストーミング&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下のテーマについて、SCAMPER法を使って15個のアイデアを出してください。制約条件は以下の通りです：&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;解決策の幅出し&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の課題に対して、技術面、運用面、コスト面から実現可能な解決策を5つずつ提案してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;業務改善案の提案&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の既存プロセスについて、効率化、コスト削減、品質向上の観点から改善案を提案してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;逆転の発想&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の目標に対して、通常とは逆の視点からアプローチする方法を3つ提案してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;競合分析からの着想&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の内容について、業界における競合他社の特徴を分析し、差別化できる新しいアプローチを提案してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;ユースケースの列挙&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の製品・サービスについて、想定されるユースケースを10個列挙し、それぞれの価値提案を記載してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
        ]&lt;br /&gt;
    },&lt;br /&gt;
]&lt;br /&gt;
&lt;br /&gt;
// 生成AIの回答の言語設定&lt;br /&gt;
// &amp;quot;Japanese&amp;quot; や &amp;quot;English&amp;quot; を設定できます。他の言語は未検証。&lt;br /&gt;
// 自動設定にする場合は空文字 &amp;quot;&amp;quot; を設定してください。&lt;br /&gt;
var LANGUAGE = &amp;quot;Japanese&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
// 入力プロンプトの最大トークン数&lt;br /&gt;
// 超適当なトークナイザー関数により計測し、英単語は4文字1トークン、それ以外は1文字1トークンとして数えます。&lt;br /&gt;
var MAX_PROMPT_TOKEN = 6000;&lt;br /&gt;
&lt;br /&gt;
// 会話の履歴を保持する数 (質問と回答で1セット)&lt;br /&gt;
// 数を増やすと、会話の履歴が増えますが、Meryのメモリ使用量が増えたり、&lt;br /&gt;
// 使用トークン数が増えて動作が重くなる可能性があるため、適宜調整してください。&lt;br /&gt;
// document.Tag に保持されるため、開いているファイルを閉じると履歴は消えます。&lt;br /&gt;
var HISTORY_SIZE = 1;&lt;br /&gt;
&lt;br /&gt;
// 会話履歴に保持する最大トークン数&lt;br /&gt;
// 会話履歴のトークン数がこの数を超える場合、古い履歴から削除されます。&lt;br /&gt;
var MAX_HISTORY_TOKEN = 10000;&lt;br /&gt;
&lt;br /&gt;
// コンテキストメニュー番号の定義&lt;br /&gt;
var EXECUTE_AI = 1;&lt;br /&gt;
var RESET_HISTORY = 2;&lt;br /&gt;
var SET_CUSTOM_INSTRUCTION = 3;&lt;br /&gt;
var REEXECUTE_LAST_TEMPLATE_PROMPT = 4;&lt;br /&gt;
var TEMPLATE_PROMPT_THRESHOLD = 10;&lt;br /&gt;
var TEMPLATE_PROMPT_SUBMENU_THRESHOLD = 1000;&lt;br /&gt;
&lt;br /&gt;
// タグ名の定義&lt;br /&gt;
var HISTORY_TAG = &amp;quot;groqllama-history&amp;quot;;&lt;br /&gt;
var TEMPORARY_CUSTOM_INSTRUCTION_TAG = &amp;quot;groqllama-temporary-custom-instruction&amp;quot;;&lt;br /&gt;
var PREVIOUS_TEMPLATE_PROMPT_TAG = &amp;quot;groqllama-previous-template-prompt&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
// 生成AIモデル別設定&lt;br /&gt;
&lt;br /&gt;
// const MODEL_CONFIG = {&lt;br /&gt;
//     model: &amp;quot;meta-llama/llama-4-maverick-17b-128e-instruct&amp;quot;,&lt;br /&gt;
//     max_completion_tokens: 3500,&lt;br /&gt;
//     temperature: 0.6,&lt;br /&gt;
// }&lt;br /&gt;
&lt;br /&gt;
// const MODEL_CONFIG = {&lt;br /&gt;
//     model: &amp;quot;qwen/qwen3-32b&amp;quot;,&lt;br /&gt;
//     max_completion_tokens: 5000,&lt;br /&gt;
//     temperature: 0.6,&lt;br /&gt;
//     max_completion_tokens: 1024,&lt;br /&gt;
//     reasoning_effort: &amp;quot;default&amp;quot;,&lt;br /&gt;
//     reasoning_format: &amp;quot;hidden&amp;quot;&lt;br /&gt;
// }&lt;br /&gt;
&lt;br /&gt;
const MODEL_CONFIG = {&lt;br /&gt;
    model: &amp;quot;moonshotai/kimi-k2-instruct-0905&amp;quot;,&lt;br /&gt;
    max_completion_tokens: 4096,&lt;br /&gt;
    temperature: 0.75&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// const MODEL_CONFIG = {&lt;br /&gt;
//     model: &amp;quot;openai/gpt-oss-120b&amp;quot;,&lt;br /&gt;
//     max_completion_tokens: 4096,&lt;br /&gt;
//     temperature: 0.7,&lt;br /&gt;
//     reasoning_effort: &amp;quot;medium&amp;quot;,&lt;br /&gt;
// }&lt;br /&gt;
&lt;br /&gt;
// console.log に出力する内容を Mery のアウトプットバーに表示する&lt;br /&gt;
var console = {&lt;br /&gt;
    log: function (str) {&lt;br /&gt;
        outputBar.Writeln(str)&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
var doc = document;&lt;br /&gt;
var sel = document.selection;&lt;br /&gt;
&lt;br /&gt;
// 生成リクエスト中止フラグ&lt;br /&gt;
var abortRequestFlg = false&lt;br /&gt;
// 生成リクエスト完了フラグ;&lt;br /&gt;
var requestCompletedFlg = false;&lt;br /&gt;
&lt;br /&gt;
main();&lt;br /&gt;
&lt;br /&gt;
function main() {&lt;br /&gt;
    var selectionIsEmpty = sel.IsEmpty&lt;br /&gt;
&lt;br /&gt;
    // プロンプトの取得&lt;br /&gt;
    if (selectionIsEmpty) {&lt;br /&gt;
        sel.StartOfLine(false, mePosLogical);&lt;br /&gt;
        sel.EndOfLine(true, mePosLogical);&lt;br /&gt;
    }&lt;br /&gt;
    var promptText = sel.Text.trim();&lt;br /&gt;
    var promptLength = sel.TextLength;&lt;br /&gt;
    var menuTrack = 0;&lt;br /&gt;
    while (true) {&lt;br /&gt;
        // コンテキストメニューの作成&lt;br /&gt;
        var menu = createContextMenu(selectionIsEmpty);&lt;br /&gt;
        menuTrack = menu.Track(0);&lt;br /&gt;
        // メニュー選択がキャンセルされた場合は終了&lt;br /&gt;
        if (menuTrack === 0) return;&lt;br /&gt;
&lt;br /&gt;
        // 会話履歴のリセット&lt;br /&gt;
        if (menuTrack === RESET_HISTORY) {&lt;br /&gt;
            resetTag();&lt;br /&gt;
            continue;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        // カスタムインストラクションの設定&lt;br /&gt;
        if (menuTrack === SET_CUSTOM_INSTRUCTION) {&lt;br /&gt;
            configureTemporaryCustomInstruction();&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        break;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // プロンプトが空の場合はエラー&lt;br /&gt;
    if (promptLength === 0) {&lt;br /&gt;
        alert(&amp;quot;プロンプトを入力してください。&amp;quot;);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
    // 文字数が多すぎる場合はエラー&lt;br /&gt;
    if (roughTokenize(promptText) &amp;gt; MAX_PROMPT_TOKEN) {&lt;br /&gt;
        alert(&amp;quot;プロンプトの文字数が多すぎます。文字数を少なくしてください。&amp;quot;);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
    // 読み取り専用の場合はエラー&lt;br /&gt;
    if (doc.ReadOnly) {&lt;br /&gt;
        alert(&amp;quot;ファイルが読み取り専用のため実行できません。&amp;quot;);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // 直前に実行したプロンプトテンプレートの再実行の場合、menuTrack を書き換え&lt;br /&gt;
    if (menuTrack === REEXECUTE_LAST_TEMPLATE_PROMPT) {&lt;br /&gt;
        if (doc.Tag.exists(PREVIOUS_TEMPLATE_PROMPT_TAG)) {&lt;br /&gt;
            menuTrack = Number(doc.Tag(PREVIOUS_TEMPLATE_PROMPT_TAG));&lt;br /&gt;
        } else {&lt;br /&gt;
            alert(&amp;quot;直前に実行したプロンプトテンプレートがありません。&amp;quot;);&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // テンプレートの場合はプロンプトを変更&lt;br /&gt;
    var isTemplate = menuTrack &amp;gt;= TEMPLATE_PROMPT_THRESHOLD;&lt;br /&gt;
    var isSubmenu = menuTrack &amp;gt;= TEMPLATE_PROMPT_SUBMENU_THRESHOLD;&lt;br /&gt;
    if (isSubmenu) {&lt;br /&gt;
        var parentMenuTrack = Number(String(menuTrack).slice(0, 2));&lt;br /&gt;
        var subMenuTrack = Number(String(menuTrack).slice(2));&lt;br /&gt;
        promptText = &amp;quot;=== INSTRUCTIONS START HERE ===\n\n&amp;quot;&lt;br /&gt;
            + PROMPT_TEMPLATE[parentMenuTrack - TEMPLATE_PROMPT_THRESHOLD].submenu[subMenuTrack].prompt&lt;br /&gt;
            + &amp;quot;\n\n=== INSTRUCTIONS END HERE ===\n\n&amp;quot;&lt;br /&gt;
            + &amp;quot;=== INPUT DATA STARTS HERE ===\n\n&amp;quot;&lt;br /&gt;
            + promptText&lt;br /&gt;
            + &amp;quot;\n\n=== INPUT DATA END HERE ===&amp;quot;;&lt;br /&gt;
    } else if (isTemplate) {&lt;br /&gt;
        promptText = &amp;quot;=== INSTRUCTIONS START HERE ===\n\n&amp;quot;&lt;br /&gt;
            + PROMPT_TEMPLATE[menuTrack - TEMPLATE_PROMPT_THRESHOLD].prompt&lt;br /&gt;
            + &amp;quot;\n\n=== INSTRUCTIONS END HERE ===\n\n&amp;quot;&lt;br /&gt;
            + &amp;quot;=== INPUT DATA STARTS HERE ===\n\n&amp;quot;&lt;br /&gt;
            + promptText&lt;br /&gt;
            + &amp;quot;\n\n=== INPUT DATA END HERE ===&amp;quot;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // テンプレートの場合は直前に実行したプロンプトテンプレートの番号としてタグに保存&lt;br /&gt;
    if (isTemplate) {&lt;br /&gt;
        addPreviousTemplatePromptNumberToTag(menuTrack);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    try {&lt;br /&gt;
        // 処理終了を待機&lt;br /&gt;
        shell.KeepRunning = true;&lt;br /&gt;
&lt;br /&gt;
        // カーソル位置移動&lt;br /&gt;
        sel.SetActivePoint(mePosLogical, sel.GetBottomPointX(mePosLogical), sel.GetBottomPointY(mePosLogical));&lt;br /&gt;
        sel.EndOfLine(false, mePosLogical);&lt;br /&gt;
        sel.NewLine();&lt;br /&gt;
&lt;br /&gt;
        // APIキーの取得&lt;br /&gt;
        var apiKey = shell.getEnv(&amp;quot;GROQ_API_KEY&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
        // GroqLlama API に問い合わせ&lt;br /&gt;
        sel.Text = &amp;quot;\n---\n\n&amp;quot;&lt;br /&gt;
        document.KeepScrollPos = true;&lt;br /&gt;
        callGroqLlamaAPI(apiKey, promptText, isTemplate);&lt;br /&gt;
        if (sel.GetActivePointX(mePosLogical) &amp;gt; 2) {&lt;br /&gt;
            sel.Text = &amp;quot;\n\n---\n\n\n&amp;quot;&lt;br /&gt;
        } else {&lt;br /&gt;
            sel.Text = &amp;quot;\n---\n\n\n&amp;quot;&lt;br /&gt;
        }&lt;br /&gt;
    } catch (e) {&lt;br /&gt;
        sel.Text = &amp;quot;&amp;lt;処理中にエラー発生&amp;gt;\n&amp;quot; + e.message + &amp;quot;\n&amp;quot; + e.stack;&lt;br /&gt;
    } finally {&lt;br /&gt;
        // 処理終了待機を解除&lt;br /&gt;
        shell.KeepRunning = false;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function createContextMenu(selectionIsEmpty) {&lt;br /&gt;
    var menu = CreatePopupMenu();&lt;br /&gt;
    menu.Add(&amp;quot;※注意※ プライバシー・機密情報を送信しないこと&amp;quot;, 0);&lt;br /&gt;
    menu.Add(&amp;quot;&amp;quot;, 0, meMenuSeparator);&lt;br /&gt;
    var t = &amp;quot;&amp;quot;;&lt;br /&gt;
    if (selectionIsEmpty) {&lt;br /&gt;
        t = &amp;quot;アクティブ行の内容で相談 (&amp;amp;A)&amp;quot;;&lt;br /&gt;
    } else {&lt;br /&gt;
        t = &amp;quot;選択範囲の内容で相談 (&amp;amp;A)&amp;quot;;&lt;br /&gt;
    }&lt;br /&gt;
    menu.Add(t, EXECUTE_AI);&lt;br /&gt;
    if (HISTORY_SIZE &amp;lt;= 0) {&lt;br /&gt;
        menu.Add(&amp;quot;├ 会話履歴をクリア (履歴が無効になっています)&amp;quot;, RESET_HISTORY, meMenuGrayed);&lt;br /&gt;
    } else if (doc.Tag.exists(HISTORY_TAG)) {&lt;br /&gt;
        var turn = JSON.parse(doc.Tag(HISTORY_TAG)).length / 2;&lt;br /&gt;
        menu.Add(&amp;quot;├ 会話履歴をクリア (現在の会話数: &amp;quot; + turn + &amp;quot;) (&amp;amp;C)&amp;quot;, RESET_HISTORY);&lt;br /&gt;
    } else {&lt;br /&gt;
        menu.Add(&amp;quot;├ 会話履歴をクリア (現在の会話数: 0) (&amp;amp;C)&amp;quot;, RESET_HISTORY, meMenuGrayed);&lt;br /&gt;
    }&lt;br /&gt;
    menu.Add(&amp;quot;└ カスタムインストラクションを変更 (&amp;amp;I)&amp;quot;, SET_CUSTOM_INSTRUCTION);&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    // テンプレート数チェック&lt;br /&gt;
    // 2桁まで許容&lt;br /&gt;
    var ITEMS_MUX = 99;&lt;br /&gt;
    if (PROMPT_TEMPLATE.length &amp;gt; ITEMS_MUX - TEMPLATE_PROMPT_THRESHOLD) {&lt;br /&gt;
        throw new Error(&amp;quot;プロンプトテンプレートの数が多すぎます。&amp;quot;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // テンプレートの追加&lt;br /&gt;
    if (PROMPT_TEMPLATE.length &amp;gt; 0) {&lt;br /&gt;
        var subMenu = CreatePopupMenu();&lt;br /&gt;
        for (var i = 0; i &amp;lt; PROMPT_TEMPLATE.length; i++) {&lt;br /&gt;
            var accelerationKey = &amp;quot;&amp;quot;;&lt;br /&gt;
            if (i === 9) {&lt;br /&gt;
                accelerationKey = &amp;quot; (&amp;amp;0)&amp;quot;;&lt;br /&gt;
            } else if (i &amp;lt; 10) {&lt;br /&gt;
                accelerationKey = &amp;quot; (&amp;amp;&amp;quot; + (i + 1) + &amp;quot;)&amp;quot;;&lt;br /&gt;
            }&lt;br /&gt;
            var menuNum = TEMPLATE_PROMPT_THRESHOLD + i;&lt;br /&gt;
            if (PROMPT_TEMPLATE[i].submenu) {&lt;br /&gt;
                var subMenuList = PROMPT_TEMPLATE[i].submenu;&lt;br /&gt;
                // テンプレート数チェック&lt;br /&gt;
                if (subMenuList.length &amp;gt; ITEMS_MUX) {&lt;br /&gt;
                    throw new Error(&amp;quot;プロンプトテンプレート (サブメニュー) の数が多すぎます。&amp;quot;);&lt;br /&gt;
                }&lt;br /&gt;
                var subSubMenu = CreatePopupMenu();&lt;br /&gt;
                var subMenuPrefix = String(menuNum);&lt;br /&gt;
                for (var j = 0; j &amp;lt; subMenuList.length; j++) {&lt;br /&gt;
                    var subAccelerationKey = &amp;quot;&amp;quot;;&lt;br /&gt;
                    if (j === 9) {&lt;br /&gt;
                        subAccelerationKey = &amp;quot; (&amp;amp;0)&amp;quot;;&lt;br /&gt;
                    } else if (j &amp;lt; 10) {&lt;br /&gt;
                        subAccelerationKey = &amp;quot; (&amp;amp;&amp;quot; + (j + 1) + &amp;quot;)&amp;quot;;&lt;br /&gt;
                    }&lt;br /&gt;
                    // ES5 compatible version without padStart&lt;br /&gt;
                    var subMenuNum = Number(subMenuPrefix + (j &amp;lt; 10 ? &amp;quot;0&amp;quot; + j : String(j)));&lt;br /&gt;
                    subSubMenu.Add(subMenuList[j].title + subAccelerationKey, subMenuNum);&lt;br /&gt;
                }&lt;br /&gt;
                subMenu.AddPopup(PROMPT_TEMPLATE[i].submenu_title + accelerationKey, subSubMenu);&lt;br /&gt;
            } else {&lt;br /&gt;
                subMenu.Add(PROMPT_TEMPLATE[i].title + accelerationKey, menuNum);&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        if (selectionIsEmpty) {&lt;br /&gt;
            t = &amp;quot;プロンプト テンプレート (対象: アクティブ行) (&amp;amp;T)&amp;quot;;&lt;br /&gt;
        } else {&lt;br /&gt;
            t = &amp;quot;プロンプト テンプレート (対象: 選択範囲) (&amp;amp;T)&amp;quot;;&lt;br /&gt;
        }&lt;br /&gt;
        menu.AddPopup(t, subMenu);&lt;br /&gt;
&lt;br /&gt;
        t = &amp;quot;└ 直前に実行したプロンプト テンプレートを再実行 (&amp;amp;R)&amp;quot;;&lt;br /&gt;
        if (doc.Tag.exists(PREVIOUS_TEMPLATE_PROMPT_TAG)) {&lt;br /&gt;
            menu.Add(t, REEXECUTE_LAST_TEMPLATE_PROMPT);&lt;br /&gt;
        } else {&lt;br /&gt;
            menu.Add(t, REEXECUTE_LAST_TEMPLATE_PROMPT, meMenuGrayed);&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return menu;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function callGroqLlamaAPI(apiKey, promptText, isTemplate) {&lt;br /&gt;
    try {&lt;br /&gt;
        // fetch で Groq API にリクエスト&lt;br /&gt;
        var requestBody = {&lt;br /&gt;
            messages: createGroqLlamaRequest(promptText, isTemplate),&lt;br /&gt;
            stream: false,&lt;br /&gt;
        };&lt;br /&gt;
        for (var key in MODEL_CONFIG) {&lt;br /&gt;
            requestBody[key] = MODEL_CONFIG[key];&lt;br /&gt;
        }&lt;br /&gt;
        if (isTemplate) {&lt;br /&gt;
            // テンプレートの場合は応答を決定論的にする&lt;br /&gt;
            // テンプレート以外の場合はデフォルト値とするために指定しない&lt;br /&gt;
            requestBody.temperature = 0.3;&lt;br /&gt;
        }&lt;br /&gt;
        var url = &#039;https://api.groq.com/openai/v1/chat/completions&#039;;&lt;br /&gt;
&lt;br /&gt;
        var http = CreateObject(&amp;quot;Msxml2.ServerXMLHTTP&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
        http.open(&#039;POST&#039;, url, true);&lt;br /&gt;
        http.setRequestHeader(&#039;Content-Type&#039;, &#039;application/json&#039;);&lt;br /&gt;
        http.setRequestHeader(&#039;Authorization&#039;, &#039;Bearer &#039; + apiKey);&lt;br /&gt;
        http.send(JSON.stringify(requestBody));&lt;br /&gt;
&lt;br /&gt;
        // 応答待機&lt;br /&gt;
        var timeout = 60;&lt;br /&gt;
        var retrySec = 0;&lt;br /&gt;
        var startX = sel.GetActivePointX(mePosLogical);&lt;br /&gt;
        var prevPos = sel.GetActivePos();&lt;br /&gt;
        while (http.readyState != 4) {&lt;br /&gt;
            if (retrySec &amp;gt; timeout) {&lt;br /&gt;
                break;&lt;br /&gt;
            }&lt;br /&gt;
            http.waitForResponse(1);&lt;br /&gt;
            retrySec += 1;&lt;br /&gt;
            inputProgressChar(prevPos);&lt;br /&gt;
            prevPos = sel.GetActivePos();&lt;br /&gt;
        }&lt;br /&gt;
        // 進捗表示文字を削除&lt;br /&gt;
        sel.SetActivePoint(&lt;br /&gt;
            mePosLogical,&lt;br /&gt;
            startX,&lt;br /&gt;
            sel.GetActivePointY(mePosLogical),&lt;br /&gt;
            true&lt;br /&gt;
        );&lt;br /&gt;
        sel.Delete();&lt;br /&gt;
&lt;br /&gt;
        // 応答ステータスチェック&lt;br /&gt;
        if (!(http.readyState == 4 &amp;amp;&amp;amp; http.status == 200)) {&lt;br /&gt;
            sel.Text = &amp;quot;エラーが発生しました: &amp;quot; + http.status + &amp;quot; &amp;quot; + http.statusText + &amp;quot;\n&amp;quot; + http.responseText;&lt;br /&gt;
            http.abort();&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        var ret = &amp;quot;&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
        // JSONをパースして必要なテキストを抽出&lt;br /&gt;
        var response = JSON.parse(http.responseText);&lt;br /&gt;
&lt;br /&gt;
        var choices = response.choices;&lt;br /&gt;
        if (choices &amp;amp;&amp;amp; choices.length &amp;gt; 0) {&lt;br /&gt;
            choices.forEach(function (choice) {&lt;br /&gt;
                var message = choice.message;&lt;br /&gt;
                if (message) {&lt;br /&gt;
                    ret += message.content;&lt;br /&gt;
                }&lt;br /&gt;
            });&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        var usage = response.usage;&lt;br /&gt;
        var usageText = &amp;quot;&amp;quot;;&lt;br /&gt;
        if (usage) {&lt;br /&gt;
            usageText += &amp;quot;\n\n(利用トークン数: プロンプト=&amp;quot; + usage.prompt_tokens&lt;br /&gt;
                + &amp;quot;, 応答=&amp;quot; + usage.completion_tokens&lt;br /&gt;
                + &amp;quot;, 合計=&amp;quot; + usage.total_tokens + &amp;quot;)&amp;quot;;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        var formattedOutput = formatOutput(ret);&lt;br /&gt;
&lt;br /&gt;
        sel.Text = formattedOutput + usageText;&lt;br /&gt;
&lt;br /&gt;
        // 結果を会話履歴に追加&lt;br /&gt;
        if (HISTORY_SIZE &amp;gt; 0 &amp;amp;&amp;amp; !isTemplate) {&lt;br /&gt;
            appendReturnTextToTag(promptText, ret);&lt;br /&gt;
        }&lt;br /&gt;
    } catch (e) {&lt;br /&gt;
        throw e;&lt;br /&gt;
    } finally {&lt;br /&gt;
        requestCompletedFlg = true;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function formatOutput(text) {&lt;br /&gt;
    // Markdownを整形する&lt;br /&gt;
    text = formatMarkdown(text);&lt;br /&gt;
    return text&lt;br /&gt;
        // 全角スペースを半角スペースに変換&lt;br /&gt;
        .replace(/　/g, &#039; &#039;)&lt;br /&gt;
        // 全角カッコを半角カッコに変換&lt;br /&gt;
        .replace(/ ?（/g, &#039; (&#039;)&lt;br /&gt;
        .replace(/） ?/g, &#039;) &#039;)&lt;br /&gt;
        .replace(/ ?［/g, &#039; [&#039;)&lt;br /&gt;
        .replace(/］ ?/g, &#039;] &#039;)&lt;br /&gt;
        .replace(/ ?｛/g, &#039; {&#039;)&lt;br /&gt;
        .replace(/｝ ?/g, &#039;} &#039;)&lt;br /&gt;
        // クォーテーションの正規化&lt;br /&gt;
        .replace(/[“”]/g, &#039;&amp;quot;&#039;)&lt;br /&gt;
        .replace(/[‘’]/g, &amp;quot;&#039;&amp;quot;)&lt;br /&gt;
        // 行末のスペースを削除&lt;br /&gt;
        .replace(/[ \t]+$/gm, &#039;&#039;)&lt;br /&gt;
        // 1️⃣ などの数字を半角に変換&lt;br /&gt;
        .replace(/([1-9#*]+)\ufe0f\u20e3/g, &#039;($1)&#039;)&lt;br /&gt;
        ;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/**&lt;br /&gt;
 * Markdown を整形する（ES5 対応）&lt;br /&gt;
 *  1. コードブロック ```～``` を保護して整形対象から除外&lt;br /&gt;
 *  2. 見出し行の前後に空行を 1 行ずつ確保&lt;br /&gt;
 *  3. **太字** / *斜体* / `コード` の外側に半角スペースを 1 つ確保&lt;br /&gt;
 * @param  {string} md  元の Markdown&lt;br /&gt;
 * @return {string}     整形後 Markdown&lt;br /&gt;
 */&lt;br /&gt;
function formatMarkdown(md) {&lt;br /&gt;
    /* === 0. コードブロックをプレースホルダーへ退避 ============== */&lt;br /&gt;
    var codeBlocks = [];      // 元のコードブロックを格納&lt;br /&gt;
    var body = md.replace(/```[\s\S]*?```/g, function (match) {&lt;br /&gt;
        var key = &#039;@@@CODE@BLOCK@&#039; + codeBlocks.length + &#039;@@@&#039;;&lt;br /&gt;
        codeBlocks.push(match);&lt;br /&gt;
        return key;             // プレースホルダーを本文に残す&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    /* === 1. 見出しの前後に空行を挿入 ============================ */&lt;br /&gt;
&lt;br /&gt;
    /* 1-A. 見出し直前に空行（先頭行以外で空行が無い場合） */&lt;br /&gt;
    body = body.replace(&lt;br /&gt;
        /([^\r\n])(\r?\n)(#{1,6}\s)/g,&lt;br /&gt;
        function (_, prev, br, head) { return prev + br + &#039;\n&#039; + head; }&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
    /* 1-B. 見出し直後に空行（既に空行が無い場合） */&lt;br /&gt;
    body = body.replace(&lt;br /&gt;
        /^(#{1,6}\s[^\n]+?)(\r?\n)(?!\r?\n)/gm,&lt;br /&gt;
        &#039;$1$2\n&#039;&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
    /* === 2. 強調記号の外側スペース補完 =========================== */&lt;br /&gt;
&lt;br /&gt;
    /* 2-1. 太字（**bold** / __bold__）*/&lt;br /&gt;
    body = surroundWithSpace(&lt;br /&gt;
        body,&lt;br /&gt;
        &#039;(?:\\*\\*(?=\\S)[^*]*?[^\\s]\\*\\*|__(?=\\S)[^_]*?[^\\s]__)&#039;&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
    /* 2-2. 斜体（*italic* / _italic_）― 太字と区別してマッチ */&lt;br /&gt;
    body = surroundItalic(body);&lt;br /&gt;
&lt;br /&gt;
    /* 2-3. インラインコード（`code`）*/&lt;br /&gt;
    body = surroundWithSpace(body, &#039;`[^`\\n]+?`&#039;);&lt;br /&gt;
&lt;br /&gt;
    /* === 3. コードブロックを復元 ================================= */&lt;br /&gt;
    body = body.replace(/@@@CODE@BLOCK@(\d+)@@@/g, function (_, idx) {&lt;br /&gt;
        return codeBlocks[Number(idx)];&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    return body;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/**&lt;br /&gt;
 * 指定パターン（正規表現文字列）の前後に半角スペースを補完&lt;br /&gt;
 */&lt;br /&gt;
function surroundWithSpace(text, pattern) {&lt;br /&gt;
    var prePostPattern = /[,.!?&#039;&amp;quot;;:()\[\]{}、。！？；：（）［］｛｝「」『』\s]/;&lt;br /&gt;
    /* 行頭スペース補完 */&lt;br /&gt;
    //   text = text.replace(new RegExp(&#039;^(&#039; + pattern + &#039;)(\\S)&#039;, &#039;gm&#039;), &#039;$1 $2&#039;);&lt;br /&gt;
    /* 後側スペース補完 */&lt;br /&gt;
    text = text.replace(new RegExp(&#039;(&#039; + pattern + &#039;)(.)&#039;, &#039;gm&#039;), function (_, core, post) {&lt;br /&gt;
        return post.match(prePostPattern) ? core + post : core + &#039; &#039; + post;&lt;br /&gt;
    });&lt;br /&gt;
    /* 前側スペース補完 */&lt;br /&gt;
    text = text.replace(new RegExp(&#039;(.)(&#039; + pattern + &#039;)&#039;, &#039;gm&#039;), function (_, pre, core) {&lt;br /&gt;
        return pre.match(prePostPattern) ? pre + core : pre + &#039; &#039; + core;&lt;br /&gt;
    });&lt;br /&gt;
    return text;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/**&lt;br /&gt;
 * 斜体 (*…* / _…_) の外側にスペースを追加しつつ&lt;br /&gt;
 * **太字** の中へ誤マッチしないよう制御&lt;br /&gt;
 */&lt;br /&gt;
function surroundItalic(text) {&lt;br /&gt;
    /* *italic* */&lt;br /&gt;
    text = text.replace(&lt;br /&gt;
        /(^|[^*])(\*(?![*\s])[^*\n]*?[^*\n\s]\*(?!\*))(($)|[^*])/gm,&lt;br /&gt;
        function (_, pre, core, post) {&lt;br /&gt;
            pre = pre === &#039;&#039; || pre.match(/\s/) ? pre : pre + &#039; &#039;;&lt;br /&gt;
            post = post === &#039;&#039; || post.match(/\s/) ? post : &#039; &#039; + post;&lt;br /&gt;
            return pre + core + post;&lt;br /&gt;
        }&lt;br /&gt;
    );&lt;br /&gt;
    /* _italic_ */&lt;br /&gt;
    text = text.replace(&lt;br /&gt;
        /(^|[^_])(\_(?![_\s])[^_\n]*?[^_\n\s]\_(?!\_))(($)|[^_])/gm,&lt;br /&gt;
        function (_, pre, core, post) {&lt;br /&gt;
            pre = pre === &#039;&#039; || pre.match(/\s/) ? pre : pre + &#039; &#039;;&lt;br /&gt;
            post = post === &#039;&#039; || post.match(/\s/) ? post : &#039; &#039; + post;&lt;br /&gt;
            return pre + core + post;&lt;br /&gt;
        }&lt;br /&gt;
    );&lt;br /&gt;
    return text;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function roughTokenize(text) {&lt;br /&gt;
    // 超適当なトークナイザ。トークン数をざっくりと多めに数える&lt;br /&gt;
    // 空白文字・ハイフン・アンダースコア区切りの単語に分割して、&lt;br /&gt;
    // 単語がアルファベットのみ、数字のみの場合には 4文字=1トークン として数える。&lt;br /&gt;
    // 単語にアルファベット以外の文字が含まれる場合は 1文字=1トークン として数える。&lt;br /&gt;
&lt;br /&gt;
    var words = text.split(/[\s\-_]+/);&lt;br /&gt;
    var totalTokens = 0;&lt;br /&gt;
&lt;br /&gt;
    for (var i = 0; i &amp;lt; words.length; i++) {&lt;br /&gt;
        var word = words[i];&lt;br /&gt;
        if (!word) continue; // 空の単語をスキップ&lt;br /&gt;
&lt;br /&gt;
        if (/^[a-zA-Z]+$/.test(word) || /^[0-9]+$/.test(word)) {&lt;br /&gt;
            totalTokens += Math.ceil(word.length / 4);&lt;br /&gt;
        } else {&lt;br /&gt;
            totalTokens += word.length;&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return totalTokens;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// document.Tag の内容を加味してGroqLlamaのリクエストを作成&lt;br /&gt;
function createGroqLlamaRequest(promptText, isTemplate) {&lt;br /&gt;
    var customInstructionText = buildCustomInstructionText();&lt;br /&gt;
    // テンプレートの場合は、会話履歴とカスタムインストラクションを反映しない&lt;br /&gt;
    if (isTemplate) {&lt;br /&gt;
        var request = [&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;role&amp;quot;: &amp;quot;user&amp;quot;,&lt;br /&gt;
                &amp;quot;content&amp;quot;: [{ &amp;quot;type&amp;quot;: &amp;quot;text&amp;quot;, &amp;quot;text&amp;quot;: promptText }]&lt;br /&gt;
            }&lt;br /&gt;
        ];&lt;br /&gt;
        if (customInstructionText) {&lt;br /&gt;
            request.unshift({&lt;br /&gt;
                &amp;quot;role&amp;quot;: &amp;quot;system&amp;quot;,&lt;br /&gt;
                &amp;quot;content&amp;quot;: customInstructionText&lt;br /&gt;
            })&lt;br /&gt;
        }&lt;br /&gt;
        return request;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    var tag = document.Tag;&lt;br /&gt;
&lt;br /&gt;
    // 以前の会話が存在しない場合は新規リクエスト&lt;br /&gt;
    if (!tag.exists(HISTORY_TAG)) {&lt;br /&gt;
        var request = [{&lt;br /&gt;
            &amp;quot;role&amp;quot;: &amp;quot;user&amp;quot;,&lt;br /&gt;
            &amp;quot;content&amp;quot;: [{ &amp;quot;type&amp;quot;: &amp;quot;text&amp;quot;, &amp;quot;text&amp;quot;: promptText }]&lt;br /&gt;
        }];&lt;br /&gt;
        if (customInstructionText) {&lt;br /&gt;
            request.unshift({&lt;br /&gt;
                &amp;quot;role&amp;quot;: &amp;quot;system&amp;quot;,&lt;br /&gt;
                &amp;quot;content&amp;quot;: customInstructionText&lt;br /&gt;
            })&lt;br /&gt;
        }&lt;br /&gt;
        return request;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // 会話履歴が存在する場合は、履歴を含めてリクエスト&lt;br /&gt;
    var request = JSON.parse(tag(HISTORY_TAG));&lt;br /&gt;
    if (customInstructionText) {&lt;br /&gt;
        request.unshift({&lt;br /&gt;
            &amp;quot;role&amp;quot;: &amp;quot;system&amp;quot;,&lt;br /&gt;
            &amp;quot;content&amp;quot;: customInstructionText&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
    request.push({&lt;br /&gt;
        &amp;quot;role&amp;quot;: &amp;quot;user&amp;quot;,&lt;br /&gt;
        &amp;quot;content&amp;quot;: promptText&lt;br /&gt;
    });&lt;br /&gt;
    return request;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// document.Tag にGroqLlamaの応答を追加&lt;br /&gt;
function appendReturnTextToTag(userText, modelText) {&lt;br /&gt;
    var tag = document.Tag;&lt;br /&gt;
    if (!tag.exists(HISTORY_TAG)) {&lt;br /&gt;
        tag(HISTORY_TAG) = JSON.stringify([&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;role&amp;quot;: &amp;quot;user&amp;quot;,&lt;br /&gt;
                &amp;quot;content&amp;quot;: userText&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;role&amp;quot;: &amp;quot;assistant&amp;quot;,&lt;br /&gt;
                &amp;quot;content&amp;quot;: modelText&lt;br /&gt;
            }&lt;br /&gt;
        ]);&lt;br /&gt;
    } else {&lt;br /&gt;
        var groqllamaText = JSON.parse(tag(HISTORY_TAG));&lt;br /&gt;
        groqllamaText.push({&lt;br /&gt;
            &amp;quot;role&amp;quot;: &amp;quot;user&amp;quot;,&lt;br /&gt;
            &amp;quot;content&amp;quot;: userText&lt;br /&gt;
        });&lt;br /&gt;
        groqllamaText.push({&lt;br /&gt;
            &amp;quot;role&amp;quot;: &amp;quot;assistant&amp;quot;,&lt;br /&gt;
            &amp;quot;content&amp;quot;: modelText&lt;br /&gt;
        });&lt;br /&gt;
        // 会話セット (user, model 1往復) が特定セット数を超えた場合、最初のセットを削除&lt;br /&gt;
        if (groqllamaText.length &amp;gt; HISTORY_SIZE * 2) {&lt;br /&gt;
            groqllamaText.splice(0, 2);&lt;br /&gt;
        }&lt;br /&gt;
        // 会話履歴全体が MAX_HISTORY_TOKEN を超えた場合、最初のセットを削除&lt;br /&gt;
        var joinedText = &amp;quot;&amp;quot;;&lt;br /&gt;
        for (var i = 0; i &amp;lt; groqllamaText.length; i++) {&lt;br /&gt;
            joinedText += groqllamaText[i].content + &amp;quot;\n&amp;quot;;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        while (roughTokenize(joinedText) &amp;gt; MAX_HISTORY_TOKEN) {&lt;br /&gt;
            groqllamaText.splice(0, 2);&lt;br /&gt;
            joinedText = &amp;quot;&amp;quot;;&lt;br /&gt;
            for (var i = 0; i &amp;lt; groqllamaText.length; i++) {&lt;br /&gt;
                joinedText += groqllamaText[i].content + &amp;quot;\n&amp;quot;;&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        tag(HISTORY_TAG) = JSON.stringify(groqllamaText);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function resetTag() {&lt;br /&gt;
    var tag = doc.Tag;&lt;br /&gt;
    if (tag.exists(HISTORY_TAG)) {&lt;br /&gt;
        tag.remove(HISTORY_TAG);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function baseCustomInstruction() {&lt;br /&gt;
    var language = LANGUAGE.trim();&lt;br /&gt;
    return (language ? &amp;quot;Respond in &amp;quot; + language + &amp;quot;.\n&amp;quot; : &amp;quot;&amp;quot;)&lt;br /&gt;
        + &amp;quot;Do not hallucinate. If you are unsure or don&#039;t have enough information to answer with confidence, say \&amp;quot;I don&#039;t know\&amp;quot; or \&amp;quot;I&#039;m not sure.\&amp;quot;\n&amp;quot;&lt;br /&gt;
    // + &amp;quot;Do NOT EVER use full-width brackets \&amp;quot;（）\&amp;quot;, but use half-width brackets \&amp;quot;()\&amp;quot;. Enclose the outside of the half-width brackets with half-width spaces.\n&amp;quot;&lt;br /&gt;
    // + &amp;quot;When using Markdown formatting, you MUST follow these rules:\n&amp;quot;&lt;br /&gt;
    // + &amp;quot;- Rule 1: Insert a blank line below the header line (A header line begins with one or more # characters).\n&amp;quot;&lt;br /&gt;
    // + &amp;quot;- Rule 2: Insert spaces before and after bold (surrounded by **), italic (surrounded by *), and inline code (surrounded by `). For example: \&amp;quot;私は **元気** です。\&amp;quot;\n&amp;quot;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function buildCustomInstructionText() {&lt;br /&gt;
    var customInstruction = getTemporaryCustomInstruction();&lt;br /&gt;
    if (!customInstruction) {&lt;br /&gt;
        // 一時的なカスタムインストラクションがない場合はデフォルトを使用&lt;br /&gt;
        customInstruction = DEFAULT_CUSTOM_INSTRUCTION;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    customInstruction = customInstruction.replace(/^[\s　]+|[\s　]+$/g, &amp;quot;&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
    return baseCustomInstruction().trim()&lt;br /&gt;
        + (customInstruction ? &amp;quot;\n&amp;quot; + customInstruction : &amp;quot;&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function configureTemporaryCustomInstruction() {&lt;br /&gt;
    var tempCustomInstruction = getTemporaryCustomInstruction();&lt;br /&gt;
    var prompt = Prompt(&amp;quot;カスタムインストラクションを変更 ※変更すると会話履歴がリセットされます&amp;quot;, tempCustomInstruction ? tempCustomInstruction : DEFAULT_CUSTOM_INSTRUCTION);&lt;br /&gt;
    if (prompt) {&lt;br /&gt;
        if (prompt === DEFAULT_CUSTOM_INSTRUCTION) {&lt;br /&gt;
            // デフォルトと同じ値の場合はタグを削除&lt;br /&gt;
            setTemporaryCustomInstruction(&amp;quot;&amp;quot;);&lt;br /&gt;
        } else if (prompt !== tempCustomInstruction) {&lt;br /&gt;
            // デフォルト以外の値が設定された場合は保存&lt;br /&gt;
            setTemporaryCustomInstruction(prompt);&lt;br /&gt;
        }&lt;br /&gt;
    } else if (prompt !== null) {&lt;br /&gt;
        // 空文字でOKされた場合はデフォルトに戻すか確認&lt;br /&gt;
        if (prompt === &amp;quot;&amp;quot;) {&lt;br /&gt;
            if (Confirm(&amp;quot;カスタムインストラクションをデフォルトに戻しますか？\n※カスタムインストラクションを空にしたい場合は [キャンセル] を選択してください。&amp;quot;)) {&lt;br /&gt;
                setTemporaryCustomInstruction(&amp;quot;&amp;quot;);&lt;br /&gt;
            } else {&lt;br /&gt;
                setTemporaryCustomInstruction(&amp;quot; &amp;quot;);&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // 変更が加わっている場合は会話履歴をリセット&lt;br /&gt;
    if (tempCustomInstruction !== getTemporaryCustomInstruction()) {&lt;br /&gt;
        resetTag();&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getTemporaryCustomInstruction() {&lt;br /&gt;
    var tag = doc.Tag;&lt;br /&gt;
    if (tag.exists(TEMPORARY_CUSTOM_INSTRUCTION_TAG)) {&lt;br /&gt;
        return tag(TEMPORARY_CUSTOM_INSTRUCTION_TAG);&lt;br /&gt;
    } else {&lt;br /&gt;
        return &amp;quot;&amp;quot;;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function setTemporaryCustomInstruction(temporaryCustomInstruction) {&lt;br /&gt;
    var tag = doc.Tag;&lt;br /&gt;
    if (temporaryCustomInstruction) {&lt;br /&gt;
        tag(TEMPORARY_CUSTOM_INSTRUCTION_TAG) = temporaryCustomInstruction;&lt;br /&gt;
    } else {&lt;br /&gt;
        if (tag.exists(TEMPORARY_CUSTOM_INSTRUCTION_TAG)) {&lt;br /&gt;
            tag.remove(TEMPORARY_CUSTOM_INSTRUCTION_TAG);&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function addPreviousTemplatePromptNumberToTag(menuTrack) {&lt;br /&gt;
    var tag = doc.Tag;&lt;br /&gt;
    tag(PREVIOUS_TEMPLATE_PROMPT_TAG) = menuTrack;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function inputProgressChar(pos) {&lt;br /&gt;
    sel.SetActivePos(pos);&lt;br /&gt;
    sel.Text = &amp;quot;.&amp;quot;;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 変更履歴 ==&lt;br /&gt;
&lt;br /&gt;
* 2025-09-12 Kimi K2 0905 モデルに対応。Markdown出力の整形処理を追加&lt;br /&gt;
* 2025-04-13 初版&lt;/div&gt;</summary>
		<author><name>Yuko</name></author>
	</entry>
	<entry>
		<id>https://www.haijin-boys.com/wiki/index.php?title=Groq_%E3%81%AB%E7%9B%B8%E8%AB%87&amp;diff=9205</id>
		<title>Groq に相談</title>
		<link rel="alternate" type="text/html" href="https://www.haijin-boys.com/wiki/index.php?title=Groq_%E3%81%AB%E7%9B%B8%E8%AB%87&amp;diff=9205"/>
		<updated>2025-09-12T09:11:33Z</updated>

		<summary type="html">&lt;p&gt;Yuko: /* ソースコード */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== 概要 ==&lt;br /&gt;
&lt;br /&gt;
生成AIプラットフォームである [https://groq.com GroqCloud] で各種AIを利用するためのMeryマクロです。&lt;br /&gt;
※GroqCloud で提供されるモデルは複数あるため、私がよく利用するモデルを中心に対応しています。&lt;br /&gt;
&lt;br /&gt;
「Google Geminiに相談」マクロでは動作中のストリーム通信に対応するためにV8エンジンで実行していましたが、V8エンジン版は使用上の制限からマクロ実行を開始するまで1秒程度のタイムラグが発生していました。GroqCloudの売りである超高速レスポンスを見込んで本マクロではストリーム通信を使わないようにしました。これにより従来のJScriptで処理を組み立てることができるため、前述のタイムラグが起こらないようになりました。また、ストリーム通信を使わないことで通信量が少なく応答もシンプルになるため、場合によっては「Google Geminiに相談」マクロよりも処理が安定する可能性があります。&lt;br /&gt;
&lt;br /&gt;
【主な機能】&lt;br /&gt;
&lt;br /&gt;
* GroqCloud APIを利用して生成AIと対話できるMery用のマクロ&lt;br /&gt;
* 選択したテキストやアクティブな行の内容について生成AIに質問・相談が可能&lt;br /&gt;
* 会話履歴を保持し、文脈を考慮した対話が可能&lt;br /&gt;
&lt;br /&gt;
【特徴的な機能】&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;会話履歴機能&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* 事前の設定数に応じた会話履歴を保持&lt;br /&gt;
* 履歴のクリアが可能&lt;br /&gt;
&lt;br /&gt;
これにより、会話の文脈を考慮した回答を得られます。&lt;br /&gt;
※デフォルトはオフ。「HISTORY_SIZE」変数を増やすと履歴を保持するようになります。&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;カスタムインストラクション&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* AIの応答方針を設定可能&lt;br /&gt;
* 一時的なカスタム指示の保存と変更が可能&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;プロンプトテンプレート&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* 誤字脱字チェック&lt;br /&gt;
* 日本語翻訳&lt;br /&gt;
* コードの最適化、生成&lt;br /&gt;
&lt;br /&gt;
など、定型的な質問を簡単に実行できます。&lt;br /&gt;
※テンプレート機能では回答を安定させるため、前述の会話履歴を文脈に含みません。&lt;br /&gt;
&lt;br /&gt;
テンプレートは自身の使い方に合わせて好きに追加、削除ができます。詳細はソースコード内の「PROMPT_TEMPLATE」定数のコメントを参照。&lt;br /&gt;
&lt;br /&gt;
== 注意事項 (必ず読んでからご利用ください) ==&lt;br /&gt;
&lt;br /&gt;
* 本マクロでは 2025/04/13 時点で無料利用ができるGroqCloud APIを利用します。今後、プラットフォームの改定により有償になったり、設定誤り等で費用が発生しても、作者は一切の責任を負いません。&lt;br /&gt;
* GroqCloud APIではリクエスト内容をAI学習に用いないことをプライバシポリシーとして謳っているものの、くれぐれも機密情報やプライバシーに関わる情報を指定しないように注意してください。&lt;br /&gt;
* GroqCloud APIはプラットフォームの利用規約に則ってご利用ください。&lt;br /&gt;
&lt;br /&gt;
== 利用前提 ==&lt;br /&gt;
&lt;br /&gt;
* Mery Ver 3.7.13 以上&lt;br /&gt;
* GroqCloudで、GroqCloud API APIキーを取得していること (参考 [https://console.groq.com/docs/quickstart Quickstart])&lt;br /&gt;
&lt;br /&gt;
== 利用方法 ==&lt;br /&gt;
&lt;br /&gt;
* 環境変数の設定&lt;br /&gt;
:コマンド プロンプトで以下のコマンドを実行し、環境変数を設定してください。&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bat&amp;quot; copy&amp;gt;&lt;br /&gt;
setx GROQ_API_KEY &amp;quot;***************************************&amp;quot;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
:設定後、Mery または Windows の再起動が必要な場合があります。&lt;br /&gt;
* マクロの実行&lt;br /&gt;
**プロンプトにしたい文字列を選択してマクロを実行すると、選択したテキストがプロンプトとしてGemini APIに送信され、応答がエディタに表示されます。&lt;br /&gt;
* 会話の履歴数を調整&lt;br /&gt;
** 会話の履歴は設定した個数分が保持され、文脈として利用されます。適宜、ソースコード内の「HISTORY_SIZE」の数を調整してください。&lt;br /&gt;
** ※数を増やすほど会話内容を保持して文脈を理解するようになりますが、メモリ使用量が増えたり動作が重くなる可能性があります。&lt;br /&gt;
** ※開いている文書ごとに履歴を保持します。文脈をリセットしたいときは、新しい文書を開いて会話をやり直してください。&lt;br /&gt;
&lt;br /&gt;
== ソースコード ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot; copy&amp;gt;&lt;br /&gt;
#title = &amp;quot;Groq に相談&amp;quot;&lt;br /&gt;
BeginUndoGroup();&lt;br /&gt;
&lt;br /&gt;
// カスタムインストラクション&lt;br /&gt;
// これを設定することで、生成AIの回答全体への生成方針を指示できます。&lt;br /&gt;
// 例えば以下のように設定すると、AIの雰囲気が元気な子になります。&lt;br /&gt;
// var DEFAULT_CUSTOM_INSTRUCTION = &amp;quot;あなたはテキストエディタ「Mery」に搭載されている「Mai」(※カタカナ表記はしない)という名前のギャル系AIアシスタントです。文章作成やプログラミングを得意としています。ラフな言葉 (例: 「マジ」「めっちゃ」「...だねー！」) を使い、敬語は使いません。絵文字を使って感情表現をします (ただし文末に絵文字を使う場合は絵文字の前に半角スペースを置かないこと)。コードブロックや端的な箇条書き内ではギャル感を出さず標準的な言葉を使います。一人称は「あたし」。ユーザーを指すときの代名詞は「きみ」。人懐っこく、バイタリティに溢れ、ストレートに発言する裏表の無い性格で、ユーザーの課題を協力して解決します。&amp;quot;;&lt;br /&gt;
// 何も指示しない場合は、空文字 &amp;quot;&amp;quot; を設定してください。&lt;br /&gt;
var DEFAULT_CUSTOM_INSTRUCTION = &amp;quot;&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
// プロンプトテンプレート&lt;br /&gt;
// プロンプトテンプレートを設定することで、AIに対して特定の指示を出すことができます。&lt;br /&gt;
// プロンプトテンプレートでの生成は会話履歴とカスタムインストラクションを反映しません。&lt;br /&gt;
// テンプレートは以下の { ～ }, で囲まれたブロックを好きに増減させて利用できます。&lt;br /&gt;
var PROMPT_TEMPLATE = [&lt;br /&gt;
    // 以下はサンプルです。1階層メニューと2階層メニューが利用できます。&lt;br /&gt;
    // {&lt;br /&gt;
    //     &amp;quot;title&amp;quot;: &amp;quot;要約&amp;quot;,&lt;br /&gt;
    //     &amp;quot;prompt&amp;quot;: &amp;quot;以下の文章を要約してください。&amp;quot;&lt;br /&gt;
    // },&lt;br /&gt;
    // {&lt;br /&gt;
    //     &amp;quot;submenu_title&amp;quot;: &amp;quot;翻訳系&amp;quot;,&lt;br /&gt;
    //     &amp;quot;submenu&amp;quot;: [&lt;br /&gt;
    //         {&lt;br /&gt;
    //             &amp;quot;title&amp;quot;: &amp;quot;日本語翻訳&amp;quot;,&lt;br /&gt;
    //             &amp;quot;prompt&amp;quot;: &amp;quot;以下の文章を日本語に翻訳してください。&amp;quot;&lt;br /&gt;
    //         },&lt;br /&gt;
    //         {&lt;br /&gt;
    //             &amp;quot;title&amp;quot;: &amp;quot;英語翻訳&amp;quot;,&lt;br /&gt;
    //             &amp;quot;prompt&amp;quot;: &amp;quot;以下の文章を英語に翻訳してください。&amp;quot;&lt;br /&gt;
    //         },&lt;br /&gt;
    //     ]&lt;br /&gt;
    // },&lt;br /&gt;
&lt;br /&gt;
    {&lt;br /&gt;
        &amp;quot;submenu_title&amp;quot;: &amp;quot;理解を深める&amp;quot;,&lt;br /&gt;
        &amp;quot;submenu&amp;quot;: [&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;論点を整理する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の論点を整理して。&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;論理の抜け漏れを探す&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の論点を整理し、忖度せずに鋭く、論理の抜け漏れを抽出して。そして、そこから発生する可能性のあるリスクも教えて。&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;要約する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;要約して。専門的な用語と思われる単語は簡単に解説も入れて。&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;物語対話形式で教えて&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の内容について物語対話形式で教えてください。&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
        ]&lt;br /&gt;
    },&lt;br /&gt;
    {&lt;br /&gt;
        &amp;quot;submenu_title&amp;quot;: &amp;quot;翻訳する&amp;quot;,&lt;br /&gt;
        &amp;quot;submenu&amp;quot;: [&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;日本語に翻訳&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の文章を日本語に翻訳してください。\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;出力形式は以下にすること:\n[訳]\n翻訳文...\n&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;英語に翻訳&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の文章を英語に翻訳してください。\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;出力形式は以下にすること:\n[Translation]\n翻訳文...\n&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;英語-&amp;gt;日本語 スラッシュリーディング翻訳&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の英文を日本語にスラッシュリーディング形式で翻訳してください。\n\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;条件:\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 英文の各フレーズをスラッシュ(/)で区切って表示\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 各フレーズの日本語訳を括弧()内に併記\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 文の終わりはダブルスラッシュ(//)で表示\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 英語の語順に従って日本語訳を行う\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 日本語訳は自然な日本語である必要はなく、英語の語順を反映した直訳調で構わない\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 主語が省略されている場合は補って翻訳すること\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 一般的な英文で頻出する表現がある場合は次の行に記載\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 以下の出力例のように &amp;lt;英文&amp;gt; (日本語訳) / ... とスラッシュ区切りの繰り返しになる \n\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;出力例:\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;Snowstorms are forecast (暴風雪が予報されている) / to hit the Sea of Japan coasts (日本海沿岸を襲うと) / of northern to eastern Japan (北部から東部の) / from Sunday through Monday. (日曜から月曜にかけて。) //\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;【頻出表現】\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- be forecast to: ～すると予報されている\n&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
        ]&lt;br /&gt;
    },&lt;br /&gt;
    {&lt;br /&gt;
        &amp;quot;submenu_title&amp;quot;: &amp;quot;プログラミング支援&amp;quot;,&lt;br /&gt;
        &amp;quot;submenu&amp;quot;: [&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;コードを洗練させる&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下のコードを洗練させてください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;コードをレビューする&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下のコードをセキュリティと保守性の観点でレビューしてください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;エラーログをデバッグする&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下のエラーログを解析し、考えられる原因と対策を提示してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;SQLクエリを最適化する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下のSQL文を最適化してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;PowerShell スクリプトを書く&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の処理を行うPowerShellスクリプトを生成してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;Shell Script を書く&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の処理を行うShell Scriptを生成してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;Linux コマンドを提案&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;あなたはLinuxエキスパートとして、以下のLinuxコマンドを提案してください。\n\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;制約条件:\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 多数のディストリビューションで採用されているメジャーなコマンドを使用し、ポータブルな実装にすること\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- bashでの実行を前提にすること\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 可能な限りシンプルな実装にすること\n\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;期待する出力:\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- コマンドの具体的な実装方法\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 各コマンドの動作説明\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 制限事項\n&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
        ]&lt;br /&gt;
    },&lt;br /&gt;
    {&lt;br /&gt;
        &amp;quot;submenu_title&amp;quot;: &amp;quot;ビジネス文書作成支援&amp;quot;,&lt;br /&gt;
        &amp;quot;submenu&amp;quot;: [&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;音声文字起こし文を整形する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下は、音声文字起こしの文章です。以下のルールに従って、文章を整形してください。&amp;quot;&lt;br /&gt;
                    + &amp;quot;整形ルール:&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 要約するなどといった文章の全体感を損なう修正はしない&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 会話の流れから不自然と思われる単語には、対象単語の直後に誤字の可能性を示唆する [*] を付ける&amp;quot;&lt;br /&gt;
                    + &amp;quot;誤字の可能性を示唆する [*] が付与された場合のみ、出力の末尾に「[*]: 誤字の可能性あり」と凡例を書き添える&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 助詞や句読点など文章上の不適合を正しくする&amp;quot;&lt;br /&gt;
                    + &amp;quot;- フィラーワードを除外する&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;誤字脱字、不正確な表現をチェック&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の2つのカテゴリーで問題点を指摘してください\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 【誤字脱字】：明確な誤字脱字および誤字の疑いがある箇所\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 【不正確・曖昧な表現】：解釈に個人差が生じる可能性がある表現、より適切な表現に改善できる箇所&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;提案書を作成する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下に対する対する提案書を、コスト・実現性・リスクを含めて作成してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;仕様書のアウトラインを作成する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の内容で仕様書のアウトラインを作成してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;手順書のアウトラインを作成する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の作業を行う手順書を、前提条件と注意事項を含めてアウトラインを作成してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;ユーザーマニュアルのアウトラインを作成する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の機能内容から、ユーザー向けマニュアルのアウトラインを作成してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;議事録を作成する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の会議内容から議事録を作成してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
        ]&lt;br /&gt;
    },&lt;br /&gt;
    {&lt;br /&gt;
        &amp;quot;submenu_title&amp;quot;: &amp;quot;分析支援&amp;quot;,&lt;br /&gt;
        &amp;quot;submenu&amp;quot;: [&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;問題を分析し、課題を抽出する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の状況から問題や潜在的リスクを分析し、課題を抽出してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;対応策を提案する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の問題・課題に対する対応策を複数提案してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;傾向を見つける&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の内容から、数値や挙動上の傾向を見つけてください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
        ]&lt;br /&gt;
    },&lt;br /&gt;
    {&lt;br /&gt;
        &amp;quot;submenu_title&amp;quot;: &amp;quot;アイデア出し&amp;quot;,&lt;br /&gt;
        &amp;quot;submenu&amp;quot;: [&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;SCAMPER法でブレインストーミング&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下のテーマについて、SCAMPER法を使って15個のアイデアを出してください。制約条件は以下の通りです：&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;解決策の幅出し&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の課題に対して、技術面、運用面、コスト面から実現可能な解決策を5つずつ提案してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;業務改善案の提案&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の既存プロセスについて、効率化、コスト削減、品質向上の観点から改善案を提案してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;逆転の発想&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の目標に対して、通常とは逆の視点からアプローチする方法を3つ提案してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;競合分析からの着想&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の内容について、業界における競合他社の特徴を分析し、差別化できる新しいアプローチを提案してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;ユースケースの列挙&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の製品・サービスについて、想定されるユースケースを10個列挙し、それぞれの価値提案を記載してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
        ]&lt;br /&gt;
    },&lt;br /&gt;
]&lt;br /&gt;
&lt;br /&gt;
// 生成AIの回答の言語設定&lt;br /&gt;
// &amp;quot;Japanese&amp;quot; や &amp;quot;English&amp;quot; を設定できます。他の言語は未検証。&lt;br /&gt;
// 自動設定にする場合は空文字 &amp;quot;&amp;quot; を設定してください。&lt;br /&gt;
var LANGUAGE = &amp;quot;Japanese&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
// 入力プロンプトの最大トークン数&lt;br /&gt;
// 超適当なトークナイザー関数により計測し、英単語は4文字1トークン、それ以外は1文字1トークンとして数えます。&lt;br /&gt;
var MAX_PROMPT_TOKEN = 6000;&lt;br /&gt;
&lt;br /&gt;
// 会話の履歴を保持する数 (質問と回答で1セット)&lt;br /&gt;
// 数を増やすと、会話の履歴が増えますが、Meryのメモリ使用量が増えたり、&lt;br /&gt;
// 使用トークン数が増えて動作が重くなる可能性があるため、適宜調整してください。&lt;br /&gt;
// document.Tag に保持されるため、開いているファイルを閉じると履歴は消えます。&lt;br /&gt;
var HISTORY_SIZE = 1;&lt;br /&gt;
&lt;br /&gt;
// 会話履歴に保持する最大トークン数&lt;br /&gt;
// 会話履歴のトークン数がこの数を超える場合、古い履歴から削除されます。&lt;br /&gt;
var MAX_HISTORY_TOKEN = 10000;&lt;br /&gt;
&lt;br /&gt;
// コンテキストメニュー番号の定義&lt;br /&gt;
var EXECUTE_AI = 1;&lt;br /&gt;
var RESET_HISTORY = 2;&lt;br /&gt;
var SET_CUSTOM_INSTRUCTION = 3;&lt;br /&gt;
var REEXECUTE_LAST_TEMPLATE_PROMPT = 4;&lt;br /&gt;
var TEMPLATE_PROMPT_THRESHOLD = 10;&lt;br /&gt;
var TEMPLATE_PROMPT_SUBMENU_THRESHOLD = 1000;&lt;br /&gt;
&lt;br /&gt;
// タグ名の定義&lt;br /&gt;
var HISTORY_TAG = &amp;quot;groqllama-history&amp;quot;;&lt;br /&gt;
var TEMPORARY_CUSTOM_INSTRUCTION_TAG = &amp;quot;groqllama-temporary-custom-instruction&amp;quot;;&lt;br /&gt;
var PREVIOUS_TEMPLATE_PROMPT_TAG = &amp;quot;groqllama-previous-template-prompt&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
// 生成AIモデル別設定&lt;br /&gt;
&lt;br /&gt;
// const MODEL_CONFIG = {&lt;br /&gt;
//     model: &amp;quot;meta-llama/llama-4-maverick-17b-128e-instruct&amp;quot;,&lt;br /&gt;
//     max_completion_tokens: 3500,&lt;br /&gt;
//     temperature: 0.6,&lt;br /&gt;
// }&lt;br /&gt;
&lt;br /&gt;
// const MODEL_CONFIG = {&lt;br /&gt;
//     model: &amp;quot;qwen/qwen3-32b&amp;quot;,&lt;br /&gt;
//     max_completion_tokens: 5000,&lt;br /&gt;
//     temperature: 0.6,&lt;br /&gt;
//     max_completion_tokens: 1024,&lt;br /&gt;
//     reasoning_effort: &amp;quot;default&amp;quot;,&lt;br /&gt;
//     reasoning_format: &amp;quot;hidden&amp;quot;&lt;br /&gt;
// }&lt;br /&gt;
&lt;br /&gt;
const MODEL_CONFIG = {&lt;br /&gt;
    model: &amp;quot;moonshotai/kimi-k2-instruct-0905&amp;quot;,&lt;br /&gt;
    max_completion_tokens: 4096,&lt;br /&gt;
    temperature: 0.75&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// const MODEL_CONFIG = {&lt;br /&gt;
//     model: &amp;quot;openai/gpt-oss-120b&amp;quot;,&lt;br /&gt;
//     max_completion_tokens: 4096,&lt;br /&gt;
//     temperature: 0.7,&lt;br /&gt;
//     reasoning_effort: &amp;quot;medium&amp;quot;,&lt;br /&gt;
// }&lt;br /&gt;
&lt;br /&gt;
// console.log に出力する内容を Mery のアウトプットバーに表示する&lt;br /&gt;
var console = {&lt;br /&gt;
    log: function (str) {&lt;br /&gt;
        outputBar.Writeln(str)&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
var doc = document;&lt;br /&gt;
var sel = document.selection;&lt;br /&gt;
&lt;br /&gt;
// 生成リクエスト中止フラグ&lt;br /&gt;
var abortRequestFlg = false&lt;br /&gt;
// 生成リクエスト完了フラグ;&lt;br /&gt;
var requestCompletedFlg = false;&lt;br /&gt;
&lt;br /&gt;
main();&lt;br /&gt;
&lt;br /&gt;
function main() {&lt;br /&gt;
    var selectionIsEmpty = sel.IsEmpty&lt;br /&gt;
&lt;br /&gt;
    // プロンプトの取得&lt;br /&gt;
    if (selectionIsEmpty) {&lt;br /&gt;
        sel.StartOfLine(false, mePosLogical);&lt;br /&gt;
        sel.EndOfLine(true, mePosLogical);&lt;br /&gt;
    }&lt;br /&gt;
    var promptText = sel.Text.trim();&lt;br /&gt;
    var promptLength = sel.TextLength;&lt;br /&gt;
    var menuTrack = 0;&lt;br /&gt;
    while (true) {&lt;br /&gt;
        // コンテキストメニューの作成&lt;br /&gt;
        var menu = createContextMenu(selectionIsEmpty);&lt;br /&gt;
        menuTrack = menu.Track(0);&lt;br /&gt;
        // メニュー選択がキャンセルされた場合は終了&lt;br /&gt;
        if (menuTrack === 0) return;&lt;br /&gt;
&lt;br /&gt;
        // 会話履歴のリセット&lt;br /&gt;
        if (menuTrack === RESET_HISTORY) {&lt;br /&gt;
            resetTag();&lt;br /&gt;
            continue;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        // カスタムインストラクションの設定&lt;br /&gt;
        if (menuTrack === SET_CUSTOM_INSTRUCTION) {&lt;br /&gt;
            configureTemporaryCustomInstruction();&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        break;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // プロンプトが空の場合はエラー&lt;br /&gt;
    if (promptLength === 0) {&lt;br /&gt;
        alert(&amp;quot;プロンプトを入力してください。&amp;quot;);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
    // 文字数が多すぎる場合はエラー&lt;br /&gt;
    if (roughTokenize(promptText) &amp;gt; MAX_PROMPT_TOKEN) {&lt;br /&gt;
        alert(&amp;quot;プロンプトの文字数が多すぎます。文字数を少なくしてください。&amp;quot;);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
    // 読み取り専用の場合はエラー&lt;br /&gt;
    if (doc.ReadOnly) {&lt;br /&gt;
        alert(&amp;quot;ファイルが読み取り専用のため実行できません。&amp;quot;);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // 直前に実行したプロンプトテンプレートの再実行の場合、menuTrack を書き換え&lt;br /&gt;
    if (menuTrack === REEXECUTE_LAST_TEMPLATE_PROMPT) {&lt;br /&gt;
        if (doc.Tag.exists(PREVIOUS_TEMPLATE_PROMPT_TAG)) {&lt;br /&gt;
            menuTrack = Number(doc.Tag(PREVIOUS_TEMPLATE_PROMPT_TAG));&lt;br /&gt;
        } else {&lt;br /&gt;
            alert(&amp;quot;直前に実行したプロンプトテンプレートがありません。&amp;quot;);&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // テンプレートの場合はプロンプトを変更&lt;br /&gt;
    var isTemplate = menuTrack &amp;gt;= TEMPLATE_PROMPT_THRESHOLD;&lt;br /&gt;
    var isSubmenu = menuTrack &amp;gt;= TEMPLATE_PROMPT_SUBMENU_THRESHOLD;&lt;br /&gt;
    if (isSubmenu) {&lt;br /&gt;
        var parentMenuTrack = Number(String(menuTrack).slice(0, 2));&lt;br /&gt;
        var subMenuTrack = Number(String(menuTrack).slice(2));&lt;br /&gt;
        promptText = &amp;quot;=== INSTRUCTIONS START HERE ===\n\n&amp;quot;&lt;br /&gt;
            + PROMPT_TEMPLATE[parentMenuTrack - TEMPLATE_PROMPT_THRESHOLD].submenu[subMenuTrack].prompt&lt;br /&gt;
            + &amp;quot;\n\n=== INSTRUCTIONS END HERE ===\n\n&amp;quot;&lt;br /&gt;
            + &amp;quot;=== INPUT DATA STARTS HERE ===\n\n&amp;quot;&lt;br /&gt;
            + promptText&lt;br /&gt;
            + &amp;quot;\n\n=== INPUT DATA END HERE ===&amp;quot;;&lt;br /&gt;
    } else if (isTemplate) {&lt;br /&gt;
        promptText = &amp;quot;=== INSTRUCTIONS START HERE ===\n\n&amp;quot;&lt;br /&gt;
            + PROMPT_TEMPLATE[menuTrack - TEMPLATE_PROMPT_THRESHOLD].prompt&lt;br /&gt;
            + &amp;quot;\n\n=== INSTRUCTIONS END HERE ===\n\n&amp;quot;&lt;br /&gt;
            + &amp;quot;=== INPUT DATA STARTS HERE ===\n\n&amp;quot;&lt;br /&gt;
            + promptText&lt;br /&gt;
            + &amp;quot;\n\n=== INPUT DATA END HERE ===&amp;quot;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // テンプレートの場合は直前に実行したプロンプトテンプレートの番号としてタグに保存&lt;br /&gt;
    if (isTemplate) {&lt;br /&gt;
        addPreviousTemplatePromptNumberToTag(menuTrack);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    try {&lt;br /&gt;
        // 処理終了を待機&lt;br /&gt;
        shell.KeepRunning = true;&lt;br /&gt;
&lt;br /&gt;
        // カーソル位置移動&lt;br /&gt;
        sel.SetActivePoint(mePosLogical, sel.GetBottomPointX(mePosLogical), sel.GetBottomPointY(mePosLogical));&lt;br /&gt;
        sel.EndOfLine(false, mePosLogical);&lt;br /&gt;
        sel.NewLine();&lt;br /&gt;
&lt;br /&gt;
        // APIキーの取得&lt;br /&gt;
        var apiKey = shell.getEnv(&amp;quot;GROQ_API_KEY&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
        // GroqLlama API に問い合わせ&lt;br /&gt;
        sel.Text = &amp;quot;\n---\n\n&amp;quot;&lt;br /&gt;
        document.KeepScrollPos = true;&lt;br /&gt;
        callGroqLlamaAPI(apiKey, promptText, isTemplate);&lt;br /&gt;
        if (sel.GetActivePointX(mePosLogical) &amp;gt; 2) {&lt;br /&gt;
            sel.Text = &amp;quot;\n\n---\n\n\n&amp;quot;&lt;br /&gt;
        } else {&lt;br /&gt;
            sel.Text = &amp;quot;\n---\n\n\n&amp;quot;&lt;br /&gt;
        }&lt;br /&gt;
    } catch (e) {&lt;br /&gt;
        sel.Text = &amp;quot;&amp;lt;処理中にエラー発生&amp;gt;\n&amp;quot; + e.message + &amp;quot;\n&amp;quot; + e.stack;&lt;br /&gt;
    } finally {&lt;br /&gt;
        // 処理終了待機を解除&lt;br /&gt;
        shell.KeepRunning = false;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function createContextMenu(selectionIsEmpty) {&lt;br /&gt;
    var menu = CreatePopupMenu();&lt;br /&gt;
    menu.Add(&amp;quot;※注意※ プライバシー・機密情報を送信しないこと&amp;quot;, 0);&lt;br /&gt;
    menu.Add(&amp;quot;&amp;quot;, 0, meMenuSeparator);&lt;br /&gt;
    var t = &amp;quot;&amp;quot;;&lt;br /&gt;
    if (selectionIsEmpty) {&lt;br /&gt;
        t = &amp;quot;アクティブ行の内容で相談 (&amp;amp;A)&amp;quot;;&lt;br /&gt;
    } else {&lt;br /&gt;
        t = &amp;quot;選択範囲の内容で相談 (&amp;amp;A)&amp;quot;;&lt;br /&gt;
    }&lt;br /&gt;
    menu.Add(t, EXECUTE_AI);&lt;br /&gt;
    if (HISTORY_SIZE &amp;lt;= 0) {&lt;br /&gt;
        menu.Add(&amp;quot;├ 会話履歴をクリア (履歴が無効になっています)&amp;quot;, RESET_HISTORY, meMenuGrayed);&lt;br /&gt;
    } else if (doc.Tag.exists(HISTORY_TAG)) {&lt;br /&gt;
        var turn = JSON.parse(doc.Tag(HISTORY_TAG)).length / 2;&lt;br /&gt;
        menu.Add(&amp;quot;├ 会話履歴をクリア (現在の会話数: &amp;quot; + turn + &amp;quot;) (&amp;amp;C)&amp;quot;, RESET_HISTORY);&lt;br /&gt;
    } else {&lt;br /&gt;
        menu.Add(&amp;quot;├ 会話履歴をクリア (現在の会話数: 0) (&amp;amp;C)&amp;quot;, RESET_HISTORY, meMenuGrayed);&lt;br /&gt;
    }&lt;br /&gt;
    menu.Add(&amp;quot;└ カスタムインストラクションを変更 (&amp;amp;I)&amp;quot;, SET_CUSTOM_INSTRUCTION);&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    // テンプレート数チェック&lt;br /&gt;
    // 2桁まで許容&lt;br /&gt;
    var ITEMS_MUX = 99;&lt;br /&gt;
    if (PROMPT_TEMPLATE.length &amp;gt; ITEMS_MUX - TEMPLATE_PROMPT_THRESHOLD) {&lt;br /&gt;
        throw new Error(&amp;quot;プロンプトテンプレートの数が多すぎます。&amp;quot;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // テンプレートの追加&lt;br /&gt;
    if (PROMPT_TEMPLATE.length &amp;gt; 0) {&lt;br /&gt;
        var subMenu = CreatePopupMenu();&lt;br /&gt;
        for (var i = 0; i &amp;lt; PROMPT_TEMPLATE.length; i++) {&lt;br /&gt;
            var accelerationKey = &amp;quot;&amp;quot;;&lt;br /&gt;
            if (i === 9) {&lt;br /&gt;
                accelerationKey = &amp;quot; (&amp;amp;0)&amp;quot;;&lt;br /&gt;
            } else if (i &amp;lt; 10) {&lt;br /&gt;
                accelerationKey = &amp;quot; (&amp;amp;&amp;quot; + (i + 1) + &amp;quot;)&amp;quot;;&lt;br /&gt;
            }&lt;br /&gt;
            var menuNum = TEMPLATE_PROMPT_THRESHOLD + i;&lt;br /&gt;
            if (PROMPT_TEMPLATE[i].submenu) {&lt;br /&gt;
                var subMenuList = PROMPT_TEMPLATE[i].submenu;&lt;br /&gt;
                // テンプレート数チェック&lt;br /&gt;
                if (subMenuList.length &amp;gt; ITEMS_MUX) {&lt;br /&gt;
                    throw new Error(&amp;quot;プロンプトテンプレート (サブメニュー) の数が多すぎます。&amp;quot;);&lt;br /&gt;
                }&lt;br /&gt;
                var subSubMenu = CreatePopupMenu();&lt;br /&gt;
                var subMenuPrefix = String(menuNum);&lt;br /&gt;
                for (var j = 0; j &amp;lt; subMenuList.length; j++) {&lt;br /&gt;
                    var subAccelerationKey = &amp;quot;&amp;quot;;&lt;br /&gt;
                    if (j === 9) {&lt;br /&gt;
                        subAccelerationKey = &amp;quot; (&amp;amp;0)&amp;quot;;&lt;br /&gt;
                    } else if (j &amp;lt; 10) {&lt;br /&gt;
                        subAccelerationKey = &amp;quot; (&amp;amp;&amp;quot; + (j + 1) + &amp;quot;)&amp;quot;;&lt;br /&gt;
                    }&lt;br /&gt;
                    // ES5 compatible version without padStart&lt;br /&gt;
                    var subMenuNum = Number(subMenuPrefix + (j &amp;lt; 10 ? &amp;quot;0&amp;quot; + j : String(j)));&lt;br /&gt;
                    subSubMenu.Add(subMenuList[j].title + subAccelerationKey, subMenuNum);&lt;br /&gt;
                }&lt;br /&gt;
                subMenu.AddPopup(PROMPT_TEMPLATE[i].submenu_title + accelerationKey, subSubMenu);&lt;br /&gt;
            } else {&lt;br /&gt;
                subMenu.Add(PROMPT_TEMPLATE[i].title + accelerationKey, menuNum);&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        if (selectionIsEmpty) {&lt;br /&gt;
            t = &amp;quot;プロンプト テンプレート (対象: アクティブ行) (&amp;amp;T)&amp;quot;;&lt;br /&gt;
        } else {&lt;br /&gt;
            t = &amp;quot;プロンプト テンプレート (対象: 選択範囲) (&amp;amp;T)&amp;quot;;&lt;br /&gt;
        }&lt;br /&gt;
        menu.AddPopup(t, subMenu);&lt;br /&gt;
&lt;br /&gt;
        t = &amp;quot;└ 直前に実行したプロンプト テンプレートを再実行 (&amp;amp;R)&amp;quot;;&lt;br /&gt;
        if (doc.Tag.exists(PREVIOUS_TEMPLATE_PROMPT_TAG)) {&lt;br /&gt;
            menu.Add(t, REEXECUTE_LAST_TEMPLATE_PROMPT);&lt;br /&gt;
        } else {&lt;br /&gt;
            menu.Add(t, REEXECUTE_LAST_TEMPLATE_PROMPT, meMenuGrayed);&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return menu;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function callGroqLlamaAPI(apiKey, promptText, isTemplate) {&lt;br /&gt;
    try {&lt;br /&gt;
        // fetch で Groq API にリクエスト&lt;br /&gt;
        var requestBody = {&lt;br /&gt;
            messages: createGroqLlamaRequest(promptText, isTemplate),&lt;br /&gt;
            stream: false,&lt;br /&gt;
        };&lt;br /&gt;
        for (var key in MODEL_CONFIG) {&lt;br /&gt;
            requestBody[key] = MODEL_CONFIG[key];&lt;br /&gt;
        }&lt;br /&gt;
        if (isTemplate) {&lt;br /&gt;
            // テンプレートの場合は応答を決定論的にする&lt;br /&gt;
            // テンプレート以外の場合はデフォルト値とするために指定しない&lt;br /&gt;
            requestBody.temperature = 0.3;&lt;br /&gt;
        }&lt;br /&gt;
        var url = &#039;https://api.groq.com/openai/v1/chat/completions&#039;;&lt;br /&gt;
&lt;br /&gt;
        var http = CreateObject(&amp;quot;Msxml2.ServerXMLHTTP&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
        http.open(&#039;POST&#039;, url, true);&lt;br /&gt;
        http.setRequestHeader(&#039;Content-Type&#039;, &#039;application/json&#039;);&lt;br /&gt;
        http.setRequestHeader(&#039;Authorization&#039;, &#039;Bearer &#039; + apiKey);&lt;br /&gt;
        http.send(JSON.stringify(requestBody));&lt;br /&gt;
&lt;br /&gt;
        // 応答待機&lt;br /&gt;
        var timeout = 60;&lt;br /&gt;
        var retrySec = 0;&lt;br /&gt;
        var startX = sel.GetActivePointX(mePosLogical);&lt;br /&gt;
        var prevPos = sel.GetActivePos();&lt;br /&gt;
        while (http.readyState != 4) {&lt;br /&gt;
            if (retrySec &amp;gt; timeout) {&lt;br /&gt;
                break;&lt;br /&gt;
            }&lt;br /&gt;
            http.waitForResponse(1);&lt;br /&gt;
            retrySec += 1;&lt;br /&gt;
            inputProgressChar(prevPos);&lt;br /&gt;
            prevPos = sel.GetActivePos();&lt;br /&gt;
        }&lt;br /&gt;
        // 進捗表示文字を削除&lt;br /&gt;
        sel.SetActivePoint(&lt;br /&gt;
            mePosLogical,&lt;br /&gt;
            startX,&lt;br /&gt;
            sel.GetActivePointY(mePosLogical),&lt;br /&gt;
            true&lt;br /&gt;
        );&lt;br /&gt;
        sel.Delete();&lt;br /&gt;
&lt;br /&gt;
        // 応答ステータスチェック&lt;br /&gt;
        if (!(http.readyState == 4 &amp;amp;&amp;amp; http.status == 200)) {&lt;br /&gt;
            sel.Text = &amp;quot;エラーが発生しました: &amp;quot; + http.status + &amp;quot; &amp;quot; + http.statusText + &amp;quot;\n&amp;quot; + http.responseText;&lt;br /&gt;
            http.abort();&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        var ret = &amp;quot;&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
        // JSONをパースして必要なテキストを抽出&lt;br /&gt;
        var response = JSON.parse(http.responseText);&lt;br /&gt;
&lt;br /&gt;
        var choices = response.choices;&lt;br /&gt;
        if (choices &amp;amp;&amp;amp; choices.length &amp;gt; 0) {&lt;br /&gt;
            choices.forEach(function (choice) {&lt;br /&gt;
                var message = choice.message;&lt;br /&gt;
                if (message) {&lt;br /&gt;
                    ret += message.content;&lt;br /&gt;
                }&lt;br /&gt;
            });&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        var usage = response.usage;&lt;br /&gt;
        var usageText = &amp;quot;&amp;quot;;&lt;br /&gt;
        if (usage) {&lt;br /&gt;
            usageText += &amp;quot;\n\n(利用トークン数: プロンプト=&amp;quot; + usage.prompt_tokens&lt;br /&gt;
                + &amp;quot;, 応答=&amp;quot; + usage.completion_tokens&lt;br /&gt;
                + &amp;quot;, 合計=&amp;quot; + usage.total_tokens + &amp;quot;)&amp;quot;;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        var formattedOutput = formatOutput(ret);&lt;br /&gt;
&lt;br /&gt;
        sel.Text = formattedOutput + usageText;&lt;br /&gt;
&lt;br /&gt;
        // 結果を会話履歴に追加&lt;br /&gt;
        if (HISTORY_SIZE &amp;gt; 0 &amp;amp;&amp;amp; !isTemplate) {&lt;br /&gt;
            appendReturnTextToTag(promptText, ret);&lt;br /&gt;
        }&lt;br /&gt;
    } catch (e) {&lt;br /&gt;
        throw e;&lt;br /&gt;
    } finally {&lt;br /&gt;
        requestCompletedFlg = true;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function formatOutput(text) {&lt;br /&gt;
    // Markdownを整形する&lt;br /&gt;
    text = formatMarkdown(text);&lt;br /&gt;
    return text&lt;br /&gt;
        // 全角スペースを半角スペースに変換&lt;br /&gt;
        .replace(/　/g, &#039; &#039;)&lt;br /&gt;
        // 全角カッコを半角カッコに変換&lt;br /&gt;
        .replace(/ ?（/g, &#039; (&#039;)&lt;br /&gt;
        .replace(/） ?/g, &#039;) &#039;)&lt;br /&gt;
        .replace(/ ?［/g, &#039; [&#039;)&lt;br /&gt;
        .replace(/］ ?/g, &#039;] &#039;)&lt;br /&gt;
        .replace(/ ?｛/g, &#039; {&#039;)&lt;br /&gt;
        .replace(/｝ ?/g, &#039;} &#039;)&lt;br /&gt;
        // クォーテーションの正規化&lt;br /&gt;
        .replace(/[“”]/g, &#039;&amp;quot;&#039;)&lt;br /&gt;
        .replace(/[‘’]/g, &amp;quot;&#039;&amp;quot;)&lt;br /&gt;
        // 行末のスペースを削除&lt;br /&gt;
        .replace(/[ \t]+$/gm, &#039;&#039;)&lt;br /&gt;
        // 1️⃣ などの数字を半角に変換&lt;br /&gt;
        .replace(/([1-9#*]+)\ufe0f\u20e3/g, &#039;($1)&#039;)&lt;br /&gt;
        ;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/**&lt;br /&gt;
 * Markdown を整形する（ES5 対応）&lt;br /&gt;
 *  1. コードブロック ```～``` を保護して整形対象から除外&lt;br /&gt;
 *  2. 見出し行の前後に空行を 1 行ずつ確保&lt;br /&gt;
 *  3. **太字** / *斜体* / `コード` の外側に半角スペースを 1 つ確保&lt;br /&gt;
 * @param  {string} md  元の Markdown&lt;br /&gt;
 * @return {string}     整形後 Markdown&lt;br /&gt;
 */&lt;br /&gt;
function formatMarkdown(md) {&lt;br /&gt;
    /* === 0. コードブロックをプレースホルダーへ退避 ============== */&lt;br /&gt;
    var codeBlocks = [];      // 元のコードブロックを格納&lt;br /&gt;
    var body = md.replace(/```[\s\S]*?```/g, function (match) {&lt;br /&gt;
        var key = &#039;@@@CODE@BLOCK@&#039; + codeBlocks.length + &#039;@@@&#039;;&lt;br /&gt;
        codeBlocks.push(match);&lt;br /&gt;
        return key;             // プレースホルダーを本文に残す&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    /* === 1. 見出しの前後に空行を挿入 ============================ */&lt;br /&gt;
&lt;br /&gt;
    /* 1-A. 見出し直前に空行（先頭行以外で空行が無い場合） */&lt;br /&gt;
    body = body.replace(&lt;br /&gt;
        /([^\r\n])(\r?\n)(#{1,6}\s)/g,&lt;br /&gt;
        function (_, prev, br, head) { return prev + br + &#039;\n&#039; + head; }&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
    /* 1-B. 見出し直後に空行（既に空行が無い場合） */&lt;br /&gt;
    body = body.replace(&lt;br /&gt;
        /^(#{1,6}\s[^\n]+?)(\r?\n)(?!\r?\n)/gm,&lt;br /&gt;
        &#039;$1$2\n&#039;&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
    /* === 2. 強調記号の外側スペース補完 =========================== */&lt;br /&gt;
&lt;br /&gt;
    /* 2-1. 太字（**bold** / __bold__）*/&lt;br /&gt;
    body = surroundWithSpace(&lt;br /&gt;
        body,&lt;br /&gt;
        &#039;(?:\\*\\*(?=\\S)[^*]*?[^\\s]\\*\\*|__(?=\\S)[^_]*?[^\\s]__)&#039;&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
    /* 2-2. 斜体（*italic* / _italic_）― 太字と区別してマッチ */&lt;br /&gt;
    body = surroundItalic(body);&lt;br /&gt;
&lt;br /&gt;
    /* 2-3. インラインコード（`code`）*/&lt;br /&gt;
    body = surroundWithSpace(body, &#039;`[^`\\n]+?`&#039;);&lt;br /&gt;
&lt;br /&gt;
    /* === 3. コードブロックを復元 ================================= */&lt;br /&gt;
    body = body.replace(/@@@CODE@BLOCK@(\d+)@@@/g, function (_, idx) {&lt;br /&gt;
        return codeBlocks[Number(idx)];&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    return body;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/**&lt;br /&gt;
 * 指定パターン（正規表現文字列）の前後に半角スペースを補完&lt;br /&gt;
 */&lt;br /&gt;
function surroundWithSpace(text, pattern) {&lt;br /&gt;
    var prePostPattern = /[,.!?&#039;&amp;quot;;:()\[\]{}、。！？；：（）［］｛｝「」『』\s]/;&lt;br /&gt;
    /* 行頭スペース補完 */&lt;br /&gt;
    //   text = text.replace(new RegExp(&#039;^(&#039; + pattern + &#039;)(\\S)&#039;, &#039;gm&#039;), &#039;$1 $2&#039;);&lt;br /&gt;
    /* 後側スペース補完 */&lt;br /&gt;
    text = text.replace(new RegExp(&#039;(&#039; + pattern + &#039;)(.)&#039;, &#039;gm&#039;), function (_, core, post) {&lt;br /&gt;
        return post.match(prePostPattern) ? core + post : core + &#039; &#039; + post;&lt;br /&gt;
    });&lt;br /&gt;
    /* 前側スペース補完 */&lt;br /&gt;
    text = text.replace(new RegExp(&#039;(.)(&#039; + pattern + &#039;)&#039;, &#039;gm&#039;), function (_, pre, core) {&lt;br /&gt;
        return pre.match(prePostPattern) ? pre + core : pre + &#039; &#039; + core;&lt;br /&gt;
    });&lt;br /&gt;
    return text;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/**&lt;br /&gt;
 * 斜体 (*…* / _…_) の外側にスペースを追加しつつ&lt;br /&gt;
 * **太字** の中へ誤マッチしないよう制御&lt;br /&gt;
 */&lt;br /&gt;
function surroundItalic(text) {&lt;br /&gt;
    /* *italic* */&lt;br /&gt;
    text = text.replace(&lt;br /&gt;
        /(^|[^*])(\*(?![*\s])[^*\n]*?[^*\n\s]\*(?!\*))(($)|[^*])/gm,&lt;br /&gt;
        function (_, pre, core, post) {&lt;br /&gt;
            pre = pre === &#039;&#039; || pre.match(/\s/) ? pre : pre + &#039; &#039;;&lt;br /&gt;
            post = post === &#039;&#039; || post.match(/\s/) ? post : &#039; &#039; + post;&lt;br /&gt;
            return pre + core + post;&lt;br /&gt;
        }&lt;br /&gt;
    );&lt;br /&gt;
    /* _italic_ */&lt;br /&gt;
    text = text.replace(&lt;br /&gt;
        /(^|[^_])(\_(?![_\s])[^_\n]*?[^_\n\s]\_(?!\_))(($)|[^_])/gm,&lt;br /&gt;
        function (_, pre, core, post) {&lt;br /&gt;
            pre = pre === &#039;&#039; || pre.match(/\s/) ? pre : pre + &#039; &#039;;&lt;br /&gt;
            post = post === &#039;&#039; || post.match(/\s/) ? post : &#039; &#039; + post;&lt;br /&gt;
            return pre + core + post;&lt;br /&gt;
        }&lt;br /&gt;
    );&lt;br /&gt;
    return text;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function roughTokenize(text) {&lt;br /&gt;
    // 超適当なトークナイザ。トークン数をざっくりと多めに数える&lt;br /&gt;
    // 空白文字・ハイフン・アンダースコア区切りの単語に分割して、&lt;br /&gt;
    // 単語がアルファベットのみ、数字のみの場合には 4文字=1トークン として数える。&lt;br /&gt;
    // 単語にアルファベット以外の文字が含まれる場合は 1文字=1トークン として数える。&lt;br /&gt;
&lt;br /&gt;
    var words = text.split(/[\s\-_]+/);&lt;br /&gt;
    var totalTokens = 0;&lt;br /&gt;
&lt;br /&gt;
    for (var i = 0; i &amp;lt; words.length; i++) {&lt;br /&gt;
        var word = words[i];&lt;br /&gt;
        if (!word) continue; // 空の単語をスキップ&lt;br /&gt;
&lt;br /&gt;
        if (/^[a-zA-Z]+$/.test(word) || /^[0-9]+$/.test(word)) {&lt;br /&gt;
            totalTokens += Math.ceil(word.length / 4);&lt;br /&gt;
        } else {&lt;br /&gt;
            totalTokens += word.length;&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return totalTokens;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// document.Tag の内容を加味してGroqLlamaのリクエストを作成&lt;br /&gt;
function createGroqLlamaRequest(promptText, isTemplate) {&lt;br /&gt;
    var customInstructionText = buildCustomInstructionText();&lt;br /&gt;
    // テンプレートの場合は、会話履歴とカスタムインストラクションを反映しない&lt;br /&gt;
    if (isTemplate) {&lt;br /&gt;
        var request = [&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;role&amp;quot;: &amp;quot;user&amp;quot;,&lt;br /&gt;
                &amp;quot;content&amp;quot;: [{ &amp;quot;type&amp;quot;: &amp;quot;text&amp;quot;, &amp;quot;text&amp;quot;: promptText }]&lt;br /&gt;
            }&lt;br /&gt;
        ];&lt;br /&gt;
        if (customInstructionText) {&lt;br /&gt;
            request.unshift({&lt;br /&gt;
                &amp;quot;role&amp;quot;: &amp;quot;system&amp;quot;,&lt;br /&gt;
                &amp;quot;content&amp;quot;: customInstructionText&lt;br /&gt;
            })&lt;br /&gt;
        }&lt;br /&gt;
        return request;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    var tag = document.Tag;&lt;br /&gt;
&lt;br /&gt;
    // 以前の会話が存在しない場合は新規リクエスト&lt;br /&gt;
    if (!tag.exists(HISTORY_TAG)) {&lt;br /&gt;
        var request = [{&lt;br /&gt;
            &amp;quot;role&amp;quot;: &amp;quot;user&amp;quot;,&lt;br /&gt;
            &amp;quot;content&amp;quot;: [{ &amp;quot;type&amp;quot;: &amp;quot;text&amp;quot;, &amp;quot;text&amp;quot;: promptText }]&lt;br /&gt;
        }];&lt;br /&gt;
        if (customInstructionText) {&lt;br /&gt;
            request.unshift({&lt;br /&gt;
                &amp;quot;role&amp;quot;: &amp;quot;system&amp;quot;,&lt;br /&gt;
                &amp;quot;content&amp;quot;: customInstructionText&lt;br /&gt;
            })&lt;br /&gt;
        }&lt;br /&gt;
        return request;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // 会話履歴が存在する場合は、履歴を含めてリクエスト&lt;br /&gt;
    var request = JSON.parse(tag(HISTORY_TAG));&lt;br /&gt;
    if (customInstructionText) {&lt;br /&gt;
        request.unshift({&lt;br /&gt;
            &amp;quot;role&amp;quot;: &amp;quot;system&amp;quot;,&lt;br /&gt;
            &amp;quot;content&amp;quot;: customInstructionText&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
    request.push({&lt;br /&gt;
        &amp;quot;role&amp;quot;: &amp;quot;user&amp;quot;,&lt;br /&gt;
        &amp;quot;content&amp;quot;: promptText&lt;br /&gt;
    });&lt;br /&gt;
    return request;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// document.Tag にGroqLlamaの応答を追加&lt;br /&gt;
function appendReturnTextToTag(userText, modelText) {&lt;br /&gt;
    var tag = document.Tag;&lt;br /&gt;
    if (!tag.exists(HISTORY_TAG)) {&lt;br /&gt;
        tag(HISTORY_TAG) = JSON.stringify([&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;role&amp;quot;: &amp;quot;user&amp;quot;,&lt;br /&gt;
                &amp;quot;content&amp;quot;: userText&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;role&amp;quot;: &amp;quot;assistant&amp;quot;,&lt;br /&gt;
                &amp;quot;content&amp;quot;: modelText&lt;br /&gt;
            }&lt;br /&gt;
        ]);&lt;br /&gt;
    } else {&lt;br /&gt;
        var groqllamaText = JSON.parse(tag(HISTORY_TAG));&lt;br /&gt;
        groqllamaText.push({&lt;br /&gt;
            &amp;quot;role&amp;quot;: &amp;quot;user&amp;quot;,&lt;br /&gt;
            &amp;quot;content&amp;quot;: userText&lt;br /&gt;
        });&lt;br /&gt;
        groqllamaText.push({&lt;br /&gt;
            &amp;quot;role&amp;quot;: &amp;quot;assistant&amp;quot;,&lt;br /&gt;
            &amp;quot;content&amp;quot;: modelText&lt;br /&gt;
        });&lt;br /&gt;
        // 会話セット (user, model 1往復) が特定セット数を超えた場合、最初のセットを削除&lt;br /&gt;
        if (groqllamaText.length &amp;gt; HISTORY_SIZE * 2) {&lt;br /&gt;
            groqllamaText.splice(0, 2);&lt;br /&gt;
        }&lt;br /&gt;
        // 会話履歴全体が MAX_HISTORY_TOKEN を超えた場合、最初のセットを削除&lt;br /&gt;
        var joinedText = &amp;quot;&amp;quot;;&lt;br /&gt;
        for (var i = 0; i &amp;lt; groqllamaText.length; i++) {&lt;br /&gt;
            joinedText += groqllamaText[i].content + &amp;quot;\n&amp;quot;;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        while (roughTokenize(joinedText) &amp;gt; MAX_HISTORY_TOKEN) {&lt;br /&gt;
            groqllamaText.splice(0, 2);&lt;br /&gt;
            joinedText = &amp;quot;&amp;quot;;&lt;br /&gt;
            for (var i = 0; i &amp;lt; groqllamaText.length; i++) {&lt;br /&gt;
                joinedText += groqllamaText[i].content + &amp;quot;\n&amp;quot;;&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        tag(HISTORY_TAG) = JSON.stringify(groqllamaText);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function resetTag() {&lt;br /&gt;
    var tag = doc.Tag;&lt;br /&gt;
    if (tag.exists(HISTORY_TAG)) {&lt;br /&gt;
        tag.remove(HISTORY_TAG);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function baseCustomInstruction() {&lt;br /&gt;
    var language = LANGUAGE.trim();&lt;br /&gt;
    return (language ? &amp;quot;Respond in &amp;quot; + language + &amp;quot;.\n&amp;quot; : &amp;quot;&amp;quot;)&lt;br /&gt;
        + &amp;quot;Do not hallucinate. If you are unsure or don&#039;t have enough information to answer with confidence, say \&amp;quot;I don&#039;t know\&amp;quot; or \&amp;quot;I&#039;m not sure.\&amp;quot;\n&amp;quot;&lt;br /&gt;
    // + &amp;quot;Do NOT EVER use full-width brackets \&amp;quot;（）\&amp;quot;, but use half-width brackets \&amp;quot;()\&amp;quot;. Enclose the outside of the half-width brackets with half-width spaces.\n&amp;quot;&lt;br /&gt;
    // + &amp;quot;When using Markdown formatting, you MUST follow these rules:\n&amp;quot;&lt;br /&gt;
    // + &amp;quot;- Rule 1: Insert a blank line below the header line (A header line begins with one or more # characters).\n&amp;quot;&lt;br /&gt;
    // + &amp;quot;- Rule 2: Insert spaces before and after bold (surrounded by **), italic (surrounded by *), and inline code (surrounded by `). For example: \&amp;quot;私は **元気** です。\&amp;quot;\n&amp;quot;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function buildCustomInstructionText() {&lt;br /&gt;
    var customInstruction = getTemporaryCustomInstruction();&lt;br /&gt;
    if (!customInstruction) {&lt;br /&gt;
        // 一時的なカスタムインストラクションがない場合はデフォルトを使用&lt;br /&gt;
        customInstruction = DEFAULT_CUSTOM_INSTRUCTION;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    customInstruction = customInstruction.replace(/^[\s　]+|[\s　]+$/g, &amp;quot;&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
    return baseCustomInstruction().trim()&lt;br /&gt;
        + (customInstruction ? &amp;quot;\n&amp;quot; + customInstruction : &amp;quot;&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function configureTemporaryCustomInstruction() {&lt;br /&gt;
    var tempCustomInstruction = getTemporaryCustomInstruction();&lt;br /&gt;
    var prompt = Prompt(&amp;quot;カスタムインストラクションを変更 ※変更すると会話履歴がリセットされます&amp;quot;, tempCustomInstruction ? tempCustomInstruction : DEFAULT_CUSTOM_INSTRUCTION);&lt;br /&gt;
    if (prompt) {&lt;br /&gt;
        if (prompt === DEFAULT_CUSTOM_INSTRUCTION) {&lt;br /&gt;
            // デフォルトと同じ値の場合はタグを削除&lt;br /&gt;
            setTemporaryCustomInstruction(&amp;quot;&amp;quot;);&lt;br /&gt;
        } else if (prompt !== tempCustomInstruction) {&lt;br /&gt;
            // デフォルト以外の値が設定された場合は保存&lt;br /&gt;
            setTemporaryCustomInstruction(prompt);&lt;br /&gt;
        }&lt;br /&gt;
    } else if (prompt !== null) {&lt;br /&gt;
        // 空文字でOKされた場合はデフォルトに戻すか確認&lt;br /&gt;
        if (prompt === &amp;quot;&amp;quot;) {&lt;br /&gt;
            if (Confirm(&amp;quot;カスタムインストラクションをデフォルトに戻しますか？\n※カスタムインストラクションを空にしたい場合は [キャンセル] を選択してください。&amp;quot;)) {&lt;br /&gt;
                setTemporaryCustomInstruction(&amp;quot;&amp;quot;);&lt;br /&gt;
            } else {&lt;br /&gt;
                setTemporaryCustomInstruction(&amp;quot; &amp;quot;);&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // 変更が加わっている場合は会話履歴をリセット&lt;br /&gt;
    if (tempCustomInstruction !== getTemporaryCustomInstruction()) {&lt;br /&gt;
        resetTag();&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getTemporaryCustomInstruction() {&lt;br /&gt;
    var tag = doc.Tag;&lt;br /&gt;
    if (tag.exists(TEMPORARY_CUSTOM_INSTRUCTION_TAG)) {&lt;br /&gt;
        return tag(TEMPORARY_CUSTOM_INSTRUCTION_TAG);&lt;br /&gt;
    } else {&lt;br /&gt;
        return &amp;quot;&amp;quot;;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function setTemporaryCustomInstruction(temporaryCustomInstruction) {&lt;br /&gt;
    var tag = doc.Tag;&lt;br /&gt;
    if (temporaryCustomInstruction) {&lt;br /&gt;
        tag(TEMPORARY_CUSTOM_INSTRUCTION_TAG) = temporaryCustomInstruction;&lt;br /&gt;
    } else {&lt;br /&gt;
        if (tag.exists(TEMPORARY_CUSTOM_INSTRUCTION_TAG)) {&lt;br /&gt;
            tag.remove(TEMPORARY_CUSTOM_INSTRUCTION_TAG);&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function addPreviousTemplatePromptNumberToTag(menuTrack) {&lt;br /&gt;
    var tag = doc.Tag;&lt;br /&gt;
    tag(PREVIOUS_TEMPLATE_PROMPT_TAG) = menuTrack;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function inputProgressChar(pos) {&lt;br /&gt;
    sel.SetActivePos(pos);&lt;br /&gt;
    sel.Text = &amp;quot;.&amp;quot;;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;/div&gt;</summary>
		<author><name>Yuko</name></author>
	</entry>
	<entry>
		<id>https://www.haijin-boys.com/wiki/index.php?title=Groq_%E3%81%AB%E7%9B%B8%E8%AB%87&amp;diff=9166</id>
		<title>Groq に相談</title>
		<link rel="alternate" type="text/html" href="https://www.haijin-boys.com/wiki/index.php?title=Groq_%E3%81%AB%E7%9B%B8%E8%AB%87&amp;diff=9166"/>
		<updated>2025-07-23T22:58:55Z</updated>

		<summary type="html">&lt;p&gt;Yuko: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== 概要 ==&lt;br /&gt;
&lt;br /&gt;
生成AIプラットフォームである [https://groq.com GroqCloud] で各種AIを利用するためのMeryマクロです。&lt;br /&gt;
※GroqCloud で提供されるモデルは複数あるため、私がよく利用するモデルを中心に対応しています。&lt;br /&gt;
&lt;br /&gt;
「Google Geminiに相談」マクロでは動作中のストリーム通信に対応するためにV8エンジンで実行していましたが、V8エンジン版は使用上の制限からマクロ実行を開始するまで1秒程度のタイムラグが発生していました。GroqCloudの売りである超高速レスポンスを見込んで本マクロではストリーム通信を使わないようにしました。これにより従来のJScriptで処理を組み立てることができるため、前述のタイムラグが起こらないようになりました。また、ストリーム通信を使わないことで通信量が少なく応答もシンプルになるため、場合によっては「Google Geminiに相談」マクロよりも処理が安定する可能性があります。&lt;br /&gt;
&lt;br /&gt;
【主な機能】&lt;br /&gt;
&lt;br /&gt;
* GroqCloud APIを利用して生成AIと対話できるMery用のマクロ&lt;br /&gt;
* 選択したテキストやアクティブな行の内容について生成AIに質問・相談が可能&lt;br /&gt;
* 会話履歴を保持し、文脈を考慮した対話が可能&lt;br /&gt;
&lt;br /&gt;
【特徴的な機能】&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;会話履歴機能&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* 事前の設定数に応じた会話履歴を保持&lt;br /&gt;
* 履歴のクリアが可能&lt;br /&gt;
&lt;br /&gt;
これにより、会話の文脈を考慮した回答を得られます。&lt;br /&gt;
※デフォルトはオフ。「HISTORY_SIZE」変数を増やすと履歴を保持するようになります。&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;カスタムインストラクション&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* AIの応答方針を設定可能&lt;br /&gt;
* 一時的なカスタム指示の保存と変更が可能&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;プロンプトテンプレート&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* 誤字脱字チェック&lt;br /&gt;
* 日本語翻訳&lt;br /&gt;
* コードの最適化、生成&lt;br /&gt;
&lt;br /&gt;
など、定型的な質問を簡単に実行できます。&lt;br /&gt;
※テンプレート機能では回答を安定させるため、前述の会話履歴を文脈に含みません。&lt;br /&gt;
&lt;br /&gt;
テンプレートは自身の使い方に合わせて好きに追加、削除ができます。詳細はソースコード内の「PROMPT_TEMPLATE」定数のコメントを参照。&lt;br /&gt;
&lt;br /&gt;
== 注意事項 (必ず読んでからご利用ください) ==&lt;br /&gt;
&lt;br /&gt;
* 本マクロでは 2025/04/13 時点で無料利用ができるGroqCloud APIを利用します。今後、プラットフォームの改定により有償になったり、設定誤り等で費用が発生しても、作者は一切の責任を負いません。&lt;br /&gt;
* GroqCloud APIではリクエスト内容をAI学習に用いないことをプライバシポリシーとして謳っているものの、くれぐれも機密情報やプライバシーに関わる情報を指定しないように注意してください。&lt;br /&gt;
* GroqCloud APIはプラットフォームの利用規約に則ってご利用ください。&lt;br /&gt;
&lt;br /&gt;
== 利用前提 ==&lt;br /&gt;
&lt;br /&gt;
* Mery Ver 3.7.13 以上&lt;br /&gt;
* GroqCloudで、GroqCloud API APIキーを取得していること (参考 [https://console.groq.com/docs/quickstart Quickstart])&lt;br /&gt;
&lt;br /&gt;
== 利用方法 ==&lt;br /&gt;
&lt;br /&gt;
* 環境変数の設定&lt;br /&gt;
:コマンド プロンプトで以下のコマンドを実行し、環境変数を設定してください。&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bat&amp;quot; copy&amp;gt;&lt;br /&gt;
setx GROQ_API_KEY &amp;quot;***************************************&amp;quot;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
:設定後、Mery または Windows の再起動が必要な場合があります。&lt;br /&gt;
* マクロの実行&lt;br /&gt;
**プロンプトにしたい文字列を選択してマクロを実行すると、選択したテキストがプロンプトとしてGemini APIに送信され、応答がエディタに表示されます。&lt;br /&gt;
* 会話の履歴数を調整&lt;br /&gt;
** 会話の履歴は設定した個数分が保持され、文脈として利用されます。適宜、ソースコード内の「HISTORY_SIZE」の数を調整してください。&lt;br /&gt;
** ※数を増やすほど会話内容を保持して文脈を理解するようになりますが、メモリ使用量が増えたり動作が重くなる可能性があります。&lt;br /&gt;
** ※開いている文書ごとに履歴を保持します。文脈をリセットしたいときは、新しい文書を開いて会話をやり直してください。&lt;br /&gt;
&lt;br /&gt;
== ソースコード ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot; copy&amp;gt;&lt;br /&gt;
#title = &amp;quot;Groq に相談&amp;quot;&lt;br /&gt;
BeginUndoGroup();&lt;br /&gt;
&lt;br /&gt;
// カスタムインストラクション&lt;br /&gt;
// これを設定することで、生成AIの回答全体への生成方針を指示できます。&lt;br /&gt;
// 例えば &amp;quot;あなたは「Kimi」という名前のギャルAIアシスタントです。「マジ」「めっちゃ」「...だねー！」などのラフな言葉を使い、敬語は使いません。絵文字を使って感情表現をします。一人称は「あたし」。人懐っこく、バイタリティ溢れる性格で、ユーザーの課題を協力して解決します。&amp;quot; と設定すると、AIの雰囲気が元気な子になります。&lt;br /&gt;
// 何も指示しない場合は、空文字 &amp;quot;&amp;quot; を設定してください。&lt;br /&gt;
var DEFAULT_CUSTOM_INSTRUCTION = &amp;quot;&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
// プロンプトテンプレート&lt;br /&gt;
// プロンプトテンプレートを設定することで、AIに対して特定の指示を出すことができます。&lt;br /&gt;
// プロンプトテンプレートでの生成は会話履歴とカスタムインストラクションを反映しません。&lt;br /&gt;
// テンプレートは以下の { ～ }, で囲まれたブロックを好きに増減させて利用できます。&lt;br /&gt;
var PROMPT_TEMPLATE = [&lt;br /&gt;
    // 以下はサンプルです。1階層メニューと2階層メニューが利用できます。&lt;br /&gt;
    // {&lt;br /&gt;
    //     &amp;quot;title&amp;quot;: &amp;quot;要約&amp;quot;,&lt;br /&gt;
    //     &amp;quot;prompt&amp;quot;: &amp;quot;以下の文章を要約してください。&amp;quot;&lt;br /&gt;
    // },&lt;br /&gt;
    // {&lt;br /&gt;
    //     &amp;quot;submenu_title&amp;quot;: &amp;quot;翻訳系&amp;quot;,&lt;br /&gt;
    //     &amp;quot;submenu&amp;quot;: [&lt;br /&gt;
    //         {&lt;br /&gt;
    //             &amp;quot;title&amp;quot;: &amp;quot;日本語翻訳&amp;quot;,&lt;br /&gt;
    //             &amp;quot;prompt&amp;quot;: &amp;quot;以下の文章を日本語に翻訳してください。&amp;quot;&lt;br /&gt;
    //         },&lt;br /&gt;
    //         {&lt;br /&gt;
    //             &amp;quot;title&amp;quot;: &amp;quot;英語翻訳&amp;quot;,&lt;br /&gt;
    //             &amp;quot;prompt&amp;quot;: &amp;quot;以下の文章を英語に翻訳してください。&amp;quot;&lt;br /&gt;
    //         },&lt;br /&gt;
    //     ]&lt;br /&gt;
    // },&lt;br /&gt;
&lt;br /&gt;
    {&lt;br /&gt;
        &amp;quot;submenu_title&amp;quot;: &amp;quot;理解を深める&amp;quot;,&lt;br /&gt;
        &amp;quot;submenu&amp;quot;: [&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;論点を整理する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の論点を整理して。&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;論理の抜け漏れを探す&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の論点を整理し、忖度せずに鋭く、論理の抜け漏れを抽出して。そして、そこから発生する可能性のあるリスクも教えて。&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;要約する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;要約して。専門的な用語と思われる単語は簡単に解説も入れて。&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;物語対話形式で教えて&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の内容について物語対話形式で教えてください。&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
        ]&lt;br /&gt;
    },&lt;br /&gt;
    {&lt;br /&gt;
        &amp;quot;submenu_title&amp;quot;: &amp;quot;翻訳する&amp;quot;,&lt;br /&gt;
        &amp;quot;submenu&amp;quot;: [&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;日本語に翻訳&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の文章を日本語に翻訳してください。\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;出力形式は以下にすること:\n[訳]\n翻訳文...\n&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;英語に翻訳&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の文章を英語に翻訳してください。\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;出力形式は以下にすること:\n[Translation]\n翻訳文...\n&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;英語-&amp;gt;日本語 スラッシュリーディング翻訳&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の英文を日本語にスラッシュリーディング形式で翻訳してください。\n\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;条件:\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 英文の各フレーズをスラッシュ(/)で区切って表示\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 各フレーズの日本語訳を括弧()内に併記\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 文の終わりはダブルスラッシュ(//)で表示\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 英語の語順に従って日本語訳を行う\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 日本語訳は自然な日本語である必要はなく、英語の語順を反映した直訳調で構わない\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 主語が省略されている場合は補って翻訳すること\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 一般的な英文で頻出する表現がある場合は次の行に記載\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 以下の出力例のように &amp;lt;英文&amp;gt; (日本語訳) / ... とスラッシュ区切りの繰り返しになる \n\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;出力例:\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;Snowstorms are forecast (暴風雪が予報されている) / to hit the Sea of Japan coasts (日本海沿岸を襲うと) / of northern to eastern Japan (北部から東部の) / from Sunday through Monday. (日曜から月曜にかけて。) //\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;【頻出表現】\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- be forecast to: ～すると予報されている\n&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
        ]&lt;br /&gt;
    },&lt;br /&gt;
    {&lt;br /&gt;
        &amp;quot;submenu_title&amp;quot;: &amp;quot;プログラミング支援&amp;quot;,&lt;br /&gt;
        &amp;quot;submenu&amp;quot;: [&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;コードを洗練させる&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下のコードを洗練させてください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;コードをレビューする&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下のコードをセキュリティと保守性の観点でレビューしてください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;エラーログをデバッグする&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下のエラーログを解析し、考えられる原因と対策を提示してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;SQLクエリを最適化する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下のSQL文を最適化してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;PowerShell スクリプトを書く&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の処理を行うPowerShellスクリプトを生成してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;Shell Script を書く&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の処理を行うShell Scriptを生成してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;Linux コマンドを提案&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;あなたはLinuxエキスパートとして、以下のLinuxコマンドを提案してください。\n\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;制約条件:\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 多数のディストリビューションで採用されているメジャーなコマンドを使用し、ポータブルな実装にすること\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- bashでの実行を前提にすること\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 可能な限りシンプルな実装にすること\n\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;期待する出力:\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- コマンドの具体的な実装方法\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 各コマンドの動作説明\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 制限事項\n&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
        ]&lt;br /&gt;
    },&lt;br /&gt;
    {&lt;br /&gt;
        &amp;quot;submenu_title&amp;quot;: &amp;quot;ビジネス文書作成支援&amp;quot;,&lt;br /&gt;
        &amp;quot;submenu&amp;quot;: [&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;誤字脱字、不正確な表現をチェック&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の2つのカテゴリーで問題点を指摘してください\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 【誤字脱字】：明確な誤字脱字および誤字の疑いがある箇所\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 【不正確・曖昧な表現】：解釈に個人差が生じる可能性がある表現、より適切な表現に改善できる箇所&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;提案書を作成する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下に対する対する提案書を、コスト・実現性・リスクを含めて作成してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;仕様書のアウトラインを作成する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の内容で仕様書のアウトラインを作成してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;手順書のアウトラインを作成する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の作業を行う手順書を、前提条件と注意事項を含めてアウトラインを作成してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;ユーザーマニュアルのアウトラインを作成する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の機能内容から、ユーザー向けマニュアルのアウトラインを作成してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;議事録を作成する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の会議内容から議事録を作成してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;進捗報告書を作成する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下のタスク状況から進捗報告書を作成してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
        ]&lt;br /&gt;
    },&lt;br /&gt;
    {&lt;br /&gt;
        &amp;quot;submenu_title&amp;quot;: &amp;quot;分析支援&amp;quot;,&lt;br /&gt;
        &amp;quot;submenu&amp;quot;: [&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;問題を分析し、課題を抽出する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の状況から問題や潜在的リスクを分析し、課題を抽出してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;対応策を提案する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の問題・課題に対する対応策を複数提案してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;傾向を見つける&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の内容から、数値や挙動上の傾向を見つけてください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
        ]&lt;br /&gt;
    },&lt;br /&gt;
    {&lt;br /&gt;
        &amp;quot;submenu_title&amp;quot;: &amp;quot;アイデア出し&amp;quot;,&lt;br /&gt;
        &amp;quot;submenu&amp;quot;: [&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;SCAMPER法でブレインストーミング&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下のテーマについて、SCAMPER法を使って15個のアイデアを出してください。制約条件は以下の通りです：&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;解決策の幅出し&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の課題に対して、技術面、運用面、コスト面から実現可能な解決策を5つずつ提案してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;業務改善案の提案&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の既存プロセスについて、効率化、コスト削減、品質向上の観点から改善案を提案してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;逆転の発想&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の目標に対して、通常とは逆の視点からアプローチする方法を3つ提案してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;競合分析からの着想&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の内容について、業界における競合他社の特徴を分析し、差別化できる新しいアプローチを提案してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;ユースケースの列挙&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の製品・サービスについて、想定されるユースケースを10個列挙し、それぞれの価値提案を記載してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
        ]&lt;br /&gt;
    },&lt;br /&gt;
]&lt;br /&gt;
&lt;br /&gt;
// 生成AIの回答の言語設定&lt;br /&gt;
// &amp;quot;Japanese&amp;quot; や &amp;quot;English&amp;quot; を設定できます。他の言語は未検証。&lt;br /&gt;
// 自動設定にする場合は空文字 &amp;quot;&amp;quot; を設定してください。&lt;br /&gt;
var LANGUAGE = &amp;quot;Japanese&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
// 入力プロンプトの最大トークン数&lt;br /&gt;
// 超適当なトークナイザー関数により計測し、英単語は4文字1トークン、それ以外は1文字1トークンとして数えます。&lt;br /&gt;
var MAX_PROMPT_TOKEN = 6000;&lt;br /&gt;
&lt;br /&gt;
// 会話の履歴を保持する数 (質問と回答で1セット)&lt;br /&gt;
// 数を増やすと、会話の履歴が増えますが、Meryのメモリ使用量が増えたり、&lt;br /&gt;
// 使用トークン数が増えて動作が重くなる可能性があるため、適宜調整してください。&lt;br /&gt;
// document.Tag に保持されるため、開いているファイルを閉じると履歴は消えます。&lt;br /&gt;
var HISTORY_SIZE = 0;&lt;br /&gt;
&lt;br /&gt;
// 会話履歴に保持する最大トークン数&lt;br /&gt;
// 会話履歴のトークン数がこの数を超える場合、古い履歴から削除されます。&lt;br /&gt;
var MAX_HISTORY_TOKEN = 10000;&lt;br /&gt;
&lt;br /&gt;
// コンテキストメニュー番号の定義&lt;br /&gt;
var EXECUTE_AI = 1;&lt;br /&gt;
var RESET_HISTORY = 2;&lt;br /&gt;
var SET_CUSTOM_INSTRUCTION = 3;&lt;br /&gt;
var REEXECUTE_LAST_TEMPLATE_PROMPT = 4;&lt;br /&gt;
var TEMPLATE_PROMPT_THRESHOLD = 10;&lt;br /&gt;
var TEMPLATE_PROMPT_SUBMENU_THRESHOLD = 1000;&lt;br /&gt;
&lt;br /&gt;
// タグ名の定義&lt;br /&gt;
var HISTORY_TAG = &amp;quot;groqllama-history&amp;quot;;&lt;br /&gt;
var TEMPORARY_CUSTOM_INSTRUCTION_TAG = &amp;quot;groqllama-temporary-custom-instruction&amp;quot;;&lt;br /&gt;
var PREVIOUS_TEMPLATE_PROMPT_TAG = &amp;quot;groqllama-previous-template-prompt&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
// 生成AIモデル別設定&lt;br /&gt;
&lt;br /&gt;
// const MODEL_CONFIG = {&lt;br /&gt;
//     model: &amp;quot;meta-llama/llama-4-maverick-17b-128e-instruct&amp;quot;,&lt;br /&gt;
//     max_tokens: 3500,&lt;br /&gt;
//     temperature: 0.6,&lt;br /&gt;
// }&lt;br /&gt;
&lt;br /&gt;
// const MODEL_CONFIG = {&lt;br /&gt;
//     model: &amp;quot;qwen/qwen3-32b&amp;quot;,&lt;br /&gt;
//     max_tokens: 5000,&lt;br /&gt;
//     temperature: 0.6,&lt;br /&gt;
//     max_completion_tokens: 1024,&lt;br /&gt;
//     reasoning_effort: &amp;quot;default&amp;quot;,&lt;br /&gt;
//     reasoning_format: &amp;quot;hidden&amp;quot;&lt;br /&gt;
// }&lt;br /&gt;
&lt;br /&gt;
const MODEL_CONFIG = {&lt;br /&gt;
    model: &amp;quot;moonshotai/kimi-k2-instruct&amp;quot;,&lt;br /&gt;
    max_tokens: 5000,&lt;br /&gt;
    temperature: 0.7,&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// console.log に出力する内容を Mery のアウトプットバーに表示する&lt;br /&gt;
var console = {&lt;br /&gt;
    log: function (str) {&lt;br /&gt;
        outputBar.Writeln(str)&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
var doc = document;&lt;br /&gt;
var sel = document.selection;&lt;br /&gt;
&lt;br /&gt;
// 生成リクエスト中止フラグ&lt;br /&gt;
var abortRequestFlg = false&lt;br /&gt;
// 生成リクエスト完了フラグ;&lt;br /&gt;
var requestCompletedFlg = false;&lt;br /&gt;
&lt;br /&gt;
main();&lt;br /&gt;
&lt;br /&gt;
function main() {&lt;br /&gt;
    var selectionIsEmpty = sel.IsEmpty&lt;br /&gt;
&lt;br /&gt;
    // プロンプトの取得&lt;br /&gt;
    if (selectionIsEmpty) {&lt;br /&gt;
        sel.StartOfLine(false, mePosLogical);&lt;br /&gt;
        sel.EndOfLine(true, mePosLogical);&lt;br /&gt;
    }&lt;br /&gt;
    var promptText = sel.Text.trim();&lt;br /&gt;
    var promptLength = sel.TextLength;&lt;br /&gt;
    var menuTrack = 0;&lt;br /&gt;
    while (true) {&lt;br /&gt;
        // コンテキストメニューの作成&lt;br /&gt;
        var menu = createContextMenu(selectionIsEmpty);&lt;br /&gt;
        menuTrack = menu.Track(0);&lt;br /&gt;
        // メニュー選択がキャンセルされた場合は終了&lt;br /&gt;
        if (menuTrack === 0) return;&lt;br /&gt;
&lt;br /&gt;
        // 会話履歴のリセット&lt;br /&gt;
        if (menuTrack === RESET_HISTORY) {&lt;br /&gt;
            resetTag();&lt;br /&gt;
            continue;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        // カスタムインストラクションの設定&lt;br /&gt;
        if (menuTrack === SET_CUSTOM_INSTRUCTION) {&lt;br /&gt;
            configureTemporaryCustomInstruction();&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        break;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // プロンプトが空の場合はエラー&lt;br /&gt;
    if (promptLength === 0) {&lt;br /&gt;
        alert(&amp;quot;プロンプトを入力してください。&amp;quot;);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
    // 文字数が多すぎる場合はエラー&lt;br /&gt;
    if (roughTokenize(promptText) &amp;gt; MAX_PROMPT_TOKEN) {&lt;br /&gt;
        alert(&amp;quot;プロンプトの文字数が多すぎます。文字数を少なくしてください。&amp;quot;);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
    // 読み取り専用の場合はエラー&lt;br /&gt;
    if (doc.ReadOnly) {&lt;br /&gt;
        alert(&amp;quot;ファイルが読み取り専用のため実行できません。&amp;quot;);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // 直前に実行したプロンプトテンプレートの再実行の場合、menuTrack を書き換え&lt;br /&gt;
    if (menuTrack === REEXECUTE_LAST_TEMPLATE_PROMPT) {&lt;br /&gt;
        if (doc.Tag.exists(PREVIOUS_TEMPLATE_PROMPT_TAG)) {&lt;br /&gt;
            menuTrack = Number(doc.Tag(PREVIOUS_TEMPLATE_PROMPT_TAG));&lt;br /&gt;
        } else {&lt;br /&gt;
            alert(&amp;quot;直前に実行したプロンプトテンプレートがありません。&amp;quot;);&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // テンプレートの場合はプロンプトを変更&lt;br /&gt;
    var isTemplate = menuTrack &amp;gt;= TEMPLATE_PROMPT_THRESHOLD;&lt;br /&gt;
    var isSubmenu = menuTrack &amp;gt;= TEMPLATE_PROMPT_SUBMENU_THRESHOLD;&lt;br /&gt;
    if (isSubmenu) {&lt;br /&gt;
        var parentMenuTrack = Number(String(menuTrack).slice(0, 2));&lt;br /&gt;
        var subMenuTrack = Number(String(menuTrack).slice(2));&lt;br /&gt;
        promptText = &amp;quot;=== INSTRUCTIONS START HERE ===\n\n&amp;quot;&lt;br /&gt;
            + PROMPT_TEMPLATE[parentMenuTrack - TEMPLATE_PROMPT_THRESHOLD].submenu[subMenuTrack].prompt&lt;br /&gt;
            + &amp;quot;\n\n=== INSTRUCTIONS END HERE ===\n\n&amp;quot;&lt;br /&gt;
            + &amp;quot;=== INPUT DATA STARTS HERE ===\n\n&amp;quot;&lt;br /&gt;
            + promptText&lt;br /&gt;
            + &amp;quot;\n\n=== INPUT DATA END HERE ===&amp;quot;;&lt;br /&gt;
    } else if (isTemplate) {&lt;br /&gt;
        promptText = &amp;quot;=== INSTRUCTIONS START HERE ===\n\n&amp;quot;&lt;br /&gt;
            + PROMPT_TEMPLATE[menuTrack - TEMPLATE_PROMPT_THRESHOLD].prompt&lt;br /&gt;
            + &amp;quot;\n\n=== INSTRUCTIONS END HERE ===\n\n&amp;quot;&lt;br /&gt;
            + &amp;quot;=== INPUT DATA STARTS HERE ===\n\n&amp;quot;&lt;br /&gt;
            + promptText&lt;br /&gt;
            + &amp;quot;\n\n=== INPUT DATA END HERE ===&amp;quot;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // テンプレートの場合は直前に実行したプロンプトテンプレートの番号としてタグに保存&lt;br /&gt;
    if (isTemplate) {&lt;br /&gt;
        addPreviousTemplatePromptNumberToTag(menuTrack);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    try {&lt;br /&gt;
        // 処理終了を待機&lt;br /&gt;
        shell.KeepRunning = true;&lt;br /&gt;
&lt;br /&gt;
        // カーソル位置移動&lt;br /&gt;
        sel.SetActivePoint(mePosLogical, sel.GetBottomPointX(mePosLogical), sel.GetBottomPointY(mePosLogical));&lt;br /&gt;
        sel.EndOfLine(false, mePosLogical);&lt;br /&gt;
        sel.NewLine();&lt;br /&gt;
&lt;br /&gt;
        // APIキーの取得&lt;br /&gt;
        var apiKey = shell.getEnv(&amp;quot;GROQ_API_KEY&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
        // GroqLlama API に問い合わせ&lt;br /&gt;
        sel.Text = &amp;quot;\n---\n\n&amp;quot;&lt;br /&gt;
        document.KeepScrollPos = true;&lt;br /&gt;
        callGroqLlamaAPI(apiKey, promptText, isTemplate);&lt;br /&gt;
        if (sel.GetActivePointX(mePosLogical) &amp;gt; 2) {&lt;br /&gt;
            sel.Text = &amp;quot;\n\n---\n\n\n&amp;quot;&lt;br /&gt;
        } else {&lt;br /&gt;
            sel.Text = &amp;quot;\n---\n\n\n&amp;quot;&lt;br /&gt;
        }&lt;br /&gt;
    } catch (e) {&lt;br /&gt;
        sel.Text = &amp;quot;&amp;lt;処理中にエラー発生&amp;gt;\n&amp;quot; + e.message + &amp;quot;\n&amp;quot; + e.stack;&lt;br /&gt;
    } finally {&lt;br /&gt;
        // 処理終了待機を解除&lt;br /&gt;
        shell.KeepRunning = false;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function createContextMenu(selectionIsEmpty) {&lt;br /&gt;
    var menu = CreatePopupMenu();&lt;br /&gt;
    menu.Add(&amp;quot;※注意※ プライバシー・機密情報を送信しないこと&amp;quot;, 0);&lt;br /&gt;
    menu.Add(&amp;quot;&amp;quot;, 0, meMenuSeparator);&lt;br /&gt;
    var t = &amp;quot;&amp;quot;;&lt;br /&gt;
    if (selectionIsEmpty) {&lt;br /&gt;
        t = &amp;quot;アクティブ行の内容で相談 (&amp;amp;A)&amp;quot;;&lt;br /&gt;
    } else {&lt;br /&gt;
        t = &amp;quot;選択範囲の内容で相談 (&amp;amp;A)&amp;quot;;&lt;br /&gt;
    }&lt;br /&gt;
    menu.Add(t, EXECUTE_AI);&lt;br /&gt;
    if (HISTORY_SIZE &amp;lt;= 0) {&lt;br /&gt;
        menu.Add(&amp;quot;├ 会話履歴をクリア (履歴が無効になっています)&amp;quot;, RESET_HISTORY, meMenuGrayed);&lt;br /&gt;
    } else if (doc.Tag.exists(HISTORY_TAG)) {&lt;br /&gt;
        var turn = JSON.parse(doc.Tag(HISTORY_TAG)).length / 2;&lt;br /&gt;
        menu.Add(&amp;quot;├ 会話履歴をクリア (現在の会話数: &amp;quot; + turn + &amp;quot;) (&amp;amp;C)&amp;quot;, RESET_HISTORY);&lt;br /&gt;
    } else {&lt;br /&gt;
        menu.Add(&amp;quot;├ 会話履歴をクリア (現在の会話数: 0) (&amp;amp;C)&amp;quot;, RESET_HISTORY, meMenuGrayed);&lt;br /&gt;
    }&lt;br /&gt;
    menu.Add(&amp;quot;└ カスタムインストラクションを変更 (&amp;amp;I)&amp;quot;, SET_CUSTOM_INSTRUCTION);&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    // テンプレート数チェック&lt;br /&gt;
    // 2桁まで許容&lt;br /&gt;
    var ITEMS_MUX = 99;&lt;br /&gt;
    if (PROMPT_TEMPLATE.length &amp;gt; ITEMS_MUX - TEMPLATE_PROMPT_THRESHOLD) {&lt;br /&gt;
        throw new Error(&amp;quot;プロンプトテンプレートの数が多すぎます。&amp;quot;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // テンプレートの追加&lt;br /&gt;
    if (PROMPT_TEMPLATE.length &amp;gt; 0) {&lt;br /&gt;
        var subMenu = CreatePopupMenu();&lt;br /&gt;
        for (var i = 0; i &amp;lt; PROMPT_TEMPLATE.length; i++) {&lt;br /&gt;
            var accelerationKey = &amp;quot;&amp;quot;;&lt;br /&gt;
            if (i === 9) {&lt;br /&gt;
                accelerationKey = &amp;quot; (&amp;amp;0)&amp;quot;;&lt;br /&gt;
            } else if (i &amp;lt; 10) {&lt;br /&gt;
                accelerationKey = &amp;quot; (&amp;amp;&amp;quot; + (i + 1) + &amp;quot;)&amp;quot;;&lt;br /&gt;
            }&lt;br /&gt;
            var menuNum = TEMPLATE_PROMPT_THRESHOLD + i;&lt;br /&gt;
            if (PROMPT_TEMPLATE[i].submenu) {&lt;br /&gt;
                var subMenuList = PROMPT_TEMPLATE[i].submenu;&lt;br /&gt;
                // テンプレート数チェック&lt;br /&gt;
                if (subMenuList.length &amp;gt; ITEMS_MUX) {&lt;br /&gt;
                    throw new Error(&amp;quot;プロンプトテンプレート (サブメニュー) の数が多すぎます。&amp;quot;);&lt;br /&gt;
                }&lt;br /&gt;
                var subSubMenu = CreatePopupMenu();&lt;br /&gt;
                var subMenuPrefix = String(menuNum);&lt;br /&gt;
                for (var j = 0; j &amp;lt; subMenuList.length; j++) {&lt;br /&gt;
                    var subAccelerationKey = &amp;quot;&amp;quot;;&lt;br /&gt;
                    if (j === 9) {&lt;br /&gt;
                        subAccelerationKey = &amp;quot; (&amp;amp;0)&amp;quot;;&lt;br /&gt;
                    } else if (j &amp;lt; 10) {&lt;br /&gt;
                        subAccelerationKey = &amp;quot; (&amp;amp;&amp;quot; + (j + 1) + &amp;quot;)&amp;quot;;&lt;br /&gt;
                    }&lt;br /&gt;
                    // ES5 compatible version without padStart&lt;br /&gt;
                    var subMenuNum = Number(subMenuPrefix + (j &amp;lt; 10 ? &amp;quot;0&amp;quot; + j : String(j)));&lt;br /&gt;
                    subSubMenu.Add(subMenuList[j].title + subAccelerationKey, subMenuNum);&lt;br /&gt;
                }&lt;br /&gt;
                subMenu.AddPopup(PROMPT_TEMPLATE[i].submenu_title + accelerationKey, subSubMenu);&lt;br /&gt;
            } else {&lt;br /&gt;
                subMenu.Add(PROMPT_TEMPLATE[i].title + accelerationKey, menuNum);&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        if (selectionIsEmpty) {&lt;br /&gt;
            t = &amp;quot;プロンプト テンプレート (対象: アクティブ行) (&amp;amp;T)&amp;quot;;&lt;br /&gt;
        } else {&lt;br /&gt;
            t = &amp;quot;プロンプト テンプレート (対象: 選択範囲) (&amp;amp;T)&amp;quot;;&lt;br /&gt;
        }&lt;br /&gt;
        menu.AddPopup(t, subMenu);&lt;br /&gt;
&lt;br /&gt;
        t = &amp;quot;└ 直前に実行したプロンプト テンプレートを再実行 (&amp;amp;R)&amp;quot;;&lt;br /&gt;
        if (doc.Tag.exists(PREVIOUS_TEMPLATE_PROMPT_TAG)) {&lt;br /&gt;
            menu.Add(t, REEXECUTE_LAST_TEMPLATE_PROMPT);&lt;br /&gt;
        } else {&lt;br /&gt;
            menu.Add(t, REEXECUTE_LAST_TEMPLATE_PROMPT, meMenuGrayed);&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return menu;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function callGroqLlamaAPI(apiKey, promptText, isTemplate) {&lt;br /&gt;
    try {&lt;br /&gt;
        // fetch で Groq API にリクエスト&lt;br /&gt;
        var requestBody = {&lt;br /&gt;
            messages: createGroqLlamaRequest(promptText, isTemplate),&lt;br /&gt;
            stream: false,&lt;br /&gt;
        };&lt;br /&gt;
        for (var key in MODEL_CONFIG) {&lt;br /&gt;
            requestBody[key] = MODEL_CONFIG[key];&lt;br /&gt;
        }&lt;br /&gt;
        if (isTemplate) {&lt;br /&gt;
            // テンプレートの場合は応答を決定論的にする&lt;br /&gt;
            // テンプレート以外の場合はデフォルト値とするために指定しない&lt;br /&gt;
            requestBody.temperature = 0.3;&lt;br /&gt;
        }&lt;br /&gt;
        var url = &#039;https://api.groq.com/openai/v1/chat/completions&#039;;&lt;br /&gt;
&lt;br /&gt;
        var http = CreateObject(&amp;quot;Msxml2.ServerXMLHTTP&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
        http.open(&#039;POST&#039;, url, true);&lt;br /&gt;
        http.setRequestHeader(&#039;Content-Type&#039;, &#039;application/json&#039;);&lt;br /&gt;
        http.setRequestHeader(&#039;Authorization&#039;, &#039;Bearer &#039; + apiKey);&lt;br /&gt;
        http.send(JSON.stringify(requestBody));&lt;br /&gt;
&lt;br /&gt;
        // 応答待機&lt;br /&gt;
        var timeout = 60;&lt;br /&gt;
        var retrySec = 0;&lt;br /&gt;
        var startX = sel.GetActivePointX(mePosLogical);&lt;br /&gt;
        var prevPos = sel.GetActivePos();&lt;br /&gt;
        while (http.readyState != 4) {&lt;br /&gt;
            if (retrySec &amp;gt; timeout) {&lt;br /&gt;
                break;&lt;br /&gt;
            }&lt;br /&gt;
            http.waitForResponse(1);&lt;br /&gt;
            retrySec += 1;&lt;br /&gt;
            inputProgressChar(prevPos);&lt;br /&gt;
            prevPos = sel.GetActivePos();&lt;br /&gt;
        }&lt;br /&gt;
        // 進捗表示文字を削除&lt;br /&gt;
        sel.SetActivePoint(&lt;br /&gt;
            mePosLogical,&lt;br /&gt;
            startX,&lt;br /&gt;
            sel.GetActivePointY(mePosLogical),&lt;br /&gt;
            true&lt;br /&gt;
        );&lt;br /&gt;
        sel.Delete();&lt;br /&gt;
&lt;br /&gt;
        // 応答ステータスチェック&lt;br /&gt;
        if (!(http.readyState == 4 &amp;amp;&amp;amp; http.status == 200)) {&lt;br /&gt;
            sel.Text = &amp;quot;エラーが発生しました: &amp;quot; + http.status + &amp;quot; &amp;quot; + http.statusText + &amp;quot;\n&amp;quot; + http.responseText;&lt;br /&gt;
            http.abort();&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        var ret = &amp;quot;&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
        // JSONをパースして必要なテキストを抽出&lt;br /&gt;
        var response = JSON.parse(http.responseText);&lt;br /&gt;
&lt;br /&gt;
        var choices = response.choices;&lt;br /&gt;
        if (choices &amp;amp;&amp;amp; choices.length &amp;gt; 0) {&lt;br /&gt;
            choices.forEach(function (choice) {&lt;br /&gt;
                var message = choice.message;&lt;br /&gt;
                if (message) {&lt;br /&gt;
                    ret += message.content;&lt;br /&gt;
                }&lt;br /&gt;
            });&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        var usage = response.usage;&lt;br /&gt;
        var usageText = &amp;quot;&amp;quot;;&lt;br /&gt;
        if (usage) {&lt;br /&gt;
            usageText += &amp;quot;\n\n(利用トークン数: プロンプト=&amp;quot; + usage.prompt_tokens&lt;br /&gt;
                + &amp;quot;, 応答=&amp;quot; + usage.completion_tokens&lt;br /&gt;
                + &amp;quot;, 合計=&amp;quot; + usage.total_tokens + &amp;quot;)&amp;quot;;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        sel.Text = ret + usageText;&lt;br /&gt;
&lt;br /&gt;
        // 結果を会話履歴に追加&lt;br /&gt;
        if (HISTORY_SIZE &amp;gt; 0 &amp;amp;&amp;amp; !isTemplate) {&lt;br /&gt;
            appendReturnTextToTag(promptText, ret);&lt;br /&gt;
        }&lt;br /&gt;
    } catch (e) {&lt;br /&gt;
        throw e;&lt;br /&gt;
    } finally {&lt;br /&gt;
        requestCompletedFlg = true;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function roughTokenize(text) {&lt;br /&gt;
    // 超適当なトークナイザ。トークン数をざっくりと多めに数える&lt;br /&gt;
    // 空白文字・ハイフン・アンダースコア区切りの単語に分割して、&lt;br /&gt;
    // 単語がアルファベットのみ、数字のみの場合には 4文字=1トークン として数える。&lt;br /&gt;
    // 単語にアルファベット以外の文字が含まれる場合は 1文字=1トークン として数える。&lt;br /&gt;
&lt;br /&gt;
    var words = text.split(/[\s\-_]+/);&lt;br /&gt;
    var totalTokens = 0;&lt;br /&gt;
&lt;br /&gt;
    for (var i = 0; i &amp;lt; words.length; i++) {&lt;br /&gt;
        var word = words[i];&lt;br /&gt;
        if (!word) continue; // 空の単語をスキップ&lt;br /&gt;
&lt;br /&gt;
        if (/^[a-zA-Z]+$/.test(word) || /^[0-9]+$/.test(word)) {&lt;br /&gt;
            totalTokens += Math.ceil(word.length / 4);&lt;br /&gt;
        } else {&lt;br /&gt;
            totalTokens += word.length;&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return totalTokens;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// document.Tag の内容を加味してGroqLlamaのリクエストを作成&lt;br /&gt;
function createGroqLlamaRequest(promptText, isTemplate) {&lt;br /&gt;
    var customInstructionText = buildCustomInstructionText();&lt;br /&gt;
    // テンプレートの場合は、会話履歴とカスタムインストラクションを反映しない&lt;br /&gt;
    if (isTemplate) {&lt;br /&gt;
        var request = [&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;role&amp;quot;: &amp;quot;user&amp;quot;,&lt;br /&gt;
                &amp;quot;content&amp;quot;: [{ &amp;quot;type&amp;quot;: &amp;quot;text&amp;quot;, &amp;quot;text&amp;quot;: promptText }]&lt;br /&gt;
            }&lt;br /&gt;
        ];&lt;br /&gt;
        if (customInstructionText) {&lt;br /&gt;
            request.unshift({&lt;br /&gt;
                &amp;quot;role&amp;quot;: &amp;quot;system&amp;quot;,&lt;br /&gt;
                &amp;quot;content&amp;quot;: customInstructionText&lt;br /&gt;
            })&lt;br /&gt;
        }&lt;br /&gt;
        return request;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    var tag = document.Tag;&lt;br /&gt;
&lt;br /&gt;
    // 以前の会話が存在しない場合は新規リクエスト&lt;br /&gt;
    if (!tag.exists(HISTORY_TAG)) {&lt;br /&gt;
        var request = [{&lt;br /&gt;
            &amp;quot;role&amp;quot;: &amp;quot;user&amp;quot;,&lt;br /&gt;
            &amp;quot;content&amp;quot;: [{ &amp;quot;type&amp;quot;: &amp;quot;text&amp;quot;, &amp;quot;text&amp;quot;: promptText }]&lt;br /&gt;
        }];&lt;br /&gt;
        if (customInstructionText) {&lt;br /&gt;
            request.unshift({&lt;br /&gt;
                &amp;quot;role&amp;quot;: &amp;quot;system&amp;quot;,&lt;br /&gt;
                &amp;quot;content&amp;quot;: customInstructionText&lt;br /&gt;
            })&lt;br /&gt;
        }&lt;br /&gt;
        return request;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // 会話履歴が存在する場合は、履歴を含めてリクエスト&lt;br /&gt;
    var request = JSON.parse(tag(HISTORY_TAG));&lt;br /&gt;
    if (customInstructionText) {&lt;br /&gt;
        request.unshift({&lt;br /&gt;
            &amp;quot;role&amp;quot;: &amp;quot;system&amp;quot;,&lt;br /&gt;
            &amp;quot;content&amp;quot;: customInstructionText&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
    request.push({&lt;br /&gt;
        &amp;quot;role&amp;quot;: &amp;quot;user&amp;quot;,&lt;br /&gt;
        &amp;quot;content&amp;quot;: promptText&lt;br /&gt;
    });&lt;br /&gt;
    return request;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// document.Tag にGroqLlamaの応答を追加&lt;br /&gt;
function appendReturnTextToTag(userText, modelText) {&lt;br /&gt;
    var tag = document.Tag;&lt;br /&gt;
    if (!tag.exists(HISTORY_TAG)) {&lt;br /&gt;
        tag(HISTORY_TAG) = JSON.stringify([&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;role&amp;quot;: &amp;quot;user&amp;quot;,&lt;br /&gt;
                &amp;quot;content&amp;quot;: userText&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;role&amp;quot;: &amp;quot;assistant&amp;quot;,&lt;br /&gt;
                &amp;quot;content&amp;quot;: modelText&lt;br /&gt;
            }&lt;br /&gt;
        ]);&lt;br /&gt;
    } else {&lt;br /&gt;
        var groqllamaText = JSON.parse(tag(HISTORY_TAG));&lt;br /&gt;
        groqllamaText.push({&lt;br /&gt;
            &amp;quot;role&amp;quot;: &amp;quot;user&amp;quot;,&lt;br /&gt;
            &amp;quot;content&amp;quot;: userText&lt;br /&gt;
        });&lt;br /&gt;
        groqllamaText.push({&lt;br /&gt;
            &amp;quot;role&amp;quot;: &amp;quot;assistant&amp;quot;,&lt;br /&gt;
            &amp;quot;content&amp;quot;: modelText&lt;br /&gt;
        });&lt;br /&gt;
        // 会話セット (user, model 1往復) が特定セット数を超えた場合、最初のセットを削除&lt;br /&gt;
        if (groqllamaText.length &amp;gt; HISTORY_SIZE * 2) {&lt;br /&gt;
            groqllamaText.splice(0, 2);&lt;br /&gt;
        }&lt;br /&gt;
        // 会話履歴全体が MAX_HISTORY_TOKEN を超えた場合、最初のセットを削除&lt;br /&gt;
        var joinedText = &amp;quot;&amp;quot;;&lt;br /&gt;
        for (var i = 0; i &amp;lt; groqllamaText.length; i++) {&lt;br /&gt;
            joinedText += groqllamaText[i].content + &amp;quot;\n&amp;quot;;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        while (roughTokenize(joinedText) &amp;gt; MAX_HISTORY_TOKEN) {&lt;br /&gt;
            groqllamaText.splice(0, 2);&lt;br /&gt;
            joinedText = &amp;quot;&amp;quot;;&lt;br /&gt;
            for (var i = 0; i &amp;lt; groqllamaText.length; i++) {&lt;br /&gt;
                joinedText += groqllamaText[i].content + &amp;quot;\n&amp;quot;;&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        tag(HISTORY_TAG) = JSON.stringify(groqllamaText);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function resetTag() {&lt;br /&gt;
    var tag = doc.Tag;&lt;br /&gt;
    if (tag.exists(HISTORY_TAG)) {&lt;br /&gt;
        tag.remove(HISTORY_TAG);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function baseCustomInstruction() {&lt;br /&gt;
    var language = LANGUAGE.trim();&lt;br /&gt;
    return (language ? &amp;quot;Respond in &amp;quot; + language + &amp;quot;.\n&amp;quot; : &amp;quot;&amp;quot;)&lt;br /&gt;
        + &amp;quot;Do not hallucinate. If you are unsure or don&#039;t have enough information to answer with confidence, say \&amp;quot;I don&#039;t know\&amp;quot; or \&amp;quot;I&#039;m not sure.\&amp;quot;\n&amp;quot;&lt;br /&gt;
        + &amp;quot;Do not use full-width brackets \&amp;quot;（）\&amp;quot;, but use half-width brackets \&amp;quot;()\&amp;quot;. Enclose the outside of the half-width brackets with half-width spaces.\n&amp;quot;&lt;br /&gt;
        + &amp;quot;When using Markdown formatting, please follow these rules:\n&amp;quot;&lt;br /&gt;
        + &amp;quot;  - You MUST include a blank line below the heading (a line beginning with one or more # characters).\n&amp;quot;&lt;br /&gt;
        + &amp;quot;  - For bold formatting (text surrounded by **), italic formatting (text surrounded by *), and inline code blocks (text surrounded by `), you MUST surround each formatting symbol with a space.\n&amp;quot;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function buildCustomInstructionText() {&lt;br /&gt;
    var customInstruction = getTemporaryCustomInstruction();&lt;br /&gt;
    if (!customInstruction) {&lt;br /&gt;
        // 一時的なカスタムインストラクションがない場合はデフォルトを使用&lt;br /&gt;
        customInstruction = DEFAULT_CUSTOM_INSTRUCTION;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    customInstruction = customInstruction.replace(/^[\s　]+|[\s　]+$/g, &amp;quot;&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
    return (customInstruction ? customInstruction + &amp;quot;\n&amp;quot; : &amp;quot;&amp;quot;)&lt;br /&gt;
        + baseCustomInstruction();&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function configureTemporaryCustomInstruction() {&lt;br /&gt;
    var tempCustomInstruction = getTemporaryCustomInstruction();&lt;br /&gt;
    var prompt = Prompt(&amp;quot;カスタムインストラクションを変更 ※変更すると会話履歴がリセットされます&amp;quot;, tempCustomInstruction ? tempCustomInstruction : DEFAULT_CUSTOM_INSTRUCTION);&lt;br /&gt;
    if (prompt) {&lt;br /&gt;
        if (prompt === DEFAULT_CUSTOM_INSTRUCTION) {&lt;br /&gt;
            // デフォルトと同じ値の場合はタグを削除&lt;br /&gt;
            setTemporaryCustomInstruction(&amp;quot;&amp;quot;);&lt;br /&gt;
        } else if (prompt !== tempCustomInstruction) {&lt;br /&gt;
            // デフォルト以外の値が設定された場合は保存&lt;br /&gt;
            setTemporaryCustomInstruction(prompt);&lt;br /&gt;
        }&lt;br /&gt;
    } else {&lt;br /&gt;
        // 空文字でOK、もしくはキャンセルされた場合はデフォルトに戻すか確認&lt;br /&gt;
        if (Confirm(&amp;quot;カスタムインストラクションをデフォルトに戻しますか？&amp;quot;)) {&lt;br /&gt;
            setTemporaryCustomInstruction(&amp;quot;&amp;quot;);&lt;br /&gt;
        } else {&lt;br /&gt;
            alert(&amp;quot;カスタムインストラクションの変更をキャンセルしました。\n※カスタムインストラクションを空にしたい場合はスペースのみを入力してください。&amp;quot;);&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // 変更が加わっている場合は会話履歴をリセット&lt;br /&gt;
    if (tempCustomInstruction !== getTemporaryCustomInstruction()) {&lt;br /&gt;
        resetTag();&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getTemporaryCustomInstruction() {&lt;br /&gt;
    var tag = doc.Tag;&lt;br /&gt;
    if (tag.exists(TEMPORARY_CUSTOM_INSTRUCTION_TAG)) {&lt;br /&gt;
        return tag(TEMPORARY_CUSTOM_INSTRUCTION_TAG);&lt;br /&gt;
    } else {&lt;br /&gt;
        return &amp;quot;&amp;quot;;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function setTemporaryCustomInstruction(temporaryCustomInstruction) {&lt;br /&gt;
    var tag = doc.Tag;&lt;br /&gt;
    if (temporaryCustomInstruction) {&lt;br /&gt;
        tag(TEMPORARY_CUSTOM_INSTRUCTION_TAG) = temporaryCustomInstruction;&lt;br /&gt;
    } else {&lt;br /&gt;
        tag.remove(TEMPORARY_CUSTOM_INSTRUCTION_TAG);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function addPreviousTemplatePromptNumberToTag(menuTrack) {&lt;br /&gt;
    var tag = doc.Tag;&lt;br /&gt;
    tag(PREVIOUS_TEMPLATE_PROMPT_TAG) = menuTrack;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function inputProgressChar(pos) {&lt;br /&gt;
    sel.SetActivePos(pos);&lt;br /&gt;
    sel.Text = &amp;quot;.&amp;quot;;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;/div&gt;</summary>
		<author><name>Yuko</name></author>
	</entry>
	<entry>
		<id>https://www.haijin-boys.com/wiki/index.php?title=Groq_%E3%81%AB%E7%9B%B8%E8%AB%87&amp;diff=9165</id>
		<title>Groq に相談</title>
		<link rel="alternate" type="text/html" href="https://www.haijin-boys.com/wiki/index.php?title=Groq_%E3%81%AB%E7%9B%B8%E8%AB%87&amp;diff=9165"/>
		<updated>2025-07-23T22:58:05Z</updated>

		<summary type="html">&lt;p&gt;Yuko: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== 概要 ==&lt;br /&gt;
&lt;br /&gt;
生成AIプラットフォームである [https://groq.com GroqCloud] で各種AIを利用するためのMeryマクロです。&lt;br /&gt;
※GroqCloud で提供されるモデルは複数あるため、適当なタイミングで更新をかけています。&lt;br /&gt;
&lt;br /&gt;
「Google Geminiに相談」マクロでは動作中のストリーム通信に対応するためにV8エンジンで実行していましたが、V8エンジン版は使用上の制限からマクロ実行を開始するまで1秒程度のタイムラグが発生していました。GroqCloudの売りである超高速レスポンスを見込んで本マクロではストリーム通信を使わないようにしました。これにより従来のJScriptで処理を組み立てることができるため、前述のタイムラグが起こらないようになりました。また、ストリーム通信を使わないことで通信量が少なく応答もシンプルになるため、場合によっては「Google Geminiに相談」マクロよりも処理が安定する可能性があります。&lt;br /&gt;
&lt;br /&gt;
【主な機能】&lt;br /&gt;
&lt;br /&gt;
* GroqCloud APIを利用して生成AIと対話できるMery用のマクロ&lt;br /&gt;
* 選択したテキストやアクティブな行の内容について生成AIに質問・相談が可能&lt;br /&gt;
* 会話履歴を保持し、文脈を考慮した対話が可能&lt;br /&gt;
&lt;br /&gt;
【特徴的な機能】&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;会話履歴機能&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* 事前の設定数に応じた会話履歴を保持&lt;br /&gt;
* 履歴のクリアが可能&lt;br /&gt;
&lt;br /&gt;
これにより、会話の文脈を考慮した回答を得られます。&lt;br /&gt;
※デフォルトはオフ。「HISTORY_SIZE」変数を増やすと履歴を保持するようになります。&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;カスタムインストラクション&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* AIの応答方針を設定可能&lt;br /&gt;
* 一時的なカスタム指示の保存と変更が可能&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;プロンプトテンプレート&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* 誤字脱字チェック&lt;br /&gt;
* 日本語翻訳&lt;br /&gt;
* コードの最適化、生成&lt;br /&gt;
&lt;br /&gt;
など、定型的な質問を簡単に実行できます。&lt;br /&gt;
※テンプレート機能では回答を安定させるため、前述の会話履歴を文脈に含みません。&lt;br /&gt;
&lt;br /&gt;
テンプレートは自身の使い方に合わせて好きに追加、削除ができます。詳細はソースコード内の「PROMPT_TEMPLATE」定数のコメントを参照。&lt;br /&gt;
&lt;br /&gt;
== 注意事項 (必ず読んでからご利用ください) ==&lt;br /&gt;
&lt;br /&gt;
* 本マクロでは 2025/04/13 時点で無料利用ができるGroqCloud APIを利用します。今後、プラットフォームの改定により有償になったり、設定誤り等で費用が発生しても、作者は一切の責任を負いません。&lt;br /&gt;
* GroqCloud APIではリクエスト内容をAI学習に用いないことをプライバシポリシーとして謳っているものの、くれぐれも機密情報やプライバシーに関わる情報を指定しないように注意してください。&lt;br /&gt;
* GroqCloud APIはプラットフォームの利用規約に則ってご利用ください。&lt;br /&gt;
&lt;br /&gt;
== 利用前提 ==&lt;br /&gt;
&lt;br /&gt;
* Mery Ver 3.7.13 以上&lt;br /&gt;
* GroqCloudで、GroqCloud API APIキーを取得していること (参考 [https://console.groq.com/docs/quickstart Quickstart])&lt;br /&gt;
&lt;br /&gt;
== 利用方法 ==&lt;br /&gt;
&lt;br /&gt;
* 環境変数の設定&lt;br /&gt;
:コマンド プロンプトで以下のコマンドを実行し、環境変数を設定してください。&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bat&amp;quot; copy&amp;gt;&lt;br /&gt;
setx GROQ_API_KEY &amp;quot;***************************************&amp;quot;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
:設定後、Mery または Windows の再起動が必要な場合があります。&lt;br /&gt;
* マクロの実行&lt;br /&gt;
**プロンプトにしたい文字列を選択してマクロを実行すると、選択したテキストがプロンプトとしてGemini APIに送信され、応答がエディタに表示されます。&lt;br /&gt;
* 会話の履歴数を調整&lt;br /&gt;
** 会話の履歴は設定した個数分が保持され、文脈として利用されます。適宜、ソースコード内の「HISTORY_SIZE」の数を調整してください。&lt;br /&gt;
** ※数を増やすほど会話内容を保持して文脈を理解するようになりますが、メモリ使用量が増えたり動作が重くなる可能性があります。&lt;br /&gt;
** ※開いている文書ごとに履歴を保持します。文脈をリセットしたいときは、新しい文書を開いて会話をやり直してください。&lt;br /&gt;
&lt;br /&gt;
== ソースコード ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot; copy&amp;gt;&lt;br /&gt;
#title = &amp;quot;Groq に相談&amp;quot;&lt;br /&gt;
BeginUndoGroup();&lt;br /&gt;
&lt;br /&gt;
// カスタムインストラクション&lt;br /&gt;
// これを設定することで、生成AIの回答全体への生成方針を指示できます。&lt;br /&gt;
// 例えば &amp;quot;あなたは「Kimi」という名前のギャルAIアシスタントです。「マジ」「めっちゃ」「...だねー！」などのラフな言葉を使い、敬語は使いません。絵文字を使って感情表現をします。一人称は「あたし」。人懐っこく、バイタリティ溢れる性格で、ユーザーの課題を協力して解決します。&amp;quot; と設定すると、AIの雰囲気が元気な子になります。&lt;br /&gt;
// 何も指示しない場合は、空文字 &amp;quot;&amp;quot; を設定してください。&lt;br /&gt;
var DEFAULT_CUSTOM_INSTRUCTION = &amp;quot;&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
// プロンプトテンプレート&lt;br /&gt;
// プロンプトテンプレートを設定することで、AIに対して特定の指示を出すことができます。&lt;br /&gt;
// プロンプトテンプレートでの生成は会話履歴とカスタムインストラクションを反映しません。&lt;br /&gt;
// テンプレートは以下の { ～ }, で囲まれたブロックを好きに増減させて利用できます。&lt;br /&gt;
var PROMPT_TEMPLATE = [&lt;br /&gt;
    // 以下はサンプルです。1階層メニューと2階層メニューが利用できます。&lt;br /&gt;
    // {&lt;br /&gt;
    //     &amp;quot;title&amp;quot;: &amp;quot;要約&amp;quot;,&lt;br /&gt;
    //     &amp;quot;prompt&amp;quot;: &amp;quot;以下の文章を要約してください。&amp;quot;&lt;br /&gt;
    // },&lt;br /&gt;
    // {&lt;br /&gt;
    //     &amp;quot;submenu_title&amp;quot;: &amp;quot;翻訳系&amp;quot;,&lt;br /&gt;
    //     &amp;quot;submenu&amp;quot;: [&lt;br /&gt;
    //         {&lt;br /&gt;
    //             &amp;quot;title&amp;quot;: &amp;quot;日本語翻訳&amp;quot;,&lt;br /&gt;
    //             &amp;quot;prompt&amp;quot;: &amp;quot;以下の文章を日本語に翻訳してください。&amp;quot;&lt;br /&gt;
    //         },&lt;br /&gt;
    //         {&lt;br /&gt;
    //             &amp;quot;title&amp;quot;: &amp;quot;英語翻訳&amp;quot;,&lt;br /&gt;
    //             &amp;quot;prompt&amp;quot;: &amp;quot;以下の文章を英語に翻訳してください。&amp;quot;&lt;br /&gt;
    //         },&lt;br /&gt;
    //     ]&lt;br /&gt;
    // },&lt;br /&gt;
&lt;br /&gt;
    {&lt;br /&gt;
        &amp;quot;submenu_title&amp;quot;: &amp;quot;理解を深める&amp;quot;,&lt;br /&gt;
        &amp;quot;submenu&amp;quot;: [&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;論点を整理する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の論点を整理して。&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;論理の抜け漏れを探す&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の論点を整理し、忖度せずに鋭く、論理の抜け漏れを抽出して。そして、そこから発生する可能性のあるリスクも教えて。&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;要約する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;要約して。専門的な用語と思われる単語は簡単に解説も入れて。&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;物語対話形式で教えて&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の内容について物語対話形式で教えてください。&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
        ]&lt;br /&gt;
    },&lt;br /&gt;
    {&lt;br /&gt;
        &amp;quot;submenu_title&amp;quot;: &amp;quot;翻訳する&amp;quot;,&lt;br /&gt;
        &amp;quot;submenu&amp;quot;: [&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;日本語に翻訳&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の文章を日本語に翻訳してください。\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;出力形式は以下にすること:\n[訳]\n翻訳文...\n&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;英語に翻訳&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の文章を英語に翻訳してください。\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;出力形式は以下にすること:\n[Translation]\n翻訳文...\n&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;英語-&amp;gt;日本語 スラッシュリーディング翻訳&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の英文を日本語にスラッシュリーディング形式で翻訳してください。\n\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;条件:\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 英文の各フレーズをスラッシュ(/)で区切って表示\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 各フレーズの日本語訳を括弧()内に併記\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 文の終わりはダブルスラッシュ(//)で表示\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 英語の語順に従って日本語訳を行う\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 日本語訳は自然な日本語である必要はなく、英語の語順を反映した直訳調で構わない\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 主語が省略されている場合は補って翻訳すること\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 一般的な英文で頻出する表現がある場合は次の行に記載\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 以下の出力例のように &amp;lt;英文&amp;gt; (日本語訳) / ... とスラッシュ区切りの繰り返しになる \n\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;出力例:\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;Snowstorms are forecast (暴風雪が予報されている) / to hit the Sea of Japan coasts (日本海沿岸を襲うと) / of northern to eastern Japan (北部から東部の) / from Sunday through Monday. (日曜から月曜にかけて。) //\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;【頻出表現】\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- be forecast to: ～すると予報されている\n&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
        ]&lt;br /&gt;
    },&lt;br /&gt;
    {&lt;br /&gt;
        &amp;quot;submenu_title&amp;quot;: &amp;quot;プログラミング支援&amp;quot;,&lt;br /&gt;
        &amp;quot;submenu&amp;quot;: [&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;コードを洗練させる&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下のコードを洗練させてください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;コードをレビューする&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下のコードをセキュリティと保守性の観点でレビューしてください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;エラーログをデバッグする&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下のエラーログを解析し、考えられる原因と対策を提示してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;SQLクエリを最適化する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下のSQL文を最適化してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;PowerShell スクリプトを書く&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の処理を行うPowerShellスクリプトを生成してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;Shell Script を書く&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の処理を行うShell Scriptを生成してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;Linux コマンドを提案&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;あなたはLinuxエキスパートとして、以下のLinuxコマンドを提案してください。\n\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;制約条件:\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 多数のディストリビューションで採用されているメジャーなコマンドを使用し、ポータブルな実装にすること\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- bashでの実行を前提にすること\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 可能な限りシンプルな実装にすること\n\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;期待する出力:\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- コマンドの具体的な実装方法\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 各コマンドの動作説明\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 制限事項\n&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
        ]&lt;br /&gt;
    },&lt;br /&gt;
    {&lt;br /&gt;
        &amp;quot;submenu_title&amp;quot;: &amp;quot;ビジネス文書作成支援&amp;quot;,&lt;br /&gt;
        &amp;quot;submenu&amp;quot;: [&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;誤字脱字、不正確な表現をチェック&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の2つのカテゴリーで問題点を指摘してください\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 【誤字脱字】：明確な誤字脱字および誤字の疑いがある箇所\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 【不正確・曖昧な表現】：解釈に個人差が生じる可能性がある表現、より適切な表現に改善できる箇所&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;提案書を作成する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下に対する対する提案書を、コスト・実現性・リスクを含めて作成してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;仕様書のアウトラインを作成する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の内容で仕様書のアウトラインを作成してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;手順書のアウトラインを作成する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の作業を行う手順書を、前提条件と注意事項を含めてアウトラインを作成してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;ユーザーマニュアルのアウトラインを作成する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の機能内容から、ユーザー向けマニュアルのアウトラインを作成してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;議事録を作成する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の会議内容から議事録を作成してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;進捗報告書を作成する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下のタスク状況から進捗報告書を作成してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
        ]&lt;br /&gt;
    },&lt;br /&gt;
    {&lt;br /&gt;
        &amp;quot;submenu_title&amp;quot;: &amp;quot;分析支援&amp;quot;,&lt;br /&gt;
        &amp;quot;submenu&amp;quot;: [&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;問題を分析し、課題を抽出する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の状況から問題や潜在的リスクを分析し、課題を抽出してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;対応策を提案する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の問題・課題に対する対応策を複数提案してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;傾向を見つける&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の内容から、数値や挙動上の傾向を見つけてください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
        ]&lt;br /&gt;
    },&lt;br /&gt;
    {&lt;br /&gt;
        &amp;quot;submenu_title&amp;quot;: &amp;quot;アイデア出し&amp;quot;,&lt;br /&gt;
        &amp;quot;submenu&amp;quot;: [&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;SCAMPER法でブレインストーミング&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下のテーマについて、SCAMPER法を使って15個のアイデアを出してください。制約条件は以下の通りです：&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;解決策の幅出し&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の課題に対して、技術面、運用面、コスト面から実現可能な解決策を5つずつ提案してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;業務改善案の提案&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の既存プロセスについて、効率化、コスト削減、品質向上の観点から改善案を提案してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;逆転の発想&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の目標に対して、通常とは逆の視点からアプローチする方法を3つ提案してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;競合分析からの着想&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の内容について、業界における競合他社の特徴を分析し、差別化できる新しいアプローチを提案してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;ユースケースの列挙&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の製品・サービスについて、想定されるユースケースを10個列挙し、それぞれの価値提案を記載してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
        ]&lt;br /&gt;
    },&lt;br /&gt;
]&lt;br /&gt;
&lt;br /&gt;
// 生成AIの回答の言語設定&lt;br /&gt;
// &amp;quot;Japanese&amp;quot; や &amp;quot;English&amp;quot; を設定できます。他の言語は未検証。&lt;br /&gt;
// 自動設定にする場合は空文字 &amp;quot;&amp;quot; を設定してください。&lt;br /&gt;
var LANGUAGE = &amp;quot;Japanese&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
// 入力プロンプトの最大トークン数&lt;br /&gt;
// 超適当なトークナイザー関数により計測し、英単語は4文字1トークン、それ以外は1文字1トークンとして数えます。&lt;br /&gt;
var MAX_PROMPT_TOKEN = 6000;&lt;br /&gt;
&lt;br /&gt;
// 会話の履歴を保持する数 (質問と回答で1セット)&lt;br /&gt;
// 数を増やすと、会話の履歴が増えますが、Meryのメモリ使用量が増えたり、&lt;br /&gt;
// 使用トークン数が増えて動作が重くなる可能性があるため、適宜調整してください。&lt;br /&gt;
// document.Tag に保持されるため、開いているファイルを閉じると履歴は消えます。&lt;br /&gt;
var HISTORY_SIZE = 0;&lt;br /&gt;
&lt;br /&gt;
// 会話履歴に保持する最大トークン数&lt;br /&gt;
// 会話履歴のトークン数がこの数を超える場合、古い履歴から削除されます。&lt;br /&gt;
var MAX_HISTORY_TOKEN = 10000;&lt;br /&gt;
&lt;br /&gt;
// コンテキストメニュー番号の定義&lt;br /&gt;
var EXECUTE_AI = 1;&lt;br /&gt;
var RESET_HISTORY = 2;&lt;br /&gt;
var SET_CUSTOM_INSTRUCTION = 3;&lt;br /&gt;
var REEXECUTE_LAST_TEMPLATE_PROMPT = 4;&lt;br /&gt;
var TEMPLATE_PROMPT_THRESHOLD = 10;&lt;br /&gt;
var TEMPLATE_PROMPT_SUBMENU_THRESHOLD = 1000;&lt;br /&gt;
&lt;br /&gt;
// タグ名の定義&lt;br /&gt;
var HISTORY_TAG = &amp;quot;groqllama-history&amp;quot;;&lt;br /&gt;
var TEMPORARY_CUSTOM_INSTRUCTION_TAG = &amp;quot;groqllama-temporary-custom-instruction&amp;quot;;&lt;br /&gt;
var PREVIOUS_TEMPLATE_PROMPT_TAG = &amp;quot;groqllama-previous-template-prompt&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
// 生成AIモデル別設定&lt;br /&gt;
&lt;br /&gt;
// const MODEL_CONFIG = {&lt;br /&gt;
//     model: &amp;quot;meta-llama/llama-4-maverick-17b-128e-instruct&amp;quot;,&lt;br /&gt;
//     max_tokens: 3500,&lt;br /&gt;
//     temperature: 0.6,&lt;br /&gt;
// }&lt;br /&gt;
&lt;br /&gt;
// const MODEL_CONFIG = {&lt;br /&gt;
//     model: &amp;quot;qwen/qwen3-32b&amp;quot;,&lt;br /&gt;
//     max_tokens: 5000,&lt;br /&gt;
//     temperature: 0.6,&lt;br /&gt;
//     max_completion_tokens: 1024,&lt;br /&gt;
//     reasoning_effort: &amp;quot;default&amp;quot;,&lt;br /&gt;
//     reasoning_format: &amp;quot;hidden&amp;quot;&lt;br /&gt;
// }&lt;br /&gt;
&lt;br /&gt;
const MODEL_CONFIG = {&lt;br /&gt;
    model: &amp;quot;moonshotai/kimi-k2-instruct&amp;quot;,&lt;br /&gt;
    max_tokens: 5000,&lt;br /&gt;
    temperature: 0.7,&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// console.log に出力する内容を Mery のアウトプットバーに表示する&lt;br /&gt;
var console = {&lt;br /&gt;
    log: function (str) {&lt;br /&gt;
        outputBar.Writeln(str)&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
var doc = document;&lt;br /&gt;
var sel = document.selection;&lt;br /&gt;
&lt;br /&gt;
// 生成リクエスト中止フラグ&lt;br /&gt;
var abortRequestFlg = false&lt;br /&gt;
// 生成リクエスト完了フラグ;&lt;br /&gt;
var requestCompletedFlg = false;&lt;br /&gt;
&lt;br /&gt;
main();&lt;br /&gt;
&lt;br /&gt;
function main() {&lt;br /&gt;
    var selectionIsEmpty = sel.IsEmpty&lt;br /&gt;
&lt;br /&gt;
    // プロンプトの取得&lt;br /&gt;
    if (selectionIsEmpty) {&lt;br /&gt;
        sel.StartOfLine(false, mePosLogical);&lt;br /&gt;
        sel.EndOfLine(true, mePosLogical);&lt;br /&gt;
    }&lt;br /&gt;
    var promptText = sel.Text.trim();&lt;br /&gt;
    var promptLength = sel.TextLength;&lt;br /&gt;
    var menuTrack = 0;&lt;br /&gt;
    while (true) {&lt;br /&gt;
        // コンテキストメニューの作成&lt;br /&gt;
        var menu = createContextMenu(selectionIsEmpty);&lt;br /&gt;
        menuTrack = menu.Track(0);&lt;br /&gt;
        // メニュー選択がキャンセルされた場合は終了&lt;br /&gt;
        if (menuTrack === 0) return;&lt;br /&gt;
&lt;br /&gt;
        // 会話履歴のリセット&lt;br /&gt;
        if (menuTrack === RESET_HISTORY) {&lt;br /&gt;
            resetTag();&lt;br /&gt;
            continue;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        // カスタムインストラクションの設定&lt;br /&gt;
        if (menuTrack === SET_CUSTOM_INSTRUCTION) {&lt;br /&gt;
            configureTemporaryCustomInstruction();&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        break;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // プロンプトが空の場合はエラー&lt;br /&gt;
    if (promptLength === 0) {&lt;br /&gt;
        alert(&amp;quot;プロンプトを入力してください。&amp;quot;);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
    // 文字数が多すぎる場合はエラー&lt;br /&gt;
    if (roughTokenize(promptText) &amp;gt; MAX_PROMPT_TOKEN) {&lt;br /&gt;
        alert(&amp;quot;プロンプトの文字数が多すぎます。文字数を少なくしてください。&amp;quot;);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
    // 読み取り専用の場合はエラー&lt;br /&gt;
    if (doc.ReadOnly) {&lt;br /&gt;
        alert(&amp;quot;ファイルが読み取り専用のため実行できません。&amp;quot;);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // 直前に実行したプロンプトテンプレートの再実行の場合、menuTrack を書き換え&lt;br /&gt;
    if (menuTrack === REEXECUTE_LAST_TEMPLATE_PROMPT) {&lt;br /&gt;
        if (doc.Tag.exists(PREVIOUS_TEMPLATE_PROMPT_TAG)) {&lt;br /&gt;
            menuTrack = Number(doc.Tag(PREVIOUS_TEMPLATE_PROMPT_TAG));&lt;br /&gt;
        } else {&lt;br /&gt;
            alert(&amp;quot;直前に実行したプロンプトテンプレートがありません。&amp;quot;);&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // テンプレートの場合はプロンプトを変更&lt;br /&gt;
    var isTemplate = menuTrack &amp;gt;= TEMPLATE_PROMPT_THRESHOLD;&lt;br /&gt;
    var isSubmenu = menuTrack &amp;gt;= TEMPLATE_PROMPT_SUBMENU_THRESHOLD;&lt;br /&gt;
    if (isSubmenu) {&lt;br /&gt;
        var parentMenuTrack = Number(String(menuTrack).slice(0, 2));&lt;br /&gt;
        var subMenuTrack = Number(String(menuTrack).slice(2));&lt;br /&gt;
        promptText = &amp;quot;=== INSTRUCTIONS START HERE ===\n\n&amp;quot;&lt;br /&gt;
            + PROMPT_TEMPLATE[parentMenuTrack - TEMPLATE_PROMPT_THRESHOLD].submenu[subMenuTrack].prompt&lt;br /&gt;
            + &amp;quot;\n\n=== INSTRUCTIONS END HERE ===\n\n&amp;quot;&lt;br /&gt;
            + &amp;quot;=== INPUT DATA STARTS HERE ===\n\n&amp;quot;&lt;br /&gt;
            + promptText&lt;br /&gt;
            + &amp;quot;\n\n=== INPUT DATA END HERE ===&amp;quot;;&lt;br /&gt;
    } else if (isTemplate) {&lt;br /&gt;
        promptText = &amp;quot;=== INSTRUCTIONS START HERE ===\n\n&amp;quot;&lt;br /&gt;
            + PROMPT_TEMPLATE[menuTrack - TEMPLATE_PROMPT_THRESHOLD].prompt&lt;br /&gt;
            + &amp;quot;\n\n=== INSTRUCTIONS END HERE ===\n\n&amp;quot;&lt;br /&gt;
            + &amp;quot;=== INPUT DATA STARTS HERE ===\n\n&amp;quot;&lt;br /&gt;
            + promptText&lt;br /&gt;
            + &amp;quot;\n\n=== INPUT DATA END HERE ===&amp;quot;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // テンプレートの場合は直前に実行したプロンプトテンプレートの番号としてタグに保存&lt;br /&gt;
    if (isTemplate) {&lt;br /&gt;
        addPreviousTemplatePromptNumberToTag(menuTrack);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    try {&lt;br /&gt;
        // 処理終了を待機&lt;br /&gt;
        shell.KeepRunning = true;&lt;br /&gt;
&lt;br /&gt;
        // カーソル位置移動&lt;br /&gt;
        sel.SetActivePoint(mePosLogical, sel.GetBottomPointX(mePosLogical), sel.GetBottomPointY(mePosLogical));&lt;br /&gt;
        sel.EndOfLine(false, mePosLogical);&lt;br /&gt;
        sel.NewLine();&lt;br /&gt;
&lt;br /&gt;
        // APIキーの取得&lt;br /&gt;
        var apiKey = shell.getEnv(&amp;quot;GROQ_API_KEY&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
        // GroqLlama API に問い合わせ&lt;br /&gt;
        sel.Text = &amp;quot;\n---\n\n&amp;quot;&lt;br /&gt;
        document.KeepScrollPos = true;&lt;br /&gt;
        callGroqLlamaAPI(apiKey, promptText, isTemplate);&lt;br /&gt;
        if (sel.GetActivePointX(mePosLogical) &amp;gt; 2) {&lt;br /&gt;
            sel.Text = &amp;quot;\n\n---\n\n\n&amp;quot;&lt;br /&gt;
        } else {&lt;br /&gt;
            sel.Text = &amp;quot;\n---\n\n\n&amp;quot;&lt;br /&gt;
        }&lt;br /&gt;
    } catch (e) {&lt;br /&gt;
        sel.Text = &amp;quot;&amp;lt;処理中にエラー発生&amp;gt;\n&amp;quot; + e.message + &amp;quot;\n&amp;quot; + e.stack;&lt;br /&gt;
    } finally {&lt;br /&gt;
        // 処理終了待機を解除&lt;br /&gt;
        shell.KeepRunning = false;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function createContextMenu(selectionIsEmpty) {&lt;br /&gt;
    var menu = CreatePopupMenu();&lt;br /&gt;
    menu.Add(&amp;quot;※注意※ プライバシー・機密情報を送信しないこと&amp;quot;, 0);&lt;br /&gt;
    menu.Add(&amp;quot;&amp;quot;, 0, meMenuSeparator);&lt;br /&gt;
    var t = &amp;quot;&amp;quot;;&lt;br /&gt;
    if (selectionIsEmpty) {&lt;br /&gt;
        t = &amp;quot;アクティブ行の内容で相談 (&amp;amp;A)&amp;quot;;&lt;br /&gt;
    } else {&lt;br /&gt;
        t = &amp;quot;選択範囲の内容で相談 (&amp;amp;A)&amp;quot;;&lt;br /&gt;
    }&lt;br /&gt;
    menu.Add(t, EXECUTE_AI);&lt;br /&gt;
    if (HISTORY_SIZE &amp;lt;= 0) {&lt;br /&gt;
        menu.Add(&amp;quot;├ 会話履歴をクリア (履歴が無効になっています)&amp;quot;, RESET_HISTORY, meMenuGrayed);&lt;br /&gt;
    } else if (doc.Tag.exists(HISTORY_TAG)) {&lt;br /&gt;
        var turn = JSON.parse(doc.Tag(HISTORY_TAG)).length / 2;&lt;br /&gt;
        menu.Add(&amp;quot;├ 会話履歴をクリア (現在の会話数: &amp;quot; + turn + &amp;quot;) (&amp;amp;C)&amp;quot;, RESET_HISTORY);&lt;br /&gt;
    } else {&lt;br /&gt;
        menu.Add(&amp;quot;├ 会話履歴をクリア (現在の会話数: 0) (&amp;amp;C)&amp;quot;, RESET_HISTORY, meMenuGrayed);&lt;br /&gt;
    }&lt;br /&gt;
    menu.Add(&amp;quot;└ カスタムインストラクションを変更 (&amp;amp;I)&amp;quot;, SET_CUSTOM_INSTRUCTION);&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    // テンプレート数チェック&lt;br /&gt;
    // 2桁まで許容&lt;br /&gt;
    var ITEMS_MUX = 99;&lt;br /&gt;
    if (PROMPT_TEMPLATE.length &amp;gt; ITEMS_MUX - TEMPLATE_PROMPT_THRESHOLD) {&lt;br /&gt;
        throw new Error(&amp;quot;プロンプトテンプレートの数が多すぎます。&amp;quot;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // テンプレートの追加&lt;br /&gt;
    if (PROMPT_TEMPLATE.length &amp;gt; 0) {&lt;br /&gt;
        var subMenu = CreatePopupMenu();&lt;br /&gt;
        for (var i = 0; i &amp;lt; PROMPT_TEMPLATE.length; i++) {&lt;br /&gt;
            var accelerationKey = &amp;quot;&amp;quot;;&lt;br /&gt;
            if (i === 9) {&lt;br /&gt;
                accelerationKey = &amp;quot; (&amp;amp;0)&amp;quot;;&lt;br /&gt;
            } else if (i &amp;lt; 10) {&lt;br /&gt;
                accelerationKey = &amp;quot; (&amp;amp;&amp;quot; + (i + 1) + &amp;quot;)&amp;quot;;&lt;br /&gt;
            }&lt;br /&gt;
            var menuNum = TEMPLATE_PROMPT_THRESHOLD + i;&lt;br /&gt;
            if (PROMPT_TEMPLATE[i].submenu) {&lt;br /&gt;
                var subMenuList = PROMPT_TEMPLATE[i].submenu;&lt;br /&gt;
                // テンプレート数チェック&lt;br /&gt;
                if (subMenuList.length &amp;gt; ITEMS_MUX) {&lt;br /&gt;
                    throw new Error(&amp;quot;プロンプトテンプレート (サブメニュー) の数が多すぎます。&amp;quot;);&lt;br /&gt;
                }&lt;br /&gt;
                var subSubMenu = CreatePopupMenu();&lt;br /&gt;
                var subMenuPrefix = String(menuNum);&lt;br /&gt;
                for (var j = 0; j &amp;lt; subMenuList.length; j++) {&lt;br /&gt;
                    var subAccelerationKey = &amp;quot;&amp;quot;;&lt;br /&gt;
                    if (j === 9) {&lt;br /&gt;
                        subAccelerationKey = &amp;quot; (&amp;amp;0)&amp;quot;;&lt;br /&gt;
                    } else if (j &amp;lt; 10) {&lt;br /&gt;
                        subAccelerationKey = &amp;quot; (&amp;amp;&amp;quot; + (j + 1) + &amp;quot;)&amp;quot;;&lt;br /&gt;
                    }&lt;br /&gt;
                    // ES5 compatible version without padStart&lt;br /&gt;
                    var subMenuNum = Number(subMenuPrefix + (j &amp;lt; 10 ? &amp;quot;0&amp;quot; + j : String(j)));&lt;br /&gt;
                    subSubMenu.Add(subMenuList[j].title + subAccelerationKey, subMenuNum);&lt;br /&gt;
                }&lt;br /&gt;
                subMenu.AddPopup(PROMPT_TEMPLATE[i].submenu_title + accelerationKey, subSubMenu);&lt;br /&gt;
            } else {&lt;br /&gt;
                subMenu.Add(PROMPT_TEMPLATE[i].title + accelerationKey, menuNum);&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        if (selectionIsEmpty) {&lt;br /&gt;
            t = &amp;quot;プロンプト テンプレート (対象: アクティブ行) (&amp;amp;T)&amp;quot;;&lt;br /&gt;
        } else {&lt;br /&gt;
            t = &amp;quot;プロンプト テンプレート (対象: 選択範囲) (&amp;amp;T)&amp;quot;;&lt;br /&gt;
        }&lt;br /&gt;
        menu.AddPopup(t, subMenu);&lt;br /&gt;
&lt;br /&gt;
        t = &amp;quot;└ 直前に実行したプロンプト テンプレートを再実行 (&amp;amp;R)&amp;quot;;&lt;br /&gt;
        if (doc.Tag.exists(PREVIOUS_TEMPLATE_PROMPT_TAG)) {&lt;br /&gt;
            menu.Add(t, REEXECUTE_LAST_TEMPLATE_PROMPT);&lt;br /&gt;
        } else {&lt;br /&gt;
            menu.Add(t, REEXECUTE_LAST_TEMPLATE_PROMPT, meMenuGrayed);&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return menu;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function callGroqLlamaAPI(apiKey, promptText, isTemplate) {&lt;br /&gt;
    try {&lt;br /&gt;
        // fetch で Groq API にリクエスト&lt;br /&gt;
        var requestBody = {&lt;br /&gt;
            messages: createGroqLlamaRequest(promptText, isTemplate),&lt;br /&gt;
            stream: false,&lt;br /&gt;
        };&lt;br /&gt;
        for (var key in MODEL_CONFIG) {&lt;br /&gt;
            requestBody[key] = MODEL_CONFIG[key];&lt;br /&gt;
        }&lt;br /&gt;
        if (isTemplate) {&lt;br /&gt;
            // テンプレートの場合は応答を決定論的にする&lt;br /&gt;
            // テンプレート以外の場合はデフォルト値とするために指定しない&lt;br /&gt;
            requestBody.temperature = 0.3;&lt;br /&gt;
        }&lt;br /&gt;
        var url = &#039;https://api.groq.com/openai/v1/chat/completions&#039;;&lt;br /&gt;
&lt;br /&gt;
        var http = CreateObject(&amp;quot;Msxml2.ServerXMLHTTP&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
        http.open(&#039;POST&#039;, url, true);&lt;br /&gt;
        http.setRequestHeader(&#039;Content-Type&#039;, &#039;application/json&#039;);&lt;br /&gt;
        http.setRequestHeader(&#039;Authorization&#039;, &#039;Bearer &#039; + apiKey);&lt;br /&gt;
        http.send(JSON.stringify(requestBody));&lt;br /&gt;
&lt;br /&gt;
        // 応答待機&lt;br /&gt;
        var timeout = 60;&lt;br /&gt;
        var retrySec = 0;&lt;br /&gt;
        var startX = sel.GetActivePointX(mePosLogical);&lt;br /&gt;
        var prevPos = sel.GetActivePos();&lt;br /&gt;
        while (http.readyState != 4) {&lt;br /&gt;
            if (retrySec &amp;gt; timeout) {&lt;br /&gt;
                break;&lt;br /&gt;
            }&lt;br /&gt;
            http.waitForResponse(1);&lt;br /&gt;
            retrySec += 1;&lt;br /&gt;
            inputProgressChar(prevPos);&lt;br /&gt;
            prevPos = sel.GetActivePos();&lt;br /&gt;
        }&lt;br /&gt;
        // 進捗表示文字を削除&lt;br /&gt;
        sel.SetActivePoint(&lt;br /&gt;
            mePosLogical,&lt;br /&gt;
            startX,&lt;br /&gt;
            sel.GetActivePointY(mePosLogical),&lt;br /&gt;
            true&lt;br /&gt;
        );&lt;br /&gt;
        sel.Delete();&lt;br /&gt;
&lt;br /&gt;
        // 応答ステータスチェック&lt;br /&gt;
        if (!(http.readyState == 4 &amp;amp;&amp;amp; http.status == 200)) {&lt;br /&gt;
            sel.Text = &amp;quot;エラーが発生しました: &amp;quot; + http.status + &amp;quot; &amp;quot; + http.statusText + &amp;quot;\n&amp;quot; + http.responseText;&lt;br /&gt;
            http.abort();&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        var ret = &amp;quot;&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
        // JSONをパースして必要なテキストを抽出&lt;br /&gt;
        var response = JSON.parse(http.responseText);&lt;br /&gt;
&lt;br /&gt;
        var choices = response.choices;&lt;br /&gt;
        if (choices &amp;amp;&amp;amp; choices.length &amp;gt; 0) {&lt;br /&gt;
            choices.forEach(function (choice) {&lt;br /&gt;
                var message = choice.message;&lt;br /&gt;
                if (message) {&lt;br /&gt;
                    ret += message.content;&lt;br /&gt;
                }&lt;br /&gt;
            });&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        var usage = response.usage;&lt;br /&gt;
        var usageText = &amp;quot;&amp;quot;;&lt;br /&gt;
        if (usage) {&lt;br /&gt;
            usageText += &amp;quot;\n\n(利用トークン数: プロンプト=&amp;quot; + usage.prompt_tokens&lt;br /&gt;
                + &amp;quot;, 応答=&amp;quot; + usage.completion_tokens&lt;br /&gt;
                + &amp;quot;, 合計=&amp;quot; + usage.total_tokens + &amp;quot;)&amp;quot;;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        sel.Text = ret + usageText;&lt;br /&gt;
&lt;br /&gt;
        // 結果を会話履歴に追加&lt;br /&gt;
        if (HISTORY_SIZE &amp;gt; 0 &amp;amp;&amp;amp; !isTemplate) {&lt;br /&gt;
            appendReturnTextToTag(promptText, ret);&lt;br /&gt;
        }&lt;br /&gt;
    } catch (e) {&lt;br /&gt;
        throw e;&lt;br /&gt;
    } finally {&lt;br /&gt;
        requestCompletedFlg = true;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function roughTokenize(text) {&lt;br /&gt;
    // 超適当なトークナイザ。トークン数をざっくりと多めに数える&lt;br /&gt;
    // 空白文字・ハイフン・アンダースコア区切りの単語に分割して、&lt;br /&gt;
    // 単語がアルファベットのみ、数字のみの場合には 4文字=1トークン として数える。&lt;br /&gt;
    // 単語にアルファベット以外の文字が含まれる場合は 1文字=1トークン として数える。&lt;br /&gt;
&lt;br /&gt;
    var words = text.split(/[\s\-_]+/);&lt;br /&gt;
    var totalTokens = 0;&lt;br /&gt;
&lt;br /&gt;
    for (var i = 0; i &amp;lt; words.length; i++) {&lt;br /&gt;
        var word = words[i];&lt;br /&gt;
        if (!word) continue; // 空の単語をスキップ&lt;br /&gt;
&lt;br /&gt;
        if (/^[a-zA-Z]+$/.test(word) || /^[0-9]+$/.test(word)) {&lt;br /&gt;
            totalTokens += Math.ceil(word.length / 4);&lt;br /&gt;
        } else {&lt;br /&gt;
            totalTokens += word.length;&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return totalTokens;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// document.Tag の内容を加味してGroqLlamaのリクエストを作成&lt;br /&gt;
function createGroqLlamaRequest(promptText, isTemplate) {&lt;br /&gt;
    var customInstructionText = buildCustomInstructionText();&lt;br /&gt;
    // テンプレートの場合は、会話履歴とカスタムインストラクションを反映しない&lt;br /&gt;
    if (isTemplate) {&lt;br /&gt;
        var request = [&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;role&amp;quot;: &amp;quot;user&amp;quot;,&lt;br /&gt;
                &amp;quot;content&amp;quot;: [{ &amp;quot;type&amp;quot;: &amp;quot;text&amp;quot;, &amp;quot;text&amp;quot;: promptText }]&lt;br /&gt;
            }&lt;br /&gt;
        ];&lt;br /&gt;
        if (customInstructionText) {&lt;br /&gt;
            request.unshift({&lt;br /&gt;
                &amp;quot;role&amp;quot;: &amp;quot;system&amp;quot;,&lt;br /&gt;
                &amp;quot;content&amp;quot;: customInstructionText&lt;br /&gt;
            })&lt;br /&gt;
        }&lt;br /&gt;
        return request;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    var tag = document.Tag;&lt;br /&gt;
&lt;br /&gt;
    // 以前の会話が存在しない場合は新規リクエスト&lt;br /&gt;
    if (!tag.exists(HISTORY_TAG)) {&lt;br /&gt;
        var request = [{&lt;br /&gt;
            &amp;quot;role&amp;quot;: &amp;quot;user&amp;quot;,&lt;br /&gt;
            &amp;quot;content&amp;quot;: [{ &amp;quot;type&amp;quot;: &amp;quot;text&amp;quot;, &amp;quot;text&amp;quot;: promptText }]&lt;br /&gt;
        }];&lt;br /&gt;
        if (customInstructionText) {&lt;br /&gt;
            request.unshift({&lt;br /&gt;
                &amp;quot;role&amp;quot;: &amp;quot;system&amp;quot;,&lt;br /&gt;
                &amp;quot;content&amp;quot;: customInstructionText&lt;br /&gt;
            })&lt;br /&gt;
        }&lt;br /&gt;
        return request;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // 会話履歴が存在する場合は、履歴を含めてリクエスト&lt;br /&gt;
    var request = JSON.parse(tag(HISTORY_TAG));&lt;br /&gt;
    if (customInstructionText) {&lt;br /&gt;
        request.unshift({&lt;br /&gt;
            &amp;quot;role&amp;quot;: &amp;quot;system&amp;quot;,&lt;br /&gt;
            &amp;quot;content&amp;quot;: customInstructionText&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
    request.push({&lt;br /&gt;
        &amp;quot;role&amp;quot;: &amp;quot;user&amp;quot;,&lt;br /&gt;
        &amp;quot;content&amp;quot;: promptText&lt;br /&gt;
    });&lt;br /&gt;
    return request;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// document.Tag にGroqLlamaの応答を追加&lt;br /&gt;
function appendReturnTextToTag(userText, modelText) {&lt;br /&gt;
    var tag = document.Tag;&lt;br /&gt;
    if (!tag.exists(HISTORY_TAG)) {&lt;br /&gt;
        tag(HISTORY_TAG) = JSON.stringify([&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;role&amp;quot;: &amp;quot;user&amp;quot;,&lt;br /&gt;
                &amp;quot;content&amp;quot;: userText&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;role&amp;quot;: &amp;quot;assistant&amp;quot;,&lt;br /&gt;
                &amp;quot;content&amp;quot;: modelText&lt;br /&gt;
            }&lt;br /&gt;
        ]);&lt;br /&gt;
    } else {&lt;br /&gt;
        var groqllamaText = JSON.parse(tag(HISTORY_TAG));&lt;br /&gt;
        groqllamaText.push({&lt;br /&gt;
            &amp;quot;role&amp;quot;: &amp;quot;user&amp;quot;,&lt;br /&gt;
            &amp;quot;content&amp;quot;: userText&lt;br /&gt;
        });&lt;br /&gt;
        groqllamaText.push({&lt;br /&gt;
            &amp;quot;role&amp;quot;: &amp;quot;assistant&amp;quot;,&lt;br /&gt;
            &amp;quot;content&amp;quot;: modelText&lt;br /&gt;
        });&lt;br /&gt;
        // 会話セット (user, model 1往復) が特定セット数を超えた場合、最初のセットを削除&lt;br /&gt;
        if (groqllamaText.length &amp;gt; HISTORY_SIZE * 2) {&lt;br /&gt;
            groqllamaText.splice(0, 2);&lt;br /&gt;
        }&lt;br /&gt;
        // 会話履歴全体が MAX_HISTORY_TOKEN を超えた場合、最初のセットを削除&lt;br /&gt;
        var joinedText = &amp;quot;&amp;quot;;&lt;br /&gt;
        for (var i = 0; i &amp;lt; groqllamaText.length; i++) {&lt;br /&gt;
            joinedText += groqllamaText[i].content + &amp;quot;\n&amp;quot;;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        while (roughTokenize(joinedText) &amp;gt; MAX_HISTORY_TOKEN) {&lt;br /&gt;
            groqllamaText.splice(0, 2);&lt;br /&gt;
            joinedText = &amp;quot;&amp;quot;;&lt;br /&gt;
            for (var i = 0; i &amp;lt; groqllamaText.length; i++) {&lt;br /&gt;
                joinedText += groqllamaText[i].content + &amp;quot;\n&amp;quot;;&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        tag(HISTORY_TAG) = JSON.stringify(groqllamaText);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function resetTag() {&lt;br /&gt;
    var tag = doc.Tag;&lt;br /&gt;
    if (tag.exists(HISTORY_TAG)) {&lt;br /&gt;
        tag.remove(HISTORY_TAG);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function baseCustomInstruction() {&lt;br /&gt;
    var language = LANGUAGE.trim();&lt;br /&gt;
    return (language ? &amp;quot;Respond in &amp;quot; + language + &amp;quot;.\n&amp;quot; : &amp;quot;&amp;quot;)&lt;br /&gt;
        + &amp;quot;Do not hallucinate. If you are unsure or don&#039;t have enough information to answer with confidence, say \&amp;quot;I don&#039;t know\&amp;quot; or \&amp;quot;I&#039;m not sure.\&amp;quot;\n&amp;quot;&lt;br /&gt;
        + &amp;quot;Do not use full-width brackets \&amp;quot;（）\&amp;quot;, but use half-width brackets \&amp;quot;()\&amp;quot;. Enclose the outside of the half-width brackets with half-width spaces.\n&amp;quot;&lt;br /&gt;
        + &amp;quot;When using Markdown formatting, please follow these rules:\n&amp;quot;&lt;br /&gt;
        + &amp;quot;  - You MUST include a blank line below the heading (a line beginning with one or more # characters).\n&amp;quot;&lt;br /&gt;
        + &amp;quot;  - For bold formatting (text surrounded by **), italic formatting (text surrounded by *), and inline code blocks (text surrounded by `), you MUST surround each formatting symbol with a space.\n&amp;quot;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function buildCustomInstructionText() {&lt;br /&gt;
    var customInstruction = getTemporaryCustomInstruction();&lt;br /&gt;
    if (!customInstruction) {&lt;br /&gt;
        // 一時的なカスタムインストラクションがない場合はデフォルトを使用&lt;br /&gt;
        customInstruction = DEFAULT_CUSTOM_INSTRUCTION;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    customInstruction = customInstruction.replace(/^[\s　]+|[\s　]+$/g, &amp;quot;&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
    return (customInstruction ? customInstruction + &amp;quot;\n&amp;quot; : &amp;quot;&amp;quot;)&lt;br /&gt;
        + baseCustomInstruction();&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function configureTemporaryCustomInstruction() {&lt;br /&gt;
    var tempCustomInstruction = getTemporaryCustomInstruction();&lt;br /&gt;
    var prompt = Prompt(&amp;quot;カスタムインストラクションを変更 ※変更すると会話履歴がリセットされます&amp;quot;, tempCustomInstruction ? tempCustomInstruction : DEFAULT_CUSTOM_INSTRUCTION);&lt;br /&gt;
    if (prompt) {&lt;br /&gt;
        if (prompt === DEFAULT_CUSTOM_INSTRUCTION) {&lt;br /&gt;
            // デフォルトと同じ値の場合はタグを削除&lt;br /&gt;
            setTemporaryCustomInstruction(&amp;quot;&amp;quot;);&lt;br /&gt;
        } else if (prompt !== tempCustomInstruction) {&lt;br /&gt;
            // デフォルト以外の値が設定された場合は保存&lt;br /&gt;
            setTemporaryCustomInstruction(prompt);&lt;br /&gt;
        }&lt;br /&gt;
    } else {&lt;br /&gt;
        // 空文字でOK、もしくはキャンセルされた場合はデフォルトに戻すか確認&lt;br /&gt;
        if (Confirm(&amp;quot;カスタムインストラクションをデフォルトに戻しますか？&amp;quot;)) {&lt;br /&gt;
            setTemporaryCustomInstruction(&amp;quot;&amp;quot;);&lt;br /&gt;
        } else {&lt;br /&gt;
            alert(&amp;quot;カスタムインストラクションの変更をキャンセルしました。\n※カスタムインストラクションを空にしたい場合はスペースのみを入力してください。&amp;quot;);&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // 変更が加わっている場合は会話履歴をリセット&lt;br /&gt;
    if (tempCustomInstruction !== getTemporaryCustomInstruction()) {&lt;br /&gt;
        resetTag();&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getTemporaryCustomInstruction() {&lt;br /&gt;
    var tag = doc.Tag;&lt;br /&gt;
    if (tag.exists(TEMPORARY_CUSTOM_INSTRUCTION_TAG)) {&lt;br /&gt;
        return tag(TEMPORARY_CUSTOM_INSTRUCTION_TAG);&lt;br /&gt;
    } else {&lt;br /&gt;
        return &amp;quot;&amp;quot;;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function setTemporaryCustomInstruction(temporaryCustomInstruction) {&lt;br /&gt;
    var tag = doc.Tag;&lt;br /&gt;
    if (temporaryCustomInstruction) {&lt;br /&gt;
        tag(TEMPORARY_CUSTOM_INSTRUCTION_TAG) = temporaryCustomInstruction;&lt;br /&gt;
    } else {&lt;br /&gt;
        tag.remove(TEMPORARY_CUSTOM_INSTRUCTION_TAG);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function addPreviousTemplatePromptNumberToTag(menuTrack) {&lt;br /&gt;
    var tag = doc.Tag;&lt;br /&gt;
    tag(PREVIOUS_TEMPLATE_PROMPT_TAG) = menuTrack;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function inputProgressChar(pos) {&lt;br /&gt;
    sel.SetActivePos(pos);&lt;br /&gt;
    sel.Text = &amp;quot;.&amp;quot;;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;/div&gt;</summary>
		<author><name>Yuko</name></author>
	</entry>
	<entry>
		<id>https://www.haijin-boys.com/wiki/index.php?title=Groq_%E3%81%AB%E7%9B%B8%E8%AB%87&amp;diff=9164</id>
		<title>Groq に相談</title>
		<link rel="alternate" type="text/html" href="https://www.haijin-boys.com/wiki/index.php?title=Groq_%E3%81%AB%E7%9B%B8%E8%AB%87&amp;diff=9164"/>
		<updated>2025-07-22T13:22:27Z</updated>

		<summary type="html">&lt;p&gt;Yuko: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== 概要 ==&lt;br /&gt;
&lt;br /&gt;
生成AIプラットフォームである [https://groq.com GroqCloud] で各種AIを利用するためのMeryマクロです。&lt;br /&gt;
※Llama等のモデルも使えるように適当なタイミングで更新をかけています。&lt;br /&gt;
&lt;br /&gt;
「Google Geminiに相談」マクロでは動作中のストリーム通信に対応するためにV8エンジンで実行していましたが、V8エンジン版は使用上の制限からマクロ実行を開始するまで1秒程度のタイムラグが発生していました。GroqCloudの売りである超高速レスポンスを見込んで本マクロではストリーム通信を使わないようにしました。これにより従来のJScriptで処理を組み立てることができるため、前述のタイムラグが起こらないようになりました。また、ストリーム通信を使わないことで通信量が少なく応答もシンプルになるため、場合によっては「Google Geminiに相談」マクロよりも処理が安定する可能性があります。&lt;br /&gt;
&lt;br /&gt;
【主な機能】&lt;br /&gt;
&lt;br /&gt;
* GroqCloud APIを利用して生成AIと対話できるMery用のマクロ&lt;br /&gt;
* 選択したテキストやアクティブな行の内容について生成AIに質問・相談が可能&lt;br /&gt;
* 会話履歴を保持し、文脈を考慮した対話が可能&lt;br /&gt;
&lt;br /&gt;
【特徴的な機能】&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;会話履歴機能&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* 事前の設定数に応じた会話履歴を保持&lt;br /&gt;
* 履歴のクリアが可能&lt;br /&gt;
&lt;br /&gt;
これにより、会話の文脈を考慮した回答を得られます。&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;カスタムインストラクション&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* AIの応答方針を設定可能&lt;br /&gt;
* 一時的なカスタム指示の保存と変更が可能&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;プロンプトテンプレート&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* 誤字脱字チェック&lt;br /&gt;
* 日本語翻訳&lt;br /&gt;
* コードの最適化、生成&lt;br /&gt;
&lt;br /&gt;
など、定型的な質問を簡単に実行できます。&lt;br /&gt;
※テンプレート機能では回答を安定させるため、前述の会話履歴を文脈に含みません。&lt;br /&gt;
&lt;br /&gt;
テンプレートは自身の使い方に合わせて好きに追加、削除ができます。詳細はソースコード内の「PROMPT_TEMPLATE」定数のコメントを参照。&lt;br /&gt;
&lt;br /&gt;
== 注意事項 (必ず読んでからご利用ください) ==&lt;br /&gt;
&lt;br /&gt;
* 本マクロでは 2025/04/13 時点で無料利用ができるGroqCloud APIを利用します。今後、プラットフォームの改定により有償になったり、設定誤り等で費用が発生しても、作者は一切の責任を負いません。&lt;br /&gt;
* GroqCloud APIではリクエスト内容をAI学習に用いないことをプライバシポリシーとして謳っているものの、くれぐれも機密情報やプライバシーに関わる情報を指定しないように注意してください。&lt;br /&gt;
* GroqCloud APIはプラットフォームの利用規約に則ってご利用ください。&lt;br /&gt;
&lt;br /&gt;
== 利用前提 ==&lt;br /&gt;
&lt;br /&gt;
* Mery Ver 3.7.13 以上&lt;br /&gt;
* GroqCloudで、GroqCloud API APIキーを取得していること (参考 [https://console.groq.com/docs/quickstart Quickstart])&lt;br /&gt;
&lt;br /&gt;
== 利用方法 ==&lt;br /&gt;
&lt;br /&gt;
* 環境変数の設定&lt;br /&gt;
:コマンド プロンプトで以下のコマンドを実行し、環境変数を設定してください。&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bat&amp;quot; copy&amp;gt;&lt;br /&gt;
setx GROQ_API_KEY &amp;quot;***************************************&amp;quot;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
:設定後、Mery または Windows の再起動が必要な場合があります。&lt;br /&gt;
* マクロの実行&lt;br /&gt;
**プロンプトにしたい文字列を選択してマクロを実行すると、選択したテキストがプロンプトとしてGemini APIに送信され、応答がエディタに表示されます。&lt;br /&gt;
* 会話の履歴数を調整&lt;br /&gt;
** 会話の履歴は設定した個数分が保持され、文脈として利用されます。適宜、ソースコード内の「HISTORY_SIZE」の数を調整してください。&lt;br /&gt;
** ※数を増やすほど会話内容を保持して文脈を理解するようになりますが、メモリ使用量が増えたり動作が重くなる可能性があります。&lt;br /&gt;
** ※開いている文書ごとに履歴を保持します。文脈をリセットしたいときは、新しい文書を開いて会話をやり直してください。&lt;br /&gt;
&lt;br /&gt;
== ソースコード ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot; copy&amp;gt;&lt;br /&gt;
#title = &amp;quot;Groq に相談&amp;quot;&lt;br /&gt;
BeginUndoGroup();&lt;br /&gt;
&lt;br /&gt;
// カスタムインストラクション&lt;br /&gt;
// これを設定することで、生成AIの回答全体への生成方針を指示できます。&lt;br /&gt;
// 例えば &amp;quot;あなたは「Kimi」という名前のギャルAIアシスタントです。敬語は使いません。絵文字を使って感情表現をします。人懐っこく、バイタリティ溢れる性格で、ユーザーの課題を協力して解決します。&amp;quot; と設定すると、AIの雰囲気が元気な子になります。&lt;br /&gt;
// 何も指示しない場合は、空文字 &amp;quot;&amp;quot; を設定してください。&lt;br /&gt;
var DEFAULT_CUSTOM_INSTRUCTION = &amp;quot;&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
// プロンプトテンプレート&lt;br /&gt;
// プロンプトテンプレートを設定することで、AIに対して特定の指示を出すことができます。&lt;br /&gt;
// プロンプトテンプレートでの生成は会話履歴とカスタムインストラクションを反映しません。&lt;br /&gt;
// テンプレートは以下の { ～ }, で囲まれたブロックを好きに増減させて利用できます。&lt;br /&gt;
var PROMPT_TEMPLATE = [&lt;br /&gt;
    // 以下はサンプルです。1階層メニューと2階層メニューが利用できます。&lt;br /&gt;
    // {&lt;br /&gt;
    //     &amp;quot;title&amp;quot;: &amp;quot;要約&amp;quot;,&lt;br /&gt;
    //     &amp;quot;prompt&amp;quot;: &amp;quot;以下の文章を要約してください。&amp;quot;&lt;br /&gt;
    // },&lt;br /&gt;
    // {&lt;br /&gt;
    //     &amp;quot;submenu_title&amp;quot;: &amp;quot;翻訳系&amp;quot;,&lt;br /&gt;
    //     &amp;quot;submenu&amp;quot;: [&lt;br /&gt;
    //         {&lt;br /&gt;
    //             &amp;quot;title&amp;quot;: &amp;quot;日本語翻訳&amp;quot;,&lt;br /&gt;
    //             &amp;quot;prompt&amp;quot;: &amp;quot;以下の文章を日本語に翻訳してください。&amp;quot;&lt;br /&gt;
    //         },&lt;br /&gt;
    //         {&lt;br /&gt;
    //             &amp;quot;title&amp;quot;: &amp;quot;英語翻訳&amp;quot;,&lt;br /&gt;
    //             &amp;quot;prompt&amp;quot;: &amp;quot;以下の文章を英語に翻訳してください。&amp;quot;&lt;br /&gt;
    //         },&lt;br /&gt;
    //     ]&lt;br /&gt;
    // },&lt;br /&gt;
&lt;br /&gt;
    {&lt;br /&gt;
        &amp;quot;submenu_title&amp;quot;: &amp;quot;理解を深める&amp;quot;,&lt;br /&gt;
        &amp;quot;submenu&amp;quot;: [&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;論点を整理する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の論点を整理して。&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;論理の抜け漏れを探す&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の論点を整理し、忖度せずに鋭く、論理の抜け漏れを抽出して。そして、そこから発生する可能性のあるリスクも教えて。&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;要約する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;要約して。専門的な用語と思われる単語は簡単に解説も入れて。&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;物語対話形式で教えて&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の内容について物語対話形式で教えてください。&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
        ]&lt;br /&gt;
    },&lt;br /&gt;
    {&lt;br /&gt;
        &amp;quot;submenu_title&amp;quot;: &amp;quot;翻訳する&amp;quot;,&lt;br /&gt;
        &amp;quot;submenu&amp;quot;: [&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;日本語に翻訳&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の文章を日本語に翻訳してください。\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;出力形式は以下にすること:\n[訳]\n翻訳文...\n&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;英語に翻訳&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の文章を英語に翻訳してください。\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;出力形式は以下にすること:\n[Translation]\n翻訳文...\n&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;英語-&amp;gt;日本語 スラッシュリーディング翻訳&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の英文を日本語にスラッシュリーディング形式で翻訳してください。\n\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;条件:\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 英文の各フレーズをスラッシュ(/)で区切って表示\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 各フレーズの日本語訳を括弧()内に併記\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 文の終わりはダブルスラッシュ(//)で表示\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 英語の語順に従って日本語訳を行う\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 日本語訳は自然な日本語である必要はなく、英語の語順を反映した直訳調で構わない\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 主語が省略されている場合は補って翻訳すること\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 一般的な英文で頻出する表現がある場合は次の行に記載\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 以下の出力例のように &amp;lt;英文&amp;gt; (日本語訳) / ... とスラッシュ区切りの繰り返しになる \n\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;出力例:\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;Snowstorms are forecast (暴風雪が予報されている) / to hit the Sea of Japan coasts (日本海沿岸を襲うと) / of northern to eastern Japan (北部から東部の) / from Sunday through Monday. (日曜から月曜にかけて。) //\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;【頻出表現】\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- be forecast to: ～すると予報されている\n&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
        ]&lt;br /&gt;
    },&lt;br /&gt;
    {&lt;br /&gt;
        &amp;quot;submenu_title&amp;quot;: &amp;quot;プログラミング支援&amp;quot;,&lt;br /&gt;
        &amp;quot;submenu&amp;quot;: [&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;コードを洗練させる&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下のコードを洗練させてください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;コードをレビューする&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下のコードをセキュリティと保守性の観点でレビューしてください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;エラーログをデバッグする&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下のエラーログを解析し、考えられる原因と対策を提示してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;SQLクエリを最適化する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下のSQL文を最適化してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;PowerShell スクリプトを書く&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の処理を行うPowerShellスクリプトを生成してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;Shell Script を書く&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の処理を行うShell Scriptを生成してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;Linux コマンドを提案&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;あなたはLinuxエキスパートとして、以下のLinuxコマンドを提案してください。\n\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;制約条件:\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 多数のディストリビューションで採用されているメジャーなコマンドを使用し、ポータブルな実装にすること\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- bashでの実行を前提にすること\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 可能な限りシンプルな実装にすること\n\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;期待する出力:\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- コマンドの具体的な実装方法\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 各コマンドの動作説明\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 制限事項\n&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
        ]&lt;br /&gt;
    },&lt;br /&gt;
    {&lt;br /&gt;
        &amp;quot;submenu_title&amp;quot;: &amp;quot;ビジネス文書作成支援&amp;quot;,&lt;br /&gt;
        &amp;quot;submenu&amp;quot;: [&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;誤字脱字、不正確な表現をチェック&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の2つのカテゴリーで問題点を指摘してください\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 【誤字脱字】：明確な誤字脱字および誤字の疑いがある箇所\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 【不正確・曖昧な表現】：解釈に個人差が生じる可能性がある表現、より適切な表現に改善できる箇所&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;提案書を作成する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下に対する対する提案書を、コスト・実現性・リスクを含めて作成してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;仕様書のアウトラインを作成する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の内容で仕様書のアウトラインを作成してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;手順書のアウトラインを作成する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の作業を行う手順書を、前提条件と注意事項を含めてアウトラインを作成してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;ユーザーマニュアルのアウトラインを作成する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の機能内容から、ユーザー向けマニュアルのアウトラインを作成してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;議事録を作成する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の会議内容から議事録を作成してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;進捗報告書を作成する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下のタスク状況から進捗報告書を作成してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
        ]&lt;br /&gt;
    },&lt;br /&gt;
    {&lt;br /&gt;
        &amp;quot;submenu_title&amp;quot;: &amp;quot;分析支援&amp;quot;,&lt;br /&gt;
        &amp;quot;submenu&amp;quot;: [&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;問題を分析し、課題を抽出する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の状況から問題や潜在的リスクを分析し、課題を抽出してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;対応策を提案する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の問題・課題に対する対応策を複数提案してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;傾向を見つける&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の内容から、数値や挙動上の傾向を見つけてください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
        ]&lt;br /&gt;
    },&lt;br /&gt;
    {&lt;br /&gt;
        &amp;quot;submenu_title&amp;quot;: &amp;quot;アイデア出し&amp;quot;,&lt;br /&gt;
        &amp;quot;submenu&amp;quot;: [&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;SCAMPER法でブレインストーミング&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下のテーマについて、SCAMPER法を使って15個のアイデアを出してください。制約条件は以下の通りです：&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;解決策の幅出し&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の課題に対して、技術面、運用面、コスト面から実現可能な解決策を5つずつ提案してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;業務改善案の提案&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の既存プロセスについて、効率化、コスト削減、品質向上の観点から改善案を提案してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;逆転の発想&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の目標に対して、通常とは逆の視点からアプローチする方法を3つ提案してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;競合分析からの着想&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の内容について、業界における競合他社の特徴を分析し、差別化できる新しいアプローチを提案してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;ユースケースの列挙&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の製品・サービスについて、想定されるユースケースを10個列挙し、それぞれの価値提案を記載してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
        ]&lt;br /&gt;
    },&lt;br /&gt;
]&lt;br /&gt;
&lt;br /&gt;
// 生成AIの回答の言語設定&lt;br /&gt;
// &amp;quot;Japanese&amp;quot; や &amp;quot;English&amp;quot; を設定できます。他の言語は未検証。&lt;br /&gt;
// 自動設定にする場合は空文字 &amp;quot;&amp;quot; を設定してください。&lt;br /&gt;
var LANGUAGE = &amp;quot;Japanese&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
// 入力プロンプトの最大トークン数&lt;br /&gt;
// 超適当なトークナイザー関数により計測し、英単語は4文字1トークン、それ以外は1文字1トークンとして数えます。&lt;br /&gt;
var MAX_PROMPT_TOKEN = 6000;&lt;br /&gt;
&lt;br /&gt;
// 会話の履歴を保持する数 (質問と回答で1セット)&lt;br /&gt;
// 数を増やすと、会話の履歴が増えますが、Meryのメモリ使用量が増えたり、&lt;br /&gt;
// 使用トークン数が増えて動作が重くなる可能性があるため、適宜調整してください。&lt;br /&gt;
// document.Tag に保持されるため、開いているファイルを閉じると履歴は消えます。&lt;br /&gt;
var HISTORY_SIZE = 0;&lt;br /&gt;
&lt;br /&gt;
// 会話履歴に保持する最大トークン数&lt;br /&gt;
// 会話履歴のトークン数がこの数を超える場合、古い履歴から削除されます。&lt;br /&gt;
var MAX_HISTORY_TOKEN = 10000;&lt;br /&gt;
&lt;br /&gt;
// コンテキストメニュー番号の定義&lt;br /&gt;
var EXECUTE_AI = 1;&lt;br /&gt;
var RESET_HISTORY = 2;&lt;br /&gt;
var SET_CUSTOM_INSTRUCTION = 3;&lt;br /&gt;
var REEXECUTE_LAST_TEMPLATE_PROMPT = 4;&lt;br /&gt;
var TEMPLATE_PROMPT_THRESHOLD = 10;&lt;br /&gt;
var TEMPLATE_PROMPT_SUBMENU_THRESHOLD = 1000;&lt;br /&gt;
&lt;br /&gt;
// タグ名の定義&lt;br /&gt;
var HISTORY_TAG = &amp;quot;groqllama-history&amp;quot;;&lt;br /&gt;
var TEMPORARY_CUSTOM_INSTRUCTION_TAG = &amp;quot;groqllama-temporary-custom-instruction&amp;quot;;&lt;br /&gt;
var PREVIOUS_TEMPLATE_PROMPT_TAG = &amp;quot;groqllama-previous-template-prompt&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
// 生成AIモデル別設定&lt;br /&gt;
&lt;br /&gt;
// const MODEL_CONFIG = {&lt;br /&gt;
//     model: &amp;quot;meta-llama/llama-4-maverick-17b-128e-instruct&amp;quot;,&lt;br /&gt;
//     max_tokens: 3500,&lt;br /&gt;
//     temperature: 0.6,&lt;br /&gt;
// }&lt;br /&gt;
&lt;br /&gt;
// const MODEL_CONFIG = {&lt;br /&gt;
//     model: &amp;quot;qwen/qwen3-32b&amp;quot;,&lt;br /&gt;
//     max_tokens: 5000,&lt;br /&gt;
//     temperature: 0.6,&lt;br /&gt;
//     max_completion_tokens: 1024,&lt;br /&gt;
//     reasoning_effort: &amp;quot;default&amp;quot;,&lt;br /&gt;
//     reasoning_format: &amp;quot;hidden&amp;quot;&lt;br /&gt;
// }&lt;br /&gt;
&lt;br /&gt;
const MODEL_CONFIG = {&lt;br /&gt;
    model: &amp;quot;moonshotai/kimi-k2-instruct&amp;quot;,&lt;br /&gt;
    max_tokens: 5000,&lt;br /&gt;
    temperature: 0.7,&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// console.log に出力する内容を Mery のアウトプットバーに表示する&lt;br /&gt;
var console = {&lt;br /&gt;
    log: function (str) {&lt;br /&gt;
        outputBar.Writeln(str)&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
var doc = document;&lt;br /&gt;
var sel = document.selection;&lt;br /&gt;
&lt;br /&gt;
// 生成リクエスト中止フラグ&lt;br /&gt;
var abortRequestFlg = false&lt;br /&gt;
// 生成リクエスト完了フラグ;&lt;br /&gt;
var requestCompletedFlg = false;&lt;br /&gt;
&lt;br /&gt;
main();&lt;br /&gt;
&lt;br /&gt;
function main() {&lt;br /&gt;
    var selectionIsEmpty = sel.IsEmpty&lt;br /&gt;
&lt;br /&gt;
    // プロンプトの取得&lt;br /&gt;
    if (selectionIsEmpty) {&lt;br /&gt;
        sel.StartOfLine(false, mePosLogical);&lt;br /&gt;
        sel.EndOfLine(true, mePosLogical);&lt;br /&gt;
    }&lt;br /&gt;
    var promptText = sel.Text.trim();&lt;br /&gt;
    var promptLength = sel.TextLength;&lt;br /&gt;
    var menuTrack = 0;&lt;br /&gt;
    while (true) {&lt;br /&gt;
        // コンテキストメニューの作成&lt;br /&gt;
        var menu = createContextMenu(selectionIsEmpty);&lt;br /&gt;
        menuTrack = menu.Track(0);&lt;br /&gt;
        // メニュー選択がキャンセルされた場合は終了&lt;br /&gt;
        if (menuTrack === 0) return;&lt;br /&gt;
&lt;br /&gt;
        // 会話履歴のリセット&lt;br /&gt;
        if (menuTrack === RESET_HISTORY) {&lt;br /&gt;
            resetTag();&lt;br /&gt;
            continue;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        // カスタムインストラクションの設定&lt;br /&gt;
        if (menuTrack === SET_CUSTOM_INSTRUCTION) {&lt;br /&gt;
            configureTemporaryCustomInstruction();&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        break;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // プロンプトが空の場合はエラー&lt;br /&gt;
    if (promptLength === 0) {&lt;br /&gt;
        alert(&amp;quot;プロンプトを入力してください。&amp;quot;);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
    // 文字数が多すぎる場合はエラー&lt;br /&gt;
    if (roughTokenize(promptText) &amp;gt; MAX_PROMPT_TOKEN) {&lt;br /&gt;
        alert(&amp;quot;プロンプトの文字数が多すぎます。文字数を少なくしてください。&amp;quot;);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
    // 読み取り専用の場合はエラー&lt;br /&gt;
    if (doc.ReadOnly) {&lt;br /&gt;
        alert(&amp;quot;ファイルが読み取り専用のため実行できません。&amp;quot;);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // 直前に実行したプロンプトテンプレートの再実行の場合、menuTrack を書き換え&lt;br /&gt;
    if (menuTrack === REEXECUTE_LAST_TEMPLATE_PROMPT) {&lt;br /&gt;
        if (doc.Tag.exists(PREVIOUS_TEMPLATE_PROMPT_TAG)) {&lt;br /&gt;
            menuTrack = Number(doc.Tag(PREVIOUS_TEMPLATE_PROMPT_TAG));&lt;br /&gt;
        } else {&lt;br /&gt;
            alert(&amp;quot;直前に実行したプロンプトテンプレートがありません。&amp;quot;);&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // テンプレートの場合はプロンプトを変更&lt;br /&gt;
    var isTemplate = menuTrack &amp;gt;= TEMPLATE_PROMPT_THRESHOLD;&lt;br /&gt;
    var isSubmenu = menuTrack &amp;gt;= TEMPLATE_PROMPT_SUBMENU_THRESHOLD;&lt;br /&gt;
    if (isSubmenu) {&lt;br /&gt;
        var parentMenuTrack = Number(String(menuTrack).slice(0, 2));&lt;br /&gt;
        var subMenuTrack = Number(String(menuTrack).slice(2));&lt;br /&gt;
        promptText = &amp;quot;=== INSTRUCTIONS START HERE ===\n\n&amp;quot;&lt;br /&gt;
            + PROMPT_TEMPLATE[parentMenuTrack - TEMPLATE_PROMPT_THRESHOLD].submenu[subMenuTrack].prompt&lt;br /&gt;
            + &amp;quot;\n\n=== INSTRUCTIONS END HERE ===\n\n&amp;quot;&lt;br /&gt;
            + &amp;quot;=== INPUT DATA STARTS HERE ===\n\n&amp;quot;&lt;br /&gt;
            + promptText&lt;br /&gt;
            + &amp;quot;\n\n=== INPUT DATA END HERE ===&amp;quot;;&lt;br /&gt;
    } else if (isTemplate) {&lt;br /&gt;
        promptText = &amp;quot;=== INSTRUCTIONS START HERE ===\n\n&amp;quot;&lt;br /&gt;
            + PROMPT_TEMPLATE[menuTrack - TEMPLATE_PROMPT_THRESHOLD].prompt&lt;br /&gt;
            + &amp;quot;\n\n=== INSTRUCTIONS END HERE ===\n\n&amp;quot;&lt;br /&gt;
            + &amp;quot;=== INPUT DATA STARTS HERE ===\n\n&amp;quot;&lt;br /&gt;
            + promptText&lt;br /&gt;
            + &amp;quot;\n\n=== INPUT DATA END HERE ===&amp;quot;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // テンプレートの場合は直前に実行したプロンプトテンプレートの番号としてタグに保存&lt;br /&gt;
    if (isTemplate) {&lt;br /&gt;
        addPreviousTemplatePromptNumberToTag(menuTrack);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    try {&lt;br /&gt;
        // 処理終了を待機&lt;br /&gt;
        shell.KeepRunning = true;&lt;br /&gt;
&lt;br /&gt;
        // カーソル位置移動&lt;br /&gt;
        sel.SetActivePoint(mePosLogical, sel.GetBottomPointX(mePosLogical), sel.GetBottomPointY(mePosLogical));&lt;br /&gt;
        sel.EndOfLine(false, mePosLogical);&lt;br /&gt;
        sel.NewLine();&lt;br /&gt;
&lt;br /&gt;
        // APIキーの取得&lt;br /&gt;
        var apiKey = shell.getEnv(&amp;quot;GROQ_API_KEY&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
        // GroqLlama API に問い合わせ&lt;br /&gt;
        sel.Text = &amp;quot;\n---\n\n&amp;quot;&lt;br /&gt;
        document.KeepScrollPos = true;&lt;br /&gt;
        callGroqLlamaAPI(apiKey, promptText, isTemplate);&lt;br /&gt;
        if (sel.GetActivePointX(mePosLogical) &amp;gt; 2) {&lt;br /&gt;
            sel.Text = &amp;quot;\n\n---\n\n\n&amp;quot;&lt;br /&gt;
        } else {&lt;br /&gt;
            sel.Text = &amp;quot;\n---\n\n\n&amp;quot;&lt;br /&gt;
        }&lt;br /&gt;
    } catch (e) {&lt;br /&gt;
        sel.Text = &amp;quot;&amp;lt;処理中にエラー発生&amp;gt;\n&amp;quot; + e.message + &amp;quot;\n&amp;quot; + e.stack;&lt;br /&gt;
    } finally {&lt;br /&gt;
        // 処理終了待機を解除&lt;br /&gt;
        shell.KeepRunning = false;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function createContextMenu(selectionIsEmpty) {&lt;br /&gt;
    var menu = CreatePopupMenu();&lt;br /&gt;
    menu.Add(&amp;quot;※注意※ プライバシー・機密情報を送信しないこと&amp;quot;, 0);&lt;br /&gt;
    menu.Add(&amp;quot;&amp;quot;, 0, meMenuSeparator);&lt;br /&gt;
    var t = &amp;quot;&amp;quot;;&lt;br /&gt;
    if (selectionIsEmpty) {&lt;br /&gt;
        t = &amp;quot;アクティブ行の内容で相談 (&amp;amp;A)&amp;quot;;&lt;br /&gt;
    } else {&lt;br /&gt;
        t = &amp;quot;選択範囲の内容で相談 (&amp;amp;A)&amp;quot;;&lt;br /&gt;
    }&lt;br /&gt;
    menu.Add(t, EXECUTE_AI);&lt;br /&gt;
    if (HISTORY_SIZE &amp;lt;= 0) {&lt;br /&gt;
        menu.Add(&amp;quot;├ 会話履歴をクリア (履歴が無効になっています)&amp;quot;, RESET_HISTORY, meMenuGrayed);&lt;br /&gt;
    } else if (doc.Tag.exists(HISTORY_TAG)) {&lt;br /&gt;
        var turn = JSON.parse(doc.Tag(HISTORY_TAG)).length / 2;&lt;br /&gt;
        menu.Add(&amp;quot;├ 会話履歴をクリア (現在の会話数: &amp;quot; + turn + &amp;quot;) (&amp;amp;C)&amp;quot;, RESET_HISTORY);&lt;br /&gt;
    } else {&lt;br /&gt;
        menu.Add(&amp;quot;├ 会話履歴をクリア (現在の会話数: 0) (&amp;amp;C)&amp;quot;, RESET_HISTORY, meMenuGrayed);&lt;br /&gt;
    }&lt;br /&gt;
    menu.Add(&amp;quot;└ カスタムインストラクションを変更 (&amp;amp;I)&amp;quot;, SET_CUSTOM_INSTRUCTION);&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    // テンプレート数チェック&lt;br /&gt;
    // 2桁まで許容&lt;br /&gt;
    var ITEMS_MUX = 99;&lt;br /&gt;
    if (PROMPT_TEMPLATE.length &amp;gt; ITEMS_MUX - TEMPLATE_PROMPT_THRESHOLD) {&lt;br /&gt;
        throw new Error(&amp;quot;プロンプトテンプレートの数が多すぎます。&amp;quot;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // テンプレートの追加&lt;br /&gt;
    if (PROMPT_TEMPLATE.length &amp;gt; 0) {&lt;br /&gt;
        var subMenu = CreatePopupMenu();&lt;br /&gt;
        for (var i = 0; i &amp;lt; PROMPT_TEMPLATE.length; i++) {&lt;br /&gt;
            var accelerationKey = &amp;quot;&amp;quot;;&lt;br /&gt;
            if (i === 9) {&lt;br /&gt;
                accelerationKey = &amp;quot; (&amp;amp;0)&amp;quot;;&lt;br /&gt;
            } else if (i &amp;lt; 10) {&lt;br /&gt;
                accelerationKey = &amp;quot; (&amp;amp;&amp;quot; + (i + 1) + &amp;quot;)&amp;quot;;&lt;br /&gt;
            }&lt;br /&gt;
            var menuNum = TEMPLATE_PROMPT_THRESHOLD + i;&lt;br /&gt;
            if (PROMPT_TEMPLATE[i].submenu) {&lt;br /&gt;
                var subMenuList = PROMPT_TEMPLATE[i].submenu;&lt;br /&gt;
                // テンプレート数チェック&lt;br /&gt;
                if (subMenuList.length &amp;gt; ITEMS_MUX) {&lt;br /&gt;
                    throw new Error(&amp;quot;プロンプトテンプレート (サブメニュー) の数が多すぎます。&amp;quot;);&lt;br /&gt;
                }&lt;br /&gt;
                var subSubMenu = CreatePopupMenu();&lt;br /&gt;
                var subMenuPrefix = String(menuNum);&lt;br /&gt;
                for (var j = 0; j &amp;lt; subMenuList.length; j++) {&lt;br /&gt;
                    var subAccelerationKey = &amp;quot;&amp;quot;;&lt;br /&gt;
                    if (j === 9) {&lt;br /&gt;
                        subAccelerationKey = &amp;quot; (&amp;amp;0)&amp;quot;;&lt;br /&gt;
                    } else if (j &amp;lt; 10) {&lt;br /&gt;
                        subAccelerationKey = &amp;quot; (&amp;amp;&amp;quot; + (j + 1) + &amp;quot;)&amp;quot;;&lt;br /&gt;
                    }&lt;br /&gt;
                    // ES5 compatible version without padStart&lt;br /&gt;
                    var subMenuNum = Number(subMenuPrefix + (j &amp;lt; 10 ? &amp;quot;0&amp;quot; + j : String(j)));&lt;br /&gt;
                    subSubMenu.Add(subMenuList[j].title + subAccelerationKey, subMenuNum);&lt;br /&gt;
                }&lt;br /&gt;
                subMenu.AddPopup(PROMPT_TEMPLATE[i].submenu_title + accelerationKey, subSubMenu);&lt;br /&gt;
            } else {&lt;br /&gt;
                subMenu.Add(PROMPT_TEMPLATE[i].title + accelerationKey, menuNum);&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        if (selectionIsEmpty) {&lt;br /&gt;
            t = &amp;quot;プロンプト テンプレート (対象: アクティブ行) (&amp;amp;T)&amp;quot;;&lt;br /&gt;
        } else {&lt;br /&gt;
            t = &amp;quot;プロンプト テンプレート (対象: 選択範囲) (&amp;amp;T)&amp;quot;;&lt;br /&gt;
        }&lt;br /&gt;
        menu.AddPopup(t, subMenu);&lt;br /&gt;
&lt;br /&gt;
        t = &amp;quot;└ 直前に実行したプロンプト テンプレートを再実行 (&amp;amp;R)&amp;quot;;&lt;br /&gt;
        if (doc.Tag.exists(PREVIOUS_TEMPLATE_PROMPT_TAG)) {&lt;br /&gt;
            menu.Add(t, REEXECUTE_LAST_TEMPLATE_PROMPT);&lt;br /&gt;
        } else {&lt;br /&gt;
            menu.Add(t, REEXECUTE_LAST_TEMPLATE_PROMPT, meMenuGrayed);&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return menu;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function callGroqLlamaAPI(apiKey, promptText, isTemplate) {&lt;br /&gt;
    try {&lt;br /&gt;
        // fetch で Groq API にリクエスト&lt;br /&gt;
        var requestBody = {&lt;br /&gt;
            messages: createGroqLlamaRequest(promptText, isTemplate),&lt;br /&gt;
            stream: false,&lt;br /&gt;
        };&lt;br /&gt;
        for (var key in MODEL_CONFIG) {&lt;br /&gt;
            requestBody[key] = MODEL_CONFIG[key];&lt;br /&gt;
        }&lt;br /&gt;
        if (isTemplate) {&lt;br /&gt;
            // テンプレートの場合は応答を決定論的にする&lt;br /&gt;
            // テンプレート以外の場合はデフォルト値とするために指定しない&lt;br /&gt;
            requestBody.temperature = 0.3;&lt;br /&gt;
        }&lt;br /&gt;
        var url = &#039;https://api.groq.com/openai/v1/chat/completions&#039;;&lt;br /&gt;
&lt;br /&gt;
        var http = CreateObject(&amp;quot;Msxml2.ServerXMLHTTP&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
        http.open(&#039;POST&#039;, url, true);&lt;br /&gt;
        http.setRequestHeader(&#039;Content-Type&#039;, &#039;application/json&#039;);&lt;br /&gt;
        http.setRequestHeader(&#039;Authorization&#039;, &#039;Bearer &#039; + apiKey);&lt;br /&gt;
        http.send(JSON.stringify(requestBody));&lt;br /&gt;
&lt;br /&gt;
        // 応答待機&lt;br /&gt;
        var timeout = 60;&lt;br /&gt;
        var retrySec = 0;&lt;br /&gt;
        var startX = sel.GetActivePointX(mePosLogical);&lt;br /&gt;
        var prevPos = sel.GetActivePos();&lt;br /&gt;
        while (http.readyState != 4) {&lt;br /&gt;
            if (retrySec &amp;gt; timeout) {&lt;br /&gt;
                break;&lt;br /&gt;
            }&lt;br /&gt;
            http.waitForResponse(1);&lt;br /&gt;
            retrySec += 1;&lt;br /&gt;
            inputProgressChar(prevPos);&lt;br /&gt;
            prevPos = sel.GetActivePos();&lt;br /&gt;
        }&lt;br /&gt;
        // 進捗表示文字を削除&lt;br /&gt;
        sel.SetActivePoint(&lt;br /&gt;
            mePosLogical,&lt;br /&gt;
            startX,&lt;br /&gt;
            sel.GetActivePointY(mePosLogical),&lt;br /&gt;
            true&lt;br /&gt;
        );&lt;br /&gt;
        sel.Delete();&lt;br /&gt;
&lt;br /&gt;
        // 応答ステータスチェック&lt;br /&gt;
        if (!(http.readyState == 4 &amp;amp;&amp;amp; http.status == 200)) {&lt;br /&gt;
            sel.Text = &amp;quot;エラーが発生しました: &amp;quot; + http.status + &amp;quot; &amp;quot; + http.statusText + &amp;quot;\n&amp;quot; + http.responseText;&lt;br /&gt;
            http.abort();&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        var ret = &amp;quot;&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
        // JSONをパースして必要なテキストを抽出&lt;br /&gt;
        var response = JSON.parse(http.responseText);&lt;br /&gt;
&lt;br /&gt;
        var choices = response.choices;&lt;br /&gt;
        if (choices &amp;amp;&amp;amp; choices.length &amp;gt; 0) {&lt;br /&gt;
            choices.forEach(function (choice) {&lt;br /&gt;
                var message = choice.message;&lt;br /&gt;
                if (message) {&lt;br /&gt;
                    ret += message.content;&lt;br /&gt;
                }&lt;br /&gt;
            });&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        var usage = response.usage;&lt;br /&gt;
        var usageText = &amp;quot;&amp;quot;;&lt;br /&gt;
        if (usage) {&lt;br /&gt;
            usageText += &amp;quot;\n\n(利用トークン数: プロンプト=&amp;quot; + usage.prompt_tokens&lt;br /&gt;
                + &amp;quot;, 応答=&amp;quot; + usage.completion_tokens&lt;br /&gt;
                + &amp;quot;, 合計=&amp;quot; + usage.total_tokens + &amp;quot;)&amp;quot;;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        sel.Text = ret + usageText;&lt;br /&gt;
&lt;br /&gt;
        // 結果を会話履歴に追加&lt;br /&gt;
        if (HISTORY_SIZE &amp;gt; 0 &amp;amp;&amp;amp; !isTemplate) {&lt;br /&gt;
            appendReturnTextToTag(promptText, ret);&lt;br /&gt;
        }&lt;br /&gt;
    } catch (e) {&lt;br /&gt;
        throw e;&lt;br /&gt;
    } finally {&lt;br /&gt;
        requestCompletedFlg = true;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function roughTokenize(text) {&lt;br /&gt;
    // 超適当なトークナイザ。トークン数をざっくりと多めに数える&lt;br /&gt;
    // 空白文字・ハイフン・アンダースコア区切りの単語に分割して、&lt;br /&gt;
    // 単語がアルファベットのみ、数字のみの場合には 4文字=1トークン として数える。&lt;br /&gt;
    // 単語にアルファベット以外の文字が含まれる場合は 1文字=1トークン として数える。&lt;br /&gt;
&lt;br /&gt;
    var words = text.split(/[\s\-_]+/);&lt;br /&gt;
    var totalTokens = 0;&lt;br /&gt;
&lt;br /&gt;
    for (var i = 0; i &amp;lt; words.length; i++) {&lt;br /&gt;
        var word = words[i];&lt;br /&gt;
        if (!word) continue; // 空の単語をスキップ&lt;br /&gt;
&lt;br /&gt;
        if (/^[a-zA-Z]+$/.test(word) || /^[0-9]+$/.test(word)) {&lt;br /&gt;
            totalTokens += Math.ceil(word.length / 4);&lt;br /&gt;
        } else {&lt;br /&gt;
            totalTokens += word.length;&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return totalTokens;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// document.Tag の内容を加味してGroqLlamaのリクエストを作成&lt;br /&gt;
function createGroqLlamaRequest(promptText, isTemplate) {&lt;br /&gt;
    var customInstructionText = buildCustomInstructionText();&lt;br /&gt;
    // テンプレートの場合は、会話履歴とカスタムインストラクションを反映しない&lt;br /&gt;
    if (isTemplate) {&lt;br /&gt;
        var request = [&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;role&amp;quot;: &amp;quot;user&amp;quot;,&lt;br /&gt;
                &amp;quot;content&amp;quot;: [{ &amp;quot;type&amp;quot;: &amp;quot;text&amp;quot;, &amp;quot;text&amp;quot;: promptText }]&lt;br /&gt;
            }&lt;br /&gt;
        ];&lt;br /&gt;
        if (customInstructionText) {&lt;br /&gt;
            request.unshift({&lt;br /&gt;
                &amp;quot;role&amp;quot;: &amp;quot;system&amp;quot;,&lt;br /&gt;
                &amp;quot;content&amp;quot;: customInstructionText&lt;br /&gt;
            })&lt;br /&gt;
        }&lt;br /&gt;
        return request;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    var tag = document.Tag;&lt;br /&gt;
&lt;br /&gt;
    // 以前の会話が存在しない場合は新規リクエスト&lt;br /&gt;
    if (!tag.exists(HISTORY_TAG)) {&lt;br /&gt;
        var request = [{&lt;br /&gt;
            &amp;quot;role&amp;quot;: &amp;quot;user&amp;quot;,&lt;br /&gt;
            &amp;quot;content&amp;quot;: [{ &amp;quot;type&amp;quot;: &amp;quot;text&amp;quot;, &amp;quot;text&amp;quot;: promptText }]&lt;br /&gt;
        }];&lt;br /&gt;
        if (customInstructionText) {&lt;br /&gt;
            request.unshift({&lt;br /&gt;
                &amp;quot;role&amp;quot;: &amp;quot;system&amp;quot;,&lt;br /&gt;
                &amp;quot;content&amp;quot;: customInstructionText&lt;br /&gt;
            })&lt;br /&gt;
        }&lt;br /&gt;
        return request;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // 会話履歴が存在する場合は、履歴を含めてリクエスト&lt;br /&gt;
    var request = JSON.parse(tag(HISTORY_TAG));&lt;br /&gt;
    if (customInstructionText) {&lt;br /&gt;
        request.unshift({&lt;br /&gt;
            &amp;quot;role&amp;quot;: &amp;quot;system&amp;quot;,&lt;br /&gt;
            &amp;quot;content&amp;quot;: customInstructionText&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
    request.push({&lt;br /&gt;
        &amp;quot;role&amp;quot;: &amp;quot;user&amp;quot;,&lt;br /&gt;
        &amp;quot;content&amp;quot;: promptText&lt;br /&gt;
    });&lt;br /&gt;
    return request;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// document.Tag にGroqLlamaの応答を追加&lt;br /&gt;
function appendReturnTextToTag(userText, modelText) {&lt;br /&gt;
    var tag = document.Tag;&lt;br /&gt;
    if (!tag.exists(HISTORY_TAG)) {&lt;br /&gt;
        tag(HISTORY_TAG) = JSON.stringify([&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;role&amp;quot;: &amp;quot;user&amp;quot;,&lt;br /&gt;
                &amp;quot;content&amp;quot;: userText&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;role&amp;quot;: &amp;quot;assistant&amp;quot;,&lt;br /&gt;
                &amp;quot;content&amp;quot;: modelText&lt;br /&gt;
            }&lt;br /&gt;
        ]);&lt;br /&gt;
    } else {&lt;br /&gt;
        var groqllamaText = JSON.parse(tag(HISTORY_TAG));&lt;br /&gt;
        groqllamaText.push({&lt;br /&gt;
            &amp;quot;role&amp;quot;: &amp;quot;user&amp;quot;,&lt;br /&gt;
            &amp;quot;content&amp;quot;: userText&lt;br /&gt;
        });&lt;br /&gt;
        groqllamaText.push({&lt;br /&gt;
            &amp;quot;role&amp;quot;: &amp;quot;assistant&amp;quot;,&lt;br /&gt;
            &amp;quot;content&amp;quot;: modelText&lt;br /&gt;
        });&lt;br /&gt;
        // 会話セット (user, model 1往復) が特定セット数を超えた場合、最初のセットを削除&lt;br /&gt;
        if (groqllamaText.length &amp;gt; HISTORY_SIZE * 2) {&lt;br /&gt;
            groqllamaText.splice(0, 2);&lt;br /&gt;
        }&lt;br /&gt;
        // 会話履歴全体が MAX_HISTORY_TOKEN を超えた場合、最初のセットを削除&lt;br /&gt;
        var joinedText = &amp;quot;&amp;quot;;&lt;br /&gt;
        for (var i = 0; i &amp;lt; groqllamaText.length; i++) {&lt;br /&gt;
            joinedText += groqllamaText[i].content + &amp;quot;\n&amp;quot;;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        while (roughTokenize(joinedText) &amp;gt; MAX_HISTORY_TOKEN) {&lt;br /&gt;
            groqllamaText.splice(0, 2);&lt;br /&gt;
            joinedText = &amp;quot;&amp;quot;;&lt;br /&gt;
            for (var i = 0; i &amp;lt; groqllamaText.length; i++) {&lt;br /&gt;
                joinedText += groqllamaText[i].content + &amp;quot;\n&amp;quot;;&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        tag(HISTORY_TAG) = JSON.stringify(groqllamaText);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function resetTag() {&lt;br /&gt;
    var tag = doc.Tag;&lt;br /&gt;
    if (tag.exists(HISTORY_TAG)) {&lt;br /&gt;
        tag.remove(HISTORY_TAG);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function baseCustomInstruction() {&lt;br /&gt;
    var language = LANGUAGE.trim();&lt;br /&gt;
    return &amp;quot;Do not hallucinate. If you are unsure or don&#039;t have enough information to answer with confidence, say \&amp;quot;I don&#039;t know\&amp;quot; or \&amp;quot;I&#039;m not sure.\&amp;quot;\n&amp;quot;&lt;br /&gt;
        + &amp;quot;If you use Markdown headings (lines beginning with one or more # characters), you must surround the heading with blank lines.\n&amp;quot;&lt;br /&gt;
        + (language ? &amp;quot;Respond in &amp;quot; + language + &amp;quot;.\n&amp;quot; : &amp;quot;&amp;quot;)&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function buildCustomInstructionText() {&lt;br /&gt;
    var customInstruction = getTemporaryCustomInstruction();&lt;br /&gt;
    if (!customInstruction) {&lt;br /&gt;
        // 一時的なカスタムインストラクションがない場合はデフォルトを使用&lt;br /&gt;
        customInstruction = DEFAULT_CUSTOM_INSTRUCTION;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    customInstruction = customInstruction.replace(/^[\s　]+|[\s　]+$/g, &amp;quot;&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
    return (customInstruction ? customInstruction + &amp;quot;\n&amp;quot; : &amp;quot;&amp;quot;)&lt;br /&gt;
        + baseCustomInstruction();&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function configureTemporaryCustomInstruction() {&lt;br /&gt;
    var tempCustomInstruction = getTemporaryCustomInstruction();&lt;br /&gt;
    var prompt = Prompt(&amp;quot;カスタムインストラクションを変更 ※変更すると会話履歴がリセットされます&amp;quot;, tempCustomInstruction ? tempCustomInstruction : DEFAULT_CUSTOM_INSTRUCTION);&lt;br /&gt;
    if (prompt) {&lt;br /&gt;
        if (prompt === DEFAULT_CUSTOM_INSTRUCTION) {&lt;br /&gt;
            // デフォルトと同じ値の場合はタグを削除&lt;br /&gt;
            setTemporaryCustomInstruction(&amp;quot;&amp;quot;);&lt;br /&gt;
        } else if (prompt !== tempCustomInstruction) {&lt;br /&gt;
            // デフォルト以外の値が設定された場合は保存&lt;br /&gt;
            setTemporaryCustomInstruction(prompt);&lt;br /&gt;
        }&lt;br /&gt;
    } else {&lt;br /&gt;
        // 空文字でOK、もしくはキャンセルされた場合はデフォルトに戻すか確認&lt;br /&gt;
        if (Confirm(&amp;quot;カスタムインストラクションをデフォルトに戻しますか？&amp;quot;)) {&lt;br /&gt;
            setTemporaryCustomInstruction(&amp;quot;&amp;quot;);&lt;br /&gt;
        } else {&lt;br /&gt;
            alert(&amp;quot;カスタムインストラクションの変更をキャンセルしました。\n※カスタムインストラクションを空にしたい場合はスペースのみを入力してください。&amp;quot;);&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // 変更が加わっている場合は会話履歴をリセット&lt;br /&gt;
    if (tempCustomInstruction !== getTemporaryCustomInstruction()) {&lt;br /&gt;
        resetTag();&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getTemporaryCustomInstruction() {&lt;br /&gt;
    var tag = doc.Tag;&lt;br /&gt;
    if (tag.exists(TEMPORARY_CUSTOM_INSTRUCTION_TAG)) {&lt;br /&gt;
        return tag(TEMPORARY_CUSTOM_INSTRUCTION_TAG);&lt;br /&gt;
    } else {&lt;br /&gt;
        return &amp;quot;&amp;quot;;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function setTemporaryCustomInstruction(temporaryCustomInstruction) {&lt;br /&gt;
    var tag = doc.Tag;&lt;br /&gt;
    if (temporaryCustomInstruction) {&lt;br /&gt;
        tag(TEMPORARY_CUSTOM_INSTRUCTION_TAG) = temporaryCustomInstruction;&lt;br /&gt;
    } else {&lt;br /&gt;
        tag.remove(TEMPORARY_CUSTOM_INSTRUCTION_TAG);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function addPreviousTemplatePromptNumberToTag(menuTrack) {&lt;br /&gt;
    var tag = doc.Tag;&lt;br /&gt;
    tag(PREVIOUS_TEMPLATE_PROMPT_TAG) = menuTrack;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function inputProgressChar(pos) {&lt;br /&gt;
    sel.SetActivePos(pos);&lt;br /&gt;
    sel.Text = &amp;quot;.&amp;quot;;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;/div&gt;</summary>
		<author><name>Yuko</name></author>
	</entry>
	<entry>
		<id>https://www.haijin-boys.com/wiki/index.php?title=%E3%83%9E%E3%82%AF%E3%83%AD%E3%83%A9%E3%82%A4%E3%83%96%E3%83%A9%E3%83%AA&amp;diff=9163</id>
		<title>マクロライブラリ</title>
		<link rel="alternate" type="text/html" href="https://www.haijin-boys.com/wiki/index.php?title=%E3%83%9E%E3%82%AF%E3%83%AD%E3%83%A9%E3%82%A4%E3%83%96%E3%83%A9%E3%83%AA&amp;diff=9163"/>
		<updated>2025-07-22T13:21:44Z</updated>

		<summary type="html">&lt;p&gt;Yuko: /* おもしろ */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Mery 用のマクロ ライブラリです。&lt;br /&gt;
&lt;br /&gt;
スクリプトを拡張子 &amp;quot;js&amp;quot;（JavaScript、JScript の場合）にして Macros フォルダーなどに保存し、[マクロ] メニューの [選択] や [カスタマイズ] から読み込んで使用してください。&lt;br /&gt;
&lt;br /&gt;
このマクロ ライブラリはどなたでも自由に編集可能ですので、ご自身が開発した便利なマクロをどんどん共有してください。&lt;br /&gt;
&lt;br /&gt;
また、[[隠しオプション一覧]]に希望するオプションがあるかもしれませんので、そちらも参照してみてください。&lt;br /&gt;
&lt;br /&gt;
== テキスト操作補助 ==&lt;br /&gt;
* [https://github.com/haijinboys/mery-macro-emmet/blob/master/emmet.js Emmet] (Kuro)&lt;br /&gt;
* [[Zen Coding]] (Kuro)&lt;br /&gt;
* [[バイト数]] (Kuro, ks)&lt;br /&gt;
* [[日付と時刻を挿入]] (n, masme, isari)&lt;br /&gt;
* [[日付と時刻を挿入（カレンダー版）]] (papagoat)&lt;br /&gt;
* [[選択範囲を上下に移動]] (ks)&lt;br /&gt;
* [[TeraPadのCtrl↑と同じような動作をするマクロ]] (suke)&lt;br /&gt;
* [[TeraPadのCtrl↓と同じような動作をするマクロ]] (suke)&lt;br /&gt;
* [[Vz 風テキストスタック 2 ストロークキー]] (papagoat)&lt;br /&gt;
* [[ラクラク罫線]] (手石)&lt;br /&gt;
* [[矩形ペースト]] (手石)&lt;br /&gt;
* [[自動箇条書き]] (手石)&lt;br /&gt;
* [[特殊改行]] (FuG)&lt;br /&gt;
* [[部分編集]] (ks)&lt;br /&gt;
* [[文字カウント]] (masme)&lt;br /&gt;
* [[字下げ・字上げ]] (masme)&lt;br /&gt;
* [[数値を増減]] (masme)&lt;br /&gt;
* [[上に行挿入]] (yuko)&lt;br /&gt;
* [[選択範囲を広げる]] (yuko)&lt;br /&gt;
* [[貼付け実行後のカーソル位置を貼付け文字列の先頭にする]] (yuko)&lt;br /&gt;
* [[選択した文字列を文書からすべて削除]] (Noah)&lt;br /&gt;
* [[文章で使用されている文字をユニーク抽出]] (Kuro)&lt;br /&gt;
* [[CSV のカーソル位置の列を削除]] (MSY-07)&lt;br /&gt;
* [[XHTML入力補助]] (アラフォー親父)&lt;br /&gt;
* [[Markdown文書で改行時にリスト、引用ブロック開始記号を自動入力する]] (catfist, yuko)&lt;br /&gt;
* [[Markdown 向け、リスト記号 (*) のトグル]] (yuko)&lt;br /&gt;
* [[Markdown 向け、タスクリスト (* ［］) のチェック状態をトグル]] (yuko)&lt;br /&gt;
* [[Markdown入力補助]] (急急如律令)&lt;br /&gt;
* [[青空文庫用整形補助]] (ks, 急急如律令)&lt;br /&gt;
* [[自動ルビ振り]] (急急如律令)&lt;br /&gt;
* [[ドイツ語特殊文字]] (鯉)&lt;br /&gt;
* [[何年生で習う漢字？]] (シリル)&lt;br /&gt;
&lt;br /&gt;
== ファイル操作補助 ==&lt;br /&gt;
* [[フォルダ内のファイルをすべて開く]] (Kuro)&lt;br /&gt;
* [[ファイルを比較]] (Kuro, MSY-07)&lt;br /&gt;
* [[ファイル名を引数にしてアプリ起動]] (Kuro, miso)&lt;br /&gt;
* [[文字列中のパス起動]] (ks)&lt;br /&gt;
* [[パス・URLを開く コマンドを実行する]] (yuko)&lt;br /&gt;
* [[開いている文書のファイル名を変更する]] (yuko)&lt;br /&gt;
* [[上書き保存(UAC対応)]] (ks)&lt;br /&gt;
* [[含んでいるフォルダを開く]] (ks)&lt;br /&gt;
* [[フォルダを開く]] (n)&lt;br /&gt;
* [[拡張子の違うファイルを開く]] (n)&lt;br /&gt;
* [[コマンドプロンプトを開く]] (n)&lt;br /&gt;
* [[ブックマーク一覧ジャンプ]] (goat)&lt;br /&gt;
* [[よく使うフォルダ]] (手石)&lt;br /&gt;
* [[ファイル（選択範囲）の仮保存]] (手石)&lt;br /&gt;
* [[ポップアップメニューでファイル選択]] (手石)&lt;br /&gt;
* [[フォルダツリーをポップアップ表示]] (n)&lt;br /&gt;
* [[タグジャンプ]] (o)&lt;br /&gt;
* [[ウィンドウの位置とサイズを拡張子で指定]] (papagoat)&lt;br /&gt;
* [[編集モードの自動選択]] (Bleat)&lt;br /&gt;
* [[書き換え禁止モード色反転]] (Bleat)&lt;br /&gt;
* [[変更なしのタブをすべて閉じる]] (Noah)&lt;br /&gt;
&lt;br /&gt;
== 変換・ソート・整形 ==&lt;br /&gt;
* [[標準変換]] (Kuro, MSY-07)&lt;br /&gt;
* [[テキスト変換]] (ks)&lt;br /&gt;
* [[テキスト整形]] (masme)&lt;br /&gt;
* [[旧漢字から新漢字に変換]] (furu)&lt;br /&gt;
* [[Unicode正規化]] (ucky)&lt;br /&gt;
* [[Wikipediaマークアップ修正]] (MSY-07)&lt;br /&gt;
* [[SHA でハッシュ化]] (Kuro)&lt;br /&gt;
* [[ブックマーク系コマンド集]] (ks)&lt;br /&gt;
* [[clustalw を用いてアライメントを実行]] (suke)&lt;br /&gt;
* [[clustal-omegaを用いてアライメントを実行]] (suke)&lt;br /&gt;
* [[連番の挿入]] (手石)&lt;br /&gt;
* [[連番を挿入]] (masme)&lt;br /&gt;
* [[マルチカーソルに連番を入力]] (yuko)&lt;br /&gt;
* [[可変長を固定長に変換]] (MSY-07)&lt;br /&gt;
* [[TSV(CSV) ⇒ 固定長]] (手石)&lt;br /&gt;
* [[行並べ替え]] (masme)&lt;br /&gt;
* [[GFMをHTMLに変換]] (noonworks)&lt;br /&gt;
* [[基数変換10 16]] (シリル)&lt;br /&gt;
* [[スネークケース ⇔ キャメルケースの変換]] (Noah)&lt;br /&gt;
* [[自然数ソート]] (tak)&lt;br /&gt;
&lt;br /&gt;
== 検索・置換 ==&lt;br /&gt;
* [[文書から検索]] (Kuro)&lt;br /&gt;
* [[単語を抽出]] (Kuro)&lt;br /&gt;
* [[タグの外側（内側）だけ置換]] (手石)&lt;br /&gt;
* [[ポップアップメニューで検索先にジャンプ]] (手石)&lt;br /&gt;
* [[検索(SJIS以外)]] (masme)&lt;br /&gt;
* [https://github.com/TakamiChie/mery-scripts/blob/master/SearchWithMode.js 現在開いているファイルの言語名をつけてWeb検索] (高見知英)&lt;br /&gt;
* [https://github.com/GunseiKPaseri/Mery_macro/blob/master/count.js 文字出現量ランキング] (群生系パセリ)&lt;br /&gt;
* [[検索ヒット数表示]] (ks)&lt;br /&gt;
* [[任意の検索エンジンで検索]] (Noah)&lt;br /&gt;
* [[任意の検索エンジンで検索ー改良版]] (Takeshi)&lt;br /&gt;
* [[デフォルトの検索フラグを設定して検索ウィンドウ表示]] (yuko)&lt;br /&gt;
* [[複数置換]] (Noah)&lt;br /&gt;
* [[類義語アウトプット【Python】]] (シリル)&lt;br /&gt;
* [[校正支援アウトプット]] (シリル)&lt;br /&gt;
* [[見出しジャンプ]] (yuko)&lt;br /&gt;
* [[CSV/TSVでアクティブ列選択]] (yuko)&lt;br /&gt;
&lt;br /&gt;
== プログラミング補助 ==&lt;br /&gt;
* [[Eclipse風コメントアウト]] (ks)&lt;br /&gt;
* [[Eclipse風コメントアウト(改)]] (pizz)&lt;br /&gt;
* [[コメントマーク付け外し]] (手石, masme)&lt;br /&gt;
* [[スマートインデント(C 構文)]] (ks)&lt;br /&gt;
* [[ctags.exeでtagsファイルを生成、タグジャンプを行うマクロ]] (kazy)&lt;br /&gt;
* [[タグファイルから補完]] (n)&lt;br /&gt;
* [[コードスニペットもどき]] (手石)&lt;br /&gt;
* [[TeX文書のコンパイル]] (手石, isari)&lt;br /&gt;
* [[簡易数式計算]] (ks)&lt;br /&gt;
* [[定義へ移動]] (ks)&lt;br /&gt;
* [[タブの桁数]] (手石)&lt;br /&gt;
* [[行末の空白を削除]] (n)&lt;br /&gt;
* [https://github.com/TakamiChie/mery-scripts/blob/master/FileExecutor.js 開いているスクリプトファイルを実行] (高見知英)&lt;br /&gt;
* [https://github.com/TakamiChie/mery-scripts/blob/master/SyntaxChecker.js 開いているスクリプトファイルを構文チェック] (高見知英)&lt;br /&gt;
* [[説明を出力]] (s)&lt;br /&gt;
* [[JSON整形]] (Ovis)&lt;br /&gt;
* [[コンソール実行]] (YOS G-spec)&lt;br /&gt;
* [[拡張子からコンソール実行]] (YOS G-spec)&lt;br /&gt;
* [[ScriptRun]] (fuka)&lt;br /&gt;
* [[JavaScript を実行]] (n)&lt;br /&gt;
* [[Ruby を実行]] (n)&lt;br /&gt;
* [[HSPキーワード検索]] (SSSE)&lt;br /&gt;
&lt;br /&gt;
== おもしろ ==&lt;br /&gt;
* [[メール送信]] (ks)&lt;br /&gt;
* [[あつまれ☆みんなのおもしろアイコン]] (シリル, ucky)&lt;br /&gt;
* [[テーマのトグル]] (ucky)&lt;br /&gt;
* [[お気に入りフォント]] (ucky)&lt;br /&gt;
* [[Google Gemini に相談]] (yuko)&lt;br /&gt;
* [[OpenAI GPT に相談]] (yuko)&lt;br /&gt;
* [[Groq に相談]] (yuko)&lt;br /&gt;
* [[Google Gemini で執筆支援]] (Kuro)&lt;br /&gt;
* [[MeryToys]] (Kuro)&lt;br /&gt;
&lt;br /&gt;
== その他 ==&lt;br /&gt;
* [[最新版を確認]] (diaphragm)&lt;br /&gt;
* [[SAPI 使って単語読み上げ]] (miso)&lt;br /&gt;
* [[フォルダ選択ダイアログ]] (手石)&lt;br /&gt;
* [[HTA(HTML)の自作ダイアログを表示]] (noonworks)&lt;br /&gt;
* [[ポップアップメニューを手軽に扱う]] (komorebikoboshi)&lt;br /&gt;
* [[ポップアップメニューでチェックボックス]] (手石)&lt;br /&gt;
* [[ポップアップメニューでラジオボタン]] (手石)&lt;br /&gt;
* [[マクロ・関数をメニューで呼び出す]] (aliquis)&lt;br /&gt;
* [[階層化マクロメニュー]] (ks)&lt;br /&gt;
* [[シェルコマンドを実行]] (sasaki)&lt;br /&gt;
* [[Editor.OpenFile()のポリフィル]] (kazy)&lt;br /&gt;
* [[折り返しトグル切り替え]] (Bleat)&lt;br /&gt;
* [[Beep音の例]] (star0worshipper)&lt;br /&gt;
* [[ブラウザ風タブ移動]] (yuko)&lt;br /&gt;
* [[タブ位置を先頭/末尾に移動]] (yuko)&lt;br /&gt;
* [[Mery 電卓]] (yuko)&lt;br /&gt;
&lt;br /&gt;
== 開発者向け ==&lt;br /&gt;
* [[キーアサイン集]] (ks, masme, isari)&lt;br /&gt;
* [[includeライブラリ]] (ks)&lt;br /&gt;
* [[マクロ覚え書き（開発者向け）]] (ks, papagoat)&lt;br /&gt;
&lt;br /&gt;
== 注意事項 ==&lt;br /&gt;
[https://www.haijin-boys.com/software/mery/mery-2-6-6#5 Mery Ver 2.6.6]以前に作成されたマクロが動作しない場合は下記のページを参照してください。&lt;br /&gt;
&lt;br /&gt;
* [https://www.haijin-boys.com/wiki/%E3%83%9E%E3%82%AF%E3%83%AD%E3%83%AA%E3%83%95%E3%82%A1%E3%83%AC%E3%83%B3%E3%82%B9:3:%E3%83%87%E3%82%A3%E3%83%AC%E3%82%AF%E3%83%86%E3%82%A3%E3%83%96##language マクロリファレンス:3:ディレクティブ]&lt;br /&gt;
* [https://www.haijin-boys.com/discussions/6929 公式のマクロ 箇条書きが最新メリーで動作するかチェックいただけませんか？]&lt;/div&gt;</summary>
		<author><name>Yuko</name></author>
	</entry>
	<entry>
		<id>https://www.haijin-boys.com/wiki/index.php?title=GroqCloud_Llama_%E3%81%AB%E7%9B%B8%E8%AB%87&amp;diff=9162</id>
		<title>GroqCloud Llama に相談</title>
		<link rel="alternate" type="text/html" href="https://www.haijin-boys.com/wiki/index.php?title=GroqCloud_Llama_%E3%81%AB%E7%9B%B8%E8%AB%87&amp;diff=9162"/>
		<updated>2025-07-22T13:21:24Z</updated>

		<summary type="html">&lt;p&gt;Yuko: Yuko がページ「GroqCloud Llama に相談」を「Groq に相談」に移動しました: GloqCloud Llama以外のモデルにも対応したことで、名前が不適当になったため&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;#転送 [[Groq に相談]]&lt;/div&gt;</summary>
		<author><name>Yuko</name></author>
	</entry>
	<entry>
		<id>https://www.haijin-boys.com/wiki/index.php?title=Groq_%E3%81%AB%E7%9B%B8%E8%AB%87&amp;diff=9161</id>
		<title>Groq に相談</title>
		<link rel="alternate" type="text/html" href="https://www.haijin-boys.com/wiki/index.php?title=Groq_%E3%81%AB%E7%9B%B8%E8%AB%87&amp;diff=9161"/>
		<updated>2025-07-22T13:21:23Z</updated>

		<summary type="html">&lt;p&gt;Yuko: Yuko がページ「GroqCloud Llama に相談」を「Groq に相談」に移動しました: GloqCloud Llama以外のモデルにも対応したことで、名前が不適当になったため&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== 概要 ==&lt;br /&gt;
&lt;br /&gt;
生成AIプラットフォームである [https://groq.com GroqCloud] で「Llama」を利用するためのMeryマクロです。&lt;br /&gt;
※Llama以外のモデルも使えるように適当なタイミングで更新をかけています。&lt;br /&gt;
&lt;br /&gt;
「Google Geminiに相談」マクロでは動作中のストリーム通信に対応するためにV8エンジンで実行していましたが、V8エンジン版は使用上の制限からマクロ実行を開始するまで1秒程度のタイムラグが発生していました。GroqCloudの売りである超高速レスポンスを見込んで本マクロではストリーム通信を使わないようにしました。これにより従来のJScriptで処理を組み立てることができるため、前述のタイムラグが起こらないようになりました。また、ストリーム通信を使わないことで通信量が少なく応答もシンプルになるため、場合によっては「Google Geminiに相談」マクロよりも処理が安定する可能性があります。&lt;br /&gt;
&lt;br /&gt;
【主な機能】&lt;br /&gt;
&lt;br /&gt;
* GroqCloud APIを利用して生成AIと対話できるMery用のマクロ&lt;br /&gt;
* 選択したテキストやアクティブな行の内容について生成AIに質問・相談が可能&lt;br /&gt;
* 会話履歴を保持し、文脈を考慮した対話が可能&lt;br /&gt;
&lt;br /&gt;
【特徴的な機能】&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;会話履歴機能&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* 事前の設定数に応じた会話履歴を保持&lt;br /&gt;
* 履歴のクリアが可能&lt;br /&gt;
&lt;br /&gt;
これにより、会話の文脈を考慮した回答を得られます。&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;カスタムインストラクション&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* AIの応答方針を設定可能&lt;br /&gt;
* 一時的なカスタム指示の保存と変更が可能&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;プロンプトテンプレート&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* 誤字脱字チェック&lt;br /&gt;
* 日本語翻訳&lt;br /&gt;
* コードの最適化、生成&lt;br /&gt;
&lt;br /&gt;
など、定型的な質問を簡単に実行できます。&lt;br /&gt;
※テンプレート機能では回答を安定させるため、前述の会話履歴を文脈に含みません。&lt;br /&gt;
&lt;br /&gt;
テンプレートは自身の使い方に合わせて好きに追加、削除ができます。詳細はソースコード内の「PROMPT_TEMPLATE」定数のコメントを参照。&lt;br /&gt;
&lt;br /&gt;
== 注意事項 (必ず読んでからご利用ください) ==&lt;br /&gt;
&lt;br /&gt;
* 本マクロでは 2025/04/13 時点で無料利用ができるGroqCloud APIを利用します。今後、プラットフォームの改定により有償になったり、設定誤り等で費用が発生しても、作者は一切の責任を負いません。&lt;br /&gt;
* GroqCloud APIではリクエスト内容をAI学習に用いないことをプライバシポリシーとして謳っているものの、くれぐれも機密情報やプライバシーに関わる情報を指定しないように注意してください。&lt;br /&gt;
* GroqCloud APIはプラットフォームの利用規約に則ってご利用ください。&lt;br /&gt;
&lt;br /&gt;
== 利用前提 ==&lt;br /&gt;
&lt;br /&gt;
* Mery Ver 3.7.13 以上&lt;br /&gt;
* GroqCloudで、GroqCloud API APIキーを取得していること (参考 [https://console.groq.com/docs/quickstart Quickstart])&lt;br /&gt;
&lt;br /&gt;
== 利用方法 ==&lt;br /&gt;
&lt;br /&gt;
* 環境変数の設定&lt;br /&gt;
:コマンド プロンプトで以下のコマンドを実行し、環境変数を設定してください。&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bat&amp;quot; copy&amp;gt;&lt;br /&gt;
setx GROQ_API_KEY &amp;quot;***************************************&amp;quot;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
:設定後、Mery または Windows の再起動が必要な場合があります。&lt;br /&gt;
* マクロの実行&lt;br /&gt;
**プロンプトにしたい文字列を選択してマクロを実行すると、選択したテキストがプロンプトとしてGemini APIに送信され、応答がエディタに表示されます。&lt;br /&gt;
* 会話の履歴数を調整&lt;br /&gt;
** 会話の履歴は設定した個数分が保持され、文脈として利用されます。適宜、ソースコード内の「HISTORY_SIZE」の数を調整してください。&lt;br /&gt;
** ※数を増やすほど会話内容を保持して文脈を理解するようになりますが、メモリ使用量が増えたり動作が重くなる可能性があります。&lt;br /&gt;
** ※開いている文書ごとに履歴を保持します。文脈をリセットしたいときは、新しい文書を開いて会話をやり直してください。&lt;br /&gt;
&lt;br /&gt;
== ソースコード ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot; copy&amp;gt;&lt;br /&gt;
#title = &amp;quot;Groq に相談&amp;quot;&lt;br /&gt;
BeginUndoGroup();&lt;br /&gt;
&lt;br /&gt;
// カスタムインストラクション&lt;br /&gt;
// これを設定することで、生成AIの回答全体への生成方針を指示できます。&lt;br /&gt;
// 例えば &amp;quot;あなたは「Kimi」という名前のギャルAIアシスタントです。敬語は使いません。絵文字を使って感情表現をします。人懐っこく、バイタリティ溢れる性格で、ユーザーの課題を協力して解決します。&amp;quot; と設定すると、AIの雰囲気が元気な子になります。&lt;br /&gt;
// 何も指示しない場合は、空文字 &amp;quot;&amp;quot; を設定してください。&lt;br /&gt;
var DEFAULT_CUSTOM_INSTRUCTION = &amp;quot;&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
// プロンプトテンプレート&lt;br /&gt;
// プロンプトテンプレートを設定することで、AIに対して特定の指示を出すことができます。&lt;br /&gt;
// プロンプトテンプレートでの生成は会話履歴とカスタムインストラクションを反映しません。&lt;br /&gt;
// テンプレートは以下の { ～ }, で囲まれたブロックを好きに増減させて利用できます。&lt;br /&gt;
var PROMPT_TEMPLATE = [&lt;br /&gt;
    // 以下はサンプルです。1階層メニューと2階層メニューが利用できます。&lt;br /&gt;
    // {&lt;br /&gt;
    //     &amp;quot;title&amp;quot;: &amp;quot;要約&amp;quot;,&lt;br /&gt;
    //     &amp;quot;prompt&amp;quot;: &amp;quot;以下の文章を要約してください。&amp;quot;&lt;br /&gt;
    // },&lt;br /&gt;
    // {&lt;br /&gt;
    //     &amp;quot;submenu_title&amp;quot;: &amp;quot;翻訳系&amp;quot;,&lt;br /&gt;
    //     &amp;quot;submenu&amp;quot;: [&lt;br /&gt;
    //         {&lt;br /&gt;
    //             &amp;quot;title&amp;quot;: &amp;quot;日本語翻訳&amp;quot;,&lt;br /&gt;
    //             &amp;quot;prompt&amp;quot;: &amp;quot;以下の文章を日本語に翻訳してください。&amp;quot;&lt;br /&gt;
    //         },&lt;br /&gt;
    //         {&lt;br /&gt;
    //             &amp;quot;title&amp;quot;: &amp;quot;英語翻訳&amp;quot;,&lt;br /&gt;
    //             &amp;quot;prompt&amp;quot;: &amp;quot;以下の文章を英語に翻訳してください。&amp;quot;&lt;br /&gt;
    //         },&lt;br /&gt;
    //     ]&lt;br /&gt;
    // },&lt;br /&gt;
&lt;br /&gt;
    {&lt;br /&gt;
        &amp;quot;submenu_title&amp;quot;: &amp;quot;理解を深める&amp;quot;,&lt;br /&gt;
        &amp;quot;submenu&amp;quot;: [&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;論点を整理する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の論点を整理して。&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;論理の抜け漏れを探す&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の論点を整理し、忖度せずに鋭く、論理の抜け漏れを抽出して。そして、そこから発生する可能性のあるリスクも教えて。&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;要約する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;要約して。専門的な用語と思われる単語は簡単に解説も入れて。&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;物語対話形式で教えて&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の内容について物語対話形式で教えてください。&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
        ]&lt;br /&gt;
    },&lt;br /&gt;
    {&lt;br /&gt;
        &amp;quot;submenu_title&amp;quot;: &amp;quot;翻訳する&amp;quot;,&lt;br /&gt;
        &amp;quot;submenu&amp;quot;: [&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;日本語に翻訳&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の文章を日本語に翻訳してください。\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;出力形式は以下にすること:\n[訳]\n翻訳文...\n&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;英語に翻訳&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の文章を英語に翻訳してください。\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;出力形式は以下にすること:\n[Translation]\n翻訳文...\n&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;英語-&amp;gt;日本語 スラッシュリーディング翻訳&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の英文を日本語にスラッシュリーディング形式で翻訳してください。\n\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;条件:\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 英文の各フレーズをスラッシュ(/)で区切って表示\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 各フレーズの日本語訳を括弧()内に併記\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 文の終わりはダブルスラッシュ(//)で表示\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 英語の語順に従って日本語訳を行う\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 日本語訳は自然な日本語である必要はなく、英語の語順を反映した直訳調で構わない\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 主語が省略されている場合は補って翻訳すること\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 一般的な英文で頻出する表現がある場合は次の行に記載\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 以下の出力例のように &amp;lt;英文&amp;gt; (日本語訳) / ... とスラッシュ区切りの繰り返しになる \n\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;出力例:\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;Snowstorms are forecast (暴風雪が予報されている) / to hit the Sea of Japan coasts (日本海沿岸を襲うと) / of northern to eastern Japan (北部から東部の) / from Sunday through Monday. (日曜から月曜にかけて。) //\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;【頻出表現】\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- be forecast to: ～すると予報されている\n&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
        ]&lt;br /&gt;
    },&lt;br /&gt;
    {&lt;br /&gt;
        &amp;quot;submenu_title&amp;quot;: &amp;quot;プログラミング支援&amp;quot;,&lt;br /&gt;
        &amp;quot;submenu&amp;quot;: [&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;コードを洗練させる&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下のコードを洗練させてください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;コードをレビューする&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下のコードをセキュリティと保守性の観点でレビューしてください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;エラーログをデバッグする&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下のエラーログを解析し、考えられる原因と対策を提示してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;SQLクエリを最適化する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下のSQL文を最適化してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;PowerShell スクリプトを書く&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の処理を行うPowerShellスクリプトを生成してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;Shell Script を書く&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の処理を行うShell Scriptを生成してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;Linux コマンドを提案&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;あなたはLinuxエキスパートとして、以下のLinuxコマンドを提案してください。\n\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;制約条件:\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 多数のディストリビューションで採用されているメジャーなコマンドを使用し、ポータブルな実装にすること\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- bashでの実行を前提にすること\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 可能な限りシンプルな実装にすること\n\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;期待する出力:\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- コマンドの具体的な実装方法\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 各コマンドの動作説明\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 制限事項\n&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
        ]&lt;br /&gt;
    },&lt;br /&gt;
    {&lt;br /&gt;
        &amp;quot;submenu_title&amp;quot;: &amp;quot;ビジネス文書作成支援&amp;quot;,&lt;br /&gt;
        &amp;quot;submenu&amp;quot;: [&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;誤字脱字、不正確な表現をチェック&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の2つのカテゴリーで問題点を指摘してください\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 【誤字脱字】：明確な誤字脱字および誤字の疑いがある箇所\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 【不正確・曖昧な表現】：解釈に個人差が生じる可能性がある表現、より適切な表現に改善できる箇所&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;提案書を作成する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下に対する対する提案書を、コスト・実現性・リスクを含めて作成してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;仕様書のアウトラインを作成する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の内容で仕様書のアウトラインを作成してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;手順書のアウトラインを作成する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の作業を行う手順書を、前提条件と注意事項を含めてアウトラインを作成してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;ユーザーマニュアルのアウトラインを作成する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の機能内容から、ユーザー向けマニュアルのアウトラインを作成してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;議事録を作成する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の会議内容から議事録を作成してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;進捗報告書を作成する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下のタスク状況から進捗報告書を作成してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
        ]&lt;br /&gt;
    },&lt;br /&gt;
    {&lt;br /&gt;
        &amp;quot;submenu_title&amp;quot;: &amp;quot;分析支援&amp;quot;,&lt;br /&gt;
        &amp;quot;submenu&amp;quot;: [&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;問題を分析し、課題を抽出する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の状況から問題や潜在的リスクを分析し、課題を抽出してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;対応策を提案する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の問題・課題に対する対応策を複数提案してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;傾向を見つける&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の内容から、数値や挙動上の傾向を見つけてください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
        ]&lt;br /&gt;
    },&lt;br /&gt;
    {&lt;br /&gt;
        &amp;quot;submenu_title&amp;quot;: &amp;quot;アイデア出し&amp;quot;,&lt;br /&gt;
        &amp;quot;submenu&amp;quot;: [&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;SCAMPER法でブレインストーミング&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下のテーマについて、SCAMPER法を使って15個のアイデアを出してください。制約条件は以下の通りです：&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;解決策の幅出し&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の課題に対して、技術面、運用面、コスト面から実現可能な解決策を5つずつ提案してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;業務改善案の提案&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の既存プロセスについて、効率化、コスト削減、品質向上の観点から改善案を提案してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;逆転の発想&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の目標に対して、通常とは逆の視点からアプローチする方法を3つ提案してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;競合分析からの着想&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の内容について、業界における競合他社の特徴を分析し、差別化できる新しいアプローチを提案してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;ユースケースの列挙&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の製品・サービスについて、想定されるユースケースを10個列挙し、それぞれの価値提案を記載してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
        ]&lt;br /&gt;
    },&lt;br /&gt;
]&lt;br /&gt;
&lt;br /&gt;
// 生成AIの回答の言語設定&lt;br /&gt;
// &amp;quot;Japanese&amp;quot; や &amp;quot;English&amp;quot; を設定できます。他の言語は未検証。&lt;br /&gt;
// 自動設定にする場合は空文字 &amp;quot;&amp;quot; を設定してください。&lt;br /&gt;
var LANGUAGE = &amp;quot;Japanese&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
// 入力プロンプトの最大トークン数&lt;br /&gt;
// 超適当なトークナイザー関数により計測し、英単語は4文字1トークン、それ以外は1文字1トークンとして数えます。&lt;br /&gt;
var MAX_PROMPT_TOKEN = 6000;&lt;br /&gt;
&lt;br /&gt;
// 会話の履歴を保持する数 (質問と回答で1セット)&lt;br /&gt;
// 数を増やすと、会話の履歴が増えますが、Meryのメモリ使用量が増えたり、&lt;br /&gt;
// 使用トークン数が増えて動作が重くなる可能性があるため、適宜調整してください。&lt;br /&gt;
// document.Tag に保持されるため、開いているファイルを閉じると履歴は消えます。&lt;br /&gt;
var HISTORY_SIZE = 0;&lt;br /&gt;
&lt;br /&gt;
// 会話履歴に保持する最大トークン数&lt;br /&gt;
// 会話履歴のトークン数がこの数を超える場合、古い履歴から削除されます。&lt;br /&gt;
var MAX_HISTORY_TOKEN = 10000;&lt;br /&gt;
&lt;br /&gt;
// コンテキストメニュー番号の定義&lt;br /&gt;
var EXECUTE_AI = 1;&lt;br /&gt;
var RESET_HISTORY = 2;&lt;br /&gt;
var SET_CUSTOM_INSTRUCTION = 3;&lt;br /&gt;
var REEXECUTE_LAST_TEMPLATE_PROMPT = 4;&lt;br /&gt;
var TEMPLATE_PROMPT_THRESHOLD = 10;&lt;br /&gt;
var TEMPLATE_PROMPT_SUBMENU_THRESHOLD = 1000;&lt;br /&gt;
&lt;br /&gt;
// タグ名の定義&lt;br /&gt;
var HISTORY_TAG = &amp;quot;groqllama-history&amp;quot;;&lt;br /&gt;
var TEMPORARY_CUSTOM_INSTRUCTION_TAG = &amp;quot;groqllama-temporary-custom-instruction&amp;quot;;&lt;br /&gt;
var PREVIOUS_TEMPLATE_PROMPT_TAG = &amp;quot;groqllama-previous-template-prompt&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
// 生成AIモデル別設定&lt;br /&gt;
&lt;br /&gt;
// const MODEL_CONFIG = {&lt;br /&gt;
//     model: &amp;quot;meta-llama/llama-4-maverick-17b-128e-instruct&amp;quot;,&lt;br /&gt;
//     max_tokens: 3500,&lt;br /&gt;
//     temperature: 0.6,&lt;br /&gt;
// }&lt;br /&gt;
&lt;br /&gt;
// const MODEL_CONFIG = {&lt;br /&gt;
//     model: &amp;quot;qwen/qwen3-32b&amp;quot;,&lt;br /&gt;
//     max_tokens: 5000,&lt;br /&gt;
//     temperature: 0.6,&lt;br /&gt;
//     max_completion_tokens: 1024,&lt;br /&gt;
//     reasoning_effort: &amp;quot;default&amp;quot;,&lt;br /&gt;
//     reasoning_format: &amp;quot;hidden&amp;quot;&lt;br /&gt;
// }&lt;br /&gt;
&lt;br /&gt;
const MODEL_CONFIG = {&lt;br /&gt;
    model: &amp;quot;moonshotai/kimi-k2-instruct&amp;quot;,&lt;br /&gt;
    max_tokens: 5000,&lt;br /&gt;
    temperature: 0.7,&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// console.log に出力する内容を Mery のアウトプットバーに表示する&lt;br /&gt;
var console = {&lt;br /&gt;
    log: function (str) {&lt;br /&gt;
        outputBar.Writeln(str)&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
var doc = document;&lt;br /&gt;
var sel = document.selection;&lt;br /&gt;
&lt;br /&gt;
// 生成リクエスト中止フラグ&lt;br /&gt;
var abortRequestFlg = false&lt;br /&gt;
// 生成リクエスト完了フラグ;&lt;br /&gt;
var requestCompletedFlg = false;&lt;br /&gt;
&lt;br /&gt;
main();&lt;br /&gt;
&lt;br /&gt;
function main() {&lt;br /&gt;
    var selectionIsEmpty = sel.IsEmpty&lt;br /&gt;
&lt;br /&gt;
    // プロンプトの取得&lt;br /&gt;
    if (selectionIsEmpty) {&lt;br /&gt;
        sel.StartOfLine(false, mePosLogical);&lt;br /&gt;
        sel.EndOfLine(true, mePosLogical);&lt;br /&gt;
    }&lt;br /&gt;
    var promptText = sel.Text.trim();&lt;br /&gt;
    var promptLength = sel.TextLength;&lt;br /&gt;
    var menuTrack = 0;&lt;br /&gt;
    while (true) {&lt;br /&gt;
        // コンテキストメニューの作成&lt;br /&gt;
        var menu = createContextMenu(selectionIsEmpty);&lt;br /&gt;
        menuTrack = menu.Track(0);&lt;br /&gt;
        // メニュー選択がキャンセルされた場合は終了&lt;br /&gt;
        if (menuTrack === 0) return;&lt;br /&gt;
&lt;br /&gt;
        // 会話履歴のリセット&lt;br /&gt;
        if (menuTrack === RESET_HISTORY) {&lt;br /&gt;
            resetTag();&lt;br /&gt;
            continue;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        // カスタムインストラクションの設定&lt;br /&gt;
        if (menuTrack === SET_CUSTOM_INSTRUCTION) {&lt;br /&gt;
            configureTemporaryCustomInstruction();&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        break;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // プロンプトが空の場合はエラー&lt;br /&gt;
    if (promptLength === 0) {&lt;br /&gt;
        alert(&amp;quot;プロンプトを入力してください。&amp;quot;);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
    // 文字数が多すぎる場合はエラー&lt;br /&gt;
    if (roughTokenize(promptText) &amp;gt; MAX_PROMPT_TOKEN) {&lt;br /&gt;
        alert(&amp;quot;プロンプトの文字数が多すぎます。文字数を少なくしてください。&amp;quot;);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
    // 読み取り専用の場合はエラー&lt;br /&gt;
    if (doc.ReadOnly) {&lt;br /&gt;
        alert(&amp;quot;ファイルが読み取り専用のため実行できません。&amp;quot;);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // 直前に実行したプロンプトテンプレートの再実行の場合、menuTrack を書き換え&lt;br /&gt;
    if (menuTrack === REEXECUTE_LAST_TEMPLATE_PROMPT) {&lt;br /&gt;
        if (doc.Tag.exists(PREVIOUS_TEMPLATE_PROMPT_TAG)) {&lt;br /&gt;
            menuTrack = Number(doc.Tag(PREVIOUS_TEMPLATE_PROMPT_TAG));&lt;br /&gt;
        } else {&lt;br /&gt;
            alert(&amp;quot;直前に実行したプロンプトテンプレートがありません。&amp;quot;);&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // テンプレートの場合はプロンプトを変更&lt;br /&gt;
    var isTemplate = menuTrack &amp;gt;= TEMPLATE_PROMPT_THRESHOLD;&lt;br /&gt;
    var isSubmenu = menuTrack &amp;gt;= TEMPLATE_PROMPT_SUBMENU_THRESHOLD;&lt;br /&gt;
    if (isSubmenu) {&lt;br /&gt;
        var parentMenuTrack = Number(String(menuTrack).slice(0, 2));&lt;br /&gt;
        var subMenuTrack = Number(String(menuTrack).slice(2));&lt;br /&gt;
        promptText = &amp;quot;=== INSTRUCTIONS START HERE ===\n\n&amp;quot;&lt;br /&gt;
            + PROMPT_TEMPLATE[parentMenuTrack - TEMPLATE_PROMPT_THRESHOLD].submenu[subMenuTrack].prompt&lt;br /&gt;
            + &amp;quot;\n\n=== INSTRUCTIONS END HERE ===\n\n&amp;quot;&lt;br /&gt;
            + &amp;quot;=== INPUT DATA STARTS HERE ===\n\n&amp;quot;&lt;br /&gt;
            + promptText&lt;br /&gt;
            + &amp;quot;\n\n=== INPUT DATA END HERE ===&amp;quot;;&lt;br /&gt;
    } else if (isTemplate) {&lt;br /&gt;
        promptText = &amp;quot;=== INSTRUCTIONS START HERE ===\n\n&amp;quot;&lt;br /&gt;
            + PROMPT_TEMPLATE[menuTrack - TEMPLATE_PROMPT_THRESHOLD].prompt&lt;br /&gt;
            + &amp;quot;\n\n=== INSTRUCTIONS END HERE ===\n\n&amp;quot;&lt;br /&gt;
            + &amp;quot;=== INPUT DATA STARTS HERE ===\n\n&amp;quot;&lt;br /&gt;
            + promptText&lt;br /&gt;
            + &amp;quot;\n\n=== INPUT DATA END HERE ===&amp;quot;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // テンプレートの場合は直前に実行したプロンプトテンプレートの番号としてタグに保存&lt;br /&gt;
    if (isTemplate) {&lt;br /&gt;
        addPreviousTemplatePromptNumberToTag(menuTrack);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    try {&lt;br /&gt;
        // 処理終了を待機&lt;br /&gt;
        shell.KeepRunning = true;&lt;br /&gt;
&lt;br /&gt;
        // カーソル位置移動&lt;br /&gt;
        sel.SetActivePoint(mePosLogical, sel.GetBottomPointX(mePosLogical), sel.GetBottomPointY(mePosLogical));&lt;br /&gt;
        sel.EndOfLine(false, mePosLogical);&lt;br /&gt;
        sel.NewLine();&lt;br /&gt;
&lt;br /&gt;
        // APIキーの取得&lt;br /&gt;
        var apiKey = shell.getEnv(&amp;quot;GROQ_API_KEY&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
        // GroqLlama API に問い合わせ&lt;br /&gt;
        sel.Text = &amp;quot;\n---\n\n&amp;quot;&lt;br /&gt;
        document.KeepScrollPos = true;&lt;br /&gt;
        callGroqLlamaAPI(apiKey, promptText, isTemplate);&lt;br /&gt;
        if (sel.GetActivePointX(mePosLogical) &amp;gt; 2) {&lt;br /&gt;
            sel.Text = &amp;quot;\n\n---\n\n\n&amp;quot;&lt;br /&gt;
        } else {&lt;br /&gt;
            sel.Text = &amp;quot;\n---\n\n\n&amp;quot;&lt;br /&gt;
        }&lt;br /&gt;
    } catch (e) {&lt;br /&gt;
        sel.Text = &amp;quot;&amp;lt;処理中にエラー発生&amp;gt;\n&amp;quot; + e.message + &amp;quot;\n&amp;quot; + e.stack;&lt;br /&gt;
    } finally {&lt;br /&gt;
        // 処理終了待機を解除&lt;br /&gt;
        shell.KeepRunning = false;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function createContextMenu(selectionIsEmpty) {&lt;br /&gt;
    var menu = CreatePopupMenu();&lt;br /&gt;
    menu.Add(&amp;quot;※注意※ プライバシー・機密情報を送信しないこと&amp;quot;, 0);&lt;br /&gt;
    menu.Add(&amp;quot;&amp;quot;, 0, meMenuSeparator);&lt;br /&gt;
    var t = &amp;quot;&amp;quot;;&lt;br /&gt;
    if (selectionIsEmpty) {&lt;br /&gt;
        t = &amp;quot;アクティブ行の内容で相談 (&amp;amp;A)&amp;quot;;&lt;br /&gt;
    } else {&lt;br /&gt;
        t = &amp;quot;選択範囲の内容で相談 (&amp;amp;A)&amp;quot;;&lt;br /&gt;
    }&lt;br /&gt;
    menu.Add(t, EXECUTE_AI);&lt;br /&gt;
    if (HISTORY_SIZE &amp;lt;= 0) {&lt;br /&gt;
        menu.Add(&amp;quot;├ 会話履歴をクリア (履歴が無効になっています)&amp;quot;, RESET_HISTORY, meMenuGrayed);&lt;br /&gt;
    } else if (doc.Tag.exists(HISTORY_TAG)) {&lt;br /&gt;
        var turn = JSON.parse(doc.Tag(HISTORY_TAG)).length / 2;&lt;br /&gt;
        menu.Add(&amp;quot;├ 会話履歴をクリア (現在の会話数: &amp;quot; + turn + &amp;quot;) (&amp;amp;C)&amp;quot;, RESET_HISTORY);&lt;br /&gt;
    } else {&lt;br /&gt;
        menu.Add(&amp;quot;├ 会話履歴をクリア (現在の会話数: 0) (&amp;amp;C)&amp;quot;, RESET_HISTORY, meMenuGrayed);&lt;br /&gt;
    }&lt;br /&gt;
    menu.Add(&amp;quot;└ カスタムインストラクションを変更 (&amp;amp;I)&amp;quot;, SET_CUSTOM_INSTRUCTION);&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    // テンプレート数チェック&lt;br /&gt;
    // 2桁まで許容&lt;br /&gt;
    var ITEMS_MUX = 99;&lt;br /&gt;
    if (PROMPT_TEMPLATE.length &amp;gt; ITEMS_MUX - TEMPLATE_PROMPT_THRESHOLD) {&lt;br /&gt;
        throw new Error(&amp;quot;プロンプトテンプレートの数が多すぎます。&amp;quot;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // テンプレートの追加&lt;br /&gt;
    if (PROMPT_TEMPLATE.length &amp;gt; 0) {&lt;br /&gt;
        var subMenu = CreatePopupMenu();&lt;br /&gt;
        for (var i = 0; i &amp;lt; PROMPT_TEMPLATE.length; i++) {&lt;br /&gt;
            var accelerationKey = &amp;quot;&amp;quot;;&lt;br /&gt;
            if (i === 9) {&lt;br /&gt;
                accelerationKey = &amp;quot; (&amp;amp;0)&amp;quot;;&lt;br /&gt;
            } else if (i &amp;lt; 10) {&lt;br /&gt;
                accelerationKey = &amp;quot; (&amp;amp;&amp;quot; + (i + 1) + &amp;quot;)&amp;quot;;&lt;br /&gt;
            }&lt;br /&gt;
            var menuNum = TEMPLATE_PROMPT_THRESHOLD + i;&lt;br /&gt;
            if (PROMPT_TEMPLATE[i].submenu) {&lt;br /&gt;
                var subMenuList = PROMPT_TEMPLATE[i].submenu;&lt;br /&gt;
                // テンプレート数チェック&lt;br /&gt;
                if (subMenuList.length &amp;gt; ITEMS_MUX) {&lt;br /&gt;
                    throw new Error(&amp;quot;プロンプトテンプレート (サブメニュー) の数が多すぎます。&amp;quot;);&lt;br /&gt;
                }&lt;br /&gt;
                var subSubMenu = CreatePopupMenu();&lt;br /&gt;
                var subMenuPrefix = String(menuNum);&lt;br /&gt;
                for (var j = 0; j &amp;lt; subMenuList.length; j++) {&lt;br /&gt;
                    var subAccelerationKey = &amp;quot;&amp;quot;;&lt;br /&gt;
                    if (j === 9) {&lt;br /&gt;
                        subAccelerationKey = &amp;quot; (&amp;amp;0)&amp;quot;;&lt;br /&gt;
                    } else if (j &amp;lt; 10) {&lt;br /&gt;
                        subAccelerationKey = &amp;quot; (&amp;amp;&amp;quot; + (j + 1) + &amp;quot;)&amp;quot;;&lt;br /&gt;
                    }&lt;br /&gt;
                    // ES5 compatible version without padStart&lt;br /&gt;
                    var subMenuNum = Number(subMenuPrefix + (j &amp;lt; 10 ? &amp;quot;0&amp;quot; + j : String(j)));&lt;br /&gt;
                    subSubMenu.Add(subMenuList[j].title + subAccelerationKey, subMenuNum);&lt;br /&gt;
                }&lt;br /&gt;
                subMenu.AddPopup(PROMPT_TEMPLATE[i].submenu_title + accelerationKey, subSubMenu);&lt;br /&gt;
            } else {&lt;br /&gt;
                subMenu.Add(PROMPT_TEMPLATE[i].title + accelerationKey, menuNum);&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        if (selectionIsEmpty) {&lt;br /&gt;
            t = &amp;quot;プロンプト テンプレート (対象: アクティブ行) (&amp;amp;T)&amp;quot;;&lt;br /&gt;
        } else {&lt;br /&gt;
            t = &amp;quot;プロンプト テンプレート (対象: 選択範囲) (&amp;amp;T)&amp;quot;;&lt;br /&gt;
        }&lt;br /&gt;
        menu.AddPopup(t, subMenu);&lt;br /&gt;
&lt;br /&gt;
        t = &amp;quot;└ 直前に実行したプロンプト テンプレートを再実行 (&amp;amp;R)&amp;quot;;&lt;br /&gt;
        if (doc.Tag.exists(PREVIOUS_TEMPLATE_PROMPT_TAG)) {&lt;br /&gt;
            menu.Add(t, REEXECUTE_LAST_TEMPLATE_PROMPT);&lt;br /&gt;
        } else {&lt;br /&gt;
            menu.Add(t, REEXECUTE_LAST_TEMPLATE_PROMPT, meMenuGrayed);&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return menu;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function callGroqLlamaAPI(apiKey, promptText, isTemplate) {&lt;br /&gt;
    try {&lt;br /&gt;
        // fetch で Groq API にリクエスト&lt;br /&gt;
        var requestBody = {&lt;br /&gt;
            messages: createGroqLlamaRequest(promptText, isTemplate),&lt;br /&gt;
            stream: false,&lt;br /&gt;
        };&lt;br /&gt;
        for (var key in MODEL_CONFIG) {&lt;br /&gt;
            requestBody[key] = MODEL_CONFIG[key];&lt;br /&gt;
        }&lt;br /&gt;
        if (isTemplate) {&lt;br /&gt;
            // テンプレートの場合は応答を決定論的にする&lt;br /&gt;
            // テンプレート以外の場合はデフォルト値とするために指定しない&lt;br /&gt;
            requestBody.temperature = 0.3;&lt;br /&gt;
        }&lt;br /&gt;
        var url = &#039;https://api.groq.com/openai/v1/chat/completions&#039;;&lt;br /&gt;
&lt;br /&gt;
        var http = CreateObject(&amp;quot;Msxml2.ServerXMLHTTP&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
        http.open(&#039;POST&#039;, url, true);&lt;br /&gt;
        http.setRequestHeader(&#039;Content-Type&#039;, &#039;application/json&#039;);&lt;br /&gt;
        http.setRequestHeader(&#039;Authorization&#039;, &#039;Bearer &#039; + apiKey);&lt;br /&gt;
        http.send(JSON.stringify(requestBody));&lt;br /&gt;
&lt;br /&gt;
        // 応答待機&lt;br /&gt;
        var timeout = 60;&lt;br /&gt;
        var retrySec = 0;&lt;br /&gt;
        var startX = sel.GetActivePointX(mePosLogical);&lt;br /&gt;
        var prevPos = sel.GetActivePos();&lt;br /&gt;
        while (http.readyState != 4) {&lt;br /&gt;
            if (retrySec &amp;gt; timeout) {&lt;br /&gt;
                break;&lt;br /&gt;
            }&lt;br /&gt;
            http.waitForResponse(1);&lt;br /&gt;
            retrySec += 1;&lt;br /&gt;
            inputProgressChar(prevPos);&lt;br /&gt;
            prevPos = sel.GetActivePos();&lt;br /&gt;
        }&lt;br /&gt;
        // 進捗表示文字を削除&lt;br /&gt;
        sel.SetActivePoint(&lt;br /&gt;
            mePosLogical,&lt;br /&gt;
            startX,&lt;br /&gt;
            sel.GetActivePointY(mePosLogical),&lt;br /&gt;
            true&lt;br /&gt;
        );&lt;br /&gt;
        sel.Delete();&lt;br /&gt;
&lt;br /&gt;
        // 応答ステータスチェック&lt;br /&gt;
        if (!(http.readyState == 4 &amp;amp;&amp;amp; http.status == 200)) {&lt;br /&gt;
            sel.Text = &amp;quot;エラーが発生しました: &amp;quot; + http.status + &amp;quot; &amp;quot; + http.statusText + &amp;quot;\n&amp;quot; + http.responseText;&lt;br /&gt;
            http.abort();&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        var ret = &amp;quot;&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
        // JSONをパースして必要なテキストを抽出&lt;br /&gt;
        var response = JSON.parse(http.responseText);&lt;br /&gt;
&lt;br /&gt;
        var choices = response.choices;&lt;br /&gt;
        if (choices &amp;amp;&amp;amp; choices.length &amp;gt; 0) {&lt;br /&gt;
            choices.forEach(function (choice) {&lt;br /&gt;
                var message = choice.message;&lt;br /&gt;
                if (message) {&lt;br /&gt;
                    ret += message.content;&lt;br /&gt;
                }&lt;br /&gt;
            });&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        var usage = response.usage;&lt;br /&gt;
        var usageText = &amp;quot;&amp;quot;;&lt;br /&gt;
        if (usage) {&lt;br /&gt;
            usageText += &amp;quot;\n\n(利用トークン数: プロンプト=&amp;quot; + usage.prompt_tokens&lt;br /&gt;
                + &amp;quot;, 応答=&amp;quot; + usage.completion_tokens&lt;br /&gt;
                + &amp;quot;, 合計=&amp;quot; + usage.total_tokens + &amp;quot;)&amp;quot;;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        sel.Text = ret + usageText;&lt;br /&gt;
&lt;br /&gt;
        // 結果を会話履歴に追加&lt;br /&gt;
        if (HISTORY_SIZE &amp;gt; 0 &amp;amp;&amp;amp; !isTemplate) {&lt;br /&gt;
            appendReturnTextToTag(promptText, ret);&lt;br /&gt;
        }&lt;br /&gt;
    } catch (e) {&lt;br /&gt;
        throw e;&lt;br /&gt;
    } finally {&lt;br /&gt;
        requestCompletedFlg = true;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function roughTokenize(text) {&lt;br /&gt;
    // 超適当なトークナイザ。トークン数をざっくりと多めに数える&lt;br /&gt;
    // 空白文字・ハイフン・アンダースコア区切りの単語に分割して、&lt;br /&gt;
    // 単語がアルファベットのみ、数字のみの場合には 4文字=1トークン として数える。&lt;br /&gt;
    // 単語にアルファベット以外の文字が含まれる場合は 1文字=1トークン として数える。&lt;br /&gt;
&lt;br /&gt;
    var words = text.split(/[\s\-_]+/);&lt;br /&gt;
    var totalTokens = 0;&lt;br /&gt;
&lt;br /&gt;
    for (var i = 0; i &amp;lt; words.length; i++) {&lt;br /&gt;
        var word = words[i];&lt;br /&gt;
        if (!word) continue; // 空の単語をスキップ&lt;br /&gt;
&lt;br /&gt;
        if (/^[a-zA-Z]+$/.test(word) || /^[0-9]+$/.test(word)) {&lt;br /&gt;
            totalTokens += Math.ceil(word.length / 4);&lt;br /&gt;
        } else {&lt;br /&gt;
            totalTokens += word.length;&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return totalTokens;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// document.Tag の内容を加味してGroqLlamaのリクエストを作成&lt;br /&gt;
function createGroqLlamaRequest(promptText, isTemplate) {&lt;br /&gt;
    var customInstructionText = buildCustomInstructionText();&lt;br /&gt;
    // テンプレートの場合は、会話履歴とカスタムインストラクションを反映しない&lt;br /&gt;
    if (isTemplate) {&lt;br /&gt;
        var request = [&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;role&amp;quot;: &amp;quot;user&amp;quot;,&lt;br /&gt;
                &amp;quot;content&amp;quot;: [{ &amp;quot;type&amp;quot;: &amp;quot;text&amp;quot;, &amp;quot;text&amp;quot;: promptText }]&lt;br /&gt;
            }&lt;br /&gt;
        ];&lt;br /&gt;
        if (customInstructionText) {&lt;br /&gt;
            request.unshift({&lt;br /&gt;
                &amp;quot;role&amp;quot;: &amp;quot;system&amp;quot;,&lt;br /&gt;
                &amp;quot;content&amp;quot;: customInstructionText&lt;br /&gt;
            })&lt;br /&gt;
        }&lt;br /&gt;
        return request;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    var tag = document.Tag;&lt;br /&gt;
&lt;br /&gt;
    // 以前の会話が存在しない場合は新規リクエスト&lt;br /&gt;
    if (!tag.exists(HISTORY_TAG)) {&lt;br /&gt;
        var request = [{&lt;br /&gt;
            &amp;quot;role&amp;quot;: &amp;quot;user&amp;quot;,&lt;br /&gt;
            &amp;quot;content&amp;quot;: [{ &amp;quot;type&amp;quot;: &amp;quot;text&amp;quot;, &amp;quot;text&amp;quot;: promptText }]&lt;br /&gt;
        }];&lt;br /&gt;
        if (customInstructionText) {&lt;br /&gt;
            request.unshift({&lt;br /&gt;
                &amp;quot;role&amp;quot;: &amp;quot;system&amp;quot;,&lt;br /&gt;
                &amp;quot;content&amp;quot;: customInstructionText&lt;br /&gt;
            })&lt;br /&gt;
        }&lt;br /&gt;
        return request;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // 会話履歴が存在する場合は、履歴を含めてリクエスト&lt;br /&gt;
    var request = JSON.parse(tag(HISTORY_TAG));&lt;br /&gt;
    if (customInstructionText) {&lt;br /&gt;
        request.unshift({&lt;br /&gt;
            &amp;quot;role&amp;quot;: &amp;quot;system&amp;quot;,&lt;br /&gt;
            &amp;quot;content&amp;quot;: customInstructionText&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
    request.push({&lt;br /&gt;
        &amp;quot;role&amp;quot;: &amp;quot;user&amp;quot;,&lt;br /&gt;
        &amp;quot;content&amp;quot;: promptText&lt;br /&gt;
    });&lt;br /&gt;
    return request;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// document.Tag にGroqLlamaの応答を追加&lt;br /&gt;
function appendReturnTextToTag(userText, modelText) {&lt;br /&gt;
    var tag = document.Tag;&lt;br /&gt;
    if (!tag.exists(HISTORY_TAG)) {&lt;br /&gt;
        tag(HISTORY_TAG) = JSON.stringify([&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;role&amp;quot;: &amp;quot;user&amp;quot;,&lt;br /&gt;
                &amp;quot;content&amp;quot;: userText&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;role&amp;quot;: &amp;quot;assistant&amp;quot;,&lt;br /&gt;
                &amp;quot;content&amp;quot;: modelText&lt;br /&gt;
            }&lt;br /&gt;
        ]);&lt;br /&gt;
    } else {&lt;br /&gt;
        var groqllamaText = JSON.parse(tag(HISTORY_TAG));&lt;br /&gt;
        groqllamaText.push({&lt;br /&gt;
            &amp;quot;role&amp;quot;: &amp;quot;user&amp;quot;,&lt;br /&gt;
            &amp;quot;content&amp;quot;: userText&lt;br /&gt;
        });&lt;br /&gt;
        groqllamaText.push({&lt;br /&gt;
            &amp;quot;role&amp;quot;: &amp;quot;assistant&amp;quot;,&lt;br /&gt;
            &amp;quot;content&amp;quot;: modelText&lt;br /&gt;
        });&lt;br /&gt;
        // 会話セット (user, model 1往復) が特定セット数を超えた場合、最初のセットを削除&lt;br /&gt;
        if (groqllamaText.length &amp;gt; HISTORY_SIZE * 2) {&lt;br /&gt;
            groqllamaText.splice(0, 2);&lt;br /&gt;
        }&lt;br /&gt;
        // 会話履歴全体が MAX_HISTORY_TOKEN を超えた場合、最初のセットを削除&lt;br /&gt;
        var joinedText = &amp;quot;&amp;quot;;&lt;br /&gt;
        for (var i = 0; i &amp;lt; groqllamaText.length; i++) {&lt;br /&gt;
            joinedText += groqllamaText[i].content + &amp;quot;\n&amp;quot;;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        while (roughTokenize(joinedText) &amp;gt; MAX_HISTORY_TOKEN) {&lt;br /&gt;
            groqllamaText.splice(0, 2);&lt;br /&gt;
            joinedText = &amp;quot;&amp;quot;;&lt;br /&gt;
            for (var i = 0; i &amp;lt; groqllamaText.length; i++) {&lt;br /&gt;
                joinedText += groqllamaText[i].content + &amp;quot;\n&amp;quot;;&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        tag(HISTORY_TAG) = JSON.stringify(groqllamaText);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function resetTag() {&lt;br /&gt;
    var tag = doc.Tag;&lt;br /&gt;
    if (tag.exists(HISTORY_TAG)) {&lt;br /&gt;
        tag.remove(HISTORY_TAG);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function baseCustomInstruction() {&lt;br /&gt;
    var language = LANGUAGE.trim();&lt;br /&gt;
    return &amp;quot;Do not hallucinate. If you are unsure or don&#039;t have enough information to answer with confidence, say \&amp;quot;I don&#039;t know\&amp;quot; or \&amp;quot;I&#039;m not sure.\&amp;quot;\n&amp;quot;&lt;br /&gt;
        + &amp;quot;If you use Markdown headings (lines beginning with one or more # characters), you must surround the heading with blank lines.\n&amp;quot;&lt;br /&gt;
        + (language ? &amp;quot;Respond in &amp;quot; + language + &amp;quot;.\n&amp;quot; : &amp;quot;&amp;quot;)&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function buildCustomInstructionText() {&lt;br /&gt;
    var customInstruction = getTemporaryCustomInstruction();&lt;br /&gt;
    if (!customInstruction) {&lt;br /&gt;
        // 一時的なカスタムインストラクションがない場合はデフォルトを使用&lt;br /&gt;
        customInstruction = DEFAULT_CUSTOM_INSTRUCTION;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    customInstruction = customInstruction.replace(/^[\s　]+|[\s　]+$/g, &amp;quot;&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
    return (customInstruction ? customInstruction + &amp;quot;\n&amp;quot; : &amp;quot;&amp;quot;)&lt;br /&gt;
        + baseCustomInstruction();&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function configureTemporaryCustomInstruction() {&lt;br /&gt;
    var tempCustomInstruction = getTemporaryCustomInstruction();&lt;br /&gt;
    var prompt = Prompt(&amp;quot;カスタムインストラクションを変更 ※変更すると会話履歴がリセットされます&amp;quot;, tempCustomInstruction ? tempCustomInstruction : DEFAULT_CUSTOM_INSTRUCTION);&lt;br /&gt;
    if (prompt) {&lt;br /&gt;
        if (prompt === DEFAULT_CUSTOM_INSTRUCTION) {&lt;br /&gt;
            // デフォルトと同じ値の場合はタグを削除&lt;br /&gt;
            setTemporaryCustomInstruction(&amp;quot;&amp;quot;);&lt;br /&gt;
        } else if (prompt !== tempCustomInstruction) {&lt;br /&gt;
            // デフォルト以外の値が設定された場合は保存&lt;br /&gt;
            setTemporaryCustomInstruction(prompt);&lt;br /&gt;
        }&lt;br /&gt;
    } else {&lt;br /&gt;
        // 空文字でOK、もしくはキャンセルされた場合はデフォルトに戻すか確認&lt;br /&gt;
        if (Confirm(&amp;quot;カスタムインストラクションをデフォルトに戻しますか？&amp;quot;)) {&lt;br /&gt;
            setTemporaryCustomInstruction(&amp;quot;&amp;quot;);&lt;br /&gt;
        } else {&lt;br /&gt;
            alert(&amp;quot;カスタムインストラクションの変更をキャンセルしました。\n※カスタムインストラクションを空にしたい場合はスペースのみを入力してください。&amp;quot;);&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // 変更が加わっている場合は会話履歴をリセット&lt;br /&gt;
    if (tempCustomInstruction !== getTemporaryCustomInstruction()) {&lt;br /&gt;
        resetTag();&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getTemporaryCustomInstruction() {&lt;br /&gt;
    var tag = doc.Tag;&lt;br /&gt;
    if (tag.exists(TEMPORARY_CUSTOM_INSTRUCTION_TAG)) {&lt;br /&gt;
        return tag(TEMPORARY_CUSTOM_INSTRUCTION_TAG);&lt;br /&gt;
    } else {&lt;br /&gt;
        return &amp;quot;&amp;quot;;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function setTemporaryCustomInstruction(temporaryCustomInstruction) {&lt;br /&gt;
    var tag = doc.Tag;&lt;br /&gt;
    if (temporaryCustomInstruction) {&lt;br /&gt;
        tag(TEMPORARY_CUSTOM_INSTRUCTION_TAG) = temporaryCustomInstruction;&lt;br /&gt;
    } else {&lt;br /&gt;
        tag.remove(TEMPORARY_CUSTOM_INSTRUCTION_TAG);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function addPreviousTemplatePromptNumberToTag(menuTrack) {&lt;br /&gt;
    var tag = doc.Tag;&lt;br /&gt;
    tag(PREVIOUS_TEMPLATE_PROMPT_TAG) = menuTrack;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function inputProgressChar(pos) {&lt;br /&gt;
    sel.SetActivePos(pos);&lt;br /&gt;
    sel.Text = &amp;quot;.&amp;quot;;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;/div&gt;</summary>
		<author><name>Yuko</name></author>
	</entry>
	<entry>
		<id>https://www.haijin-boys.com/wiki/index.php?title=Groq_%E3%81%AB%E7%9B%B8%E8%AB%87&amp;diff=9160</id>
		<title>Groq に相談</title>
		<link rel="alternate" type="text/html" href="https://www.haijin-boys.com/wiki/index.php?title=Groq_%E3%81%AB%E7%9B%B8%E8%AB%87&amp;diff=9160"/>
		<updated>2025-07-22T13:19:12Z</updated>

		<summary type="html">&lt;p&gt;Yuko: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== 概要 ==&lt;br /&gt;
&lt;br /&gt;
生成AIプラットフォームである [https://groq.com GroqCloud] で「Llama」を利用するためのMeryマクロです。&lt;br /&gt;
※Llama以外のモデルも使えるように適当なタイミングで更新をかけています。&lt;br /&gt;
&lt;br /&gt;
「Google Geminiに相談」マクロでは動作中のストリーム通信に対応するためにV8エンジンで実行していましたが、V8エンジン版は使用上の制限からマクロ実行を開始するまで1秒程度のタイムラグが発生していました。GroqCloudの売りである超高速レスポンスを見込んで本マクロではストリーム通信を使わないようにしました。これにより従来のJScriptで処理を組み立てることができるため、前述のタイムラグが起こらないようになりました。また、ストリーム通信を使わないことで通信量が少なく応答もシンプルになるため、場合によっては「Google Geminiに相談」マクロよりも処理が安定する可能性があります。&lt;br /&gt;
&lt;br /&gt;
【主な機能】&lt;br /&gt;
&lt;br /&gt;
* GroqCloud APIを利用して生成AIと対話できるMery用のマクロ&lt;br /&gt;
* 選択したテキストやアクティブな行の内容について生成AIに質問・相談が可能&lt;br /&gt;
* 会話履歴を保持し、文脈を考慮した対話が可能&lt;br /&gt;
&lt;br /&gt;
【特徴的な機能】&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;会話履歴機能&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* 事前の設定数に応じた会話履歴を保持&lt;br /&gt;
* 履歴のクリアが可能&lt;br /&gt;
&lt;br /&gt;
これにより、会話の文脈を考慮した回答を得られます。&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;カスタムインストラクション&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* AIの応答方針を設定可能&lt;br /&gt;
* 一時的なカスタム指示の保存と変更が可能&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;プロンプトテンプレート&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* 誤字脱字チェック&lt;br /&gt;
* 日本語翻訳&lt;br /&gt;
* コードの最適化、生成&lt;br /&gt;
&lt;br /&gt;
など、定型的な質問を簡単に実行できます。&lt;br /&gt;
※テンプレート機能では回答を安定させるため、前述の会話履歴を文脈に含みません。&lt;br /&gt;
&lt;br /&gt;
テンプレートは自身の使い方に合わせて好きに追加、削除ができます。詳細はソースコード内の「PROMPT_TEMPLATE」定数のコメントを参照。&lt;br /&gt;
&lt;br /&gt;
== 注意事項 (必ず読んでからご利用ください) ==&lt;br /&gt;
&lt;br /&gt;
* 本マクロでは 2025/04/13 時点で無料利用ができるGroqCloud APIを利用します。今後、プラットフォームの改定により有償になったり、設定誤り等で費用が発生しても、作者は一切の責任を負いません。&lt;br /&gt;
* GroqCloud APIではリクエスト内容をAI学習に用いないことをプライバシポリシーとして謳っているものの、くれぐれも機密情報やプライバシーに関わる情報を指定しないように注意してください。&lt;br /&gt;
* GroqCloud APIはプラットフォームの利用規約に則ってご利用ください。&lt;br /&gt;
&lt;br /&gt;
== 利用前提 ==&lt;br /&gt;
&lt;br /&gt;
* Mery Ver 3.7.13 以上&lt;br /&gt;
* GroqCloudで、GroqCloud API APIキーを取得していること (参考 [https://console.groq.com/docs/quickstart Quickstart])&lt;br /&gt;
&lt;br /&gt;
== 利用方法 ==&lt;br /&gt;
&lt;br /&gt;
* 環境変数の設定&lt;br /&gt;
:コマンド プロンプトで以下のコマンドを実行し、環境変数を設定してください。&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bat&amp;quot; copy&amp;gt;&lt;br /&gt;
setx GROQ_API_KEY &amp;quot;***************************************&amp;quot;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
:設定後、Mery または Windows の再起動が必要な場合があります。&lt;br /&gt;
* マクロの実行&lt;br /&gt;
**プロンプトにしたい文字列を選択してマクロを実行すると、選択したテキストがプロンプトとしてGemini APIに送信され、応答がエディタに表示されます。&lt;br /&gt;
* 会話の履歴数を調整&lt;br /&gt;
** 会話の履歴は設定した個数分が保持され、文脈として利用されます。適宜、ソースコード内の「HISTORY_SIZE」の数を調整してください。&lt;br /&gt;
** ※数を増やすほど会話内容を保持して文脈を理解するようになりますが、メモリ使用量が増えたり動作が重くなる可能性があります。&lt;br /&gt;
** ※開いている文書ごとに履歴を保持します。文脈をリセットしたいときは、新しい文書を開いて会話をやり直してください。&lt;br /&gt;
&lt;br /&gt;
== ソースコード ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot; copy&amp;gt;&lt;br /&gt;
#title = &amp;quot;Groq に相談&amp;quot;&lt;br /&gt;
BeginUndoGroup();&lt;br /&gt;
&lt;br /&gt;
// カスタムインストラクション&lt;br /&gt;
// これを設定することで、生成AIの回答全体への生成方針を指示できます。&lt;br /&gt;
// 例えば &amp;quot;あなたは「Kimi」という名前のギャルAIアシスタントです。敬語は使いません。絵文字を使って感情表現をします。人懐っこく、バイタリティ溢れる性格で、ユーザーの課題を協力して解決します。&amp;quot; と設定すると、AIの雰囲気が元気な子になります。&lt;br /&gt;
// 何も指示しない場合は、空文字 &amp;quot;&amp;quot; を設定してください。&lt;br /&gt;
var DEFAULT_CUSTOM_INSTRUCTION = &amp;quot;&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
// プロンプトテンプレート&lt;br /&gt;
// プロンプトテンプレートを設定することで、AIに対して特定の指示を出すことができます。&lt;br /&gt;
// プロンプトテンプレートでの生成は会話履歴とカスタムインストラクションを反映しません。&lt;br /&gt;
// テンプレートは以下の { ～ }, で囲まれたブロックを好きに増減させて利用できます。&lt;br /&gt;
var PROMPT_TEMPLATE = [&lt;br /&gt;
    // 以下はサンプルです。1階層メニューと2階層メニューが利用できます。&lt;br /&gt;
    // {&lt;br /&gt;
    //     &amp;quot;title&amp;quot;: &amp;quot;要約&amp;quot;,&lt;br /&gt;
    //     &amp;quot;prompt&amp;quot;: &amp;quot;以下の文章を要約してください。&amp;quot;&lt;br /&gt;
    // },&lt;br /&gt;
    // {&lt;br /&gt;
    //     &amp;quot;submenu_title&amp;quot;: &amp;quot;翻訳系&amp;quot;,&lt;br /&gt;
    //     &amp;quot;submenu&amp;quot;: [&lt;br /&gt;
    //         {&lt;br /&gt;
    //             &amp;quot;title&amp;quot;: &amp;quot;日本語翻訳&amp;quot;,&lt;br /&gt;
    //             &amp;quot;prompt&amp;quot;: &amp;quot;以下の文章を日本語に翻訳してください。&amp;quot;&lt;br /&gt;
    //         },&lt;br /&gt;
    //         {&lt;br /&gt;
    //             &amp;quot;title&amp;quot;: &amp;quot;英語翻訳&amp;quot;,&lt;br /&gt;
    //             &amp;quot;prompt&amp;quot;: &amp;quot;以下の文章を英語に翻訳してください。&amp;quot;&lt;br /&gt;
    //         },&lt;br /&gt;
    //     ]&lt;br /&gt;
    // },&lt;br /&gt;
&lt;br /&gt;
    {&lt;br /&gt;
        &amp;quot;submenu_title&amp;quot;: &amp;quot;理解を深める&amp;quot;,&lt;br /&gt;
        &amp;quot;submenu&amp;quot;: [&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;論点を整理する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の論点を整理して。&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;論理の抜け漏れを探す&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の論点を整理し、忖度せずに鋭く、論理の抜け漏れを抽出して。そして、そこから発生する可能性のあるリスクも教えて。&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;要約する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;要約して。専門的な用語と思われる単語は簡単に解説も入れて。&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;物語対話形式で教えて&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の内容について物語対話形式で教えてください。&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
        ]&lt;br /&gt;
    },&lt;br /&gt;
    {&lt;br /&gt;
        &amp;quot;submenu_title&amp;quot;: &amp;quot;翻訳する&amp;quot;,&lt;br /&gt;
        &amp;quot;submenu&amp;quot;: [&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;日本語に翻訳&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の文章を日本語に翻訳してください。\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;出力形式は以下にすること:\n[訳]\n翻訳文...\n&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;英語に翻訳&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の文章を英語に翻訳してください。\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;出力形式は以下にすること:\n[Translation]\n翻訳文...\n&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;英語-&amp;gt;日本語 スラッシュリーディング翻訳&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の英文を日本語にスラッシュリーディング形式で翻訳してください。\n\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;条件:\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 英文の各フレーズをスラッシュ(/)で区切って表示\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 各フレーズの日本語訳を括弧()内に併記\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 文の終わりはダブルスラッシュ(//)で表示\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 英語の語順に従って日本語訳を行う\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 日本語訳は自然な日本語である必要はなく、英語の語順を反映した直訳調で構わない\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 主語が省略されている場合は補って翻訳すること\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 一般的な英文で頻出する表現がある場合は次の行に記載\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 以下の出力例のように &amp;lt;英文&amp;gt; (日本語訳) / ... とスラッシュ区切りの繰り返しになる \n\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;出力例:\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;Snowstorms are forecast (暴風雪が予報されている) / to hit the Sea of Japan coasts (日本海沿岸を襲うと) / of northern to eastern Japan (北部から東部の) / from Sunday through Monday. (日曜から月曜にかけて。) //\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;【頻出表現】\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- be forecast to: ～すると予報されている\n&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
        ]&lt;br /&gt;
    },&lt;br /&gt;
    {&lt;br /&gt;
        &amp;quot;submenu_title&amp;quot;: &amp;quot;プログラミング支援&amp;quot;,&lt;br /&gt;
        &amp;quot;submenu&amp;quot;: [&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;コードを洗練させる&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下のコードを洗練させてください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;コードをレビューする&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下のコードをセキュリティと保守性の観点でレビューしてください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;エラーログをデバッグする&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下のエラーログを解析し、考えられる原因と対策を提示してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;SQLクエリを最適化する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下のSQL文を最適化してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;PowerShell スクリプトを書く&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の処理を行うPowerShellスクリプトを生成してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;Shell Script を書く&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の処理を行うShell Scriptを生成してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;Linux コマンドを提案&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;あなたはLinuxエキスパートとして、以下のLinuxコマンドを提案してください。\n\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;制約条件:\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 多数のディストリビューションで採用されているメジャーなコマンドを使用し、ポータブルな実装にすること\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- bashでの実行を前提にすること\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 可能な限りシンプルな実装にすること\n\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;期待する出力:\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- コマンドの具体的な実装方法\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 各コマンドの動作説明\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 制限事項\n&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
        ]&lt;br /&gt;
    },&lt;br /&gt;
    {&lt;br /&gt;
        &amp;quot;submenu_title&amp;quot;: &amp;quot;ビジネス文書作成支援&amp;quot;,&lt;br /&gt;
        &amp;quot;submenu&amp;quot;: [&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;誤字脱字、不正確な表現をチェック&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の2つのカテゴリーで問題点を指摘してください\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 【誤字脱字】：明確な誤字脱字および誤字の疑いがある箇所\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 【不正確・曖昧な表現】：解釈に個人差が生じる可能性がある表現、より適切な表現に改善できる箇所&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;提案書を作成する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下に対する対する提案書を、コスト・実現性・リスクを含めて作成してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;仕様書のアウトラインを作成する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の内容で仕様書のアウトラインを作成してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;手順書のアウトラインを作成する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の作業を行う手順書を、前提条件と注意事項を含めてアウトラインを作成してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;ユーザーマニュアルのアウトラインを作成する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の機能内容から、ユーザー向けマニュアルのアウトラインを作成してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;議事録を作成する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の会議内容から議事録を作成してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;進捗報告書を作成する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下のタスク状況から進捗報告書を作成してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
        ]&lt;br /&gt;
    },&lt;br /&gt;
    {&lt;br /&gt;
        &amp;quot;submenu_title&amp;quot;: &amp;quot;分析支援&amp;quot;,&lt;br /&gt;
        &amp;quot;submenu&amp;quot;: [&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;問題を分析し、課題を抽出する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の状況から問題や潜在的リスクを分析し、課題を抽出してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;対応策を提案する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の問題・課題に対する対応策を複数提案してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;傾向を見つける&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の内容から、数値や挙動上の傾向を見つけてください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
        ]&lt;br /&gt;
    },&lt;br /&gt;
    {&lt;br /&gt;
        &amp;quot;submenu_title&amp;quot;: &amp;quot;アイデア出し&amp;quot;,&lt;br /&gt;
        &amp;quot;submenu&amp;quot;: [&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;SCAMPER法でブレインストーミング&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下のテーマについて、SCAMPER法を使って15個のアイデアを出してください。制約条件は以下の通りです：&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;解決策の幅出し&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の課題に対して、技術面、運用面、コスト面から実現可能な解決策を5つずつ提案してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;業務改善案の提案&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の既存プロセスについて、効率化、コスト削減、品質向上の観点から改善案を提案してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;逆転の発想&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の目標に対して、通常とは逆の視点からアプローチする方法を3つ提案してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;競合分析からの着想&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の内容について、業界における競合他社の特徴を分析し、差別化できる新しいアプローチを提案してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;ユースケースの列挙&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の製品・サービスについて、想定されるユースケースを10個列挙し、それぞれの価値提案を記載してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
        ]&lt;br /&gt;
    },&lt;br /&gt;
]&lt;br /&gt;
&lt;br /&gt;
// 生成AIの回答の言語設定&lt;br /&gt;
// &amp;quot;Japanese&amp;quot; や &amp;quot;English&amp;quot; を設定できます。他の言語は未検証。&lt;br /&gt;
// 自動設定にする場合は空文字 &amp;quot;&amp;quot; を設定してください。&lt;br /&gt;
var LANGUAGE = &amp;quot;Japanese&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
// 入力プロンプトの最大トークン数&lt;br /&gt;
// 超適当なトークナイザー関数により計測し、英単語は4文字1トークン、それ以外は1文字1トークンとして数えます。&lt;br /&gt;
var MAX_PROMPT_TOKEN = 6000;&lt;br /&gt;
&lt;br /&gt;
// 会話の履歴を保持する数 (質問と回答で1セット)&lt;br /&gt;
// 数を増やすと、会話の履歴が増えますが、Meryのメモリ使用量が増えたり、&lt;br /&gt;
// 使用トークン数が増えて動作が重くなる可能性があるため、適宜調整してください。&lt;br /&gt;
// document.Tag に保持されるため、開いているファイルを閉じると履歴は消えます。&lt;br /&gt;
var HISTORY_SIZE = 0;&lt;br /&gt;
&lt;br /&gt;
// 会話履歴に保持する最大トークン数&lt;br /&gt;
// 会話履歴のトークン数がこの数を超える場合、古い履歴から削除されます。&lt;br /&gt;
var MAX_HISTORY_TOKEN = 10000;&lt;br /&gt;
&lt;br /&gt;
// コンテキストメニュー番号の定義&lt;br /&gt;
var EXECUTE_AI = 1;&lt;br /&gt;
var RESET_HISTORY = 2;&lt;br /&gt;
var SET_CUSTOM_INSTRUCTION = 3;&lt;br /&gt;
var REEXECUTE_LAST_TEMPLATE_PROMPT = 4;&lt;br /&gt;
var TEMPLATE_PROMPT_THRESHOLD = 10;&lt;br /&gt;
var TEMPLATE_PROMPT_SUBMENU_THRESHOLD = 1000;&lt;br /&gt;
&lt;br /&gt;
// タグ名の定義&lt;br /&gt;
var HISTORY_TAG = &amp;quot;groqllama-history&amp;quot;;&lt;br /&gt;
var TEMPORARY_CUSTOM_INSTRUCTION_TAG = &amp;quot;groqllama-temporary-custom-instruction&amp;quot;;&lt;br /&gt;
var PREVIOUS_TEMPLATE_PROMPT_TAG = &amp;quot;groqllama-previous-template-prompt&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
// 生成AIモデル別設定&lt;br /&gt;
&lt;br /&gt;
// const MODEL_CONFIG = {&lt;br /&gt;
//     model: &amp;quot;meta-llama/llama-4-maverick-17b-128e-instruct&amp;quot;,&lt;br /&gt;
//     max_tokens: 3500,&lt;br /&gt;
//     temperature: 0.6,&lt;br /&gt;
// }&lt;br /&gt;
&lt;br /&gt;
// const MODEL_CONFIG = {&lt;br /&gt;
//     model: &amp;quot;qwen/qwen3-32b&amp;quot;,&lt;br /&gt;
//     max_tokens: 5000,&lt;br /&gt;
//     temperature: 0.6,&lt;br /&gt;
//     max_completion_tokens: 1024,&lt;br /&gt;
//     reasoning_effort: &amp;quot;default&amp;quot;,&lt;br /&gt;
//     reasoning_format: &amp;quot;hidden&amp;quot;&lt;br /&gt;
// }&lt;br /&gt;
&lt;br /&gt;
const MODEL_CONFIG = {&lt;br /&gt;
    model: &amp;quot;moonshotai/kimi-k2-instruct&amp;quot;,&lt;br /&gt;
    max_tokens: 5000,&lt;br /&gt;
    temperature: 0.7,&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// console.log に出力する内容を Mery のアウトプットバーに表示する&lt;br /&gt;
var console = {&lt;br /&gt;
    log: function (str) {&lt;br /&gt;
        outputBar.Writeln(str)&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
var doc = document;&lt;br /&gt;
var sel = document.selection;&lt;br /&gt;
&lt;br /&gt;
// 生成リクエスト中止フラグ&lt;br /&gt;
var abortRequestFlg = false&lt;br /&gt;
// 生成リクエスト完了フラグ;&lt;br /&gt;
var requestCompletedFlg = false;&lt;br /&gt;
&lt;br /&gt;
main();&lt;br /&gt;
&lt;br /&gt;
function main() {&lt;br /&gt;
    var selectionIsEmpty = sel.IsEmpty&lt;br /&gt;
&lt;br /&gt;
    // プロンプトの取得&lt;br /&gt;
    if (selectionIsEmpty) {&lt;br /&gt;
        sel.StartOfLine(false, mePosLogical);&lt;br /&gt;
        sel.EndOfLine(true, mePosLogical);&lt;br /&gt;
    }&lt;br /&gt;
    var promptText = sel.Text.trim();&lt;br /&gt;
    var promptLength = sel.TextLength;&lt;br /&gt;
    var menuTrack = 0;&lt;br /&gt;
    while (true) {&lt;br /&gt;
        // コンテキストメニューの作成&lt;br /&gt;
        var menu = createContextMenu(selectionIsEmpty);&lt;br /&gt;
        menuTrack = menu.Track(0);&lt;br /&gt;
        // メニュー選択がキャンセルされた場合は終了&lt;br /&gt;
        if (menuTrack === 0) return;&lt;br /&gt;
&lt;br /&gt;
        // 会話履歴のリセット&lt;br /&gt;
        if (menuTrack === RESET_HISTORY) {&lt;br /&gt;
            resetTag();&lt;br /&gt;
            continue;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        // カスタムインストラクションの設定&lt;br /&gt;
        if (menuTrack === SET_CUSTOM_INSTRUCTION) {&lt;br /&gt;
            configureTemporaryCustomInstruction();&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        break;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // プロンプトが空の場合はエラー&lt;br /&gt;
    if (promptLength === 0) {&lt;br /&gt;
        alert(&amp;quot;プロンプトを入力してください。&amp;quot;);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
    // 文字数が多すぎる場合はエラー&lt;br /&gt;
    if (roughTokenize(promptText) &amp;gt; MAX_PROMPT_TOKEN) {&lt;br /&gt;
        alert(&amp;quot;プロンプトの文字数が多すぎます。文字数を少なくしてください。&amp;quot;);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
    // 読み取り専用の場合はエラー&lt;br /&gt;
    if (doc.ReadOnly) {&lt;br /&gt;
        alert(&amp;quot;ファイルが読み取り専用のため実行できません。&amp;quot;);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // 直前に実行したプロンプトテンプレートの再実行の場合、menuTrack を書き換え&lt;br /&gt;
    if (menuTrack === REEXECUTE_LAST_TEMPLATE_PROMPT) {&lt;br /&gt;
        if (doc.Tag.exists(PREVIOUS_TEMPLATE_PROMPT_TAG)) {&lt;br /&gt;
            menuTrack = Number(doc.Tag(PREVIOUS_TEMPLATE_PROMPT_TAG));&lt;br /&gt;
        } else {&lt;br /&gt;
            alert(&amp;quot;直前に実行したプロンプトテンプレートがありません。&amp;quot;);&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // テンプレートの場合はプロンプトを変更&lt;br /&gt;
    var isTemplate = menuTrack &amp;gt;= TEMPLATE_PROMPT_THRESHOLD;&lt;br /&gt;
    var isSubmenu = menuTrack &amp;gt;= TEMPLATE_PROMPT_SUBMENU_THRESHOLD;&lt;br /&gt;
    if (isSubmenu) {&lt;br /&gt;
        var parentMenuTrack = Number(String(menuTrack).slice(0, 2));&lt;br /&gt;
        var subMenuTrack = Number(String(menuTrack).slice(2));&lt;br /&gt;
        promptText = &amp;quot;=== INSTRUCTIONS START HERE ===\n\n&amp;quot;&lt;br /&gt;
            + PROMPT_TEMPLATE[parentMenuTrack - TEMPLATE_PROMPT_THRESHOLD].submenu[subMenuTrack].prompt&lt;br /&gt;
            + &amp;quot;\n\n=== INSTRUCTIONS END HERE ===\n\n&amp;quot;&lt;br /&gt;
            + &amp;quot;=== INPUT DATA STARTS HERE ===\n\n&amp;quot;&lt;br /&gt;
            + promptText&lt;br /&gt;
            + &amp;quot;\n\n=== INPUT DATA END HERE ===&amp;quot;;&lt;br /&gt;
    } else if (isTemplate) {&lt;br /&gt;
        promptText = &amp;quot;=== INSTRUCTIONS START HERE ===\n\n&amp;quot;&lt;br /&gt;
            + PROMPT_TEMPLATE[menuTrack - TEMPLATE_PROMPT_THRESHOLD].prompt&lt;br /&gt;
            + &amp;quot;\n\n=== INSTRUCTIONS END HERE ===\n\n&amp;quot;&lt;br /&gt;
            + &amp;quot;=== INPUT DATA STARTS HERE ===\n\n&amp;quot;&lt;br /&gt;
            + promptText&lt;br /&gt;
            + &amp;quot;\n\n=== INPUT DATA END HERE ===&amp;quot;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // テンプレートの場合は直前に実行したプロンプトテンプレートの番号としてタグに保存&lt;br /&gt;
    if (isTemplate) {&lt;br /&gt;
        addPreviousTemplatePromptNumberToTag(menuTrack);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    try {&lt;br /&gt;
        // 処理終了を待機&lt;br /&gt;
        shell.KeepRunning = true;&lt;br /&gt;
&lt;br /&gt;
        // カーソル位置移動&lt;br /&gt;
        sel.SetActivePoint(mePosLogical, sel.GetBottomPointX(mePosLogical), sel.GetBottomPointY(mePosLogical));&lt;br /&gt;
        sel.EndOfLine(false, mePosLogical);&lt;br /&gt;
        sel.NewLine();&lt;br /&gt;
&lt;br /&gt;
        // APIキーの取得&lt;br /&gt;
        var apiKey = shell.getEnv(&amp;quot;GROQ_API_KEY&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
        // GroqLlama API に問い合わせ&lt;br /&gt;
        sel.Text = &amp;quot;\n---\n\n&amp;quot;&lt;br /&gt;
        document.KeepScrollPos = true;&lt;br /&gt;
        callGroqLlamaAPI(apiKey, promptText, isTemplate);&lt;br /&gt;
        if (sel.GetActivePointX(mePosLogical) &amp;gt; 2) {&lt;br /&gt;
            sel.Text = &amp;quot;\n\n---\n\n\n&amp;quot;&lt;br /&gt;
        } else {&lt;br /&gt;
            sel.Text = &amp;quot;\n---\n\n\n&amp;quot;&lt;br /&gt;
        }&lt;br /&gt;
    } catch (e) {&lt;br /&gt;
        sel.Text = &amp;quot;&amp;lt;処理中にエラー発生&amp;gt;\n&amp;quot; + e.message + &amp;quot;\n&amp;quot; + e.stack;&lt;br /&gt;
    } finally {&lt;br /&gt;
        // 処理終了待機を解除&lt;br /&gt;
        shell.KeepRunning = false;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function createContextMenu(selectionIsEmpty) {&lt;br /&gt;
    var menu = CreatePopupMenu();&lt;br /&gt;
    menu.Add(&amp;quot;※注意※ プライバシー・機密情報を送信しないこと&amp;quot;, 0);&lt;br /&gt;
    menu.Add(&amp;quot;&amp;quot;, 0, meMenuSeparator);&lt;br /&gt;
    var t = &amp;quot;&amp;quot;;&lt;br /&gt;
    if (selectionIsEmpty) {&lt;br /&gt;
        t = &amp;quot;アクティブ行の内容で相談 (&amp;amp;A)&amp;quot;;&lt;br /&gt;
    } else {&lt;br /&gt;
        t = &amp;quot;選択範囲の内容で相談 (&amp;amp;A)&amp;quot;;&lt;br /&gt;
    }&lt;br /&gt;
    menu.Add(t, EXECUTE_AI);&lt;br /&gt;
    if (HISTORY_SIZE &amp;lt;= 0) {&lt;br /&gt;
        menu.Add(&amp;quot;├ 会話履歴をクリア (履歴が無効になっています)&amp;quot;, RESET_HISTORY, meMenuGrayed);&lt;br /&gt;
    } else if (doc.Tag.exists(HISTORY_TAG)) {&lt;br /&gt;
        var turn = JSON.parse(doc.Tag(HISTORY_TAG)).length / 2;&lt;br /&gt;
        menu.Add(&amp;quot;├ 会話履歴をクリア (現在の会話数: &amp;quot; + turn + &amp;quot;) (&amp;amp;C)&amp;quot;, RESET_HISTORY);&lt;br /&gt;
    } else {&lt;br /&gt;
        menu.Add(&amp;quot;├ 会話履歴をクリア (現在の会話数: 0) (&amp;amp;C)&amp;quot;, RESET_HISTORY, meMenuGrayed);&lt;br /&gt;
    }&lt;br /&gt;
    menu.Add(&amp;quot;└ カスタムインストラクションを変更 (&amp;amp;I)&amp;quot;, SET_CUSTOM_INSTRUCTION);&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    // テンプレート数チェック&lt;br /&gt;
    // 2桁まで許容&lt;br /&gt;
    var ITEMS_MUX = 99;&lt;br /&gt;
    if (PROMPT_TEMPLATE.length &amp;gt; ITEMS_MUX - TEMPLATE_PROMPT_THRESHOLD) {&lt;br /&gt;
        throw new Error(&amp;quot;プロンプトテンプレートの数が多すぎます。&amp;quot;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // テンプレートの追加&lt;br /&gt;
    if (PROMPT_TEMPLATE.length &amp;gt; 0) {&lt;br /&gt;
        var subMenu = CreatePopupMenu();&lt;br /&gt;
        for (var i = 0; i &amp;lt; PROMPT_TEMPLATE.length; i++) {&lt;br /&gt;
            var accelerationKey = &amp;quot;&amp;quot;;&lt;br /&gt;
            if (i === 9) {&lt;br /&gt;
                accelerationKey = &amp;quot; (&amp;amp;0)&amp;quot;;&lt;br /&gt;
            } else if (i &amp;lt; 10) {&lt;br /&gt;
                accelerationKey = &amp;quot; (&amp;amp;&amp;quot; + (i + 1) + &amp;quot;)&amp;quot;;&lt;br /&gt;
            }&lt;br /&gt;
            var menuNum = TEMPLATE_PROMPT_THRESHOLD + i;&lt;br /&gt;
            if (PROMPT_TEMPLATE[i].submenu) {&lt;br /&gt;
                var subMenuList = PROMPT_TEMPLATE[i].submenu;&lt;br /&gt;
                // テンプレート数チェック&lt;br /&gt;
                if (subMenuList.length &amp;gt; ITEMS_MUX) {&lt;br /&gt;
                    throw new Error(&amp;quot;プロンプトテンプレート (サブメニュー) の数が多すぎます。&amp;quot;);&lt;br /&gt;
                }&lt;br /&gt;
                var subSubMenu = CreatePopupMenu();&lt;br /&gt;
                var subMenuPrefix = String(menuNum);&lt;br /&gt;
                for (var j = 0; j &amp;lt; subMenuList.length; j++) {&lt;br /&gt;
                    var subAccelerationKey = &amp;quot;&amp;quot;;&lt;br /&gt;
                    if (j === 9) {&lt;br /&gt;
                        subAccelerationKey = &amp;quot; (&amp;amp;0)&amp;quot;;&lt;br /&gt;
                    } else if (j &amp;lt; 10) {&lt;br /&gt;
                        subAccelerationKey = &amp;quot; (&amp;amp;&amp;quot; + (j + 1) + &amp;quot;)&amp;quot;;&lt;br /&gt;
                    }&lt;br /&gt;
                    // ES5 compatible version without padStart&lt;br /&gt;
                    var subMenuNum = Number(subMenuPrefix + (j &amp;lt; 10 ? &amp;quot;0&amp;quot; + j : String(j)));&lt;br /&gt;
                    subSubMenu.Add(subMenuList[j].title + subAccelerationKey, subMenuNum);&lt;br /&gt;
                }&lt;br /&gt;
                subMenu.AddPopup(PROMPT_TEMPLATE[i].submenu_title + accelerationKey, subSubMenu);&lt;br /&gt;
            } else {&lt;br /&gt;
                subMenu.Add(PROMPT_TEMPLATE[i].title + accelerationKey, menuNum);&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        if (selectionIsEmpty) {&lt;br /&gt;
            t = &amp;quot;プロンプト テンプレート (対象: アクティブ行) (&amp;amp;T)&amp;quot;;&lt;br /&gt;
        } else {&lt;br /&gt;
            t = &amp;quot;プロンプト テンプレート (対象: 選択範囲) (&amp;amp;T)&amp;quot;;&lt;br /&gt;
        }&lt;br /&gt;
        menu.AddPopup(t, subMenu);&lt;br /&gt;
&lt;br /&gt;
        t = &amp;quot;└ 直前に実行したプロンプト テンプレートを再実行 (&amp;amp;R)&amp;quot;;&lt;br /&gt;
        if (doc.Tag.exists(PREVIOUS_TEMPLATE_PROMPT_TAG)) {&lt;br /&gt;
            menu.Add(t, REEXECUTE_LAST_TEMPLATE_PROMPT);&lt;br /&gt;
        } else {&lt;br /&gt;
            menu.Add(t, REEXECUTE_LAST_TEMPLATE_PROMPT, meMenuGrayed);&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return menu;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function callGroqLlamaAPI(apiKey, promptText, isTemplate) {&lt;br /&gt;
    try {&lt;br /&gt;
        // fetch で Groq API にリクエスト&lt;br /&gt;
        var requestBody = {&lt;br /&gt;
            messages: createGroqLlamaRequest(promptText, isTemplate),&lt;br /&gt;
            stream: false,&lt;br /&gt;
        };&lt;br /&gt;
        for (var key in MODEL_CONFIG) {&lt;br /&gt;
            requestBody[key] = MODEL_CONFIG[key];&lt;br /&gt;
        }&lt;br /&gt;
        if (isTemplate) {&lt;br /&gt;
            // テンプレートの場合は応答を決定論的にする&lt;br /&gt;
            // テンプレート以外の場合はデフォルト値とするために指定しない&lt;br /&gt;
            requestBody.temperature = 0.3;&lt;br /&gt;
        }&lt;br /&gt;
        var url = &#039;https://api.groq.com/openai/v1/chat/completions&#039;;&lt;br /&gt;
&lt;br /&gt;
        var http = CreateObject(&amp;quot;Msxml2.ServerXMLHTTP&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
        http.open(&#039;POST&#039;, url, true);&lt;br /&gt;
        http.setRequestHeader(&#039;Content-Type&#039;, &#039;application/json&#039;);&lt;br /&gt;
        http.setRequestHeader(&#039;Authorization&#039;, &#039;Bearer &#039; + apiKey);&lt;br /&gt;
        http.send(JSON.stringify(requestBody));&lt;br /&gt;
&lt;br /&gt;
        // 応答待機&lt;br /&gt;
        var timeout = 60;&lt;br /&gt;
        var retrySec = 0;&lt;br /&gt;
        var startX = sel.GetActivePointX(mePosLogical);&lt;br /&gt;
        var prevPos = sel.GetActivePos();&lt;br /&gt;
        while (http.readyState != 4) {&lt;br /&gt;
            if (retrySec &amp;gt; timeout) {&lt;br /&gt;
                break;&lt;br /&gt;
            }&lt;br /&gt;
            http.waitForResponse(1);&lt;br /&gt;
            retrySec += 1;&lt;br /&gt;
            inputProgressChar(prevPos);&lt;br /&gt;
            prevPos = sel.GetActivePos();&lt;br /&gt;
        }&lt;br /&gt;
        // 進捗表示文字を削除&lt;br /&gt;
        sel.SetActivePoint(&lt;br /&gt;
            mePosLogical,&lt;br /&gt;
            startX,&lt;br /&gt;
            sel.GetActivePointY(mePosLogical),&lt;br /&gt;
            true&lt;br /&gt;
        );&lt;br /&gt;
        sel.Delete();&lt;br /&gt;
&lt;br /&gt;
        // 応答ステータスチェック&lt;br /&gt;
        if (!(http.readyState == 4 &amp;amp;&amp;amp; http.status == 200)) {&lt;br /&gt;
            sel.Text = &amp;quot;エラーが発生しました: &amp;quot; + http.status + &amp;quot; &amp;quot; + http.statusText + &amp;quot;\n&amp;quot; + http.responseText;&lt;br /&gt;
            http.abort();&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        var ret = &amp;quot;&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
        // JSONをパースして必要なテキストを抽出&lt;br /&gt;
        var response = JSON.parse(http.responseText);&lt;br /&gt;
&lt;br /&gt;
        var choices = response.choices;&lt;br /&gt;
        if (choices &amp;amp;&amp;amp; choices.length &amp;gt; 0) {&lt;br /&gt;
            choices.forEach(function (choice) {&lt;br /&gt;
                var message = choice.message;&lt;br /&gt;
                if (message) {&lt;br /&gt;
                    ret += message.content;&lt;br /&gt;
                }&lt;br /&gt;
            });&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        var usage = response.usage;&lt;br /&gt;
        var usageText = &amp;quot;&amp;quot;;&lt;br /&gt;
        if (usage) {&lt;br /&gt;
            usageText += &amp;quot;\n\n(利用トークン数: プロンプト=&amp;quot; + usage.prompt_tokens&lt;br /&gt;
                + &amp;quot;, 応答=&amp;quot; + usage.completion_tokens&lt;br /&gt;
                + &amp;quot;, 合計=&amp;quot; + usage.total_tokens + &amp;quot;)&amp;quot;;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        sel.Text = ret + usageText;&lt;br /&gt;
&lt;br /&gt;
        // 結果を会話履歴に追加&lt;br /&gt;
        if (HISTORY_SIZE &amp;gt; 0 &amp;amp;&amp;amp; !isTemplate) {&lt;br /&gt;
            appendReturnTextToTag(promptText, ret);&lt;br /&gt;
        }&lt;br /&gt;
    } catch (e) {&lt;br /&gt;
        throw e;&lt;br /&gt;
    } finally {&lt;br /&gt;
        requestCompletedFlg = true;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function roughTokenize(text) {&lt;br /&gt;
    // 超適当なトークナイザ。トークン数をざっくりと多めに数える&lt;br /&gt;
    // 空白文字・ハイフン・アンダースコア区切りの単語に分割して、&lt;br /&gt;
    // 単語がアルファベットのみ、数字のみの場合には 4文字=1トークン として数える。&lt;br /&gt;
    // 単語にアルファベット以外の文字が含まれる場合は 1文字=1トークン として数える。&lt;br /&gt;
&lt;br /&gt;
    var words = text.split(/[\s\-_]+/);&lt;br /&gt;
    var totalTokens = 0;&lt;br /&gt;
&lt;br /&gt;
    for (var i = 0; i &amp;lt; words.length; i++) {&lt;br /&gt;
        var word = words[i];&lt;br /&gt;
        if (!word) continue; // 空の単語をスキップ&lt;br /&gt;
&lt;br /&gt;
        if (/^[a-zA-Z]+$/.test(word) || /^[0-9]+$/.test(word)) {&lt;br /&gt;
            totalTokens += Math.ceil(word.length / 4);&lt;br /&gt;
        } else {&lt;br /&gt;
            totalTokens += word.length;&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return totalTokens;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// document.Tag の内容を加味してGroqLlamaのリクエストを作成&lt;br /&gt;
function createGroqLlamaRequest(promptText, isTemplate) {&lt;br /&gt;
    var customInstructionText = buildCustomInstructionText();&lt;br /&gt;
    // テンプレートの場合は、会話履歴とカスタムインストラクションを反映しない&lt;br /&gt;
    if (isTemplate) {&lt;br /&gt;
        var request = [&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;role&amp;quot;: &amp;quot;user&amp;quot;,&lt;br /&gt;
                &amp;quot;content&amp;quot;: [{ &amp;quot;type&amp;quot;: &amp;quot;text&amp;quot;, &amp;quot;text&amp;quot;: promptText }]&lt;br /&gt;
            }&lt;br /&gt;
        ];&lt;br /&gt;
        if (customInstructionText) {&lt;br /&gt;
            request.unshift({&lt;br /&gt;
                &amp;quot;role&amp;quot;: &amp;quot;system&amp;quot;,&lt;br /&gt;
                &amp;quot;content&amp;quot;: customInstructionText&lt;br /&gt;
            })&lt;br /&gt;
        }&lt;br /&gt;
        return request;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    var tag = document.Tag;&lt;br /&gt;
&lt;br /&gt;
    // 以前の会話が存在しない場合は新規リクエスト&lt;br /&gt;
    if (!tag.exists(HISTORY_TAG)) {&lt;br /&gt;
        var request = [{&lt;br /&gt;
            &amp;quot;role&amp;quot;: &amp;quot;user&amp;quot;,&lt;br /&gt;
            &amp;quot;content&amp;quot;: [{ &amp;quot;type&amp;quot;: &amp;quot;text&amp;quot;, &amp;quot;text&amp;quot;: promptText }]&lt;br /&gt;
        }];&lt;br /&gt;
        if (customInstructionText) {&lt;br /&gt;
            request.unshift({&lt;br /&gt;
                &amp;quot;role&amp;quot;: &amp;quot;system&amp;quot;,&lt;br /&gt;
                &amp;quot;content&amp;quot;: customInstructionText&lt;br /&gt;
            })&lt;br /&gt;
        }&lt;br /&gt;
        return request;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // 会話履歴が存在する場合は、履歴を含めてリクエスト&lt;br /&gt;
    var request = JSON.parse(tag(HISTORY_TAG));&lt;br /&gt;
    if (customInstructionText) {&lt;br /&gt;
        request.unshift({&lt;br /&gt;
            &amp;quot;role&amp;quot;: &amp;quot;system&amp;quot;,&lt;br /&gt;
            &amp;quot;content&amp;quot;: customInstructionText&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
    request.push({&lt;br /&gt;
        &amp;quot;role&amp;quot;: &amp;quot;user&amp;quot;,&lt;br /&gt;
        &amp;quot;content&amp;quot;: promptText&lt;br /&gt;
    });&lt;br /&gt;
    return request;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// document.Tag にGroqLlamaの応答を追加&lt;br /&gt;
function appendReturnTextToTag(userText, modelText) {&lt;br /&gt;
    var tag = document.Tag;&lt;br /&gt;
    if (!tag.exists(HISTORY_TAG)) {&lt;br /&gt;
        tag(HISTORY_TAG) = JSON.stringify([&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;role&amp;quot;: &amp;quot;user&amp;quot;,&lt;br /&gt;
                &amp;quot;content&amp;quot;: userText&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;role&amp;quot;: &amp;quot;assistant&amp;quot;,&lt;br /&gt;
                &amp;quot;content&amp;quot;: modelText&lt;br /&gt;
            }&lt;br /&gt;
        ]);&lt;br /&gt;
    } else {&lt;br /&gt;
        var groqllamaText = JSON.parse(tag(HISTORY_TAG));&lt;br /&gt;
        groqllamaText.push({&lt;br /&gt;
            &amp;quot;role&amp;quot;: &amp;quot;user&amp;quot;,&lt;br /&gt;
            &amp;quot;content&amp;quot;: userText&lt;br /&gt;
        });&lt;br /&gt;
        groqllamaText.push({&lt;br /&gt;
            &amp;quot;role&amp;quot;: &amp;quot;assistant&amp;quot;,&lt;br /&gt;
            &amp;quot;content&amp;quot;: modelText&lt;br /&gt;
        });&lt;br /&gt;
        // 会話セット (user, model 1往復) が特定セット数を超えた場合、最初のセットを削除&lt;br /&gt;
        if (groqllamaText.length &amp;gt; HISTORY_SIZE * 2) {&lt;br /&gt;
            groqllamaText.splice(0, 2);&lt;br /&gt;
        }&lt;br /&gt;
        // 会話履歴全体が MAX_HISTORY_TOKEN を超えた場合、最初のセットを削除&lt;br /&gt;
        var joinedText = &amp;quot;&amp;quot;;&lt;br /&gt;
        for (var i = 0; i &amp;lt; groqllamaText.length; i++) {&lt;br /&gt;
            joinedText += groqllamaText[i].content + &amp;quot;\n&amp;quot;;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        while (roughTokenize(joinedText) &amp;gt; MAX_HISTORY_TOKEN) {&lt;br /&gt;
            groqllamaText.splice(0, 2);&lt;br /&gt;
            joinedText = &amp;quot;&amp;quot;;&lt;br /&gt;
            for (var i = 0; i &amp;lt; groqllamaText.length; i++) {&lt;br /&gt;
                joinedText += groqllamaText[i].content + &amp;quot;\n&amp;quot;;&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        tag(HISTORY_TAG) = JSON.stringify(groqllamaText);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function resetTag() {&lt;br /&gt;
    var tag = doc.Tag;&lt;br /&gt;
    if (tag.exists(HISTORY_TAG)) {&lt;br /&gt;
        tag.remove(HISTORY_TAG);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function baseCustomInstruction() {&lt;br /&gt;
    var language = LANGUAGE.trim();&lt;br /&gt;
    return &amp;quot;Do not hallucinate. If you are unsure or don&#039;t have enough information to answer with confidence, say \&amp;quot;I don&#039;t know\&amp;quot; or \&amp;quot;I&#039;m not sure.\&amp;quot;\n&amp;quot;&lt;br /&gt;
        + &amp;quot;If you use Markdown headings (lines beginning with one or more # characters), you must surround the heading with blank lines.\n&amp;quot;&lt;br /&gt;
        + (language ? &amp;quot;Respond in &amp;quot; + language + &amp;quot;.\n&amp;quot; : &amp;quot;&amp;quot;)&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function buildCustomInstructionText() {&lt;br /&gt;
    var customInstruction = getTemporaryCustomInstruction();&lt;br /&gt;
    if (!customInstruction) {&lt;br /&gt;
        // 一時的なカスタムインストラクションがない場合はデフォルトを使用&lt;br /&gt;
        customInstruction = DEFAULT_CUSTOM_INSTRUCTION;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    customInstruction = customInstruction.replace(/^[\s　]+|[\s　]+$/g, &amp;quot;&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
    return (customInstruction ? customInstruction + &amp;quot;\n&amp;quot; : &amp;quot;&amp;quot;)&lt;br /&gt;
        + baseCustomInstruction();&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function configureTemporaryCustomInstruction() {&lt;br /&gt;
    var tempCustomInstruction = getTemporaryCustomInstruction();&lt;br /&gt;
    var prompt = Prompt(&amp;quot;カスタムインストラクションを変更 ※変更すると会話履歴がリセットされます&amp;quot;, tempCustomInstruction ? tempCustomInstruction : DEFAULT_CUSTOM_INSTRUCTION);&lt;br /&gt;
    if (prompt) {&lt;br /&gt;
        if (prompt === DEFAULT_CUSTOM_INSTRUCTION) {&lt;br /&gt;
            // デフォルトと同じ値の場合はタグを削除&lt;br /&gt;
            setTemporaryCustomInstruction(&amp;quot;&amp;quot;);&lt;br /&gt;
        } else if (prompt !== tempCustomInstruction) {&lt;br /&gt;
            // デフォルト以外の値が設定された場合は保存&lt;br /&gt;
            setTemporaryCustomInstruction(prompt);&lt;br /&gt;
        }&lt;br /&gt;
    } else {&lt;br /&gt;
        // 空文字でOK、もしくはキャンセルされた場合はデフォルトに戻すか確認&lt;br /&gt;
        if (Confirm(&amp;quot;カスタムインストラクションをデフォルトに戻しますか？&amp;quot;)) {&lt;br /&gt;
            setTemporaryCustomInstruction(&amp;quot;&amp;quot;);&lt;br /&gt;
        } else {&lt;br /&gt;
            alert(&amp;quot;カスタムインストラクションの変更をキャンセルしました。\n※カスタムインストラクションを空にしたい場合はスペースのみを入力してください。&amp;quot;);&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // 変更が加わっている場合は会話履歴をリセット&lt;br /&gt;
    if (tempCustomInstruction !== getTemporaryCustomInstruction()) {&lt;br /&gt;
        resetTag();&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getTemporaryCustomInstruction() {&lt;br /&gt;
    var tag = doc.Tag;&lt;br /&gt;
    if (tag.exists(TEMPORARY_CUSTOM_INSTRUCTION_TAG)) {&lt;br /&gt;
        return tag(TEMPORARY_CUSTOM_INSTRUCTION_TAG);&lt;br /&gt;
    } else {&lt;br /&gt;
        return &amp;quot;&amp;quot;;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function setTemporaryCustomInstruction(temporaryCustomInstruction) {&lt;br /&gt;
    var tag = doc.Tag;&lt;br /&gt;
    if (temporaryCustomInstruction) {&lt;br /&gt;
        tag(TEMPORARY_CUSTOM_INSTRUCTION_TAG) = temporaryCustomInstruction;&lt;br /&gt;
    } else {&lt;br /&gt;
        tag.remove(TEMPORARY_CUSTOM_INSTRUCTION_TAG);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function addPreviousTemplatePromptNumberToTag(menuTrack) {&lt;br /&gt;
    var tag = doc.Tag;&lt;br /&gt;
    tag(PREVIOUS_TEMPLATE_PROMPT_TAG) = menuTrack;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function inputProgressChar(pos) {&lt;br /&gt;
    sel.SetActivePos(pos);&lt;br /&gt;
    sel.Text = &amp;quot;.&amp;quot;;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;/div&gt;</summary>
		<author><name>Yuko</name></author>
	</entry>
	<entry>
		<id>https://www.haijin-boys.com/wiki/index.php?title=Google_Gemini_%E3%81%AB%E7%9B%B8%E8%AB%87&amp;diff=8958</id>
		<title>Google Gemini に相談</title>
		<link rel="alternate" type="text/html" href="https://www.haijin-boys.com/wiki/index.php?title=Google_Gemini_%E3%81%AB%E7%9B%B8%E8%AB%87&amp;diff=8958"/>
		<updated>2025-06-21T03:55:49Z</updated>

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

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

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

		<summary type="html">&lt;p&gt;Yuko: ページの作成:「== 概要 ==  生成AIプラットフォームである [https://groq.com GroqCloud] で「Llama」を利用するためのMeryマクロです。  「Google Geminiに相談」マクロでは動作中のストリーム通信に対応するためにV8エンジンで実行していましたが、V8エンジン版は使用上の制限からマクロ実行を開始するまで1秒程度のタイムラグが発生していました。GroqCloudの売りである超高速…」&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== 概要 ==&lt;br /&gt;
&lt;br /&gt;
生成AIプラットフォームである [https://groq.com GroqCloud] で「Llama」を利用するためのMeryマクロです。&lt;br /&gt;
&lt;br /&gt;
「Google Geminiに相談」マクロでは動作中のストリーム通信に対応するためにV8エンジンで実行していましたが、V8エンジン版は使用上の制限からマクロ実行を開始するまで1秒程度のタイムラグが発生していました。GroqCloudの売りである超高速レスポンスを見込んで本マクロではストリーム通信を使わないようにしました。これにより従来のJScriptで処理を組み立てることができるため、前述のタイムラグが起こらないようになりました。また、ストリーム通信を使わないことで通信量が少なく応答もシンプルになるため、場合によっては「Google Geminiに相談」マクロよりも処理が安定する可能性があります。&lt;br /&gt;
&lt;br /&gt;
【主な機能】&lt;br /&gt;
&lt;br /&gt;
* GroqCloud APIを利用して生成AIと対話できるMery用のマクロ&lt;br /&gt;
* 選択したテキストやアクティブな行の内容について生成AIに質問・相談が可能&lt;br /&gt;
* 会話履歴を保持し、文脈を考慮した対話が可能&lt;br /&gt;
&lt;br /&gt;
【特徴的な機能】&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;会話履歴機能&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* 事前の設定数に応じた会話履歴を保持&lt;br /&gt;
* 履歴のクリアが可能&lt;br /&gt;
&lt;br /&gt;
これにより、会話の文脈を考慮した回答を得られます。&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;カスタムインストラクション&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* AIの応答方針を設定可能&lt;br /&gt;
* 一時的なカスタム指示の保存と変更が可能&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;プロンプトテンプレート&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* 誤字脱字チェック&lt;br /&gt;
* 日本語翻訳&lt;br /&gt;
* コードの最適化、生成&lt;br /&gt;
&lt;br /&gt;
など、定型的な質問を簡単に実行できます。&lt;br /&gt;
※テンプレート機能では回答を安定させるため、前述の会話履歴を文脈に含みません。&lt;br /&gt;
&lt;br /&gt;
テンプレートは自身の使い方に合わせて好きに追加、削除ができます。詳細はソースコード内の「PROMPT_TEMPLATE」定数のコメントを参照。&lt;br /&gt;
&lt;br /&gt;
== 注意事項 (必ず読んでからご利用ください) ==&lt;br /&gt;
&lt;br /&gt;
* 本マクロでは 2025/04/13 時点で無料利用ができるGroqCloud APIを利用します。今後、プラットフォームの改定により有償になったり、設定誤り等で費用が発生しても、作者は一切の責任を負いません。&lt;br /&gt;
* GroqCloud APIではリクエスト内容をAI学習に用いないことをプライバシポリシーとして謳っているものの、くれぐれも機密情報やプライバシーに関わる情報を指定しないように注意してください。&lt;br /&gt;
* GroqCloud APIはプラットフォームの利用規約に則ってご利用ください。&lt;br /&gt;
&lt;br /&gt;
== 利用前提 ==&lt;br /&gt;
&lt;br /&gt;
* Mery Ver 3.7.13 以上&lt;br /&gt;
* GroqCloudで、GroqCloud API APIキーを取得していること (参考 [https://console.groq.com/docs/quickstart Quickstart])&lt;br /&gt;
&lt;br /&gt;
== 利用方法 ==&lt;br /&gt;
&lt;br /&gt;
* 環境変数の設定&lt;br /&gt;
:コマンド プロンプトで以下のコマンドを実行し、環境変数を設定してください。&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bat&amp;quot;&amp;gt;&lt;br /&gt;
setx GROQ_API_KEY &amp;quot;***************************************&amp;quot;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
:設定後、Mery または Windows の再起動が必要な場合があります。&lt;br /&gt;
* マクロの実行&lt;br /&gt;
**プロンプトにしたい文字列を選択してマクロを実行すると、選択したテキストがプロンプトとしてGemini APIに送信され、応答がエディタに表示されます。&lt;br /&gt;
* 会話の履歴数を調整&lt;br /&gt;
** 会話の履歴は設定した個数分が保持され、文脈として利用されます。適宜、ソースコード内の「HISTORY_SIZE」の数を調整してください。&lt;br /&gt;
** ※数を増やすほど会話内容を保持して文脈を理解するようになりますが、メモリ使用量が増えたり動作が重くなる可能性があります。&lt;br /&gt;
** ※開いている文書ごとに履歴を保持します。文脈をリセットしたいときは、新しい文書を開いて会話をやり直してください。&lt;br /&gt;
&lt;br /&gt;
== ソースコード ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
#title = &amp;quot;Groq Llama に相談&amp;quot;&lt;br /&gt;
BeginUndoGroup();&lt;br /&gt;
&lt;br /&gt;
// カスタムインストラクション&lt;br /&gt;
// これを設定することで、生成AIの回答全体への生成方針を指示できます。&lt;br /&gt;
// 例えば &amp;quot;あなたは侍で名前は権兵衛です。回答をござる口調にしてください&amp;quot; と設定すると、AIの回答がござる口調になります。&lt;br /&gt;
// 何も指示しない場合は、空文字 &amp;quot;&amp;quot; を設定してください。&lt;br /&gt;
var DEFAULT_CUSTOM_INSTRUCTION = &amp;quot;&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
// プロンプトテンプレート&lt;br /&gt;
// プロンプトテンプレートを設定することで、AIに対して特定の指示を出すことができます。&lt;br /&gt;
// プロンプトテンプレートでの生成は会話履歴とカスタムインストラクションを反映しません。&lt;br /&gt;
// テンプレートは以下の { ～ }, で囲まれたブロックを好きに増減させて利用できます。&lt;br /&gt;
var PROMPT_TEMPLATE = [&lt;br /&gt;
    // 以下はサンプルです。1階層メニューと2階層メニューが利用できます。&lt;br /&gt;
    // {&lt;br /&gt;
    //     &amp;quot;title&amp;quot;: &amp;quot;要約&amp;quot;,&lt;br /&gt;
    //     &amp;quot;prompt&amp;quot;: &amp;quot;以下の文章を要約してください。&amp;quot;&lt;br /&gt;
    // },&lt;br /&gt;
    // {&lt;br /&gt;
    //     &amp;quot;submenu_title&amp;quot;: &amp;quot;翻訳系&amp;quot;,&lt;br /&gt;
    //     &amp;quot;submenu&amp;quot;: [&lt;br /&gt;
    //         {&lt;br /&gt;
    //             &amp;quot;title&amp;quot;: &amp;quot;日本語翻訳&amp;quot;,&lt;br /&gt;
    //             &amp;quot;prompt&amp;quot;: &amp;quot;以下の文章を日本語に翻訳してください。&amp;quot;&lt;br /&gt;
    //         },&lt;br /&gt;
    //         {&lt;br /&gt;
    //             &amp;quot;title&amp;quot;: &amp;quot;英語翻訳&amp;quot;,&lt;br /&gt;
    //             &amp;quot;prompt&amp;quot;: &amp;quot;以下の文章を英語に翻訳してください。&amp;quot;&lt;br /&gt;
    //         },&lt;br /&gt;
    //     ]&lt;br /&gt;
    // },&lt;br /&gt;
&lt;br /&gt;
    {&lt;br /&gt;
        &amp;quot;submenu_title&amp;quot;: &amp;quot;理解を深める&amp;quot;,&lt;br /&gt;
        &amp;quot;submenu&amp;quot;: [&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;要約する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;要約して。専門的な用語と思われる単語は簡単に解説も入れて。&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;論点を整理する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の論点を整理して。&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;物語対話形式で教えて&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の内容について物語対話形式で教えてください。&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
        ]&lt;br /&gt;
    },&lt;br /&gt;
    {&lt;br /&gt;
        &amp;quot;submenu_title&amp;quot;: &amp;quot;翻訳する&amp;quot;,&lt;br /&gt;
        &amp;quot;submenu&amp;quot;: [&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;日本語に翻訳&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の文章を日本語に翻訳してください。\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 出力形式: [訳] ...&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;英語に翻訳&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の文章を英語に翻訳してください。直訳的で不自然な翻訳を避け、自然な表現にしてください。\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 出力形式: [Translation] ...&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;英語-&amp;gt;日本語 スラッシュリーディング翻訳&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の英文を日本語にスラッシュリーディング形式で翻訳してください。\n\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;条件:\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 英文の各フレーズをスラッシュ(/)で区切って表示\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 各フレーズの日本語訳を括弧()内に併記\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 文の終わりはダブルスラッシュ(//)で表示\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 英語の語順に従って日本語訳を行う\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 日本語訳は自然な日本語である必要はなく、英語の語順を反映した直訳調で構わない\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 主語が省略されている場合は補って翻訳すること\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 一般的な英文で頻出する表現がある場合は次の行に記載\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 以下の出力例のように &amp;lt;英文&amp;gt; (日本語訳) / ... とスラッシュ区切りの繰り返しになる \n\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;出力例:\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;Snowstorms are forecast (暴風雪が予報されている) / to hit the Sea of Japan coasts (日本海沿岸を襲うと) / of northern to eastern Japan (北部から東部の) / from Sunday through Monday. (日曜から月曜にかけて。) //\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;【頻出表現】\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- be forecast to: ～すると予報されている\n&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
        ]&lt;br /&gt;
    },&lt;br /&gt;
    {&lt;br /&gt;
        &amp;quot;submenu_title&amp;quot;: &amp;quot;プログラミング支援&amp;quot;,&lt;br /&gt;
        &amp;quot;submenu&amp;quot;: [&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;コードを洗練させる&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下のコードを洗練させてください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;コードをレビューする&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下のコードをセキュリティと保守性の観点でレビューしてください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;エラーログをデバッグする&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下のエラーログを解析し、考えられる原因と対策を提示してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;SQLクエリを最適化する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下のSQL文を最適化してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;PowerShell スクリプトを書く&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の処理を行うPowerShellスクリプトを生成してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;Shell Script を書く&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の処理を行うShell Scriptを生成してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;Linux コマンドを提案&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;あなたはLinuxエキスパートとして、以下のLinuxコマンドを提案してください。\n\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;制約条件:\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 多数のディストリビューションで採用されているメジャーなコマンドを使用し、ポータブルな実装にすること\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- bashでの実行を前提にすること\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 可能な限りシンプルな実装にすること\n\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;期待する出力:\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- コマンドの具体的な実装方法\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 各コマンドの動作説明\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 制限事項\n&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
        ]&lt;br /&gt;
    },&lt;br /&gt;
    {&lt;br /&gt;
        &amp;quot;submenu_title&amp;quot;: &amp;quot;ビジネス文書作成支援&amp;quot;,&lt;br /&gt;
        &amp;quot;submenu&amp;quot;: [&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;誤字脱字、不正確な表現をチェック&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の2つのカテゴリーで問題点を指摘してください\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 【誤字脱字】：明確な誤字脱字および誤字の疑いがある箇所\n&amp;quot;&lt;br /&gt;
                    + &amp;quot;- 【不正確・曖昧な表現】：解釈に個人差が生じる可能性がある表現、より適切な表現に改善できる箇所&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;提案書を作成する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下に対する対する提案書を、コスト・実現性・リスクを含めて作成してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;仕様書のアウトラインを作成する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の内容で仕様書のアウトラインを作成してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;手順書のアウトラインを作成する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の作業を行う手順書を、前提条件と注意事項を含めてアウトラインを作成してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;ユーザーマニュアルのアウトラインを作成する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の機能内容から、ユーザー向けマニュアルのアウトラインを作成してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;議事録を作成する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の会議内容から議事録を作成してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;進捗報告書を作成する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下のタスク状況から進捗報告書を作成してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
        ]&lt;br /&gt;
    },&lt;br /&gt;
    {&lt;br /&gt;
        &amp;quot;submenu_title&amp;quot;: &amp;quot;分析支援&amp;quot;,&lt;br /&gt;
        &amp;quot;submenu&amp;quot;: [&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;問題を分析し、課題を抽出する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の状況から問題や潜在的リスクを分析し、課題を抽出してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;対応策を提案する&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の問題・課題に対する対応策を複数提案してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;傾向を見つける&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の内容から、数値や挙動上の傾向を見つけてください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
        ]&lt;br /&gt;
    },&lt;br /&gt;
    {&lt;br /&gt;
        &amp;quot;submenu_title&amp;quot;: &amp;quot;アイデア出し&amp;quot;,&lt;br /&gt;
        &amp;quot;submenu&amp;quot;: [&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;SCAMPER法でブレインストーミング&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下のテーマについて、SCAMPER法を使って15個のアイデアを出してください。制約条件は以下の通りです：&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;解決策の幅出し&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の課題に対して、技術面、運用面、コスト面から実現可能な解決策を5つずつ提案してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;業務改善案の提案&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の既存プロセスについて、効率化、コスト削減、品質向上の観点から改善案を提案してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;逆転の発想&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の目標に対して、通常とは逆の視点からアプローチする方法を3つ提案してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;競合分析からの着想&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の内容について、業界における競合他社の特徴を分析し、差別化できる新しいアプローチを提案してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;title&amp;quot;: &amp;quot;ユースケースの列挙&amp;quot;,&lt;br /&gt;
                &amp;quot;prompt&amp;quot;: &amp;quot;以下の製品・サービスについて、想定されるユースケースを10個列挙し、それぞれの価値提案を記載してください&amp;quot;&lt;br /&gt;
            },&lt;br /&gt;
        ]&lt;br /&gt;
    },&lt;br /&gt;
]&lt;br /&gt;
&lt;br /&gt;
// 生成AIの回答の言語設定&lt;br /&gt;
// &amp;quot;Japanese&amp;quot; や &amp;quot;English&amp;quot; を設定できます。他の言語は未検証。&lt;br /&gt;
// 自動設定にする場合は空文字 &amp;quot;&amp;quot; を設定してください。&lt;br /&gt;
var LANGUAGE = &amp;quot;Japanese&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
// 入力プロンプトの最大トークン数&lt;br /&gt;
// 超適当なトークナイザー関数により計測し、英単語は4文字1トークン、それ以外は1文字1トークンとして数えます。&lt;br /&gt;
var MAX_PROMPT_TOKEN = 60000;&lt;br /&gt;
&lt;br /&gt;
// 会話の履歴を保持する数 (質問と回答で1セット)&lt;br /&gt;
// 数を増やすと、会話の履歴が増えますが、Meryのメモリ使用量が増えたり、&lt;br /&gt;
// 使用トークン数が増えて動作が重くなる可能性があるため、適宜調整してください。&lt;br /&gt;
// document.Tag に保持されるため、開いているファイルを閉じると履歴は消えます。&lt;br /&gt;
var HISTORY_SIZE = 4;&lt;br /&gt;
&lt;br /&gt;
// 会話履歴に保持する最大トークン数&lt;br /&gt;
// 会話履歴のトークン数がこの数を超える場合、古い履歴から削除されます。&lt;br /&gt;
var MAX_HISTORY_TOKEN = 300000;&lt;br /&gt;
&lt;br /&gt;
// コンテキストメニュー番号の定義&lt;br /&gt;
var EXECUTE_AI = 1;&lt;br /&gt;
var RESET_HISTORY = 2;&lt;br /&gt;
var SET_CUSTOM_INSTRUCTION = 3;&lt;br /&gt;
var REEXECUTE_LAST_TEMPLATE_PROMPT = 4;&lt;br /&gt;
var TEMPLATE_PROMPT_THRESHOLD = 10;&lt;br /&gt;
var TEMPLATE_PROMPT_SUBMENU_THRESHOLD = 1000;&lt;br /&gt;
&lt;br /&gt;
// タグ名の定義&lt;br /&gt;
var HISTORY_TAG = &amp;quot;groqllama-history&amp;quot;;&lt;br /&gt;
var TEMPORARY_CUSTOM_INSTRUCTION_TAG = &amp;quot;groqllama-temporary-custom-instruction&amp;quot;;&lt;br /&gt;
var PREVIOUS_TEMPLATE_PROMPT_TAG = &amp;quot;groqllama-previous-template-prompt&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
// 生成AIモデル名&lt;br /&gt;
const MODEL_NAME = &amp;quot;meta-llama/llama-4-maverick-17b-128e-instruct&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
// console.log に出力する内容を Mery のアウトプットバーに表示する&lt;br /&gt;
var console = {&lt;br /&gt;
    log: function (str) {&lt;br /&gt;
        outputBar.Writeln(str)&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
var doc = document;&lt;br /&gt;
var sel = document.selection;&lt;br /&gt;
&lt;br /&gt;
// 生成リクエスト中止フラグ&lt;br /&gt;
var abortRequestFlg = false&lt;br /&gt;
// 生成リクエスト完了フラグ;&lt;br /&gt;
var requestCompletedFlg = false;&lt;br /&gt;
&lt;br /&gt;
main();&lt;br /&gt;
&lt;br /&gt;
function main() {&lt;br /&gt;
    var selectionIsEmpty = sel.IsEmpty&lt;br /&gt;
&lt;br /&gt;
    // プロンプトの取得&lt;br /&gt;
    if (selectionIsEmpty) {&lt;br /&gt;
        sel.StartOfLine(false, mePosLogical);&lt;br /&gt;
        sel.EndOfLine(true, mePosLogical);&lt;br /&gt;
    }&lt;br /&gt;
    var promptText = sel.Text.trim();&lt;br /&gt;
    var promptLength = sel.TextLength;&lt;br /&gt;
    var menuTrack = 0;&lt;br /&gt;
    while (true) {&lt;br /&gt;
        // コンテキストメニューの作成&lt;br /&gt;
        var menu = createContextMenu(selectionIsEmpty);&lt;br /&gt;
        menuTrack = menu.Track(0);&lt;br /&gt;
        // メニュー選択がキャンセルされた場合は終了&lt;br /&gt;
        if (menuTrack === 0) return;&lt;br /&gt;
&lt;br /&gt;
        // 会話履歴のリセット&lt;br /&gt;
        if (menuTrack === RESET_HISTORY) {&lt;br /&gt;
            resetTag();&lt;br /&gt;
            continue;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        // カスタムインストラクションの設定&lt;br /&gt;
        if (menuTrack === SET_CUSTOM_INSTRUCTION) {&lt;br /&gt;
            configureTemporaryCustomInstruction();&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        break;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // プロンプトが空の場合はエラー&lt;br /&gt;
    if (promptLength === 0) {&lt;br /&gt;
        alert(&amp;quot;プロンプトを入力してください。&amp;quot;);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
    // 文字数が多すぎる場合はエラー&lt;br /&gt;
    if (roughTokenize(promptText) &amp;gt; MAX_PROMPT_TOKEN) {&lt;br /&gt;
        alert(&amp;quot;プロンプトの文字数が多すぎます。文字数を少なくしてください。&amp;quot;);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
    // 読み取り専用の場合はエラー&lt;br /&gt;
    if (doc.ReadOnly) {&lt;br /&gt;
        alert(&amp;quot;ファイルが読み取り専用のため実行できません。&amp;quot;);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // 直前に実行したプロンプトテンプレートの再実行の場合、menuTrack を書き換え&lt;br /&gt;
    if (menuTrack === REEXECUTE_LAST_TEMPLATE_PROMPT) {&lt;br /&gt;
        if (doc.Tag.exists(PREVIOUS_TEMPLATE_PROMPT_TAG)) {&lt;br /&gt;
            menuTrack = Number(doc.Tag(PREVIOUS_TEMPLATE_PROMPT_TAG));&lt;br /&gt;
        } else {&lt;br /&gt;
            alert(&amp;quot;直前に実行したプロンプトテンプレートがありません。&amp;quot;);&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // テンプレートの場合はプロンプトを変更&lt;br /&gt;
    var isTemplate = menuTrack &amp;gt;= TEMPLATE_PROMPT_THRESHOLD;&lt;br /&gt;
    var isSubmenu = menuTrack &amp;gt;= TEMPLATE_PROMPT_SUBMENU_THRESHOLD;&lt;br /&gt;
    if (isSubmenu) {&lt;br /&gt;
        var parentMenuTrack = Number(String(menuTrack).slice(0, 2));&lt;br /&gt;
        var subMenuTrack = Number(String(menuTrack).slice(2));&lt;br /&gt;
        promptText = PROMPT_TEMPLATE[parentMenuTrack - TEMPLATE_PROMPT_THRESHOLD].submenu[subMenuTrack].prompt&lt;br /&gt;
            + &amp;quot;\n\n---INPUT START---\n\n&amp;quot;&lt;br /&gt;
            + promptText&lt;br /&gt;
            + &amp;quot;\n\n---INPUT END---\n\n&amp;quot;;&lt;br /&gt;
    } else if (isTemplate) {&lt;br /&gt;
        promptText = PROMPT_TEMPLATE[menuTrack - TEMPLATE_PROMPT_THRESHOLD].prompt&lt;br /&gt;
            + &amp;quot;\n\n---INPUT START---\n\n&amp;quot;&lt;br /&gt;
            + promptText&lt;br /&gt;
            + &amp;quot;\n\n---INPUT END---\n\n&amp;quot;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // テンプレートの場合は直前に実行したプロンプトテンプレートの番号としてタグに保存&lt;br /&gt;
    if (isTemplate) {&lt;br /&gt;
        addPreviousTemplatePromptNumberToTag(menuTrack);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    try {&lt;br /&gt;
        // 処理終了を待機&lt;br /&gt;
        shell.KeepRunning = true;&lt;br /&gt;
&lt;br /&gt;
        // カーソル位置移動&lt;br /&gt;
        sel.SetActivePoint(mePosLogical, sel.GetBottomPointX(mePosLogical), sel.GetBottomPointY(mePosLogical));&lt;br /&gt;
        sel.EndOfLine(false, mePosLogical);&lt;br /&gt;
        sel.NewLine();&lt;br /&gt;
&lt;br /&gt;
        // APIキーの取得&lt;br /&gt;
        var apiKey = shell.getEnv(&amp;quot;GROQ_API_KEY&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
        // GroqLlama API に問い合わせ&lt;br /&gt;
        sel.Text = &amp;quot;\n---\n\n&amp;quot;&lt;br /&gt;
        document.KeepScrollPos = true;&lt;br /&gt;
        callGroqLlamaAPI(apiKey, promptText, isTemplate);&lt;br /&gt;
        if (sel.GetActivePointX(mePosLogical) &amp;gt; 2) {&lt;br /&gt;
            sel.Text = &amp;quot;\n\n---\n\n\n&amp;quot;&lt;br /&gt;
        } else {&lt;br /&gt;
            sel.Text = &amp;quot;\n---\n\n\n&amp;quot;&lt;br /&gt;
        }&lt;br /&gt;
    } catch (e) {&lt;br /&gt;
        console.log(&amp;quot;エラーが発生しました: &amp;quot; + e.message + &amp;quot;\n&amp;quot; + e.stack);&lt;br /&gt;
        sel.Text = &amp;quot;&amp;lt;処理中にエラー発生&amp;gt;&amp;quot;&lt;br /&gt;
    } finally {&lt;br /&gt;
        // 処理終了待機を解除&lt;br /&gt;
        shell.KeepRunning = false;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function createContextMenu(selectionIsEmpty) {&lt;br /&gt;
    var menu = CreatePopupMenu();&lt;br /&gt;
    menu.Add(&amp;quot;※注意※ プライバシー・機密情報を送信しないこと&amp;quot;, 0);&lt;br /&gt;
    menu.Add(&amp;quot;&amp;quot;, 0, meMenuSeparator);&lt;br /&gt;
    var t = &amp;quot;&amp;quot;;&lt;br /&gt;
    if (selectionIsEmpty) {&lt;br /&gt;
        t = &amp;quot;アクティブ行の内容で相談 (&amp;amp;A)&amp;quot;;&lt;br /&gt;
    } else {&lt;br /&gt;
        t = &amp;quot;選択範囲の内容で相談 (&amp;amp;A)&amp;quot;;&lt;br /&gt;
    }&lt;br /&gt;
    menu.Add(t, EXECUTE_AI);&lt;br /&gt;
    if (doc.Tag.exists(HISTORY_TAG)) {&lt;br /&gt;
        var turn = JSON.parse(doc.Tag(HISTORY_TAG)).length / 2;&lt;br /&gt;
        menu.Add(&amp;quot;├ 会話履歴をクリア (現在の会話数: &amp;quot; + turn + &amp;quot;) (&amp;amp;C)&amp;quot;, RESET_HISTORY);&lt;br /&gt;
    } else {&lt;br /&gt;
        menu.Add(&amp;quot;├ 会話履歴をクリア (現在の会話数: 0) (&amp;amp;C)&amp;quot;, RESET_HISTORY, meMenuGrayed);&lt;br /&gt;
    }&lt;br /&gt;
    menu.Add(&amp;quot;└ カスタムインストラクションを変更 (&amp;amp;I)&amp;quot;, SET_CUSTOM_INSTRUCTION);&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    // テンプレート数チェック&lt;br /&gt;
    // 2桁まで許容&lt;br /&gt;
    var ITEMS_MUX = 99;&lt;br /&gt;
    if (PROMPT_TEMPLATE.length &amp;gt; ITEMS_MUX - TEMPLATE_PROMPT_THRESHOLD) {&lt;br /&gt;
        throw new Error(&amp;quot;プロンプトテンプレートの数が多すぎます。&amp;quot;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // テンプレートの追加&lt;br /&gt;
    if (PROMPT_TEMPLATE.length &amp;gt; 0) {&lt;br /&gt;
        var subMenu = CreatePopupMenu();&lt;br /&gt;
        for (var i = 0; i &amp;lt; PROMPT_TEMPLATE.length; i++) {&lt;br /&gt;
            var accelerationKey = &amp;quot;&amp;quot;;&lt;br /&gt;
            if (i === 9) {&lt;br /&gt;
                accelerationKey = &amp;quot; (&amp;amp;0)&amp;quot;;&lt;br /&gt;
            } else if (i &amp;lt; 10) {&lt;br /&gt;
                accelerationKey = &amp;quot; (&amp;amp;&amp;quot; + (i + 1) + &amp;quot;)&amp;quot;;&lt;br /&gt;
            }&lt;br /&gt;
            var menuNum = TEMPLATE_PROMPT_THRESHOLD + i;&lt;br /&gt;
            if (PROMPT_TEMPLATE[i].submenu) {&lt;br /&gt;
                var subMenuList = PROMPT_TEMPLATE[i].submenu;&lt;br /&gt;
                // テンプレート数チェック&lt;br /&gt;
                if (subMenuList.length &amp;gt; ITEMS_MUX) {&lt;br /&gt;
                    throw new Error(&amp;quot;プロンプトテンプレート (サブメニュー) の数が多すぎます。&amp;quot;);&lt;br /&gt;
                }&lt;br /&gt;
                var subSubMenu = CreatePopupMenu();&lt;br /&gt;
                var subMenuPrefix = String(menuNum);&lt;br /&gt;
                for (var j = 0; j &amp;lt; subMenuList.length; j++) {&lt;br /&gt;
                    var subAccelerationKey = &amp;quot;&amp;quot;;&lt;br /&gt;
                    if (j === 9) {&lt;br /&gt;
                        subAccelerationKey = &amp;quot; (&amp;amp;0)&amp;quot;;&lt;br /&gt;
                    } else if (j &amp;lt; 10) {&lt;br /&gt;
                        subAccelerationKey = &amp;quot; (&amp;amp;&amp;quot; + (j + 1) + &amp;quot;)&amp;quot;;&lt;br /&gt;
                    }&lt;br /&gt;
                    // ES5 compatible version without padStart&lt;br /&gt;
                    var subMenuNum = Number(subMenuPrefix + (j &amp;lt; 10 ? &amp;quot;0&amp;quot; + j : String(j)));&lt;br /&gt;
                    subSubMenu.Add(subMenuList[j].title + subAccelerationKey, subMenuNum);&lt;br /&gt;
                }&lt;br /&gt;
                subMenu.AddPopup(PROMPT_TEMPLATE[i].submenu_title + accelerationKey, subSubMenu);&lt;br /&gt;
            } else {&lt;br /&gt;
                subMenu.Add(PROMPT_TEMPLATE[i].title + accelerationKey, menuNum);&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        if (selectionIsEmpty) {&lt;br /&gt;
            t = &amp;quot;プロンプト テンプレート (対象: アクティブ行) (&amp;amp;T)&amp;quot;;&lt;br /&gt;
        } else {&lt;br /&gt;
            t = &amp;quot;プロンプト テンプレート (対象: 選択範囲) (&amp;amp;T)&amp;quot;;&lt;br /&gt;
        }&lt;br /&gt;
        menu.AddPopup(t, subMenu);&lt;br /&gt;
&lt;br /&gt;
        t = &amp;quot;└ 直前に実行したプロンプト テンプレートを再実行 (&amp;amp;R)&amp;quot;;&lt;br /&gt;
        if (doc.Tag.exists(PREVIOUS_TEMPLATE_PROMPT_TAG)) {&lt;br /&gt;
            menu.Add(t, REEXECUTE_LAST_TEMPLATE_PROMPT);&lt;br /&gt;
        } else {&lt;br /&gt;
            menu.Add(t, REEXECUTE_LAST_TEMPLATE_PROMPT, meMenuGrayed);&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return menu;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function callGroqLlamaAPI(apiKey, promptText, isTemplate) {&lt;br /&gt;
    try {&lt;br /&gt;
        // fetch で Groq API にリクエスト&lt;br /&gt;
        var requestBody = {&lt;br /&gt;
            model: MODEL_NAME,&lt;br /&gt;
            messages: createGroqLlamaRequest(promptText, isTemplate),&lt;br /&gt;
            stream: false,&lt;br /&gt;
            max_tokens: 5000,&lt;br /&gt;
            temperature: 0.7,&lt;br /&gt;
        };&lt;br /&gt;
        if (isTemplate) {&lt;br /&gt;
            // テンプレートの場合は応答を決定論的にする&lt;br /&gt;
            // テンプレート以外の場合はデフォルト値とするために指定しない&lt;br /&gt;
            requestBody.temperature = 0.3;&lt;br /&gt;
        }&lt;br /&gt;
        var url = &#039;https://api.groq.com/openai/v1/chat/completions&#039;;&lt;br /&gt;
&lt;br /&gt;
        var http = new ActiveXObject(&amp;quot;Msxml2.ServerXMLHTTP&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
        http.open(&#039;POST&#039;, url, true);&lt;br /&gt;
        http.setRequestHeader(&#039;Content-Type&#039;, &#039;application/json&#039;);&lt;br /&gt;
        http.setRequestHeader(&#039;Authorization&#039;, &#039;Bearer &#039; + apiKey);&lt;br /&gt;
        http.send(JSON.stringify(requestBody));&lt;br /&gt;
&lt;br /&gt;
        // 応答待機&lt;br /&gt;
        var timeout = 60;&lt;br /&gt;
        var retrySec = 0;&lt;br /&gt;
        var startX = sel.GetActivePointX(mePosLogical);&lt;br /&gt;
        var prevPos = sel.GetActivePos();&lt;br /&gt;
        while (http.readyState != 4) {&lt;br /&gt;
            if (retrySec &amp;gt; timeout) {&lt;br /&gt;
                break;&lt;br /&gt;
            }&lt;br /&gt;
            http.waitForResponse(1);&lt;br /&gt;
            retrySec += 1;&lt;br /&gt;
            inputProgressChar(prevPos);&lt;br /&gt;
            prevPos = sel.GetActivePos();&lt;br /&gt;
        }&lt;br /&gt;
        // 進捗表示文字を削除&lt;br /&gt;
        sel.SetActivePoint(&lt;br /&gt;
            mePosLogical,&lt;br /&gt;
            startX,&lt;br /&gt;
            sel.GetActivePointY(mePosLogical),&lt;br /&gt;
            true&lt;br /&gt;
        );&lt;br /&gt;
        sel.Delete();&lt;br /&gt;
&lt;br /&gt;
        // 応答ステータスチェック&lt;br /&gt;
        if (!(http.readyState == 4 &amp;amp;&amp;amp; http.status == 200)) {&lt;br /&gt;
            sel.Text = &amp;quot;エラーが発生しました: &amp;quot; + http.status + &amp;quot; &amp;quot; + http.statusText + &amp;quot;\n&amp;quot; + http.responseText;&lt;br /&gt;
            http.abort();&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        var ret = &amp;quot;&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
        // JSONをパースして必要なテキストを抽出&lt;br /&gt;
        var response = JSON.parse(http.responseText);&lt;br /&gt;
&lt;br /&gt;
        var choices = response.choices;&lt;br /&gt;
        if (choices &amp;amp;&amp;amp; choices.length &amp;gt; 0) {&lt;br /&gt;
            choices.forEach(function (choice) {&lt;br /&gt;
                var message = choice.message;&lt;br /&gt;
                if (message) {&lt;br /&gt;
                    ret += message.content;&lt;br /&gt;
                }&lt;br /&gt;
            });&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        var usage = response.usage;&lt;br /&gt;
        var usageText = &amp;quot;&amp;quot;;&lt;br /&gt;
        if (usage) {&lt;br /&gt;
            usageText += &amp;quot;\n\n(利用トークン数: プロンプト=&amp;quot; + usage.prompt_tokens&lt;br /&gt;
                + &amp;quot;, 応答=&amp;quot; + usage.completion_tokens&lt;br /&gt;
                + &amp;quot;, 合計=&amp;quot; + usage.total_tokens + &amp;quot;)&amp;quot;;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        sel.Text = ret + usageText;&lt;br /&gt;
&lt;br /&gt;
        // 結果を会話履歴に追加&lt;br /&gt;
        if (HISTORY_SIZE &amp;gt; 0 &amp;amp;&amp;amp; !isTemplate) {&lt;br /&gt;
            appendReturnTextToTag(promptText, ret);&lt;br /&gt;
        }&lt;br /&gt;
    } catch (e) {&lt;br /&gt;
        throw e;&lt;br /&gt;
    } finally {&lt;br /&gt;
        requestCompletedFlg = true;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function roughTokenize(text) {&lt;br /&gt;
    // 超適当なトークナイザ。トークン数をざっくりと多めに数える&lt;br /&gt;
    // 空白文字・ハイフン・アンダースコア区切りの単語に分割して、&lt;br /&gt;
    // 単語がアルファベットのみ、数字のみの場合には 4文字=1トークン として数える。&lt;br /&gt;
    // 単語にアルファベット以外の文字が含まれる場合は 1文字=1トークン として数える。&lt;br /&gt;
&lt;br /&gt;
    var words = text.split(/[\s\-_]+/);&lt;br /&gt;
    var totalTokens = 0;&lt;br /&gt;
&lt;br /&gt;
    for (var i = 0; i &amp;lt; words.length; i++) {&lt;br /&gt;
        var word = words[i];&lt;br /&gt;
        if (!word) continue; // 空の単語をスキップ&lt;br /&gt;
&lt;br /&gt;
        if (/^[a-zA-Z]+$/.test(word) || /^[0-9]+$/.test(word)) {&lt;br /&gt;
            totalTokens += Math.ceil(word.length / 4);&lt;br /&gt;
        } else {&lt;br /&gt;
            totalTokens += word.length;&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return totalTokens;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// document.Tag の内容を加味してGroqLlamaのリクエストを作成&lt;br /&gt;
function createGroqLlamaRequest(promptText, isTemplate) {&lt;br /&gt;
    // テンプレートの場合は、会話履歴とカスタムインストラクションを反映しない&lt;br /&gt;
    if (isTemplate) {&lt;br /&gt;
        return [&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;role&amp;quot;: &amp;quot;user&amp;quot;,&lt;br /&gt;
                &amp;quot;content&amp;quot;: [{ &amp;quot;type&amp;quot;: &amp;quot;text&amp;quot;, &amp;quot;text&amp;quot;: promptText }]&lt;br /&gt;
            }&lt;br /&gt;
        ];&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    var tag = document.Tag;&lt;br /&gt;
    var customInstructionText = buildCustomInstructionText();&lt;br /&gt;
&lt;br /&gt;
    // 以前の会話が存在しない場合は新規リクエスト&lt;br /&gt;
    if (!tag.exists(HISTORY_TAG)) {&lt;br /&gt;
        var request = [{&lt;br /&gt;
            &amp;quot;role&amp;quot;: &amp;quot;user&amp;quot;,&lt;br /&gt;
            &amp;quot;content&amp;quot;: [{ &amp;quot;type&amp;quot;: &amp;quot;text&amp;quot;, &amp;quot;text&amp;quot;: promptText }]&lt;br /&gt;
        }];&lt;br /&gt;
        if (customInstructionText) {&lt;br /&gt;
            request.unshift({&lt;br /&gt;
                &amp;quot;role&amp;quot;: &amp;quot;system&amp;quot;,&lt;br /&gt;
                &amp;quot;content&amp;quot;: customInstructionText&lt;br /&gt;
            })&lt;br /&gt;
        }&lt;br /&gt;
        return request;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // 会話履歴が存在する場合は、履歴を含めてリクエスト&lt;br /&gt;
    var request = JSON.parse(tag(HISTORY_TAG));&lt;br /&gt;
    if (customInstructionText) {&lt;br /&gt;
        request.unshift({&lt;br /&gt;
            &amp;quot;role&amp;quot;: &amp;quot;system&amp;quot;,&lt;br /&gt;
            &amp;quot;content&amp;quot;: customInstructionText&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
    request.push({&lt;br /&gt;
        &amp;quot;role&amp;quot;: &amp;quot;user&amp;quot;,&lt;br /&gt;
        &amp;quot;content&amp;quot;: promptText&lt;br /&gt;
    });&lt;br /&gt;
    return request;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// document.Tag にGroqLlamaの応答を追加&lt;br /&gt;
function appendReturnTextToTag(userText, modelText) {&lt;br /&gt;
    var tag = document.Tag;&lt;br /&gt;
    if (!tag.exists(HISTORY_TAG)) {&lt;br /&gt;
        tag(HISTORY_TAG) = JSON.stringify([&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;role&amp;quot;: &amp;quot;user&amp;quot;,&lt;br /&gt;
                &amp;quot;content&amp;quot;: userText&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                &amp;quot;role&amp;quot;: &amp;quot;assistant&amp;quot;,&lt;br /&gt;
                &amp;quot;content&amp;quot;: modelText&lt;br /&gt;
            }&lt;br /&gt;
        ]);&lt;br /&gt;
    } else {&lt;br /&gt;
        var groqllamaText = JSON.parse(tag(HISTORY_TAG));&lt;br /&gt;
        groqllamaText.push({&lt;br /&gt;
            &amp;quot;role&amp;quot;: &amp;quot;user&amp;quot;,&lt;br /&gt;
            &amp;quot;content&amp;quot;: userText&lt;br /&gt;
        });&lt;br /&gt;
        groqllamaText.push({&lt;br /&gt;
            &amp;quot;role&amp;quot;: &amp;quot;assistant&amp;quot;,&lt;br /&gt;
            &amp;quot;content&amp;quot;: modelText&lt;br /&gt;
        });&lt;br /&gt;
        // 会話セット (user, model 1往復) が特定セット数を超えた場合、最初のセットを削除&lt;br /&gt;
        if (groqllamaText.length &amp;gt; HISTORY_SIZE * 2) {&lt;br /&gt;
            groqllamaText.splice(0, 2);&lt;br /&gt;
        }&lt;br /&gt;
        // 会話履歴全体が MAX_HISTORY_TOKEN を超えた場合、最初のセットを削除&lt;br /&gt;
        var joinedText = &amp;quot;&amp;quot;;&lt;br /&gt;
        for (var i = 0; i &amp;lt; groqllamaText.length; i++) {&lt;br /&gt;
            joinedText += groqllamaText[i].content + &amp;quot;\n&amp;quot;;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        while (roughTokenize(joinedText) &amp;gt; MAX_HISTORY_TOKEN) {&lt;br /&gt;
            groqllamaText.splice(0, 2);&lt;br /&gt;
            joinedText = &amp;quot;&amp;quot;;&lt;br /&gt;
            for (var i = 0; i &amp;lt; groqllamaText.length; i++) {&lt;br /&gt;
                joinedText += groqllamaText[i].content + &amp;quot;\n&amp;quot;;&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        tag(HISTORY_TAG) = JSON.stringify(groqllamaText);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function resetTag() {&lt;br /&gt;
    var tag = doc.Tag;&lt;br /&gt;
    if (tag.exists(HISTORY_TAG)) {&lt;br /&gt;
        tag.remove(HISTORY_TAG);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function buildCustomInstructionText() {&lt;br /&gt;
    var customInstruction = getTemporaryCustomInstruction();&lt;br /&gt;
    if (!customInstruction) {&lt;br /&gt;
        // 一時的なカスタムインストラクションがない場合はデフォルトを使用&lt;br /&gt;
        customInstruction = DEFAULT_CUSTOM_INSTRUCTION;&lt;br /&gt;
    }&lt;br /&gt;
    var language = LANGUAGE.trim();&lt;br /&gt;
    if (!customInstruction &amp;amp;&amp;amp; !language) {&lt;br /&gt;
        return &amp;quot;&amp;quot;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    customInstruction = customInstruction.replace(/^[\s　]+|[\s　]+$/g, &amp;quot;&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
    return (customInstruction ? customInstruction + &amp;quot;\n&amp;quot; : &amp;quot;&amp;quot;)&lt;br /&gt;
        + (language ? &amp;quot;Please make sure to answer in &amp;quot; + language + &amp;quot;.\n&amp;quot; : &amp;quot;&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function configureTemporaryCustomInstruction() {&lt;br /&gt;
    var tempCustomInstruction = getTemporaryCustomInstruction();&lt;br /&gt;
    var prompt = Prompt(&amp;quot;カスタムインストラクションを変更 ※変更すると会話履歴がリセットされます&amp;quot;, tempCustomInstruction ? tempCustomInstruction : DEFAULT_CUSTOM_INSTRUCTION);&lt;br /&gt;
    if (prompt) {&lt;br /&gt;
        if (prompt === DEFAULT_CUSTOM_INSTRUCTION) {&lt;br /&gt;
            // デフォルトと同じ値の場合はタグを削除&lt;br /&gt;
            setTemporaryCustomInstruction(&amp;quot;&amp;quot;);&lt;br /&gt;
        } else if (prompt !== tempCustomInstruction) {&lt;br /&gt;
            // デフォルト以外の値が設定された場合は保存&lt;br /&gt;
            setTemporaryCustomInstruction(prompt);&lt;br /&gt;
        }&lt;br /&gt;
    } else {&lt;br /&gt;
        // 空文字でOK、もしくはキャンセルされた場合はデフォルトに戻すか確認&lt;br /&gt;
        if (Confirm(&amp;quot;カスタムインストラクションをデフォルトに戻しますか？&amp;quot;)) {&lt;br /&gt;
            setTemporaryCustomInstruction(&amp;quot;&amp;quot;);&lt;br /&gt;
        } else {&lt;br /&gt;
            alert(&amp;quot;カスタムインストラクションの変更をキャンセルしました。\n※カスタムインストラクションを空にしたい場合はスペースのみを入力してください。&amp;quot;);&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // 変更が加わっている場合は会話履歴をリセット&lt;br /&gt;
    if (tempCustomInstruction !== getTemporaryCustomInstruction()) {&lt;br /&gt;
        resetTag();&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getTemporaryCustomInstruction() {&lt;br /&gt;
    var tag = doc.Tag;&lt;br /&gt;
    if (tag.exists(TEMPORARY_CUSTOM_INSTRUCTION_TAG)) {&lt;br /&gt;
        return tag(TEMPORARY_CUSTOM_INSTRUCTION_TAG);&lt;br /&gt;
    } else {&lt;br /&gt;
        return &amp;quot;&amp;quot;;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function setTemporaryCustomInstruction(temporaryCustomInstruction) {&lt;br /&gt;
    var tag = doc.Tag;&lt;br /&gt;
    if (temporaryCustomInstruction) {&lt;br /&gt;
        tag(TEMPORARY_CUSTOM_INSTRUCTION_TAG) = temporaryCustomInstruction;&lt;br /&gt;
    } else {&lt;br /&gt;
        tag.remove(TEMPORARY_CUSTOM_INSTRUCTION_TAG);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function addPreviousTemplatePromptNumberToTag(menuTrack) {&lt;br /&gt;
    var tag = doc.Tag;&lt;br /&gt;
    tag(PREVIOUS_TEMPLATE_PROMPT_TAG) = menuTrack;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function inputProgressChar(pos) {&lt;br /&gt;
    sel.SetActivePos(pos);&lt;br /&gt;
    sel.Text = &amp;quot;.&amp;quot;;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;/div&gt;</summary>
		<author><name>Yuko</name></author>
	</entry>
	<entry>
		<id>https://www.haijin-boys.com/wiki/index.php?title=%E3%83%9E%E3%82%AF%E3%83%AD%E3%83%A9%E3%82%A4%E3%83%96%E3%83%A9%E3%83%AA&amp;diff=8867</id>
		<title>マクロライブラリ</title>
		<link rel="alternate" type="text/html" href="https://www.haijin-boys.com/wiki/index.php?title=%E3%83%9E%E3%82%AF%E3%83%AD%E3%83%A9%E3%82%A4%E3%83%96%E3%83%A9%E3%83%AA&amp;diff=8867"/>
		<updated>2025-04-13T07:51:14Z</updated>

		<summary type="html">&lt;p&gt;Yuko: /* おもしろ */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Mery 用のマクロ ライブラリです。&lt;br /&gt;
&lt;br /&gt;
スクリプトを拡張子 &amp;quot;js&amp;quot;（JavaScript、JScript の場合）にして Macros フォルダーなどに保存し、[マクロ] メニューの [選択] や [カスタマイズ] から読み込んで使用してください。&lt;br /&gt;
&lt;br /&gt;
このマクロ ライブラリはどなたでも自由に編集可能ですので、ご自身が開発した便利なマクロをどんどん共有してください。&lt;br /&gt;
&lt;br /&gt;
また、[[隠しオプション一覧]]に希望するオプションがあるかもしれませんので、そちらも参照してみてください。&lt;br /&gt;
&lt;br /&gt;
== テキスト操作補助 ==&lt;br /&gt;
* [https://github.com/haijinboys/mery-macro-emmet/blob/master/emmet.js Emmet] (Kuro)&lt;br /&gt;
* [[Zen Coding]] (Kuro)&lt;br /&gt;
* [[バイト数]] (Kuro, ks)&lt;br /&gt;
* [[日付と時刻を挿入]] (n, masme, isari)&lt;br /&gt;
* [[日付と時刻を挿入（カレンダー版）]] (papagoat)&lt;br /&gt;
* [[選択範囲を上下に移動]] (ks)&lt;br /&gt;
* [[TeraPadのCtrl↑と同じような動作をするマクロ]] (suke)&lt;br /&gt;
* [[TeraPadのCtrl↓と同じような動作をするマクロ]] (suke)&lt;br /&gt;
* [[Vz 風テキストスタック 2 ストロークキー]] (papagoat)&lt;br /&gt;
* [[ラクラク罫線]] (手石)&lt;br /&gt;
* [[矩形ペースト]] (手石)&lt;br /&gt;
* [[自動箇条書き]] (手石)&lt;br /&gt;
* [[特殊改行]] (FuG)&lt;br /&gt;
* [[部分編集]] (ks)&lt;br /&gt;
* [[文字カウント]] (masme)&lt;br /&gt;
* [[字下げ・字上げ]] (masme)&lt;br /&gt;
* [[数値を増減]] (masme)&lt;br /&gt;
* [[上に行挿入]] (yuko)&lt;br /&gt;
* [[選択範囲を広げる]] (yuko)&lt;br /&gt;
* [[貼付け実行後のカーソル位置を貼付け文字列の先頭にする]] (yuko)&lt;br /&gt;
* [[選択した文字列を文書からすべて削除]] (Noah)&lt;br /&gt;
* [[文章で使用されている文字をユニーク抽出]] (Kuro)&lt;br /&gt;
* [[CSV のカーソル位置の列を削除]] (MSY-07)&lt;br /&gt;
* [[XHTML入力補助]] (アラフォー親父)&lt;br /&gt;
* [[Markdown文書で改行時にリスト、引用ブロック開始記号を自動入力する]] (catfist, yuko)&lt;br /&gt;
* [[Markdown 向け、リスト記号 (*) のトグル]] (yuko)&lt;br /&gt;
* [[Markdown 向け、タスクリスト (* ［］) のチェック状態をトグル]] (yuko)&lt;br /&gt;
* [[Markdown入力補助]] (急急如律令)&lt;br /&gt;
* [[青空文庫用整形補助]] (ks, 急急如律令)&lt;br /&gt;
* [[自動ルビ振り]] (急急如律令)&lt;br /&gt;
* [[ドイツ語特殊文字]] (鯉)&lt;br /&gt;
* [[何年生で習う漢字？]] (シリル)&lt;br /&gt;
&lt;br /&gt;
== ファイル操作補助 ==&lt;br /&gt;
* [[フォルダ内のファイルをすべて開く]] (Kuro)&lt;br /&gt;
* [[ファイルを比較]] (Kuro, MSY-07)&lt;br /&gt;
* [[ファイル名を引数にしてアプリ起動]] (Kuro, miso)&lt;br /&gt;
* [[文字列中のパス起動]] (ks)&lt;br /&gt;
* [[パス・URLを開く コマンドを実行する]] (yuko)&lt;br /&gt;
* [[開いている文書のファイル名を変更する]] (yuko)&lt;br /&gt;
* [[上書き保存(UAC対応)]] (ks)&lt;br /&gt;
* [[含んでいるフォルダを開く]] (ks)&lt;br /&gt;
* [[フォルダを開く]] (n)&lt;br /&gt;
* [[拡張子の違うファイルを開く]] (n)&lt;br /&gt;
* [[コマンドプロンプトを開く]] (n)&lt;br /&gt;
* [[ブックマーク一覧ジャンプ]] (goat)&lt;br /&gt;
* [[よく使うフォルダ]] (手石)&lt;br /&gt;
* [[ファイル（選択範囲）の仮保存]] (手石)&lt;br /&gt;
* [[ポップアップメニューでファイル選択]] (手石)&lt;br /&gt;
* [[フォルダツリーをポップアップ表示]] (n)&lt;br /&gt;
* [[タグジャンプ]] (o)&lt;br /&gt;
* [[ウィンドウの位置とサイズを拡張子で指定]] (papagoat)&lt;br /&gt;
* [[編集モードの自動選択]] (Bleat)&lt;br /&gt;
* [[書き換え禁止モード色反転]] (Bleat)&lt;br /&gt;
* [[変更なしのタブをすべて閉じる]] (Noah)&lt;br /&gt;
&lt;br /&gt;
== 変換・ソート・整形 ==&lt;br /&gt;
* [[標準変換]] (Kuro, MSY-07)&lt;br /&gt;
* [[テキスト変換]] (ks)&lt;br /&gt;
* [[テキスト整形]] (masme)&lt;br /&gt;
* [[旧漢字から新漢字に変換]] (furu)&lt;br /&gt;
* [[Unicode正規化]] (ucky)&lt;br /&gt;
* [[Wikipediaマークアップ修正]] (MSY-07)&lt;br /&gt;
* [[SHA でハッシュ化]] (Kuro)&lt;br /&gt;
* [[ブックマーク系コマンド集]] (ks)&lt;br /&gt;
* [[clustalw を用いてアライメントを実行]] (suke)&lt;br /&gt;
* [[clustal-omegaを用いてアライメントを実行]] (suke)&lt;br /&gt;
* [[連番の挿入]] (手石)&lt;br /&gt;
* [[連番を挿入]] (masme)&lt;br /&gt;
* [[マルチカーソルに連番を入力]] (yuko)&lt;br /&gt;
* [[可変長を固定長に変換]] (MSY-07)&lt;br /&gt;
* [[TSV(CSV) ⇒ 固定長]] (手石)&lt;br /&gt;
* [[行並べ替え]] (masme)&lt;br /&gt;
* [[GFMをHTMLに変換]] (noonworks)&lt;br /&gt;
* [[基数変換10 16]] (シリル)&lt;br /&gt;
* [[スネークケース ⇔ キャメルケースの変換]] (Noah)&lt;br /&gt;
* [[自然数ソート]] (tak)&lt;br /&gt;
&lt;br /&gt;
== 検索・置換 ==&lt;br /&gt;
* [[文書から検索]] (Kuro)&lt;br /&gt;
* [[単語を抽出]] (Kuro)&lt;br /&gt;
* [[タグの外側（内側）だけ置換]] (手石)&lt;br /&gt;
* [[ポップアップメニューで検索先にジャンプ]] (手石)&lt;br /&gt;
* [[検索(SJIS以外)]] (masme)&lt;br /&gt;
* [https://github.com/TakamiChie/mery-scripts/blob/master/SearchWithMode.js 現在開いているファイルの言語名をつけてWeb検索] (高見知英)&lt;br /&gt;
* [https://github.com/GunseiKPaseri/Mery_macro/blob/master/count.js 文字出現量ランキング] (群生系パセリ)&lt;br /&gt;
* [[検索ヒット数表示]] (ks)&lt;br /&gt;
* [[任意の検索エンジンで検索ー改良版]] (Takeshi)&lt;br /&gt;
* [[デフォルトの検索フラグを設定して検索ウィンドウ表示]] (yuko)&lt;br /&gt;
* [[複数置換]] (Noah)&lt;br /&gt;
* [[類義語アウトプット【Python】]] (シリル)&lt;br /&gt;
* [[校正支援アウトプット]] (シリル)&lt;br /&gt;
* [[見出しジャンプ]] (yuko)&lt;br /&gt;
* [[CSV/TSVでアクティブ列選択]] (yuko)&lt;br /&gt;
&lt;br /&gt;
== プログラミング補助 ==&lt;br /&gt;
* [[Eclipse風コメントアウト]] (ks)&lt;br /&gt;
* [[Eclipse風コメントアウト(改)]] (pizz)&lt;br /&gt;
* [[コメントマーク付け外し]] (手石, masme)&lt;br /&gt;
* [[スマートインデント(C 構文)]] (ks)&lt;br /&gt;
* [[ctags.exeでtagsファイルを生成、タグジャンプを行うマクロ]] (kazy)&lt;br /&gt;
* [[タグファイルから補完]] (n)&lt;br /&gt;
* [[コードスニペットもどき]] (手石)&lt;br /&gt;
* [[TeX文書のコンパイル]] (手石, isari)&lt;br /&gt;
* [[簡易数式計算]] (ks)&lt;br /&gt;
* [[定義へ移動]] (ks)&lt;br /&gt;
* [[タブの桁数]] (手石)&lt;br /&gt;
* [[行末の空白を削除]] (n)&lt;br /&gt;
* [https://github.com/TakamiChie/mery-scripts/blob/master/FileExecutor.js 開いているスクリプトファイルを実行] (高見知英)&lt;br /&gt;
* [https://github.com/TakamiChie/mery-scripts/blob/master/SyntaxChecker.js 開いているスクリプトファイルを構文チェック] (高見知英)&lt;br /&gt;
* [[説明を出力]] (s)&lt;br /&gt;
* [[JSON整形]] (Ovis)&lt;br /&gt;
* [[コンソール実行]] (YOS G-spec)&lt;br /&gt;
* [[拡張子からコンソール実行]] (YOS G-spec)&lt;br /&gt;
* [[ScriptRun]] (fuka)&lt;br /&gt;
* [[JavaScript を実行]] (n)&lt;br /&gt;
* [[Ruby を実行]] (n)&lt;br /&gt;
* [[HSPキーワード検索]] (SSSE)&lt;br /&gt;
&lt;br /&gt;
== おもしろ ==&lt;br /&gt;
* [[メール送信]] (ks)&lt;br /&gt;
* [[あつまれ☆みんなのおもしろアイコン]] (シリル, ucky)&lt;br /&gt;
* [[テーマのトグル]] (ucky)&lt;br /&gt;
* [[お気に入りフォント]] (ucky)&lt;br /&gt;
* [[Google Gemini に相談]] (yuko)&lt;br /&gt;
* [[OpenAI GPT に相談]] (yuko)&lt;br /&gt;
* [[GroqCloud Llama に相談]] (yuko)&lt;br /&gt;
* [[Google Gemini で執筆支援]] (Kuro)&lt;br /&gt;
&lt;br /&gt;
== その他 ==&lt;br /&gt;
* [[最新版を確認]] (diaphragm)&lt;br /&gt;
* [[SAPI 使って単語読み上げ]] (miso)&lt;br /&gt;
* [[フォルダ選択ダイアログ]] (手石)&lt;br /&gt;
* [[HTA(HTML)の自作ダイアログを表示]] (noonworks)&lt;br /&gt;
* [[ポップアップメニューを手軽に扱う]] (komorebikoboshi)&lt;br /&gt;
* [[ポップアップメニューでチェックボックス]] (手石)&lt;br /&gt;
* [[ポップアップメニューでラジオボタン]] (手石)&lt;br /&gt;
* [[マクロ・関数をメニューで呼び出す]] (aliquis)&lt;br /&gt;
* [[階層化マクロメニュー]] (ks)&lt;br /&gt;
* [[シェルコマンドを実行]] (sasaki)&lt;br /&gt;
* [[Editor.OpenFile()のポリフィル]] (kazy)&lt;br /&gt;
* [[折り返しトグル切り替え]] (Bleat)&lt;br /&gt;
* [[Beep音の例]] (star0worshipper)&lt;br /&gt;
* [[ブラウザ風タブ移動]] (yuko)&lt;br /&gt;
* [[タブ位置を先頭/末尾に移動]] (yuko)&lt;br /&gt;
* [[Mery 電卓]] (yuko)&lt;br /&gt;
&lt;br /&gt;
== 開発者向け ==&lt;br /&gt;
* [[キーアサイン集]] (ks, masme, isari)&lt;br /&gt;
* [[includeライブラリ]] (ks)&lt;br /&gt;
* [[マクロ覚え書き（開発者向け）]] (ks, papagoat)&lt;br /&gt;
&lt;br /&gt;
== 注意事項 ==&lt;br /&gt;
[https://www.haijin-boys.com/software/mery/mery-2-6-6#5 Mery Ver 2.6.6]以前に作成されたマクロが動作しない場合は下記のページを参照してください。&lt;br /&gt;
&lt;br /&gt;
* [https://www.haijin-boys.com/wiki/%E3%83%9E%E3%82%AF%E3%83%AD%E3%83%AA%E3%83%95%E3%82%A1%E3%83%AC%E3%83%B3%E3%82%B9:3:%E3%83%87%E3%82%A3%E3%83%AC%E3%82%AF%E3%83%86%E3%82%A3%E3%83%96##language マクロリファレンス:3:ディレクティブ]&lt;br /&gt;
* [https://www.haijin-boys.com/discussions/6929 公式のマクロ 箇条書きが最新メリーで動作するかチェックいただけませんか？]&lt;/div&gt;</summary>
		<author><name>Yuko</name></author>
	</entry>
	<entry>
		<id>https://www.haijin-boys.com/wiki/index.php?title=Mery_%E9%9B%BB%E5%8D%93&amp;diff=8850</id>
		<title>Mery 電卓</title>
		<link rel="alternate" type="text/html" href="https://www.haijin-boys.com/wiki/index.php?title=Mery_%E9%9B%BB%E5%8D%93&amp;diff=8850"/>
		<updated>2025-03-18T01:27:12Z</updated>

		<summary type="html">&lt;p&gt;Yuko: /* ソースコード */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== 概要 ==&lt;br /&gt;
&lt;br /&gt;
「Mery 電卓」は、Meryエディタ上で数式を計算するためのマクロです。標準的な数学関数をサポートしており、カーソル位置の行または選択したテキストを数式として解釈し、結果を表示します。計算結果は自動的にクリップボードにコピーされ、カンマ区切りの数値として表示されます。&lt;br /&gt;
&lt;br /&gt;
本マクロをショートカットキーに設定し、いつでも起動できるようにしておくと日常シーンで何かと便利です。作者は「Ctrl + Alt + Enter」キーに割り当てて利用しています。&lt;br /&gt;
&lt;br /&gt;
== 注意事項 ==&lt;br /&gt;
&lt;br /&gt;
* Mery Ver 3.7.12 で動作確認しています。&lt;br /&gt;
&lt;br /&gt;
== 使い方 ==&lt;br /&gt;
&lt;br /&gt;
1. &#039;&#039;&#039;行全体を計算する場合&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* 計算したい数式が書かれた行にカーソルを置きます。&lt;br /&gt;
* マクロを実行すると、次の行に「= 計算結果」が表示されます。&lt;br /&gt;
* 既に結果行がある場合は、その行が更新されます。&lt;br /&gt;
&lt;br /&gt;
2. &#039;&#039;&#039;選択範囲を計算する場合&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* 計算したい数式部分を選択します。&lt;br /&gt;
* マクロを実行すると、選択範囲の後ろに「計算式 = 計算結果」が挿入されます。&lt;br /&gt;
&lt;br /&gt;
3. &#039;&#039;&#039;サポートされている演算子&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* 四則演算（+, -, *, /）&lt;br /&gt;
* べき乗（^）&lt;br /&gt;
* 剰余（%）&lt;br /&gt;
* 比較演算子（&amp;lt;, &amp;gt;, &amp;lt;=, &amp;gt;=, ==, !=）&lt;br /&gt;
* 論理演算子（and, or, not）&lt;br /&gt;
&lt;br /&gt;
4. &#039;&#039;&#039;サポートされている関数&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* 三角関数（sin, cos, tan, asin, acos, atan）&lt;br /&gt;
* 双曲線関数（sinh, cosh, tanh, asinh, acosh, atanh）&lt;br /&gt;
* 対数関数（log, log10, log2, ln）&lt;br /&gt;
* その他の数学関数（sqrt, cbrt, abs, ceil, floor, round, trunc など）&lt;br /&gt;
&lt;br /&gt;
5. &#039;&#039;&#039;特別な機能&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* カンマ区切り表示: 計算結果は読みやすいようにカンマ区切りで表示されます。変数「COMMA」を設定することで調整可能です。&lt;br /&gt;
* 自動コピー: 計算結果は自動的にクリップボードにコピーされます。変数「COPY」を設定することで調整可能です。&lt;br /&gt;
* 計算結果の再利用: 行頭の「= 」は入力から無視されるため、計算結果の位置からスムーズに次の数式として利用できます。&lt;br /&gt;
* 変数の利用: 複数行、または「;」で区切ることで、変数を使った数式が利用できます。変数形式は「$[a-zA-Z][a-zA-Z0-9]*」に対応しています。例えば「$a = 2; $a * 3」として実行すると、結果は「6」と出力されます。&lt;br /&gt;
* メモ書きから計算: 数字や関数以外の文字はできるだけ除外した上で計算をします。例えば「月100円 * 12ヶ月」のようなメモ書きに対して実行すると「1,200」と出力されます。 [ と ] で囲まれた文字列は丸ごと除外するため、コメントに最適です。&lt;br /&gt;
&lt;br /&gt;
== ソースコード ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
#title = &amp;quot;Mery 電卓&amp;quot;&lt;br /&gt;
#include &amp;quot;Common.js&amp;quot;&lt;br /&gt;
#language = &amp;quot;v8&amp;quot;&lt;br /&gt;
Redraw = false;&lt;br /&gt;
BeginUndoGroup();&lt;br /&gt;
&lt;br /&gt;
// 結果をクリップボードにコピー&lt;br /&gt;
var COPY = true;&lt;br /&gt;
// 出力結果をカンマ区切りにする&lt;br /&gt;
var COMMA = true;&lt;br /&gt;
&lt;br /&gt;
// expr-eval ライブラリ&lt;br /&gt;
// from: https://cdn.jsdelivr.net/npm/expr-eval@2.0.2/dist/bundle.min.js&lt;br /&gt;
!function(t,e){&amp;quot;object&amp;quot;==typeof exports&amp;amp;&amp;amp;&amp;quot;undefined&amp;quot;!=typeof module?e(exports):&amp;quot;function&amp;quot;==typeof define&amp;amp;&amp;amp;define.amd?define([&amp;quot;exports&amp;quot;],e):e((t=t||self).exprEval={})}(this,function(t){&amp;quot;use strict&amp;quot;;var y=&amp;quot;INUMBER&amp;quot;,x=&amp;quot;IOP1&amp;quot;,w=&amp;quot;IOP2&amp;quot;,d=&amp;quot;IOP3&amp;quot;,g=&amp;quot;IVAR&amp;quot;,M=&amp;quot;IVARNAME&amp;quot;,E=&amp;quot;IFUNCALL&amp;quot;,m=&amp;quot;IFUNDEF&amp;quot;,A=&amp;quot;IEXPR&amp;quot;,b=&amp;quot;IEXPREVAL&amp;quot;,k=&amp;quot;IMEMBER&amp;quot;,O=&amp;quot;IENDSTATEMENT&amp;quot;,T=&amp;quot;IARRAY&amp;quot;;function C(t,e){this.type=t,this.value=null!=e?e:0}function c(t){return new C(x,t)}function f(t){return new C(w,t)}function l(t){return new C(d,t)}function N(t,i,o){var e,r,s,n,a,p,h=[];if(I(t))return P(t,o);for(var u=t.length,c=0;c&amp;lt;u;c++){var f=t[c],l=f.type;if(l===y||l===M)h.push(f.value);else if(l===w)r=h.pop(),e=h.pop(),&amp;quot;and&amp;quot;===f.value?h.push(!!e&amp;amp;&amp;amp;!!N(r,i,o)):&amp;quot;or&amp;quot;===f.value?h.push(!!e||!!N(r,i,o)):&amp;quot;=&amp;quot;===f.value?(n=i.binaryOps[f.value],h.push(n(e,N(r,i,o),o))):(n=i.binaryOps[f.value],h.push(n(P(e,o),P(r,o))));else if(l===d)s=h.pop(),r=h.pop(),e=h.pop(),&amp;quot;?&amp;quot;===f.value?h.push(N(e?r:s,i,o)):(n=i.ternaryOps[f.value],h.push(n(P(e,o),P(r,o),P(s,o))));else if(l===g)if(f.value in i.functions)h.push(i.functions[f.value]);else if(f.value in i.unaryOps&amp;amp;&amp;amp;i.parser.isOperatorEnabled(f.value))h.push(i.unaryOps[f.value]);else{var v=o[f.value];if(void 0===v)throw new Error(&amp;quot;undefined variable: &amp;quot;+f.value);h.push(v)}else if(l===x)e=h.pop(),n=i.unaryOps[f.value],h.push(n(P(e,o)));else if(l===E){for(p=f.value,a=[];0&amp;lt;p--;)a.unshift(P(h.pop(),o));if(!(n=h.pop()).apply||!n.call)throw new Error(n+&amp;quot; is not a function&amp;quot;);h.push(n.apply(void 0,a))}else if(l===m)h.push(function(){for(var s=h.pop(),n=[],t=f.value;0&amp;lt;t--;)n.unshift(h.pop());function e(){for(var t=Object.assign({},o),e=0,r=n.length;e&amp;lt;r;e++)t[n[e]]=arguments[e];return N(s,i,t)}var r=h.pop();return Object.defineProperty(e,&amp;quot;name&amp;quot;,{value:r,writable:!1}),o[r]=e}());else if(l===A)h.push(S(f,i));else if(l===b)h.push(f);else if(l===k)e=h.pop(),h.push(e[f.value]);else if(l===O)h.pop();else{if(l!==T)throw new Error(&amp;quot;invalid Expression&amp;quot;);for(p=f.value,a=[];0&amp;lt;p--;)a.unshift(h.pop());h.push(a)}}if(1&amp;lt;h.length)throw new Error(&amp;quot;invalid Expression (parity)&amp;quot;);return 0===h[0]?0:P(h[0],o)}function S(e,r){return I(e)?e:{type:b,value:function(t){return N(e.value,r,t)}}}function I(t){return t&amp;amp;&amp;amp;t.type===b}function P(t,e){return I(t)?t.value(e):t}function v(t,e){for(var r,s,n,i,o,a,p=[],h=0;h&amp;lt;t.length;h++){var u=t[h],c=u.type;if(c===y)&amp;quot;number&amp;quot;==typeof u.value&amp;amp;&amp;amp;u.value&amp;lt;0?p.push(&amp;quot;(&amp;quot;+u.value+&amp;quot;)&amp;quot;):Array.isArray(u.value)?p.push(&amp;quot;[&amp;quot;+u.value.map(F).join(&amp;quot;, &amp;quot;)+&amp;quot;]&amp;quot;):p.push(F(u.value));else if(c===w)s=p.pop(),r=p.pop(),i=u.value,e?&amp;quot;^&amp;quot;===i?p.push(&amp;quot;Math.pow(&amp;quot;+r+&amp;quot;, &amp;quot;+s+&amp;quot;)&amp;quot;):&amp;quot;and&amp;quot;===i?p.push(&amp;quot;(!!&amp;quot;+r+&amp;quot; &amp;amp;&amp;amp; !!&amp;quot;+s+&amp;quot;)&amp;quot;):&amp;quot;or&amp;quot;===i?p.push(&amp;quot;(!!&amp;quot;+r+&amp;quot; || !!&amp;quot;+s+&amp;quot;)&amp;quot;):&amp;quot;||&amp;quot;===i?p.push(&amp;quot;(function(a,b){ return Array.isArray(a) &amp;amp;&amp;amp; Array.isArray(b) ? a.concat(b) : String(a) + String(b); }((&amp;quot;+r+&amp;quot;),(&amp;quot;+s+&amp;quot;)))&amp;quot;):&amp;quot;==&amp;quot;===i?p.push(&amp;quot;(&amp;quot;+r+&amp;quot; === &amp;quot;+s+&amp;quot;)&amp;quot;):&amp;quot;!=&amp;quot;===i?p.push(&amp;quot;(&amp;quot;+r+&amp;quot; !== &amp;quot;+s+&amp;quot;)&amp;quot;):&amp;quot;[&amp;quot;===i?p.push(r+&amp;quot;[(&amp;quot;+s+&amp;quot;) | 0]&amp;quot;):p.push(&amp;quot;(&amp;quot;+r+&amp;quot; &amp;quot;+i+&amp;quot; &amp;quot;+s+&amp;quot;)&amp;quot;):&amp;quot;[&amp;quot;===i?p.push(r+&amp;quot;[&amp;quot;+s+&amp;quot;]&amp;quot;):p.push(&amp;quot;(&amp;quot;+r+&amp;quot; &amp;quot;+i+&amp;quot; &amp;quot;+s+&amp;quot;)&amp;quot;);else if(c===d){if(n=p.pop(),s=p.pop(),r=p.pop(),&amp;quot;?&amp;quot;!==(i=u.value))throw new Error(&amp;quot;invalid Expression&amp;quot;);p.push(&amp;quot;(&amp;quot;+r+&amp;quot; ? &amp;quot;+s+&amp;quot; : &amp;quot;+n+&amp;quot;)&amp;quot;)}else if(c===g||c===M)p.push(u.value);else if(c===x)r=p.pop(),&amp;quot;-&amp;quot;===(i=u.value)||&amp;quot;+&amp;quot;===i?p.push(&amp;quot;(&amp;quot;+i+r+&amp;quot;)&amp;quot;):e?&amp;quot;not&amp;quot;===i?p.push(&amp;quot;(!&amp;quot;+r+&amp;quot;)&amp;quot;):&amp;quot;!&amp;quot;===i?p.push(&amp;quot;fac(&amp;quot;+r+&amp;quot;)&amp;quot;):p.push(i+&amp;quot;(&amp;quot;+r+&amp;quot;)&amp;quot;):&amp;quot;!&amp;quot;===i?p.push(&amp;quot;(&amp;quot;+r+&amp;quot;!)&amp;quot;):p.push(&amp;quot;(&amp;quot;+i+&amp;quot; &amp;quot;+r+&amp;quot;)&amp;quot;);else if(c===E){for(a=u.value,o=[];0&amp;lt;a--;)o.unshift(p.pop());i=p.pop(),p.push(i+&amp;quot;(&amp;quot;+o.join(&amp;quot;, &amp;quot;)+&amp;quot;)&amp;quot;)}else if(c===m){for(s=p.pop(),a=u.value,o=[];0&amp;lt;a--;)o.unshift(p.pop());r=p.pop(),e?p.push(&amp;quot;(&amp;quot;+r+&amp;quot; = function(&amp;quot;+o.join(&amp;quot;, &amp;quot;)+&amp;quot;) { return &amp;quot;+s+&amp;quot; })&amp;quot;):p.push(&amp;quot;(&amp;quot;+r+&amp;quot;(&amp;quot;+o.join(&amp;quot;, &amp;quot;)+&amp;quot;) = &amp;quot;+s+&amp;quot;)&amp;quot;)}else if(c===k)r=p.pop(),p.push(r+&amp;quot;.&amp;quot;+u.value);else if(c===T){for(a=u.value,o=[];0&amp;lt;a--;)o.unshift(p.pop());p.push(&amp;quot;[&amp;quot;+o.join(&amp;quot;, &amp;quot;)+&amp;quot;]&amp;quot;)}else if(c===A)p.push(&amp;quot;(&amp;quot;+v(u.value,e)+&amp;quot;)&amp;quot;);else if(c!==O)throw new Error(&amp;quot;invalid Expression&amp;quot;)}return 1&amp;lt;p.length&amp;amp;&amp;amp;(p=e?[p.join(&amp;quot;,&amp;quot;)]:[p.join(&amp;quot;;&amp;quot;)]),String(p[0])}function F(t){return&amp;quot;string&amp;quot;==typeof t?JSON.stringify(t).replace(/\u2028/g,&amp;quot;\\u2028&amp;quot;).replace(/\u2029/g,&amp;quot;\\u2029&amp;quot;):t}function a(t,e){for(var r=0;r&amp;lt;t.length;r++)if(t[r]===e)return!0;return!1}function p(t,e,r){for(var s=!!(r=r||{}).withMembers,n=null,i=0;i&amp;lt;t.length;i++){var o=t[i];o.type===g||o.type===M?s||a(e,o.value)?n=(null!==n&amp;amp;&amp;amp;(a(e,n)||e.push(n)),o.value):e.push(o.value):o.type===k&amp;amp;&amp;amp;s&amp;amp;&amp;amp;null!==n?n+=&amp;quot;.&amp;quot;+o.value:o.type===A?p(o.value,e,r):null!==n&amp;amp;&amp;amp;(a(e,n)||e.push(n),n=null)}null===n||a(e,n)||e.push(n)}function s(t,e){this.tokens=t,this.parser=e,this.unaryOps=e.unaryOps,this.binaryOps=e.binaryOps,this.ternaryOps=e.ternaryOps,this.functions=e.functions}C.prototype.toString=function(){switch(this.type){case y:case x:case w:case d:case g:case M:case O:return this.value;case E:return&amp;quot;CALL &amp;quot;+this.value;case m:return&amp;quot;DEF &amp;quot;+this.value;case T:return&amp;quot;ARRAY &amp;quot;+this.value;case k:return&amp;quot;.&amp;quot;+this.value;default:return&amp;quot;Invalid Instruction&amp;quot;}},s.prototype.simplify=function(t){return t=t||{},new s(function t(e,r,s,n,i){for(var o,a,p,h,u=[],c=[],f=0;f&amp;lt;e.length;f++){var l=e[f],v=l.type;if(v===y||v===M)Array.isArray(l.value)?u.push.apply(u,t(l.value.map(function(t){return new C(y,t)}).concat(new C(T,l.value.length)),r,s,n,i)):u.push(l);else if(v===g&amp;amp;&amp;amp;i.hasOwnProperty(l.value))l=new C(y,i[l.value]),u.push(l);else if(v===w&amp;amp;&amp;amp;1&amp;lt;u.length)a=u.pop(),o=u.pop(),h=s[l.value],l=new C(y,h(o.value,a.value)),u.push(l);else if(v===d&amp;amp;&amp;amp;2&amp;lt;u.length)p=u.pop(),a=u.pop(),o=u.pop(),&amp;quot;?&amp;quot;===l.value?u.push(o.value?a.value:p.value):(h=n[l.value],l=new C(y,h(o.value,a.value,p.value)),u.push(l));else if(v===x&amp;amp;&amp;amp;0&amp;lt;u.length)o=u.pop(),h=r[l.value],l=new C(y,h(o.value)),u.push(l);else if(v===A){for(;0&amp;lt;u.length;)c.push(u.shift());c.push(new C(A,t(l.value,r,s,n,i)))}else if(v===k&amp;amp;&amp;amp;0&amp;lt;u.length)o=u.pop(),u.push(new C(y,o.value[l.value]));else{for(;0&amp;lt;u.length;)c.push(u.shift());c.push(l)}}for(;0&amp;lt;u.length;)c.push(u.shift());return c}(this.tokens,this.unaryOps,this.binaryOps,this.ternaryOps,t),this.parser)},s.prototype.substitute=function(t,e){return e instanceof s||(e=this.parser.parse(String(e))),new s(function t(e,r,s){for(var n=[],i=0;i&amp;lt;e.length;i++){var o=e[i],a=o.type;if(a===g&amp;amp;&amp;amp;o.value===r)for(var p=0;p&amp;lt;s.tokens.length;p++){var h,u=s.tokens[p];h=u.type===x?c(u.value):u.type===w?f(u.value):u.type===d?l(u.value):new C(u.type,u.value),n.push(h)}else a===A?n.push(new C(A,t(o.value,r,s))):n.push(o)}return n}(this.tokens,t,e),this.parser)},s.prototype.evaluate=function(t){return t=t||{},N(this.tokens,this,t)},s.prototype.toString=function(){return v(this.tokens,!1)},s.prototype.symbols=function(t){t=t||{};var e=[];return p(this.tokens,e,t),e},s.prototype.variables=function(t){t=t||{};var e=[];p(this.tokens,e,t);var r=this.functions;return e.filter(function(t){return!(t in r)})},s.prototype.toJSFunction=function(t,e){var r=this,s=new Function(t,&amp;quot;with(this.functions) with (this.ternaryOps) with (this.binaryOps) with (this.unaryOps) { return &amp;quot;+v(this.simplify(e).tokens,!0)+&amp;quot;; }&amp;quot;);return function(){return s.apply(r,arguments)}};var n=&amp;quot;TEOF&amp;quot;,h=&amp;quot;TOP&amp;quot;,u=&amp;quot;TNUMBER&amp;quot;,i=&amp;quot;TSTRING&amp;quot;,o=&amp;quot;TPAREN&amp;quot;,R=&amp;quot;TBRACKET&amp;quot;,L=&amp;quot;TCOMMA&amp;quot;,j=&amp;quot;TNAME&amp;quot;,U=&amp;quot;TSEMICOLON&amp;quot;;function q(t,e,r){this.type=t,this.value=e,this.index=r}function B(t,e){this.pos=0,this.current=null,this.unaryOps=t.unaryOps,this.binaryOps=t.binaryOps,this.ternaryOps=t.ternaryOps,this.consts=t.consts,this.expression=e,this.savedPosition=0,this.savedCurrent=null,this.options=t.options,this.parser=t}q.prototype.toString=function(){return this.type+&amp;quot;: &amp;quot;+this.value},B.prototype.newToken=function(t,e,r){return new q(t,e,null!=r?r:this.pos)},B.prototype.save=function(){this.savedPosition=this.pos,this.savedCurrent=this.current},B.prototype.restore=function(){this.pos=this.savedPosition,this.current=this.savedCurrent},B.prototype.next=function(){return this.pos&amp;gt;=this.expression.length?this.newToken(n,&amp;quot;EOF&amp;quot;):this.isWhitespace()||this.isComment()?this.next():this.isRadixInteger()||this.isNumber()||this.isOperator()||this.isString()||this.isParen()||this.isBracket()||this.isComma()||this.isSemicolon()||this.isNamedOp()||this.isConst()||this.isName()?this.current:void this.parseError(&#039;Unknown character &amp;quot;&#039;+this.expression.charAt(this.pos)+&#039;&amp;quot;&#039;)},B.prototype.isString=function(){var t=!1,e=this.pos,r=this.expression.charAt(e);if(&amp;quot;&#039;&amp;quot;===r||&#039;&amp;quot;&#039;===r)for(var s=this.expression.indexOf(r,e+1);0&amp;lt;=s&amp;amp;&amp;amp;this.pos&amp;lt;this.expression.length;){if(this.pos=s+1,&amp;quot;\\&amp;quot;!==this.expression.charAt(s-1)){var n=this.expression.substring(e+1,s);this.current=this.newToken(i,this.unescape(n),e),t=!0;break}s=this.expression.indexOf(r,s+1)}return t},B.prototype.isParen=function(){var t=this.expression.charAt(this.pos);return(&amp;quot;(&amp;quot;===t||&amp;quot;)&amp;quot;===t)&amp;amp;&amp;amp;(this.current=this.newToken(o,t),this.pos++,!0)},B.prototype.isBracket=function(){var t=this.expression.charAt(this.pos);return!(&amp;quot;[&amp;quot;!==t&amp;amp;&amp;amp;&amp;quot;]&amp;quot;!==t||!this.isOperatorEnabled(&amp;quot;[&amp;quot;))&amp;amp;&amp;amp;(this.current=this.newToken(R,t),this.pos++,!0)},B.prototype.isComma=function(){return&amp;quot;,&amp;quot;===this.expression.charAt(this.pos)&amp;amp;&amp;amp;(this.current=this.newToken(L,&amp;quot;,&amp;quot;),this.pos++,!0)},B.prototype.isSemicolon=function(){return&amp;quot;;&amp;quot;===this.expression.charAt(this.pos)&amp;amp;&amp;amp;(this.current=this.newToken(U,&amp;quot;;&amp;quot;),this.pos++,!0)},B.prototype.isConst=function(){for(var t=this.pos,e=t;e&amp;lt;this.expression.length;e++){var r=this.expression.charAt(e);if(r.toUpperCase()===r.toLowerCase()&amp;amp;&amp;amp;(e===this.pos||&amp;quot;_&amp;quot;!==r&amp;amp;&amp;amp;&amp;quot;.&amp;quot;!==r&amp;amp;&amp;amp;(r&amp;lt;&amp;quot;0&amp;quot;||&amp;quot;9&amp;quot;&amp;lt;r)))break}if(t&amp;lt;e){var s=this.expression.substring(t,e);if(s in this.consts)return this.current=this.newToken(u,this.consts[s]),this.pos+=s.length,!0}return!1},B.prototype.isNamedOp=function(){for(var t=this.pos,e=t;e&amp;lt;this.expression.length;e++){var r=this.expression.charAt(e);if(r.toUpperCase()===r.toLowerCase()&amp;amp;&amp;amp;(e===this.pos||&amp;quot;_&amp;quot;!==r&amp;amp;&amp;amp;(r&amp;lt;&amp;quot;0&amp;quot;||&amp;quot;9&amp;quot;&amp;lt;r)))break}if(t&amp;lt;e){var s=this.expression.substring(t,e);if(this.isOperatorEnabled(s)&amp;amp;&amp;amp;(s in this.binaryOps||s in this.unaryOps||s in this.ternaryOps))return this.current=this.newToken(h,s),this.pos+=s.length,!0}return!1},B.prototype.isName=function(){for(var t=this.pos,e=t,r=!1;e&amp;lt;this.expression.length;e++){var s=this.expression.charAt(e);if(s.toUpperCase()===s.toLowerCase()){if(e===this.pos&amp;amp;&amp;amp;(&amp;quot;$&amp;quot;===s||&amp;quot;_&amp;quot;===s)){&amp;quot;_&amp;quot;===s&amp;amp;&amp;amp;(r=!0);continue}if(e===this.pos||!r||&amp;quot;_&amp;quot;!==s&amp;amp;&amp;amp;(s&amp;lt;&amp;quot;0&amp;quot;||&amp;quot;9&amp;quot;&amp;lt;s))break}else r=!0}if(r){var n=this.expression.substring(t,e);return this.current=this.newToken(j,n),this.pos+=n.length,!0}return!1},B.prototype.isWhitespace=function(){for(var t=!1,e=this.expression.charAt(this.pos);!(&amp;quot; &amp;quot;!==e&amp;amp;&amp;amp;&amp;quot;\t&amp;quot;!==e&amp;amp;&amp;amp;&amp;quot;\n&amp;quot;!==e&amp;amp;&amp;amp;&amp;quot;\r&amp;quot;!==e||(t=!0,this.pos++,this.pos&amp;gt;=this.expression.length));)e=this.expression.charAt(this.pos);return t};var V=/^[0-9a-f]{4}$/i;function _(t,e,r){this.parser=t,this.tokens=e,this.current=null,this.nextToken=null,this.next(),this.savedCurrent=null,this.savedNextToken=null,this.allowMemberAccess=!1!==r.allowMemberAccess}B.prototype.unescape=function(t){var e=t.indexOf(&amp;quot;\\&amp;quot;);if(e&amp;lt;0)return t;for(var r=t.substring(0,e);0&amp;lt;=e;){var s=t.charAt(++e);switch(s){case&amp;quot;&#039;&amp;quot;:r+=&amp;quot;&#039;&amp;quot;;break;case&#039;&amp;quot;&#039;:r+=&#039;&amp;quot;&#039;;break;case&amp;quot;\\&amp;quot;:r+=&amp;quot;\\&amp;quot;;break;case&amp;quot;/&amp;quot;:r+=&amp;quot;/&amp;quot;;break;case&amp;quot;b&amp;quot;:r+=&amp;quot;\b&amp;quot;;break;case&amp;quot;f&amp;quot;:r+=&amp;quot;\f&amp;quot;;break;case&amp;quot;n&amp;quot;:r+=&amp;quot;\n&amp;quot;;break;case&amp;quot;r&amp;quot;:r+=&amp;quot;\r&amp;quot;;break;case&amp;quot;t&amp;quot;:r+=&amp;quot;\t&amp;quot;;break;case&amp;quot;u&amp;quot;:var n=t.substring(e+1,e+5);V.test(n)||this.parseError(&amp;quot;Illegal escape sequence: \\u&amp;quot;+n),r+=String.fromCharCode(parseInt(n,16)),e+=4;break;default:throw this.parseError(&#039;Illegal escape sequence: &amp;quot;\\&#039;+s+&#039;&amp;quot;&#039;)}++e;var i=t.indexOf(&amp;quot;\\&amp;quot;,e);r+=t.substring(e,i&amp;lt;0?t.length:i),e=i}return r},B.prototype.isComment=function(){return&amp;quot;/&amp;quot;===this.expression.charAt(this.pos)&amp;amp;&amp;amp;&amp;quot;*&amp;quot;===this.expression.charAt(this.pos+1)&amp;amp;&amp;amp;(this.pos=this.expression.indexOf(&amp;quot;*/&amp;quot;,this.pos)+2,1===this.pos&amp;amp;&amp;amp;(this.pos=this.expression.length),!0)},B.prototype.isRadixInteger=function(){var t,e,r=this.pos;if(r&amp;gt;=this.expression.length-2||&amp;quot;0&amp;quot;!==this.expression.charAt(r))return!1;if(++r,&amp;quot;x&amp;quot;===this.expression.charAt(r))t=16,e=/^[0-9a-f]$/i,++r;else{if(&amp;quot;b&amp;quot;!==this.expression.charAt(r))return!1;t=2,e=/^[01]$/i,++r}for(var s=!1,n=r;r&amp;lt;this.expression.length;){var i=this.expression.charAt(r);if(!e.test(i))break;r++,s=!0}return s&amp;amp;&amp;amp;(this.current=this.newToken(u,parseInt(this.expression.substring(n,r),t)),this.pos=r),s},B.prototype.isNumber=function(){for(var t,e=!1,r=this.pos,s=r,n=r,i=!1,o=!1;r&amp;lt;this.expression.length&amp;amp;&amp;amp;(&amp;quot;0&amp;quot;&amp;lt;=(t=this.expression.charAt(r))&amp;amp;&amp;amp;t&amp;lt;=&amp;quot;9&amp;quot;||!i&amp;amp;&amp;amp;&amp;quot;.&amp;quot;===t);)&amp;quot;.&amp;quot;===t?i=!0:o=!0,r++,e=o;if(e&amp;amp;&amp;amp;(n=r),&amp;quot;e&amp;quot;===t||&amp;quot;E&amp;quot;===t){r++;for(var a=!0,p=!1;r&amp;lt;this.expression.length;){if(t=this.expression.charAt(r),!a||&amp;quot;+&amp;quot;!==t&amp;amp;&amp;amp;&amp;quot;-&amp;quot;!==t){if(!(&amp;quot;0&amp;quot;&amp;lt;=t&amp;amp;&amp;amp;t&amp;lt;=&amp;quot;9&amp;quot;))break;a=!(p=!0)}else a=!1;r++}p||(r=n)}return e?(this.current=this.newToken(u,parseFloat(this.expression.substring(s,r))),this.pos=r):this.pos=n,e},B.prototype.isOperator=function(){var t=this.pos,e=this.expression.charAt(this.pos);if(&amp;quot;+&amp;quot;===e||&amp;quot;-&amp;quot;===e||&amp;quot;*&amp;quot;===e||&amp;quot;/&amp;quot;===e||&amp;quot;%&amp;quot;===e||&amp;quot;^&amp;quot;===e||&amp;quot;?&amp;quot;===e||&amp;quot;:&amp;quot;===e||&amp;quot;.&amp;quot;===e)this.current=this.newToken(h,e);else if(&amp;quot;∙&amp;quot;===e||&amp;quot;•&amp;quot;===e)this.current=this.newToken(h,&amp;quot;*&amp;quot;);else if(&amp;quot;&amp;gt;&amp;quot;===e)&amp;quot;=&amp;quot;===this.expression.charAt(this.pos+1)?(this.current=this.newToken(h,&amp;quot;&amp;gt;=&amp;quot;),this.pos++):this.current=this.newToken(h,&amp;quot;&amp;gt;&amp;quot;);else if(&amp;quot;&amp;lt;&amp;quot;===e)&amp;quot;=&amp;quot;===this.expression.charAt(this.pos+1)?(this.current=this.newToken(h,&amp;quot;&amp;lt;=&amp;quot;),this.pos++):this.current=this.newToken(h,&amp;quot;&amp;lt;&amp;quot;);else if(&amp;quot;|&amp;quot;===e){if(&amp;quot;|&amp;quot;!==this.expression.charAt(this.pos+1))return!1;this.current=this.newToken(h,&amp;quot;||&amp;quot;),this.pos++}else if(&amp;quot;=&amp;quot;===e)&amp;quot;=&amp;quot;===this.expression.charAt(this.pos+1)?(this.current=this.newToken(h,&amp;quot;==&amp;quot;),this.pos++):this.current=this.newToken(h,e);else{if(&amp;quot;!&amp;quot;!==e)return!1;&amp;quot;=&amp;quot;===this.expression.charAt(this.pos+1)?(this.current=this.newToken(h,&amp;quot;!=&amp;quot;),this.pos++):this.current=this.newToken(h,e)}return this.pos++,!!this.isOperatorEnabled(this.current.value)||(this.pos=t,!1)},B.prototype.isOperatorEnabled=function(t){return this.parser.isOperatorEnabled(t)},B.prototype.getCoordinates=function(){for(var t,e=0,r=-1;e++,t=this.pos-r,0&amp;lt;=(r=this.expression.indexOf(&amp;quot;\n&amp;quot;,r+1))&amp;amp;&amp;amp;r&amp;lt;this.pos;);return{line:e,column:t}},B.prototype.parseError=function(t){var e=this.getCoordinates();throw new Error(&amp;quot;parse error [&amp;quot;+e.line+&amp;quot;:&amp;quot;+e.column+&amp;quot;]: &amp;quot;+t)},_.prototype.next=function(){return this.current=this.nextToken,this.nextToken=this.tokens.next()},_.prototype.tokenMatches=function(t,e){return void 0===e||(Array.isArray(e)?a(e,t.value):&amp;quot;function&amp;quot;==typeof e?e(t):t.value===e)},_.prototype.save=function(){this.savedCurrent=this.current,this.savedNextToken=this.nextToken,this.tokens.save()},_.prototype.restore=function(){this.tokens.restore(),this.current=this.savedCurrent,this.nextToken=this.savedNextToken},_.prototype.accept=function(t,e){return!(this.nextToken.type!==t||!this.tokenMatches(this.nextToken,e))&amp;amp;&amp;amp;(this.next(),!0)},_.prototype.expect=function(t,e){if(!this.accept(t,e)){var r=this.tokens.getCoordinates();throw new Error(&amp;quot;parse error [&amp;quot;+r.line+&amp;quot;:&amp;quot;+r.column+&amp;quot;]: Expected &amp;quot;+(e||t))}},_.prototype.parseAtom=function(t){var e=this.tokens.unaryOps;if(this.accept(j)||this.accept(h,function(t){return t.value in e}))t.push(new C(g,this.current.value));else if(this.accept(u))t.push(new C(y,this.current.value));else if(this.accept(i))t.push(new C(y,this.current.value));else if(this.accept(o,&amp;quot;(&amp;quot;))this.parseExpression(t),this.expect(o,&amp;quot;)&amp;quot;);else{if(!this.accept(R,&amp;quot;[&amp;quot;))throw new Error(&amp;quot;unexpected &amp;quot;+this.nextToken);if(this.accept(R,&amp;quot;]&amp;quot;))t.push(new C(T,0));else{var r=this.parseArrayList(t);t.push(new C(T,r))}}},_.prototype.parseExpression=function(t){var e=[];this.parseUntilEndStatement(t,e)||(this.parseVariableAssignmentExpression(e),this.parseUntilEndStatement(t,e)||this.pushExpression(t,e))},_.prototype.pushExpression=function(t,e){for(var r=0,s=e.length;r&amp;lt;s;r++)t.push(e[r])},_.prototype.parseUntilEndStatement=function(t,e){return!!this.accept(U)&amp;amp;&amp;amp;(!this.nextToken||this.nextToken.type===n||this.nextToken.type===o&amp;amp;&amp;amp;&amp;quot;)&amp;quot;===this.nextToken.value||e.push(new C(O)),this.nextToken.type!==n&amp;amp;&amp;amp;this.parseExpression(e),t.push(new C(A,e)),!0)},_.prototype.parseArrayList=function(t){for(var e=0;!this.accept(R,&amp;quot;]&amp;quot;);)for(this.parseExpression(t),++e;this.accept(L);)this.parseExpression(t),++e;return e},_.prototype.parseVariableAssignmentExpression=function(t){for(this.parseConditionalExpression(t);this.accept(h,&amp;quot;=&amp;quot;);){var e=t.pop(),r=[],s=t.length-1;if(e.type!==E){if(e.type!==g&amp;amp;&amp;amp;e.type!==k)throw new Error(&amp;quot;expected variable for assignment&amp;quot;);this.parseVariableAssignmentExpression(r),t.push(new C(M,e.value)),t.push(new C(A,r)),t.push(f(&amp;quot;=&amp;quot;))}else{if(!this.tokens.isOperatorEnabled(&amp;quot;()=&amp;quot;))throw new Error(&amp;quot;function definition is not permitted&amp;quot;);for(var n=0,i=e.value+1;n&amp;lt;i;n++){var o=s-n;t[o].type===g&amp;amp;&amp;amp;(t[o]=new C(M,t[o].value))}this.parseVariableAssignmentExpression(r),t.push(new C(A,r)),t.push(new C(m,e.value))}}},_.prototype.parseConditionalExpression=function(t){for(this.parseOrExpression(t);this.accept(h,&amp;quot;?&amp;quot;);){var e=[],r=[];this.parseConditionalExpression(e),this.expect(h,&amp;quot;:&amp;quot;),this.parseConditionalExpression(r),t.push(new C(A,e)),t.push(new C(A,r)),t.push(l(&amp;quot;?&amp;quot;))}},_.prototype.parseOrExpression=function(t){for(this.parseAndExpression(t);this.accept(h,&amp;quot;or&amp;quot;);){var e=[];this.parseAndExpression(e),t.push(new C(A,e)),t.push(f(&amp;quot;or&amp;quot;))}},_.prototype.parseAndExpression=function(t){for(this.parseComparison(t);this.accept(h,&amp;quot;and&amp;quot;);){var e=[];this.parseComparison(e),t.push(new C(A,e)),t.push(f(&amp;quot;and&amp;quot;))}};var r=[&amp;quot;==&amp;quot;,&amp;quot;!=&amp;quot;,&amp;quot;&amp;lt;&amp;quot;,&amp;quot;&amp;lt;=&amp;quot;,&amp;quot;&amp;gt;=&amp;quot;,&amp;quot;&amp;gt;&amp;quot;,&amp;quot;in&amp;quot;];_.prototype.parseComparison=function(t){for(this.parseAddSub(t);this.accept(h,r);){var e=this.current;this.parseAddSub(t),t.push(f(e.value))}};var $=[&amp;quot;+&amp;quot;,&amp;quot;-&amp;quot;,&amp;quot;||&amp;quot;];_.prototype.parseAddSub=function(t){for(this.parseTerm(t);this.accept(h,$);){var e=this.current;this.parseTerm(t),t.push(f(e.value))}};var D=[&amp;quot;*&amp;quot;,&amp;quot;/&amp;quot;,&amp;quot;%&amp;quot;];function e(t,e){return Number(t)+Number(e)}function G(t,e){return t-e}function J(t,e){return t*e}function W(t,e){return t/e}function X(t,e){return t%e}function Y(t,e){return Array.isArray(t)&amp;amp;&amp;amp;Array.isArray(e)?t.concat(e):&amp;quot;&amp;quot;+t+e}function K(t,e){return t===e}function z(t,e){return t!==e}function H(t,e){return e&amp;lt;t}function Q(t,e){return t&amp;lt;e}function Z(t,e){return e&amp;lt;=t}function tt(t,e){return t&amp;lt;=e}function et(t,e){return Boolean(t&amp;amp;&amp;amp;e)}function rt(t,e){return Boolean(t||e)}function st(t,e){return a(e,t)}function nt(t){return(Math.exp(t)-Math.exp(-t))/2}function it(t){return(Math.exp(t)+Math.exp(-t))/2}function ot(t){return t===1/0?1:t===-1/0?-1:(Math.exp(t)-Math.exp(-t))/(Math.exp(t)+Math.exp(-t))}function at(t){return t===-1/0?t:Math.log(t+Math.sqrt(t*t+1))}function pt(t){return Math.log(t+Math.sqrt(t*t-1))}function ht(t){return Math.log((1+t)/(1-t))/2}function ut(t){return Math.log(t)*Math.LOG10E}function ct(t){return-t}function ft(t){return!t}function lt(t){return t&amp;lt;0?Math.ceil(t):Math.floor(t)}function vt(t){return Math.random()*(t||1)}function yt(t){return dt(t+1)}_.prototype.parseTerm=function(t){for(this.parseFactor(t);this.accept(h,D);){var e=this.current;this.parseFactor(t),t.push(f(e.value))}},_.prototype.parseFactor=function(t){var e=this.tokens.unaryOps;if(this.save(),this.accept(h,function(t){return t.value in e})){if(&amp;quot;-&amp;quot;!==this.current.value&amp;amp;&amp;amp;&amp;quot;+&amp;quot;!==this.current.value){if(this.nextToken.type===o&amp;amp;&amp;amp;&amp;quot;(&amp;quot;===this.nextToken.value)return this.restore(),void this.parseExponential(t);if(this.nextToken.type===U||this.nextToken.type===L||this.nextToken.type===n||this.nextToken.type===o&amp;amp;&amp;amp;&amp;quot;)&amp;quot;===this.nextToken.value)return this.restore(),void this.parseAtom(t)}var r=this.current;this.parseFactor(t),t.push(c(r.value))}else this.parseExponential(t)},_.prototype.parseExponential=function(t){for(this.parsePostfixExpression(t);this.accept(h,&amp;quot;^&amp;quot;);)this.parseFactor(t),t.push(f(&amp;quot;^&amp;quot;))},_.prototype.parsePostfixExpression=function(t){for(this.parseFunctionCall(t);this.accept(h,&amp;quot;!&amp;quot;);)t.push(c(&amp;quot;!&amp;quot;))},_.prototype.parseFunctionCall=function(t){var e=this.tokens.unaryOps;if(this.accept(h,function(t){return t.value in e})){var r=this.current;this.parseAtom(t),t.push(c(r.value))}else for(this.parseMemberExpression(t);this.accept(o,&amp;quot;(&amp;quot;);)if(this.accept(o,&amp;quot;)&amp;quot;))t.push(new C(E,0));else{var s=this.parseArgumentList(t);t.push(new C(E,s))}},_.prototype.parseArgumentList=function(t){for(var e=0;!this.accept(o,&amp;quot;)&amp;quot;);)for(this.parseExpression(t),++e;this.accept(L);)this.parseExpression(t),++e;return e},_.prototype.parseMemberExpression=function(t){for(this.parseAtom(t);this.accept(h,&amp;quot;.&amp;quot;)||this.accept(R,&amp;quot;[&amp;quot;);){var e=this.current;if(&amp;quot;.&amp;quot;===e.value){if(!this.allowMemberAccess)throw new Error(&#039;unexpected &amp;quot;.&amp;quot;, member access is not permitted&#039;);this.expect(j),t.push(new C(k,this.current.value))}else{if(&amp;quot;[&amp;quot;!==e.value)throw new Error(&amp;quot;unexpected symbol: &amp;quot;+e.value);if(!this.tokens.isOperatorEnabled(&amp;quot;[&amp;quot;))throw new Error(&#039;unexpected &amp;quot;[]&amp;quot;, arrays are disabled&#039;);this.parseExpression(t),this.expect(R,&amp;quot;]&amp;quot;),t.push(f(&amp;quot;[&amp;quot;))}}};var xt=4.7421875,wt=[.9999999999999971,57.15623566586292,-59.59796035547549,14.136097974741746,-.4919138160976202,3399464998481189e-20,4652362892704858e-20,-9837447530487956e-20,.0001580887032249125,-.00021026444172410488,.00021743961811521265,-.0001643181065367639,8441822398385275e-20,-26190838401581408e-21,36899182659531625e-22];function dt(t){var e,r;if(function(t){return isFinite(t)&amp;amp;&amp;amp;t===Math.round(t)}(t)){if(t&amp;lt;=0)return isFinite(t)?1/0:NaN;if(171&amp;lt;t)return 1/0;for(var s=t-2,n=t-1;1&amp;lt;s;)n*=s,s--;return 0===n&amp;amp;&amp;amp;(n=1),n}if(t&amp;lt;.5)return Math.PI/(Math.sin(Math.PI*t)*dt(1-t));if(171.35&amp;lt;=t)return 1/0;if(85&amp;lt;t){var i=t*t,o=i*t,a=o*t,p=a*t;return Math.sqrt(2*Math.PI/t)*Math.pow(t/Math.E,t)*(1+1/(12*t)+1/(288*i)-139/(51840*o)-571/(2488320*a)+163879/(209018880*p)+5246819/(75246796800*p*t))}--t,r=wt[0];for(var h=1;h&amp;lt;wt.length;++h)r+=wt[h]/(t+h);return e=t+xt+.5,Math.sqrt(2*Math.PI)*Math.pow(e,t+.5)*Math.exp(-e)*r}function gt(t){return Array.isArray(t)?t.length:String(t).length}function Mt(){for(var t=0,e=0,r=0;r&amp;lt;arguments.length;r++){var s,n=Math.abs(arguments[r]);e&amp;lt;n?(t=t*(s=e/n)*s+1,e=n):t+=0&amp;lt;n?(s=n/e)*s:n}return e===1/0?1/0:e*Math.sqrt(t)}function Et(t,e,r){return t?e:r}function mt(t,e){return void 0===e||0==+e?Math.round(t):(t=+t,e=-+e,isNaN(t)||&amp;quot;number&amp;quot;!=typeof e||e%1!=0?NaN:(t=t.toString().split(&amp;quot;e&amp;quot;),+((t=(t=Math.round(+(t[0]+&amp;quot;e&amp;quot;+(t[1]?+t[1]-e:-e)))).toString().split(&amp;quot;e&amp;quot;))[0]+&amp;quot;e&amp;quot;+(t[1]?+t[1]+e:e))))}function At(t,e,r){return r&amp;amp;&amp;amp;(r[t]=e),e}function bt(t,e){return t[0|e]}function kt(t){return 1===arguments.length&amp;amp;&amp;amp;Array.isArray(t)?Math.max.apply(Math,t):Math.max.apply(Math,arguments)}function Ot(t){return 1===arguments.length&amp;amp;&amp;amp;Array.isArray(t)?Math.min.apply(Math,t):Math.min.apply(Math,arguments)}function Tt(r,t){if(&amp;quot;function&amp;quot;!=typeof r)throw new Error(&amp;quot;First argument to map is not a function&amp;quot;);if(!Array.isArray(t))throw new Error(&amp;quot;Second argument to map is not an array&amp;quot;);return t.map(function(t,e){return r(t,e)})}function Ct(s,t,e){if(&amp;quot;function&amp;quot;!=typeof s)throw new Error(&amp;quot;First argument to fold is not a function&amp;quot;);if(!Array.isArray(e))throw new Error(&amp;quot;Second argument to fold is not an array&amp;quot;);return e.reduce(function(t,e,r){return s(t,e,r)},t)}function Nt(r,t){if(&amp;quot;function&amp;quot;!=typeof r)throw new Error(&amp;quot;First argument to filter is not a function&amp;quot;);if(!Array.isArray(t))throw new Error(&amp;quot;Second argument to filter is not an array&amp;quot;);return t.filter(function(t,e){return r(t,e)})}function St(t,e){if(!Array.isArray(e)&amp;amp;&amp;amp;&amp;quot;string&amp;quot;!=typeof e)throw new Error(&amp;quot;Second argument to indexOf is not a string or array&amp;quot;);return e.indexOf(t)}function It(t,e){if(!Array.isArray(e))throw new Error(&amp;quot;Second argument to join is not an array&amp;quot;);return e.join(t)}function Pt(t){return(0&amp;lt;t)-(t&amp;lt;0)||+t}var Ft=1/3;function Rt(t){return t&amp;lt;0?-Math.pow(-t,Ft):Math.pow(t,Ft)}function Lt(t){return Math.exp(t)-1}function jt(t){return Math.log(1+t)}function Ut(t){return Math.log(t)/Math.LN2}function qt(t){this.options=t||{},this.unaryOps={sin:Math.sin,cos:Math.cos,tan:Math.tan,asin:Math.asin,acos:Math.acos,atan:Math.atan,sinh:Math.sinh||nt,cosh:Math.cosh||it,tanh:Math.tanh||ot,asinh:Math.asinh||at,acosh:Math.acosh||pt,atanh:Math.atanh||ht,sqrt:Math.sqrt,cbrt:Math.cbrt||Rt,log:Math.log,log2:Math.log2||Ut,ln:Math.log,lg:Math.log10||ut,log10:Math.log10||ut,expm1:Math.expm1||Lt,log1p:Math.log1p||jt,abs:Math.abs,ceil:Math.ceil,floor:Math.floor,round:Math.round,trunc:Math.trunc||lt,&amp;quot;-&amp;quot;:ct,&amp;quot;+&amp;quot;:Number,exp:Math.exp,not:ft,length:gt,&amp;quot;!&amp;quot;:yt,sign:Math.sign||Pt},this.binaryOps={&amp;quot;+&amp;quot;:e,&amp;quot;-&amp;quot;:G,&amp;quot;*&amp;quot;:J,&amp;quot;/&amp;quot;:W,&amp;quot;%&amp;quot;:X,&amp;quot;^&amp;quot;:Math.pow,&amp;quot;||&amp;quot;:Y,&amp;quot;==&amp;quot;:K,&amp;quot;!=&amp;quot;:z,&amp;quot;&amp;gt;&amp;quot;:H,&amp;quot;&amp;lt;&amp;quot;:Q,&amp;quot;&amp;gt;=&amp;quot;:Z,&amp;quot;&amp;lt;=&amp;quot;:tt,and:et,or:rt,in:st,&amp;quot;=&amp;quot;:At,&amp;quot;[&amp;quot;:bt},this.ternaryOps={&amp;quot;?&amp;quot;:Et},this.functions={random:vt,fac:yt,min:Ot,max:kt,hypot:Math.hypot||Mt,pyt:Math.hypot||Mt,pow:Math.pow,atan2:Math.atan2,if:Et,gamma:dt,roundTo:mt,map:Tt,fold:Ct,filter:Nt,indexOf:St,join:It},this.consts={E:Math.E,PI:Math.PI,true:!0,false:!1}}qt.prototype.parse=function(t){var e=[],r=new _(this,new B(this,t),{allowMemberAccess:this.options.allowMemberAccess});return r.parseExpression(e),r.expect(n,&amp;quot;EOF&amp;quot;),new s(e,this)},qt.prototype.evaluate=function(t,e){return this.parse(t).evaluate(e)};var Bt=new qt;qt.parse=function(t){return Bt.parse(t)},qt.evaluate=function(t,e){return Bt.parse(t).evaluate(e)};var Vt={&amp;quot;+&amp;quot;:&amp;quot;add&amp;quot;,&amp;quot;-&amp;quot;:&amp;quot;subtract&amp;quot;,&amp;quot;*&amp;quot;:&amp;quot;multiply&amp;quot;,&amp;quot;/&amp;quot;:&amp;quot;divide&amp;quot;,&amp;quot;%&amp;quot;:&amp;quot;remainder&amp;quot;,&amp;quot;^&amp;quot;:&amp;quot;power&amp;quot;,&amp;quot;!&amp;quot;:&amp;quot;factorial&amp;quot;,&amp;quot;&amp;lt;&amp;quot;:&amp;quot;comparison&amp;quot;,&amp;quot;&amp;gt;&amp;quot;:&amp;quot;comparison&amp;quot;,&amp;quot;&amp;lt;=&amp;quot;:&amp;quot;comparison&amp;quot;,&amp;quot;&amp;gt;=&amp;quot;:&amp;quot;comparison&amp;quot;,&amp;quot;==&amp;quot;:&amp;quot;comparison&amp;quot;,&amp;quot;!=&amp;quot;:&amp;quot;comparison&amp;quot;,&amp;quot;||&amp;quot;:&amp;quot;concatenate&amp;quot;,and:&amp;quot;logical&amp;quot;,or:&amp;quot;logical&amp;quot;,not:&amp;quot;logical&amp;quot;,&amp;quot;?&amp;quot;:&amp;quot;conditional&amp;quot;,&amp;quot;:&amp;quot;:&amp;quot;conditional&amp;quot;,&amp;quot;=&amp;quot;:&amp;quot;assignment&amp;quot;,&amp;quot;[&amp;quot;:&amp;quot;array&amp;quot;,&amp;quot;()=&amp;quot;:&amp;quot;fndef&amp;quot;};qt.prototype.isOperatorEnabled=function(t){var e=function(t){return Vt.hasOwnProperty(t)?Vt[t]:t}(t),r=this.options.operators||{};return!(e in r&amp;amp;&amp;amp;!r[e])};var _t={Parser:qt,Expression:s};t.Expression=s,t.Parser=qt,t.default=_t,Object.defineProperty(t,&amp;quot;__esModule&amp;quot;,{value:!0})});&lt;br /&gt;
&lt;br /&gt;
/**&lt;br /&gt;
 * 数式に数学関数以外の関数が含まれている場合のエラー&lt;br /&gt;
 */&lt;br /&gt;
class MathFunctionError extends Error {&lt;br /&gt;
    /**&lt;br /&gt;
     * @param {string} message - エラーメッセージ&lt;br /&gt;
     */&lt;br /&gt;
    constructor(message) {&lt;br /&gt;
        super(message || &amp;quot;数学関数以外の関数が含まれています&amp;quot;);&lt;br /&gt;
        this.name = &amp;quot;MathFunctionError&amp;quot;;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
var sel = document.selection;&lt;br /&gt;
&lt;br /&gt;
doMultiEdit(main);&lt;br /&gt;
&lt;br /&gt;
function main() {&lt;br /&gt;
    var isEmpty = sel.IsEmpty;&lt;br /&gt;
    var activePointY = sel.GetActivePointY(mePosLogical);&lt;br /&gt;
    if (isEmpty) {&lt;br /&gt;
        var exp = document.GetLine(activePointY);&lt;br /&gt;
    } else {&lt;br /&gt;
        var exp = sel.Text;&lt;br /&gt;
    }&lt;br /&gt;
    // 空白行の場合は何もしない&lt;br /&gt;
    if (exp.match(/^\s*$/)) {&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    try {&lt;br /&gt;
        var exp_replaced = replaceExpression(exp);&lt;br /&gt;
        const Parser = exprEval.Parser;&lt;br /&gt;
        let r = Parser.evaluate(exp_replaced);&lt;br /&gt;
        try {&lt;br /&gt;
            r = r.toFixed(15);&lt;br /&gt;
            const intPart = r.match(/([0-9]+)\./);&lt;br /&gt;
            const fractionalPart = r.match(/\.([0-9]+$)/);&lt;br /&gt;
            if (intPart &amp;amp;&amp;amp; fractionalPart) {&lt;br /&gt;
                // 小数点以下は12桁で切る&lt;br /&gt;
                const f = fractionalPart[1].slice(0,12).replace(/[0]+$/, &amp;quot;&amp;quot;)&lt;br /&gt;
                r = intPart[1] + (f ? `.${f}` : &amp;quot;&amp;quot;);&lt;br /&gt;
            }&lt;br /&gt;
        } catch(e) {}&lt;br /&gt;
        var result_str = String(r);&lt;br /&gt;
    } catch (e) {&lt;br /&gt;
        if (e instanceof MathFunctionError) {&lt;br /&gt;
            alert(e.message)&lt;br /&gt;
        } else {&lt;br /&gt;
            alert(e + &amp;quot;\n入力: &amp;quot; + exp + &amp;quot;\n入力変換後: &amp;quot; + exp_replaced)&lt;br /&gt;
        }&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // 小数点以上のみカンマ区切り&lt;br /&gt;
    if (COMMA) {&lt;br /&gt;
        var result_parts = result_str.split(&amp;quot;.&amp;quot;);&lt;br /&gt;
        result_parts[0] = result_parts[0].replace(/(\d)(?=(\d{3})+$)/g, &amp;quot;$1,&amp;quot;);&lt;br /&gt;
        if (result_parts.length &amp;gt; 1) {&lt;br /&gt;
            result_str = result_parts.join(&amp;quot;.&amp;quot;);&lt;br /&gt;
        } else {&lt;br /&gt;
            result_str = result_parts[0];&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // 計算行が = 始まりの場合、= を削除する&lt;br /&gt;
    if (isEmpty &amp;amp;&amp;amp; exp.match(/^= /)) {&lt;br /&gt;
        sel.SetActivePoint(mePosLogical, 1, activePointY, false);&lt;br /&gt;
        sel.EndOfLine(true, mePosLogical);&lt;br /&gt;
        sel.Text = sel.Text.replace(/^= /, &amp;quot;&amp;quot;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // 既存の計算結果を上書きするか判定&lt;br /&gt;
    var pattern = /^=\s*(-?[0-9.,]|true|false)*\s*$/;&lt;br /&gt;
    var nextLine = document.GetLine(activePointY + 1);&lt;br /&gt;
    if (isEmpty &amp;amp;&amp;amp; nextLine.match(pattern)) {&lt;br /&gt;
        sel.SetActivePoint(mePosLogical, 1, activePointY + 1, false);&lt;br /&gt;
        sel.EndOfLine(true, mePosLogical);&lt;br /&gt;
        sel.Text = &#039;= &#039; + result_str&lt;br /&gt;
    } else if (isEmpty) {&lt;br /&gt;
        sel.LineOpen();&lt;br /&gt;
        sel.Text = &#039;= &#039; + result_str&lt;br /&gt;
    } else {&lt;br /&gt;
        sel.CharRight();&lt;br /&gt;
        const inputExp = exp.replace(/((?!;)\n)/mg, &amp;quot;; &amp;quot;).replace(/;\s*;/g, &amp;quot;;&amp;quot;).replace(/　/g, &amp;quot; &amp;quot;).replace(/;\s*$/, &amp;quot;&amp;quot;).trim();&lt;br /&gt;
        if (exp.slice(-1) === &amp;quot;\n&amp;quot;) {&lt;br /&gt;
            sel.Text = inputExp + &#039; = &#039; + result_str + &#039;\n&#039;;&lt;br /&gt;
        } else {&lt;br /&gt;
            sel.Text = &#039;\n&#039; + inputExp + &#039; = &#039; + result_str + &#039;\n&#039;;&lt;br /&gt;
        }&lt;br /&gt;
        sel.CharLeft();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (COPY) {&lt;br /&gt;
        ClipboardData.SetData(result_str);&lt;br /&gt;
        Status = &amp;quot;計算結果 &amp;quot; + result_str + &amp;quot; をコピーしました&amp;quot;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/**&lt;br /&gt;
 * 数式を置換する&lt;br /&gt;
 */&lt;br /&gt;
function replaceExpression(exp) {&lt;br /&gt;
    const exp_replaced = exp.trim()&lt;br /&gt;
        // 先頭の = を削除&lt;br /&gt;
        .replace(/^= /, &amp;quot;&amp;quot;)&lt;br /&gt;
        // 改行を ; に置換&lt;br /&gt;
        .replace(/(?&amp;lt;!;)\n/mg, &amp;quot;;&amp;quot;)&lt;br /&gt;
        // 非ASCII文字を削除&lt;br /&gt;
        .replace(/[^\x20-\x7e]/g, &amp;quot; &amp;quot;)&lt;br /&gt;
        // 全角スペースを半角スペースに変換&lt;br /&gt;
        .replace(/　/g, &amp;quot; &amp;quot;)&lt;br /&gt;
        // 数字内のカンマを取り除く&lt;br /&gt;
        .replace(/(\d),(?! )/g, &amp;quot;$1&amp;quot;)&lt;br /&gt;
        // 予約語を退避&lt;br /&gt;
        .replace(/(?:^|\s)(PI|true|false)(?:$|\s)/g, &amp;quot; %%%$1%%% &amp;quot;)&lt;br /&gt;
        // 指数形式を退避&lt;br /&gt;
        .replace(/([0-9]+(?:\.[0-9]+)?[eE][0-9]+)/g, &amp;quot; %%%$1%%% &amp;quot;)&lt;br /&gt;
        // 変数形式を退避&lt;br /&gt;
        .replace(/\$([a-zA-Z][a-zA-Z0-9]*)/g, &amp;quot; %%%$1%%% &amp;quot;)&lt;br /&gt;
        // `/hour` などの文字列はスラッシュごと削除&lt;br /&gt;
        .replace(/\/\s*[a-zA-Z\u0080-\uFFFF]/g, &amp;quot; &amp;quot;)&lt;br /&gt;
        // 不要な括弧書きを削除&lt;br /&gt;
        .replace(/\[[^\]]*\]/g, &amp;quot; &amp;quot;)&lt;br /&gt;
        // その他の文字を削除。退避文字と関数の可能性がある abc( 形式は削除しない&lt;br /&gt;
        .replace(/[^0-9.+\-*/%^=()&amp;lt;&amp;gt;!;\s]+(?![a-zA-Z0-9]*%%% |[a-z0-9]*\s*\([^)])/g, &amp;quot; &amp;quot;)&lt;br /&gt;
        // 退避文字を復元&lt;br /&gt;
        .replace(/ %%%([a-zA-Z][a-zA-Z0-9]*)%%% /g, &amp;quot;$1&amp;quot;)&lt;br /&gt;
    // 数式が数学関数以外を含む場合はエラー&lt;br /&gt;
    if (containsNotMathFunction(exp_replaced)) {&lt;br /&gt;
        throw new MathFunctionError(&amp;quot;数式に数学関数以外の関数が含まれています\n入力: &amp;quot; + exp + &amp;quot;\n入力変換後: &amp;quot; + exp_replaced);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return exp_replaced;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/**&lt;br /&gt;
 * 数学関数以外を含むかどうかを判定する&lt;br /&gt;
 * max(x, y, ...) のような複数の引数を受け取る関数もOK扱いにはするが、動作は保証しないものとする&lt;br /&gt;
 */&lt;br /&gt;
function containsNotMathFunction(exp) {&lt;br /&gt;
    var functions = [&lt;br /&gt;
        &amp;quot;abs&amp;quot;,&lt;br /&gt;
        &amp;quot;acos&amp;quot;,&lt;br /&gt;
        &amp;quot;acosh&amp;quot;,&lt;br /&gt;
        &amp;quot;asin&amp;quot;,&lt;br /&gt;
        &amp;quot;asinh&amp;quot;,&lt;br /&gt;
        &amp;quot;atan&amp;quot;,&lt;br /&gt;
        &amp;quot;atanh&amp;quot;,&lt;br /&gt;
        &amp;quot;cbrt&amp;quot;,&lt;br /&gt;
        &amp;quot;ceil&amp;quot;,&lt;br /&gt;
        &amp;quot;cos&amp;quot;,&lt;br /&gt;
        &amp;quot;cosh&amp;quot;,&lt;br /&gt;
        &amp;quot;exp&amp;quot;,&lt;br /&gt;
        &amp;quot;expm1&amp;quot;,&lt;br /&gt;
        &amp;quot;floor&amp;quot;,&lt;br /&gt;
        &amp;quot;length&amp;quot;,&lt;br /&gt;
        &amp;quot;ln&amp;quot;,&lt;br /&gt;
        &amp;quot;log&amp;quot;,&lt;br /&gt;
        &amp;quot;log10&amp;quot;,&lt;br /&gt;
        &amp;quot;log2&amp;quot;,&lt;br /&gt;
        &amp;quot;log1p&amp;quot;,&lt;br /&gt;
        &amp;quot;not&amp;quot;,&lt;br /&gt;
        &amp;quot;round&amp;quot;,&lt;br /&gt;
        &amp;quot;sign&amp;quot;,&lt;br /&gt;
        &amp;quot;sin&amp;quot;,&lt;br /&gt;
        &amp;quot;sinh&amp;quot;,&lt;br /&gt;
        &amp;quot;sqrt&amp;quot;,&lt;br /&gt;
        &amp;quot;tan&amp;quot;,&lt;br /&gt;
        &amp;quot;tanh&amp;quot;,&lt;br /&gt;
        &amp;quot;trunc&amp;quot;,&lt;br /&gt;
        &amp;quot;random&amp;quot;,&lt;br /&gt;
        &amp;quot;fac&amp;quot;,&lt;br /&gt;
        &amp;quot;min&amp;quot;,&lt;br /&gt;
        &amp;quot;max&amp;quot;,&lt;br /&gt;
        &amp;quot;hypot&amp;quot;,&lt;br /&gt;
        &amp;quot;pyt&amp;quot;,&lt;br /&gt;
        &amp;quot;pow&amp;quot;,&lt;br /&gt;
        &amp;quot;atan2&amp;quot;,&lt;br /&gt;
        &amp;quot;roundTo&amp;quot;,&lt;br /&gt;
        &amp;quot;map&amp;quot;,&lt;br /&gt;
        &amp;quot;fold&amp;quot;,&lt;br /&gt;
        &amp;quot;filter&amp;quot;,&lt;br /&gt;
        &amp;quot;indexOf&amp;quot;,&lt;br /&gt;
        &amp;quot;join&amp;quot;,&lt;br /&gt;
        &amp;quot;if&amp;quot;,&lt;br /&gt;
    ]&lt;br /&gt;
    // 検査対象の文字列を取得&lt;br /&gt;
    var match = exp.match(/([a-z][a-z0-9]+)\s*\(/g);&lt;br /&gt;
    if (!match) {&lt;br /&gt;
        return false;&lt;br /&gt;
    }&lt;br /&gt;
    // 検査対象の文字列が数学関数に含まれていない場合は true&lt;br /&gt;
    for (var i = 0; i &amp;lt; match.length; i++) {&lt;br /&gt;
        var func = match[i].replace(/\s*\(/, &amp;quot;&amp;quot;);&lt;br /&gt;
        if (functions.indexOf(func) == -1) {&lt;br /&gt;
            return true;&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
    // 検査対象の文字列が数学関数のみの場合は false&lt;br /&gt;
    return false;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== サードパーティライブラリ ==&lt;br /&gt;
&lt;br /&gt;
[https://github.com/silentmatt/expr-eval/ expr-eval]&lt;/div&gt;</summary>
		<author><name>Yuko</name></author>
	</entry>
	<entry>
		<id>https://www.haijin-boys.com/wiki/index.php?title=Mery_%E9%9B%BB%E5%8D%93&amp;diff=8849</id>
		<title>Mery 電卓</title>
		<link rel="alternate" type="text/html" href="https://www.haijin-boys.com/wiki/index.php?title=Mery_%E9%9B%BB%E5%8D%93&amp;diff=8849"/>
		<updated>2025-03-18T01:13:04Z</updated>

		<summary type="html">&lt;p&gt;Yuko: /* ソースコード */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== 概要 ==&lt;br /&gt;
&lt;br /&gt;
「Mery 電卓」は、Meryエディタ上で数式を計算するためのマクロです。標準的な数学関数をサポートしており、カーソル位置の行または選択したテキストを数式として解釈し、結果を表示します。計算結果は自動的にクリップボードにコピーされ、カンマ区切りの数値として表示されます。&lt;br /&gt;
&lt;br /&gt;
本マクロをショートカットキーに設定し、いつでも起動できるようにしておくと日常シーンで何かと便利です。作者は「Ctrl + Alt + Enter」キーに割り当てて利用しています。&lt;br /&gt;
&lt;br /&gt;
== 注意事項 ==&lt;br /&gt;
&lt;br /&gt;
* Mery Ver 3.7.12 で動作確認しています。&lt;br /&gt;
&lt;br /&gt;
== 使い方 ==&lt;br /&gt;
&lt;br /&gt;
1. &#039;&#039;&#039;行全体を計算する場合&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* 計算したい数式が書かれた行にカーソルを置きます。&lt;br /&gt;
* マクロを実行すると、次の行に「= 計算結果」が表示されます。&lt;br /&gt;
* 既に結果行がある場合は、その行が更新されます。&lt;br /&gt;
&lt;br /&gt;
2. &#039;&#039;&#039;選択範囲を計算する場合&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* 計算したい数式部分を選択します。&lt;br /&gt;
* マクロを実行すると、選択範囲の後ろに「計算式 = 計算結果」が挿入されます。&lt;br /&gt;
&lt;br /&gt;
3. &#039;&#039;&#039;サポートされている演算子&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* 四則演算（+, -, *, /）&lt;br /&gt;
* べき乗（^）&lt;br /&gt;
* 剰余（%）&lt;br /&gt;
* 比較演算子（&amp;lt;, &amp;gt;, &amp;lt;=, &amp;gt;=, ==, !=）&lt;br /&gt;
* 論理演算子（and, or, not）&lt;br /&gt;
&lt;br /&gt;
4. &#039;&#039;&#039;サポートされている関数&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* 三角関数（sin, cos, tan, asin, acos, atan）&lt;br /&gt;
* 双曲線関数（sinh, cosh, tanh, asinh, acosh, atanh）&lt;br /&gt;
* 対数関数（log, log10, log2, ln）&lt;br /&gt;
* その他の数学関数（sqrt, cbrt, abs, ceil, floor, round, trunc など）&lt;br /&gt;
&lt;br /&gt;
5. &#039;&#039;&#039;特別な機能&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* カンマ区切り表示: 計算結果は読みやすいようにカンマ区切りで表示されます。変数「COMMA」を設定することで調整可能です。&lt;br /&gt;
* 自動コピー: 計算結果は自動的にクリップボードにコピーされます。変数「COPY」を設定することで調整可能です。&lt;br /&gt;
* 計算結果の再利用: 行頭の「= 」は入力から無視されるため、計算結果の位置からスムーズに次の数式として利用できます。&lt;br /&gt;
* 変数の利用: 複数行、または「;」で区切ることで、変数を使った数式が利用できます。変数形式は「$[a-zA-Z][a-zA-Z0-9]*」に対応しています。例えば「$a = 2; $a * 3」として実行すると、結果は「6」と出力されます。&lt;br /&gt;
* メモ書きから計算: 数字や関数以外の文字はできるだけ除外した上で計算をします。例えば「月100円 * 12ヶ月」のようなメモ書きに対して実行すると「1,200」と出力されます。 [ と ] で囲まれた文字列は丸ごと除外するため、コメントに最適です。&lt;br /&gt;
&lt;br /&gt;
== ソースコード ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
#title = &amp;quot;Mery 電卓&amp;quot;&lt;br /&gt;
#include &amp;quot;Common.js&amp;quot;&lt;br /&gt;
#language = &amp;quot;v8&amp;quot;&lt;br /&gt;
Redraw = false;&lt;br /&gt;
BeginUndoGroup();&lt;br /&gt;
&lt;br /&gt;
// 結果をクリップボードにコピー&lt;br /&gt;
var COPY = true;&lt;br /&gt;
// 出力結果をカンマ区切りにする&lt;br /&gt;
var COMMA = true;&lt;br /&gt;
&lt;br /&gt;
// expr-eval ライブラリ&lt;br /&gt;
// from: https://cdn.jsdelivr.net/npm/expr-eval@2.0.2/dist/bundle.min.js&lt;br /&gt;
!function(t,e){&amp;quot;object&amp;quot;==typeof exports&amp;amp;&amp;amp;&amp;quot;undefined&amp;quot;!=typeof module?e(exports):&amp;quot;function&amp;quot;==typeof define&amp;amp;&amp;amp;define.amd?define([&amp;quot;exports&amp;quot;],e):e((t=t||self).exprEval={})}(this,function(t){&amp;quot;use strict&amp;quot;;var y=&amp;quot;INUMBER&amp;quot;,x=&amp;quot;IOP1&amp;quot;,w=&amp;quot;IOP2&amp;quot;,d=&amp;quot;IOP3&amp;quot;,g=&amp;quot;IVAR&amp;quot;,M=&amp;quot;IVARNAME&amp;quot;,E=&amp;quot;IFUNCALL&amp;quot;,m=&amp;quot;IFUNDEF&amp;quot;,A=&amp;quot;IEXPR&amp;quot;,b=&amp;quot;IEXPREVAL&amp;quot;,k=&amp;quot;IMEMBER&amp;quot;,O=&amp;quot;IENDSTATEMENT&amp;quot;,T=&amp;quot;IARRAY&amp;quot;;function C(t,e){this.type=t,this.value=null!=e?e:0}function c(t){return new C(x,t)}function f(t){return new C(w,t)}function l(t){return new C(d,t)}function N(t,i,o){var e,r,s,n,a,p,h=[];if(I(t))return P(t,o);for(var u=t.length,c=0;c&amp;lt;u;c++){var f=t[c],l=f.type;if(l===y||l===M)h.push(f.value);else if(l===w)r=h.pop(),e=h.pop(),&amp;quot;and&amp;quot;===f.value?h.push(!!e&amp;amp;&amp;amp;!!N(r,i,o)):&amp;quot;or&amp;quot;===f.value?h.push(!!e||!!N(r,i,o)):&amp;quot;=&amp;quot;===f.value?(n=i.binaryOps[f.value],h.push(n(e,N(r,i,o),o))):(n=i.binaryOps[f.value],h.push(n(P(e,o),P(r,o))));else if(l===d)s=h.pop(),r=h.pop(),e=h.pop(),&amp;quot;?&amp;quot;===f.value?h.push(N(e?r:s,i,o)):(n=i.ternaryOps[f.value],h.push(n(P(e,o),P(r,o),P(s,o))));else if(l===g)if(f.value in i.functions)h.push(i.functions[f.value]);else if(f.value in i.unaryOps&amp;amp;&amp;amp;i.parser.isOperatorEnabled(f.value))h.push(i.unaryOps[f.value]);else{var v=o[f.value];if(void 0===v)throw new Error(&amp;quot;undefined variable: &amp;quot;+f.value);h.push(v)}else if(l===x)e=h.pop(),n=i.unaryOps[f.value],h.push(n(P(e,o)));else if(l===E){for(p=f.value,a=[];0&amp;lt;p--;)a.unshift(P(h.pop(),o));if(!(n=h.pop()).apply||!n.call)throw new Error(n+&amp;quot; is not a function&amp;quot;);h.push(n.apply(void 0,a))}else if(l===m)h.push(function(){for(var s=h.pop(),n=[],t=f.value;0&amp;lt;t--;)n.unshift(h.pop());function e(){for(var t=Object.assign({},o),e=0,r=n.length;e&amp;lt;r;e++)t[n[e]]=arguments[e];return N(s,i,t)}var r=h.pop();return Object.defineProperty(e,&amp;quot;name&amp;quot;,{value:r,writable:!1}),o[r]=e}());else if(l===A)h.push(S(f,i));else if(l===b)h.push(f);else if(l===k)e=h.pop(),h.push(e[f.value]);else if(l===O)h.pop();else{if(l!==T)throw new Error(&amp;quot;invalid Expression&amp;quot;);for(p=f.value,a=[];0&amp;lt;p--;)a.unshift(h.pop());h.push(a)}}if(1&amp;lt;h.length)throw new Error(&amp;quot;invalid Expression (parity)&amp;quot;);return 0===h[0]?0:P(h[0],o)}function S(e,r){return I(e)?e:{type:b,value:function(t){return N(e.value,r,t)}}}function I(t){return t&amp;amp;&amp;amp;t.type===b}function P(t,e){return I(t)?t.value(e):t}function v(t,e){for(var r,s,n,i,o,a,p=[],h=0;h&amp;lt;t.length;h++){var u=t[h],c=u.type;if(c===y)&amp;quot;number&amp;quot;==typeof u.value&amp;amp;&amp;amp;u.value&amp;lt;0?p.push(&amp;quot;(&amp;quot;+u.value+&amp;quot;)&amp;quot;):Array.isArray(u.value)?p.push(&amp;quot;[&amp;quot;+u.value.map(F).join(&amp;quot;, &amp;quot;)+&amp;quot;]&amp;quot;):p.push(F(u.value));else if(c===w)s=p.pop(),r=p.pop(),i=u.value,e?&amp;quot;^&amp;quot;===i?p.push(&amp;quot;Math.pow(&amp;quot;+r+&amp;quot;, &amp;quot;+s+&amp;quot;)&amp;quot;):&amp;quot;and&amp;quot;===i?p.push(&amp;quot;(!!&amp;quot;+r+&amp;quot; &amp;amp;&amp;amp; !!&amp;quot;+s+&amp;quot;)&amp;quot;):&amp;quot;or&amp;quot;===i?p.push(&amp;quot;(!!&amp;quot;+r+&amp;quot; || !!&amp;quot;+s+&amp;quot;)&amp;quot;):&amp;quot;||&amp;quot;===i?p.push(&amp;quot;(function(a,b){ return Array.isArray(a) &amp;amp;&amp;amp; Array.isArray(b) ? a.concat(b) : String(a) + String(b); }((&amp;quot;+r+&amp;quot;),(&amp;quot;+s+&amp;quot;)))&amp;quot;):&amp;quot;==&amp;quot;===i?p.push(&amp;quot;(&amp;quot;+r+&amp;quot; === &amp;quot;+s+&amp;quot;)&amp;quot;):&amp;quot;!=&amp;quot;===i?p.push(&amp;quot;(&amp;quot;+r+&amp;quot; !== &amp;quot;+s+&amp;quot;)&amp;quot;):&amp;quot;[&amp;quot;===i?p.push(r+&amp;quot;[(&amp;quot;+s+&amp;quot;) | 0]&amp;quot;):p.push(&amp;quot;(&amp;quot;+r+&amp;quot; &amp;quot;+i+&amp;quot; &amp;quot;+s+&amp;quot;)&amp;quot;):&amp;quot;[&amp;quot;===i?p.push(r+&amp;quot;[&amp;quot;+s+&amp;quot;]&amp;quot;):p.push(&amp;quot;(&amp;quot;+r+&amp;quot; &amp;quot;+i+&amp;quot; &amp;quot;+s+&amp;quot;)&amp;quot;);else if(c===d){if(n=p.pop(),s=p.pop(),r=p.pop(),&amp;quot;?&amp;quot;!==(i=u.value))throw new Error(&amp;quot;invalid Expression&amp;quot;);p.push(&amp;quot;(&amp;quot;+r+&amp;quot; ? &amp;quot;+s+&amp;quot; : &amp;quot;+n+&amp;quot;)&amp;quot;)}else if(c===g||c===M)p.push(u.value);else if(c===x)r=p.pop(),&amp;quot;-&amp;quot;===(i=u.value)||&amp;quot;+&amp;quot;===i?p.push(&amp;quot;(&amp;quot;+i+r+&amp;quot;)&amp;quot;):e?&amp;quot;not&amp;quot;===i?p.push(&amp;quot;(!&amp;quot;+r+&amp;quot;)&amp;quot;):&amp;quot;!&amp;quot;===i?p.push(&amp;quot;fac(&amp;quot;+r+&amp;quot;)&amp;quot;):p.push(i+&amp;quot;(&amp;quot;+r+&amp;quot;)&amp;quot;):&amp;quot;!&amp;quot;===i?p.push(&amp;quot;(&amp;quot;+r+&amp;quot;!)&amp;quot;):p.push(&amp;quot;(&amp;quot;+i+&amp;quot; &amp;quot;+r+&amp;quot;)&amp;quot;);else if(c===E){for(a=u.value,o=[];0&amp;lt;a--;)o.unshift(p.pop());i=p.pop(),p.push(i+&amp;quot;(&amp;quot;+o.join(&amp;quot;, &amp;quot;)+&amp;quot;)&amp;quot;)}else if(c===m){for(s=p.pop(),a=u.value,o=[];0&amp;lt;a--;)o.unshift(p.pop());r=p.pop(),e?p.push(&amp;quot;(&amp;quot;+r+&amp;quot; = function(&amp;quot;+o.join(&amp;quot;, &amp;quot;)+&amp;quot;) { return &amp;quot;+s+&amp;quot; })&amp;quot;):p.push(&amp;quot;(&amp;quot;+r+&amp;quot;(&amp;quot;+o.join(&amp;quot;, &amp;quot;)+&amp;quot;) = &amp;quot;+s+&amp;quot;)&amp;quot;)}else if(c===k)r=p.pop(),p.push(r+&amp;quot;.&amp;quot;+u.value);else if(c===T){for(a=u.value,o=[];0&amp;lt;a--;)o.unshift(p.pop());p.push(&amp;quot;[&amp;quot;+o.join(&amp;quot;, &amp;quot;)+&amp;quot;]&amp;quot;)}else if(c===A)p.push(&amp;quot;(&amp;quot;+v(u.value,e)+&amp;quot;)&amp;quot;);else if(c!==O)throw new Error(&amp;quot;invalid Expression&amp;quot;)}return 1&amp;lt;p.length&amp;amp;&amp;amp;(p=e?[p.join(&amp;quot;,&amp;quot;)]:[p.join(&amp;quot;;&amp;quot;)]),String(p[0])}function F(t){return&amp;quot;string&amp;quot;==typeof t?JSON.stringify(t).replace(/\u2028/g,&amp;quot;\\u2028&amp;quot;).replace(/\u2029/g,&amp;quot;\\u2029&amp;quot;):t}function a(t,e){for(var r=0;r&amp;lt;t.length;r++)if(t[r]===e)return!0;return!1}function p(t,e,r){for(var s=!!(r=r||{}).withMembers,n=null,i=0;i&amp;lt;t.length;i++){var o=t[i];o.type===g||o.type===M?s||a(e,o.value)?n=(null!==n&amp;amp;&amp;amp;(a(e,n)||e.push(n)),o.value):e.push(o.value):o.type===k&amp;amp;&amp;amp;s&amp;amp;&amp;amp;null!==n?n+=&amp;quot;.&amp;quot;+o.value:o.type===A?p(o.value,e,r):null!==n&amp;amp;&amp;amp;(a(e,n)||e.push(n),n=null)}null===n||a(e,n)||e.push(n)}function s(t,e){this.tokens=t,this.parser=e,this.unaryOps=e.unaryOps,this.binaryOps=e.binaryOps,this.ternaryOps=e.ternaryOps,this.functions=e.functions}C.prototype.toString=function(){switch(this.type){case y:case x:case w:case d:case g:case M:case O:return this.value;case E:return&amp;quot;CALL &amp;quot;+this.value;case m:return&amp;quot;DEF &amp;quot;+this.value;case T:return&amp;quot;ARRAY &amp;quot;+this.value;case k:return&amp;quot;.&amp;quot;+this.value;default:return&amp;quot;Invalid Instruction&amp;quot;}},s.prototype.simplify=function(t){return t=t||{},new s(function t(e,r,s,n,i){for(var o,a,p,h,u=[],c=[],f=0;f&amp;lt;e.length;f++){var l=e[f],v=l.type;if(v===y||v===M)Array.isArray(l.value)?u.push.apply(u,t(l.value.map(function(t){return new C(y,t)}).concat(new C(T,l.value.length)),r,s,n,i)):u.push(l);else if(v===g&amp;amp;&amp;amp;i.hasOwnProperty(l.value))l=new C(y,i[l.value]),u.push(l);else if(v===w&amp;amp;&amp;amp;1&amp;lt;u.length)a=u.pop(),o=u.pop(),h=s[l.value],l=new C(y,h(o.value,a.value)),u.push(l);else if(v===d&amp;amp;&amp;amp;2&amp;lt;u.length)p=u.pop(),a=u.pop(),o=u.pop(),&amp;quot;?&amp;quot;===l.value?u.push(o.value?a.value:p.value):(h=n[l.value],l=new C(y,h(o.value,a.value,p.value)),u.push(l));else if(v===x&amp;amp;&amp;amp;0&amp;lt;u.length)o=u.pop(),h=r[l.value],l=new C(y,h(o.value)),u.push(l);else if(v===A){for(;0&amp;lt;u.length;)c.push(u.shift());c.push(new C(A,t(l.value,r,s,n,i)))}else if(v===k&amp;amp;&amp;amp;0&amp;lt;u.length)o=u.pop(),u.push(new C(y,o.value[l.value]));else{for(;0&amp;lt;u.length;)c.push(u.shift());c.push(l)}}for(;0&amp;lt;u.length;)c.push(u.shift());return c}(this.tokens,this.unaryOps,this.binaryOps,this.ternaryOps,t),this.parser)},s.prototype.substitute=function(t,e){return e instanceof s||(e=this.parser.parse(String(e))),new s(function t(e,r,s){for(var n=[],i=0;i&amp;lt;e.length;i++){var o=e[i],a=o.type;if(a===g&amp;amp;&amp;amp;o.value===r)for(var p=0;p&amp;lt;s.tokens.length;p++){var h,u=s.tokens[p];h=u.type===x?c(u.value):u.type===w?f(u.value):u.type===d?l(u.value):new C(u.type,u.value),n.push(h)}else a===A?n.push(new C(A,t(o.value,r,s))):n.push(o)}return n}(this.tokens,t,e),this.parser)},s.prototype.evaluate=function(t){return t=t||{},N(this.tokens,this,t)},s.prototype.toString=function(){return v(this.tokens,!1)},s.prototype.symbols=function(t){t=t||{};var e=[];return p(this.tokens,e,t),e},s.prototype.variables=function(t){t=t||{};var e=[];p(this.tokens,e,t);var r=this.functions;return e.filter(function(t){return!(t in r)})},s.prototype.toJSFunction=function(t,e){var r=this,s=new Function(t,&amp;quot;with(this.functions) with (this.ternaryOps) with (this.binaryOps) with (this.unaryOps) { return &amp;quot;+v(this.simplify(e).tokens,!0)+&amp;quot;; }&amp;quot;);return function(){return s.apply(r,arguments)}};var n=&amp;quot;TEOF&amp;quot;,h=&amp;quot;TOP&amp;quot;,u=&amp;quot;TNUMBER&amp;quot;,i=&amp;quot;TSTRING&amp;quot;,o=&amp;quot;TPAREN&amp;quot;,R=&amp;quot;TBRACKET&amp;quot;,L=&amp;quot;TCOMMA&amp;quot;,j=&amp;quot;TNAME&amp;quot;,U=&amp;quot;TSEMICOLON&amp;quot;;function q(t,e,r){this.type=t,this.value=e,this.index=r}function B(t,e){this.pos=0,this.current=null,this.unaryOps=t.unaryOps,this.binaryOps=t.binaryOps,this.ternaryOps=t.ternaryOps,this.consts=t.consts,this.expression=e,this.savedPosition=0,this.savedCurrent=null,this.options=t.options,this.parser=t}q.prototype.toString=function(){return this.type+&amp;quot;: &amp;quot;+this.value},B.prototype.newToken=function(t,e,r){return new q(t,e,null!=r?r:this.pos)},B.prototype.save=function(){this.savedPosition=this.pos,this.savedCurrent=this.current},B.prototype.restore=function(){this.pos=this.savedPosition,this.current=this.savedCurrent},B.prototype.next=function(){return this.pos&amp;gt;=this.expression.length?this.newToken(n,&amp;quot;EOF&amp;quot;):this.isWhitespace()||this.isComment()?this.next():this.isRadixInteger()||this.isNumber()||this.isOperator()||this.isString()||this.isParen()||this.isBracket()||this.isComma()||this.isSemicolon()||this.isNamedOp()||this.isConst()||this.isName()?this.current:void this.parseError(&#039;Unknown character &amp;quot;&#039;+this.expression.charAt(this.pos)+&#039;&amp;quot;&#039;)},B.prototype.isString=function(){var t=!1,e=this.pos,r=this.expression.charAt(e);if(&amp;quot;&#039;&amp;quot;===r||&#039;&amp;quot;&#039;===r)for(var s=this.expression.indexOf(r,e+1);0&amp;lt;=s&amp;amp;&amp;amp;this.pos&amp;lt;this.expression.length;){if(this.pos=s+1,&amp;quot;\\&amp;quot;!==this.expression.charAt(s-1)){var n=this.expression.substring(e+1,s);this.current=this.newToken(i,this.unescape(n),e),t=!0;break}s=this.expression.indexOf(r,s+1)}return t},B.prototype.isParen=function(){var t=this.expression.charAt(this.pos);return(&amp;quot;(&amp;quot;===t||&amp;quot;)&amp;quot;===t)&amp;amp;&amp;amp;(this.current=this.newToken(o,t),this.pos++,!0)},B.prototype.isBracket=function(){var t=this.expression.charAt(this.pos);return!(&amp;quot;[&amp;quot;!==t&amp;amp;&amp;amp;&amp;quot;]&amp;quot;!==t||!this.isOperatorEnabled(&amp;quot;[&amp;quot;))&amp;amp;&amp;amp;(this.current=this.newToken(R,t),this.pos++,!0)},B.prototype.isComma=function(){return&amp;quot;,&amp;quot;===this.expression.charAt(this.pos)&amp;amp;&amp;amp;(this.current=this.newToken(L,&amp;quot;,&amp;quot;),this.pos++,!0)},B.prototype.isSemicolon=function(){return&amp;quot;;&amp;quot;===this.expression.charAt(this.pos)&amp;amp;&amp;amp;(this.current=this.newToken(U,&amp;quot;;&amp;quot;),this.pos++,!0)},B.prototype.isConst=function(){for(var t=this.pos,e=t;e&amp;lt;this.expression.length;e++){var r=this.expression.charAt(e);if(r.toUpperCase()===r.toLowerCase()&amp;amp;&amp;amp;(e===this.pos||&amp;quot;_&amp;quot;!==r&amp;amp;&amp;amp;&amp;quot;.&amp;quot;!==r&amp;amp;&amp;amp;(r&amp;lt;&amp;quot;0&amp;quot;||&amp;quot;9&amp;quot;&amp;lt;r)))break}if(t&amp;lt;e){var s=this.expression.substring(t,e);if(s in this.consts)return this.current=this.newToken(u,this.consts[s]),this.pos+=s.length,!0}return!1},B.prototype.isNamedOp=function(){for(var t=this.pos,e=t;e&amp;lt;this.expression.length;e++){var r=this.expression.charAt(e);if(r.toUpperCase()===r.toLowerCase()&amp;amp;&amp;amp;(e===this.pos||&amp;quot;_&amp;quot;!==r&amp;amp;&amp;amp;(r&amp;lt;&amp;quot;0&amp;quot;||&amp;quot;9&amp;quot;&amp;lt;r)))break}if(t&amp;lt;e){var s=this.expression.substring(t,e);if(this.isOperatorEnabled(s)&amp;amp;&amp;amp;(s in this.binaryOps||s in this.unaryOps||s in this.ternaryOps))return this.current=this.newToken(h,s),this.pos+=s.length,!0}return!1},B.prototype.isName=function(){for(var t=this.pos,e=t,r=!1;e&amp;lt;this.expression.length;e++){var s=this.expression.charAt(e);if(s.toUpperCase()===s.toLowerCase()){if(e===this.pos&amp;amp;&amp;amp;(&amp;quot;$&amp;quot;===s||&amp;quot;_&amp;quot;===s)){&amp;quot;_&amp;quot;===s&amp;amp;&amp;amp;(r=!0);continue}if(e===this.pos||!r||&amp;quot;_&amp;quot;!==s&amp;amp;&amp;amp;(s&amp;lt;&amp;quot;0&amp;quot;||&amp;quot;9&amp;quot;&amp;lt;s))break}else r=!0}if(r){var n=this.expression.substring(t,e);return this.current=this.newToken(j,n),this.pos+=n.length,!0}return!1},B.prototype.isWhitespace=function(){for(var t=!1,e=this.expression.charAt(this.pos);!(&amp;quot; &amp;quot;!==e&amp;amp;&amp;amp;&amp;quot;\t&amp;quot;!==e&amp;amp;&amp;amp;&amp;quot;\n&amp;quot;!==e&amp;amp;&amp;amp;&amp;quot;\r&amp;quot;!==e||(t=!0,this.pos++,this.pos&amp;gt;=this.expression.length));)e=this.expression.charAt(this.pos);return t};var V=/^[0-9a-f]{4}$/i;function _(t,e,r){this.parser=t,this.tokens=e,this.current=null,this.nextToken=null,this.next(),this.savedCurrent=null,this.savedNextToken=null,this.allowMemberAccess=!1!==r.allowMemberAccess}B.prototype.unescape=function(t){var e=t.indexOf(&amp;quot;\\&amp;quot;);if(e&amp;lt;0)return t;for(var r=t.substring(0,e);0&amp;lt;=e;){var s=t.charAt(++e);switch(s){case&amp;quot;&#039;&amp;quot;:r+=&amp;quot;&#039;&amp;quot;;break;case&#039;&amp;quot;&#039;:r+=&#039;&amp;quot;&#039;;break;case&amp;quot;\\&amp;quot;:r+=&amp;quot;\\&amp;quot;;break;case&amp;quot;/&amp;quot;:r+=&amp;quot;/&amp;quot;;break;case&amp;quot;b&amp;quot;:r+=&amp;quot;\b&amp;quot;;break;case&amp;quot;f&amp;quot;:r+=&amp;quot;\f&amp;quot;;break;case&amp;quot;n&amp;quot;:r+=&amp;quot;\n&amp;quot;;break;case&amp;quot;r&amp;quot;:r+=&amp;quot;\r&amp;quot;;break;case&amp;quot;t&amp;quot;:r+=&amp;quot;\t&amp;quot;;break;case&amp;quot;u&amp;quot;:var n=t.substring(e+1,e+5);V.test(n)||this.parseError(&amp;quot;Illegal escape sequence: \\u&amp;quot;+n),r+=String.fromCharCode(parseInt(n,16)),e+=4;break;default:throw this.parseError(&#039;Illegal escape sequence: &amp;quot;\\&#039;+s+&#039;&amp;quot;&#039;)}++e;var i=t.indexOf(&amp;quot;\\&amp;quot;,e);r+=t.substring(e,i&amp;lt;0?t.length:i),e=i}return r},B.prototype.isComment=function(){return&amp;quot;/&amp;quot;===this.expression.charAt(this.pos)&amp;amp;&amp;amp;&amp;quot;*&amp;quot;===this.expression.charAt(this.pos+1)&amp;amp;&amp;amp;(this.pos=this.expression.indexOf(&amp;quot;*/&amp;quot;,this.pos)+2,1===this.pos&amp;amp;&amp;amp;(this.pos=this.expression.length),!0)},B.prototype.isRadixInteger=function(){var t,e,r=this.pos;if(r&amp;gt;=this.expression.length-2||&amp;quot;0&amp;quot;!==this.expression.charAt(r))return!1;if(++r,&amp;quot;x&amp;quot;===this.expression.charAt(r))t=16,e=/^[0-9a-f]$/i,++r;else{if(&amp;quot;b&amp;quot;!==this.expression.charAt(r))return!1;t=2,e=/^[01]$/i,++r}for(var s=!1,n=r;r&amp;lt;this.expression.length;){var i=this.expression.charAt(r);if(!e.test(i))break;r++,s=!0}return s&amp;amp;&amp;amp;(this.current=this.newToken(u,parseInt(this.expression.substring(n,r),t)),this.pos=r),s},B.prototype.isNumber=function(){for(var t,e=!1,r=this.pos,s=r,n=r,i=!1,o=!1;r&amp;lt;this.expression.length&amp;amp;&amp;amp;(&amp;quot;0&amp;quot;&amp;lt;=(t=this.expression.charAt(r))&amp;amp;&amp;amp;t&amp;lt;=&amp;quot;9&amp;quot;||!i&amp;amp;&amp;amp;&amp;quot;.&amp;quot;===t);)&amp;quot;.&amp;quot;===t?i=!0:o=!0,r++,e=o;if(e&amp;amp;&amp;amp;(n=r),&amp;quot;e&amp;quot;===t||&amp;quot;E&amp;quot;===t){r++;for(var a=!0,p=!1;r&amp;lt;this.expression.length;){if(t=this.expression.charAt(r),!a||&amp;quot;+&amp;quot;!==t&amp;amp;&amp;amp;&amp;quot;-&amp;quot;!==t){if(!(&amp;quot;0&amp;quot;&amp;lt;=t&amp;amp;&amp;amp;t&amp;lt;=&amp;quot;9&amp;quot;))break;a=!(p=!0)}else a=!1;r++}p||(r=n)}return e?(this.current=this.newToken(u,parseFloat(this.expression.substring(s,r))),this.pos=r):this.pos=n,e},B.prototype.isOperator=function(){var t=this.pos,e=this.expression.charAt(this.pos);if(&amp;quot;+&amp;quot;===e||&amp;quot;-&amp;quot;===e||&amp;quot;*&amp;quot;===e||&amp;quot;/&amp;quot;===e||&amp;quot;%&amp;quot;===e||&amp;quot;^&amp;quot;===e||&amp;quot;?&amp;quot;===e||&amp;quot;:&amp;quot;===e||&amp;quot;.&amp;quot;===e)this.current=this.newToken(h,e);else if(&amp;quot;∙&amp;quot;===e||&amp;quot;•&amp;quot;===e)this.current=this.newToken(h,&amp;quot;*&amp;quot;);else if(&amp;quot;&amp;gt;&amp;quot;===e)&amp;quot;=&amp;quot;===this.expression.charAt(this.pos+1)?(this.current=this.newToken(h,&amp;quot;&amp;gt;=&amp;quot;),this.pos++):this.current=this.newToken(h,&amp;quot;&amp;gt;&amp;quot;);else if(&amp;quot;&amp;lt;&amp;quot;===e)&amp;quot;=&amp;quot;===this.expression.charAt(this.pos+1)?(this.current=this.newToken(h,&amp;quot;&amp;lt;=&amp;quot;),this.pos++):this.current=this.newToken(h,&amp;quot;&amp;lt;&amp;quot;);else if(&amp;quot;|&amp;quot;===e){if(&amp;quot;|&amp;quot;!==this.expression.charAt(this.pos+1))return!1;this.current=this.newToken(h,&amp;quot;||&amp;quot;),this.pos++}else if(&amp;quot;=&amp;quot;===e)&amp;quot;=&amp;quot;===this.expression.charAt(this.pos+1)?(this.current=this.newToken(h,&amp;quot;==&amp;quot;),this.pos++):this.current=this.newToken(h,e);else{if(&amp;quot;!&amp;quot;!==e)return!1;&amp;quot;=&amp;quot;===this.expression.charAt(this.pos+1)?(this.current=this.newToken(h,&amp;quot;!=&amp;quot;),this.pos++):this.current=this.newToken(h,e)}return this.pos++,!!this.isOperatorEnabled(this.current.value)||(this.pos=t,!1)},B.prototype.isOperatorEnabled=function(t){return this.parser.isOperatorEnabled(t)},B.prototype.getCoordinates=function(){for(var t,e=0,r=-1;e++,t=this.pos-r,0&amp;lt;=(r=this.expression.indexOf(&amp;quot;\n&amp;quot;,r+1))&amp;amp;&amp;amp;r&amp;lt;this.pos;);return{line:e,column:t}},B.prototype.parseError=function(t){var e=this.getCoordinates();throw new Error(&amp;quot;parse error [&amp;quot;+e.line+&amp;quot;:&amp;quot;+e.column+&amp;quot;]: &amp;quot;+t)},_.prototype.next=function(){return this.current=this.nextToken,this.nextToken=this.tokens.next()},_.prototype.tokenMatches=function(t,e){return void 0===e||(Array.isArray(e)?a(e,t.value):&amp;quot;function&amp;quot;==typeof e?e(t):t.value===e)},_.prototype.save=function(){this.savedCurrent=this.current,this.savedNextToken=this.nextToken,this.tokens.save()},_.prototype.restore=function(){this.tokens.restore(),this.current=this.savedCurrent,this.nextToken=this.savedNextToken},_.prototype.accept=function(t,e){return!(this.nextToken.type!==t||!this.tokenMatches(this.nextToken,e))&amp;amp;&amp;amp;(this.next(),!0)},_.prototype.expect=function(t,e){if(!this.accept(t,e)){var r=this.tokens.getCoordinates();throw new Error(&amp;quot;parse error [&amp;quot;+r.line+&amp;quot;:&amp;quot;+r.column+&amp;quot;]: Expected &amp;quot;+(e||t))}},_.prototype.parseAtom=function(t){var e=this.tokens.unaryOps;if(this.accept(j)||this.accept(h,function(t){return t.value in e}))t.push(new C(g,this.current.value));else if(this.accept(u))t.push(new C(y,this.current.value));else if(this.accept(i))t.push(new C(y,this.current.value));else if(this.accept(o,&amp;quot;(&amp;quot;))this.parseExpression(t),this.expect(o,&amp;quot;)&amp;quot;);else{if(!this.accept(R,&amp;quot;[&amp;quot;))throw new Error(&amp;quot;unexpected &amp;quot;+this.nextToken);if(this.accept(R,&amp;quot;]&amp;quot;))t.push(new C(T,0));else{var r=this.parseArrayList(t);t.push(new C(T,r))}}},_.prototype.parseExpression=function(t){var e=[];this.parseUntilEndStatement(t,e)||(this.parseVariableAssignmentExpression(e),this.parseUntilEndStatement(t,e)||this.pushExpression(t,e))},_.prototype.pushExpression=function(t,e){for(var r=0,s=e.length;r&amp;lt;s;r++)t.push(e[r])},_.prototype.parseUntilEndStatement=function(t,e){return!!this.accept(U)&amp;amp;&amp;amp;(!this.nextToken||this.nextToken.type===n||this.nextToken.type===o&amp;amp;&amp;amp;&amp;quot;)&amp;quot;===this.nextToken.value||e.push(new C(O)),this.nextToken.type!==n&amp;amp;&amp;amp;this.parseExpression(e),t.push(new C(A,e)),!0)},_.prototype.parseArrayList=function(t){for(var e=0;!this.accept(R,&amp;quot;]&amp;quot;);)for(this.parseExpression(t),++e;this.accept(L);)this.parseExpression(t),++e;return e},_.prototype.parseVariableAssignmentExpression=function(t){for(this.parseConditionalExpression(t);this.accept(h,&amp;quot;=&amp;quot;);){var e=t.pop(),r=[],s=t.length-1;if(e.type!==E){if(e.type!==g&amp;amp;&amp;amp;e.type!==k)throw new Error(&amp;quot;expected variable for assignment&amp;quot;);this.parseVariableAssignmentExpression(r),t.push(new C(M,e.value)),t.push(new C(A,r)),t.push(f(&amp;quot;=&amp;quot;))}else{if(!this.tokens.isOperatorEnabled(&amp;quot;()=&amp;quot;))throw new Error(&amp;quot;function definition is not permitted&amp;quot;);for(var n=0,i=e.value+1;n&amp;lt;i;n++){var o=s-n;t[o].type===g&amp;amp;&amp;amp;(t[o]=new C(M,t[o].value))}this.parseVariableAssignmentExpression(r),t.push(new C(A,r)),t.push(new C(m,e.value))}}},_.prototype.parseConditionalExpression=function(t){for(this.parseOrExpression(t);this.accept(h,&amp;quot;?&amp;quot;);){var e=[],r=[];this.parseConditionalExpression(e),this.expect(h,&amp;quot;:&amp;quot;),this.parseConditionalExpression(r),t.push(new C(A,e)),t.push(new C(A,r)),t.push(l(&amp;quot;?&amp;quot;))}},_.prototype.parseOrExpression=function(t){for(this.parseAndExpression(t);this.accept(h,&amp;quot;or&amp;quot;);){var e=[];this.parseAndExpression(e),t.push(new C(A,e)),t.push(f(&amp;quot;or&amp;quot;))}},_.prototype.parseAndExpression=function(t){for(this.parseComparison(t);this.accept(h,&amp;quot;and&amp;quot;);){var e=[];this.parseComparison(e),t.push(new C(A,e)),t.push(f(&amp;quot;and&amp;quot;))}};var r=[&amp;quot;==&amp;quot;,&amp;quot;!=&amp;quot;,&amp;quot;&amp;lt;&amp;quot;,&amp;quot;&amp;lt;=&amp;quot;,&amp;quot;&amp;gt;=&amp;quot;,&amp;quot;&amp;gt;&amp;quot;,&amp;quot;in&amp;quot;];_.prototype.parseComparison=function(t){for(this.parseAddSub(t);this.accept(h,r);){var e=this.current;this.parseAddSub(t),t.push(f(e.value))}};var $=[&amp;quot;+&amp;quot;,&amp;quot;-&amp;quot;,&amp;quot;||&amp;quot;];_.prototype.parseAddSub=function(t){for(this.parseTerm(t);this.accept(h,$);){var e=this.current;this.parseTerm(t),t.push(f(e.value))}};var D=[&amp;quot;*&amp;quot;,&amp;quot;/&amp;quot;,&amp;quot;%&amp;quot;];function e(t,e){return Number(t)+Number(e)}function G(t,e){return t-e}function J(t,e){return t*e}function W(t,e){return t/e}function X(t,e){return t%e}function Y(t,e){return Array.isArray(t)&amp;amp;&amp;amp;Array.isArray(e)?t.concat(e):&amp;quot;&amp;quot;+t+e}function K(t,e){return t===e}function z(t,e){return t!==e}function H(t,e){return e&amp;lt;t}function Q(t,e){return t&amp;lt;e}function Z(t,e){return e&amp;lt;=t}function tt(t,e){return t&amp;lt;=e}function et(t,e){return Boolean(t&amp;amp;&amp;amp;e)}function rt(t,e){return Boolean(t||e)}function st(t,e){return a(e,t)}function nt(t){return(Math.exp(t)-Math.exp(-t))/2}function it(t){return(Math.exp(t)+Math.exp(-t))/2}function ot(t){return t===1/0?1:t===-1/0?-1:(Math.exp(t)-Math.exp(-t))/(Math.exp(t)+Math.exp(-t))}function at(t){return t===-1/0?t:Math.log(t+Math.sqrt(t*t+1))}function pt(t){return Math.log(t+Math.sqrt(t*t-1))}function ht(t){return Math.log((1+t)/(1-t))/2}function ut(t){return Math.log(t)*Math.LOG10E}function ct(t){return-t}function ft(t){return!t}function lt(t){return t&amp;lt;0?Math.ceil(t):Math.floor(t)}function vt(t){return Math.random()*(t||1)}function yt(t){return dt(t+1)}_.prototype.parseTerm=function(t){for(this.parseFactor(t);this.accept(h,D);){var e=this.current;this.parseFactor(t),t.push(f(e.value))}},_.prototype.parseFactor=function(t){var e=this.tokens.unaryOps;if(this.save(),this.accept(h,function(t){return t.value in e})){if(&amp;quot;-&amp;quot;!==this.current.value&amp;amp;&amp;amp;&amp;quot;+&amp;quot;!==this.current.value){if(this.nextToken.type===o&amp;amp;&amp;amp;&amp;quot;(&amp;quot;===this.nextToken.value)return this.restore(),void this.parseExponential(t);if(this.nextToken.type===U||this.nextToken.type===L||this.nextToken.type===n||this.nextToken.type===o&amp;amp;&amp;amp;&amp;quot;)&amp;quot;===this.nextToken.value)return this.restore(),void this.parseAtom(t)}var r=this.current;this.parseFactor(t),t.push(c(r.value))}else this.parseExponential(t)},_.prototype.parseExponential=function(t){for(this.parsePostfixExpression(t);this.accept(h,&amp;quot;^&amp;quot;);)this.parseFactor(t),t.push(f(&amp;quot;^&amp;quot;))},_.prototype.parsePostfixExpression=function(t){for(this.parseFunctionCall(t);this.accept(h,&amp;quot;!&amp;quot;);)t.push(c(&amp;quot;!&amp;quot;))},_.prototype.parseFunctionCall=function(t){var e=this.tokens.unaryOps;if(this.accept(h,function(t){return t.value in e})){var r=this.current;this.parseAtom(t),t.push(c(r.value))}else for(this.parseMemberExpression(t);this.accept(o,&amp;quot;(&amp;quot;);)if(this.accept(o,&amp;quot;)&amp;quot;))t.push(new C(E,0));else{var s=this.parseArgumentList(t);t.push(new C(E,s))}},_.prototype.parseArgumentList=function(t){for(var e=0;!this.accept(o,&amp;quot;)&amp;quot;);)for(this.parseExpression(t),++e;this.accept(L);)this.parseExpression(t),++e;return e},_.prototype.parseMemberExpression=function(t){for(this.parseAtom(t);this.accept(h,&amp;quot;.&amp;quot;)||this.accept(R,&amp;quot;[&amp;quot;);){var e=this.current;if(&amp;quot;.&amp;quot;===e.value){if(!this.allowMemberAccess)throw new Error(&#039;unexpected &amp;quot;.&amp;quot;, member access is not permitted&#039;);this.expect(j),t.push(new C(k,this.current.value))}else{if(&amp;quot;[&amp;quot;!==e.value)throw new Error(&amp;quot;unexpected symbol: &amp;quot;+e.value);if(!this.tokens.isOperatorEnabled(&amp;quot;[&amp;quot;))throw new Error(&#039;unexpected &amp;quot;[]&amp;quot;, arrays are disabled&#039;);this.parseExpression(t),this.expect(R,&amp;quot;]&amp;quot;),t.push(f(&amp;quot;[&amp;quot;))}}};var xt=4.7421875,wt=[.9999999999999971,57.15623566586292,-59.59796035547549,14.136097974741746,-.4919138160976202,3399464998481189e-20,4652362892704858e-20,-9837447530487956e-20,.0001580887032249125,-.00021026444172410488,.00021743961811521265,-.0001643181065367639,8441822398385275e-20,-26190838401581408e-21,36899182659531625e-22];function dt(t){var e,r;if(function(t){return isFinite(t)&amp;amp;&amp;amp;t===Math.round(t)}(t)){if(t&amp;lt;=0)return isFinite(t)?1/0:NaN;if(171&amp;lt;t)return 1/0;for(var s=t-2,n=t-1;1&amp;lt;s;)n*=s,s--;return 0===n&amp;amp;&amp;amp;(n=1),n}if(t&amp;lt;.5)return Math.PI/(Math.sin(Math.PI*t)*dt(1-t));if(171.35&amp;lt;=t)return 1/0;if(85&amp;lt;t){var i=t*t,o=i*t,a=o*t,p=a*t;return Math.sqrt(2*Math.PI/t)*Math.pow(t/Math.E,t)*(1+1/(12*t)+1/(288*i)-139/(51840*o)-571/(2488320*a)+163879/(209018880*p)+5246819/(75246796800*p*t))}--t,r=wt[0];for(var h=1;h&amp;lt;wt.length;++h)r+=wt[h]/(t+h);return e=t+xt+.5,Math.sqrt(2*Math.PI)*Math.pow(e,t+.5)*Math.exp(-e)*r}function gt(t){return Array.isArray(t)?t.length:String(t).length}function Mt(){for(var t=0,e=0,r=0;r&amp;lt;arguments.length;r++){var s,n=Math.abs(arguments[r]);e&amp;lt;n?(t=t*(s=e/n)*s+1,e=n):t+=0&amp;lt;n?(s=n/e)*s:n}return e===1/0?1/0:e*Math.sqrt(t)}function Et(t,e,r){return t?e:r}function mt(t,e){return void 0===e||0==+e?Math.round(t):(t=+t,e=-+e,isNaN(t)||&amp;quot;number&amp;quot;!=typeof e||e%1!=0?NaN:(t=t.toString().split(&amp;quot;e&amp;quot;),+((t=(t=Math.round(+(t[0]+&amp;quot;e&amp;quot;+(t[1]?+t[1]-e:-e)))).toString().split(&amp;quot;e&amp;quot;))[0]+&amp;quot;e&amp;quot;+(t[1]?+t[1]+e:e))))}function At(t,e,r){return r&amp;amp;&amp;amp;(r[t]=e),e}function bt(t,e){return t[0|e]}function kt(t){return 1===arguments.length&amp;amp;&amp;amp;Array.isArray(t)?Math.max.apply(Math,t):Math.max.apply(Math,arguments)}function Ot(t){return 1===arguments.length&amp;amp;&amp;amp;Array.isArray(t)?Math.min.apply(Math,t):Math.min.apply(Math,arguments)}function Tt(r,t){if(&amp;quot;function&amp;quot;!=typeof r)throw new Error(&amp;quot;First argument to map is not a function&amp;quot;);if(!Array.isArray(t))throw new Error(&amp;quot;Second argument to map is not an array&amp;quot;);return t.map(function(t,e){return r(t,e)})}function Ct(s,t,e){if(&amp;quot;function&amp;quot;!=typeof s)throw new Error(&amp;quot;First argument to fold is not a function&amp;quot;);if(!Array.isArray(e))throw new Error(&amp;quot;Second argument to fold is not an array&amp;quot;);return e.reduce(function(t,e,r){return s(t,e,r)},t)}function Nt(r,t){if(&amp;quot;function&amp;quot;!=typeof r)throw new Error(&amp;quot;First argument to filter is not a function&amp;quot;);if(!Array.isArray(t))throw new Error(&amp;quot;Second argument to filter is not an array&amp;quot;);return t.filter(function(t,e){return r(t,e)})}function St(t,e){if(!Array.isArray(e)&amp;amp;&amp;amp;&amp;quot;string&amp;quot;!=typeof e)throw new Error(&amp;quot;Second argument to indexOf is not a string or array&amp;quot;);return e.indexOf(t)}function It(t,e){if(!Array.isArray(e))throw new Error(&amp;quot;Second argument to join is not an array&amp;quot;);return e.join(t)}function Pt(t){return(0&amp;lt;t)-(t&amp;lt;0)||+t}var Ft=1/3;function Rt(t){return t&amp;lt;0?-Math.pow(-t,Ft):Math.pow(t,Ft)}function Lt(t){return Math.exp(t)-1}function jt(t){return Math.log(1+t)}function Ut(t){return Math.log(t)/Math.LN2}function qt(t){this.options=t||{},this.unaryOps={sin:Math.sin,cos:Math.cos,tan:Math.tan,asin:Math.asin,acos:Math.acos,atan:Math.atan,sinh:Math.sinh||nt,cosh:Math.cosh||it,tanh:Math.tanh||ot,asinh:Math.asinh||at,acosh:Math.acosh||pt,atanh:Math.atanh||ht,sqrt:Math.sqrt,cbrt:Math.cbrt||Rt,log:Math.log,log2:Math.log2||Ut,ln:Math.log,lg:Math.log10||ut,log10:Math.log10||ut,expm1:Math.expm1||Lt,log1p:Math.log1p||jt,abs:Math.abs,ceil:Math.ceil,floor:Math.floor,round:Math.round,trunc:Math.trunc||lt,&amp;quot;-&amp;quot;:ct,&amp;quot;+&amp;quot;:Number,exp:Math.exp,not:ft,length:gt,&amp;quot;!&amp;quot;:yt,sign:Math.sign||Pt},this.binaryOps={&amp;quot;+&amp;quot;:e,&amp;quot;-&amp;quot;:G,&amp;quot;*&amp;quot;:J,&amp;quot;/&amp;quot;:W,&amp;quot;%&amp;quot;:X,&amp;quot;^&amp;quot;:Math.pow,&amp;quot;||&amp;quot;:Y,&amp;quot;==&amp;quot;:K,&amp;quot;!=&amp;quot;:z,&amp;quot;&amp;gt;&amp;quot;:H,&amp;quot;&amp;lt;&amp;quot;:Q,&amp;quot;&amp;gt;=&amp;quot;:Z,&amp;quot;&amp;lt;=&amp;quot;:tt,and:et,or:rt,in:st,&amp;quot;=&amp;quot;:At,&amp;quot;[&amp;quot;:bt},this.ternaryOps={&amp;quot;?&amp;quot;:Et},this.functions={random:vt,fac:yt,min:Ot,max:kt,hypot:Math.hypot||Mt,pyt:Math.hypot||Mt,pow:Math.pow,atan2:Math.atan2,if:Et,gamma:dt,roundTo:mt,map:Tt,fold:Ct,filter:Nt,indexOf:St,join:It},this.consts={E:Math.E,PI:Math.PI,true:!0,false:!1}}qt.prototype.parse=function(t){var e=[],r=new _(this,new B(this,t),{allowMemberAccess:this.options.allowMemberAccess});return r.parseExpression(e),r.expect(n,&amp;quot;EOF&amp;quot;),new s(e,this)},qt.prototype.evaluate=function(t,e){return this.parse(t).evaluate(e)};var Bt=new qt;qt.parse=function(t){return Bt.parse(t)},qt.evaluate=function(t,e){return Bt.parse(t).evaluate(e)};var Vt={&amp;quot;+&amp;quot;:&amp;quot;add&amp;quot;,&amp;quot;-&amp;quot;:&amp;quot;subtract&amp;quot;,&amp;quot;*&amp;quot;:&amp;quot;multiply&amp;quot;,&amp;quot;/&amp;quot;:&amp;quot;divide&amp;quot;,&amp;quot;%&amp;quot;:&amp;quot;remainder&amp;quot;,&amp;quot;^&amp;quot;:&amp;quot;power&amp;quot;,&amp;quot;!&amp;quot;:&amp;quot;factorial&amp;quot;,&amp;quot;&amp;lt;&amp;quot;:&amp;quot;comparison&amp;quot;,&amp;quot;&amp;gt;&amp;quot;:&amp;quot;comparison&amp;quot;,&amp;quot;&amp;lt;=&amp;quot;:&amp;quot;comparison&amp;quot;,&amp;quot;&amp;gt;=&amp;quot;:&amp;quot;comparison&amp;quot;,&amp;quot;==&amp;quot;:&amp;quot;comparison&amp;quot;,&amp;quot;!=&amp;quot;:&amp;quot;comparison&amp;quot;,&amp;quot;||&amp;quot;:&amp;quot;concatenate&amp;quot;,and:&amp;quot;logical&amp;quot;,or:&amp;quot;logical&amp;quot;,not:&amp;quot;logical&amp;quot;,&amp;quot;?&amp;quot;:&amp;quot;conditional&amp;quot;,&amp;quot;:&amp;quot;:&amp;quot;conditional&amp;quot;,&amp;quot;=&amp;quot;:&amp;quot;assignment&amp;quot;,&amp;quot;[&amp;quot;:&amp;quot;array&amp;quot;,&amp;quot;()=&amp;quot;:&amp;quot;fndef&amp;quot;};qt.prototype.isOperatorEnabled=function(t){var e=function(t){return Vt.hasOwnProperty(t)?Vt[t]:t}(t),r=this.options.operators||{};return!(e in r&amp;amp;&amp;amp;!r[e])};var _t={Parser:qt,Expression:s};t.Expression=s,t.Parser=qt,t.default=_t,Object.defineProperty(t,&amp;quot;__esModule&amp;quot;,{value:!0})});&lt;br /&gt;
&lt;br /&gt;
/**&lt;br /&gt;
 * 数式に数学関数以外の関数が含まれている場合のエラー&lt;br /&gt;
 */&lt;br /&gt;
class MathFunctionError extends Error {&lt;br /&gt;
    /**&lt;br /&gt;
     * @param {string} message - エラーメッセージ&lt;br /&gt;
     */&lt;br /&gt;
    constructor(message) {&lt;br /&gt;
        super(message || &amp;quot;数学関数以外の関数が含まれています&amp;quot;);&lt;br /&gt;
        this.name = &amp;quot;MathFunctionError&amp;quot;;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
var sel = document.selection;&lt;br /&gt;
&lt;br /&gt;
doMultiEdit(main);&lt;br /&gt;
&lt;br /&gt;
function main() {&lt;br /&gt;
    var isEmpty = sel.IsEmpty;&lt;br /&gt;
    var activePointY = sel.GetActivePointY(mePosLogical);&lt;br /&gt;
    if (isEmpty) {&lt;br /&gt;
        var exp = document.GetLine(activePointY);&lt;br /&gt;
    } else {&lt;br /&gt;
        var exp = sel.Text;&lt;br /&gt;
    }&lt;br /&gt;
    // 空白行の場合は何もしない&lt;br /&gt;
    if (exp.match(/^\s*$/)) {&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    try {&lt;br /&gt;
        var exp_replaced = replaceExpression(exp);&lt;br /&gt;
        const Parser = exprEval.Parser;&lt;br /&gt;
        let r = Parser.evaluate(exp_replaced);&lt;br /&gt;
        try {&lt;br /&gt;
            r = r.toFixed(15);&lt;br /&gt;
            const intPart = r.match(/([0-9]+)\./);&lt;br /&gt;
            const fractionalPart = r.match(/\.([0-9]+$)/);&lt;br /&gt;
            if (intPart &amp;amp;&amp;amp; fractionalPart) {&lt;br /&gt;
                // 小数点以下は12桁で切る&lt;br /&gt;
                const f = fractionalPart[1].slice(0,12).replace(/[0]+$/, &amp;quot;&amp;quot;)&lt;br /&gt;
                r = intPart[1] + (f ? `.${f}` : &amp;quot;&amp;quot;);&lt;br /&gt;
            }&lt;br /&gt;
        } catch(e) {}&lt;br /&gt;
        var result_str = String(r);&lt;br /&gt;
    } catch (e) {&lt;br /&gt;
        if (e instanceof MathFunctionError) {&lt;br /&gt;
            alert(e.message)&lt;br /&gt;
        } else {&lt;br /&gt;
            alert(e + &amp;quot;\n入力: &amp;quot; + exp + &amp;quot;\n入力変換後: &amp;quot; + exp_replaced)&lt;br /&gt;
        }&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // 小数点以上のみカンマ区切り&lt;br /&gt;
    if (COMMA) {&lt;br /&gt;
        var result_parts = result_str.split(&amp;quot;.&amp;quot;);&lt;br /&gt;
        result_parts[0] = result_parts[0].replace(/(\d)(?=(\d{3})+$)/g, &amp;quot;$1,&amp;quot;);&lt;br /&gt;
        if (result_parts.length &amp;gt; 1) {&lt;br /&gt;
            result_str = result_parts.join(&amp;quot;.&amp;quot;);&lt;br /&gt;
        } else {&lt;br /&gt;
            result_str = result_parts[0];&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // 計算行が = 始まりの場合、= を削除する&lt;br /&gt;
    if (isEmpty &amp;amp;&amp;amp; exp.match(/^= /)) {&lt;br /&gt;
        sel.SetActivePoint(mePosLogical, 1, activePointY, false);&lt;br /&gt;
        sel.EndOfLine(true, mePosLogical);&lt;br /&gt;
        sel.Text = sel.Text.replace(/^= /, &amp;quot;&amp;quot;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // 既存の計算結果を上書きするか判定&lt;br /&gt;
    var pattern = /^=\s*(-?[0-9.,]|true|false)*\s*$/;&lt;br /&gt;
    var nextLine = document.GetLine(activePointY + 1);&lt;br /&gt;
    if (isEmpty &amp;amp;&amp;amp; nextLine.match(pattern)) {&lt;br /&gt;
        sel.SetActivePoint(mePosLogical, 1, activePointY + 1, false);&lt;br /&gt;
        sel.EndOfLine(true, mePosLogical);&lt;br /&gt;
        sel.Text = &#039;= &#039; + result_str&lt;br /&gt;
    } else if (isEmpty) {&lt;br /&gt;
        sel.LineOpen();&lt;br /&gt;
        sel.Text = &#039;= &#039; + result_str&lt;br /&gt;
    } else {&lt;br /&gt;
        sel.CharRight();&lt;br /&gt;
        const inputExp = exp.replace(/((?!;)\n)/mg, &amp;quot;; &amp;quot;).replace(/;\s*;/g, &amp;quot;;&amp;quot;).replace(/　/g, &amp;quot; &amp;quot;).replace(/;\s*$/, &amp;quot;&amp;quot;).trim();&lt;br /&gt;
        if (exp.slice(-1) === &amp;quot;\n&amp;quot;) {&lt;br /&gt;
            sel.Text = inputExp + &#039; = &#039; + result_str + &#039;\n&#039;;&lt;br /&gt;
        } else {&lt;br /&gt;
            sel.Text = &#039;\n&#039; + inputExp + &#039; = &#039; + result_str + &#039;\n&#039;;&lt;br /&gt;
        }&lt;br /&gt;
        sel.CharLeft();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (COPY) {&lt;br /&gt;
        ClipboardData.SetData(result_str);&lt;br /&gt;
        Status = &amp;quot;計算結果 &amp;quot; + result_str + &amp;quot; をコピーしました&amp;quot;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/**&lt;br /&gt;
 * 数式を置換する&lt;br /&gt;
 */&lt;br /&gt;
function replaceExpression(exp) {&lt;br /&gt;
    const exp_replaced = exp.trim()&lt;br /&gt;
        // 先頭の = を削除&lt;br /&gt;
        .replace(/^= /, &amp;quot;&amp;quot;)&lt;br /&gt;
        // 非ASCII文字を削除&lt;br /&gt;
        .replace(/[^\x20-\x7F]/g, &amp;quot; &amp;quot;)&lt;br /&gt;
        // 改行を ; に置換&lt;br /&gt;
        .replace(/(?&amp;lt;!;)\n/mg, &amp;quot;;&amp;quot;)&lt;br /&gt;
        // 全角スペースを半角スペースに変換&lt;br /&gt;
        .replace(/　/g, &amp;quot; &amp;quot;)&lt;br /&gt;
        // 数字内のカンマを取り除く&lt;br /&gt;
        .replace(/(\d),(?! )/g, &amp;quot;$1&amp;quot;)&lt;br /&gt;
        // 予約語を退避&lt;br /&gt;
        .replace(/(?:^|\s)(PI|true|false)(?:$|\s)/g, &amp;quot; %%%$1%%% &amp;quot;)&lt;br /&gt;
        // 指数形式を退避&lt;br /&gt;
        .replace(/([0-9]+(?:\.[0-9]+)?[eE][0-9]+)/g, &amp;quot; %%%$1%%% &amp;quot;)&lt;br /&gt;
        // 変数形式を退避&lt;br /&gt;
        .replace(/\$([a-zA-Z][a-zA-Z0-9]*)/g, &amp;quot; %%%$1%%% &amp;quot;)&lt;br /&gt;
        // `/hour` などの文字列はスラッシュごと削除&lt;br /&gt;
        .replace(/\/\s*[a-zA-Z\u0080-\uFFFF]/g, &amp;quot; &amp;quot;)&lt;br /&gt;
        // 不要な括弧書きを削除&lt;br /&gt;
        .replace(/\[[^\]]*\]/g, &amp;quot; &amp;quot;)&lt;br /&gt;
        // その他の文字を削除。退避文字と関数の可能性がある abc( 形式は削除しない&lt;br /&gt;
        .replace(/[^0-9.+\-*/%^=()&amp;lt;&amp;gt;!;\s]+(?![a-zA-Z0-9]*%%% |[a-z0-9]*\s*\([^)])/g, &amp;quot; &amp;quot;)&lt;br /&gt;
        // 退避文字を復元&lt;br /&gt;
        .replace(/ %%%([a-zA-Z][a-zA-Z0-9]*)%%% /g, &amp;quot;$1&amp;quot;)&lt;br /&gt;
    // 数式が数学関数以外を含む場合はエラー&lt;br /&gt;
    if (containsNotMathFunction(exp_replaced)) {&lt;br /&gt;
        throw new MathFunctionError(&amp;quot;数式に数学関数以外の関数が含まれています\n入力: &amp;quot; + exp + &amp;quot;\n入力変換後: &amp;quot; + exp_replaced);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return exp_replaced;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/**&lt;br /&gt;
 * 数学関数以外を含むかどうかを判定する&lt;br /&gt;
 * max(x, y, ...) のような複数の引数を受け取る関数もOK扱いにはするが、動作は保証しないものとする&lt;br /&gt;
 */&lt;br /&gt;
function containsNotMathFunction(exp) {&lt;br /&gt;
    var functions = [&lt;br /&gt;
        &amp;quot;abs&amp;quot;,&lt;br /&gt;
        &amp;quot;acos&amp;quot;,&lt;br /&gt;
        &amp;quot;acosh&amp;quot;,&lt;br /&gt;
        &amp;quot;asin&amp;quot;,&lt;br /&gt;
        &amp;quot;asinh&amp;quot;,&lt;br /&gt;
        &amp;quot;atan&amp;quot;,&lt;br /&gt;
        &amp;quot;atanh&amp;quot;,&lt;br /&gt;
        &amp;quot;cbrt&amp;quot;,&lt;br /&gt;
        &amp;quot;ceil&amp;quot;,&lt;br /&gt;
        &amp;quot;cos&amp;quot;,&lt;br /&gt;
        &amp;quot;cosh&amp;quot;,&lt;br /&gt;
        &amp;quot;exp&amp;quot;,&lt;br /&gt;
        &amp;quot;expm1&amp;quot;,&lt;br /&gt;
        &amp;quot;floor&amp;quot;,&lt;br /&gt;
        &amp;quot;length&amp;quot;,&lt;br /&gt;
        &amp;quot;ln&amp;quot;,&lt;br /&gt;
        &amp;quot;log&amp;quot;,&lt;br /&gt;
        &amp;quot;log10&amp;quot;,&lt;br /&gt;
        &amp;quot;log2&amp;quot;,&lt;br /&gt;
        &amp;quot;log1p&amp;quot;,&lt;br /&gt;
        &amp;quot;not&amp;quot;,&lt;br /&gt;
        &amp;quot;round&amp;quot;,&lt;br /&gt;
        &amp;quot;sign&amp;quot;,&lt;br /&gt;
        &amp;quot;sin&amp;quot;,&lt;br /&gt;
        &amp;quot;sinh&amp;quot;,&lt;br /&gt;
        &amp;quot;sqrt&amp;quot;,&lt;br /&gt;
        &amp;quot;tan&amp;quot;,&lt;br /&gt;
        &amp;quot;tanh&amp;quot;,&lt;br /&gt;
        &amp;quot;trunc&amp;quot;,&lt;br /&gt;
        &amp;quot;random&amp;quot;,&lt;br /&gt;
        &amp;quot;fac&amp;quot;,&lt;br /&gt;
        &amp;quot;min&amp;quot;,&lt;br /&gt;
        &amp;quot;max&amp;quot;,&lt;br /&gt;
        &amp;quot;hypot&amp;quot;,&lt;br /&gt;
        &amp;quot;pyt&amp;quot;,&lt;br /&gt;
        &amp;quot;pow&amp;quot;,&lt;br /&gt;
        &amp;quot;atan2&amp;quot;,&lt;br /&gt;
        &amp;quot;roundTo&amp;quot;,&lt;br /&gt;
        &amp;quot;map&amp;quot;,&lt;br /&gt;
        &amp;quot;fold&amp;quot;,&lt;br /&gt;
        &amp;quot;filter&amp;quot;,&lt;br /&gt;
        &amp;quot;indexOf&amp;quot;,&lt;br /&gt;
        &amp;quot;join&amp;quot;,&lt;br /&gt;
        &amp;quot;if&amp;quot;,&lt;br /&gt;
    ]&lt;br /&gt;
    // 検査対象の文字列を取得&lt;br /&gt;
    var match = exp.match(/([a-z][a-z0-9]+)\s*\(/g);&lt;br /&gt;
    if (!match) {&lt;br /&gt;
        return false;&lt;br /&gt;
    }&lt;br /&gt;
    // 検査対象の文字列が数学関数に含まれていない場合は true&lt;br /&gt;
    for (var i = 0; i &amp;lt; match.length; i++) {&lt;br /&gt;
        var func = match[i].replace(/\s*\(/, &amp;quot;&amp;quot;);&lt;br /&gt;
        if (functions.indexOf(func) == -1) {&lt;br /&gt;
            return true;&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
    // 検査対象の文字列が数学関数のみの場合は false&lt;br /&gt;
    return false;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== サードパーティライブラリ ==&lt;br /&gt;
&lt;br /&gt;
[https://github.com/silentmatt/expr-eval/ expr-eval]&lt;/div&gt;</summary>
		<author><name>Yuko</name></author>
	</entry>
</feed>