この記事で伝えたいこと
- WebUI版Geminiの「5時間使用量上限」と「リセットまでの残り時間」を、画面左上角
(0,0)の 4×4pxの極小ピクセル だけで常時可視化するUserscriptを作りました。 - 「ブロック数(1〜5個)= 残り時間」「ブロックの色(青/緑/黄/赤)= 使用率」 の一体型UIで、作業画面を1ミリも邪魔せず、チャットのリロードも発生させません。
- GitHubリポジトリ: https://github.com/gumi278/gemini-quote-indicator
本記事について
本記事の執筆およびコードのリファクタリングにあたっては、設計検討・デバッグのログをもとにAI(Gemini)を活用し、筆者による実機検証と加筆修正を行っています。
対象プラン
Google AI プラン加入アカウント(Google AI Plus / Google AI Pro / Google AI Ultra)
※ https://gemini.google.com/usage を開いた際に5時間枠の使用量メーターが表示されるアカウントに対応しています。
背景:Argonの消費が激しすぎる問題と「確認リロード」のジレンマ
最近のGemini WebUIでは、推論能力の高い Argon(特に思考深度・高)と、軽量・高速な Flash を使い分ける場面が増えました。
複雑なアルゴリズムや破綻が許されない設計を組むときは、Flashでは潜在的な不具合が混入しやすく、どうしてもArgonに頼らざるを得ない局面があります。しかし、ProプランであってもArgonのクォータ消費量は桁違いに大きく、思考深度を高くして数往復やり取りしただけで、5時間枠のゲージがあっという間に削られていきます。
ここで強いストレスになるのが 「今、Argonを投入して大丈夫か?それともリセットまで待つか?」が対話画面から見えないこと です。
WebUIの左下から「設定 > 使用量上限(gemini.google.com/usage)」を開けば正確なパーセントとリセット時刻は確認できます。しかし、この /usage は独立したルーティングになっているため、確認して元のチャット画面に戻ると会話セッションの再取得(リロード)が走り、作業の流れも思考も分断されてしまいます。
「画面を切り替えずに、今のクォータ状況と残り時間を把握したい。ただし、作業領域に邪魔なプログレスバーや数字のボックスは置きたくない」
この矛盾を解決するために、徹底的に引き算をしたインジケーターを設計しました。
UI設計:「4pxの点」と「アスペクト比」だけで直感する
既存のクォータ監視系の拡張機能やスクリプトの多くは、画面のヘッダーや右上に 5h Limit: [==== ] 45% のようなボックスを表示します。最初は同じようなフロート枠を試作してみましたが、チャットやコードを追っている視界に常時ボックスが居座るのは想像以上にノイズでした。
そもそも、私たちが知りたいのは「42%なのか43%なのか」という1%単位の正確な数字ではありません。
- 「今、心置きなくArgonを回していい状態か(青)」
- 「そろそろFlashに切り替えて温存すべきか(黄)」
- 「次のリセットまであと何時間くらい耐えればいいか(長さ)」
この意思決定に必要なシグナルだけを抽出し、ブラウザのビューポート左上角 (0,0) に 4×4pxの正方形ブロック を並べる仕様に辿り着きました。
なぜ「使用量」と「残り時間」を混ぜて一体化したのか?
独立した「使用量ピクセル」すら置かず、「残り時間を表すブロック(1〜5個)そのものが、現在の使用率の色で染まる」 という一体型ルールにしています。
-
横の長さ(ブロック数 1〜5個)= リセットまでの残り時間
- 4×4pxの塊1つが「残り1時間(切り上げ)」を表します。
- 残り4時間12分なら 5個、残り10分(あるいは残り10秒)なら 1個 点灯します。
- ブロック間に
1pxの隙間(gap: 1px)を入れているため、わざわざ数を数えなくても、周辺視野に入る「縦4pxに対する横のアスペクト比と刻み」だけでおおよその残り時間がアナログ目測できます。
-
全体の色 = クォータ使用量とステータス
- 青(0〜30%): 余裕あり。迷わずArgonを投入できるタイミング。
- 緑(31〜60%): 通常運用。
- 黄(61〜80%): 消費進行。Argonを控え、Flashへの切り替えを検討するライン。
- 赤(81〜100%): 上限間近。残り時間ブロックが1個(1時間以下)ならそのまま使い切る判断もアリ。
- 灰(Error): 後述する自動リロードが3回連続タイムアウトした際の安全停止(サーキットブレーカー作動)状態。
また、コンテナには pointer-events: none を指定しているため、すぐ下にあるGeminiのハンバーガーメニュー等のクリック操作を一切妨害しません。
技術的な試行錯誤とアーキテクチャの決定
実装にあたり、Googleの堅牢なセキュリティ仕様とブラウザ(特に普段使いしているSafari / WebKit)の省電力制御による「4つの壁」にぶつかりました。
壁1:GoogleのCSP(Trusted Types)による innerHTML 拒否
最初は手軽な box.innerHTML = '...' で要素を差し込もうとしましたが、Google側のContent Security Policy(require-trusted-types-for 'script')によって即座にブロックされました。
これに対しては、文字列からのHTMLパースを一切使わず、document.createElement と style.cssText、および textContent = '' の純粋なDOM APIのみでブロックを構築することで、CSPに100%準拠したクリーンなインジェクトを実現しています。
壁2:X-Frame-Options: DENY による裏 <iframe> の遮断
/usage が独立したURLなら、チャット画面の裏にサイズ 0×0 の非表示 <iframe> を置いて定期リロードさせれば完全自動化できるのでは?と考えました。
しかし実際に検証したところ、同一オリジン(gemini.google.com)であってもサーバーから X-Frame-Options: DENY およびSandbox制限が返され、フレーム内のDOMアクセスはブラウザレベルで完全に遮断されました。
壁3:Safari(WebKit)のバックグラウンドタブ凍結
ならば「別タブで /usage を開いておき、localStorage 経由でチャットタブに数値を送る(マルチタブ協調)」というアプローチはどうでしょうか。
これも検証しましたが、Safariの省電力管理は非常にアグレッシブで、同じウィンドウ内の隠れた裏タブはタイマーが間引かれるだけでなく、放置するとJavaScriptスレッドごと完全にフリーズ(Page Suspension)してしまいます。
解決策:「仮想デスクトップ(Spaces)への単体ウィンドウ放置」方式
内部RPC(難読化された通信)を無理やり解析・偽装して叩く方法は、Google側のアップデートで即死するうえ、アカウントのレートリミット(429 Too Many Requests)を踏むリスクがあります。
そこで今回は発想を転換し、「macOSの使っていない仮想デスクトップ(Spaces)に、単体のSafariウィンドウとして /usage を開きっぱなしにしておく」 という運用を前提とした Companion Window(コンパニオンウィンドウ)アーキテクチャ を採用しました。
- 同一ウィンドウの「裏タブ」ではなく、別Space上の「独立したウィンドウの最前面タブ」として置いておくことで、WebKitのタブ凍結とmacOSのApp Napを回避できます(※Dockへ最小化せず開いたまま別Spaceに置くのがコツです)。
- センサー側(
/usage)が3分ごとに自動リロードしてlocalStorageを書き換えると、メイン画面(/app)のwindow.addEventListener('storage', ...)が即座に発火し、ラグ0秒で左上のLEDが更新されます。
無限リロードバグの発見と「サーキットブレーカー」の実装
開発中、センサー側の /usage ウィンドウを別Spaceに放置していたところ、Safariのアドレスバーでリロードエフェクトが毎秒休みなく走り続け、DOMが再構築されない という危険な現象に遭遇しました。
何が起きていたのか?
原因は、1秒ごと(1000ms) の setInterval 内で単純に if (Date.now() - mountTime >= 3分) location.reload(); を呼んでいたことでした。
- 3分経過して
location.reload()が呼ばれる。 - GeminiのWebアプリは重いため、サーバーから応答が返りページが切り替わる(メモリ空間が破棄される)までに数秒かかる。
- 特に別Spaceでの放置中や、裏で重い処理(ローカルLLMやComfyUIでの画像生成など)を回している環境では、レスポンスにさらなるタイムラグが生じる。
- ページ切り替えが完了する前の「1秒後」に次のタイマーが発火し、進行中のリロードリクエストを自ら強制キャンセル(Abort)して、またゼロから
location.reload()を呼び直してしまう。
このまま放置すると、毎秒Googleのサーバーへリクエストを連打し続けることになり、アクセス制限に引っかかるのは時間の問題でした。
揮発性グローバル変数と3回リトライによる防波堤
この問題に対処するため、ページのライフサイクル(リロードが成功すればメモリが初期化される特性)を活かしたステートマシンを組み込みました。
// 揮発性グローバル変数(リロード成功時に自動で0に初期化される)
let reloadCalledAt = 0; // 0: 未コール / 正数: 最終reloadコール時刻(ms)
let reloadCount = 0; // reload試行回数(最大3回)
function handleAutoReload() {
if (!isUsagePage()) return;
const now = Date.now();
// ケース1: 通常待機中(3分経過で初回リロード)
if (reloadCalledAt === 0) {
if (now - mountTime >= reloadIntervalMs) {
reloadCount = 1;
reloadCalledAt = now;
location.reload();
}
return;
}
// ケース2: リロード要求後、まだページが切り替わっていない場合
if (now - reloadCalledAt >= CONFIG.RELOAD_TIMEOUT_MS) { // 10秒待機
if (reloadCount < CONFIG.MAX_RELOAD_ATTEMPTS) {
// 10秒経っても終わっていなければ再リロードし、基準時刻を現在時刻で更新(最大3回)
reloadCount++;
reloadCalledAt = now;
location.reload();
} else {
// 3回(計30秒)試行してもダメなら異常状態を記録し、タイマーを完全停止
localStorage.setItem(CONFIG.STORAGE_KEYS.ERROR_STATE, 'true');
updateDisplayFromStorage();
if (timerId !== null) clearInterval(timerId);
}
}
}
- フレーム数(カウンタ)ではなく
Date.now()の実時間差分で判定し、リトライのたびにreloadCalledAt = nowでチェックポイントを更新するため、タイマーのバースト等で処理が一瞬で駆け抜けてしまうことがありません。 - 万が一、ネットワーク切断等で10秒×3回(計30秒)リロードが失敗し続けた場合は、インジケーターを「灰色(
#80868b)」に変えてポーリングを完全停止(サーキットブレーカー作動) します。ユーザーが/usageを手動リロードすれば自動で正常状態へ復帰します。
導入方法
Safari(Userscripts)はもちろん、Chrome / Edge / Firefox / Brave(Tampermonkey / Violentmonkey)でも動作します。
- ブラウザにユーザースクリプト拡張機能(SafariならMac App Storeの無料拡張機能 Userscripts)を導入します。
- 以下のGitHubリポジトリから
gemini-quote-indicator.user.jsをインストール(またはコピペ登録)します。 - 新規ウィンドウ(
Cmd + N)を1つ開き、https://gemini.google.com/usageを表示させた状態で、使っていない仮想デスクトップ(Spaces)に置いておきます。 - メイン画面でGeminiと対話すれば、左上の4pxに現在の状態が常時表示されます。
おわりに(PR・Issue歓迎)
スクリプト冒頭の CONFIG オブジェクトを書き換えるだけで、ブロックのサイズ(Retina環境で4pxが小さすぎる場合は6pxにする等)や、色のしきい値、リロード間隔を自由にカスタマイズできるよう構造化してあります。
現在は日本語UI(現在の使用量)と英語UI(Current usage)の両方に対応していますが、他の言語ロケールの正規表現追加や、よりスマートな同期アイデアなどのPull Request(Good First Issue)も大歓迎です。
普段はローカルLLM環境や認知アーキテクチャ等の開発を進めており、今回はその開発補助としてGeminiのArgonを使い倒す中で生まれたツールでした。同じように「Argonのクォータ配分に悩んでいる」「画面を汚さずに残量を知りたい」という方の参考になれば幸いです!
作者から
この記事のdraft作成をArgonに依頼したのですが、結構長い context にも関わらず必要なところを適切に拾って作ってくれたので、ほぼ手直し無しで掲載することにしました。
とりあえず、具体的な私の動作環境です:
- MacbookPro M1Pro (メモリ16GB)
- macOS Tahoe 26.6.2
- safari 26.6.2
- userscripts 4.8.6
- Gemini Proプラン(※PlusやUltraプランでも
/usageに上限が出る仕様なら適合するはずです)
申し訳ないのですが、windowsやlinux、他のブラウザなどの環境ではテストできていません。
理論上動くはず、のつもりです。
現在ちょっと困った現象が起こって、とりあえず対策をして様子見中です。
- ACアダプター接続
- クラムシェル状態
- Magic Keyboard
- Magic Mouse
- Type-C (Alt mode) 外部ディスプレイ
- caffeinateコマンド
- 有線LAN無しのWiFi接続
この状態で
- 離席するので画面をロック
- 戻ってきてメインスクリーンで作業再開
- あれ?いつまで経ってもインジケーターが更新されないなあ?
- センサースクリーンを見たら使用量100%なってた
ということがありました。
原因の特定はできていないのですが、5分データ更新がない場合はインジケーターの色をGrayに落とすことで注意喚起することにしました。
推測では、おそらく一度ロック状態(クラムシェルだからスリープですか?)になった後は表示されていない仮想スクリーンのjavascriptスレッドは停止したままになるのかも?しれません。
これはこの場合だけではなく、センサー側が働かない場合というエッジケースの顕在と考えて、向き合うことにしました。
そもそも使用量取得の完全なゴールは「Googleが使用量を返すAPIを実装してくれること」ですが、対話型のWebUIという生成AIの入り口とも言える場所に、あえてAPIを実装する理由がGoogleにはありません。
かといって、これ以上のことをこちら側でやろうとすると「実装が別の実装を呼ぶ(解決するための実装が何かの不都合を生み、それを解決するためにまた別の実装を行う必要がある、という数珠繋ぎの連鎖)」と思ったのでやめました。
そのため、理想は理想で持ちつつ「持続可能で放置可能な何かの解決を1つ試みるくらいはする」というのがちょうどいい距離感だと考えました。ソフトウェアで無理やり自動復旧させるのではなく、異常を知らせて一度ユーザー操作で見に行かせる。つまり一言で言えば『運用でカバー』ってことです。
それで気がつきました、仮想スクリーンに使用量が表示されているのなら、Argonを使うタイミングで切り替えて見に行けば?とも思いました。
ただ、実際にはその「見に行く」という操作自体が開発者のアテンションを削り、思考を分断するので、私は「その場で」見たい人間です。「これがあれば100%完全に把握できるんだ」とは思ってほしくないのですが、このあたりのラインがわかっている人にはきっと役に立つと思い、せっかく作ったので公開します。
よかったら使ってみてください。