最終更新日 2024-06-09

外部マクロからHmGoogleGeminiを呼び出す

呼び出しサンプル

外部マクロから「HmGoogleGemini.mac」を呼び出し、
「テキスト選択なしで、特定のタスクに特化したマクロ」や「メニューから機能選択してタスクを実行するマクロ」を作成することが出来るようになっています。

「HmGoogleGeminiSample.mac」は、そのサンプルです。

一番簡単な例

以下のようなコードにすることで、

  • 質問内容は「100と100を足すと?」となり、
  • AIの返答が終わると、アウトプット枠へと(デバッグ)表示

するようになります。

jsmode @"WebView2\HmGoogleGemini";

とするのを忘れないようにしてください。

sample.mac などといった適当な名前で (HmGoogleGemini.mac と同じフォルダに)
jsmode @"WebView2\HmGoogleGemini";
js {

function onRequestQuestionText() {
    return "100と100は足すと?";
}

function onCompleteAnswerText(answer_text) {
    debuginfo(2);
    console.log(answer_text)
}

function myEntryPoint() {
    // 1を立てている関数は利用する。0なら(関数が定義してあったとしても)利用しない。
    let argobj = {
        onRequestQuestionText:1,
        onCompleteAnswerText:1
    };
    hidemaru.postExecMacroFile( currentmacrodirectory() + "\\HmGoogleGemini.mac", argobj );
}

myEntryPoint();

} // js

onRequestQuestionText()

質問内容が要求された時に呼び出される、

onCompleteAnswerText(answer_text)

AIからの返答が完了した直後に呼び出される

の2つを提供しています。

関数が存在するだけでは呼び出されない、マクロ呼び出しの際に引数が必要

それぞれの関数を利用するよ、という意味で

hidemaru.postExecMacroFile の引数の解説
let argobj = {
    onRequestQuestionText:1,
    onCompleteAnswerText:1
};
hidemaru.postExecMacroFile( currentmacrodirectory() + "\\HmGoogleGemini.mac", argobj );

といった記述があります。
1:その関数を利用する。
0:その関数は(定義してあったとしても)利用しない。

メニューなどを使うと便利になる

サンプルにあるように「メニュー」などを使って、
「選択テキスト」をどうしたいのか、
「あらかじめ登録してある処理内容」を選択することが出来るようになります。

PICTURE

HmGoogleGeminiMini.mac (HmGoogleGemini.mac と同じフォルダに)
jsmode @"WebView2\HmGoogleGemini";
js {

function onRequestQuestionText() {
    debuginfo(2);
    console.log("onRequestQuestionText")
    let selectedtext = getselectedtext();

    // テキストを選択してなかったら、呼び出し先(HmGoogleGemini.mac)のメニューは表示する(=true)
    if (!selectedtext) {
        return true;
    }

    let menu_label_list = 
    [
        "英翻訳",
        "日本訳"
    ];

    let menu_command_list =
    [
       "以下の文章を英語に翻訳してください。\n\n###\n" + selectedtext,
       "以下の文章を日本語に翻訳してください。\n\n###\n" + selectedtext,
    ];

    let selectedMenuID = menuarray(menu_label_list);

    // キャンセルしたら、呼び出し先(HmGoogleGemini.mac)のメニューもキャンセル(=false);
    if (selectedMenuID <= 0) {
        return false;
    }

    let selectedMenuIX = selectedMenuID - 1; // menu 関数は選択した項目が1オリジンで返ってくるので、0オリジンに直す。

    // 選択した項目に対応する質問内容
    let question_text = menu_command_list[selectedMenuIX];

    return question_text;
}

function onCompleteAnswerText(answer_text) {
    debuginfo(2);
    console.log("onCompleteAnswerText")

    // 行末に移動
    golineend();
    // 前後を改行で挟み、挿入
    insert("\n" + answer_text + "\n");
}

function myEntryPoint() {
    let argobj = {onRequestQuestionText:1, onCompleteAnswerText:1};
    hidemaru.postExecMacroFile( currentmacrodirectory() + "\\HmGoogleGemini.mac", argobj );
}

myEntryPoint();

} // js

