Retour à l'accueil

Saisie Vocale Claude Russe Hors Ligne

Instructions pour créer un système de saisie vocale local pour Claude.ai en russe avec Whisper, serveur Flask et extension Chrome. L'app Menu Bar gère les services, tout fonctionne hors ligne sans limites. Code complet et solutions aux erreurs courantes.

Voix Hors Ligne dans Claude : Whisper + rumps + Chrome
Advertisement 728x90

# Saisie vocale locale pour Claude en russe : réalisé en une soirée

Un serveur Flask sur localhost:5555 reçoit l'audio d'une extension Chrome, le transcrit localement avec Whisper (modèle small), et envoie le texte dans le champ de saisie de Claude.ai. Une app Menu Bar créée avec rumps gère le démarrage et l'arrêt, avec l'icône du menu qui passe de 🤖 à 🎤. Tout fonctionne entièrement hors ligne, sans limites, totalement gratuit. Compatible macOS, facilement adaptable à Linux.

Prérequis

Installez les dépendances :

  • Whisper : brew install openai-whisper
  • ffmpeg : brew install ffmpeg
  • Python 3 et Chrome

Testez Whisper :

Google AdInline article slot
whisper --version

Vérifiez la présence du russe dans la sortie. Créez la structure du projet :

mkdir -p ~/projects/whisper-claude
cd ~/projects/whisper-claude
python3 -m venv venv
source venv/bin/activate
pip install flask flask-cors rumps
mkdir -p extension/icons

L'environnement virtuel est indispensable sur macOS Ventura+ pour éviter les conflits avec Python système.

Serveur de transcription (server.py)

Le serveur gère POST /transcribe avec audio en base64 ou fichier, appelle Whisper avec --language ru --fp16 False (crucial pour Apple Silicon), et renvoie un JSON avec le texte. Inclut les endpoints /ping et /shutdown.

Google AdInline article slot
from flask import Flask, request, jsonify
from flask_cors import CORS
import subprocess
import tempfile
import os
import threading

app = Flask(__name__)
CORS(app)

WHISPER_PATH = "/opt/homebrew/bin/whisper"
WHISPER_MODEL = "small"
PORT = 5555

@app.route("/ping", methods=["GET"])
def ping():
    return jsonify({"status": "ok"})

@app.route("/transcribe", methods=["POST"])
def transcribe():
    if "audio" not in request.files:
        return jsonify({"error": "No audio file found"}), 400

    audio_file = request.files["audio"]

    with tempfile.NamedTemporaryFile(suffix=".webm", delete=False) as tmp:
        tmp_path = tmp.name
        audio_file.save(tmp_path)

    try:
        result = subprocess.run(
            [
                WHISPER_PATH, tmp_path,
                "--model", WHISPER_MODEL,
                "--language", "ru",
                "--output_format", "txt",
                "--output_dir", tempfile.gettempdir(),
                "--fp16", "False",
            ],
            capture_output=True, text=True, timeout=60
        )

        txt_path = tmp_path.replace(".webm", ".txt")
        if os.path.exists(txt_path):
            with open(txt_path, "r", encoding="utf-8") as f:
                text = f.read().strip()
            os.unlink(txt_path)
        else:
            text = result.stdout.strip()

        return jsonify({"text": text})

    except subprocess.TimeoutExpired:
        return jsonify({"error": "Timeout"}), 500
    except Exception as e:
        return jsonify({"error": str(e)}), 500
    finally:
        os.unlink(tmp_path)

@app.route("/shutdown", methods=["POST"])
def shutdown():
    threading.Timer(0.5, lambda: os._exit(0)).start()
    return jsonify({"status": "shutting down"})

if __name__ == "__main__":
    print(f"[Whisper] Starting on port {PORT}, model: {WHISPER_MODEL}")
    app.run(host="127.0.0.1", port=PORT, debug=False)

Astuce clé : --fp16 False évite les plantages sur puces M1/M2/M3.

Contrôleur Menu Bar (menubar.py)

L'app vérifie les ports toutes les 5 secondes avec rumps.Timer, démarre/arrête les serveurs dans des threads. La config TOOLS permet d'ajouter des services en une ligne. Statut affiché en emojis : ✅ en cours, ⏸ en pause.

import rumps
import subprocess
import threading
import os
import time
import urllib.request

BASE_DIR = "~/projects/whisper-claude"
VENV_PYTHON = os.path.join(BASE_DIR, "venv/bin/python3")

TOOLS = [
    {
        "name": "Whisper → Claude",
        "script": os.path.join(BASE_DIR, "server.py"),
        "port": 5555,
        "description": "Saisie vocale russe",
    },
]

# ... (reste du code comme dans l'original, abrégé pour l'exemple)

class AILauncher(rumps.App):
    # implémentation comme ci-dessus
    pass

if __name__ == "__main__":
    AILauncher().run()

Code complet inclut ToolController avec bascule, vérification et arrêt propre.

Google AdInline article slot

Extension Chrome

manifest.json (MV3) :

{
  "manifest_version": 3,
  "name": "Whisper pour Claude",
  "version": "1.0",
  "permissions": ["activeTab", "scripting"],
  "host_permissions": [
    "https://claude.ai/*",
    "http://127.0.0.1:5555/*"
  ],
  "content_scripts": [
    {
      "matches": ["https://claude.ai/*"],
      "js": ["content.js"],
      "run_at": "document_end"
    }
  ],
  "background": {
    "service_worker": "background.js"
  }
}

background.js gère le contenu mixte : audio base64 de content.js → fetch en background → serveur.

const SERVER_URL = "http://127.0.0.1:5555";

chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
  if (message.action === "transcribe") {
    handleTranscribe(message.audio).then(sendResponse);
    return true;
  }
});

async function handleTranscribe(base64Audio) {
  // base64 vers Blob vers FormData vers POST /transcribe
}

content.js ajoute un bouton 🎤 à côté du champ de saisie et capture MediaRecorder.

Points forts

  • Modèle Whisper small local : ~1 Go, transcrit le russe hors ligne en 10-30 secondes.
  • Architecture extensible : ajoutez de nouveaux outils IA à TOOLS sans modifier le code.
  • Sécurité : localhost uniquement, pas d'appels cloud, CORS pour Chrome.
  • Compatibilité : macOS Apple Silicon (fp16=False), Linux avec ajustements de chemins.
  • Performances : timeout 60 secondes, nettoyage automatique des fichiers temporaires.

— Editorial Team

Advertisement 728x90

Lire ensuite