Powrót do strony głównej

Wprowadzanie głosowe Claude rosyjski offline

Instrukcja tworzenia lokalnego systemu wprowadzania głosowego dla Claude.ai po rosyjsku z Whisper, serwerem Flask i rozszerzeniem Chrome. Aplikacja Menu Bar zarządza usługami, wszystko działa offline bez limitów. Pełny kod i rozwiązania typowych błędów.

Offline głos w Claude: Whisper + rumps + Chrome
Advertisement 728x90

Lokalny głosowy wprowadzanie dla Claude po polsku: implementacja w jeden wieczór

Serwer Flask na localhost:5555 odbiera audio z rozszerzenia Chrome, transkrybuje je lokalnym Whisper z modelem small i zwraca tekst do pola wprowadzania claude.ai. Aplikacja Menu Bar na rumps zarządza uruchomieniem, ikona w pasku menu zmienia się z 🤖 na 🎤. Wszystko działa offline, bez limitów, za darmo. Działa na macOS, łatwo adaptowalne pod Linuxa.

Wymagania wstępne

Zainstaluj zależności:

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

Sprawdź Whisper:

Google AdInline article slot
whisper --version

W wyniku powinna być widoczna obsługa polskiego. Stwórz strukturę projektu:

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

Środowisko wirtualne jest obowiązkowe na macOS Ventura+ aby uniknąć konfliktów z systemowym Pythonem.

Serwer transkrypcji (server.py)

Serwer obsługuje POST /transcribe z audio w base64 lub plikiem, wywołuje Whisper z flagami --language pl --fp16 False (kluczowe dla Apple Silicon), zwraca JSON z tekstem. Są endpointy /ping i /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": "Nie znaleziono pliku audio"}), 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", "pl",
                "--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": "Przekroczono czas"}), 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] Uruchamiam się na porcie {PORT}, model: {WHISPER_MODEL}")
    app.run(host="127.0.0.1", port=PORT, debug=False)

Kluczowa sprawa: --fp16 False zapobiega awarii na M1/M2/M3.

Kontroler Menu Bar (menubar.py)

Aplikacja monitoruje porty co 5 sekund przez rumps.Timer, uruchamia/zatrzymuje serwery w wątkach. Konfiguracja TOOLS pozwala dodawać serwisy jedną linią. Status pokazany emoji: ✅ działa, ⏸ zatrzymany.

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": "Głosowe wprowadzanie po polsku",
    },
]

# ... (pozostały kod jak w oryginale, skrócony dla przykładu)

class AILauncher(rumps.App):
    # implementacja jak wyżej
    pass

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

Pełny kod zawiera ToolController z przełącznikiem, check_all i graceful shutdown.

Google AdInline article slot

Rozszerzenie Chrome

manifest.json (MV3):

{
  "manifest_version": 3,
  "name": "Whisper dla 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 rozwiązuje mixed content: base64-audio z content.js → fetch w background → serwer.

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) {
  // konwersja base64 → Blob → FormData → POST /transcribe
}

Content.js dodaje przycisk 🎤 obok pola wprowadzania, przechwytuje MediaRecorder.

Co ważne

  • Lokalny Whisper small: ~1 GB, transkrybuje polski offline w 10-30 sek.
  • Rozszerzalna architektura: nowe AI dodaje się do TOOLS bez przepisywania kodu.
  • Bezpieczeństwo: tylko localhost, brak zapytań chmurowych, CORS dla Chrome.
  • Kompatybilność: macOS Apple Silicon (fp16=False), Linux z dostosowanymi ścieżkami.
  • Wydajność: timeout 60 sek, automatyczne czyszczenie tempfile.

— Editorial Team

Advertisement 728x90

Czytaj dalej