onRequestQuestionText() の return に 文字列ではなく、true や false を返す意味。

  • onRequestQuestionText() の return に false:
    HmGoogleGemini.mac の本体ある「メニューを出す処理やその関連処理」をまるまるスキップ出来ます。
  • onRequestQuestionText() の return に true:
    HmGoogleGemini.mac の本体ある「メニューを出す処理やその関連処理」は実行されます。
HmGoogleGeminiMini.mac (HmGoogleGemini.mac と同じフォルダに)
jsmode @"WebView2\HmGoogleGemini";
js {

function onRequestQuestionText() {
    let contents = ["1+1は?", "2+2は?"];
    let selectedMenuId = menuarray(contents);

    // 0はメニューキャンセル
    if (selectedMenuId === 0) {
        // false を返すと、「HmGoogleGemini.mac の本体のメニューやその関連処理」をスキップ。
        // true を返すと、「HmGoogleGemini.mac の本来の挙動に委譲」する。
        return false;
    }

    else {
        return contents[selectedMenuId-1]
    }
}

function onCompleteAnswerText(answer_text) {
    let dll = loaddll(hidemarudir() + "\\HmOutputPane.dll");
    dll.dllFuncW.OutputW(hidemaruhandle(0), answer_text + "\r\n");
}

function myEntryPoint() {
    let argobj = {onRequestQuestionText:1, onCompleteAnswerText:1};
    hidemaru.postExecMacroFile( currentmacrodirectory() + "\\HmGoogleGemini.mac", argobj );
}

myEntryPoint();

} // js

全体のキャンセルなどを含めた場合分けをする場合には、ご自身のメイン関数(サンプルではmyEntryPoint)で行う

HmGoogleGeminiを呼び出すかどうかも含めたmenuやinput を利用したい場合は、以下のようにするのが良いでしょう。

このサンプルが一番基本的な「外部マクロからHmGoogleGeminiを呼び出すサンプル」になるかと思います。

HmGoogleGeminiTest.mac (HmGoogleGemini.mac と同じフォルダに)
jsmode @"WebView2\HmGoogleGemini";
js {

var _tempRequestQuestionText = "";
function onRequestQuestionText() {
    return _tempRequestQuestionText;
}

function onCompleteAnswerText(answer_text) {
    let dll = loaddll(hidemarudir() + "\\HmOutputPane.dll");
    dll.dllFuncW.OutputW(hidemaruhandle(0), answer_text + "\r\n");
}

// HmGoogleGeminiと同じマクロ空間を共有しているため、
// HmGoogleGemini が使うはずのない 関数名や変数名にする必要がある
function _errorToOutputPane(error_message) {
    let dll = loaddll(hidemarudir() + "\\HmOutputPane.dll");
    dll.dllFuncW.OutputW(hidemaruhandle(0), error_message + "\r\n");
}

function myEntryPoint() {

    let selectedtext = getselectedtext();

    // テキストを選択してなかったら、呼び出し先のメニューにそのままselecting情報を委譲(true)
    if (!selectedtext) {
        _errorToOutputPane("テキストの選択対象がありません");
        return true;
    }

    let menu_label_list = 
    [
        "英翻訳",
        "日本訳"
    ];

    let menu_command_list =
    [
       "以下の文章を英語に翻訳してください。\n\n###\n" + selectedtext,
       "以下の文章を日本語に翻訳してください。\n\n###\n" + selectedtext,
    ];

    let selectedMenuID = menuarray(menu_label_list);

    // 0はメニューキャンセル
    if (selectedMenuID === 0) {

        // HmGoogleGeminiのレンダリング枠を閉じておく
        renderpanecommand({target:"HmGoogleGemini", show:0});

        // キャンセルの意思なので、HmGoogleGemini自体を呼び出さない
        return;
    }

    // なんだかのメニュー内容を選んでいるのであれば...
    else {
        let selectedMenuIX = selectedMenuID - 1; // menu は選択した番号が1から始まるから0からになおす

        // メニューで選んだ内容を後で使えるようにする。
        _tempRequestQuestionText = menu_command_list[selectedMenuIX]

        let argobj = {onRequestQuestionText:1, onCompleteAnswerText:1};
        hidemaru.postExecMacroFile( currentmacrodirectory() + "\\HmGoogleGemini.mac", argobj );
    }
}

myEntryPoint();

} // js

全リセットを呼び出したい場合

