ChatGPTを使っていて、地味に欲しかったのが 「新しいチャットを新規タブで開く」ボタン です。
通常の「新しいチャット」を押すと、現在のタブがそのまま新しいチャットに切り替わります。
しかし、
- 今の会話を残したまま別の質問をしたい
- 調査中に複数のチャットを並行して開きたい
- Ctrl / Cmd + クリックを意識せず、ワンクリックで新しいタブを作りたい
という使い方をしていると、専用ボタンが欲しくなります。
そこでTampermonkeyで、ChatGPTの「新しいチャット」ボタンの横に 「新規タブ」ボタンを自動追加 するUserScriptを作りました。
完成イメージ
ChatGPTの画面にある
「新しいチャット」
の近くに、
「新規タブ」
というボタンを追加します。
「新規タブ」をクリックすると、現在開いているチャットはそのまま残して、ChatGPTの新しいチャット画面を別タブで開けます。
複数のテーマについてChatGPTと並行して会話するときにかなり便利です。
Tampermonkeyスクリプト
以下をTampermonkeyに登録します。
// ==UserScript==
// @name Add New Chat Link
// @namespace tampermonkey
// @version 1.0
// @description Duplicates the "New Chat" button and adds a link.
// @match https://chatgpt.com/*
// @grant none
// ==/UserScript==
(function () {
'use strict';
const TARGET_TEXT = 'New Chat';
const CLONED_TEXT = 'New Tab';
const ADDED_LINK_SELECTOR = 'a[data-tm-new-chat-link]';
/**
* Searches for the "New Chat" span within the target root or its descendants.
*/
function findTargetSpan(targetRoot) {
// Check the root element itself
if (
targetRoot instanceof HTMLSpanElement &&
targetRoot.innerText.trim() === TARGET_TEXT
) {
return targetRoot;
}
// Check the descendants
if (targetRoot.querySelectorAll) {
for (const span of targetRoot.querySelectorAll('span')) {
if (span.innerText.trim() === TARGET_TEXT) {
return span;
}
}
}
return null;
}
function initialize() {
const root = document.querySelector('#root');
if (!root) {
console.warn('[Tampermonkey] #root not found');
return;
}
const observerOptions = {
childList: true,
subtree: true
};
let observer;
function process(targetRoot) {
// Stop the observer while processing
observer?.disconnect();
try {
const span = findTargetSpan(targetRoot);
if (!span) {
return;
}
// Get the ancestor button
const button = span.closest('button');
if (!button) {
return;
}
// Get the button's parent
const parent = button.parentElement;
if (!parent) {
return;
}
// Do nothing if already added
if (parent.querySelector(ADDED_LINK_SELECTOR)) {
return;
}
// Clone the button
const buttonCloned = button.cloneNode(true);
// Change "New Chat" to "New Window" in the cloned button
const clonedSpan = findTargetSpan(buttonCloned);
if (clonedSpan) {
clonedSpan.innerText = CLONED_TEXT;
}
buttonCloned.classList.replace("bg-primary-ghost-hover", "hover:bg-primary-ghost-hover");
buttonCloned.classList.add("data-[state=open]:bg-primary-ghost-hover");
buttonCloned.firstChild.classList.replace("text-emphasis", "text-default");
// Create the <a> tag
const anchor = document.createElement('a');
anchor.href = '/';
anchor.target = "_blank";
anchor.dataset.tmNewChatLink = 'true';
anchor.appendChild(buttonCloned);
// Add to the parent of the original button
parent.prepend(anchor);
console.log('[Tampermonkey] link added:', anchor);
} finally {
// Resume observation after processing
observer?.observe(root, observerOptions);
}
}
observer = new MutationObserver((mutations) => {
// Stop at the beginning of the callback
observer.disconnect();
try {
for (const mutation of mutations) {
for (const node of mutation.addedNodes) {
if (!(node instanceof Element)) {
continue;
}
process(node);
}
}
} finally {
observer.observe(root, observerOptions);
}
});
// Initial execution
process(root);
// Observe child element additions under #root
observer.observe(root, observerOptions);
}
if (document.readyState === 'complete') {
initialize();
} else {
window.addEventListener('load', initialize, { once: true });
}
})();
何をしているのか
仕組み自体はシンプルです。
ChatGPTの画面から「新しいチャット」という文字列を持つspanを探し、その親にあるbuttonを取得します。
const span = findTargetSpan(targetRoot);
const button = span.closest('button');
そして、そのボタンを丸ごと複製します。
const buttonCloned = button.cloneNode(true);
ゼロからCSSを書くのではなく、 ChatGPT自身のボタンを複製している のがポイントです。
これによって、ChatGPT側のUIと比較的馴染みやすい見た目になります。
複製したボタンについては、表示を
新しいチャット
から
新規タブ
へ変更します。
const clonedSpan = findTargetSpan(buttonCloned);
if (clonedSpan) {
clonedSpan.innerText = CLONED_TEXT;
}
新しいタブでChatGPTを開く
複製したボタンはa要素の中に入れています。
const anchor = document.createElement('a');
anchor.href = '/';
anchor.target = '_blank';
ポイントは、
anchor.target = '_blank';
です。
これによってボタンを押したとき、現在のChatGPT画面を置き換えるのではなく、 新しいタブでChatGPTを開く ようになります。
現在のチャットを残したまま別の会話を開始できます。
MutationObserverを使っている理由
ChatGPTは画面遷移のたびにページ全体を読み込み直す一般的なWebサイトとは少し違い、JavaScriptによって画面の内容が動的に変更されます。
そのため、
window.addEventListener('load', ...)
だけを利用してボタンを追加すると、ChatGPT側でUIが再描画されたときに追加したボタンが消える可能性があります。
そこでMutationObserverを利用しています。
observer = new MutationObserver((mutations) => {
// ...
});
そして、
observer.observe(root, {
childList: true,
subtree: true
});
として、#root以下に追加されるDOM要素を監視します。
ChatGPT側でUIが更新された場合にも「新しいチャット」を探して、必要なら「新規タブ」を追加し直す仕組みです。
ボタンが大量に増えないようにする
MutationObserverを使うと注意したいのが、自分自身がDOMを書き換えた結果まで監視対象になることです。
そのため、このスクリプトでは追加するリンクに
anchor.dataset.tmNewChatLink = 'true';
という目印を付けています。
HTML上では、
<a data-tm-new-chat-link="true">
のようになります。
そして、
if (parent.querySelector(ADDED_LINK_SELECTOR)) {
return;
}
として、すでにボタンが存在している場合には追加しません。
さらにprocess()中は一時的にMutationObserverを停止し、処理が終わったら監視を再開しています。
observer?.disconnect();
try {
// DOM操作
} finally {
observer?.observe(root, observerOptions);
}
これによって、自分自身のDOM変更を拾って処理が繰り返されることを避けています。
インストール方法
Tampermonkeyを導入済みなら設定は簡単です。
- Tampermonkeyのダッシュボードを開く
- 「新規スクリプトを追加」を選択
- 最初から入っているコードを削除
- 上記のUserScriptを貼り付ける
- 保存する
- ChatGPTを再読み込みする
これでChatGPTの画面に「新規タブ」が追加されます。
こういう人には特におすすめ
ChatGPTを「1つのチャットですべて質問する」のではなく、 テーマごとにチャットを分けて使っている人 には特に便利です。
例えば、
タブ1:プログラムについて質問
タブ2:メール文章を作成
タブ3:技術情報を調査
タブ4:アイデア出し
といった使い方がしやすくなります。
元のチャットから離れずに次のチャットを作れるので、ChatGPTを大量に使うほど恩恵があります。
注意点
このUserScriptはChatGPTのDOM構造を利用しています。
特に、
#root
や、
span.closest('button')
といったChatGPT側のHTML構造を前提にしています。
そのため、ChatGPTのUIがアップデートされると動作しなくなる可能性があります。
また、「新しいチャット」という日本語表示を探しているため、ChatGPTを英語など別の言語設定で使用している場合は、そのままでは動作しません。
例えば英語UIに対応させる場合は、検索対象をNew chatにも対応させるなどの変更が必要です。
TampermonkeyのUserScriptはWebページ上でJavaScriptを実行する仕組みなので、内容を理解できるスクリプトだけを導入することも重要です。
まとめ
今回のUserScriptを入れると、ChatGPTの「新しいチャット」の近くに 「新規タブ」ボタン を追加できます。
やっていることは小さなUI変更ですが、
「今のチャットを残しておきたい → 新しいタブを開く → ChatGPTを開く」
という操作を、
「新規タブ」を1回クリック
に短縮できます。
複数のチャットを並行して使うことが多い人ほど便利になるTampermonkeyスクリプトです。
ChatGPTをブラウザで頻繁に使っているなら、試してみる価値があります。