Wichtiger Hinweis
Dies ist eine rein private Hobby-Seite. Das hier beschriebene Addon ist ein Nebenprodukt meiner persönlichen Nutzung und wird ohne jede geschäftliche Absicht zur Verfügung gestellt. Es gibt keine Gewinnerzielungsabsicht, keine Werbung und keine Datenerhebung. Die Nutzung erfolgt auf eigene Gefahr, ich übernehme keinerlei Garantie oder Haftung für die Funktion oder mögliche Probleme.
Worum geht es hier?
Mich hat es gestört, bei jedem neuen Chat auf Claude.ai manuell das Opus-Modell auswählen zu müssen. Deshalb habe ich ein kleines Firefox-Addon geschrieben, das dies für mich automatisch erledigt.
Da vielleicht auch andere diesen kleinen Helfer nützlich finden, habe ich es im Add-on Store von Firefox hochgeladen. Diese Seite hier dient nur dazu, die Funktionsweise für technisch Interessierte zu dokumentieren.
Wo finde ich das Addon?
Wer es ausprobieren möchte, kann es kostenlos im offiziellen Firefox Add-on Store finden. Die Installation ist wie bei jeder anderen Erweiterung.
Zusätzlich gibt es eine experimentelle Version für Chrome-Nutzer: Experimentelle Chrome-Version.
Was das Addon tut
- Automatische Umschaltung: Wenn ein neuer, leerer Chat auf der Claude.ai-Webseite erkannt wird, versucht das Addon, das Opus-Modell auszuwählen.
- Erkennung bestehender Chats: Das Addon sollte erkennen, ob in einem Chat bereits Nachrichten geschrieben wurden oder Text im Eingabefeld steht, und in diesem Fall nichts tun, um bestehende Konversationen nicht zu stören.
- Anpassbare Erkennung: Im Einstellungs-Popup der Erweiterung kann man wählen, wie "sensibel" die Erkennung sein soll (z.B. ob auch Text im Eingabefeld einen Chat als "in Benutzung" markiert).
Technische Notizen / Funktionsweise
Grundlegender Aufbau
Die Erweiterung ist simpel gehalten und besteht aus:
- Einem Hintergrund-Addon (
background.js
), das prüft, ob der aktive Tab die Claude.ai-Seite ist. - Einem Content-Addon (
switch_to_opus.js
), das auf der Seite selbst läuft und die notwendigen Klicks simuliert. - Einem kleinen Popup-Fenster für die Einstellungen.
1. Ladeerkennung - Warten bis die Seite bereit ist
Eine der größten Herausforderungen ist es zu erkennen, wann die Claude.ai-Seite wirklich bereit ist. Die Seite ist eine React-App, die dynamisch lädt.
// Verbesserte Funktion zur Erkennung ob die Seite wirklich bereit ist
async function waitForPageReady() {
console.log("Warte auf vollständiges Laden der Seite...");
// Schritt 1: Warte auf DOM Content Loaded (falls noch nicht geschehen)
if (document.readyState === 'loading') {
await new Promise(resolve => {
document.addEventListener('DOMContentLoaded', resolve, { once: true });
});
}
// Schritt 2: Warte auf wichtige Claude.ai Elemente
const criticalSelectors = [
// Modell-Selektor ist das wichtigste Element
'button[data-testid="model-selector-dropdown"]',
'button[aria-haspopup="menu"]',
// Eingabefeld
'textarea[placeholder*="Talk to Claude"]',
'textarea[placeholder*="Message Claude"]',
// Container für Nachrichten
'main, [role="main"]'
];
let foundElements = 0;
const maxWaitTime = 30000; // 30 Sekunden Maximum
const checkInterval = 500; // Alle 500ms prüfen
const startTime = Date.now();
while (foundElements < 2 && (Date.now() - startTime) < maxWaitTime) {
foundElements = 0;
for (const selector of criticalSelectors) {
if (document.querySelector(selector)) {
foundElements++;
}
}
// Spezialprüfung: Suche Button mit Claude Text
if (foundElements < 2) {
const buttons = document.querySelectorAll('button');
const claudeButton = Array.from(buttons).find(btn => {
const text = btn.textContent || '';
return text.includes('Claude') &&
(text.includes('Sonnet') || text.includes('Opus'));
});
if (claudeButton) foundElements++;
}
if (foundElements < 2) {
await new Promise(resolve => setTimeout(resolve, checkInterval));
}
}
// Schritt 3: Finale Wartezeit für React-Initialisierung
await new Promise(resolve => setTimeout(resolve, 1000));
console.log("Seite ist bereit!");
}
2. Chat-Status Erkennung
Bevor das Addon umschaltet, prüft es ob der Chat bereits in Benutzung ist:
// Prüft ob der Chat bereits Nachrichten enthält
function isChatInUse(sensitivity = 'high') {
console.log("Prüfe Chat-Status mit Sensitivität:", sensitivity);
// Verschiedene Selektoren für Chat-Nachrichten
const messageSelectors = [
'[data-testid^="message-"]',
'[data-testid="conversation-turn"]',
'.conversation-turn',
'[role="article"]',
'div[class*="message"]',
'div[class*="turn"]'
];
// Prüfe ob Nachrichten vorhanden sind
for (const selector of messageSelectors) {
const messages = document.querySelectorAll(selector);
if (messages.length > 0) {
// Prüfe ob es echte Nachrichten sind (nicht nur Platzhalter)
for (const msg of messages) {
const text = msg.textContent || '';
if (text.trim().length > 10) {
return true; // Chat ist in Benutzung
}
}
}
}
// Bei hoher Sensitivität prüfen wir auch das Eingabefeld
if (sensitivity === 'high') {
const inputField = document.querySelector('textarea[placeholder*="Talk to Claude"]') ||
document.querySelector('textarea[placeholder*="Message Claude"]');
if (inputField) {
const hasContent = (inputField.value && inputField.value.trim().length > 0);
if (hasContent) {
return true; // Eingabefeld enthält Text
}
}
}
return false; // Chat ist neu/leer
}
3. Hauptfunktion zum Umschalten
Die eigentliche Umschaltlogik mit mehreren Klick-Strategien:
async function switchToOpus() {
// Verwende die optimierte Wartefunktion
await waitForPageReady();
// NEUE PRÜFUNG: Ist der Chat bereits in Benutzung?
const chatInUse = await isChatInUse(currentSensitivity);
if (chatInUse) {
console.log("Chat wird bereits verwendet - kein Wechsel zu Opus");
return;
}
// Prüfe ob Opus bereits aktiv ist
if (isOpusAlreadySelected()) {
console.log("Opus ist bereits aktiv!");
notifySuccess();
return;
}
// Finde Modell-Selector mit verschiedenen Strategien
const selectors = [
'button[data-testid="model-selector-dropdown"]',
'button[aria-haspopup="menu"]',
'[role="button"][aria-haspopup="menu"]'
];
let modelButton = null;
for (const selector of selectors) {
modelButton = document.querySelector(selector);
if (modelButton) break;
}
// Fallback: Suche Button mit Text
if (!modelButton) {
const buttons = document.querySelectorAll('button');
modelButton = Array.from(buttons).find(btn => {
const text = btn.textContent || '';
return text.includes('Claude') &&
(text.includes('Sonnet') || text.includes('Opus'));
});
}
if (!modelButton) {
console.log("Kein Modell-Button gefunden.");
return;
}
// Versuche Dropdown zu öffnen (mehrere Methoden)
const success = await triggerDropdown(modelButton);
if (!success) {
console.log("Dropdown konnte nicht geöffnet werden.");
return;
}
// Warte kurz und suche Opus-Option
await new Promise(resolve => setTimeout(resolve, 500));
const menuItems = document.querySelectorAll('[role="menuitem"], [role="option"]');
const opusItem = Array.from(menuItems).find(item =>
item.textContent.includes('Claude Opus 4') &&
!item.textContent.includes('Sonnet')
);
if (opusItem) {
// Klicke auf Opus-Option
simulatePointerClick(opusItem);
console.log("Opus ausgewählt!");
// Warte und prüfe ob erfolgreich
await new Promise(resolve => setTimeout(resolve, 1000));
if (isOpusAlreadySelected()) {
notifySuccess();
}
}
}
4. Klick-Simulation Strategien
Da Claude.ai eine React-App ist, funktionieren normale Klicks oft nicht. Das Addon verwendet mehrere Ansätze:
Strategie | Beschreibung | Code-Beispiel |
---|---|---|
React Props | Direkter Zugriff auf React Event Handler | element.__reactInternalInstance.memoizedProps.onClick() |
Pointer Events | Simulation vollständiger Maus-Interaktion | new PointerEvent('click', {...}) |
Keyboard Navigation | Tastatur-basierte Navigation | new KeyboardEvent('keydown', {key: 'Space'}) |
5. Mehrfachversuche und Fehlerbehandlung
// Globale Variablen für Retry-Logik
let isProcessing = false;
let attempts = 0;
const maxAttempts = 5;
async function attemptSwitch(forceSwitch = false) {
if (isProcessing) {
console.log("Bereits in Bearbeitung, überspringe...");
return;
}
if (attempts >= maxAttempts) {
console.log("Maximale Versuche erreicht.");
isProcessing = false;
return;
}
// Bei manuellem Switch ignorieren wir die Chat-Prüfung
if (!forceSwitch) {
const chatInUse = await isChatInUse(currentSensitivity);
if (chatInUse) {
console.log("Chat wird bereits verwendet");
isProcessing = false;
attempts = 0;
return;
}
}
isProcessing = true;
attempts++;
console.log(`Versuch ${attempts}/${maxAttempts}`);
switchToOpus().then(() => {
// Prüfe nach 2 Sekunden ob erfolgreich
setTimeout(() => {
if (!isOpusAlreadySelected()) {
// Nicht erfolgreich, erneut versuchen
isProcessing = false;
setTimeout(() => attemptSwitch(forceSwitch), 3000);
} else {
console.log("Erfolgreich auf Opus umgeschaltet!");
isProcessing = false;
attempts = 0;
}
}, 2000);
}).catch(error => {
console.error("Fehler beim Umschalten:", error);
isProcessing = false;
});
}
6. DOM Mutation Observer
Das Addon überwacht dynamische Änderungen auf der Seite:
// Beobachte DOM-Änderungen für dynamisch geladene Elemente
const observer = new MutationObserver(async (mutations) => {
for (const mutation of mutations) {
if (mutation.type === 'childList') {
for (const node of mutation.addedNodes) {
if (node.nodeType === Node.ELEMENT_NODE) {
// Prüfe ob es der Modell-Button ist
const isModelButton = node.matches && (
node.matches('button[data-testid="model-selector-dropdown"]') ||
node.matches('button[aria-haspopup="menu"]') ||
(node.tagName === 'BUTTON' && node.textContent &&
(node.textContent.includes('Sonnet') ||
node.textContent.includes('Opus')))
);
if (isModelButton && attempts === 0 && !isProcessing) {
console.log("Modell-Button dynamisch geladen!");
// Prüfe ob es ein neuer Chat ist
const chatInUse = await isChatInUse(currentSensitivity);
if (!chatInUse) {
chrome.storage.local.get(['autoSwitch'], (data) => {
if (data.autoSwitch !== false &&
!isOpusAlreadySelected()) {
attemptSwitch(false);
}
});
}
}
}
}
}
}
});
// Starte Observer früh
observer.observe(document.documentElement, {
childList: true,
subtree: true
});
Erweiterte Funktionen
Kommunikation zwischen Background und Content Script
Die Erweiterung nutzt Chrome's Message Passing API für die Kommunikation:
Nachricht | Richtung | Zweck |
---|---|---|
checkAndSwitch |
Background → Content | Startet automatische Umschaltung |
forceSwitch |
Background → Content | Erzwingt manuelle Umschaltung |
updateSensitivity |
Background → Content | Aktualisiert Sensitivitätseinstellung |
log |
Content → Background | Sendet Log-Nachrichten |
Timing und Optimierungen
- Frühe Injektion: Content Script wird mit
document_start
injiziert - Mehrere Trigger: onUpdated, onCompleted, onDOMContentLoaded, onHistoryStateUpdated
- Retry-Mechanismus: Bis zu 5 Versuche mit 3 Sekunden Abstand
- Timeout-Schutz: Maximale Wartezeit von 30 Sekunden
Häufige Probleme und Lösungen
F: Das Addon schaltet nicht um. Woran kann das liegen?
A: Manchmal ändert Claude.ai den Aufbau der Webseite. Die Selektoren im Code müssen dann angepasst werden. Ein Neuladen der Seite (F5) kann manchmal helfen. Das Addon hat mehrere Fallback-Mechanismen eingebaut.
F: Warum verwendet das Addon so viele verschiedene Klick-Methoden?
A: React-Apps verhindern oft normale JavaScript-Klicks. Die verschiedenen Methoden erhöhen die Erfolgschance.
F: Was bedeutet die Sensitivitätseinstellung?
A:
• Hoch: Wechselt nicht, wenn Text im Eingabefeld steht
• Niedrig: Wechselt nur nicht, wenn bereits Nachrichten gesendet wurden
F: Kann das Addon meinen Account gefährden?
A: Das Addon klickt nur auf Buttons, die jeder Nutzer auch manuell klicken kann. Es führt keine Aktionen aus, die nicht auch normal im Browser möglich wären. Trotzdem gilt: Die Nutzung ist auf eigene Gefahr.