以下のように hidemaru.setTimeout と onHtmlEventHandler("reset") を組み合わせ、
あたかもレンダリング枠のボタンを押したかのようにふるまうのば一番安全です。

HmGoogleGeminiTest.mac (HmGoogleGemini.mac と同じフォルダに)
jsmode @"WebView2\HmGoogleGemini";

js {

// このマクロの絶対パスは、最初に拾っておかなければならない。

var _currentMacroFileName = currentmacrofilename();
var _currentMacroDirectory = currentmacrodirectory();

// 質問内容の文字列の受け皿
var _bufRequestQuestionText = "";
function onRequestQuestionText() {

    // リセットなら、非同期でリセットコマンドを送信
    // 「レンダリング枠」で「全リセット」のボタンを押したかのような動きにしておく
    if (_bufRequestQuestionText == "reset") {
        hidemaru.setTimeout(() => { onHtmlEventHandler("reset"); }, 500);
        // HmOpenAiGptやHmGoogleGeminiの元来のメニューは出さない
        return false;
    }

    // それ以外なら、構築した質問内容のテキストを返す。
    return _bufRequestQuestionText;
}

function onCompleteAnswerText(answer_text) {
    // このマクロ上の文字列を対象にしているならば...
    if (filename() == _currentMacroFileName) {

        // 返答が返ってきたら、マクロを破損しないように、アウトプット枠に出力。
        _writeToOutputPane("\n" + answer_text + "\n");

    } else {

        // それ以外なら、返答が返ってきたら、そのままテキストとして編集エリアに挿入。
        insert("\n" + answer_text + "\n");
    }
}

// HmGoogleGeminiと同じマクロ空間を共有しているため、
// HmGoogleGemini が使うはずのない 関数名や変数名にする必要がある
function _writeToOutputPane(error_message) {
    // アウトプット枠は、\nでは改行されない。\r\nにする必要がある。
    // 元々が\r\nかもしれないので、一端全て\nにした後、全て\r\nにすることで綺麗にアウトプット枠でも改行される。
    error_message = error_message.replaceAll("\r\n", "\n").replaceAll("\n", "\r\n");

    let dll = loaddll(hidemarudir() + "\\HmOutputPane.dll");
    dll.dllFuncW.OutputW(hidemaruhandle(0), error_message + "\r\n");
}

function myEntryPoint() {

    _bufRequestQuestionText = "";

    let selectedText = hidemaru.getSelectedText();

    let menuhash = [];

    // テキストの選択がある時のメニュー
    if (selectedText) {
        menuhash = [
            {
                txt: "(&J)日本翻訳",
                cmd: "以下の文章を日本語に翻訳してください。翻訳の結果だけを表示してください。\n\n###\n\n" + selectedText
            },
            {
                txt: "(&E)英語翻訳",
                cmd: "以下の文章をアメリカ人風の英語に翻訳してください。翻訳の結果だけを表示してください。\n\n###\n\n" + selectedText
            },
            {
                txt: "\x01",
                cmd: ""
            },
            {
                txt: "(&R)全リセット",
                cmd: "reset"
            },
        ];
    }
    // テキストの選択が無い時のメニュー
    else {
        menuhash = [
            {
                txt: "(&R)全リセット",
                cmd: "reset"
            },
        ];
    }

    let menuTextList = menuhash.map(item => item.txt);
    let menuCommandList = menuhash.map(item => item.cmd);

    let selectedMenuID = menuarray(menuTextList);


    // 0はメニューキャンセル
    if (selectedMenuID === 0) {

        // HmGoogleGeminiのレンダリング枠を閉じておく
        renderpanecommand({ target: "HmGoogleGemini", show: 0 });

        // キャンセルの意思なので、HmGoogleGemini自体を呼び出さない
        return;
    }

    // なんだかのメニュー内容を選んでいるのであれば...
    else {
        let selectedMenuIX = selectedMenuID - 1; // menu は選択した番号が1から始まるから0からになおす

        // メニューで選んだ内容を後で使えるようにする。
        _bufRequestQuestionText = menuCommandList[selectedMenuIX]

        let argobj = { onRequestQuestionText: 1, onCompleteAnswerText: 1 };

        let scheduled = hidemaru.postExecMacroFile(`"${_currentMacroDirectory}\\HmGoogleGemini.mac"`, argobj);
    }
}

myEntryPoint();


} // js