概要
ごくごく小規模な生成AIWebアプリを開発するのに、GASを使ってみた記録です。
生成AIをWebで活用するにはサーバーサイドスクリプトが欲しくなると思います。APIキーを見えなくしたいからです。Python Flaskとかでも良いけどデプロイ先を考えるのが面倒くさいと思っていたところ、GASでWebアプリが作れることを思い出したので試してみました。
できたもの
送信されたワード(文章でもいいです。140字まで)を25カ国語くらいに翻訳して一気に表示するサイトです。
ローディング画面を作るのをサボったため、送信後の間がちょっと不安になります…。
あと phonics
という名前で発音を表示してもらおうと思ったのですが、生成具合が不安定です。
使ったもの
Clasp
ClaspはGAS上のファイルとかをローカルに pull したり、逆にサーバー上に push したりするライブラリです。
GASの画面上のエディターで編集するのは大変なので導入したほうが良いです。
Rollup / Babel
RollupとBabelを用いて ts ファイルからデプロイ用の js ファイルをビルドしました。
これにより node のモジュールを一緒にデプロイできます。
ただし、今回インストールしたモジュールは @google/generative-ai のみで、それも SchemaType しか使わなかったので、別にバンドルしなくても良かったかもしれません。
Gemini
Googleで完結しようと思ってAIモデルはGeminiを選択しました。
無料枠があります。すごい!
gemini-2.0-flash-exp を使用しました。
Almond.css
HTMLファイルのスタイリングに使用しました。クラスレスなタイプのCSSで、HTMLのネイティブなエレメントにスタイルが当たります。
詰まったところ
doPost編
GASではPOSTを受けるのにdoPost()という関数を使います。
function doPost(e) {
console.log("event", e);
const sentence = e.sentence !== undefined ? e.sentence : "hello, world!";
}
こんな感じで始めましたが、サーバー上でのテストでリクエスト(eの中身)を設定する方法がいまいち分からなかったです。
そこでローカルからcurlでPOSTリクエストをしようと思いましたが、 CORS エラーが出てきて失敗…
結局上記 "hello, world" を決め打ちでテストしました。
fetch編
@google/generative-ai は内部的にfetch APIを使っているみたいなのですが、これがGAS上では動かず。
UrlFetchApp.fetch
を使って自力でリクエストするように書き直しました。
なので @google/generative-ai で使っているのは型情報のみです。
doGet編
GASで静的なHTMLを配置しようと思ったら、ブラウザからのGETリクエストを受けることになるので doGet()
を使います。
function doGet() {
return HtmlService.createHtmlOutputFromFile("get.html");
}
get.html には簡素なフォームを置き、サブミットイベントが発火したら doPost()
へのリクエストを自力で投げます(e.preventDefault()使用)
const sendForm = document.getElementById("sendForm");
sendForm.addEventListener("submit", async (event) => {
event.preventDefault();
// フォーム送信処理
try {
google.script.run
.withSuccessHandler((data) => {
console.log(data);
}).doPost({sentence: sentenceVal})
}catch(e){
console.log(e)
}
}
HTMLネイティブのフォーム送信処理ではなく、 google.script.run.withSuccessHandler
を使うことが必要だったみたいでけっこう時間を取られました。
一応できた
もう一度載せますが、 Words Avenue です。
カスタムドメインが使えたらな、というのと、
静的HTML(get.html)は本当にただのHTMLなので 久々に Vanilla な JS を書いてDOM操作をしました。
そのあたりで大規模なアプリにはなりようがない気がしますが、小規模な実験的アプリならGASでやるのもありじゃないかなと思いました。