Claude AI Opus Autoswitch

Ein privates Addon, das automatisch auf das Opus-Modell umschaltet.

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.
Seite lädt Warte auf Bereitschaft Prüfe Chat-Status Schalte um

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.

Die waitForPageReady() Funktion:
// 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:

Die isChatInUse() Funktion:
// 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:

Die switchToOpus() Funktion:
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

Retry-Logik mit attemptSwitch():
// 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:
// 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.