Google Gemini に相談
概要
Googleの生成AIである「Gemini」を利用するためのMeryマクロです。
このマクロは、以下の手順で動作します:
- ユーザーがエディタ内でテキストを選択します。 (未選択の場合、アクティブ行が対象になります)
- 選択したテキストをプロンプトとしてGemini APIに送信します。
- APIからの応答を取得し、エディタに表示します。
- 応答内容は履歴として保持され、次回のリクエストに利用されます。
注意事項 (必ず読んでからご利用ください)
- 本マクロは、2024/11/17 時点では無料利用が可能なGemini APIを利用します。今後、Googleの改定により有償になったり、設定誤り等で費用が発生しても、作者は一切の補償をいたしません。
- Gemini APIでは、その特性上、プロンプトとして送信した文字列がAIの学習に使われるものと思われます。くれぐれも機密情報やプライバシーに関わる情報を指定しないように注意してください。
利用前提
- Google AI Studioで、Gemini APIキーを取得していること (参考 API キーを取得する | Gemini API | Google AI for Developers)
利用方法
- 【APIキーの設定】「Mery.exe」が配置されているフォルダに「GEMINI_API_KEY.txt」ファイルを作成し、取得したGemini APIキーを記載する。
- 【マクロの配置】以下のソースコード (gemini.js) をMeryの「Macros」ディレクトリに配置し、「マクロ > 選択」メニューから登録する。
- 【マクロの実行】プロンプトにしたい文字列を選択し、マクロを実行する。選択したテキストがプロンプトとしてGemini APIに送信され、応答がエディタに表示する。
- 【会話の履歴数を調整】会話の履歴は設定した個数分が保持され、文脈として利用される。適宜、ソースコード内の「HISTORY_SIZE」の数を調整する。
- ※数を増やすほど会話内容を保持して少ない言葉でも文脈を理解するようになりますが、メモリ使用量が増えたり動作が重くなる可能性があります。
- ※開いている文書ごとに履歴を保持するため、文脈をリセットしたいときは新しい文書を開いて会話をやり直してください。
ソースコード (gemini.js)
#title = "Gemini に相談"
BeginUndoGroup();
// 会話の履歴を保持する数 (質問と回答で1セット)
// 数を増やすと、会話の履歴が増えるが、Meryのメモリ使用量が増えたり、
// 動作が重くなる可能性があるため、適宜調整してください。
// document.Tag に保持されるため、開いているファイルを閉じると履歴は消えます。
var HISTORY_SIZE = 5;
var ConsoleClass = function () {
this.log = function (str) {
outputBar.Writeln(str)
}
}
var console = new ConsoleClass();
var doc = document;
var sel = document.selection;
main();
function main() {
var promptText = "";
var promptLength = 0;
if (sel.IsEmpty) {
sel.StartOfLine(false, mePosLogical);
sel.EndOfLine(true, mePosLogical);
}
promptText = sel.Text.trim();
promptLength = sel.TextLength;
sel.Clear();
// プロンプトが空の場合はエラー
if (promptLength === 0) {
alert("プロンプトを入力してください。");
return;
}
// 文字数が多すぎる場合はエラー
if (promptLength > 10000) {
alert("文字数が多すぎます。10000 文字以下にしてください。");
return;
}
// 確認メッセージ
// 先頭800文字に切り詰める
var confirmText = promptText.substring(0, 800);
if (promptText.length !== confirmText.length) {
confirmText += " ……";
}
confirmText = confirmText.replace(/\n\s+/g, "\n");
result = Confirm("以下の内容で Gemini に相談しますか?\n"
+ "## プライバシー・機密情報が含まれないことを確認してください ##\n\n"
+ "---\n"
+ confirmText);
if (!result) {
return;
}
// カーソル位置移動
sel.EndOfLine(false, mePosLogical);
sel.NewLine();
var activeY = sel.GetActivePointY(mePosLogical);
// Gemini API に問い合わせ
var ret = callGeminiAPI(promptText);
// 結果を表示
sel.Text = "\n---\n\n" + ret + "\n\n---\n";
sel.SetActivePoint(
mePosLogical,
1,
activeY,
false
)
}
function callGeminiAPI(promptText) {
// APIキーの取得
var gemini_api_key = readAPIKey();
try {
var http = new ActiveXObject("Msxml2.ServerXMLHTTP");
var url = 'https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash-latest:generateContent?key=' + gemini_api_key;
http.open('POST', url, true);
http.setRequestHeader('Content-Type', 'application/json');
http.send('{"contents":' + createGeminiRequest(promptText) + '}');
// 応答待機
var timeout = 60;
var retrySec = 0;
var startX = sel.GetActivePointX(mePosLogical);
while (http.readyState != 4) {
if (retrySec > timeout) {
break;
}
http.waitForResponse(1);
retrySec += 1;
sel.Text = ".";
}
// 進捗表示文字を削除
sel.SetActivePoint(
mePosLogical,
startX,
sel.GetActivePointY(mePosLogical),
true
);
sel.Delete();
var ret = "";
// 応答ステータスチェック
if (http.readyState == 4 && http.status == 200) {
// JSONをパースして必要なテキストを抽出
var response = JSON.parse(http.responseText);
if (response.candidates && response.candidates.length > 0) {
response.candidates.forEach(function (candidate) {
if (candidate.content && candidate.content.parts) {
candidate.content.parts.forEach(function (part) {
if (part.text) {
ret += part.text;
}
});
}
});
}
} else {
http.abort();
return "タイムアウト";
}
appendReturnTextToTag(promptText, ret);
// 整形しつつ返す
return ret
.trim()
.replace(/。 +/g, "。");
} catch (e) {
console.log(e.stack);
return "エラーが発生しました: " + e.message;
}
}
// document.Tag の内容を加味してGeminiのリクエストを作成
function createGeminiRequest(promptText) {
var tag = document.Tag;
// 以前の会話が存在しない場合
if (!tag.exists("gemini-text")) {
return '[{"parts":[{"text": "' + promptText + '"}]}]';
}
var geminiText = new Array(tag("gemini-text"));
geminiText.push({
"role": "user",
"parts": [{ "text": promptText }]
});
return JSON.stringify(geminiText);
}
// document.Tag にGeminiの応答を追加
function appendReturnTextToTag(userText, modelText) {
var tag = document.Tag;
if (!tag.exists("gemini-text")) {
tag("gemini-text") = [
{
"role": "user",
"parts": [{ "text": userText }]
},
{
"role": "model",
"parts": [{ "text": modelText }]
}
]
} else {
var geminiText = new Array(tag("gemini-text"));
geminiText.push({
"role": "user",
"parts": [{ "text": userText }]
});
geminiText.push({
"role": "model",
"parts": [{ "text": modelText }]
});
// 会話セット (user, model 1往復) が5セットを超えた場合、最初のセットを削除
while (geminiText.length > HISTORY_SIZE * 2) {
geminiText.shift();
}
tag("gemini-text") = geminiText;
}
}
function readAPIKey() {
// APIキーが設定されている場合はそれを返す
if (Tag.exists("GEMINI_API_KEY")) {
return Tag("GEMINI_API_KEY");
}
var fso = new ActiveXObject("Scripting.FileSystemObject");
var meryPath = editor.FullName;
var meryDir = fso.GetParentFolderName(meryPath);
var file = fso.OpenTextFile(meryDir + "/GEMINI_API_KEY.txt", 1);
var key = file.ReadAll().trim();
file.Close();
Tag("GEMINI_API_KEY") = key;
return key;
}
スポンサーリンク