diff --git a/addon/doc/es/readme.md b/addon/doc/es/readme.md index e313f1a..f1b4202 100644 --- a/addon/doc/es/readme.md +++ b/addon/doc/es/readme.md @@ -2,98 +2,260 @@ **Autor:** Muhammad Gagah [muha.aku@gmail.com](mailto:muha.aku@gmail.com) -Native Speech Generation es un complemento para NVDA que integra **Google Gemini AI** para generar voz de alta calidad y sonido natural directamente en NVDA. Proporciona una interfaz limpia y totalmente accesible para convertir texto en audio, admitiendo tanto **narración de un solo hablante** como **diálogos dinámicos de varios hablantes**. +Native Speech Generation es un complemento para NVDA que integra **Google Gemini AI** para generar voz natural y de alta calidad directamente desde NVDA. +Ofrece una interfaz limpia y totalmente accesible para convertir texto en audio, con soporte tanto para **narración de un solo hablante** como para **diálogos dinámicos con varios hablantes**. -Este complemento está diseñado para flujos de trabajo fluidos, interacción centrada en la accesibilidad y un control de voz flexible adecuado para narración, diálogos y producción de contenido de audio. +Este complemento está pensado para ofrecer un flujo de trabajo fluido, una interacción centrada en la accesibilidad y un control flexible de la voz, ideal para narración, diálogos y producción de contenido de audio. --- ## Características ### Generación de voz de alta calidad -* **Elija entre:** - * **Gemini Flash:** Calidad estándar, generación rápida, baja latencia. - * **Gemini Pro:** Premium, voces más realistas (modelo de pago). + +* Elige entre: + * **Gemini Flash**: calidad estándar, generación rápida y baja latencia. + * **Gemini Pro**: calidad premium y voces más realistas (modelo de pago). ### Modos de hablante único y múltiple -* **Narración de hablante único** para conversión de texto a voz estándar. -* **Modo multihablante (2 hablantes)** para diálogos con voces distintas. + +* **Narración de un solo hablante** para conversión de texto a voz estándar. +* **Modo multihablante (2 hablantes)** para diálogos con voces diferenciadas. ### Control de voz avanzado -* **Asignación de nombres a los hablantes:** Asigne nombres personalizados (ej. *Juan*, *María*) en el modo multihablante. La IA asigna automáticamente las voces según los nombres en el guion. -* **Instrucciones de estilo:** Proporcione indicaciones como *"Habla en un tono alegre"* o *"Narra con calma"* para guiar el habla. -* **Control de temperatura:** Ajuste la variación y creatividad de la salida: - * Valores bajos → voz más estable y predecible. - * Valores altos → voz más expresiva y variada. + +* **Nombres de los hablantes** + Asigna nombres personalizados (por ejemplo, *Juan* o *María*) en el modo multihablante. + La IA asigna las voces automáticamente según los nombres usados en el guion. +* **Instrucciones de estilo** + Puedes dar indicaciones como *"Habla con un tono alegre"* o *"Narra con calma"* para orientar la interpretación. +* **Control de temperatura** + Ajusta la variación y la creatividad del resultado: + * Valores más bajos -> voz más estable y predecible. + * Valores más altos -> voz más expresiva y variada. + +### Interfaz accesible y clara + +* Totalmente accesible con lectores de pantalla. +* Las opciones avanzadas están dentro de un panel desplegable para que el diálogo principal se mantenga simple y enfocado. + +### Flujo de trabajo fluido + +* El audio se reproduce automáticamente después de la generación. +* El audio generado puede reproducirse de nuevo o guardarse como archivo `.wav` de alta calidad. +* Está diseñado para reducir al mínimo la fricción durante la generación y la reproducción repetidas. + +### Carga inteligente de voces y caché + +* Las voces disponibles se obtienen dinámicamente desde la API de Gemini. +* Los datos de voz se almacenan en caché durante **24 horas** para reducir llamadas a la API y acelerar el inicio. + +### Hablar con IA (conversación en vivo) + +* **Chat de voz en tiempo real**: mantén una conversación hablada natural y de baja latencia con Gemini. +* **Grounding con Google Search**: permite que la IA acceda a información en tiempo real desde la web durante la conversación. +* **Interrumpible**: puedes interrumpir a la IA en cualquier momento hablando o pulsando "Detener conversación". +* **Personalizable**: usa la voz y las instrucciones de estilo que hayas seleccionado. +* **Control del nivel de razonamiento**: elige entre `Sin razonamiento`, `Bajo`, `Medio` o `Alto` según la profundidad de razonamiento que necesites. +* **Continuidad tras la reconexión**: el contexto reciente de la conversación se restaura automáticamente después de reconectar, sin necesidad de un interruptor de memoria independiente. +* **Streaming más estable**: reconexión mejorada (backoff + retry) y búfer de audio adaptativo para mayor resistencia en redes inestables. --- ## Requisitos + * NVDA (se recomienda la versión más reciente). -* Conexión a internet activa. +* Conexión activa a Internet. * Una **clave de API de Google Gemini** válida. --- ## Instalación -1. Descargue el paquete desde la [página de versiones (Releases)](https://github.com/MuhammadGagah/native-speech-generation/releases). -2. Instálelo como cualquier complemento de NVDA. -3. Reinicie NVDA cuando se le solicite. + +1. Descarga el paquete más reciente del complemento desde la + **página de versiones:** + [https://github.com/MuhammadGagah/native-speech-generation/releases](https://github.com/MuhammadGagah/native-speech-generation/releases) +2. Instálalo como cualquier complemento estándar de NVDA. +3. Reinicia NVDA cuando se te solicite. --- -## Configuración de la clave de API (Requerido) -1. Cree una clave en [Google AI Studio](https://aistudio.google.com/apikey). -2. Vaya a: **Menú NVDA → Herramientas → Native Speech Generation**. -3. Haga clic en **"Configuración de clave API"**. -4. Pegue su clave en el campo **GEMINI API Key**. -5. Haga clic en **Aceptar**. +## Configuración de la clave de API (obligatoria) + +1. Crea una clave de API en **Google AI Studio**: + [https://aistudio.google.com/apikey](https://aistudio.google.com/apikey) +2. Abre NVDA y ve a: + **Menú de NVDA -> Herramientas -> Native Speech Generation** +3. Haz clic en **"Configuración de la clave API"**. +4. Esto abre la configuración de NVDA directamente en la categoría *Native Speech Generation*. +5. Pega tu **clave de API de Gemini** en el campo *GEMINI API Key*. +6. Haz clic en **Aceptar** para guardar. + +Las claves guardadas se almacenan de forma segura mediante **Windows DPAPI**, por lo que el valor cifrado no puede descifrarse en otro equipo con Windows ni en otra cuenta de usuario. + +Para entornos avanzados o gestionados, también puedes proporcionar la clave mediante la variable de entorno **`GEMINI_API_KEY`**. El complemento la usará automáticamente cuando no haya una clave guardada disponible. --- ## Cómo usar -Abra el diálogo usando: -* **NVDA+Control+Mayús+G**, o -* **Menú NVDA → Herramientas → Native Speech Generation** -### Elementos de la interfaz -* **Texto a convertir:** Ingrese o pegue su texto. -* **Instrucciones de estilo:** (Opcional) Guía para el tono o emoción. -* **Seleccionar modelo:** Flash o Pro. -* **Modo de hablante:** Único o Multihablante (2). +Abre el diálogo usando: + +* **NVDA+Control+Shift+G**, o +* **Menú de NVDA -> Herramientas -> Native Speech Generation** + +### Elementos principales de la interfaz + +* **Texto a convertir** + Escribe o pega el texto que quieras convertir en voz. +* **Instrucciones de estilo (opcional)** + Añade indicaciones sobre tono, emoción o forma de hablar. +* **Seleccionar modelo** + * Flash (calidad estándar) + * Pro (alta calidad) +* **Modo de hablante** + * Un solo hablante + * Multihablante (2) --- ## Generación de voz ### Modo de hablante único -1. Seleccione **Hablante único**. -2. Elija una voz, ingrese el texto y haga clic en **Generar voz**. + +1. Selecciona **Un solo hablante**. +2. Elige una voz en la lista *Seleccionar voz*. +3. Introduce tu texto. +4. Añade instrucciones de estilo si lo deseas. +5. Haz clic en **Generar voz**. +6. El audio se reproducirá automáticamente cuando termine la generación. + +--- ### Modo multihablante -1. Seleccione **Multihablante (2)**. -2. Asigne un nombre y voz a cada hablante. -3. Formatee el texto así: - `Alicia: Hola Roberto, ¿cómo estás?` - `Roberto: ¡Muy bien! El clima es genial.` + +1. Selecciona **Multihablante (2)**. +2. Para cada hablante: + * Introduce un **nombre de hablante** único. + * Elige una **voz** distinta. +3. Da formato al texto para que cada línea comience con el nombre del hablante seguido de dos puntos. + +**Ejemplo:** + +``` +Alicia: Hola, Bob. ¿Cómo estás hoy? +Bob: ¡Muy bien, Alicia! Hace un tiempo fantástico. +``` + +4. Haz clic en **Generar voz**. + Las voces se asignarán automáticamente según los nombres de los hablantes. + +--- + +## Hablar con IA (modo en vivo) + +Disfruta de una conversación de voz bidireccional y natural con Gemini. + +1. Configura la **Voz** y las **Instrucciones de estilo** que quieras en el diálogo principal. + *(Nota: Hablar con IA actualmente solo admite el modo de un solo hablante).* +2. Haz clic en **Hablar con IA**. +3. En la nueva ventana: + * **Iniciar conversación**: inicia la sesión. Habla por tu micrófono. + * **Detener conversación**: finaliza la sesión. + * **Grounding con Google Search**: marca esta casilla para permitir que Gemini busque respuestas en la web (por ejemplo, noticias o el clima actual). + * *Nota: esta casilla se oculta mientras la conversación está activa. Detén la conversación para cambiarla.* + * **Nivel de razonamiento**: elige entre `Sin razonamiento`, `Bajo`, `Medio` o `Alto`. + * **Micrófono**: silencia o activa tu micrófono. + * **Volumen**: ajusta el volumen de reproducción de la IA. + +--- + +## Configuración avanzada + +* Activa **Configuración avanzada (temperatura)** para mostrar el control deslizante. +* **Rango de temperatura**: + * `0.0` -> resultado más determinista y estable. + * `1.0` -> equilibrio predeterminado. + * `2.0` -> resultado más creativo y variado. --- -## Hablar con IA (Modo en vivo) -1. Haga clic en **Hablar con IA**. -2. **Iniciar conversación:** Comience a hablar por el micrófono. -3. **Conexión con Google Search:** Marque esta casilla para información en tiempo real. +## Resumen de botones + +* **Generar voz** - Inicia la generación de voz. +* **Reproducir** - Vuelve a reproducir el último audio generado. +* **Hablar con IA** - Abre la interfaz de conversación de voz en tiempo real. +* **Guardar audio** - Guarda el último audio como archivo `.wav`. +* **Configuración de la clave API** - Abre la configuración del complemento en los ajustes de NVDA. +* **Ver voces en AI Studio** - Abre Google AI Studio en el navegador. +* **Cerrar** - Cierra el diálogo (o pulsa `Escape`). --- ## Gestos de entrada -Personalizables en: **Menú NVDA → Preferencias → Gestos de entrada**. -* Predeterminado: **NVDA+Control+Mayús+G** + +Personalizable desde: +**Menú de NVDA -> Preferencias -> Gestos de entrada -> Native Speech Generation** + +Gesto predeterminado: + +* **NVDA+Control+Shift+G** - Abrir el diálogo de Native Speech Generation. --- -## Contribuciones -* Reporte errores en la sección de **Issues**. -* Envíe mejoras mediante **Pull Requests**. +## Guía de desarrollo y contribución + +Si quieres desarrollar o modificar este complemento, sigue los pasos siguientes. + +### Configuración del entorno + +* **Python de 32 bits (se recomienda 3.11.9)** + [https://www.python.org/downloads/release/python-3119/](https://www.python.org/downloads/release/python-3119/) +* **SCons 4.9.1 o superior** + + ``` + pip install scons + ``` +* **Herramientas GNU Gettext** (opcional, recomendado para localización) + * Normalmente vienen preinstaladas en Linux/Cygwin. + * Windows: [https://gnuwin32.sourceforge.net/downlinks/gettext.php](https://gnuwin32.sourceforge.net/downlinks/gettext.php) +* **Markdown 3.8+** (para conversión de documentación) + + ``` + pip install markdown + ``` + +### Dependencias adicionales + +Instala las dependencias de audio de Talk With AI directamente en la ruta de bibliotecas del complemento: + +``` +python.exe -m pip install google-genai pyaudio --target "D:/myAdd-on/Native-Speech-Generation/addon/globalPlugins/NativeSpeechGeneration/lib" +``` + +Ajusta la ruta según tu directorio local del código fuente del complemento. + +Para la implementación actual de Talk With AI basada solo en audio, no necesitas `opencv-python`, `pillow` ni `mss`. + +Después, copia lo siguiente desde tu instalación de Python a: + +``` +addon/globalPlugins/NativeSpeechGeneration/lib +``` + +* Carpeta `zoneinfo` +* Archivo `secrets.py` + +--- + +## Contribuir + +Las contribuciones, sugerencias y reportes de errores son muy bienvenidos. + +* Abre un **Issue** para reportar errores o solicitar funciones. +* Envía un **Pull Request** para contribuir con código. + +**Contacto** -**Contacto:** [muha.aku@gmail.com](mailto:muha.aku@gmail.com) | [GitHub](https://github.com/MuhammadGagah) +* Email: `muha.aku@gmail.com` +* GitHub: [https://github.com/MuhammadGagah](https://github.com/MuhammadGagah) diff --git a/addon/doc/id/readme.md b/addon/doc/id/readme.md index a525452..ea2bf74 100644 --- a/addon/doc/id/readme.md +++ b/addon/doc/id/readme.md @@ -1,11 +1,11 @@ -# Native Speech Generation for NVDA +# Native Speech Generation untuk NVDA **Penulis:** Muhammad Gagah [muha.aku@gmail.com](mailto:muha.aku@gmail.com) -Native Speech Generation adalah add-on NVDA yang mengintegrasikan **Google Gemini AI** untuk menghasilkan ucapan berkualitas tinggi dan terdengar alami langsung di dalam NVDA. -Add-on ini menyediakan antarmuka yang bersih dan sepenuhnya dapat diakses untuk mengubah teks menjadi audio, mendukung **narasi pembicara tunggal** dan **dialog multi-pembicara yang dinamis**. +Native Speech Generation adalah add-on NVDA yang mengintegrasikan **Google Gemini AI** untuk menghasilkan ucapan berkualitas tinggi dengan suara yang terdengar alami langsung di NVDA. +Add-on ini menyediakan antarmuka yang bersih dan sepenuhnya dapat diakses untuk mengubah teks menjadi audio, serta mendukung **narasi pembicara tunggal** maupun **dialog multi-pembicara yang dinamis**. -Add-on ini dirancang untuk alur kerja yang lancar, interaksi yang mengutamakan aksesibilitas, dan kontrol suara yang fleksibel, cocok untuk narasi, dialog, dan produksi konten audio. +Add-on ini dirancang untuk alur kerja yang lancar, interaksi yang mengutamakan aksesibilitas, dan kontrol suara yang fleksibel, sehingga cocok untuk narasi, dialog, serta produksi konten audio. --- @@ -14,57 +14,57 @@ Add-on ini dirancang untuk alur kerja yang lancar, interaksi yang mengutamakan a ### Pembuatan Ucapan Berkualitas Tinggi * Pilih antara: - * **Gemini Flash**: Kualitas standar, pembuatan cepat, latensi rendah. - * **Gemini Pro**: Premium, suara lebih realistis (model berbayar). + * **Gemini Flash**: kualitas standar, pembuatan cepat, dan latensi rendah. + * **Gemini Pro**: kualitas premium dengan suara yang lebih realistis (model berbayar). -### Mode Single & Multi-Speaker +### Mode Pembicara Tunggal dan Multi-Pembicara * **Narasi pembicara tunggal** untuk text-to-speech standar. * **Mode multi-pembicara (2 pembicara)** untuk dialog dengan suara yang berbeda. -### Kontrol Suara Tingkat Lanjut +### Kontrol Suara Lanjutan -* **Penamaan Pembicara** - Tetapkan nama khusus (misalnya, *Budi*, *Siti*) dalam mode multi-pembicara. - AI secara otomatis memetakan suara berdasarkan nama pembicara dalam naskah. -* **Instruksi Gaya** - Berikan petunjuk seperti *“Bicaralah dengan nada ceria”* atau *“Ceritakan dengan tenang”* untuk memandu penyampaian. -* **Kontrol Temperatur** - Sesuaikan variasi dan kreativitas output: - * Nilai lebih rendah → ucapan lebih stabil dan dapat diprediksi. - * Nilai lebih tinggi → ucapan lebih ekspresif dan bervariasi. +* **Penamaan pembicara** + Tetapkan nama khusus, misalnya *Budi* atau *Siti*, dalam mode multi-pembicara. + AI akan memetakan suara secara otomatis berdasarkan nama pembicara di naskah. +* **Instruksi gaya** + Berikan petunjuk seperti *"Bicaralah dengan nada ceria"* atau *"Narasi dengan tenang"* untuk mengarahkan cara penyampaian. +* **Kontrol temperatur** + Sesuaikan variasi dan kreativitas hasil: + * Nilai lebih rendah -> ucapan lebih stabil dan mudah diprediksi. + * Nilai lebih tinggi -> ucapan lebih ekspresif dan bervariasi. -### Antarmuka Bersih & Dapat Diakses +### Antarmuka yang Bersih dan Dapat Diakses * Sepenuhnya dapat diakses dengan pembaca layar. -* Opsi lanjutan ditempatkan di panel yang dapat disembunyikan agar dialog utama tetap sederhana dan fokus. +* Opsi lanjutan ditempatkan di panel yang dapat diciutkan agar dialog utama tetap sederhana dan fokus. -### Alur Kerja yang Mulus +### Alur Kerja yang Lancar -* Audio diputar secara otomatis setelah dibuat. -* Audio yang dibuat dapat diputar ulang atau disimpan sebagai file `.wav` berkualitas tinggi. +* Audio diputar secara otomatis setelah pembuatan selesai. +* Audio yang dihasilkan dapat diputar ulang atau disimpan sebagai file `.wav` berkualitas tinggi. * Dirancang untuk meminimalkan hambatan selama pembuatan dan pemutaran berulang. -### Pemuatan Suara Cerdas & Caching +### Pemuatan Suara Cerdas dan Cache * Suara yang tersedia diambil secara dinamis dari API Gemini. -* Data suara disimpan dalam cache selama **24 jam** untuk mengurangi panggilan API dan mempercepat waktu mulai. +* Data suara disimpan dalam cache selama **24 jam** untuk mengurangi panggilan API dan mempercepat proses awal. ### Bicara dengan AI (Percakapan Langsung) -* **Obrolan Suara Real-time**: Lakukan percakapan lisan yang alami dan latensi rendah dengan Gemini. -* **Grounding dengan Pencarian Google**: Mengaktifkan AI untuk mengakses informasi real-time dari web selama obrolan Anda. -* **Dapat Diinterupsi**: Anda dapat memotong pembicaraan AI kapan saja dengan berbicara atau menekan tombol "Hentikan Percakapan". -* **Dapat Disesuaikan**: Menggunakan suara dan instruksi gaya yang Anda pilih. -* **Memori Sesi (Opsional)**: Menyimpan konteks percakapan sementara selama sesi Bicara dengan AI aktif agar respons lanjutan lebih koheren. -* **Kontrol Hapus Memori**: Menghapus memori sesi sementara sebelum percakapan dimulai (atau setelah percakapan dihentikan). -* **Streaming Lebih Stabil**: Perbaikan reconnect (backoff + retry) dan adaptive audio buffering agar lebih tahan terhadap jaringan yang tidak stabil. +* **Obrolan suara real-time**: lakukan percakapan lisan yang alami dan berlatensi rendah dengan Gemini. +* **Grounding dengan Google Search**: memungkinkan AI mengakses informasi real-time dari web selama percakapan. +* **Dapat diinterupsi**: Anda dapat memotong pembicaraan AI kapan saja dengan berbicara atau menekan "Hentikan percakapan". +* **Dapat disesuaikan**: menggunakan suara dan instruksi gaya yang Anda pilih. +* **Kontrol tingkat penalaran**: pilih `Tanpa Penalaran`, `Rendah`, `Sedang`, atau `Tinggi` sesuai kedalaman penalaran yang Anda inginkan. +* **Kontinuitas setelah koneksi ulang**: konteks percakapan terbaru dipulihkan secara otomatis setelah tersambung kembali, tanpa toggle memori terpisah. +* **Streaming lebih stabil**: perilaku reconnect yang lebih baik (backoff + retry) dan buffering audio adaptif agar lebih tangguh pada jaringan yang tidak stabil. --- ## Persyaratan -* NVDA (versi terbaru direkomendasikan). +* NVDA (disarankan menggunakan versi terbaru). * Koneksi internet aktif. * **Kunci API Google Gemini** yang valid. @@ -73,7 +73,7 @@ Add-on ini dirancang untuk alur kerja yang lancar, interaksi yang mengutamakan a ## Instalasi 1. Unduh paket add-on terbaru dari - **Halaman Rilis:** + **halaman Rilis:** [https://github.com/MuhammadGagah/native-speech-generation/releases](https://github.com/MuhammadGagah/native-speech-generation/releases) 2. Instal seperti add-on NVDA standar lainnya. 3. Mulai ulang NVDA saat diminta. @@ -84,32 +84,36 @@ Add-on ini dirancang untuk alur kerja yang lancar, interaksi yang mengutamakan a 1. Buat kunci API dari **Google AI Studio**: [https://aistudio.google.com/apikey](https://aistudio.google.com/apikey) -2. Buka NVDA dan pergi ke: - **Menu NVDA → Alat → Native Speech Generation** -3. Klik **“Pengaturan Kunci API”**. -4. Ini membuka Pengaturan NVDA langsung di kategori *Native Speech Generation*. -5. Tempelkan **Kunci API Gemini** Anda ke dalam kolom *Kunci API Gemini*. +2. Buka NVDA lalu masuk ke: + **Menu NVDA -> Alat -> Native Speech Generation** +3. Klik **"Pengaturan Kunci API"**. +4. Ini akan membuka Pengaturan NVDA langsung di kategori *Native Speech Generation*. +5. Tempelkan **kunci API Gemini** Anda ke dalam kolom *GEMINI API Key*. 6. Klik **OK** untuk menyimpan. +Kunci yang disimpan diamankan menggunakan **Windows DPAPI**, sehingga nilai terenkripsi tidak dapat didekripsi di komputer Windows lain atau akun pengguna lain. + +Untuk deployment tingkat lanjut, Anda juga dapat menyediakan kunci melalui variabel lingkungan **`GEMINI_API_KEY`**. Add-on akan menggunakannya secara otomatis saat tidak ada kunci tersimpan. + --- ## Cara Menggunakan -Buka dialog menggunakan: +Buka dialog dengan: * **NVDA+Control+Shift+G**, atau -* **Menu NVDA → Alat → Native Speech Generation** +* **Menu NVDA -> Alat -> Native Speech Generation** ### Elemen Antarmuka Utama * **Teks untuk dikonversi** Masukkan atau tempel teks yang ingin Anda ubah menjadi ucapan. * **Instruksi gaya (opsional)** - Berikan panduan untuk nada, emosi, atau penyampaian. -* **Pilih Model** - * Flash (Kualitas Standar) - * Pro (Kualitas Tinggi) -* **Mode Pembicara** + Berikan panduan untuk nada, emosi, atau cara penyampaian. +* **Pilih model** + * Flash (kualitas standar) + * Pro (kualitas tinggi) +* **Mode pembicara** * Pembicara tunggal * Multi-pembicara (2) @@ -120,11 +124,11 @@ Buka dialog menggunakan: ### Mode Pembicara Tunggal 1. Pilih **Pembicara tunggal**. -2. Pilih suara dari menu dropdown *Pilih Suara*. +2. Pilih suara dari daftar *Pilih Suara*. 3. Masukkan teks Anda. -4. (Opsional) tambahkan instruksi gaya. +4. Tambahkan instruksi gaya bila diperlukan. 5. Klik **Hasilkan Ucapan**. -6. Audio akan diputar secara otomatis setelah pembuatan selesai. +6. Audio akan diputar otomatis setelah proses selesai. --- @@ -134,7 +138,7 @@ Buka dialog menggunakan: 2. Untuk setiap pembicara: * Masukkan **Nama Pembicara** yang unik. * Pilih **Suara** yang berbeda. -3. Format teks sehingga setiap baris dimulai dengan nama pembicara diikuti oleh tanda titik dua. +3. Format teks sehingga setiap baris diawali nama pembicara, lalu diikuti tanda titik dua. **Contoh:** @@ -144,7 +148,7 @@ Bob: Aku baik-baik saja, Alice! Cuacanya luar biasa. ``` 4. Klik **Hasilkan Ucapan**. - Suara akan ditetapkan secara otomatis berdasarkan nama pembicara. + Suara akan dipetakan secara otomatis berdasarkan nama pembicara. --- @@ -152,69 +156,68 @@ Bob: Aku baik-baik saja, Alice! Cuacanya luar biasa. Rasakan percakapan suara dua arah yang alami dengan Gemini. -1. Konfigurasikan **Suara** dan **Instruksi Gaya** yang diinginkan di dialog utama. - *(Catatan: Bicara dengan AI saat ini hanya mendukung mode Pembicara Tunggal)* +1. Atur **Suara** dan **Instruksi Gaya** yang diinginkan di dialog utama. + *(Catatan: Bicara dengan AI saat ini hanya mendukung mode Pembicara Tunggal.)* 2. Klik **Bicara dengan AI**. 3. Di jendela baru: - * **Mulai Percakapan**: Memulai sesi. Bicaralah ke mikrofon Anda. - * **Hentikan Percakapan**: Mengakhiri sesi. - * **Grounding dengan Google Search**: Centang kotak ini untuk mengizinkan Gemini menelusuri web guna mencari jawaban (misalnya, berita terkini, cuaca). - * *Catatan: Kotak centang ini disembunyikan saat percakapan sedang aktif. Hentikan percakapan untuk mengubahnya.* - * **Gunakan memori sesi**: Menyimpan memori sementara hanya untuk sesi percakapan saat ini. - * **Hapus memori sekarang**: Menghapus memori sementara sebelum memulai (atau setelah menghentikan) percakapan. - * **Tombol Mikrofon**: Bisukan/Bunyikan mikrofon Anda. - * **Volume**: Sesuaikan volume pemutaran AI. + * **Mulai percakapan**: memulai sesi. Bicaralah ke mikrofon Anda. + * **Hentikan percakapan**: mengakhiri sesi. + * **Grounding dengan Google Search**: centang opsi ini untuk mengizinkan Gemini menelusuri web guna mencari jawaban, misalnya berita atau cuaca terkini. + * *Catatan: opsi ini disembunyikan saat percakapan sedang aktif. Hentikan percakapan untuk mengubahnya.* + * **Tingkat penalaran**: pilih `Tanpa Penalaran`, `Rendah`, `Sedang`, atau `Tinggi`. + * **Tombol mikrofon**: membisukan atau mengaktifkan mikrofon Anda. + * **Volume**: menyesuaikan volume pemutaran AI. --- ## Pengaturan Lanjutan -* Aktifkan **Pengaturan Lanjutan (Suhu)** untuk menampilkan slider. -* **Rentang Temperatur**: - * `0.0` → Paling deterministik dan stabil. - * `1.0` → Keseimbangan default. - * `2.0` → Paling kreatif dan bervariasi. +* Aktifkan **Pengaturan Lanjutan (Temperatur)** untuk menampilkan slider. +* **Rentang temperatur**: + * `0.0` -> hasil paling deterministik dan stabil. + * `1.0` -> keseimbangan default. + * `2.0` -> hasil paling kreatif dan bervariasi. --- -## Tinjauan Tombol +## Ringkasan Tombol -* **Hasilkan Ucapan** - Mulai pembuatan ucapan. -* **Putar** - Memutar ulang audio yang terakhir dibuat. -* **Bicara dengan AI** - Buka antarmuka percakapan suara real-time. -* **Simpan Audio** - Simpan audio terakhir sebagai file `.wav`. -* **Pengaturan Kunci API** - Buka konfigurasi add-on di Pengaturan NVDA. +* **Hasilkan Ucapan** - Memulai pembuatan ucapan. +* **Putar** - Memutar ulang audio terakhir yang dibuat. +* **Bicara dengan AI** - Membuka antarmuka percakapan suara real-time. +* **Simpan Audio** - Menyimpan audio terakhir sebagai file `.wav`. +* **Pengaturan Kunci API** - Membuka konfigurasi add-on di Pengaturan NVDA. * **Lihat suara di AI Studio** - Membuka Google AI Studio di browser. -* **Tutup** - Tutup dialog (atau tekan `Escape`). +* **Tutup** - Menutup dialog, atau tekan `Escape`. --- ## Gestur Input Dapat disesuaikan melalui: -**Menu NVDA → Preferensi → Gestur Input → Native Speech Generation** +**Menu NVDA -> Preferensi -> Gestur Input -> Native Speech Generation** Gestur default: -* **NVDA+Control+Shift+G** – Buka dialog Native Speech Generation. +* **NVDA+Control+Shift+G** - Membuka dialog Native Speech Generation. --- -## Panduan Pengembangan & Kontribusi +## Panduan Pengembangan dan Kontribusi -Jika Anda ingin mengembangkan atau memodifikasi add-on ini, ikuti langkah-langkah di bawah ini. +Jika Anda ingin mengembangkan atau memodifikasi add-on ini, ikuti langkah-langkah berikut. ### Pengaturan Lingkungan -* **Python 32-bit (direkomendasikan 3.11.9)** +* **Python 32-bit (disarankan 3.11.9)** [https://www.python.org/downloads/release/python-3119/](https://www.python.org/downloads/release/python-3119/) * **SCons 4.9.1 atau lebih baru** ``` pip install scons ``` -* **Alat GNU Gettext** (opsional, disarankan untuk lokalisasi) - * Biasanya sudah terinstal di Linux/Cygwin. +* **GNU Gettext Tools** (opsional, disarankan untuk lokalisasi) + * Biasanya sudah terpasang di Linux/Cygwin. * Windows: [https://gnuwin32.sourceforge.net/downlinks/gettext.php](https://gnuwin32.sourceforge.net/downlinks/gettext.php) * **Markdown 3.8+** (untuk konversi dokumentasi) @@ -222,24 +225,26 @@ Jika Anda ingin mengembangkan atau memodifikasi add-on ini, ikuti langkah-langka pip install markdown ``` -### Ketergantungan Tambahan +### Dependensi Tambahan -Instal Gemini SDK dan pyaudio langsung ke jalur pustaka add-on: +Instal dependensi audio untuk Talk With AI langsung ke jalur pustaka add-on: ``` python.exe -m pip install google-genai pyaudio --target "D:/myAdd-on/Native-Speech-Generation/addon/globalPlugins/NativeSpeechGeneration/lib" ``` -Sesuaikan jalur dengan direktori sumber add-on lokal Anda. +Sesuaikan jalur tersebut dengan direktori sumber add-on di komputer Anda. + +Untuk implementasi Talk With AI versi audio saat ini, Anda tidak memerlukan `opencv-python`, `pillow`, atau `mss`. -Kemudian salin file berikut dari instalasi Python Anda ke dalam: +Lalu salin file berikut dari instalasi Python Anda ke: ``` addon/globalPlugins/NativeSpeechGeneration/lib ``` -* folder `zoneinfo` -* file `secrets.py` +* Folder `zoneinfo` +* File `secrets.py` --- diff --git a/addon/doc/ru/readme.md b/addon/doc/ru/readme.md index f7a6ea4..4ea46ff 100644 --- a/addon/doc/ru/readme.md +++ b/addon/doc/ru/readme.md @@ -2,10 +2,10 @@ **Автор:** Muhammad Gagah [muha.aku@gmail.com](mailto:muha.aku@gmail.com) -Native Speech Generation — это дополнение для NVDA, которое интегрирует **Google Gemini AI** для генерации высококачественной, естественно звучащей речи непосредственно в NVDA. -Оно предоставляет чистый, полностью доступный интерфейс для преобразования текста в аудио, поддерживая как **одноголосое повествование**, так и **динамические диалоги с несколькими голосами**. +Native Speech Generation — это дополнение для NVDA, которое интегрирует **Google Gemini AI** для генерации естественно звучащей речи высокого качества прямо в NVDA. +Оно предоставляет чистый и полностью доступный интерфейс для преобразования текста в аудио, поддерживая как **озвучивание одним голосом**, так и **динамические диалоги с несколькими голосами**. -Это дополнение предназначено для плавного рабочего процесса, взаимодействия с приоритетом доступности и гибкого управления голосом, подходящего для озвучки, диалогов и создания аудиоконтента. +Это дополнение создано для удобного рабочего процесса, взаимодействия с приоритетом доступности и гибкого управления голосом, подходящего для озвучивания, диалогов и создания аудиоконтента. --- @@ -14,103 +14,106 @@ Native Speech Generation — это дополнение для NVDA, котор ### Генерация высококачественной речи * Выбор между: - - * **Gemini Flash** — стандартное качество, быстрая генерация, низкая задержка. - * **Gemini Pro** — премиум, более реалистичные голоса (платная модель). + * **Gemini Flash** — стандартное качество, быстрая генерация и низкая задержка. + * **Gemini Pro** — премиум-качество и более реалистичные голоса (платная модель). ### Режимы для одного и нескольких голосов -* **Одноголосое повествование** для стандартного преобразования текста в речь. -* **Режим с несколькими голосами (2 голоса)** для диалогов с различными голосами. +* **Озвучивание одним голосом** для стандартного преобразования текста в речь. +* **Режим нескольких голосов (2 голоса)** для диалогов с разными голосами. ### Расширенное управление голосом -* **Именование говорящих** - Назначайте пользовательские имена (например, *Иван*, *Мария*) в режиме нескольких голосов. - ИИ автоматически сопоставляет голоса на основе имён говорящих в сценарии. +* **Имена говорящих** + Задавайте пользовательские имена, например *Иван* или *Мария*, в режиме нескольких голосов. + ИИ автоматически сопоставляет голоса по именам говорящих в тексте. * **Инструкции по стилю** - Указывайте подсказки, такие как *«Говори веселым тоном»* или *«Рассказывай спокойно»*, чтобы направить манеру речи. -* **Управление температурой (Temperature)** - Настраивайте вариативность и креативность вывода: - - * Низкие значения → более стабильная и предсказуемая речь. - * Высокие значения → более выразительная и разнообразная речь. + Можно использовать подсказки вроде *"Говори весёлым тоном"* или *"Рассказывай спокойно"*, чтобы направить манеру речи. +* **Управление температурой** + Настраивайте вариативность и креативность результата: + * Более низкие значения -> более стабильная и предсказуемая речь. + * Более высокие значения -> более выразительная и разнообразная речь. ### Доступный и чистый интерфейс -* Полностью доступен для скринридеров. -* Расширенные опции расположены на свёртываемой панели, чтобы сохранить главное диалоговое окно простым и сфокусированным. +* Полностью доступен для экранных читалок. +* Расширенные параметры находятся в сворачиваемой панели, чтобы главное окно оставалось простым и удобным. -### Бесшовный рабочий процесс +### Удобный рабочий процесс * Аудио воспроизводится автоматически после генерации. -* Сгенерированное аудио можно воспроизвести повторно или сохранить как высококачественный файл `.wav`. -* Разработано для минимального трения при повторной генерации и воспроизведении. +* Сгенерированное аудио можно прослушать повторно или сохранить как качественный файл `.wav`. +* Всё рассчитано на минимальные задержки и неудобства при повторной генерации и прослушивании. -### Умная загрузка и кэширование голосов +### Умная загрузка голосов и кэширование -* Доступные голоса динамически загружаются из API Gemini. -* Данные голосов кэшируются на **24 часа**, чтобы уменьшить количество обращений к API и ускорить запуск. +* Доступные голоса загружаются динамически через API Gemini. +* Данные голосов кэшируются на **24 часа**, чтобы сократить количество обращений к API и ускорить запуск. ### Общение с ИИ (живой разговор) -* **Голосовой чат в реальном времени**: Ведите естественный, разговор с Gemini с низкой задержкой. -* **Привязка к Google Поиску**: Позвольте ИИ получать актуальную информацию из интернета во время чата. -* **Прерываемый**: Вы можете прервать ИИ в любой момент, начав говорить или нажав «Остановить разговор». -* **Настраиваемый**: Использует выбранный вами голос и инструкции по стилю. +* **Голосовой чат в реальном времени**: ведите естественный разговор с Gemini с низкой задержкой. +* **Привязка к Google Поиску**: разрешите ИИ использовать актуальную информацию из интернета во время разговора. +* **Можно прервать**: вы можете перебить ИИ в любой момент, начав говорить или нажав "Остановить разговор". +* **Гибкая настройка**: используются выбранные вами голос и инструкции по стилю. +* **Управление уровнем рассуждения**: выбирайте `Без рассуждений`, `Низкий`, `Средний` или `Высокий` в зависимости от нужной глубины рассуждения. +* **Непрерывность после переподключения**: недавний контекст разговора автоматически восстанавливается после переподключения, без отдельного переключателя памяти. +* **Более стабильный стриминг**: улучшенное поведение переподключения (backoff + retry) и адаптивная буферизация аудио для большей устойчивости при нестабильной сети. --- ## Требования * NVDA (рекомендуется последняя версия). -* Активное интернет-соединение. +* Активное подключение к Интернету. * Действующий **Google Gemini API-ключ**. --- ## Установка -1. Загрузите последнюю версию пакета дополнения со +1. Скачайте последнюю версию пакета дополнения со **страницы релизов:** [https://github.com/MuhammadGagah/native-speech-generation/releases](https://github.com/MuhammadGagah/native-speech-generation/releases) -2. Установите его как любое стандартное дополнение NVDA. -3. Перезапустите NVDA при запросе. +2. Установите его как обычное дополнение NVDA. +3. Перезапустите NVDA, когда появится запрос. --- -## Настройка API-ключа (Обязательно) +## Настройка API-ключа (обязательно) 1. Создайте API-ключ в **Google AI Studio**: [https://aistudio.google.com/apikey](https://aistudio.google.com/apikey) -2. Откройте NVDA и перейдите: - **Меню NVDA → Сервис → Native Speech Generation** -3. Нажмите **«Настройки API-ключа»**. -4. Это откроет настройки NVDA непосредственно в категории *Native Speech Generation*. +2. Откройте NVDA и перейдите в: + **Меню NVDA -> Сервис -> Native Speech Generation** +3. Нажмите **"Настройки API-ключа"**. +4. Это откроет настройки NVDA сразу в категории *Native Speech Generation*. 5. Вставьте ваш **Gemini API-ключ** в поле *GEMINI API Key*. -6. Нажмите **OK** для сохранения. +6. Нажмите **OK**, чтобы сохранить. + +Сохранённые ключи защищаются с помощью **Windows DPAPI**, поэтому зашифрованное значение нельзя расшифровать на другом компьютере с Windows или под другой учётной записью пользователя. + +Для продвинутых сценариев развёртывания вы также можете передать ключ через переменную среды **`GEMINI_API_KEY`**. Дополнение автоматически использует её, если сохранённого ключа нет. --- ## Как использовать -Откройте диалоговое окно, используя: +Откройте диалоговое окно с помощью: * **NVDA+Control+Shift+G**, или -* **Меню NVDA → Сервис → Native Speech Generation** +* **Меню NVDA -> Сервис -> Native Speech Generation** ### Основные элементы интерфейса * **Текст для преобразования** Введите или вставьте текст, который хотите преобразовать в речь. -* **Инструкции по стилю (опционально)** +* **Инструкции по стилю (необязательно)** Укажите рекомендации по тону, эмоциям или манере речи. * **Выбор модели** - - * Flash (Стандартное качество) - * Pro (Высокое качество) + * Flash (стандартное качество) + * Pro (высокое качество) * **Режим говорящего** - * Один говорящий * Несколько говорящих (2) @@ -121,11 +124,11 @@ Native Speech Generation — это дополнение для NVDA, котор ### Режим одного говорящего 1. Выберите **Один говорящий**. -2. Выберите голос в выпадающем списке *Выбрать голос*. -3. Введите ваш текст. -4. При желании добавьте инструкции по стилю. +2. Выберите голос в списке *Выбрать голос*. +3. Введите текст. +4. При необходимости добавьте инструкции по стилю. 5. Нажмите **Сгенерировать речь**. -6. Аудио будет автоматически воспроизведено после генерации. +6. После завершения генерации аудио будет воспроизведено автоматически. --- @@ -133,21 +136,19 @@ Native Speech Generation — это дополнение для NVDA, котор 1. Выберите **Несколько говорящих (2)**. 2. Для каждого говорящего: - * Введите уникальное **Имя говорящего**. - * Выберите отличный **Голос**. -3. Отформатируйте текст так, чтобы каждая строка начиналась с имени говорящего, за которым следует двоеточие. + * Выберите отдельный **Голос**. +3. Оформите текст так, чтобы каждая строка начиналась с имени говорящего, за которым следует двоеточие. **Пример:** ``` -Алиса: Привет, Боб, как дела? -Боб: Отлично, Алиса! Погода сегодня замечательная. +Алиса: Привет, Боб. Как дела сегодня? +Боб: У меня всё отлично, Алиса! Сегодня прекрасная погода. ``` - 4. Нажмите **Сгенерировать речь**. - Голоса будут автоматически назначены на основе имён говорящих. + Голоса будут назначены автоматически по именам говорящих. --- @@ -155,16 +156,17 @@ Native Speech Generation — это дополнение для NVDA, котор Попробуйте естественный двусторонний голосовой разговор с Gemini. -1. Настройте желаемые **Голос** и **Инструкции по стилю** в главном диалоговом окне. - *(Примечание: в настоящее время «Общение с ИИ» поддерживает только режим одного говорящего)* +1. Настройте нужные **Голос** и **Инструкции по стилю** в главном диалоговом окне. + *(Примечание: функция "Общение с ИИ" сейчас поддерживает только режим одного говорящего.)* 2. Нажмите **Общение с ИИ**. 3. В новом окне: - * **Начать разговор**: Запускает сессию. Говорите в микрофон. - * **Остановить разговор**: Завершает сессию. - * **Привязка к Google Поиску**: Установите этот флажок, чтобы позволить Gemini искать ответы в интернете (например, текущие новости, погоду). - * *Примечание: Этот флажок скрыт, пока разговор активен. Остановите разговор, чтобы изменить его.* - * **Переключение микрофона**: Выключить/включить микрофон. - * **Громкость**: Отрегулируйте громкость воспроизведения ИИ. + * **Начать разговор**: запускает сессию. Говорите в микрофон. + * **Остановить разговор**: завершает сессию. + * **Привязка к Google Поиску**: установите этот флажок, чтобы разрешить Gemini искать ответы в интернете, например актуальные новости или погоду. + * *Примечание: этот флажок скрыт, пока разговор активен. Остановите разговор, чтобы изменить его.* + * **Уровень рассуждения**: выберите `Без рассуждений`, `Низкий`, `Средний` или `Высокий`. + * **Микрофон**: выключить или включить микрофон. + * **Громкость**: отрегулируйте громкость воспроизведения ИИ. --- @@ -172,33 +174,32 @@ Native Speech Generation — это дополнение для NVDA, котор * Включите **Расширенные настройки (Temperature)**, чтобы показать ползунок. * **Диапазон температуры**: - - * `0.0` → Наиболее детерминированная и стабильная речь. - * `1.0` → Баланс по умолчанию. - * `2.0` → Наиболее креативная и разнообразная речь. + * `0.0` -> наиболее детерминированный и стабильный результат. + * `1.0` -> баланс по умолчанию. + * `2.0` -> наиболее креативный и разнообразный результат. --- ## Обзор кнопок -* **Сгенерировать речь** — начать генерацию речи. -* **Воспроизвести** — повторно воспроизвести последнее сгенерированное аудио. -* **Общение с ИИ** — открыть интерфейс голосового разговора в реальном времени. -* **Сохранить аудио** — сохранить последнее аудио как файл `.wav`. -* **Настройки API-ключа** — открыть конфигурацию дополнения в настройках NVDA. -* **Посмотреть голоса в AI Studio** — открывает Google AI Studio в браузере. -* **Закрыть** — закрыть диалоговое окно (или нажмите `Escape`). +* **Сгенерировать речь** - начать генерацию речи. +* **Воспроизвести** - повторно воспроизвести последнее сгенерированное аудио. +* **Общение с ИИ** - открыть интерфейс голосового разговора в реальном времени. +* **Сохранить аудио** - сохранить последнее аудио как файл `.wav`. +* **Настройки API-ключа** - открыть конфигурацию дополнения в настройках NVDA. +* **Посмотреть голоса в AI Studio** - открыть Google AI Studio в браузере. +* **Закрыть** - закрыть диалоговое окно или нажать `Escape`. --- ## Жесты ввода Настраиваются через: -**Меню NVDA → Параметры → Жесты ввода → Native Speech Generation** +**Меню NVDA -> Параметры -> Жесты ввода -> Native Speech Generation** Жест по умолчанию: -* **NVDA+Control+Shift+G** – открыть диалоговое окно Native Speech Generation. +* **NVDA+Control+Shift+G** - открыть диалоговое окно Native Speech Generation. --- @@ -212,31 +213,29 @@ Native Speech Generation — это дополнение для NVDA, котор [https://www.python.org/downloads/release/python-3119/](https://www.python.org/downloads/release/python-3119/) * **SCons 4.9.1 или новее** -``` -pip install scons -``` - -* **GNU Gettext Tools** (опционально, рекомендуется для локализации) - -* Обычно предустановлены на Linux/Cygwin. -* Windows: [https://gnuwin32.sourceforge.net/downlinks/gettext.php](https://gnuwin32.sourceforge.net/downlinks/gettext.php) -* **Markdown 3.8+** (для конвертации документации) - -``` -pip install markdown -``` + ``` + pip install scons + ``` +* **GNU Gettext Tools** (необязательно, рекомендуется для локализации) + * Обычно уже установлены в Linux/Cygwin. + * Windows: [https://gnuwin32.sourceforge.net/downlinks/gettext.php](https://gnuwin32.sourceforge.net/downlinks/gettext.php) +* **Markdown 3.8+** (для преобразования документации) + ``` + pip install markdown + ``` ### Дополнительные зависимости -Установите Gemini SDK и pyaudio непосредственно в путь к библиотеке дополнения: +Установите аудиозависимости для Talk With AI прямо в каталог библиотек дополнения: ``` python.exe -m pip install google-genai pyaudio --target "D:/myAdd-on/Native-Speech-Generation/addon/globalPlugins/NativeSpeechGeneration/lib" ``` +Скорректируйте путь под ваш локальный каталог с исходным кодом дополнения. -Скорректируйте путь в соответствии с вашим локальным каталогом исходного кода дополнения. +Для текущей аудио-реализации Talk With AI вам не нужны `opencv-python`, `pillow` и `mss`. Затем скопируйте следующее из вашей установки Python в: @@ -244,7 +243,6 @@ python.exe -m pip install google-genai pyaudio --target "D:/myAdd-on/Native-Spee addon/globalPlugins/NativeSpeechGeneration/lib ``` - * Папку `zoneinfo` * Файл `secrets.py` @@ -252,9 +250,9 @@ addon/globalPlugins/NativeSpeechGeneration/lib ## Участие в разработке -Предложения, баг-репорты и вклад в код очень приветствуются. +Предложения, отчёты об ошибках и вклад в код очень приветствуются. -* Откройте **Issue** для сообщений об ошибках или запросов возможностей. +* Откройте **Issue** для сообщений об ошибках или запросов новых возможностей. * Отправьте **Pull Request** для вклада в код. **Контакты** diff --git a/addon/globalPlugins/NativeSpeechGeneration/__init__.py b/addon/globalPlugins/NativeSpeechGeneration/__init__.py index 7207920..c5b4d09 100644 --- a/addon/globalPlugins/NativeSpeechGeneration/__init__.py +++ b/addon/globalPlugins/NativeSpeechGeneration/__init__.py @@ -3,14 +3,13 @@ import wx import addonHandler import globalPluginHandler -import config import gui from logHandler import log from scriptHandler import script from typing import Any, TYPE_CHECKING -from .core.constants import CONFIG_DOMAIN -# Initialize translation +from .core import config_store + addonHandler.initTranslation() if TYPE_CHECKING: @@ -19,14 +18,11 @@ def _(msg: str) -> str: return msg -# Initialization & Dependency Management pkgDir = os.path.dirname(os.path.abspath(__file__)) -# Libs setup try: from . import lib_updater - # Run trash cleanup and init lib_updater.initialize() libDir = lib_updater.LIB_DIR except Exception as e: @@ -50,7 +46,7 @@ def runCheck() -> None: LIBS_AVAILABLE = True if not LIBS_AVAILABLE: - # Dummy Plugin + class GlobalPlugin(globalPluginHandler.GlobalPlugin): """ A dummy plugin that informs the user that the addon is not ready @@ -75,29 +71,19 @@ def script_openDialog(self, gesture: Any) -> None: ) else: - # Full Functionality - # Import GUI components only when libs are available to avoid import errors try: - # We use local imports inside the class or method where possible to avoid circular deps during init - # But GlobalPlugin needs to register settings panel on init. from .interface.settings import NativeSpeechSettingsPanel from .interface.generation_dialog import NativeSpeechDialog except ImportError as e: log.error(f"Failed to import GUI components: {e}", exc_info=True) - # Fallback to dummy plugin or raise? - # If we can't load GUI, we can't run. - raise e + raise class GlobalPlugin(globalPluginHandler.GlobalPlugin): def __init__(self) -> None: super().__init__() - self.dialog = None # Track active dialog instance - if CONFIG_DOMAIN not in config.conf: - config.conf[CONFIG_DOMAIN] = {"apiKey": ""} - config.conf.spec[CONFIG_DOMAIN] = {"apiKey": "string(default='')"} + self.dialog = None + config_store.prepare_config_for_startup(persist=True) - # Register settings panel - # Note: We check if it is already registered if NativeSpeechSettingsPanel not in gui.settingsDialogs.NVDASettingsDialog.categoryClasses: gui.settingsDialogs.NVDASettingsDialog.categoryClasses.append(NativeSpeechSettingsPanel) @@ -156,6 +142,6 @@ def terminate(self) -> None: gui.settingsDialogs.NVDASettingsDialog.categoryClasses.remove(NativeSpeechSettingsPanel) try: gui.mainFrame.sysTrayIcon.toolsMenu.Remove(self.menuItem) - except Exception: - pass + except Exception as error: + log.debug(f"Failed to remove Native Speech Generation menu item: {error}", exc_info=True) super().terminate() diff --git a/addon/globalPlugins/NativeSpeechGeneration/core/config_store.py b/addon/globalPlugins/NativeSpeechGeneration/core/config_store.py new file mode 100644 index 0000000..57afa80 --- /dev/null +++ b/addon/globalPlugins/NativeSpeechGeneration/core/config_store.py @@ -0,0 +1,304 @@ +# -*- coding: utf-8 -*- +import base64 +import ctypes +import os +from dataclasses import dataclass +from typing import Any, Final, Literal + +import config +from logHandler import log + +from .constants import CONFIG_DOMAIN + +API_KEY_ENV_VAR: Final[str] = "GEMINI_API_KEY" +_DPAPI_DESCRIPTION: Final[str] = "Native Speech Generation API Key" +_CRYPTPROTECT_UI_FORBIDDEN: Final[int] = 0x1 +_CONFIG_SPEC: Final[dict[str, str]] = { + "apiKey": "string(default='')", + "apiKeyEncrypted": "string(default='')", +} + +ApiKeySource = Literal["stored", "environment", "missing"] +ApiKeyStatus = Literal["stored", "environment", "missing", "undecryptable", "legacyMigrated"] + + +@dataclass(frozen=True) +class ApiKeyResolution: + value: str + source: ApiKeySource + status: ApiKeyStatus + + +class ApiKeyStorageError(RuntimeError): + pass + + +class _DATA_BLOB(ctypes.Structure): + _fields_ = [ + ("cbData", ctypes.c_uint32), + ("pbData", ctypes.POINTER(ctypes.c_ubyte)), + ] + + +def register_config_spec() -> None: + config.conf.spec[CONFIG_DOMAIN] = _CONFIG_SPEC.copy() + _get_config_section() + + +def prepare_config_for_startup(*, persist: bool) -> bool: + register_config_spec() + removedLegacyPlaintext = _remove_legacy_plaintext_if_encrypted_exists() + migratedLegacyPlaintext = _migrate_plaintext_api_key() + if persist and (removedLegacyPlaintext or migratedLegacyPlaintext): + config.save() + return removedLegacyPlaintext or migratedLegacyPlaintext + + +def get_stored_api_key() -> str: + prepare_config_for_startup(persist=False) + encryptedValue = _get_text_setting("apiKeyEncrypted").strip() + if not encryptedValue: + return "" + try: + return _decrypt_api_key(encryptedValue) + except ApiKeyStorageError as error: + log.warning(f"Stored encrypted Gemini API key could not be decrypted: {error}") + return "" + + +def prepare_api_key_for_storage(value: str) -> tuple[str, str]: + cleanValue = value.strip() + if not cleanValue: + return "", "" + return cleanValue, _encrypt_api_key(cleanValue) + + +def write_prepared_api_key(cleanValue: str, encryptedValue: str) -> None: + register_config_spec() + if not cleanValue: + _set_text_setting("apiKeyEncrypted", "") + _set_text_setting("apiKey", "") + return + _set_text_setting("apiKeyEncrypted", encryptedValue) + _set_text_setting("apiKey", "") + + +def set_stored_api_key(value: str) -> None: + cleanValue, encryptedValue = prepare_api_key_for_storage(value) + write_prepared_api_key(cleanValue, encryptedValue) + + +def resolve_api_key() -> ApiKeyResolution: + migratedLegacyPlaintext = prepare_config_for_startup(persist=False) + encryptedValue = _get_text_setting("apiKeyEncrypted").strip() + if encryptedValue: + try: + return ApiKeyResolution( + value=_decrypt_api_key(encryptedValue), + source="stored", + status="legacyMigrated" if migratedLegacyPlaintext else "stored", + ) + except ApiKeyStorageError as error: + log.warning(f"Stored encrypted Gemini API key could not be decrypted: {error}") + environmentValue = _get_environment_api_key() + if environmentValue: + return ApiKeyResolution( + value=environmentValue, + source="environment", + status="undecryptable", + ) + return ApiKeyResolution(value="", source="missing", status="undecryptable") + + environmentValue = _get_environment_api_key() + if environmentValue: + return ApiKeyResolution(value=environmentValue, source="environment", status="environment") + return ApiKeyResolution(value="", source="missing", status="missing") + + +def _get_environment_api_key() -> str: + return os.environ.get(API_KEY_ENV_VAR, "").strip() + + +def _get_config_section() -> Any: + return config.conf[CONFIG_DOMAIN] + + +def _get_text_setting(name: str) -> str: + value = _get_config_section().get(name, "") + return value if isinstance(value, str) else str(value or "") + + +def _set_text_setting(name: str, value: str) -> None: + _get_config_section()[name] = value + + +def _remove_legacy_plaintext_if_encrypted_exists() -> bool: + legacyValue = _get_text_setting("apiKey").strip() + encryptedValue = _get_text_setting("apiKeyEncrypted").strip() + if not (legacyValue and encryptedValue): + return False + _set_text_setting("apiKey", "") + log.info("Removed legacy plaintext Gemini API key from configuration.") + return True + + +def _migrate_plaintext_api_key() -> bool: + legacyValue = _get_text_setting("apiKey").strip() + encryptedValue = _get_text_setting("apiKeyEncrypted").strip() + if not legacyValue or encryptedValue: + return False + try: + _set_text_setting("apiKeyEncrypted", _encrypt_api_key(legacyValue)) + except ApiKeyStorageError: + log.error( + "Failed to migrate the legacy plaintext Gemini API key to encrypted storage.", + exc_info=True, + ) + return False + _set_text_setting("apiKey", "") + log.info("Migrated legacy plaintext Gemini API key to DPAPI-protected storage.") + return True + + +def _encrypt_api_key(value: str) -> str: + if not value: + return "" + try: + protectedValue = _protect_bytes_with_dpapi(value.encode("utf-8")) + except Exception as error: + raise ApiKeyStorageError("Failed to encrypt the API key with Windows DPAPI.") from error + return base64.b64encode(protectedValue).decode("ascii") + + +def _decrypt_api_key(value: str) -> str: + if not value: + return "" + try: + protectedValue = base64.b64decode(value.encode("ascii"), validate=True) + except Exception as error: + raise ApiKeyStorageError("Stored API key data is not valid base64.") from error + try: + plainValue = _unprotect_bytes_with_dpapi(protectedValue) + except Exception as error: + raise ApiKeyStorageError( + "Stored API key data could not be decrypted for this Windows user or machine.", + ) from error + try: + return plainValue.decode("utf-8") + except UnicodeDecodeError as error: + raise ApiKeyStorageError("Stored API key data is not valid UTF-8 text.") from error + + +def _protect_bytes_with_dpapi(value: bytes) -> bytes: + try: + import win32crypt + except ImportError: + return _protect_bytes_with_ctypes(value) + return win32crypt.CryptProtectData( + value, + _DPAPI_DESCRIPTION, + None, + None, + None, + _CRYPTPROTECT_UI_FORBIDDEN, + ) + + +def _unprotect_bytes_with_dpapi(value: bytes) -> bytes: + try: + import win32crypt + except ImportError: + return _unprotect_bytes_with_ctypes(value) + _description, plainValue = win32crypt.CryptUnprotectData( + value, + None, + None, + None, + _CRYPTPROTECT_UI_FORBIDDEN, + ) + return plainValue + + +def _protect_bytes_with_ctypes(value: bytes) -> bytes: + dataIn, inputBuffer = _create_data_blob(value) + dataOut = _DATA_BLOB() + crypt32, _kernel32 = _load_dpapi_libraries() + del inputBuffer + if not crypt32.CryptProtectData( + ctypes.byref(dataIn), + _DPAPI_DESCRIPTION, + None, + None, + None, + _CRYPTPROTECT_UI_FORBIDDEN, + ctypes.byref(dataOut), + ): + raise ctypes.WinError(ctypes.get_last_error()) + return _copy_and_free_data_blob(dataOut) + + +def _unprotect_bytes_with_ctypes(value: bytes) -> bytes: + dataIn, inputBuffer = _create_data_blob(value) + dataOut = _DATA_BLOB() + crypt32, _kernel32 = _load_dpapi_libraries() + del inputBuffer + if not crypt32.CryptUnprotectData( + ctypes.byref(dataIn), + None, + None, + None, + None, + _CRYPTPROTECT_UI_FORBIDDEN, + ctypes.byref(dataOut), + ): + raise ctypes.WinError(ctypes.get_last_error()) + return _copy_and_free_data_blob(dataOut) + + +def _create_data_blob(value: bytes) -> tuple[_DATA_BLOB, ctypes.Array[ctypes.c_char] | None]: + if not value: + return _DATA_BLOB(0, ctypes.POINTER(ctypes.c_ubyte)()), None + buffer = ctypes.create_string_buffer(value, len(value)) + return _DATA_BLOB( + len(value), + ctypes.cast(buffer, ctypes.POINTER(ctypes.c_ubyte)), + ), buffer + + +def _copy_and_free_data_blob(blob: _DATA_BLOB) -> bytes: + _crypt32, kernel32 = _load_dpapi_libraries() + try: + if not blob.cbData or not blob.pbData: + return b"" + return ctypes.string_at(blob.pbData, blob.cbData) + finally: + if blob.pbData: + kernel32.LocalFree(ctypes.cast(blob.pbData, ctypes.c_void_p)) + + +def _load_dpapi_libraries() -> tuple[ctypes.WinDLL, ctypes.WinDLL]: + crypt32 = ctypes.WinDLL("crypt32", use_last_error=True) + kernel32 = ctypes.WinDLL("kernel32", use_last_error=True) + crypt32.CryptProtectData.argtypes = ( + ctypes.POINTER(_DATA_BLOB), + ctypes.c_wchar_p, + ctypes.POINTER(_DATA_BLOB), + ctypes.c_void_p, + ctypes.c_void_p, + ctypes.c_uint32, + ctypes.POINTER(_DATA_BLOB), + ) + crypt32.CryptProtectData.restype = ctypes.c_int + crypt32.CryptUnprotectData.argtypes = ( + ctypes.POINTER(_DATA_BLOB), + ctypes.POINTER(ctypes.c_wchar_p), + ctypes.POINTER(_DATA_BLOB), + ctypes.c_void_p, + ctypes.c_void_p, + ctypes.c_uint32, + ctypes.POINTER(_DATA_BLOB), + ) + crypt32.CryptUnprotectData.restype = ctypes.c_int + kernel32.LocalFree.argtypes = (ctypes.c_void_p,) + kernel32.LocalFree.restype = ctypes.c_void_p + return crypt32, kernel32 diff --git a/addon/globalPlugins/NativeSpeechGeneration/core/gemini_imports.py b/addon/globalPlugins/NativeSpeechGeneration/core/gemini_imports.py index bfae639..f6f05f2 100644 --- a/addon/globalPlugins/NativeSpeechGeneration/core/gemini_imports.py +++ b/addon/globalPlugins/NativeSpeechGeneration/core/gemini_imports.py @@ -13,6 +13,9 @@ genai = _RUNTIME.genai types = _RUNTIME.types pyaudio = _RUNTIME.pyaudio +VENDOR_VERSIONS = dict(_RUNTIME.versions) +GENAI_IMPORT_ERROR = _RUNTIME.genaiError +PYAUDIO_IMPORT_ERROR = _RUNTIME.pyaudioError GENAI_AVAILABLE = _RUNTIME.genaiAvailable PYAUDIO_AVAILABLE = _RUNTIME.pyaudioAvailable @@ -21,6 +24,9 @@ "genai", "types", "pyaudio", + "VENDOR_VERSIONS", + "GENAI_IMPORT_ERROR", + "PYAUDIO_IMPORT_ERROR", "GENAI_AVAILABLE", "PYAUDIO_AVAILABLE", "getRuntimeScope", diff --git a/addon/globalPlugins/NativeSpeechGeneration/core/vendor_loader.py b/addon/globalPlugins/NativeSpeechGeneration/core/vendor_loader.py index 04ed903..7d3ec9a 100644 --- a/addon/globalPlugins/NativeSpeechGeneration/core/vendor_loader.py +++ b/addon/globalPlugins/NativeSpeechGeneration/core/vendor_loader.py @@ -2,6 +2,8 @@ import contextlib import importlib import os +import re +import struct import sys import threading from dataclasses import dataclass @@ -23,7 +25,22 @@ "httpcore", "anyio", "sniffio", + "requests", + "urllib3", + "certifi", + "charset_normalizer", + "idna", + "cryptography", + "cffi", + "pycparser", + "h11", + "distro", + "tenacity", + "pyasn1", + "pyasn1_modules", + "typing_inspection", ) +_BINARY_TAG_PATTERN = re.compile(r"\.(cp\d+)-(win32|win_amd64|win_arm64)\.pyd$", re.IGNORECASE) def _has_prefix(moduleName: str) -> bool: @@ -46,6 +63,52 @@ def _load_versions(modules: dict[str, ModuleType], names: tuple[str, ...]) -> di return versions +def _get_current_binary_tags() -> tuple[str, str]: + pythonTag = f"cp{sys.version_info.major}{sys.version_info.minor}" + archBits = struct.calcsize("P") * 8 + if archBits == 64: + platformTag = "win_amd64" + elif archBits == 32: + platformTag = "win32" + else: + platformTag = f"{archBits}-bit" + return pythonTag, platformTag + + +def _scan_binary_compatibility_hints(libDir: str) -> dict[str, str]: + currentPythonTag, currentPlatformTag = _get_current_binary_tags() + hints: dict[str, str] = {} + for root, _dirs, files in os.walk(libDir): + for fileName in files: + match = _BINARY_TAG_PATTERN.search(fileName) + if match is None: + continue + targetPythonTag = match.group(1).lower() + targetPlatformTag = match.group(2).lower() + if targetPythonTag == currentPythonTag and targetPlatformTag == currentPlatformTag: + continue + fullPath = os.path.join(root, fileName) + relPath = os.path.relpath(fullPath, libDir) + packageName = relPath.split(os.sep, 1)[0] + if packageName in hints: + continue + hints[packageName] = ( + f"Binary compatibility mismatch for '{relPath}': built for " + f"{targetPythonTag}-{targetPlatformTag}, current runtime is " + f"{currentPythonTag}-{currentPlatformTag}. Reinstall the add-on " + "libraries using NVDA's embedded Python." + ) + return hints + + +def _combine_error_details(error: BaseException, *hints: str | None) -> str: + parts = [repr(error)] + for hint in hints: + if hint: + parts.append(hint) + return " | ".join(parts) + + @dataclass class VendorRuntime: libDir: str @@ -54,6 +117,8 @@ class VendorRuntime: pyaudio: ModuleType | None modules: dict[str, ModuleType] versions: dict[str, str] + genaiError: str | None + pyaudioError: str | None @property def genaiAvailable(self) -> bool: @@ -71,6 +136,9 @@ def _create_runtime(libDir: str) -> VendorRuntime: pyaudio = None runtimeModules: dict[str, ModuleType] = {} versions: dict[str, str] = {} + genaiError: str | None = None + pyaudioError: str | None = None + binaryCompatibilityHints = _scan_binary_compatibility_hints(absLibDir) with _RUNTIME_LOCK: originalPath = list(sys.path) @@ -83,8 +151,9 @@ def _create_runtime(libDir: str) -> VendorRuntime: try: pyaudio = importlib.import_module("pyaudio") - except Exception: + except Exception as error: pyaudio = None + pyaudioError = _combine_error_details(error, binaryCompatibilityHints.get("pyaudio")) try: from google import genai as loadedGenai @@ -92,16 +161,30 @@ def _create_runtime(libDir: str) -> VendorRuntime: genai = loadedGenai types = loadedTypes - except Exception: + except Exception as error: genai = None types = None + genaiError = _combine_error_details( + error, + binaryCompatibilityHints.get("pydantic_core"), + ) runtimeModules = _collect_conflicting_modules() if pyaudio is not None: runtimeModules["pyaudio"] = pyaudio versions = _load_versions( runtimeModules, - ("google.genai", "pydantic", "websockets", "typing_extensions"), + ( + "google.genai", + "pydantic", + "pydantic_core", + "websockets", + "httpx", + "requests", + "urllib3", + "typing_extensions", + "pyaudio", + ), ) finally: sys.path = originalPath @@ -116,6 +199,10 @@ def _create_runtime(libDir: str) -> VendorRuntime: log.info(f"vendor_loader: pyaudio loaded from {pyaudio.__file__}") if versions: log.info(f"vendor_loader: resolved versions {versions}") + if genaiError: + log.warning(f"vendor_loader: failed to import google.genai: {genaiError}") + if pyaudioError: + log.warning(f"vendor_loader: failed to import pyaudio: {pyaudioError}") return VendorRuntime( libDir=absLibDir, @@ -124,6 +211,8 @@ def _create_runtime(libDir: str) -> VendorRuntime: pyaudio=pyaudio, modules=runtimeModules, versions=versions, + genaiError=genaiError, + pyaudioError=pyaudioError, ) diff --git a/addon/globalPlugins/NativeSpeechGeneration/interface/generation_dialog.py b/addon/globalPlugins/NativeSpeechGeneration/interface/generation_dialog.py index 9dafea7..bf8335c 100644 --- a/addon/globalPlugins/NativeSpeechGeneration/interface/generation_dialog.py +++ b/addon/globalPlugins/NativeSpeechGeneration/interface/generation_dialog.py @@ -9,20 +9,19 @@ import winsound import gui import ui -import config import addonHandler from logHandler import log from typing import Any, TYPE_CHECKING from ..core.constants import ( - CONFIG_DOMAIN, DEFAULT_MODEL, SECOND_MODEL, VOICE_SAMPLE_BASE, FALLBACK_VOICES, ) +from ..core import config_store from ..core.audio_utils import convertToWav, mergeWavFiles, saveBinaryFile, safeStartFile -from ..core.gemini_imports import GENAI_AVAILABLE, genai, getRuntimeScope, types +from ..core.gemini_imports import GENAI_AVAILABLE, GENAI_IMPORT_ERROR, genai, getRuntimeScope, types from .. import talkWithAI @@ -32,10 +31,8 @@ def _(msg: str) -> str: return msg -# Initialize translation addonHandler.initTranslation() -# Need to know addon_dir for saving "last_audio_generated" _guiDir = os.path.dirname(os.path.abspath(__file__)) _pkgDir = os.path.dirname(_guiDir) _globalPluginsDir = os.path.dirname(_pkgDir) @@ -46,10 +43,6 @@ class NativeSpeechDialog(wx.Dialog): def __init__(self, parent: wx.Window) -> None: # Translators: The title of the main dialog window for generating speech. super().__init__(parent, title=_("Native Speech Generation (Gemini TTS)")) - try: - self.apiKey = config.conf[CONFIG_DOMAIN]["apiKey"] - except Exception: - self.apiKey = "" self.lastAudioPath: str | None = None self.model = DEFAULT_MODEL @@ -59,6 +52,7 @@ def __init__(self, parent: wx.Window) -> None: self.selectedVoiceIdx2 = 0 self.isGenerating = False self.client = None + self.currentStream = None self.isClosed = False self._buildUi() @@ -68,21 +62,18 @@ def __init__(self, parent: wx.Window) -> None: def _buildUi(self) -> None: mainSizer = wx.BoxSizer(wx.VERTICAL) - # Text Input # Translators: Label for the text area where user inputs text to be converted to speech. textLabel = wx.StaticText(self, label=_("&Type text to convert here:")) self.textCtrl = wx.TextCtrl(self, style=wx.TE_MULTILINE, size=(520, 160)) mainSizer.Add(textLabel, flag=wx.ALL, border=6) mainSizer.Add(self.textCtrl, flag=wx.EXPAND | wx.LEFT | wx.RIGHT, border=6) - # Style Input # Translators: Label for optional instructions on how the speech should be spoken (e.g. "Happy", "Sad"). styleLabel = wx.StaticText(self, label=_("&Style instructions (optional):")) self.styleCtrl = wx.TextCtrl(self, style=wx.TE_MULTILINE, size=(520, 60)) mainSizer.Add(styleLabel, flag=wx.ALL, border=6) mainSizer.Add(self.styleCtrl, flag=wx.EXPAND | wx.LEFT | wx.RIGHT, border=6) - # Model & Mode Selection modelSizer = wx.BoxSizer(wx.HORIZONTAL) # Translators: Label for selecting the AI model to use for generation. modelLabel = wx.StaticText(self, label=_("Select &Model:")) @@ -103,13 +94,11 @@ def _buildUi(self) -> None: modelSizer.Add(self.modeMultiRb, flag=wx.ALL, border=6) mainSizer.Add(modelSizer, flag=wx.EXPAND) - # Settings Toggle # Translators: Checkbox to show advanced settings like Temperature. self.settingsCheckbox = wx.CheckBox(self, label=_("Advanced Settings (&Temperature)")) self.settingsCheckbox.SetValue(False) mainSizer.Add(self.settingsCheckbox, flag=wx.LEFT | wx.RIGHT | wx.TOP, border=6) - # Settings Panel (Hidden) self.settingsPanel = wx.Panel(self) mainSizer.Add(self.settingsPanel, proportion=0, flag=wx.EXPAND | wx.LEFT | wx.RIGHT, border=6) @@ -134,14 +123,12 @@ def _buildUi(self) -> None: self.settingsPanel.Hide() self.Bind(wx.EVT_CHECKBOX, self.onToggleSettings, self.settingsCheckbox) - # Voice Panels self.voicePanelSingle = self._buildVoicePanelSingle() self.voicePanelMulti = self._buildVoicePanelMulti() mainSizer.Add(self.voicePanelSingle, flag=wx.EXPAND | wx.ALL, border=5) mainSizer.Add(self.voicePanelMulti, flag=wx.EXPAND | wx.ALL, border=5) self.voicePanelMulti.Hide() - # Action Buttons btnSizer = wx.StdDialogButtonSizer() # Translators: Button to start generating the speech audio. self.generateBtn = wx.Button(self, label=_("&Generate Speech")) @@ -162,13 +149,11 @@ def _buildUi(self) -> None: btnSizer.Realize() mainSizer.Add(btnSizer, flag=wx.EXPAND | wx.ALL, border=10) - # Talk With AI Button # Translators: Button to open the real-time conversation dialog. self.talkBtn = wx.Button(self, label=_("Talk With &AI")) self.talkBtn.Bind(wx.EVT_BUTTON, self.onTalkWithAi) mainSizer.Add(self.talkBtn, flag=wx.ALIGN_CENTER | wx.ALL, border=5) - # Footer footerSizer = wx.BoxSizer(wx.HORIZONTAL) # Translators: Button to open settings specifically for configuring the API key. self.getKeyBtn = wx.Button(self, label=_("API Key Settings")) @@ -213,7 +198,6 @@ def _buildVoicePanelMulti(self) -> wx.Panel: panel = wx.Panel(self) sizer = wx.BoxSizer(wx.VERTICAL) - # Speaker 1 spk1Sizer = wx.BoxSizer(wx.HORIZONTAL) spk1Label = wx.StaticText(panel, label=_("Speaker 1 Name:")) self.spk1NameCtrl = wx.TextCtrl(panel, value=_("Speaker1"), size=(100, -1)) @@ -229,7 +213,6 @@ def _buildVoicePanelMulti(self) -> wx.Panel: spk1Sizer.Add(self.voiceChoiceMulti1, proportion=1, flag=wx.EXPAND) sizer.Add(spk1Sizer, flag=wx.EXPAND | wx.ALL, border=6) - # Speaker 2 spk2Sizer = wx.BoxSizer(wx.HORIZONTAL) spk2Label = wx.StaticText(panel, label=_("Speaker 2 Name:")) self.spk2NameCtrl = wx.TextCtrl(panel, value=_("Speaker2"), size=(100, -1)) @@ -250,23 +233,31 @@ def _buildVoicePanelMulti(self) -> wx.Panel: def onClose(self, evt: wx.Event) -> None: self.isClosed = True - # Force close the stream if it's active - if hasattr(self, "currentStream") and self.currentStream: - try: - with getRuntimeScope(): - self.currentStream.close() - except Exception: - pass - - # Close the client - if self.client: - try: - with getRuntimeScope(): - self.client.close() - except Exception: - pass + currentStream = self.currentStream + client = self.client + self.currentStream = None + self.client = None + if currentStream or client: + threading.Thread( + target=self._closeGenerationResources, + args=(currentStream, client), + daemon=True, + ).start() self.Destroy() + def _closeGenerationResources(self, currentStream: Any, client: Any) -> None: + if currentStream: + self._closeRuntimeResource("generation stream", currentStream.close) + if client: + self._closeRuntimeResource("generation client", client.close) + + def _closeRuntimeResource(self, label: str, closer: Any) -> None: + try: + with getRuntimeScope(): + closer() + except Exception as error: + log.debug(f"Failed to close {label}: {error}", exc_info=True) + def _tempToLabel(self, valInt: int) -> str: return f"{valInt / 10.0:.1f}" @@ -316,7 +307,6 @@ def _getSelectedVoiceName(self, choiceCtrl: wx.Choice, idx: int | None) -> str: voiceData = self.voices[idx] if isinstance(voiceData, dict) and "name" in voiceData: return voiceData["name"] - # Fallback return str(voiceData) except IndexError: return self.voices[0]["name"] if self.voices else "Zephyr" @@ -324,11 +314,31 @@ def _getSelectedVoiceName(self, choiceCtrl: wx.Choice, idx: int | None) -> str: log.error(f"Failed to get selected voice name: {e}", exc_info=True) return "Zephyr" + def _resolveApiKeyForUse(self) -> str | None: + resolution = config_store.resolve_api_key() + if resolution.value: + return resolution.value + self._showApiKeyUnavailableMessage(resolution) + return None + + def _showApiKeyUnavailableMessage(self, resolution: config_store.ApiKeyResolution) -> None: + if resolution.status == "undecryptable": + # Translators: Error shown when a stored encrypted API key cannot be decrypted. + message = _( + "The stored Gemini API key could not be decrypted on this Windows user or machine. " + "Please enter it again in NVDA settings, or define {envVarName} in the environment.", + ).format(envVarName=config_store.API_KEY_ENV_VAR) + else: + # Translators: Error shown when no API key is available from config or environment. + message = _( + "No Gemini API key is configured. Set it in NVDA settings, or define {envVarName} " + "in the environment.", + ).format(envVarName=config_store.API_KEY_ENV_VAR) + wx.CallAfter(wx.MessageBox, message, _("Error"), wx.OK | wx.ICON_ERROR) + def onSettings(self, evt: wx.Event) -> None: - # Import panel locally to avoid circular dep if needed, or pass class from .settings import NativeSpeechSettingsPanel - self.Destroy() wx.CallAfter( gui.mainFrame.popupSettingsDialog, gui.settingsDialogs.NVDASettingsDialog, @@ -359,21 +369,17 @@ def onTalkWithAi(self, evt: wx.Event) -> None: ) return - if not self.apiKey: - wx.CallAfter(wx.MessageBox, _("No GEMINI_API_KEY configured."), _("Error"), wx.OK | wx.ICON_ERROR) + apiKey = self._resolveApiKeyForUse() + if not apiKey: return - # Get current settings voiceName = self._getSelectedVoiceName(self.voiceChoiceSingle, self.selectedVoiceIdx) styleInstructions = self.styleCtrl.GetValue().strip() try: - # Close the main dialog first - self.Close() - - # Show TalkWithAI dialog - # We use gui.mainFrame as parent since self is being destroyed - dlg = talkWithAI.TalkWithAIDialog(gui.mainFrame, self.apiKey, voiceName, styleInstructions) + # Keep this dialog alive so the user's draft remains available if the + # Talk With AI dialog fails to initialize or after it closes. + dlg = talkWithAI.TalkWithAIDialog(self, apiKey, voiceName, styleInstructions) dlg.ShowModal() except Exception as e: log.error(f"Failed to open TalkWithAI dialog: {e}", exc_info=True) @@ -388,21 +394,24 @@ def onGenerate(self, evt: wx.Event) -> None: if self.isGenerating: return if not GENAI_AVAILABLE: - wx.CallAfter( - wx.MessageBox, - _("google-genai library not installed. Please restart NVDA."), - _("Error"), - wx.OK | wx.ICON_ERROR, + message = _( + "google-genai is not available. Please restart NVDA after updating the add-on libraries.", ) - return - if not self.apiKey: + if GENAI_IMPORT_ERROR: + message = _("{baseMessage}\n\nImport detail: {errorDetail}").format( + baseMessage=message, + errorDetail=GENAI_IMPORT_ERROR, + ) wx.CallAfter( wx.MessageBox, - _("No GEMINI_API_KEY configured. Set it in NVDA settings."), + message, _("Error"), wx.OK | wx.ICON_ERROR, ) return + apiKey = self._resolveApiKeyForUse() + if not apiKey: + return text = self.textCtrl.GetValue().strip() if not text: wx.CallAfter( @@ -418,13 +427,13 @@ def onGenerate(self, evt: wx.Event) -> None: self.playBtn.Enable(False) self.saveBtn.Enable(False) self.talkBtn.Enable(False) - threading.Thread(target=self._generateThread, args=(text,), daemon=True).start() + threading.Thread(target=self._generateThread, args=(text, apiKey), daemon=True).start() - def _generateThread(self, text: str) -> None: + def _generateThread(self, text: str, apiKey: str) -> None: ui.message(_("Generating speech, please wait...")) try: with getRuntimeScope(): - self.client = genai.Client(api_key=self.apiKey) + self.client = genai.Client(api_key=apiKey) except Exception as e: log.error(f"Failed init genai client: {e}", exc_info=True) if not self.isClosed: @@ -544,12 +553,10 @@ def _streamAndSaveAudio( ) -> str | None: fileIndex = 0 savedPaths = [] - # Keep reference to current stream so we can close it from main thread if needed self.currentStream = None try: if self.isClosed: return None - # Store the stream object with getRuntimeScope(): self.currentStream = client.models.generate_content_stream( model=model, @@ -560,11 +567,13 @@ def _streamAndSaveAudio( with getRuntimeScope(): for chunk in self.currentStream: if self.isClosed: - # Explicitly close the stream iterator to kill connection try: self.currentStream.close() - except Exception: - pass + except Exception as error: + log.debug( + f"Failed to close generation stream during shutdown: {error}", + exc_info=True, + ) return None if not getattr(chunk, "candidates", None): @@ -609,7 +618,6 @@ def _streamAndSaveAudio( return savedPaths[0] except Exception as e: - # If exception is due to closure, ignore if self.isClosed: return None log.error(f"Error streaming/generating audio: {e}", exc_info=True) @@ -621,7 +629,6 @@ def _streamAndSaveAudio( ) return None finally: - # Cleanup stream reference self.currentStream = None def onPlay(self, evt: wx.Event) -> None: diff --git a/addon/globalPlugins/NativeSpeechGeneration/interface/settings.py b/addon/globalPlugins/NativeSpeechGeneration/interface/settings.py index df4b35b..d57c59e 100644 --- a/addon/globalPlugins/NativeSpeechGeneration/interface/settings.py +++ b/addon/globalPlugins/NativeSpeechGeneration/interface/settings.py @@ -7,12 +7,10 @@ from typing import TYPE_CHECKING import gui import addonHandler -import config from logHandler import log -# Import from core -from ..core.constants import CONFIG_DOMAIN from .. import lib_updater +from ..core import config_store if TYPE_CHECKING: @@ -20,7 +18,6 @@ def _(msg: str) -> str: return msg -# Initialize translation addonHandler.initTranslation() @@ -28,23 +25,27 @@ class NativeSpeechSettingsPanel(gui.settingsDialogs.SettingsPanel): # Translators: Title of the settings panel in NVDA preferences. title = _("Native Speech Generation") + def __init__(self, *args, **kwargs) -> None: + super().__init__(*args, **kwargs) + self._validatedApiKeyValue = "" + self._validatedEncryptedApiKey = "" + def makeSettings(self, settingsSizer: wx.Sizer) -> None: sHelper = gui.guiHelper.BoxSizerHelper(self, sizer=settingsSizer) + apiResolution = config_store.resolve_api_key() - # API Key Configuration Group apiSizer = wx.BoxSizer(wx.HORIZONTAL) # Translators: Label for the input field where user enters their Gemini API Key. apiLabel = wx.StaticText(self, label=_("&Gemini API Key:")) apiSizer.Add(apiLabel, 0, wx.ALIGN_CENTER_VERTICAL | wx.RIGHT, 5) - apiValue = config.conf.get(CONFIG_DOMAIN, {}).get("apiKey", "") + apiValue = config_store.get_stored_api_key() self.apiKeyCtrlHidden = wx.TextCtrl(self, value=apiValue, style=wx.TE_PASSWORD) self.apiKeyCtrlVisible = wx.TextCtrl(self, value=apiValue) self.apiKeyCtrlVisible.Hide() - # Add inputs with EXPAND to fill available space apiSizer.Add(self.apiKeyCtrlHidden, 1, wx.EXPAND | wx.RIGHT, 5) apiSizer.Add(self.apiKeyCtrlVisible, 1, wx.EXPAND | wx.RIGHT, 5) @@ -53,34 +54,107 @@ def makeSettings(self, settingsSizer: wx.Sizer) -> None: self.showApiCheck.Bind(wx.EVT_CHECKBOX, self.onToggleApiVisibility) apiSizer.Add(self.showApiCheck, 0, wx.ALIGN_CENTER_VERTICAL) - # Add the row to the main settings sizer + settingsSizer.Add(apiSizer, 0, wx.EXPAND | wx.ALL, 5) self.onToggleApiVisibility(None) # Set initial state + apiInfoMessage = self._getApiKeyInfoMessage(apiResolution) + self.apiKeyInfoLabel = sHelper.addItem(wx.StaticText(self, label=apiInfoMessage)) + if apiInfoMessage: + self.apiKeyInfoLabel.Wrap(560) + else: + self.apiKeyInfoLabel.Hide() + # Translators: Button starting a process to help user get an API key (opens a website). self.getKeyBtn = wx.Button(self, label=_("&How to get API Key...")) sHelper.addItem(self.getKeyBtn) self.getKeyBtn.Bind(wx.EVT_BUTTON, self.onGetKey) - # Reinstall libraries button # Translators: Button to force a reinstallation of external dependencies (Python libraries). self.reinstallBtn = wx.Button(self, label=_("&Reinstall Libraries")) sHelper.addItem(self.reinstallBtn) self.reinstallBtn.Bind(wx.EVT_BUTTON, self.onReinstall) - def onToggleApiVisibility(self, event: wx.Event) -> None: - if self.showApiCheck.IsChecked(): - self.apiKeyCtrlVisible.SetValue(self.apiKeyCtrlHidden.GetValue()) + def _getApiKeyInfoMessage(self, resolution: config_store.ApiKeyResolution) -> str: + if resolution.status == "undecryptable" and resolution.source == "environment": + # Translators: Information shown in settings when a stored key cannot be decrypted + # and the add-on is using GEMINI_API_KEY from the environment instead. + return _( + "The stored API key could not be decrypted on this Windows user or machine. " + "Using {envVarName} from the environment instead. Enter a new key here to replace it.", + ).format(envVarName=config_store.API_KEY_ENV_VAR) + if resolution.status == "undecryptable": + # Translators: Information shown in settings when a stored key cannot be decrypted. + return _( + "The stored API key could not be decrypted on this Windows user or machine. " + "Enter a new key here, or set {envVarName} in the environment.", + ).format(envVarName=config_store.API_KEY_ENV_VAR) + if resolution.source == "environment": + # Translators: Information shown in settings when the add-on is using GEMINI_API_KEY + # from the environment because no stored key is available. + return _( + "Using {envVarName} from the environment. Saving a key here will override it.", + ).format(envVarName=config_store.API_KEY_ENV_VAR) + return "" + + def onToggleApiVisibility(self, event: wx.Event | None) -> None: + sourceCtrl = self.apiKeyCtrlVisible if self.apiKeyCtrlVisible.IsShown() else self.apiKeyCtrlHidden + targetCtrl = self.apiKeyCtrlVisible if self.showApiCheck.IsChecked() else self.apiKeyCtrlHidden + value = sourceCtrl.GetValue() + selectionStart, selectionEnd = sourceCtrl.GetSelection() + insertionPoint = sourceCtrl.GetInsertionPoint() + restoreFocus = sourceCtrl.HasFocus() + + targetCtrl.SetValue(value) + if targetCtrl is self.apiKeyCtrlVisible: self.apiKeyCtrlHidden.Hide() self.apiKeyCtrlVisible.Show() else: - self.apiKeyCtrlHidden.SetValue(self.apiKeyCtrlVisible.GetValue()) self.apiKeyCtrlVisible.Hide() self.apiKeyCtrlHidden.Show() self.Layout() + maxPos = len(value) + selectionStart = min(selectionStart, maxPos) + selectionEnd = min(selectionEnd, maxPos) + insertionPoint = min(insertionPoint, maxPos) + targetCtrl.SetSelection(selectionStart, selectionEnd) + targetCtrl.SetInsertionPoint(insertionPoint) + if restoreFocus: + targetCtrl.SetFocus() + def onGetKey(self, evt: wx.Event) -> None: webbrowser.open("https://aistudio.google.com/apikey") + def _getCurrentApiKeyFieldValue(self) -> str: + return ( + self.apiKeyCtrlVisible.GetValue() + if self.showApiCheck.IsChecked() + else self.apiKeyCtrlHidden.GetValue() + ) + + def _showStorageError(self, error: config_store.ApiKeyStorageError) -> None: + log.error(f"Failed to store the Gemini API key securely: {error}", exc_info=True) + wx.MessageBox( + # Translators: Error shown if Windows DPAPI storage fails while saving the API key. + _("Failed to save the Gemini API key securely: {error}").format(error=str(error)), + _("Error"), + wx.OK | wx.ICON_ERROR, + ) + + def isValid(self) -> bool: + try: + self._validatedApiKeyValue, self._validatedEncryptedApiKey = ( + config_store.prepare_api_key_for_storage( + self._getCurrentApiKeyFieldValue(), + ) + ) + except config_store.ApiKeyStorageError as error: + self._validatedApiKeyValue = "" + self._validatedEncryptedApiKey = "" + self._showStorageError(error) + return False + return True + def onReinstall(self, evt: wx.Event) -> None: """Handles the reinstall libraries action.""" res = wx.MessageBox( @@ -92,22 +166,16 @@ def onReinstall(self, evt: wx.Event) -> None: return try: - # Try to get LIB_DIR from lib_updater, fallback if needed try: targetLib = lib_updater.LIB_DIR except AttributeError: - # Fallback calculation matching __init__.py logic if lib_updater fails - # This path calculation assumes we are in gui/settings.py - # And we want .../globalPlugins/NativeSpeechGeneration/lib guiDir = os.path.dirname(os.path.abspath(__file__)) pkgDir = os.path.dirname(guiDir) targetLib = os.path.join(pkgDir, "lib") if os.path.exists(targetLib): - # Rename first to avoid lock issues, let cleanupTrash handle deletion on next run tempTrash = targetLib + "_trash_" + str(time.time()) os.rename(targetLib, tempTrash) - # Try to delete immediately, but ignore errors if locked shutil.rmtree(tempTrash, ignore_errors=True) wx.MessageBox( @@ -122,17 +190,18 @@ def onReinstall(self, evt: wx.Event) -> None: except Exception as e: log.error(f"Failed to delete lib folder: {e}", exc_info=True) wx.MessageBox( - f"Failed to remove library: {e}\nPlease check log.", + _("Failed to remove library: {error}\nPlease check log.").format(error=str(e)), _("Error"), wx.OK | wx.ICON_ERROR, ) def onSave(self) -> None: - if CONFIG_DOMAIN not in config.conf: - config.conf[CONFIG_DOMAIN] = {} - value = ( - self.apiKeyCtrlVisible.GetValue() - if self.showApiCheck.IsChecked() - else self.apiKeyCtrlHidden.GetValue() - ) - config.conf[CONFIG_DOMAIN]["apiKey"] = value + if not self.isValid(): + return + try: + config_store.write_prepared_api_key( + self._validatedApiKeyValue, + self._validatedEncryptedApiKey, + ) + except config_store.ApiKeyStorageError as error: + self._showStorageError(error) diff --git a/addon/globalPlugins/NativeSpeechGeneration/talkWithAI.py b/addon/globalPlugins/NativeSpeechGeneration/talkWithAI.py index 0d435a5..dbd1d3c 100644 --- a/addon/globalPlugins/NativeSpeechGeneration/talkWithAI.py +++ b/addon/globalPlugins/NativeSpeechGeneration/talkWithAI.py @@ -1,31 +1,35 @@ # -*- coding: utf-8 -*- -import wx -import threading import asyncio -import traceback +import os +import queue import random +import struct +import threading import time -from logHandler import log -import addonHandler - -import os +import traceback import winsound -import queue -import struct +import addonHandler +import wx +import ui +from logHandler import log from .core.gemini_imports import ( GENAI_AVAILABLE, + GENAI_IMPORT_ERROR, PYAUDIO_AVAILABLE, + PYAUDIO_IMPORT_ERROR, + VENDOR_VERSIONS, genai, getRuntimeScope, pyaudio, + types, ) addonHandler.initTranslation() -MODEL_NAME = "gemini-2.5-flash-native-audio-preview-12-2025" +MODEL_NAME = "gemini-3.1-flash-live-preview" MEDIA_DIR = os.path.join(os.path.dirname(__file__), "media") STREAM_START_SOUND_PATH = os.path.join(MEDIA_DIR, "stream-start.wav") STREAM_END_SOUND_PATH = os.path.join(MEDIA_DIR, "stream-end.wav") @@ -41,19 +45,24 @@ BACKOFF_BASE_SECONDS = 1.0 BACKOFF_MAX_SECONDS = 20.0 BACKOFF_JITTER_SECONDS = 0.4 -MEMORY_MAX_LINES = 12 -MEMORY_MAX_CHARS = 1800 +HISTORY_MAX_TURNS = 12 +HISTORY_MAX_CHARS = 1800 + + +class TalkWithAIRuntimeError(RuntimeError): + pass class TalkWithAIDialog(wx.Dialog): def __init__(self, parent, apiKey, voiceName, systemInstruction): # Translators: Title of the dialog for the "Talk With AI" feature (REAL-TIME conversation). - super().__init__(parent, title=_("Talk With AI"), size=(400, 300)) + super().__init__(parent, title=_("Talk With AI"), size=(420, 320)) self.apiKey = apiKey self.voiceName = voiceName self.systemInstruction = systemInstruction self.client = None + self.session = None self.sessionActive = False self.loop = None self.loopThread = None @@ -61,39 +70,67 @@ def __init__(self, parent, apiKey, voiceName, systemInstruction): self.inputStream = None self.outputStream = None self.micOn = True + self.useGoogleSearch = False + self.selectedThinkingLevel = "minimal" + self.historyConfigSupported = True self.audioQueue = queue.Queue() self.isPlaying = False - self.playThread = None - self.volume = 80 # Default volume percentage + self.volume = 80 self.bufferThreshold = BUFFER_THRESHOLD self.lastBufferAdjustAt = 0.0 self.lastStatusAt = 0.0 - self.memoryEnabled = True - self.sessionMemory = [] - self.memoryLock = threading.Lock() + self.lastAnnouncedStatus = "" + self._isClosing = False + self.sessionHistory = [] + self.historyLock = threading.Lock() - # Audio Device Selection self.inputDevices = self._getDeviceList(input=True) self.outputDevices = self._getDeviceList(input=False) self.selectedInputIdx = None self.selectedOutputIdx = None + self.thinkingChoices = [ + # Translators: Choice label for the lowest reasoning setting in Talk With AI. + (_("No Thinking"), "minimal"), + # Translators: Choice label for low reasoning depth in Talk With AI. + (_("Low"), "low"), + # Translators: Choice label for medium reasoning depth in Talk With AI. + (_("Medium"), "medium"), + # Translators: Choice label for high reasoning depth in Talk With AI. + (_("High"), "high"), + ] self._buildUi() + self.compatibilityError = self._getRuntimeCompatibilityError() if not PYAUDIO_AVAILABLE: wx.CallAfter( self.reportError, - _("PyAudio library is not installed. This feature requires PyAudio."), + self._buildMissingDependencyMessage( + _("PyAudio is not available. This feature requires a working PyAudio installation."), + PYAUDIO_IMPORT_ERROR, + ), ) self.connectBtn.Disable() if not GENAI_AVAILABLE: - wx.CallAfter(self.reportError, _("Google GenAI library is not installed.")) + wx.CallAfter( + self.reportError, + self._buildMissingDependencyMessage( + _("Google GenAI is not available."), + GENAI_IMPORT_ERROR, + ), + ) + self.connectBtn.Disable() + elif self.compatibilityError: + wx.CallAfter(self.reportError, self.compatibilityError) self.connectBtn.Disable() self.Bind(wx.EVT_CLOSE, self.onClose) self.Bind(wx.EVT_CHAR_HOOK, self.onCharHook) + def _logCleanupFailure(self, action, error): + log.debug(f"Talk With AI cleanup issue during {action}: {error}", exc_info=True) + def _getDeviceList(self, input=True): """Returns a list of dicts: {'index': int, 'name': str}""" devices = [] @@ -112,32 +149,29 @@ def _getDeviceList(self, input=True): else: if int(dev.get("maxOutputChannels", 0)) > 0: devices.append({"index": i, "name": dev.get("name")}) - except Exception as e: - log.error(f"Error listing devices: {e}") + except Exception as error: + log.error(f"Error listing devices: {error}") finally: p.terminate() return devices def _buildUi(self): - # Main Sizer mainSizer = wx.BoxSizer(wx.VERTICAL) panel = wx.Panel(self) panelSizer = wx.BoxSizer(wx.VERTICAL) - # 1. Status Area - # Translators: Label for the status area showing connection state. statusBox = wx.StaticBox(panel, label=_("Status")) statusSizer = wx.StaticBoxSizer(statusBox, wx.VERTICAL) - # Translators: Initial status message when the dialog opens. - self.statusLabel = wx.StaticText(panel, label=_("Ready to Connect")) + self.statusLabel = wx.StaticText( + panel, + label=_("Status: {status}").format(status=_("Ready to Connect")), + ) statusSizer.Add(self.statusLabel, 0, wx.ALL | wx.EXPAND, 5) panelSizer.Add(statusSizer, 0, wx.ALL | wx.EXPAND, 5) - # 2. Controls Area controlsBox = wx.StaticBox(panel, label=_("Controls")) controlsSizer = wx.StaticBoxSizer(controlsBox, wx.VERTICAL) - # Connect/Disconnect Buttons btnSizer = wx.BoxSizer(wx.HORIZONTAL) # Translators: Button to start the voice conversation. self.connectBtn = wx.Button(panel, label=_("Start Conversation")) @@ -151,20 +185,17 @@ def _buildUi(self): btnSizer.Add(self.disconnectBtn, 1, wx.LEFT, 5) controlsSizer.Add(btnSizer, 0, wx.EXPAND | wx.ALL, 5) - # Mic Toggle # Translators: Toggle button label indicating microphone is ON. self.micBtn = wx.ToggleButton(panel, label=_("Microphone: ON")) self.micBtn.SetValue(True) self.micBtn.Bind(wx.EVT_TOGGLEBUTTON, self.onMicToggle) controlsSizer.Add(self.micBtn, 0, wx.ALL | wx.EXPAND, 5) - # Device Selection Sizer self.deviceSizer = wx.BoxSizer(wx.VERTICAL) - # Input Device inputSizer = wx.BoxSizer(wx.HORIZONTAL) inputLabel = wx.StaticText(panel, label=_("Microphone:")) - inputChoices = [d["name"] for d in self.inputDevices] + inputChoices = [device["name"] for device in self.inputDevices] self.inputChoice = wx.Choice(panel, choices=inputChoices) if inputChoices: self.inputChoice.SetSelection(0) @@ -172,10 +203,9 @@ def _buildUi(self): inputSizer.Add(self.inputChoice, 1, wx.EXPAND) self.deviceSizer.Add(inputSizer, 0, wx.ALL | wx.EXPAND, 5) - # Output Device outputSizer = wx.BoxSizer(wx.HORIZONTAL) outputLabel = wx.StaticText(panel, label=_("Speaker:")) - outputChoices = [d["name"] for d in self.outputDevices] + outputChoices = [device["name"] for device in self.outputDevices] self.outputChoice = wx.Choice(panel, choices=outputChoices) if outputChoices: self.outputChoice.SetSelection(0) @@ -185,38 +215,34 @@ def _buildUi(self): controlsSizer.Add(self.deviceSizer, 0, wx.EXPAND) - # Google Search Checkbox - # Translators: Checkbox to enable "Grounding", allowing the AI to search Google for answers. + # Translators: Checkbox to enable grounding with Google Search in Talk With AI. self.googleSearchCb = wx.CheckBox(panel, label=_("Grounding with Google Search")) self.googleSearchCb.SetValue(False) controlsSizer.Add(self.googleSearchCb, 0, wx.ALL | wx.EXPAND, 5) - # Translators: Checkbox to enable temporary conversation memory during this dialog session. - self.memoryCb = wx.CheckBox(panel, label=_("Use session memory")) - self.memoryCb.SetValue(True) - self.memoryCb.Bind(wx.EVT_CHECKBOX, self.onMemoryToggle) - controlsSizer.Add(self.memoryCb, 0, wx.ALL | wx.EXPAND, 5) + thinkingSizer = wx.BoxSizer(wx.HORIZONTAL) + # Translators: Label for choosing the reasoning depth in Talk With AI. + self.thinkingLabel = wx.StaticText(panel, label=_("Thinking level:")) + self.thinkingChoice = wx.Choice(panel, choices=[label for label, _value in self.thinkingChoices]) + self.thinkingChoice.SetSelection(0) + thinkingSizer.Add(self.thinkingLabel, 0, wx.ALIGN_CENTER_VERTICAL | wx.RIGHT, 5) + thinkingSizer.Add(self.thinkingChoice, 1, wx.EXPAND) + controlsSizer.Add(thinkingSizer, 0, wx.ALL | wx.EXPAND, 5) - # Translators: Button to clear temporary conversation memory for the current dialog session. - self.clearMemoryBtn = wx.Button(panel, label=_("Clear memory now")) - self.clearMemoryBtn.Bind(wx.EVT_BUTTON, self.onClearMemory) - controlsSizer.Add(self.clearMemoryBtn, 0, wx.ALL | wx.EXPAND, 5) - - # Volume Slider volSizer = wx.BoxSizer(wx.HORIZONTAL) volLabel = wx.StaticText(panel, label=_("Volume:")) self.volSlider = wx.Slider(panel, value=self.volume, minValue=0, maxValue=100, style=wx.SL_HORIZONTAL) self.volSlider.Bind(wx.EVT_SLIDER, self.onVolumeChange) - volSizer.Add(volLabel, 0, wx.ALIGN_CENTER_VERTICAL | wx.RIGHT, 5) volSizer.Add(self.volSlider, 1, wx.EXPAND) controlsSizer.Add(volSizer, 0, wx.ALL | wx.EXPAND, 5) panelSizer.Add(controlsSizer, 0, wx.ALL | wx.EXPAND, 5) - # 3. Info Area (Voice only) - # Translators: Label for the selected voice - infoLabel = wx.StaticText(panel, label=_("Voice: ") + str(self.voiceName)) + infoLabel = wx.StaticText( + panel, + label=_("Voice: {voiceName}").format(voiceName=str(self.voiceName)), + ) panelSizer.Add(infoLabel, 0, wx.ALL | wx.ALIGN_CENTER_HORIZONTAL, 10) panel.SetSizer(panelSizer) @@ -224,14 +250,34 @@ def _buildUi(self): self.SetSizer(mainSizer) self.CenterOnParent() - def updateStatus(self, text): - if self: - self.statusLabel.SetLabel(_("Status: ") + text) + def _announceStatus(self, text, force=False): + message = str(text or "").strip() + if not message: + return + if not force and message == self.lastAnnouncedStatus: + return + self.lastAnnouncedStatus = message + try: + ui.message(message) + except Exception as error: + self._logCleanupFailure("status announcement", error) + + def updateStatus(self, text, announce=False, forceAnnouncement=False): + try: + if self: + self.statusLabel.SetLabel(_("Status: {status}").format(status=text)) + except RuntimeError: + return + if announce: + self._announceStatus(text, force=forceAnnouncement) def reportError(self, msg): - if self: - wx.MessageBox(str(msg), _("Error"), wx.OK | wx.ICON_ERROR) - self.updateStatus(_("Error")) + try: + if self: + wx.MessageBox(str(msg), _("Error"), wx.OK | wx.ICON_ERROR) + self.updateStatus(_("Error")) + except RuntimeError: + return def onMicToggle(self, evt): self.micOn = self.micBtn.GetValue() @@ -241,43 +287,66 @@ def onMicToggle(self, evt): def onVolumeChange(self, evt): self.volume = self.volSlider.GetValue() - def onMemoryToggle(self, evt): - self.memoryEnabled = self.memoryCb.GetValue() - if not self.memoryEnabled: - self._clearSessionMemory() - if self.sessionActive: - self.clearMemoryBtn.Hide() - else: - self.clearMemoryBtn.Show(self.memoryEnabled) - self.Layout() - - def onClearMemory(self, evt): - self._clearSessionMemory() - self.updateStatus(_("Memory cleared")) - - def _clearSessionMemory(self): - with self.memoryLock: - self.sessionMemory = [] + def _getSelectedThinkingLevel(self): + selection = self.thinkingChoice.GetSelection() + if selection == wx.NOT_FOUND: + return "minimal" + return self.thinkingChoices[selection][1] + + def _clearSessionHistory(self): + with self.historyLock: + self.sessionHistory = [] + + def _buildMissingDependencyMessage(self, baseMessage, errorDetail): + if not errorDetail: + return baseMessage + return _("{baseMessage}\n\nImport detail: {errorDetail}").format( + baseMessage=baseMessage, + errorDetail=errorDetail, + ) - def _rememberMemoryLine(self, line): - if not line: - return - cleaned = str(line).strip() + def _mergeHistoryText(self, existing, incoming): + if not existing: + return incoming + if incoming == existing or existing.endswith(incoming): + return existing + if incoming.startswith(existing): + return incoming + if existing.startswith(incoming): + return existing + return f"{existing} {incoming}" + + def _rememberConversationTurn(self, role, text): + cleaned = str(text or "").strip() if not cleaned: return - with self.memoryLock: - self.sessionMemory.append(cleaned) - if len(self.sessionMemory) > MEMORY_MAX_LINES: - self.sessionMemory = self.sessionMemory[-MEMORY_MAX_LINES:] - totalChars = sum(len(item) for item in self.sessionMemory) - while self.sessionMemory and totalChars > MEMORY_MAX_CHARS: - totalChars -= len(self.sessionMemory.pop(0)) - - def _memorySummary(self): - with self.memoryLock: - if not self.sessionMemory: - return "" - return "\n".join(f"- {line}" for line in self.sessionMemory[-MEMORY_MAX_LINES:]) + with self.historyLock: + if self.sessionHistory and self.sessionHistory[-1]["role"] == role: + self.sessionHistory[-1]["text"] = self._mergeHistoryText( + self.sessionHistory[-1]["text"], + cleaned, + ) + else: + self.sessionHistory.append({"role": role, "text": cleaned}) + self._trimSessionHistory() + + def _trimSessionHistory(self): + if len(self.sessionHistory) > HISTORY_MAX_TURNS: + self.sessionHistory = self.sessionHistory[-HISTORY_MAX_TURNS:] + totalChars = sum(len(turn["text"]) for turn in self.sessionHistory) + while self.sessionHistory and totalChars > HISTORY_MAX_CHARS: + totalChars -= len(self.sessionHistory.pop(0)["text"]) + + def _buildReconnectHistoryTurns(self): + with self.historyLock: + return [ + types.Content( + role=turn["role"], + parts=[types.Part(text=turn["text"])], + ) + for turn in self.sessionHistory + if turn["text"].strip() + ] def _buildSystemInstruction(self): baseRules = ( @@ -288,30 +357,63 @@ def _buildSystemInstruction(self): parts = [baseRules] if userInstruction: parts.append(f"User preference:\n{userInstruction}") - if self.memoryEnabled: - summary = self._memorySummary() - if summary: - parts.append(f"Session memory:\n{summary}") return "\n\n".join(parts) def _buildReconnectDelay(self, attempt): baseDelay = min(BACKOFF_MAX_SECONDS, BACKOFF_BASE_SECONDS * (2 ** max(0, attempt - 1))) return baseDelay + random.uniform(0.0, BACKOFF_JITTER_SECONDS) + def _getRuntimeCompatibilityError(self): + if not GENAI_AVAILABLE: + return None + requiredTypeNames = ( + "LiveConnectConfig", + "SpeechConfig", + "VoiceConfig", + "PrebuiltVoiceConfig", + "ThinkingConfig", + "AudioTranscriptionConfig", + "Blob", + "HistoryConfig", + "Content", + "Part", + "Tool", + "GoogleSearch", + ) + missing = [name for name in requiredTypeNames if not hasattr(types, name)] + if not hasattr(genai, "Client"): + missing.append("Client") + if missing: + version = VENDOR_VERSIONS.get("google.genai", "") + if version: + return _( + "Installed google-genai library ({version}) does not support the Gemini 3.1 Live API features required by Talk With AI. Missing: {missing}. Please update the add-on libraries.", + ).format( + version=version, + missing=", ".join(missing), + ) + return _( + "Installed google-genai library does not support the Gemini 3.1 Live API features required by Talk With AI. Missing: {missing}. Please update the add-on libraries.", + ).format( + missing=", ".join(missing), + ) + return None + def onConnect(self, evt): + if self.compatibilityError: + self.reportError(self.compatibilityError) + return + self.connectBtn.Disable() self.disconnectBtn.Enable() - # Store state and hide checkbox self.useGoogleSearch = self.googleSearchCb.GetValue() - self.memoryEnabled = self.memoryCb.GetValue() - if not self.memoryEnabled: - self._clearSessionMemory() + self.selectedThinkingLevel = self._getSelectedThinkingLevel() + self.googleSearchCb.Hide() - self.memoryCb.Disable() - self.clearMemoryBtn.Hide() + self.thinkingLabel.Hide() + self.thinkingChoice.Hide() - # Get selected devices inSel = self.inputChoice.GetSelection() if inSel != wx.NOT_FOUND and self.inputDevices: self.selectedInputIdx = self.inputDevices[inSel]["index"] @@ -320,21 +422,19 @@ def onConnect(self, evt): if outSel != wx.NOT_FOUND and self.outputDevices: self.selectedOutputIdx = self.outputDevices[outSel]["index"] - # Hide device selection widgets for child in self.deviceSizer.GetChildren(): - w = child.GetWindow() - if w: - w.Hide() - s = child.GetSizer() - if s: - for c in s.GetChildren(): - w2 = c.GetWindow() - if w2: - w2.Hide() + window = child.GetWindow() + if window: + window.Hide() + sizer = child.GetSizer() + if sizer: + for nestedChild in sizer.GetChildren(): + nestedWindow = nestedChild.GetWindow() + if nestedWindow: + nestedWindow.Hide() self.Layout() - - self.updateStatus(_("Connecting...")) + self.updateStatus(_("Connecting..."), announce=True) self.sessionActive = True self.loopThread = threading.Thread(target=self._startAsyncLoop, daemon=True) @@ -342,20 +442,17 @@ def onConnect(self, evt): def onDisconnect(self, evt): self.disconnectBtn.Disable() - self.updateStatus(_("Disconnecting...")) - # Sound is played in runSession finally block + self.updateStatus(_("Disconnecting..."), announce=True) if self.loop and self.loop.is_running(): asyncio.run_coroutine_threadsafe(self.cleanupAsync(), self.loop) def _playSoundEffect(self, path): - """Plays a local sound file asynchronously.""" - def _bgPlay(): try: if os.path.exists(path): winsound.PlaySound(path, winsound.SND_FILENAME | winsound.SND_ASYNC) - except Exception: - pass + except Exception as error: + self._logCleanupFailure("sound effect playback", error) threading.Thread(target=_bgPlay, daemon=True).start() @@ -365,84 +462,73 @@ def onCharHook(self, evt): else: evt.Skip() - def onClose(self, evt: wx.Event): + def onClose(self, evt): + self._isClosing = True self.sessionActive = False self.isPlaying = False - self._clearSessionMemory() - - # Drain and clear audio queue - while not self.audioQueue.empty(): - try: - self.audioQueue.get_nowait() - except queue.Empty: - pass + self._clearSessionHistory() + self._flushAudioQueue() self.audioQueue.put(b"") if self.loop and self.loop.is_running(): try: - # Schedule cleanup and wait for it - future = asyncio.run_coroutine_threadsafe(self.cleanupAsync(), self.loop) - # Wait briefly for cleanup to run - try: - future.result(timeout=2.0) - except Exception: - pass - - # Signal loop to stop - self.loop.call_soon_threadsafe(self.loop.stop) - except Exception: - pass - - if self.audioInterface: + asyncio.run_coroutine_threadsafe(self._shutdownLoop(), self.loop) + except Exception as error: + self._logCleanupFailure("loop shutdown scheduling", error) + elif self.audioInterface: try: self.audioInterface.terminate() - except Exception: - pass + except Exception as error: + self._logCleanupFailure("audio interface termination", error) self.Destroy() + async def _shutdownLoop(self): + try: + await self.cleanupAsync() + finally: + loop = asyncio.get_running_loop() + loop.stop() + def _startAsyncLoop(self): try: self.loop = asyncio.new_event_loop() asyncio.set_event_loop(self.loop) self.loop.run_until_complete(self.runSession()) - except RuntimeError: - # Loop stopped cleanly or forcefully - pass - except Exception as e: - log.error(f"Async Loop Error: {e}", exc_info=True) + except RuntimeError as error: + self._logCleanupFailure("async loop shutdown", error) + except Exception as error: + log.error(f"Async Loop Error: {error}", exc_info=True) finally: try: - # Cancel all remaining tasks specific to this loop pending = asyncio.all_tasks(self.loop) for task in pending: task.cancel() - - # Allow cancellations to process if pending and not self.loop.is_closed(): self.loop.run_until_complete(asyncio.gather(*pending, return_exceptions=True)) - if not self.loop.is_closed(): self.loop.close() - except Exception: - pass + except Exception as error: + self._logCleanupFailure("async loop finalization", error) + + def _flushAudioQueue(self): + while not self.audioQueue.empty(): + try: + self.audioQueue.get_nowait() + except queue.Empty: + break async def cleanupAsync(self): self.sessionActive = False self.isPlaying = False + self.session = None - # Cancel all tasks in the current loop to force exits currentTask = asyncio.current_task() for task in asyncio.all_tasks(): if task is not currentTask: task.cancel() - # Drain queue - while not self.audioQueue.empty(): - try: - self.audioQueue.get_nowait() - except Exception: - pass + self._flushAudioQueue() if self.inputStream: self.inputStream.stop_stream() @@ -457,51 +543,36 @@ async def cleanupAsync(self): self.audioInterface = None def _audioPlayerWorker(self): - """ - Thread that consumes audio chunks from the queue, applies volume, and plays them. - """ buffer = [] buffering = True self.bufferThreshold = BUFFER_THRESHOLD while self.sessionActive and self.isPlaying: try: - # Wait for data data = self.audioQueue.get(timeout=0.1) - - # Apply Volume if self.volume != 100: - # Parse 16-bit PCM (signed) count = len(data) // 2 shorts = struct.unpack(f"{count}h", data) - - # Apply scale factor (0.0 to 1.0) factor = self.volume / 100.0 - - # Scale and clip scaledShorts = [] - for s in shorts: - val = int(s * factor) - # Clip to 16-bit range - if val > 32767: - val = 32767 - if val < -32768: - val = -32768 - scaledShorts.append(val) - + for sample in shorts: + value = int(sample * factor) + if value > 32767: + value = 32767 + if value < -32768: + value = -32768 + scaledShorts.append(value) data = struct.pack(f"{count}h", *scaledShorts) if buffering: buffer.append(data) if len(buffer) >= self.bufferThreshold: buffering = False - # Play accumulated buffer if self.outputStream and self.outputStream.is_active(): for chunk in buffer: self.outputStream.write(chunk) buffer = [] else: - # Direct play if self.outputStream and self.outputStream.is_active(): self.outputStream.write(data) queueDepth = self.audioQueue.qsize() @@ -514,10 +585,7 @@ def _audioPlayerWorker(self): if now - self.lastBufferAdjustAt > 1.0: self.bufferThreshold += 1 self.lastBufferAdjustAt = now - except queue.Empty: - # If queue is empty, we have run out of audio data. - # To prevent micro-stuttering, we switch back to buffering mode. if not buffering and self.sessionActive: buffering = True now = time.monotonic() @@ -525,10 +593,71 @@ def _audioPlayerWorker(self): self.bufferThreshold += 1 self.lastBufferAdjustAt = now continue - except Exception as e: - log.error(f"Audio Player Error: {e}") + except Exception as error: + log.error(f"Audio Player Error: {error}") break + def _buildLiveConfig(self, includeHistorySeed): + if not types: + raise TalkWithAIRuntimeError(_("Google GenAI types are not available.")) + try: + with getRuntimeScope(): + return types.LiveConnectConfig( + response_modalities=["AUDIO"], + speech_config=types.SpeechConfig( + voice_config=types.VoiceConfig( + prebuilt_voice_config=types.PrebuiltVoiceConfig(voice_name=self.voiceName), + ), + ), + system_instruction=self._buildSystemInstruction(), + thinking_config=types.ThinkingConfig(thinking_level=self.selectedThinkingLevel), + input_audio_transcription=types.AudioTranscriptionConfig(), + output_audio_transcription=types.AudioTranscriptionConfig(), + tools=[types.Tool(google_search=types.GoogleSearch())] if self.useGoogleSearch else None, + history_config=( + types.HistoryConfig(initial_history_in_client_content=True) + if includeHistorySeed and self.historyConfigSupported + else None + ), + ) + except Exception as error: + raise TalkWithAIRuntimeError( + _("Failed to prepare the Gemini Live configuration. Please update the add-on libraries."), + ) from error + + def _assertSessionCompatibility(self, session): + for methodName in ("send_realtime_input", "send_client_content"): + if not hasattr(session, methodName): + version = VENDOR_VERSIONS.get("google.genai", "") + if version: + raise TalkWithAIRuntimeError( + _( + "The installed google-genai library ({version}) is too old for Gemini 3.1 Live sessions. Missing session method: {methodName}. Please update the add-on libraries.", + ).format( + version=version, + methodName=methodName, + ), + ) + raise TalkWithAIRuntimeError( + _( + "The installed google-genai library is too old for Gemini 3.1 Live sessions. Missing session method: {methodName}. Please update the add-on libraries.", + ).format( + methodName=methodName, + ), + ) + + async def _seedSessionHistory(self, session): + historyTurns = self._buildReconnectHistoryTurns() + if not historyTurns: + return + await session.send_client_content(turns=historyTurns, turn_complete=False) + + def _shouldRetryWithoutHistoryConfig(self, error, usedHistoryConfig): + if not usedHistoryConfig or not self.historyConfigSupported: + return False + message = f"{error!r}".lower() + return "history_config" in message or "initial_history_in_client_content" in message + async def sendAudioLoop(self, session): while self.sessionActive: if self.micOn and self.inputStream and self.inputStream.is_active(): @@ -537,74 +666,86 @@ async def sendAudioLoop(self, session): None, lambda: self.inputStream.read(CHUNK, exception_on_overflow=False), ) - await session.send( - input={"data": data, "mime_type": f"audio/pcm;rate={INPUT_RATE}"}, - end_of_turn=False, - ) - except Exception as e: - log.error(f"Mic/Send Error: {e}") + with getRuntimeScope(): + audioBlob = types.Blob(data=data, mime_type=f"audio/pcm;rate={INPUT_RATE}") + await session.send_realtime_input(audio=audioBlob) + except asyncio.CancelledError: + raise + except Exception as error: + log.error(f"Mic/Send Error: {error}") break else: await asyncio.sleep(0.1) + def _queueAudioData(self, data): + if data: + self.audioQueue.put(data) + + def _handleServerContent(self, serverContent): + queuedAudio = False + if getattr(serverContent, "interrupted", False): + log.debug("TalkWithAI: Server Interrupted") + self._flushAudioQueue() + return queuedAudio + + inputTranscription = getattr(serverContent, "input_transcription", None) + if inputTranscription and getattr(inputTranscription, "text", None): + self._rememberConversationTurn("user", inputTranscription.text) + + outputTranscription = getattr(serverContent, "output_transcription", None) + if outputTranscription and getattr(outputTranscription, "text", None): + self._rememberConversationTurn("model", outputTranscription.text) + + modelTurn = getattr(serverContent, "model_turn", None) + if modelTurn is None: + return queuedAudio + for part in getattr(modelTurn, "parts", []) or []: + inlineData = getattr(part, "inline_data", None) + if inlineData is not None and getattr(inlineData, "data", None): + self._queueAudioData(inlineData.data) + queuedAudio = True + textPart = getattr(part, "text", None) + if textPart: + self._rememberConversationTurn("model", textPart) + return queuedAudio + async def receiveLoop(self, session): try: async for response in session.receive(): if not self.sessionActive: break - if response.server_content is None: - continue - - # Handle Interruption - if response.server_content.interrupted: - log.debug("TalkWithAI: Server Interrupted") - # Clear audio queue to stop playing immediately - # This prevents "echo" loop where model hears its own delayed speech - while not self.audioQueue.empty(): - try: - self.audioQueue.get_nowait() - except queue.Empty: - break - continue - - # Handle Turn Complete - if response.server_content.turn_complete: - log.debug("TalkWithAI: Turn Complete") - continue - - inputTranscription = getattr(response.server_content, "input_transcription", None) - if inputTranscription and getattr(inputTranscription, "text", None): - self._rememberMemoryLine(f"User: {inputTranscription.text}") - - outputTranscription = getattr(response.server_content, "output_transcription", None) - if outputTranscription and getattr(outputTranscription, "text", None): - self._rememberMemoryLine(f"Assistant: {outputTranscription.text}") - - modelTurn = response.server_content.model_turn - if modelTurn is not None: - for part in modelTurn.parts: - if part.inline_data is not None: - audioData = part.inline_data.data - # Push to queue instead of writing directly - self.audioQueue.put(audioData) - textPart = getattr(part, "text", None) - if textPart: - self._rememberMemoryLine(f"Assistant: {textPart}") - except Exception as e: - log.error(f"TalkWithAI Receive Loop Error: {e}") + text = getattr(response, "text", None) + if text: + self._rememberConversationTurn("model", text) + + serverContent = getattr(response, "server_content", None) + queuedAudioFromServerContent = False + if serverContent is not None: + queuedAudioFromServerContent = self._handleServerContent(serverContent) + + data = getattr(response, "data", None) + if data and not queuedAudioFromServerContent: + # Current python-genai Live API examples consume audio from + # server_content.model_turn.parts[].inline_data. Keep response.data + # only as a fallback for compatibility with alternate payloads. + self._queueAudioData(data) + + toolCall = getattr(response, "tool_call", None) + if toolCall is not None: + log.debug("TalkWithAI: Ignoring tool_call event from Live API.") + except asyncio.CancelledError: + raise + except Exception as error: + log.error(f"TalkWithAI Receive Loop Error: {error}") finally: log.debug("TalkWithAI: Receive loop ended") - # Do NOT set self.sessionActive = False here. - # Doing so would stop the reconnection loop in runSession. async def runSession(self): try: - # Setup Audio (Once for the entire session duration) with getRuntimeScope(): self.audioInterface = pyaudio.PyAudio() - # Output Stream (Speaker) self.outputStream = self.audioInterface.open( format=FORMAT, channels=CHANNELS, @@ -614,7 +755,6 @@ async def runSession(self): output_device_index=self.selectedOutputIdx, ) - # Input Stream (Mic) self.inputStream = self.audioInterface.open( format=FORMAT, channels=CHANNELS, @@ -624,45 +764,36 @@ async def runSession(self): input_device_index=self.selectedInputIdx, ) - # Initialize Client with getRuntimeScope(): - self.client = genai.Client(api_key=self.apiKey, http_options={"api_version": "v1alpha"}) + self.client = genai.Client(api_key=self.apiKey, http_options={"api_version": "v1beta"}) firstConnect = True retryAttempt = 0 - # Main Reconnection Loop while self.sessionActive: + usedHistoryConfig = False try: - toolsConfig = [] - if self.useGoogleSearch: - toolsConfig.append({"google_search": {}}) - systemInstruction = self._buildSystemInstruction() - config = { - "response_modalities": ["AUDIO"], - "tools": toolsConfig, - "generation_config": { - "speech_config": { - "voice_config": {"prebuilt_voice_config": {"voice_name": self.voiceName}}, - }, - }, - "system_instruction": {"parts": [{"text": systemInstruction}]}, - } - log.debug("TalkWithAI: Connecting to Gemini Live...") + historyTurns = self._buildReconnectHistoryTurns() + usedHistoryConfig = bool(historyTurns) and self.historyConfigSupported + config = self._buildLiveConfig(includeHistorySeed=bool(historyTurns)) + + log.debug("TalkWithAI: Connecting to Gemini Live 3.1...") with getRuntimeScope(): async with self.client.aio.live.connect(model=MODEL_NAME, config=config) as session: + self._assertSessionCompatibility(session) + self.session = session + + if historyTurns: + await self._seedSessionHistory(session) + retryAttempt = 0 if firstConnect: - wx.CallAfter(self.updateStatus, _("Connected")) - # Play start sound only on the very first successful connection + wx.CallAfter(self.updateStatus, _("Connected"), True) self._playSoundEffect(STREAM_START_SOUND_PATH) firstConnect = False else: log.debug("TalkWithAI: Reconnected silently") - self.session = session - - # Start sending and receiving tasks sendTask = asyncio.create_task(self.sendAudioLoop(session)) receiveTask = asyncio.create_task(self.receiveLoop(session)) @@ -671,15 +802,11 @@ async def runSession(self): self.isPlaying = True playWorker.start() - # Wait for either task to finish - # If connection drops, receiveLoop finishes. - # We then cancel sendTask and reconnect. _done, pending = await asyncio.wait( [sendTask, receiveTask], return_when=asyncio.FIRST_COMPLETED, ) - # Cancel pending tasks (e.g. send loop if receive died) for task in pending: task.cancel() try: @@ -687,8 +814,21 @@ async def runSession(self): except asyncio.CancelledError: pass - except Exception as e: - log.error(f"TalkWithAI Session/Connection Error: {e}") + except TalkWithAIRuntimeError as error: + log.error(f"TalkWithAI Compatibility Error: {error}") + wx.CallAfter(self.reportError, str(error)) + self.sessionActive = False + except asyncio.CancelledError: + raise + except Exception as error: + if self._shouldRetryWithoutHistoryConfig(error, usedHistoryConfig): + log.warning( + "TalkWithAI: Live session rejected history_config; retrying without it.", + exc_info=True, + ) + self.historyConfigSupported = False + continue + log.error(f"TalkWithAI Session/Connection Error: {error}", exc_info=True) retryAttempt += 1 delay = self._buildReconnectDelay(retryAttempt) now = time.monotonic() @@ -696,6 +836,7 @@ async def runSession(self): wx.CallAfter( self.updateStatus, _("Connection lost. Retrying in {seconds:.1f}s").format(seconds=delay), + True, ) self.lastStatusAt = now await asyncio.sleep(delay) @@ -705,16 +846,14 @@ async def runSession(self): except asyncio.CancelledError: pass - except Exception as e: + except Exception: log.error(f"TalkWithAI Fatal Error: {traceback.format_exc()}") - wx.CallAfter(self.reportError, str(e)) + wx.CallAfter(self.reportError, traceback.format_exc()) self.sessionActive = False finally: - # Play end sound only when completely stopping self._playSoundEffect(STREAM_END_SOUND_PATH) - - wx.CallAfter(self.resetUi) - # Cleanup Audio + if not self._isClosing: + wx.CallAfter(self.resetUi) if self.inputStream: self.inputStream.stop_stream() self.inputStream.close() @@ -726,6 +865,7 @@ async def runSession(self): if self.audioInterface: self.audioInterface.terminate() self.audioInterface = None + self.session = None def resetUi(self): if self: @@ -733,22 +873,21 @@ def resetUi(self): self.connectBtn.Enable() self.disconnectBtn.Disable() self.googleSearchCb.Show() - self.memoryCb.Enable() - self.clearMemoryBtn.Show(self.memoryCb.GetValue()) + self.thinkingLabel.Show() + self.thinkingChoice.Show() - # Show device selection again for child in self.deviceSizer.GetChildren(): - w = child.GetWindow() - if w: - w.Show() - s = child.GetSizer() - if s: - for c in s.GetChildren(): - w2 = c.GetWindow() - if w2: - w2.Show() + window = child.GetWindow() + if window: + window.Show() + sizer = child.GetSizer() + if sizer: + for nestedChild in sizer.GetChildren(): + nestedWindow = nestedChild.GetWindow() + if nestedWindow: + nestedWindow.Show() self.Layout() - self.updateStatus(_("Ready")) + self.updateStatus(_("Ready"), announce=True) except RuntimeError: - pass # Window might be destroyed + pass diff --git a/addon/installTasks.py b/addon/installTasks.py index d63bc42..e25f9f6 100644 --- a/addon/installTasks.py +++ b/addon/installTasks.py @@ -7,13 +7,10 @@ import os import shutil import addonHandler -import config from logHandler import log addonHandler.initTranslation() -CONFIG_DOMAIN = "NativeSpeechGeneration" - def onInstall() -> None: """ @@ -61,17 +58,6 @@ def onInstall() -> None: def onUninstall() -> None: """ Called when the add-on is uninstalled. - Cleans up configuration. Note: This also runs during updates (as the old version is removed). + The configuration is intentionally preserved so add-on updates do not wipe user settings. """ - # Clean up config spec - if CONFIG_DOMAIN in config.conf.spec: - del config.conf.spec[CONFIG_DOMAIN] - - # Clean up config values from all profiles - for profile in config.conf.profiles: - if CONFIG_DOMAIN in profile: - del profile[CONFIG_DOMAIN] - profile.save() # Ensure save? usually config.save() handles it later, but deleting dict key is immediate in memory. - - config.save() - log.info(f"Configuration for '{CONFIG_DOMAIN}' has been removed.") + log.info("NativeSpeechGeneration uninstall: preserving configuration to avoid data loss across updates.") diff --git a/addon/locale/es/LC_MESSAGES/nvda.po b/addon/locale/es/LC_MESSAGES/nvda.po index a8726ff..faf0ed6 100644 --- a/addon/locale/es/LC_MESSAGES/nvda.po +++ b/addon/locale/es/LC_MESSAGES/nvda.po @@ -1,9 +1,9 @@ msgid "" msgstr "" -"Project-Id-Version: 'NativeSpeechGeneration' '1.5.6'\n" +"Project-Id-Version: 'NativeSpeechGeneration' '1.6.0'\n" "Report-Msgid-Bugs-To: 'nvda-translations@groups.io'\n" -"POT-Creation-Date: 2026-02-20 16:58+0700\n" -"PO-Revision-Date: 2026-02-20 17:12+0700\n" +"POT-Creation-Date: 2026-04-04 16:18+0800\n" +"PO-Revision-Date: 2026-04-04 16:46+0800\n" "Last-Translator: \n" "Language-Team: \n" "Language: es\n" @@ -11,7 +11,7 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -"X-Generator: Poedit 3.8\n" +"X-Generator: Poedit 3.9\n" #: addon\globalPlugins\NativeSpeechGeneration\lib_updater.py:57 msgid "Downloading libraries..." @@ -44,21 +44,21 @@ msgstr "" #: addon\globalPlugins\NativeSpeechGeneration\lib_updater.py:97 #: addon\globalPlugins\NativeSpeechGeneration\lib_updater.py:153 -#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:233 -#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:234 -#: addon\globalPlugins\NativeSpeechGeneration\__init__.py:146 -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:346 -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:363 -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:383 -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:394 -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:402 -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:411 -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:434 -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:525 -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:596 -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:619 -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:658 -#: addon\globalPlugins\NativeSpeechGeneration\interface\settings.py:126 +#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:277 +#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:278 +#: addon\globalPlugins\NativeSpeechGeneration\__init__.py:131 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:336 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:355 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:388 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:405 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:417 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:440 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:531 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:599 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:621 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:659 +#: addon\globalPlugins\NativeSpeechGeneration\interface\settings.py:139 +#: addon\globalPlugins\NativeSpeechGeneration\interface\settings.py:191 msgid "Error" msgstr "Error" @@ -104,7 +104,7 @@ msgstr "" "dependencias y requerirá un reinicio de NVDA." #: addon\globalPlugins\NativeSpeechGeneration\lib_updater.py:166 -#: addon\globalPlugins\NativeSpeechGeneration\interface\settings.py:88 +#: addon\globalPlugins\NativeSpeechGeneration\interface\settings.py:159 msgid "Confirm Reinstall" msgstr "Confirmar reinstalación" @@ -121,134 +121,209 @@ msgid "Missing Dependencies" msgstr "Dependencias faltantes" #. Translators: Title of the dialog for the "Talk With AI" feature (REAL-TIME conversation). -#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:51 +#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:59 msgid "Talk With AI" msgstr "Hablar con IA" -#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:87 -msgid "PyAudio library is not installed. This feature requires PyAudio." +#. Translators: Choice label for the lowest reasoning setting in Talk With AI. +#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:94 +msgid "No Thinking" +msgstr "Sin razonamiento" + +#. Translators: Choice label for low reasoning depth in Talk With AI. +#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:96 +msgid "Low" +msgstr "Bajo" + +#. Translators: Choice label for medium reasoning depth in Talk With AI. +#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:98 +msgid "Medium" +msgstr "Medio" + +#. Translators: Choice label for high reasoning depth in Talk With AI. +#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:100 +msgid "High" +msgstr "Alto" + +#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:110 +msgid "" +"PyAudio is not available. This feature requires a working PyAudio " +"installation." msgstr "" -"La librería PyAudio no está instalada. Esta característica requiere PyAudio." +"PyAudio no está disponible. Esta función requiere una instalación funcional " +"de PyAudio." -#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:91 -msgid "Google GenAI library is not installed." -msgstr "La librería Google GenAI no está instalada." +#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:119 +msgid "Google GenAI is not available." +msgstr "Google GenAI no está disponible." -#. 1. Status Area -#. Translators: Label for the status area showing connection state. -#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:129 +#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:163 msgid "Status" msgstr "Estado" -#. Translators: Initial status message when the dialog opens. -#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:132 +#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:167 +#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:268 +#, python-brace-format +msgid "Status: {status}" +msgstr "Estado: {status}" + +#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:167 msgid "Ready to Connect" msgstr "Listo para conectar" -#. 2. Controls Area -#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:137 +#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:172 msgid "Controls" msgstr "Controles" #. Translators: Button to start the voice conversation. -#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:143 +#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:177 msgid "Start Conversation" msgstr "Iniciar conversación" #. Translators: Button to stop the voice conversation. -#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:146 +#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:180 msgid "Stop Conversation" msgstr "Detener conversación" -#. Mic Toggle #. Translators: Toggle button label indicating microphone is ON. -#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:156 -#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:238 +#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:189 +#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:284 msgid "Microphone: ON" msgstr "Micrófono: ENCENDIDO" -#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:166 +#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:197 msgid "Microphone:" msgstr "Micrófono:" -#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:177 +#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:207 msgid "Speaker:" msgstr "Altavoz:" -#. Google Search Checkbox -#. Translators: Checkbox to enable "Grounding", allowing the AI to search Google for answers. -#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:190 +#. Translators: Checkbox to enable grounding with Google Search in Talk With AI. +#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:219 msgid "Grounding with Google Search" msgstr "Contextualización con Búsqueda de Google" -#. Translators: Checkbox to enable temporary conversation memory during this dialog session. -#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:195 -msgid "Use session memory" -msgstr "" - -#. Translators: Button to clear temporary conversation memory for the current dialog session. -#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:201 -msgid "Clear memory now" -msgstr "" +#. Translators: Label for choosing the reasoning depth in Talk With AI. +#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:225 +msgid "Thinking level:" +msgstr "Nivel de razonamiento:" -#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:207 +#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:233 msgid "Volume:" msgstr "Volumen:" -#. 3. Info Area (Voice only) -#. Translators: Label for the selected voice -#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:219 -msgid "Voice: " -msgstr "Voz: " - -#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:229 -msgid "Status: " -msgstr "Estado: " +#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:244 +#, python-brace-format +msgid "Voice: {voiceName}" +msgstr "Voz: {voiceName}" -#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:238 +#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:284 msgid "Microphone: OFF" msgstr "Micrófono: APAGADO" -#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:256 -msgid "Memory cleared" +#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:303 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:398 +#, python-brace-format +msgid "" +"{baseMessage}\n" +"\n" +"Import detail: {errorDetail}" msgstr "" +"{baseMessage}\n" +"\n" +"Detalle de importación: {errorDetail}" -#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:337 +#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:387 +#, python-brace-format +msgid "" +"Installed google-genai library ({version}) does not support the Gemini 3.1 " +"Live API features required by Talk With AI. Missing: {missing}. Please " +"update the add-on libraries." +msgstr "" +"La biblioteca google-genai instalada ({version}) no es compatible con las " +"funciones de la API Live de Gemini 3.1 necesarias para Hablar con IA. " +"Faltan: {missing}. Actualiza las bibliotecas del complemento." + +#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:393 +#, python-brace-format +msgid "" +"Installed google-genai library does not support the Gemini 3.1 Live API " +"features required by Talk With AI. Missing: {missing}. Please update the add-" +"on libraries." +msgstr "" +"La biblioteca google-genai instalada no es compatible con las funciones de " +"la API Live de Gemini 3.1 necesarias para Hablar con IA. Faltan: {missing}. " +"Actualiza las bibliotecas del complemento." + +#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:434 msgid "Connecting..." msgstr "Conectando..." -#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:345 +#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:442 msgid "Disconnecting..." msgstr "Desconectando..." -#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:656 +#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:599 +msgid "Google GenAI types are not available." +msgstr "Los tipos de Google GenAI no están disponibles." + +#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:622 +msgid "" +"Failed to prepare the Gemini Live configuration. Please update the add-on " +"libraries." +msgstr "" +"No se pudo preparar la configuración de Gemini Live. Actualiza las " +"bibliotecas del complemento." + +#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:632 +#, python-brace-format +msgid "" +"The installed google-genai library ({version}) is too old for Gemini 3.1 " +"Live sessions. Missing session method: {methodName}. Please update the add-" +"on libraries." +msgstr "" +"La biblioteca google-genai instalada ({version}) es demasiado antigua para " +"las sesiones Live de Gemini 3.1. Falta el método de sesión: {methodName}. " +"Actualiza las bibliotecas del complemento." + +#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:640 +#, python-brace-format +msgid "" +"The installed google-genai library is too old for Gemini 3.1 Live sessions. " +"Missing session method: {methodName}. Please update the add-on libraries." +msgstr "" +"La biblioteca google-genai instalada es demasiado antigua para las sesiones " +"Live de Gemini 3.1. Falta el método de sesión: {methodName}. Actualiza las " +"bibliotecas del complemento." + +#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:788 msgid "Connected" msgstr "Conectado" -#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:698 -#, fuzzy, python-brace-format -#| msgid "Connection Lost. Retrying..." +#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:835 +#, python-brace-format msgid "Connection lost. Retrying in {seconds:.1f}s" -msgstr "Conexión perdida. Reintentando..." +msgstr "Conexión perdida. Reintentando en {seconds:.1f}s" -#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:752 +#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:888 msgid "Ready" msgstr "Listo" -#: addon\globalPlugins\NativeSpeechGeneration\__init__.py:61 -#: addon\globalPlugins\NativeSpeechGeneration\__init__.py:115 +#: addon\globalPlugins\NativeSpeechGeneration\__init__.py:56 +#: addon\globalPlugins\NativeSpeechGeneration\__init__.py:100 msgid "Open the Native Speech Generation dialog" msgstr "Abrir el diálogo de Native Speech Generation" #. Translators: Title of the settings panel in NVDA preferences. -#: addon\globalPlugins\NativeSpeechGeneration\__init__.py:62 -#: addon\globalPlugins\NativeSpeechGeneration\__init__.py:116 -#: addon\globalPlugins\NativeSpeechGeneration\interface\settings.py:29 +#: addon\globalPlugins\NativeSpeechGeneration\__init__.py:57 +#: addon\globalPlugins\NativeSpeechGeneration\__init__.py:101 +#: addon\globalPlugins\NativeSpeechGeneration\interface\settings.py:25 #: buildVars.py:6 msgid "Native Speech Generation" msgstr "Native Speech Generation" -#: addon\globalPlugins\NativeSpeechGeneration\__init__.py:69 +#: addon\globalPlugins\NativeSpeechGeneration\__init__.py:64 msgid "" "Native Speech Generation is installing dependencies. Please restart NVDA for " "the changes to take effect." @@ -257,22 +332,22 @@ msgstr "" "NVDA para que los cambios surtan efecto." #. Translators: Title of the information dialog recommending a restart. -#: addon\globalPlugins\NativeSpeechGeneration\__init__.py:73 -#: addon\globalPlugins\NativeSpeechGeneration\interface\settings.py:115 +#: addon\globalPlugins\NativeSpeechGeneration\__init__.py:68 +#: addon\globalPlugins\NativeSpeechGeneration\interface\settings.py:180 msgid "Restart Required" msgstr "Requiere reinicio" #. Translators: Name of the add-on in the NVDA Tools menu. -#: addon\globalPlugins\NativeSpeechGeneration\__init__.py:108 +#: addon\globalPlugins\NativeSpeechGeneration\__init__.py:93 msgid "&Native Speech Generation" msgstr "&Native Speech Generation" #. Translators: Tooltip or description for the menu item. -#: addon\globalPlugins\NativeSpeechGeneration\__init__.py:110 +#: addon\globalPlugins\NativeSpeechGeneration\__init__.py:95 msgid "Generate speech using Gemini TTS" msgstr "Generar voz usando Gemini TTS" -#: addon\globalPlugins\NativeSpeechGeneration\__init__.py:130 +#: addon\globalPlugins\NativeSpeechGeneration\__init__.py:115 msgid "" "The Native Speech Generation add-on is already open. Please close the dialog " "before opening it again." @@ -281,141 +356,157 @@ msgstr "" "el diálogo antes de volver a abrirlo." #. Translators: Title of warning dialog when user tries to open the add-on twice. -#: addon\globalPlugins\NativeSpeechGeneration\__init__.py:133 +#: addon\globalPlugins\NativeSpeechGeneration\__init__.py:118 msgid "Add-on Already Running" msgstr "Complemento ya en ejecución" -#: addon\globalPlugins\NativeSpeechGeneration\__init__.py:145 +#: addon\globalPlugins\NativeSpeechGeneration\__init__.py:130 #, python-brace-format msgid "Failed to open Native Speech Generation dialog: {error}" msgstr "Fallo al abrir el diálogo de Native Speech Generation: {error}" #. Translators: The title of the main dialog window for generating speech. -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:48 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:44 msgid "Native Speech Generation (Gemini TTS)" msgstr "Native Speech Generation (Gemini TTS)" -#. Text Input #. Translators: Label for the text area where user inputs text to be converted to speech. -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:73 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:65 msgid "&Type text to convert here:" msgstr "&Escribir el texto para convertir aquí:" -#. Style Input #. Translators: Label for optional instructions on how the speech should be spoken (e.g. "Happy", "Sad"). -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:80 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:71 msgid "&Style instructions (optional):" msgstr "Instrucciones de &estilo (opcional):" #. Translators: Label for selecting the AI model to use for generation. -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:88 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:78 msgid "Select &Model:" msgstr "Seleccionar &modelo:" -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:89 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:79 msgid "Flash (Standard Quality)" msgstr "Flash (calidad estándar)" -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:89 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:79 msgid "Pro (High Quality)" msgstr "Pro (alta calidad)" #. Translators: Radio button to select single speaker mode. -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:96 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:86 msgid "Single-speaker" msgstr "Un solo hablante" #. Translators: Radio button to select multi-speaker mode. -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:98 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:88 msgid "Multi-speaker (2)" msgstr "Múltiples hablantes (2)" -#. Settings Toggle #. Translators: Checkbox to show advanced settings like Temperature. -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:108 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:97 msgid "Advanced Settings (&Temperature)" msgstr "Configuración &avanzada (Temperatura)" #. Translators: Label for the temperature slider which controls creativity of the AI. -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:119 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:107 msgid "Temperature:" msgstr "Temperatura:" #. Translators: Button to start generating the speech audio. -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:147 -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:533 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:133 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:539 msgid "&Generate Speech" msgstr "&Generar voz" #. Translators: Button to play the generated audio. -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:152 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:138 msgid "&Play" msgstr "&Reproducir" #. Translators: Button to save the generated audio to a file. -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:158 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:144 msgid "Save &Audio" msgstr "&Guardar audio" -#. Talk With AI Button #. Translators: Button to open the real-time conversation dialog. -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:167 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:152 msgid "Talk With &AI" msgstr "&Hablar con IA" #. Translators: Button to open settings specifically for configuring the API key. -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:174 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:158 msgid "API Key Settings" msgstr "Configuración de clave API" #. Translators: Button that opens a web browser to view available voices in Google AI Studio. -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:177 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:161 msgid "View voices in AI Studio" msgstr "Ver voces en AI Studio" -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:183 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:167 msgid "&Close" msgstr "&Cerrar" #. Translators: Label for selecting a voice in single speaker mode. -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:201 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:185 msgid "Select &Voice:" msgstr "Seleccionar &voz:" -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:202 -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:221 -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:237 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:186 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:204 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:219 msgid "Loading voices..." msgstr "Cargando voces..." -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:218 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:201 msgid "Speaker 1 Name:" msgstr "Nombre del hablante 1:" -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:219 -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:471 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:202 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:477 msgid "Speaker1" msgstr "Hablante1" -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:220 -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:236 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:203 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:218 msgid "Voice:" msgstr "Voz:" -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:234 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:216 msgid "Speaker 2 Name:" msgstr "Nombre del hablante 2:" -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:235 -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:472 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:217 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:478 msgid "Speaker2" msgstr "Hablante2" -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:345 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:327 +#, python-brace-format +msgid "" +"The stored Gemini API key could not be decrypted on this Windows user or " +"machine. Please enter it again in NVDA settings, or define {envVarName} in " +"the environment." +msgstr "" +"La clave API de Gemini almacenada no pudo descifrarse para este usuario o " +"equipo de Windows. Introdúcela de nuevo en la configuración de NVDA o define " +"{envVarName} en el entorno." + +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:333 +#, python-brace-format +msgid "" +"No Gemini API key is configured. Set it in NVDA settings, or define " +"{envVarName} in the environment." +msgstr "" +"No hay ninguna clave API de Gemini configurada. Configúrala en la " +"configuración de NVDA o define {envVarName} en el entorno." + +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:354 msgid "Talk With AI module is missing." msgstr "El módulo Hablar con IA no está instalado." -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:355 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:364 msgid "" "Talk With AI currently does not support multi-speaker mode. Please select " "Single-speaker." @@ -423,108 +514,102 @@ msgstr "" "Hablar con IA actualmente no soporta el modo de múltiples hablantes. Por " "favor, selecciona Un solo hablante." -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:357 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:366 msgid "Feature Limitation" msgstr "Limitación de característica" -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:363 -msgid "No GEMINI_API_KEY configured." -msgstr "No se ha configurado la clave de API de Gemini." - -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:382 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:387 #, python-brace-format msgid "Failed to open Talk With AI: {error}" msgstr "Fallo al abrir Hablar con IA: {error}" -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:393 -msgid "google-genai library not installed. Please restart NVDA." -msgstr "la librería google-genai no está instalada. Por favor, reinicia NVDA." - -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:401 -msgid "No GEMINI_API_KEY configured. Set it in NVDA settings." +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:396 +msgid "" +"google-genai is not available. Please restart NVDA after updating the add-on " +"libraries." msgstr "" -"No se ha configurado la clave de API de Gemini. Establécela en los ajustes " -"de NVDA." +"google-genai no está disponible. Reinicia NVDA después de actualizar las " +"bibliotecas del complemento." -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:410 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:416 msgid "Please enter text to generate." msgstr "Por favor, introduce texto para generar." -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:417 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:423 msgid "Generating..." msgstr "Generando..." -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:424 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:430 msgid "Generating speech, please wait..." msgstr "Generando voz, por favor espera..." -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:433 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:439 #, python-brace-format msgid "Failed to initialize Google GenAI client: {error}" msgstr "Fallo al inicializar el cliente de Google GenAI: {error}" -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:444 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:450 msgid "Failed to generate audio." msgstr "Fallo al generar audio." -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:446 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:452 msgid "Generation complete." msgstr "Generación completa." -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:520 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:526 msgid "An error occurred during generation." msgstr "Ocurrió un error durante la generación." -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:524 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:530 #, python-brace-format msgid "An unexpected error occurred: {error}" msgstr "Ocurrió un error inesperado: {error}" -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:595 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:598 msgid "No inline audio data returned by model." msgstr "El modelo no devolvió datos de audio en línea." -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:618 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:620 #, python-brace-format msgid "Failed to generate speech: {error}" msgstr "Fallo al generar voz: {error}" -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:637 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:638 msgid "Save Audio File" msgstr "Guardar archivo de audio" -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:638 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:639 msgid "WAV files (*.wav)|*.wav|MP3 files (*.mp3)|*.mp3" msgstr "Archivos WAV (*.wav)|*.wav|Archivos MP3 (*.mp3)|*.mp3" -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:650 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:651 #, python-brace-format msgid "Audio saved to {path}" msgstr "Audio guardado en {path}" -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:651 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:652 msgid "Success" msgstr "Éxito" -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:657 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:658 #, python-brace-format msgid "Failed to save audio: {error}" msgstr "Fallo al guardar audio: {error}" -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:672 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:673 msgid "Sample not available" msgstr "Muestra no disponible" -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:677 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:678 msgid "Playing voice sample" msgstr "Reproduciendo muestra de voz" -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:682 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:683 msgid "Failed to play sample" msgstr "Fallo al reproducir muestra" #. Translators: Label for the input field where user enters their Gemini API Key. -#: addon\globalPlugins\NativeSpeechGeneration\interface\settings.py:38 +#: addon\globalPlugins\NativeSpeechGeneration\interface\settings.py:39 msgid "&Gemini API Key:" msgstr "Clave de API de &Gemini:" @@ -533,20 +618,51 @@ msgstr "Clave de API de &Gemini:" msgid "Show API Key" msgstr "Mostrar clave de API" -#. Add the row to the main settings sizer -#. Set initial state #. Translators: Button starting a process to help user get an API key (opens a website). -#: addon\globalPlugins\NativeSpeechGeneration\interface\settings.py:60 +#: addon\globalPlugins\NativeSpeechGeneration\interface\settings.py:67 msgid "&How to get API Key..." msgstr "&Cómo obtener la clave de API..." -#. Reinstall libraries button #. Translators: Button to force a reinstallation of external dependencies (Python libraries). -#: addon\globalPlugins\NativeSpeechGeneration\interface\settings.py:66 +#: addon\globalPlugins\NativeSpeechGeneration\interface\settings.py:72 msgid "&Reinstall Libraries" msgstr "&Reinstalar librerías" +#: addon\globalPlugins\NativeSpeechGeneration\interface\settings.py:81 +#, python-brace-format +msgid "" +"The stored API key could not be decrypted on this Windows user or machine. " +"Using {envVarName} from the environment instead. Enter a new key here to " +"replace it." +msgstr "" +"La clave API almacenada no pudo descifrarse para este usuario o equipo de " +"Windows. Se usará {envVarName} del entorno en su lugar. Introduce aquí una " +"clave nueva para reemplazarla." + #: addon\globalPlugins\NativeSpeechGeneration\interface\settings.py:87 +#, python-brace-format +msgid "" +"The stored API key could not be decrypted on this Windows user or machine. " +"Enter a new key here, or set {envVarName} in the environment." +msgstr "" +"La clave API almacenada no pudo descifrarse para este usuario o equipo de " +"Windows. Introduce aquí una clave nueva o define {envVarName} en el entorno." + +#: addon\globalPlugins\NativeSpeechGeneration\interface\settings.py:94 +#, python-brace-format +msgid "" +"Using {envVarName} from the environment. Saving a key here will override it." +msgstr "" +"Se está usando {envVarName} del entorno. Guardar una clave aquí lo " +"reemplazará." + +#. Translators: Error shown if Windows DPAPI storage fails while saving the API key. +#: addon\globalPlugins\NativeSpeechGeneration\interface\settings.py:138 +#, python-brace-format +msgid "Failed to save the Gemini API key securely: {error}" +msgstr "No se pudo guardar de forma segura la clave API de Gemini: {error}" + +#: addon\globalPlugins\NativeSpeechGeneration\interface\settings.py:158 msgid "" "This will delete the existing library and restart NVDA to redownload it.\n" "Are you sure?" @@ -555,7 +671,7 @@ msgstr "" "descargarla.\n" "¿Estás seguro?" -#: addon\globalPlugins\NativeSpeechGeneration\interface\settings.py:114 +#: addon\globalPlugins\NativeSpeechGeneration\interface\settings.py:179 msgid "" "Library removed successfully. NVDA will now restart to download the latest " "version." @@ -563,6 +679,15 @@ msgstr "" "Librería eliminada exitosamente. NVDA se reiniciará ahora para descargar la " "última versión." +#: addon\globalPlugins\NativeSpeechGeneration\interface\settings.py:190 +#, python-brace-format +msgid "" +"Failed to remove library: {error}\n" +"Please check log." +msgstr "" +"No se pudo eliminar la biblioteca: {error}\n" +"Consulta el registro." + #: buildVars.py:7 msgid "" "Harness the power of Google's state-of-the-art Gemini AI for high-quality " @@ -615,21 +740,40 @@ msgstr "" #: buildVars.py:18 msgid "" -"- Talk With AI: Added optional session memory and a clear-memory control.\n" +"- Security: Gemini API keys are now stored with Windows DPAPI instead of " +"plaintext config.\n" +"- Stability: Preserved add-on configuration across updates so API keys are " +"no longer wiped by the uninstall/update flow.\n" +"- Deployment: Added GEMINI_API_KEY environment variable fallback for managed " +"setups.\n" +"- Talk With AI: Migrated to gemini-3.1-flash-live-preview with Live API " +"thinking controls.\n" +"- Talk With AI: Replaced the memory UI with No Thinking, Low, Medium, and " +"High.\n" +"- Talk With AI: Preserved reconnect continuity internally by replaying " +"recent transcript history after reconnects.\n" +"- Talk With AI: Kept style instructions as the Live API system instruction " +"and retained Google Search grounding.\n" "- Talk With AI: Improved stream stability with reconnect backoff/retry and " "adaptive buffering.\n" "- Documentation: Added Spanish-language documentation.\n" msgstr "" - -#~ msgid "" -#~ "- Stability: Restored previous dependency handling to resolve crashes " -#~ "(pyo3 panic).\n" -#~ "- Localization: Fixed translation issues for Interface and Settings " -#~ "dialogs.\n" -#~ "- Fix: Resolved SSL Certificate path error.\n" -#~ msgstr "" -#~ "- Estabilidad: Restaurado el manejo previo de dependencias para resolver " -#~ "bloqueos (pyo3 panic).\n" -#~ "- Localización: Solucionados problemas de traducción en los diálogos de " -#~ "Interfaz y Configuración.\n" -#~ "- Corrección: Resuelto el error de la ruta del certificado SSL.\n" +"- Seguridad: Las claves API de Gemini ahora se almacenan con Windows DPAPI " +"en lugar de guardarse en texto plano.\n" +"- Estabilidad: La configuración del complemento ahora se conserva entre " +"actualizaciones, por lo que las claves API ya no se borran durante el " +"proceso de desinstalación o actualización.\n" +"- Implementación: Se añadió la variable de entorno GEMINI_API_KEY como " +"alternativa para entornos administrados.\n" +"- Hablar con IA: Se migró a gemini-3.1-flash-live-preview con controles de " +"razonamiento de la API Live.\n" +"- Hablar con IA: La interfaz de memoria se sustituyó por Sin razonamiento, " +"Bajo, Medio y Alto.\n" +"- Hablar con IA: Se preservó internamente la continuidad tras la reconexión " +"reproduciendo el historial reciente de transcripciones después de " +"reconectar.\n" +"- Hablar con IA: Se mantuvieron las instrucciones de estilo como instrucción " +"del sistema de la API Live y la integración con Búsqueda de Google.\n" +"- Hablar con IA: Se mejoró la estabilidad del flujo con reconexión, " +"reintentos y búfer adaptable.\n" +"- Documentación: Se añadió documentación en español.\n" diff --git a/addon/locale/id/LC_MESSAGES/nvda.po b/addon/locale/id/LC_MESSAGES/nvda.po index c8cdafe..1149253 100644 --- a/addon/locale/id/LC_MESSAGES/nvda.po +++ b/addon/locale/id/LC_MESSAGES/nvda.po @@ -6,17 +6,17 @@ #, fuzzy msgid "" msgstr "" -"Project-Id-Version: 'NativeSpeechGeneration' '1.5.6'\n" +"Project-Id-Version: 'NativeSpeechGeneration' '1.6.0'\n" "Report-Msgid-Bugs-To: 'nvda-translations@groups.io'\n" -"POT-Creation-Date: 2026-02-20 16:58+0700\n" -"PO-Revision-Date: 2026-02-20 17:15+0700\n" +"POT-Creation-Date: 2026-04-04 16:18+0800\n" +"PO-Revision-Date: 2026-04-04 16:45+0800\n" "Last-Translator: \n" "Language-Team: \n" "Language: id\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Generator: Poedit 3.8\n" +"X-Generator: Poedit 3.9\n" #: addon\globalPlugins\NativeSpeechGeneration\lib_updater.py:57 msgid "Downloading libraries..." @@ -49,21 +49,21 @@ msgstr "" #: addon\globalPlugins\NativeSpeechGeneration\lib_updater.py:97 #: addon\globalPlugins\NativeSpeechGeneration\lib_updater.py:153 -#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:233 -#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:234 -#: addon\globalPlugins\NativeSpeechGeneration\__init__.py:146 -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:346 -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:363 -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:383 -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:394 -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:402 -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:411 -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:434 -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:525 -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:596 -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:619 -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:658 -#: addon\globalPlugins\NativeSpeechGeneration\interface\settings.py:126 +#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:277 +#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:278 +#: addon\globalPlugins\NativeSpeechGeneration\__init__.py:131 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:336 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:355 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:388 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:405 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:417 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:440 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:531 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:599 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:621 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:659 +#: addon\globalPlugins\NativeSpeechGeneration\interface\settings.py:139 +#: addon\globalPlugins\NativeSpeechGeneration\interface\settings.py:191 msgid "Error" msgstr "Kesalahan" @@ -107,7 +107,7 @@ msgstr "" "dependensi dan memerlukan restart NVDA." #: addon\globalPlugins\NativeSpeechGeneration\lib_updater.py:166 -#: addon\globalPlugins\NativeSpeechGeneration\interface\settings.py:88 +#: addon\globalPlugins\NativeSpeechGeneration\interface\settings.py:159 msgid "Confirm Reinstall" msgstr "Konfirmasikan Instal Ulang" @@ -124,132 +124,207 @@ msgid "Missing Dependencies" msgstr "Kehilangan dependensi" #. Translators: Title of the dialog for the "Talk With AI" feature (REAL-TIME conversation). -#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:51 +#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:59 msgid "Talk With AI" -msgstr "Bicara Dengan AI" +msgstr "Bicara dengan AI" + +#. Translators: Choice label for the lowest reasoning setting in Talk With AI. +#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:94 +msgid "No Thinking" +msgstr "Tanpa Penalaran" + +#. Translators: Choice label for low reasoning depth in Talk With AI. +#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:96 +msgid "Low" +msgstr "Rendah" + +#. Translators: Choice label for medium reasoning depth in Talk With AI. +#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:98 +msgid "Medium" +msgstr "Sedang" -#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:87 -msgid "PyAudio library is not installed. This feature requires PyAudio." -msgstr "Perpustakaan PyAudio tidak diinstal. Fitur ini memerlukan PyAudio." +#. Translators: Choice label for high reasoning depth in Talk With AI. +#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:100 +msgid "High" +msgstr "Tinggi" -#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:91 -msgid "Google GenAI library is not installed." -msgstr "Pustaka Google GenAI tidak terpasang." +#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:110 +msgid "" +"PyAudio is not available. This feature requires a working PyAudio " +"installation." +msgstr "" +"PyAudio tidak tersedia. Fitur ini memerlukan instalasi PyAudio yang " +"berfungsi." -#. 1. Status Area -#. Translators: Label for the status area showing connection state. -#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:129 +#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:119 +msgid "Google GenAI is not available." +msgstr "Google GenAI tidak tersedia." + +#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:163 msgid "Status" msgstr "Status" -#. Translators: Initial status message when the dialog opens. -#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:132 +#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:167 +#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:268 +#, python-brace-format +msgid "Status: {status}" +msgstr "Status: {status}" + +#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:167 msgid "Ready to Connect" -msgstr "Siap Terhubung" +msgstr "Siap untuk terhubung" -#. 2. Controls Area -#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:137 +#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:172 msgid "Controls" -msgstr "Controls" +msgstr "Kontrol" #. Translators: Button to start the voice conversation. -#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:143 +#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:177 msgid "Start Conversation" -msgstr "Mulai Percakapan" +msgstr "Mulai percakapan" #. Translators: Button to stop the voice conversation. -#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:146 +#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:180 msgid "Stop Conversation" -msgstr "Akhiri Percakapan" +msgstr "Hentikan percakapan" -#. Mic Toggle #. Translators: Toggle button label indicating microphone is ON. -#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:156 -#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:238 +#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:189 +#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:284 msgid "Microphone: ON" msgstr "Mikrofon: AKTIF" -#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:166 +#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:197 msgid "Microphone:" msgstr "Mikrofon:" -#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:177 +#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:207 msgid "Speaker:" msgstr "Speaker:" -#. Google Search Checkbox -#. Translators: Checkbox to enable "Grounding", allowing the AI to search Google for answers. -#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:190 +#. Translators: Checkbox to enable grounding with Google Search in Talk With AI. +#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:219 msgid "Grounding with Google Search" msgstr "Grounding dengan Google Search" -#. Translators: Checkbox to enable temporary conversation memory during this dialog session. -#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:195 -msgid "Use session memory" -msgstr "Gunakan memori untuk sesi ini" +#. Translators: Label for choosing the reasoning depth in Talk With AI. +#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:225 +msgid "Thinking level:" +msgstr "Tingkat penalaran:" -#. Translators: Button to clear temporary conversation memory for the current dialog session. -#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:201 -msgid "Clear memory now" -msgstr "Hapus memori sekarang" - -#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:207 +#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:233 msgid "Volume:" msgstr "Volume:" -#. 3. Info Area (Voice only) -#. Translators: Label for the selected voice -#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:219 -msgid "Voice: " -msgstr "Suara: " - -#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:229 -msgid "Status: " -msgstr "Status: " +#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:244 +#, python-brace-format +msgid "Voice: {voiceName}" +msgstr "Suara: {voiceName}" -#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:238 +#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:284 msgid "Microphone: OFF" msgstr "Mikrofon: NONAKTIF" -#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:256 -msgid "Memory cleared" -msgstr "Memori telah dibersihkan" +#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:303 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:398 +#, python-brace-format +msgid "" +"{baseMessage}\n" +"\n" +"Import detail: {errorDetail}" +msgstr "" +"{baseMessage}\n" +"\n" +"Import detail: {errorDetail}" -#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:337 +#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:387 +#, python-brace-format +msgid "" +"Installed google-genai library ({version}) does not support the Gemini 3.1 " +"Live API features required by Talk With AI. Missing: {missing}. Please " +"update the add-on libraries." +msgstr "" +"Pustaka google-genai yang terpasang ({version}) tidak mendukung fitur Gemini " +"3.1 Live API yang diperlukan untuk Bicara dengan AI. Yang tidak tersedia: " +"{missing}. Harap perbarui pustaka add-on." + +#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:393 +#, python-brace-format +msgid "" +"Installed google-genai library does not support the Gemini 3.1 Live API " +"features required by Talk With AI. Missing: {missing}. Please update the add-" +"on libraries." +msgstr "" +"Pustaka google-genai yang terpasang tidak mendukung fitur Gemini 3.1 Live " +"API yang diperlukan untuk Bicara dengan AI. Yang tidak tersedia: {missing}. " +"Harap perbarui pustaka add-on." + +#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:434 msgid "Connecting..." msgstr "Menghubungkan..." -#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:345 +#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:442 msgid "Disconnecting..." msgstr "Memutuskan koneksi..." -#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:656 +#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:599 +msgid "Google GenAI types are not available." +msgstr "Tipe Google GenAI tidak tersedia." + +#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:622 +msgid "" +"Failed to prepare the Gemini Live configuration. Please update the add-on " +"libraries." +msgstr "" +"Gagal menyiapkan konfigurasi Gemini Live. Harap perbarui pustaka add-on." + +#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:632 +#, python-brace-format +msgid "" +"The installed google-genai library ({version}) is too old for Gemini 3.1 " +"Live sessions. Missing session method: {methodName}. Please update the add-" +"on libraries." +msgstr "" +"Pustaka google-genai yang terpasang ({version}) terlalu lama untuk sesi " +"Gemini 3.1 Live. Metode sesi yang tidak tersedia: {methodName}. Harap " +"perbarui pustaka add-on." + +#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:640 +#, python-brace-format +msgid "" +"The installed google-genai library is too old for Gemini 3.1 Live sessions. " +"Missing session method: {methodName}. Please update the add-on libraries." +msgstr "" +"Pustaka google-genai yang terpasang terlalu lama untuk sesi Gemini 3.1 Live. " +"Metode sesi yang tidak tersedia: {methodName}. Harap perbarui pustaka add-on." + +#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:788 msgid "Connected" msgstr "Terhubung" -#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:698 +#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:835 #, python-brace-format msgid "Connection lost. Retrying in {seconds:.1f}s" msgstr "Koneksi terputus. Mencoba lagi dalam {seconds:.1f}s" -#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:752 +#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:888 msgid "Ready" msgstr "Siap" -#: addon\globalPlugins\NativeSpeechGeneration\__init__.py:61 -#: addon\globalPlugins\NativeSpeechGeneration\__init__.py:115 +#: addon\globalPlugins\NativeSpeechGeneration\__init__.py:56 +#: addon\globalPlugins\NativeSpeechGeneration\__init__.py:100 msgid "Open the Native Speech Generation dialog" msgstr "Membuka add-on Native Speech Generation" #. Translators: Title of the settings panel in NVDA preferences. -#: addon\globalPlugins\NativeSpeechGeneration\__init__.py:62 -#: addon\globalPlugins\NativeSpeechGeneration\__init__.py:116 -#: addon\globalPlugins\NativeSpeechGeneration\interface\settings.py:29 +#: addon\globalPlugins\NativeSpeechGeneration\__init__.py:57 +#: addon\globalPlugins\NativeSpeechGeneration\__init__.py:101 +#: addon\globalPlugins\NativeSpeechGeneration\interface\settings.py:25 #: buildVars.py:6 msgid "Native Speech Generation" msgstr "Native Speech Generation" -#: addon\globalPlugins\NativeSpeechGeneration\__init__.py:69 +#: addon\globalPlugins\NativeSpeechGeneration\__init__.py:64 msgid "" "Native Speech Generation is installing dependencies. Please restart NVDA for " "the changes to take effect." @@ -258,22 +333,22 @@ msgstr "" "perubahan berlaku." #. Translators: Title of the information dialog recommending a restart. -#: addon\globalPlugins\NativeSpeechGeneration\__init__.py:73 -#: addon\globalPlugins\NativeSpeechGeneration\interface\settings.py:115 +#: addon\globalPlugins\NativeSpeechGeneration\__init__.py:68 +#: addon\globalPlugins\NativeSpeechGeneration\interface\settings.py:180 msgid "Restart Required" msgstr "Restart diperlukan" #. Translators: Name of the add-on in the NVDA Tools menu. -#: addon\globalPlugins\NativeSpeechGeneration\__init__.py:108 +#: addon\globalPlugins\NativeSpeechGeneration\__init__.py:93 msgid "&Native Speech Generation" msgstr "&Native Speech Generation" #. Translators: Tooltip or description for the menu item. -#: addon\globalPlugins\NativeSpeechGeneration\__init__.py:110 +#: addon\globalPlugins\NativeSpeechGeneration\__init__.py:95 msgid "Generate speech using Gemini TTS" msgstr "Hasilkan Ucapan Menggunakan Gemini TTS" -#: addon\globalPlugins\NativeSpeechGeneration\__init__.py:130 +#: addon\globalPlugins\NativeSpeechGeneration\__init__.py:115 msgid "" "The Native Speech Generation add-on is already open. Please close the dialog " "before opening it again." @@ -282,141 +357,157 @@ msgstr "" "membukanya lagi." #. Translators: Title of warning dialog when user tries to open the add-on twice. -#: addon\globalPlugins\NativeSpeechGeneration\__init__.py:133 +#: addon\globalPlugins\NativeSpeechGeneration\__init__.py:118 msgid "Add-on Already Running" msgstr "Add-on sudah berjalan" -#: addon\globalPlugins\NativeSpeechGeneration\__init__.py:145 +#: addon\globalPlugins\NativeSpeechGeneration\__init__.py:130 #, python-brace-format msgid "Failed to open Native Speech Generation dialog: {error}" msgstr "Gagal menjalankan add-on Native Speech Generation: {error}" #. Translators: The title of the main dialog window for generating speech. -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:48 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:44 msgid "Native Speech Generation (Gemini TTS)" msgstr "Native Speech Generation (Gemini TTS)" -#. Text Input #. Translators: Label for the text area where user inputs text to be converted to speech. -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:73 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:65 msgid "&Type text to convert here:" msgstr "&Masukkan teks yang ingin anda jadikan audio:" -#. Style Input #. Translators: Label for optional instructions on how the speech should be spoken (e.g. "Happy", "Sad"). -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:80 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:71 msgid "&Style instructions (optional):" msgstr "&Gaya bicara TTS (optional):" #. Translators: Label for selecting the AI model to use for generation. -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:88 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:78 msgid "Select &Model:" msgstr "Pilih &Model:" -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:89 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:79 msgid "Flash (Standard Quality)" msgstr "Flash (kualitas standar)" -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:89 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:79 msgid "Pro (High Quality)" msgstr "Pro (kualitas tinggi)" #. Translators: Radio button to select single speaker mode. -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:96 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:86 msgid "Single-speaker" msgstr "Satu pembicara" #. Translators: Radio button to select multi-speaker mode. -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:98 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:88 msgid "Multi-speaker (2)" msgstr "Multi pembicara" -#. Settings Toggle #. Translators: Checkbox to show advanced settings like Temperature. -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:108 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:97 msgid "Advanced Settings (&Temperature)" msgstr "Pengaturan lanjutan (&Temperature)" #. Translators: Label for the temperature slider which controls creativity of the AI. -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:119 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:107 msgid "Temperature:" msgstr "Temperature:" #. Translators: Button to start generating the speech audio. -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:147 -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:533 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:133 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:539 msgid "&Generate Speech" msgstr "&Hasilkan Audio" #. Translators: Button to play the generated audio. -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:152 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:138 msgid "&Play" msgstr "&Putar" #. Translators: Button to save the generated audio to a file. -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:158 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:144 msgid "Save &Audio" msgstr "Simpan &Audio" -#. Talk With AI Button #. Translators: Button to open the real-time conversation dialog. -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:167 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:152 msgid "Talk With &AI" msgstr "Bicara Dengan & AI" #. Translators: Button to open settings specifically for configuring the API key. -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:174 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:158 msgid "API Key Settings" msgstr "Pengaturan Kunci API" #. Translators: Button that opens a web browser to view available voices in Google AI Studio. -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:177 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:161 msgid "View voices in AI Studio" msgstr "Periksa Suara di AI Studio" -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:183 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:167 msgid "&Close" msgstr "&Tutup" #. Translators: Label for selecting a voice in single speaker mode. -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:201 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:185 msgid "Select &Voice:" msgstr "Pilih &Suara:" -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:202 -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:221 -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:237 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:186 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:204 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:219 msgid "Loading voices..." msgstr "Memuat suara ..." -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:218 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:201 msgid "Speaker 1 Name:" msgstr "Nama Pembicara 1:" -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:219 -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:471 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:202 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:477 msgid "Speaker1" msgstr "Pembicara1" -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:220 -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:236 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:203 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:218 msgid "Voice:" msgstr "Suara:" -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:234 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:216 msgid "Speaker 2 Name:" msgstr "Nama Pembicara 2:" -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:235 -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:472 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:217 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:478 msgid "Speaker2" msgstr "Pembicara2" -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:345 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:327 +#, python-brace-format +msgid "" +"The stored Gemini API key could not be decrypted on this Windows user or " +"machine. Please enter it again in NVDA settings, or define {envVarName} in " +"the environment." +msgstr "" +"Kunci API Gemini yang tersimpan tidak dapat didekripsi pada pengguna atau " +"mesin Windows ini. Silakan masukkan kembali di pengaturan NVDA, atau " +"definisikan {envVarName} di lingkungan." + +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:333 +#, python-brace-format +msgid "" +"No Gemini API key is configured. Set it in NVDA settings, or define " +"{envVarName} in the environment." +msgstr "" +"Tidak ada kunci API Gemini yang dikonfigurasi. Atur di pengaturan NVDA, atau " +"tentukan {envVarName} di lingkungan." + +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:354 msgid "Talk With AI module is missing." msgstr "Modul Bicara Dengan AI tidak ada." -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:355 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:364 msgid "" "Talk With AI currently does not support multi-speaker mode. Please select " "Single-speaker." @@ -424,109 +515,102 @@ msgstr "" "Talk With AI saat ini tidak mendukung mode multi-speaker. Harap pilih " "singgle speaker." -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:357 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:366 msgid "Feature Limitation" msgstr "Batasan Fitur" -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:363 -msgid "No GEMINI_API_KEY configured." -msgstr "" -"Tidak ada GEMINI_API_KEY yang dimasukkan, silahkan atur terlebih dahulu." - -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:382 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:387 #, python-brace-format msgid "Failed to open Talk With AI: {error}" msgstr "Gagal membuka Bicara Dengan AI: {error}" -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:393 -msgid "google-genai library not installed. Please restart NVDA." -msgstr "perpustakaan Google-Genai tidak diinstal. Harap restart NVDA." - -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:401 -msgid "No GEMINI_API_KEY configured. Set it in NVDA settings." +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:396 +msgid "" +"google-genai is not available. Please restart NVDA after updating the add-on " +"libraries." msgstr "" -"Tidak ada kunci API Gemini yang anda masukkan. Silahkan atur terlebih " -"dahulu di pengaturan NVDA, kategory NativeSpeechGeneration." +"google-genai tidak tersedia. Silakan mulai ulang NVDA setelah memperbarui " +"pustaka add-on." -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:410 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:416 msgid "Please enter text to generate." msgstr "Harap masukkan teks terlebih dahulu." -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:417 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:423 msgid "Generating..." msgstr "Sedang memproses..." -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:424 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:430 msgid "Generating speech, please wait..." msgstr "Sedang menghasilkan audio, harap tunggu..." -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:433 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:439 #, python-brace-format msgid "Failed to initialize Google GenAI client: {error}" msgstr "Gagal menginisialisasi klien Google Genai: {error}" -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:444 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:450 msgid "Failed to generate audio." msgstr "Gagal menghasilkan audio." -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:446 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:452 msgid "Generation complete." msgstr "Selesai membuat audio." -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:520 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:526 msgid "An error occurred during generation." msgstr "Terjadi kesalahan selama menghasilkan audio." -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:524 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:530 #, python-brace-format msgid "An unexpected error occurred: {error}" msgstr "Terjadi kesalahan yang tidak terduga: {error}" -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:595 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:598 msgid "No inline audio data returned by model." msgstr "Tidak ada data audio inline yang dikembalikan oleh model." -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:618 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:620 #, python-brace-format msgid "Failed to generate speech: {error}" msgstr "Gagal menghasilkan ucapan: {error}" -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:637 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:638 msgid "Save Audio File" msgstr "Simpan file audio" -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:638 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:639 msgid "WAV files (*.wav)|*.wav|MP3 files (*.mp3)|*.mp3" msgstr "Berkas WAV (*.wav)|*.wav|Berkas Mp3 (*.mp3)|*.mp3" -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:650 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:651 #, python-brace-format msgid "Audio saved to {path}" msgstr "Audio disimpan ke {path}" -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:651 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:652 msgid "Success" msgstr "Success" -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:657 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:658 #, python-brace-format msgid "Failed to save audio: {error}" msgstr "Gagal menyimpan audio: {error}" -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:672 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:673 msgid "Sample not available" msgstr "Suara sampel tidak tersedia" -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:677 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:678 msgid "Playing voice sample" msgstr "Memainkan sampel suara" -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:682 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:683 msgid "Failed to play sample" msgstr "Gagal memainkan sampel audio" #. Translators: Label for the input field where user enters their Gemini API Key. -#: addon\globalPlugins\NativeSpeechGeneration\interface\settings.py:38 +#: addon\globalPlugins\NativeSpeechGeneration\interface\settings.py:39 msgid "&Gemini API Key:" msgstr "& Kunci API Gemini:" @@ -535,20 +619,52 @@ msgstr "& Kunci API Gemini:" msgid "Show API Key" msgstr "Tampilkan kunci API" -#. Add the row to the main settings sizer -#. Set initial state #. Translators: Button starting a process to help user get an API key (opens a website). -#: addon\globalPlugins\NativeSpeechGeneration\interface\settings.py:60 +#: addon\globalPlugins\NativeSpeechGeneration\interface\settings.py:67 msgid "&How to get API Key..." msgstr "&Cara Mendapatkan Kunci API..." -#. Reinstall libraries button #. Translators: Button to force a reinstallation of external dependencies (Python libraries). -#: addon\globalPlugins\NativeSpeechGeneration\interface\settings.py:66 +#: addon\globalPlugins\NativeSpeechGeneration\interface\settings.py:72 msgid "&Reinstall Libraries" msgstr "&Instal ulang Librarys" +#: addon\globalPlugins\NativeSpeechGeneration\interface\settings.py:81 +#, python-brace-format +msgid "" +"The stored API key could not be decrypted on this Windows user or machine. " +"Using {envVarName} from the environment instead. Enter a new key here to " +"replace it." +msgstr "" +"Kunci API yang tersimpan tidak dapat didekripsi pada pengguna atau perangkat " +"Windows ini. Sebagai gantinya, {envVarName} dari lingkungan akan digunakan. " +"Masukkan kunci baru di sini untuk menggantikannya." + #: addon\globalPlugins\NativeSpeechGeneration\interface\settings.py:87 +#, python-brace-format +msgid "" +"The stored API key could not be decrypted on this Windows user or machine. " +"Enter a new key here, or set {envVarName} in the environment." +msgstr "" +"Kunci API yang tersimpan tidak dapat didekripsi pada pengguna atau perangkat " +"Windows ini. Masukkan kunci baru di sini, atau atur {envVarName} di " +"lingkungan." + +#: addon\globalPlugins\NativeSpeechGeneration\interface\settings.py:94 +#, python-brace-format +msgid "" +"Using {envVarName} from the environment. Saving a key here will override it." +msgstr "" +"Menggunakan {envVarName} dari lingkungan. Menyimpan kunci di sini akan " +"menimpanya." + +#. Translators: Error shown if Windows DPAPI storage fails while saving the API key. +#: addon\globalPlugins\NativeSpeechGeneration\interface\settings.py:138 +#, python-brace-format +msgid "Failed to save the Gemini API key securely: {error}" +msgstr "Gagal menyimpan kunci API Gemini dengan aman: {error}" + +#: addon\globalPlugins\NativeSpeechGeneration\interface\settings.py:158 msgid "" "This will delete the existing library and restart NVDA to redownload it.\n" "Are you sure?" @@ -557,7 +673,7 @@ msgstr "" "mengunduhnya ulang.\n" "Apakah kamu yakin?" -#: addon\globalPlugins\NativeSpeechGeneration\interface\settings.py:114 +#: addon\globalPlugins\NativeSpeechGeneration\interface\settings.py:179 msgid "" "Library removed successfully. NVDA will now restart to download the latest " "version." @@ -565,6 +681,15 @@ msgstr "" "Library berhasil dihapus. NVDA sekarang akan memulai ulang untuk mengunduh " "versi terbaru." +#: addon\globalPlugins\NativeSpeechGeneration\interface\settings.py:190 +#, python-brace-format +msgid "" +"Failed to remove library: {error}\n" +"Please check log." +msgstr "" +"Gagal menghapus pustaka: {error}\n" +"Silakan periksa log." + #: buildVars.py:7 msgid "" "Harness the power of Google's state-of-the-art Gemini AI for high-quality " @@ -618,44 +743,38 @@ msgstr "" #: buildVars.py:18 msgid "" -"- Talk With AI: Added optional session memory and a clear-memory control.\n" +"- Security: Gemini API keys are now stored with Windows DPAPI instead of " +"plaintext config.\n" +"- Stability: Preserved add-on configuration across updates so API keys are " +"no longer wiped by the uninstall/update flow.\n" +"- Deployment: Added GEMINI_API_KEY environment variable fallback for managed " +"setups.\n" +"- Talk With AI: Migrated to gemini-3.1-flash-live-preview with Live API " +"thinking controls.\n" +"- Talk With AI: Replaced the memory UI with No Thinking, Low, Medium, and " +"High.\n" +"- Talk With AI: Preserved reconnect continuity internally by replaying " +"recent transcript history after reconnects.\n" +"- Talk With AI: Kept style instructions as the Live API system instruction " +"and retained Google Search grounding.\n" "- Talk With AI: Improved stream stability with reconnect backoff/retry and " "adaptive buffering.\n" "- Documentation: Added Spanish-language documentation.\n" msgstr "" -"- Bicara Dengan AI: Menambahkan memori sesi opsional dan kontrol memori yang " -"jelas.\n" -"- Bicara Dengan AI: Peningkatan stabilitas streaming dengan koneksi ulang " -"backoff/coba lagi dan buffering adaptif.\n" +"- Keamanan: Kunci API Gemini sekarang disimpan dengan Windows DPAPI, bukan " +"lagi dalam konfigurasi teks biasa.\n" +"- Stabilitas: Konfigurasi add-on kini tetap dipertahankan saat pembaruan, " +"sehingga kunci API tidak lagi terhapus saat proses uninstall atau update.\n" +"- Deployment: Menambahkan dukungan variabel lingkungan GEMINI_API_KEY untuk " +"lingkungan yang dikelola.\n" +"- Bicara dengan AI: Bermigrasi ke gemini-3.1-flash-live-preview dengan " +"kontrol penalaran Live API.\n" +"- Bicara dengan AI: Antarmuka memori diganti dengan Tanpa Penalaran, Rendah, " +"Sedang, dan Tinggi.\n" +"- Bicara dengan AI: Kontinuitas setelah koneksi ulang kini dipertahankan " +"secara internal dengan memutar ulang riwayat transkrip terbaru.\n" +"- Bicara dengan AI: Instruksi gaya tetap digunakan sebagai instruksi sistem " +"Live API, dan grounding Google Search tetap dipertahankan.\n" +"- Bicara dengan AI: Stabilitas streaming ditingkatkan dengan reconnect/retry " +"dan buffering adaptif.\n" "- Dokumentasi: Menambahkan dokumentasi berbahasa Spanyol.\n" - -#~ msgid "" -#~ "- Stability: Restored previous dependency handling to resolve crashes " -#~ "(pyo3 panic).\n" -#~ "- Localization: Fixed translation issues for Interface and Settings " -#~ "dialogs.\n" -#~ "- Fix: Resolved SSL Certificate path error.\n" -#~ msgstr "" -#~ "- Stabilitas: Memulihkan penanganan ketergantungan sebelumnya untuk " -#~ "mengatasi kerusakan (panik pyo3).\n" -#~ "- Lokalisasi: Memperbaiki masalah terjemahan untuk dialog Antarmuka dan " -#~ "Pengaturan.\n" -#~ "- Perbaiki: Kesalahan jalur Sertifikat SSL teratasi.\n" - -#~ msgid "" -#~ "- Code Refactoring: Complies with NVDA coding standards (Tabs, " -#~ "CamelCase).\n" -#~ "- Optimized Update: Preserves library files to avoid redownloading data.\n" -#~ "- Improved UX: Restored device selection in \"Talk With AI\", better " -#~ "button states, and fixed dialog closing behavior.\n" -#~ "- Fix: Resolved namespace conflicts and ensured proper stream " -#~ "cancellation.\n" -#~ msgstr "" -#~ "- Refactoring Kode: Mematuhi standar pengkodean NVDA (Tab, CamelCase).\n" -#~ "- Pembaruan yang Dioptimalkan: Mempertahankan berkas pustaka untuk " -#~ "menghindari pengunduhan ulang data.\n" -#~ "- Peningkatan UX: Pemilihan perangkat dipulihkan di \"Bicara Dengan AI\", " -#~ "status tombol yang lebih baik, dan perilaku penutupan dialog yang " -#~ "diperbaiki.\n" -#~ "- Perbaikan: Menyelesaikan konflik ruang nama dan memastikan pembatalan " -#~ "aliran yang tepat.\n" diff --git a/addon/locale/ru/LC_MESSAGES/nvda.po b/addon/locale/ru/LC_MESSAGES/nvda.po index 9be63d7..f5a2f06 100644 --- a/addon/locale/ru/LC_MESSAGES/nvda.po +++ b/addon/locale/ru/LC_MESSAGES/nvda.po @@ -1,9 +1,9 @@ msgid "" msgstr "" -"Project-Id-Version: 'NativeSpeechGeneration' '1.5.6'\n" +"Project-Id-Version: 'NativeSpeechGeneration' '1.6.0'\n" "Report-Msgid-Bugs-To: 'nvda-translations@groups.io'\n" -"POT-Creation-Date: 2026-02-20 16:58+0700\n" -"PO-Revision-Date: 2026-02-20 17:17+0700\n" +"POT-Creation-Date: 2026-04-04 16:18+0800\n" +"PO-Revision-Date: 2026-04-04 16:47+0800\n" "Last-Translator: nvda.ru\n" "Language-Team: nvda.ru\n" "Language: ru_RU\n" @@ -12,7 +12,7 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && " "n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" -"X-Generator: Poedit 3.8\n" +"X-Generator: Poedit 3.9\n" "X-Poedit-Basepath: ../../..\n" "X-Poedit-SourceCharset: UTF-8\n" "X-Poedit-SearchPath-0: globalPlugins\n" @@ -50,21 +50,21 @@ msgstr "" #: addon\globalPlugins\NativeSpeechGeneration\lib_updater.py:97 #: addon\globalPlugins\NativeSpeechGeneration\lib_updater.py:153 -#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:233 -#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:234 -#: addon\globalPlugins\NativeSpeechGeneration\__init__.py:146 -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:346 -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:363 -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:383 -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:394 -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:402 -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:411 -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:434 -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:525 -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:596 -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:619 -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:658 -#: addon\globalPlugins\NativeSpeechGeneration\interface\settings.py:126 +#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:277 +#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:278 +#: addon\globalPlugins\NativeSpeechGeneration\__init__.py:131 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:336 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:355 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:388 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:405 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:417 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:440 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:531 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:599 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:621 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:659 +#: addon\globalPlugins\NativeSpeechGeneration\interface\settings.py:139 +#: addon\globalPlugins\NativeSpeechGeneration\interface\settings.py:191 msgid "Error" msgstr "Ошибка" @@ -108,7 +108,7 @@ msgstr "" "зависимости и потребует перезагрузки NVDA." #: addon\globalPlugins\NativeSpeechGeneration\lib_updater.py:166 -#: addon\globalPlugins\NativeSpeechGeneration\interface\settings.py:88 +#: addon\globalPlugins\NativeSpeechGeneration\interface\settings.py:159 msgid "Confirm Reinstall" msgstr "Подтвердить переустановку" @@ -125,133 +125,208 @@ msgid "Missing Dependencies" msgstr "Отсутствующие зависимости" #. Translators: Title of the dialog for the "Talk With AI" feature (REAL-TIME conversation). -#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:51 +#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:59 msgid "Talk With AI" msgstr "Разговор с ИИ" -#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:87 -msgid "PyAudio library is not installed. This feature requires PyAudio." -msgstr "Библиотека PyAudio не установлена. Для этой функции требуется PyAudio." +#. Translators: Choice label for the lowest reasoning setting in Talk With AI. +#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:94 +msgid "No Thinking" +msgstr "Без рассуждений" + +#. Translators: Choice label for low reasoning depth in Talk With AI. +#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:96 +msgid "Low" +msgstr "Низкий" + +#. Translators: Choice label for medium reasoning depth in Talk With AI. +#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:98 +msgid "Medium" +msgstr "Средний" + +#. Translators: Choice label for high reasoning depth in Talk With AI. +#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:100 +msgid "High" +msgstr "Высокий" -#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:91 -msgid "Google GenAI library is not installed." -msgstr "Библиотека Google GenAI не установлена." +#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:110 +msgid "" +"PyAudio is not available. This feature requires a working PyAudio " +"installation." +msgstr "" +"PyAudio недоступен. Для этой функции требуется рабочая установка PyAudio." -#. 1. Status Area -#. Translators: Label for the status area showing connection state. -#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:129 +#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:119 +msgid "Google GenAI is not available." +msgstr "Google GenAI недоступен." + +#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:163 msgid "Status" msgstr "Статус" -#. Translators: Initial status message when the dialog opens. -#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:132 +#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:167 +#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:268 +#, python-brace-format +msgid "Status: {status}" +msgstr "Статус: {status}" + +#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:167 msgid "Ready to Connect" msgstr "Готово к подключению" -#. 2. Controls Area -#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:137 +#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:172 msgid "Controls" msgstr "Управление" #. Translators: Button to start the voice conversation. -#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:143 +#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:177 msgid "Start Conversation" msgstr "Начать разговор" #. Translators: Button to stop the voice conversation. -#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:146 +#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:180 msgid "Stop Conversation" msgstr "Остановить разговор" -#. Mic Toggle #. Translators: Toggle button label indicating microphone is ON. -#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:156 -#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:238 +#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:189 +#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:284 msgid "Microphone: ON" msgstr "Микрофон: ВКЛ" -#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:166 +#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:197 msgid "Microphone:" msgstr "Микрофон:" -#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:177 +#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:207 msgid "Speaker:" msgstr "Динамик:" -#. Google Search Checkbox -#. Translators: Checkbox to enable "Grounding", allowing the AI to search Google for answers. -#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:190 +#. Translators: Checkbox to enable grounding with Google Search in Talk With AI. +#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:219 msgid "Grounding with Google Search" msgstr "Привязка к Google Поиску" -#. Translators: Checkbox to enable temporary conversation memory during this dialog session. -#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:195 -msgid "Use session memory" -msgstr "" +#. Translators: Label for choosing the reasoning depth in Talk With AI. +#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:225 +msgid "Thinking level:" +msgstr "Уровень мышления:" -#. Translators: Button to clear temporary conversation memory for the current dialog session. -#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:201 -msgid "Clear memory now" -msgstr "" - -#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:207 +#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:233 msgid "Volume:" msgstr "Громкость:" -#. 3. Info Area (Voice only) -#. Translators: Label for the selected voice -#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:219 -msgid "Voice: " -msgstr "Голос: " - -#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:229 -msgid "Status: " -msgstr "Статус: " +#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:244 +#, python-brace-format +msgid "Voice: {voiceName}" +msgstr "Голос: {voiceName}" -#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:238 +#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:284 msgid "Microphone: OFF" msgstr "Микрофон: ВЫКЛ" -#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:256 -msgid "Memory cleared" +#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:303 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:398 +#, python-brace-format +msgid "" +"{baseMessage}\n" +"\n" +"Import detail: {errorDetail}" +msgstr "" +"{baseMessage}\n" +"\n" +"Подробности импорта: {errorDetail}" + +#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:387 +#, python-brace-format +msgid "" +"Installed google-genai library ({version}) does not support the Gemini 3.1 " +"Live API features required by Talk With AI. Missing: {missing}. Please " +"update the add-on libraries." +msgstr "" +"Установленная библиотека google-genai ({version}) не поддерживает " +"возможности Live API Gemini 3.1, необходимые для функции «Разговор с ИИ». " +"Отсутствует: {missing}. Пожалуйста, обновите библиотеки дополнения." + +#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:393 +#, python-brace-format +msgid "" +"Installed google-genai library does not support the Gemini 3.1 Live API " +"features required by Talk With AI. Missing: {missing}. Please update the add-" +"on libraries." msgstr "" +"Установленная библиотека google-genai не поддерживает возможности Live API " +"Gemini 3.1, необходимые для функции «Разговор с ИИ». Отсутствует: {missing}. " +"Пожалуйста, обновите библиотеки дополнения." -#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:337 +#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:434 msgid "Connecting..." msgstr "Подключение..." -#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:345 +#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:442 msgid "Disconnecting..." msgstr "Отключение..." -#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:656 +#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:599 +msgid "Google GenAI types are not available." +msgstr "Типы Google GenAI недоступны." + +#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:622 +msgid "" +"Failed to prepare the Gemini Live configuration. Please update the add-on " +"libraries." +msgstr "" +"Не удалось подготовить конфигурацию Gemini Live. Пожалуйста, обновите " +"библиотеки дополнения." + +#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:632 +#, python-brace-format +msgid "" +"The installed google-genai library ({version}) is too old for Gemini 3.1 " +"Live sessions. Missing session method: {methodName}. Please update the add-" +"on libraries." +msgstr "" +"Установленная библиотека google-genai ({version}) слишком старая для Live-" +"сеансов Gemini 3.1. Отсутствует метод сеанса: {methodName}. Пожалуйста, " +"обновите библиотеки дополнения." + +#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:640 +#, python-brace-format +msgid "" +"The installed google-genai library is too old for Gemini 3.1 Live sessions. " +"Missing session method: {methodName}. Please update the add-on libraries." +msgstr "" +"Установленная библиотека google-genai слишком старая для Live-сеансов Gemini " +"3.1. Отсутствует метод сеанса: {methodName}. Пожалуйста, обновите библиотеки " +"дополнения." + +#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:788 msgid "Connected" msgstr "Подключено" -#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:698 -#, fuzzy, python-brace-format -#| msgid "Connection Lost. Retrying..." +#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:835 +#, python-brace-format msgid "Connection lost. Retrying in {seconds:.1f}s" -msgstr "Соединение потеряно. Повторная попытка..." +msgstr "Соединение потеряно. Повторная попытка через {seconds:.1f} с" -#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:752 +#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:888 msgid "Ready" msgstr "Готово" -#: addon\globalPlugins\NativeSpeechGeneration\__init__.py:61 -#: addon\globalPlugins\NativeSpeechGeneration\__init__.py:115 +#: addon\globalPlugins\NativeSpeechGeneration\__init__.py:56 +#: addon\globalPlugins\NativeSpeechGeneration\__init__.py:100 msgid "Open the Native Speech Generation dialog" msgstr "Открыть диалог Нативной генерации речи" #. Translators: Title of the settings panel in NVDA preferences. -#: addon\globalPlugins\NativeSpeechGeneration\__init__.py:62 -#: addon\globalPlugins\NativeSpeechGeneration\__init__.py:116 -#: addon\globalPlugins\NativeSpeechGeneration\interface\settings.py:29 +#: addon\globalPlugins\NativeSpeechGeneration\__init__.py:57 +#: addon\globalPlugins\NativeSpeechGeneration\__init__.py:101 +#: addon\globalPlugins\NativeSpeechGeneration\interface\settings.py:25 #: buildVars.py:6 msgid "Native Speech Generation" msgstr "Нативная генерация речи" -#: addon\globalPlugins\NativeSpeechGeneration\__init__.py:69 +#: addon\globalPlugins\NativeSpeechGeneration\__init__.py:64 msgid "" "Native Speech Generation is installing dependencies. Please restart NVDA for " "the changes to take effect." @@ -260,22 +335,22 @@ msgstr "" "NVDA, чтобы изменения вступили в силу." #. Translators: Title of the information dialog recommending a restart. -#: addon\globalPlugins\NativeSpeechGeneration\__init__.py:73 -#: addon\globalPlugins\NativeSpeechGeneration\interface\settings.py:115 +#: addon\globalPlugins\NativeSpeechGeneration\__init__.py:68 +#: addon\globalPlugins\NativeSpeechGeneration\interface\settings.py:180 msgid "Restart Required" msgstr "Требуется перезагрузка" #. Translators: Name of the add-on in the NVDA Tools menu. -#: addon\globalPlugins\NativeSpeechGeneration\__init__.py:108 +#: addon\globalPlugins\NativeSpeechGeneration\__init__.py:93 msgid "&Native Speech Generation" msgstr "&Нативная генерация речи" #. Translators: Tooltip or description for the menu item. -#: addon\globalPlugins\NativeSpeechGeneration\__init__.py:110 +#: addon\globalPlugins\NativeSpeechGeneration\__init__.py:95 msgid "Generate speech using Gemini TTS" msgstr "Генерация речи с использованием Gemini TTS" -#: addon\globalPlugins\NativeSpeechGeneration\__init__.py:130 +#: addon\globalPlugins\NativeSpeechGeneration\__init__.py:115 msgid "" "The Native Speech Generation add-on is already open. Please close the dialog " "before opening it again." @@ -284,141 +359,157 @@ msgstr "" "диалог перед повторным открытием." #. Translators: Title of warning dialog when user tries to open the add-on twice. -#: addon\globalPlugins\NativeSpeechGeneration\__init__.py:133 +#: addon\globalPlugins\NativeSpeechGeneration\__init__.py:118 msgid "Add-on Already Running" msgstr "Дополнение уже запущено" -#: addon\globalPlugins\NativeSpeechGeneration\__init__.py:145 +#: addon\globalPlugins\NativeSpeechGeneration\__init__.py:130 #, python-brace-format msgid "Failed to open Native Speech Generation dialog: {error}" msgstr "Не удалось открыть диалог «Нативная генерация речи»: {error}" #. Translators: The title of the main dialog window for generating speech. -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:48 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:44 msgid "Native Speech Generation (Gemini TTS)" msgstr "Нативная генерация речи (Gemini TTS)" -#. Text Input #. Translators: Label for the text area where user inputs text to be converted to speech. -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:73 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:65 msgid "&Type text to convert here:" msgstr "&Введите текст для преобразования:" -#. Style Input #. Translators: Label for optional instructions on how the speech should be spoken (e.g. "Happy", "Sad"). -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:80 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:71 msgid "&Style instructions (optional):" msgstr "&Инструкции по стилю (необязательно):" #. Translators: Label for selecting the AI model to use for generation. -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:88 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:78 msgid "Select &Model:" msgstr "Выбрать &модель:" -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:89 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:79 msgid "Flash (Standard Quality)" msgstr "Flash (стандартное качество)" -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:89 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:79 msgid "Pro (High Quality)" msgstr "Pro (высокое качество)" #. Translators: Radio button to select single speaker mode. -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:96 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:86 msgid "Single-speaker" msgstr "Один говорящий" #. Translators: Radio button to select multi-speaker mode. -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:98 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:88 msgid "Multi-speaker (2)" msgstr "Несколько говорящих (2)" -#. Settings Toggle #. Translators: Checkbox to show advanced settings like Temperature. -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:108 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:97 msgid "Advanced Settings (&Temperature)" msgstr "Расширенные настройки (&температура)" #. Translators: Label for the temperature slider which controls creativity of the AI. -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:119 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:107 msgid "Temperature:" msgstr "Температура:" #. Translators: Button to start generating the speech audio. -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:147 -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:533 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:133 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:539 msgid "&Generate Speech" msgstr "&Сгенерировать речь" #. Translators: Button to play the generated audio. -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:152 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:138 msgid "&Play" msgstr "&Воспроизвести" #. Translators: Button to save the generated audio to a file. -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:158 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:144 msgid "Save &Audio" msgstr "Сохранить &аудио" -#. Talk With AI Button #. Translators: Button to open the real-time conversation dialog. -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:167 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:152 msgid "Talk With &AI" msgstr "Разговор с &ИИ" #. Translators: Button to open settings specifically for configuring the API key. -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:174 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:158 msgid "API Key Settings" msgstr "Настройки ключа API" #. Translators: Button that opens a web browser to view available voices in Google AI Studio. -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:177 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:161 msgid "View voices in AI Studio" msgstr "Просмотреть голоса в AI Studio" -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:183 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:167 msgid "&Close" msgstr "&Закрыть" #. Translators: Label for selecting a voice in single speaker mode. -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:201 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:185 msgid "Select &Voice:" msgstr "Выбрать &голос:" -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:202 -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:221 -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:237 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:186 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:204 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:219 msgid "Loading voices..." msgstr "Загрузка голосов..." -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:218 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:201 msgid "Speaker 1 Name:" msgstr "Имя говорящего 1:" -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:219 -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:471 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:202 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:477 msgid "Speaker1" msgstr "Говорящий 1" -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:220 -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:236 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:203 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:218 msgid "Voice:" msgstr "Голос:" -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:234 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:216 msgid "Speaker 2 Name:" msgstr "Имя говорящего 2:" -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:235 -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:472 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:217 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:478 msgid "Speaker2" msgstr "Говорящий 2" -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:345 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:327 +#, python-brace-format +msgid "" +"The stored Gemini API key could not be decrypted on this Windows user or " +"machine. Please enter it again in NVDA settings, or define {envVarName} in " +"the environment." +msgstr "" +"Не удалось расшифровать сохранённый API-ключ Gemini для этой учётной записи " +"или этого компьютера Windows. Введите его снова в настройках NVDA или " +"задайте {envVarName} в переменных среды." + +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:333 +#, python-brace-format +msgid "" +"No Gemini API key is configured. Set it in NVDA settings, or define " +"{envVarName} in the environment." +msgstr "" +"API-ключ Gemini не настроен. Укажите его в настройках NVDA или задайте " +"{envVarName} в переменных среды." + +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:354 msgid "Talk With AI module is missing." msgstr "Модуль «Разговор с ИИ» отсутствует." -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:355 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:364 msgid "" "Talk With AI currently does not support multi-speaker mode. Please select " "Single-speaker." @@ -426,109 +517,102 @@ msgstr "" "Режим «Разговор с ИИ» в настоящее время не поддерживает несколько говорящих. " "Пожалуйста, выберите «Один говорящий»." -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:357 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:366 msgid "Feature Limitation" msgstr "Ограничение функции" -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:363 -msgid "No GEMINI_API_KEY configured." -msgstr "" -"Ключ GEMINI_API_KEY не настроен. Установите его в настройках NVDA в разделе " -"NativeSpeechGeneration." - -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:382 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:387 #, python-brace-format msgid "Failed to open Talk With AI: {error}" msgstr "Не удалось открыть «Разговор с ИИ»: {error}" -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:393 -msgid "google-genai library not installed. Please restart NVDA." +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:396 +msgid "" +"google-genai is not available. Please restart NVDA after updating the add-on " +"libraries." msgstr "" -"библиотека google-genai не установлена. Пожалуйста, перезагрузите NVDA." +"google-genai недоступен. Пожалуйста, перезапустите NVDA после обновления " +"библиотек дополнения." -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:401 -msgid "No GEMINI_API_KEY configured. Set it in NVDA settings." -msgstr "Ключ GEMINI_API_KEY не настроен. Установите его в настройках NVDA." - -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:410 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:416 msgid "Please enter text to generate." msgstr "Пожалуйста, введите текст для генерации." -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:417 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:423 msgid "Generating..." msgstr "Генерация..." -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:424 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:430 msgid "Generating speech, please wait..." msgstr "Идёт генерация речи, пожалуйста, подождите..." -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:433 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:439 #, python-brace-format msgid "Failed to initialize Google GenAI client: {error}" msgstr "Не удалось инициализировать клиент Google GenAI: {error}" -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:444 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:450 msgid "Failed to generate audio." msgstr "Не удалось сгенерировать аудио." -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:446 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:452 msgid "Generation complete." msgstr "Генерация завершена." -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:520 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:526 msgid "An error occurred during generation." msgstr "Во время генерации произошла ошибка." -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:524 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:530 #, python-brace-format msgid "An unexpected error occurred: {error}" msgstr "Произошла непредвиденная ошибка: {error}" -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:595 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:598 msgid "No inline audio data returned by model." msgstr "Модель не вернула встроенные аудиоданные." -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:618 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:620 #, python-brace-format msgid "Failed to generate speech: {error}" msgstr "Не удалось сгенерировать речь: {error}" -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:637 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:638 msgid "Save Audio File" msgstr "Сохранить аудиофайл" -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:638 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:639 msgid "WAV files (*.wav)|*.wav|MP3 files (*.mp3)|*.mp3" msgstr "Файлы WAV (*.wav)|*.wav|Файлы MP3 (*.mp3)|*.mp3" -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:650 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:651 #, python-brace-format msgid "Audio saved to {path}" msgstr "Аудио сохранено в {path}" -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:651 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:652 msgid "Success" msgstr "Успешно" -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:657 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:658 #, python-brace-format msgid "Failed to save audio: {error}" msgstr "Не удалось сохранить аудио: {error}" -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:672 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:673 msgid "Sample not available" msgstr "Образец недоступен" -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:677 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:678 msgid "Playing voice sample" msgstr "Воспроизведение образца голоса" -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:682 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:683 msgid "Failed to play sample" msgstr "Не удалось воспроизвести образец" #. Translators: Label for the input field where user enters their Gemini API Key. -#: addon\globalPlugins\NativeSpeechGeneration\interface\settings.py:38 +#: addon\globalPlugins\NativeSpeechGeneration\interface\settings.py:39 msgid "&Gemini API Key:" msgstr "&Ключ API Gemini:" @@ -537,20 +621,52 @@ msgstr "&Ключ API Gemini:" msgid "Show API Key" msgstr "Показать ключ API" -#. Add the row to the main settings sizer -#. Set initial state #. Translators: Button starting a process to help user get an API key (opens a website). -#: addon\globalPlugins\NativeSpeechGeneration\interface\settings.py:60 +#: addon\globalPlugins\NativeSpeechGeneration\interface\settings.py:67 msgid "&How to get API Key..." msgstr "&Как получить ключ API..." -#. Reinstall libraries button #. Translators: Button to force a reinstallation of external dependencies (Python libraries). -#: addon\globalPlugins\NativeSpeechGeneration\interface\settings.py:66 +#: addon\globalPlugins\NativeSpeechGeneration\interface\settings.py:72 msgid "&Reinstall Libraries" msgstr "&Переустановить библиотеки" +#: addon\globalPlugins\NativeSpeechGeneration\interface\settings.py:81 +#, python-brace-format +msgid "" +"The stored API key could not be decrypted on this Windows user or machine. " +"Using {envVarName} from the environment instead. Enter a new key here to " +"replace it." +msgstr "" +"Не удалось расшифровать сохранённый API-ключ для этой учётной записи или " +"этого компьютера Windows. Вместо него используется {envVarName} из " +"переменных среды. Введите здесь новый ключ, чтобы заменить его." + #: addon\globalPlugins\NativeSpeechGeneration\interface\settings.py:87 +#, python-brace-format +msgid "" +"The stored API key could not be decrypted on this Windows user or machine. " +"Enter a new key here, or set {envVarName} in the environment." +msgstr "" +"Не удалось расшифровать сохранённый API-ключ для этой учётной записи или " +"этого компьютера Windows. Введите здесь новый ключ или задайте {envVarName} " +"в переменных среды." + +#: addon\globalPlugins\NativeSpeechGeneration\interface\settings.py:94 +#, python-brace-format +msgid "" +"Using {envVarName} from the environment. Saving a key here will override it." +msgstr "" +"Используется {envVarName} из переменных среды. Сохранение ключа здесь " +"переопределит его." + +#. Translators: Error shown if Windows DPAPI storage fails while saving the API key. +#: addon\globalPlugins\NativeSpeechGeneration\interface\settings.py:138 +#, python-brace-format +msgid "Failed to save the Gemini API key securely: {error}" +msgstr "Не удалось безопасно сохранить API-ключ Gemini: {error}" + +#: addon\globalPlugins\NativeSpeechGeneration\interface\settings.py:158 msgid "" "This will delete the existing library and restart NVDA to redownload it.\n" "Are you sure?" @@ -559,7 +675,7 @@ msgstr "" "загрузки.\n" "Вы уверены?" -#: addon\globalPlugins\NativeSpeechGeneration\interface\settings.py:114 +#: addon\globalPlugins\NativeSpeechGeneration\interface\settings.py:179 msgid "" "Library removed successfully. NVDA will now restart to download the latest " "version." @@ -567,6 +683,15 @@ msgstr "" "Библиотека успешно удалена. NVDA будет перезагружена для загрузки последней " "версии." +#: addon\globalPlugins\NativeSpeechGeneration\interface\settings.py:190 +#, python-brace-format +msgid "" +"Failed to remove library: {error}\n" +"Please check log." +msgstr "" +"Не удалось удалить библиотеку: {error}\n" +"Пожалуйста, проверьте журнал." + #: buildVars.py:7 msgid "" "Harness the power of Google's state-of-the-art Gemini AI for high-quality " @@ -617,82 +742,39 @@ msgstr "" #: buildVars.py:18 msgid "" -"- Talk With AI: Added optional session memory and a clear-memory control.\n" +"- Security: Gemini API keys are now stored with Windows DPAPI instead of " +"plaintext config.\n" +"- Stability: Preserved add-on configuration across updates so API keys are " +"no longer wiped by the uninstall/update flow.\n" +"- Deployment: Added GEMINI_API_KEY environment variable fallback for managed " +"setups.\n" +"- Talk With AI: Migrated to gemini-3.1-flash-live-preview with Live API " +"thinking controls.\n" +"- Talk With AI: Replaced the memory UI with No Thinking, Low, Medium, and " +"High.\n" +"- Talk With AI: Preserved reconnect continuity internally by replaying " +"recent transcript history after reconnects.\n" +"- Talk With AI: Kept style instructions as the Live API system instruction " +"and retained Google Search grounding.\n" "- Talk With AI: Improved stream stability with reconnect backoff/retry and " "adaptive buffering.\n" "- Documentation: Added Spanish-language documentation.\n" msgstr "" - -#~ msgid "" -#~ "- Stability: Restored previous dependency handling to resolve crashes " -#~ "(pyo3 panic).\n" -#~ "- Localization: Fixed translation issues for Interface and Settings " -#~ "dialogs.\n" -#~ "- Fix: Resolved SSL Certificate path error.\n" -#~ msgstr "" -#~ "- Стабильность: Восстановлена прежняя обработка зависимостей для " -#~ "устранения сбоев (паника pyo3).\n" -#~ "- Локализация: Исправлены проблемы перевода в диалогах Интерфейса и " -#~ "Настроек.\n" -#~ "- Исправление: Устранена ошибка пути к SSL-сертификату.\n" - -#~ msgid "Audio generated, but failed to play automatically: {error}" -#~ msgstr "" -#~ "Аудио сгенерировано, но не удалось воспроизвести автоматически: {error}" - -#~ msgid "Info" -#~ msgstr "Информация" - -#~ msgid "Could not find the installation script. Please reinstall the add-on." -#~ msgstr "" -#~ "Не удалось найти скрипт установки. Пожалуйста, переустановите дополнение." - -#~ msgid "Dependencies already satisfied." -#~ msgstr "Зависимости уже удовлетворены." - -#~ msgid "" -#~ "The Native Speech Generation add-on has been installed successfully.\n" -#~ "\n" -#~ "To use this add-on, you must obtain a Gemini API key from Google AI " -#~ "Studio and enter it in the add-on's settings (NVDA Menu -> Preferences -> " -#~ "Settings -> Native Speech Generation).\n" -#~ "\n" -#~ "To open the generation dialog, you can press the shortcut " -#~ "NVDA+Control+Shift+G (this can be changed via NVDA Menu -> Preferences -> " -#~ "Input Gestures).\n" -#~ msgstr "" -#~ "Дополнение «Нативная генерация речи» было успешно установлено.\n" -#~ "\n" -#~ "Для использования этого дополнения необходимо получить ключ API Gemini из " -#~ "Google AI Studio и ввести его в настройках дополнения (Меню NVDA -> " -#~ "Параметры -> Настройки -> Нативная генерация речи).\n" -#~ "\n" -#~ "Чтобы открыть диалог генерации, нажмите комбинацию клавиш " -#~ "NVDA+Control+Shift+G (это можно изменить через Меню NVDA -> Параметры -> " -#~ "Жесты ввода).\n" - -#~ msgid "Native Speech Generation Installation Complete" -#~ msgstr "Установка «Нативной генерации речи» завершена" - -#~ msgid "" -#~ "Dependency installation failed. The Native Speech Generation add-on might " -#~ "not work correctly. Please review the log for errors." -#~ msgstr "" -#~ "Установка зависимостей не удалась. Дополнение «Нативная генерация речи» " -#~ "может работать некорректно. Пожалуйста, проверьте журнал на наличие " -#~ "ошибок." - -#~ msgid "Installation Warning" -#~ msgstr "Предупреждение при установке" - -#~ msgid "Please select a voice for Speaker 1." -#~ msgstr "Пожалуйста, выберите голос для Говорящего 1." - -#~ msgid "Please select voices for both speakers." -#~ msgstr "Пожалуйста, выберите голоса для обоих говорящих." - -#~ msgid "No audio available to play." -#~ msgstr "Нет доступного аудио для воспроизведения." - -#~ msgid "No generated audio to save." -#~ msgstr "Нет сгенерированного аудио для сохранения." +"- Безопасность: Ключи API Gemini теперь хранятся с использованием Windows " +"DPAPI вместо открытого текста в конфигурации.\n" +"- Стабильность: Конфигурация дополнения теперь сохраняется между " +"обновлениями, поэтому ключи API больше не стираются при удалении или " +"обновлении.\n" +"- Развёртывание: Добавлена поддержка переменной среды GEMINI_API_KEY для " +"управляемых сред.\n" +"- Разговор с ИИ: Выполнен переход на gemini-3.1-flash-live-preview с " +"элементами управления мышлением Live API.\n" +"- Разговор с ИИ: Интерфейс памяти заменён на варианты Без рассуждений, " +"Низкий, Средний и Высокий.\n" +"- Разговор с ИИ: Внутренне сохранена непрерывность после переподключения за " +"счёт повторного воспроизведения недавней истории расшифровок.\n" +"- Разговор с ИИ: Инструкции по стилю сохранены как системная инструкция Live " +"API, а интеграция с Google Search оставлена включённой.\n" +"- Разговор с ИИ: Улучшена стабильность потока благодаря повторным " +"подключениям, повторам и адаптивной буферизации.\n" +"- Документация: Добавлена документация на испанском языке.\n" diff --git a/addon/locale/uk/LC_MESSAGES/nvda.po b/addon/locale/uk/LC_MESSAGES/nvda.po index 0536df6..cf12366 100644 --- a/addon/locale/uk/LC_MESSAGES/nvda.po +++ b/addon/locale/uk/LC_MESSAGES/nvda.po @@ -1,16 +1,16 @@ msgid "" msgstr "" -"Project-Id-Version: 'NativeSpeechGeneration' '1.5.6'\n" +"Project-Id-Version: 'NativeSpeechGeneration' '1.6.0'\n" "Report-Msgid-Bugs-To: 'nvda-translations@groups.io'\n" -"POT-Creation-Date: 2026-02-20 16:58+0700\n" -"PO-Revision-Date: 2026-02-20 17:18+0700\n" +"POT-Creation-Date: 2026-04-04 16:18+0800\n" +"PO-Revision-Date: 2026-04-04 16:48+0800\n" "Last-Translator: \n" "Language-Team: \n" "Language: uk_UA\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Generator: Poedit 3.8\n" +"X-Generator: Poedit 3.9\n" "X-Poedit-Basepath: .\n" #: addon\globalPlugins\NativeSpeechGeneration\lib_updater.py:57 @@ -44,21 +44,21 @@ msgstr "" #: addon\globalPlugins\NativeSpeechGeneration\lib_updater.py:97 #: addon\globalPlugins\NativeSpeechGeneration\lib_updater.py:153 -#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:233 -#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:234 -#: addon\globalPlugins\NativeSpeechGeneration\__init__.py:146 -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:346 -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:363 -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:383 -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:394 -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:402 -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:411 -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:434 -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:525 -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:596 -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:619 -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:658 -#: addon\globalPlugins\NativeSpeechGeneration\interface\settings.py:126 +#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:277 +#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:278 +#: addon\globalPlugins\NativeSpeechGeneration\__init__.py:131 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:336 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:355 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:388 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:405 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:417 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:440 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:531 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:599 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:621 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:659 +#: addon\globalPlugins\NativeSpeechGeneration\interface\settings.py:139 +#: addon\globalPlugins\NativeSpeechGeneration\interface\settings.py:191 msgid "Error" msgstr "Помилка" @@ -102,7 +102,7 @@ msgstr "" "завантаження залежностей і потребує перезапуску NVDA." #: addon\globalPlugins\NativeSpeechGeneration\lib_updater.py:166 -#: addon\globalPlugins\NativeSpeechGeneration\interface\settings.py:88 +#: addon\globalPlugins\NativeSpeechGeneration\interface\settings.py:159 msgid "Confirm Reinstall" msgstr "Підтвердити перевстановлення" @@ -119,133 +119,208 @@ msgid "Missing Dependencies" msgstr "Відсутні залежності" #. Translators: Title of the dialog for the "Talk With AI" feature (REAL-TIME conversation). -#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:51 +#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:59 msgid "Talk With AI" msgstr "Розмова з ШІ" -#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:87 -msgid "PyAudio library is not installed. This feature requires PyAudio." -msgstr "Бібліотека PyAudio не встановлена. Для цієї функції потрібен PyAudio." +#. Translators: Choice label for the lowest reasoning setting in Talk With AI. +#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:94 +msgid "No Thinking" +msgstr "Без роздумів" + +#. Translators: Choice label for low reasoning depth in Talk With AI. +#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:96 +msgid "Low" +msgstr "Низький" + +#. Translators: Choice label for medium reasoning depth in Talk With AI. +#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:98 +msgid "Medium" +msgstr "Середній" + +#. Translators: Choice label for high reasoning depth in Talk With AI. +#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:100 +msgid "High" +msgstr "Високий" -#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:91 -msgid "Google GenAI library is not installed." -msgstr "Бібліотека Google GenAI не встановлена." +#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:110 +msgid "" +"PyAudio is not available. This feature requires a working PyAudio " +"installation." +msgstr "" +"PyAudio недоступний. Для цієї функції потрібна працездатна інсталяція " +"PyAudio." -#. 1. Status Area -#. Translators: Label for the status area showing connection state. -#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:129 +#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:119 +msgid "Google GenAI is not available." +msgstr "Google GenAI недоступний." + +#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:163 msgid "Status" msgstr "Статус" -#. Translators: Initial status message when the dialog opens. -#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:132 +#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:167 +#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:268 +#, python-brace-format +msgid "Status: {status}" +msgstr "Статус: {status}" + +#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:167 msgid "Ready to Connect" msgstr "Готовий до підключення" -#. 2. Controls Area -#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:137 +#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:172 msgid "Controls" msgstr "Керування" #. Translators: Button to start the voice conversation. -#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:143 +#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:177 msgid "Start Conversation" msgstr "Розпочати розмову" #. Translators: Button to stop the voice conversation. -#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:146 +#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:180 msgid "Stop Conversation" msgstr "Зупинити розмову" -#. Mic Toggle #. Translators: Toggle button label indicating microphone is ON. -#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:156 -#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:238 +#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:189 +#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:284 msgid "Microphone: ON" msgstr "Мікрофон: УВІМК" -#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:166 +#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:197 msgid "Microphone:" msgstr "Мікрофон:" -#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:177 +#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:207 msgid "Speaker:" msgstr "Голос:" -#. Google Search Checkbox -#. Translators: Checkbox to enable "Grounding", allowing the AI to search Google for answers. -#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:190 +#. Translators: Checkbox to enable grounding with Google Search in Talk With AI. +#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:219 msgid "Grounding with Google Search" msgstr "Перевірка за допомогою пошуку Google" -#. Translators: Checkbox to enable temporary conversation memory during this dialog session. -#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:195 -msgid "Use session memory" -msgstr "" +#. Translators: Label for choosing the reasoning depth in Talk With AI. +#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:225 +msgid "Thinking level:" +msgstr "Рівень міркування:" -#. Translators: Button to clear temporary conversation memory for the current dialog session. -#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:201 -msgid "Clear memory now" -msgstr "" - -#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:207 +#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:233 msgid "Volume:" msgstr "Гучність:" -#. 3. Info Area (Voice only) -#. Translators: Label for the selected voice -#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:219 -msgid "Voice: " -msgstr "Голос: " - -#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:229 -msgid "Status: " -msgstr "Статус: " +#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:244 +#, python-brace-format +msgid "Voice: {voiceName}" +msgstr "Голос: {voiceName}" -#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:238 +#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:284 msgid "Microphone: OFF" msgstr "Мікрофон: ВИМК" -#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:256 -msgid "Memory cleared" +#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:303 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:398 +#, python-brace-format +msgid "" +"{baseMessage}\n" +"\n" +"Import detail: {errorDetail}" msgstr "" +"{baseMessage}\n" +"\n" +"Подробиці імпорту: {errorDetail}" -#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:337 +#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:387 +#, python-brace-format +msgid "" +"Installed google-genai library ({version}) does not support the Gemini 3.1 " +"Live API features required by Talk With AI. Missing: {missing}. Please " +"update the add-on libraries." +msgstr "" +"Установлена бібліотека google-genai ({version}) не підтримує можливості " +"Gemini 3.1 Live API, потрібні для функції «Розмова з ШІ». Відсутні: " +"{missing}. Будь ласка, оновіть бібліотеки додатка." + +#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:393 +#, python-brace-format +msgid "" +"Installed google-genai library does not support the Gemini 3.1 Live API " +"features required by Talk With AI. Missing: {missing}. Please update the add-" +"on libraries." +msgstr "" +"Установлена бібліотека google-genai не підтримує можливості Gemini 3.1 Live " +"API, потрібні для функції «Розмова з ШІ». Відсутні: {missing}. Будь ласка, " +"оновіть бібліотеки додатка." + +#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:434 msgid "Connecting..." msgstr "Підключення..." -#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:345 +#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:442 msgid "Disconnecting..." msgstr "Відключення..." -#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:656 +#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:599 +msgid "Google GenAI types are not available." +msgstr "Типи Google GenAI недоступні." + +#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:622 +msgid "" +"Failed to prepare the Gemini Live configuration. Please update the add-on " +"libraries." +msgstr "" +"Не вдалося підготувати конфігурацію Gemini Live. Будь ласка, оновіть " +"бібліотеки додатка." + +#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:632 +#, python-brace-format +msgid "" +"The installed google-genai library ({version}) is too old for Gemini 3.1 " +"Live sessions. Missing session method: {methodName}. Please update the add-" +"on libraries." +msgstr "" +"Установлена бібліотека google-genai ({version}) застаріла для Live-сеансів " +"Gemini 3.1. Відсутній метод сеансу: {methodName}. Будь ласка, оновіть " +"бібліотеки додатка." + +#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:640 +#, python-brace-format +msgid "" +"The installed google-genai library is too old for Gemini 3.1 Live sessions. " +"Missing session method: {methodName}. Please update the add-on libraries." +msgstr "" +"Установлена бібліотека google-genai застаріла для Live-сеансів Gemini 3.1. " +"Відсутній метод сеансу: {methodName}. Будь ласка, оновіть бібліотеки додатка." + +#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:788 msgid "Connected" msgstr "З'єднано" -#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:698 -#, fuzzy, python-brace-format -#| msgid "Connection Lost. Retrying..." +#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:835 +#, python-brace-format msgid "Connection lost. Retrying in {seconds:.1f}s" -msgstr "З’єднання втрачено. Повторна спроба..." +msgstr "" -#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:752 +#: addon\globalPlugins\NativeSpeechGeneration\talkWithAI.py:888 msgid "Ready" msgstr "Готово" -#: addon\globalPlugins\NativeSpeechGeneration\__init__.py:61 -#: addon\globalPlugins\NativeSpeechGeneration\__init__.py:115 +#: addon\globalPlugins\NativeSpeechGeneration\__init__.py:56 +#: addon\globalPlugins\NativeSpeechGeneration\__init__.py:100 msgid "Open the Native Speech Generation dialog" msgstr "Відкриває діалог генерації природного мовлення" #. Translators: Title of the settings panel in NVDA preferences. -#: addon\globalPlugins\NativeSpeechGeneration\__init__.py:62 -#: addon\globalPlugins\NativeSpeechGeneration\__init__.py:116 -#: addon\globalPlugins\NativeSpeechGeneration\interface\settings.py:29 +#: addon\globalPlugins\NativeSpeechGeneration\__init__.py:57 +#: addon\globalPlugins\NativeSpeechGeneration\__init__.py:101 +#: addon\globalPlugins\NativeSpeechGeneration\interface\settings.py:25 #: buildVars.py:6 msgid "Native Speech Generation" msgstr "Генерація природного мовлення" -#: addon\globalPlugins\NativeSpeechGeneration\__init__.py:69 +#: addon\globalPlugins\NativeSpeechGeneration\__init__.py:64 msgid "" "Native Speech Generation is installing dependencies. Please restart NVDA for " "the changes to take effect." @@ -254,22 +329,22 @@ msgstr "" "зміни набули чинності." #. Translators: Title of the information dialog recommending a restart. -#: addon\globalPlugins\NativeSpeechGeneration\__init__.py:73 -#: addon\globalPlugins\NativeSpeechGeneration\interface\settings.py:115 +#: addon\globalPlugins\NativeSpeechGeneration\__init__.py:68 +#: addon\globalPlugins\NativeSpeechGeneration\interface\settings.py:180 msgid "Restart Required" msgstr "Потрібен перезапуск" #. Translators: Name of the add-on in the NVDA Tools menu. -#: addon\globalPlugins\NativeSpeechGeneration\__init__.py:108 +#: addon\globalPlugins\NativeSpeechGeneration\__init__.py:93 msgid "&Native Speech Generation" msgstr "&Генерація природного мовлення" #. Translators: Tooltip or description for the menu item. -#: addon\globalPlugins\NativeSpeechGeneration\__init__.py:110 +#: addon\globalPlugins\NativeSpeechGeneration\__init__.py:95 msgid "Generate speech using Gemini TTS" msgstr "Генерувати мовлення за допомогою Gemini TTS" -#: addon\globalPlugins\NativeSpeechGeneration\__init__.py:130 +#: addon\globalPlugins\NativeSpeechGeneration\__init__.py:115 msgid "" "The Native Speech Generation add-on is already open. Please close the dialog " "before opening it again." @@ -278,141 +353,157 @@ msgstr "" "перш ніж відкривати його знову." #. Translators: Title of warning dialog when user tries to open the add-on twice. -#: addon\globalPlugins\NativeSpeechGeneration\__init__.py:133 +#: addon\globalPlugins\NativeSpeechGeneration\__init__.py:118 msgid "Add-on Already Running" msgstr "Додаток вже запущено" -#: addon\globalPlugins\NativeSpeechGeneration\__init__.py:145 +#: addon\globalPlugins\NativeSpeechGeneration\__init__.py:130 #, python-brace-format msgid "Failed to open Native Speech Generation dialog: {error}" msgstr "Не вдалося відкрити діалог«Генерація природного мовлення»: {error}" #. Translators: The title of the main dialog window for generating speech. -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:48 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:44 msgid "Native Speech Generation (Gemini TTS)" msgstr "Генерація природного мовлення (Gemini TTS)" -#. Text Input #. Translators: Label for the text area where user inputs text to be converted to speech. -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:73 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:65 msgid "&Type text to convert here:" msgstr "&Введіть тут текст для перетворення:" -#. Style Input #. Translators: Label for optional instructions on how the speech should be spoken (e.g. "Happy", "Sad"). -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:80 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:71 msgid "&Style instructions (optional):" msgstr "&Інструкції щодо стилю (за бажанням):" #. Translators: Label for selecting the AI model to use for generation. -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:88 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:78 msgid "Select &Model:" msgstr "Виберіть модель:" -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:89 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:79 msgid "Flash (Standard Quality)" msgstr "Flash (стандартна якість)" -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:89 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:79 msgid "Pro (High Quality)" msgstr "Pro (висока якість)" #. Translators: Radio button to select single speaker mode. -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:96 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:86 msgid "Single-speaker" msgstr "Одномовний" #. Translators: Radio button to select multi-speaker mode. -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:98 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:88 msgid "Multi-speaker (2)" msgstr "Багатомовний (2)" -#. Settings Toggle #. Translators: Checkbox to show advanced settings like Temperature. -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:108 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:97 msgid "Advanced Settings (&Temperature)" msgstr "Розширені налаштування (&тембр)" #. Translators: Label for the temperature slider which controls creativity of the AI. -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:119 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:107 msgid "Temperature:" msgstr "Тембр:" #. Translators: Button to start generating the speech audio. -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:147 -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:533 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:133 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:539 msgid "&Generate Speech" msgstr "&Генерувати мовлення" #. Translators: Button to play the generated audio. -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:152 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:138 msgid "&Play" msgstr "&Грати" #. Translators: Button to save the generated audio to a file. -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:158 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:144 msgid "Save &Audio" msgstr "Зберегти &аудіо" -#. Talk With AI Button #. Translators: Button to open the real-time conversation dialog. -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:167 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:152 msgid "Talk With &AI" msgstr "Розмова з &ШІ" #. Translators: Button to open settings specifically for configuring the API key. -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:174 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:158 msgid "API Key Settings" msgstr "Налаштування ключа API" #. Translators: Button that opens a web browser to view available voices in Google AI Studio. -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:177 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:161 msgid "View voices in AI Studio" msgstr "Переглянути голоси в AI Studio" -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:183 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:167 msgid "&Close" msgstr "&Закрити" #. Translators: Label for selecting a voice in single speaker mode. -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:201 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:185 msgid "Select &Voice:" msgstr "Вибрати & Голос:" -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:202 -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:221 -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:237 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:186 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:204 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:219 msgid "Loading voices..." msgstr "Завантаження голосів ..." -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:218 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:201 msgid "Speaker 1 Name:" msgstr "Голос 1 Ім'я:" -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:219 -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:471 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:202 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:477 msgid "Speaker1" msgstr "Голос 1" -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:220 -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:236 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:203 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:218 msgid "Voice:" msgstr "Голос:" -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:234 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:216 msgid "Speaker 2 Name:" msgstr "Голос2 Ім'я:" -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:235 -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:472 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:217 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:478 msgid "Speaker2" msgstr "Голос 2" -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:345 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:327 +#, python-brace-format +msgid "" +"The stored Gemini API key could not be decrypted on this Windows user or " +"machine. Please enter it again in NVDA settings, or define {envVarName} in " +"the environment." +msgstr "" +"Не вдалося розшифрувати збережений API-ключ Gemini для цього користувача або " +"цього компютера Windows. Введіть його ще раз у налаштуваннях NVDA або " +"задайте {envVarName} у змінних середовища." + +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:333 +#, python-brace-format +msgid "" +"No Gemini API key is configured. Set it in NVDA settings, or define " +"{envVarName} in the environment." +msgstr "" +"API-ключ Gemini не налаштовано. Укажіть його в налаштуваннях NVDA або " +"задайте {envVarName} у змінних середовища." + +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:354 msgid "Talk With AI module is missing." msgstr "Модуль «Розмова з ШІ» відсутній." -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:355 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:364 msgid "" "Talk With AI currently does not support multi-speaker mode. Please select " "Single-speaker." @@ -420,106 +511,102 @@ msgstr "" "Зараз Розмова з ШІ не підтримує режим декількох співрозмовників. Будь ласка, " "оберіть режим одного співрозмовника." -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:357 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:366 msgid "Feature Limitation" msgstr "Обмеження функцій" -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:363 -msgid "No GEMINI_API_KEY configured." -msgstr "Не налаштовано ключ_GEMINI_API." - -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:382 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:387 #, python-brace-format msgid "Failed to open Talk With AI: {error}" msgstr "Не вдалося відкрити Розмову з ШІ: {error}" -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:393 -msgid "google-genai library not installed. Please restart NVDA." -msgstr "бібліотека google-genai не встановлена. Перезапустіть NVDA." - -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:401 -msgid "No GEMINI_API_KEY configured. Set it in NVDA settings." -msgstr "Ключ_GEMINI_API не налаштовано. Встановіть його в налаштуваннях NVDA." +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:396 +msgid "" +"google-genai is not available. Please restart NVDA after updating the add-on " +"libraries." +msgstr "" +"google-genai недоступний. Перезапустіть NVDA після оновлення бібліотек " +"додатка." -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:410 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:416 msgid "Please enter text to generate." msgstr "Будь ласка, введіть текст для генерації." -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:417 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:423 msgid "Generating..." msgstr "Генерувати..." -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:424 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:430 msgid "Generating speech, please wait..." msgstr "Генерація мовлення, будь ласка, зачекайте..." -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:433 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:439 #, python-brace-format msgid "Failed to initialize Google GenAI client: {error}" msgstr "Не вдалося ініціалізувати клієнт Google GenAI: {error}" -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:444 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:450 msgid "Failed to generate audio." msgstr "Не вдалося створити аудіо." -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:446 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:452 msgid "Generation complete." msgstr "Генерація завершена." -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:520 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:526 msgid "An error occurred during generation." msgstr "Під час генерації сталася помилка." -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:524 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:530 #, python-brace-format msgid "An unexpected error occurred: {error}" msgstr "Сталася несподівана помилка: {error}" -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:595 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:598 msgid "No inline audio data returned by model." msgstr "Модель не повертає вбудованих аудіоданих." -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:618 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:620 #, python-brace-format msgid "Failed to generate speech: {error}" msgstr "Не вдалося згенерувати мовлення: {error}" -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:637 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:638 msgid "Save Audio File" msgstr "Зберегти аудіофайл" -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:638 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:639 msgid "WAV files (*.wav)|*.wav|MP3 files (*.mp3)|*.mp3" msgstr "Файли WAV (*.wav)|*.wav|Файли MP3 (*.mp3)|*.mp3" -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:650 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:651 #, python-brace-format msgid "Audio saved to {path}" msgstr "Аудіо збережено в {path}" -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:651 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:652 msgid "Success" msgstr "Успіх" -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:657 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:658 #, python-brace-format msgid "Failed to save audio: {error}" msgstr "Не вдалося зберегти аудіо: {error}" -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:672 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:673 msgid "Sample not available" msgstr "Зразок недоступний" -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:677 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:678 msgid "Playing voice sample" msgstr "Відтворення зразка голосу" -#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:682 +#: addon\globalPlugins\NativeSpeechGeneration\interface\generation_dialog.py:683 msgid "Failed to play sample" msgstr "Не вдалося відтворити зразок" #. Translators: Label for the input field where user enters their Gemini API Key. -#: addon\globalPlugins\NativeSpeechGeneration\interface\settings.py:38 +#: addon\globalPlugins\NativeSpeechGeneration\interface\settings.py:39 msgid "&Gemini API Key:" msgstr "&Ключ API Gemini:" @@ -528,20 +615,52 @@ msgstr "&Ключ API Gemini:" msgid "Show API Key" msgstr "Показати ключ API" -#. Add the row to the main settings sizer -#. Set initial state #. Translators: Button starting a process to help user get an API key (opens a website). -#: addon\globalPlugins\NativeSpeechGeneration\interface\settings.py:60 +#: addon\globalPlugins\NativeSpeechGeneration\interface\settings.py:67 msgid "&How to get API Key..." msgstr "&Як отримати ключ API ..." -#. Reinstall libraries button #. Translators: Button to force a reinstallation of external dependencies (Python libraries). -#: addon\globalPlugins\NativeSpeechGeneration\interface\settings.py:66 +#: addon\globalPlugins\NativeSpeechGeneration\interface\settings.py:72 msgid "&Reinstall Libraries" msgstr "&Перевстановити бібліотеки" +#: addon\globalPlugins\NativeSpeechGeneration\interface\settings.py:81 +#, python-brace-format +msgid "" +"The stored API key could not be decrypted on this Windows user or machine. " +"Using {envVarName} from the environment instead. Enter a new key here to " +"replace it." +msgstr "" +"Не вдалося розшифрувати збережений API-ключ для цього користувача або цього " +"компютера Windows. Натомість використовується {envVarName} зі змінних " +"середовища. Введіть тут новий ключ, щоб замінити його." + #: addon\globalPlugins\NativeSpeechGeneration\interface\settings.py:87 +#, python-brace-format +msgid "" +"The stored API key could not be decrypted on this Windows user or machine. " +"Enter a new key here, or set {envVarName} in the environment." +msgstr "" +"Не вдалося розшифрувати збережений API-ключ для цього користувача або цього " +"компютера Windows. Введіть тут новий ключ або задайте {envVarName} у змінних " +"середовища." + +#: addon\globalPlugins\NativeSpeechGeneration\interface\settings.py:94 +#, python-brace-format +msgid "" +"Using {envVarName} from the environment. Saving a key here will override it." +msgstr "" +"Використовується {envVarName} зі змінних середовища. Збережений тут ключ " +"перевизначить його." + +#. Translators: Error shown if Windows DPAPI storage fails while saving the API key. +#: addon\globalPlugins\NativeSpeechGeneration\interface\settings.py:138 +#, python-brace-format +msgid "Failed to save the Gemini API key securely: {error}" +msgstr "Не вдалося безпечно зберегти API-ключ Gemini: {error}" + +#: addon\globalPlugins\NativeSpeechGeneration\interface\settings.py:158 msgid "" "This will delete the existing library and restart NVDA to redownload it.\n" "Are you sure?" @@ -550,7 +669,7 @@ msgstr "" "повторного завантаження.\n" "Ви впевнені?" -#: addon\globalPlugins\NativeSpeechGeneration\interface\settings.py:114 +#: addon\globalPlugins\NativeSpeechGeneration\interface\settings.py:179 msgid "" "Library removed successfully. NVDA will now restart to download the latest " "version." @@ -558,6 +677,15 @@ msgstr "" "Бібліотеку успішно видалено. Тепер NVDA перезапуститься, щоб завантажити " "останню версію." +#: addon\globalPlugins\NativeSpeechGeneration\interface\settings.py:190 +#, python-brace-format +msgid "" +"Failed to remove library: {error}\n" +"Please check log." +msgstr "" +"Не вдалося видалити бібліотеку: {error}\n" +"Будь ласка, перевірте журнал." + #: buildVars.py:7 msgid "" "Harness the power of Google's state-of-the-art Gemini AI for high-quality " @@ -608,75 +736,38 @@ msgstr "" #: buildVars.py:18 msgid "" -"- Talk With AI: Added optional session memory and a clear-memory control.\n" +"- Security: Gemini API keys are now stored with Windows DPAPI instead of " +"plaintext config.\n" +"- Stability: Preserved add-on configuration across updates so API keys are " +"no longer wiped by the uninstall/update flow.\n" +"- Deployment: Added GEMINI_API_KEY environment variable fallback for managed " +"setups.\n" +"- Talk With AI: Migrated to gemini-3.1-flash-live-preview with Live API " +"thinking controls.\n" +"- Talk With AI: Replaced the memory UI with No Thinking, Low, Medium, and " +"High.\n" +"- Talk With AI: Preserved reconnect continuity internally by replaying " +"recent transcript history after reconnects.\n" +"- Talk With AI: Kept style instructions as the Live API system instruction " +"and retained Google Search grounding.\n" "- Talk With AI: Improved stream stability with reconnect backoff/retry and " "adaptive buffering.\n" "- Documentation: Added Spanish-language documentation.\n" msgstr "" - -#, python-brace-format -#~ msgid "Audio generated, but failed to play automatically: {error}" -#~ msgstr "Аудіофайл створено, але не вдалося відтворити автоматично: {error}" - -#~ msgid "Info" -#~ msgstr "Інформація" - -#~ msgid "" -#~ "The Native Speech Generation add-on has been installed successfully.\n" -#~ "\n" -#~ "To use this add-on, you must obtain a Gemini API key from Google AI " -#~ "Studio and enter it in the add-on's settings (NVDA Menu -> Tools -> " -#~ "Native Speech Generation).\n" -#~ "\n" -#~ "To run the Native Speech Generation add-on, you can press the shortcut " -#~ "NVDA+Control+Shift+G (this can be changed via NVDA Menu -> Preferences -> " -#~ "Input Gestures, under the Native Speech Generation category).\n" -#~ "\n" -#~ "Please note that this add-on bundles external libraries. If you encounter " -#~ "any issues, please ensure your NVDA version is compatible." -#~ msgstr "" -#~ "Додаток Генерація природної мови успішно встановлено.\n" -#~ "\n" -#~ "Щоб скористатися цим додатком, необхідно отримати ключ Gemini API від " -#~ "Google AI Studio та ввести його в налаштуваннях додатка (меню NVDA -> " -#~ "Інструменти -> Генерація природної мови).\n" -#~ "\n" -#~ "Щоб запустити додаток Native Speech Generation, натисніть комбінацію " -#~ "клавіш NVDA+Control+Shift+G (її можна змінити в меню NVDA -> Налаштування " -#~ "-> Жести вводу, у категорії Генерація природної мови).\n" -#~ "\n" -#~ "Зверніть увагу, що цей додаток містить зовнішні бібліотеки. Якщо у вас " -#~ "виникли проблеми, переконайтеся, що ваша версія NVDA сумісна." - -#~ msgid "Native Speech Generation Installation Complete" -#~ msgstr "Встановлення додатка Генерація природної мови встановлено" - -#~ msgid "Please select a voice for Speaker 1." -#~ msgstr "Будь ласка, Виберіть голос для диктора 1." - -#~ msgid "Please select voices for both speakers." -#~ msgstr "Будь ласка, виберіть голоси для обох дикторів." - -#~ msgid "No audio available to play." -#~ msgstr "Аудіофайл недоступний для відтворення." - -#~ msgid "No generated audio to save." -#~ msgstr "Немає згенерованого аудіо для збереження." - -#~ msgid "No voice selected" -#~ msgstr "Жодного голосу не вибрано" - -#~ msgid "NativeSpeechGeneration" -#~ msgstr "Генерація природної мови" - -#~ msgid "" -#~ "Added ability to play voice samples directly from voice selection, " -#~ "refactored code for better readability, improved error handling and UI " -#~ "layout, optimized voice selection logic and threading, and enhanced audio " -#~ "format detection with WAV conversion." -#~ msgstr "" -#~ "Додано можливість відтворення голосових зразків безпосередньо з вибору " -#~ "голосу, перероблено код для кращої читабельності, покращено обробку " -#~ "помилок і макет інтерфейсу користувача, оптимізовано логіку вибору голосу " -#~ "і багатопотоковість, а також покращено виявлення аудіоформатів з " -#~ "конвертацією WAV." +"- Безпека: Ключі API Gemini тепер зберігаються за допомогою Windows DPAPI " +"замість відкритого тексту в конфігурації.\n" +"- Стабільність: Конфігурація додатка тепер зберігається між оновленнями, " +"тому ключі API більше не стираються під час видалення або оновлення.\n" +"- Розгортання: Додано підтримку змінної середовища GEMINI_API_KEY для " +"керованих середовищ.\n" +"- Розмова з ШІ: Виконано перехід на gemini-3.1-flash-live-preview з " +"елементами керування міркуванням у Live API.\n" +"- Розмова з ШІ: Інтерфейс памяті замінено на варіанти Без роздумів, Низький, " +"Середній і Високий.\n" +"- Розмова з ШІ: Внутрішньо збережено безперервність після повторного " +"підключення завдяки відтворенню недавньої історії транскрипцій.\n" +"- Розмова з ШІ: Інструкції щодо стилю збережено як системну інструкцію Live " +"API, а інтеграцію з Google Search залишено ввімкненою.\n" +"- Розмова з ШІ: Покращено стабільність потоку завдяки повторним " +"підключенням, повторам і адаптивній буферизації.\n" +"- Документація: Додано документацію іспанською мовою.\n" diff --git a/buildVars.py b/buildVars.py index cf76b6d..83affab 100644 --- a/buildVars.py +++ b/buildVars.py @@ -14,8 +14,14 @@ - Seamless Workflow: The add-on provides instant audio playback upon generation and allows you to save the resulting .wav file for later use. To get started, obtain a Gemini API key from Google AI Studio and enter it in the add-on's settings panel, found under NVDA's Tools menu."""), - addon_version="1.5.6", - addon_changelog=_("""- Talk With AI: Added optional session memory and a clear-memory control. + addon_version="1.6.0", + addon_changelog=_("""- Security: Gemini API keys are now stored with Windows DPAPI instead of plaintext config. +- Stability: Preserved add-on configuration across updates so API keys are no longer wiped by the uninstall/update flow. +- Deployment: Added GEMINI_API_KEY environment variable fallback for managed setups. +- Talk With AI: Migrated to gemini-3.1-flash-live-preview with Live API thinking controls. +- Talk With AI: Replaced the memory UI with No Thinking, Low, Medium, and High. +- Talk With AI: Preserved reconnect continuity internally by replaying recent transcript history after reconnects. +- Talk With AI: Kept style instructions as the Live API system instruction and retained Google Search grounding. - Talk With AI: Improved stream stability with reconnect backoff/retry and adaptive buffering. - Documentation: Added Spanish-language documentation. """), diff --git a/changelog.md b/changelog.md index ca6b39c..cddab01 100644 --- a/changelog.md +++ b/changelog.md @@ -1,9 +1,14 @@ # Changelog -## version 1.5.6 +## version 1.6.0 -- Talk With AI: Added optional session memory to keep temporary conversation context during an active voice session. -- Talk With AI: Added a clear-memory control to reset temporary session context on demand. +- Security: Gemini API keys are now stored with Windows DPAPI instead of plaintext config. +- Stability: Preserved add-on configuration across updates so API keys are no longer wiped by uninstall/update flow. +- Deployment: Added `GEMINI_API_KEY` environment variable fallback for managed or institutional setups. +- Talk With AI: Migrated to `gemini-3.1-flash-live-preview` with the current Live API flow. +- Talk With AI: Replaced the memory UI with `No Thinking`, `Low`, `Medium`, and `High` reasoning controls. +- Talk With AI: Kept reconnect conversation continuity internally by replaying recent transcript history after reconnects. +- Talk With AI: Preserved style instructions as the Live API system instruction and kept optional Google Search grounding. - Talk With AI: Improved streaming stability with better reconnection handling (backoff + retry) and adaptive audio buffering. - UI: Simplified Generate Speech status behavior back to the standard `Generating...` flow. - Documentation: Added Spanish-language documentation. diff --git a/readme.md b/readme.md index b80a3a1..e7d7d7c 100644 --- a/readme.md +++ b/readme.md @@ -58,8 +58,8 @@ This add-on is designed for smooth workflows, accessibility-first interaction, a * **Grounding with Google Search**: Enable the AI to access real-time information from the web during your chat. * **Interruptible**: You can interrupt the AI at any time by speaking or pressing "Stop Conversation". * **Customizable**: Uses your selected voice and style instructions. -* **Session Memory (Optional)**: Keep temporary conversation context during the current Talk With AI session for more coherent follow-up responses. -* **Clear Memory Control**: Clear temporary session memory before starting a conversation (or after stopping). +* **Thinking Level Control**: Choose `No Thinking`, `Low`, `Medium`, or `High` depending on the reasoning depth you want. +* **Reconnect Continuity**: Recent conversation context is restored automatically after a reconnect, without a separate memory toggle. * **More Stable Streaming**: Improved reconnection behavior (backoff + retry) and adaptive audio buffering for better resilience on unstable networks. --- @@ -93,6 +93,13 @@ This add-on is designed for smooth workflows, accessibility-first interaction, a 5. Paste your **Gemini API Key** into the *GEMINI API Key* field. 6. Click **OK** to save. +Saved keys are stored securely using **Windows DPAPI**, so the encrypted value cannot be +decrypted on a different Windows machine or user account. + +For advanced deployments, you can also provide the key through the +**`GEMINI_API_KEY`** environment variable. The add-on will use it automatically when +no stored key is available. + --- ## How to Use @@ -165,8 +172,7 @@ Experience a natural, two-way voice conversation with Gemini. * **Stop Conversation**: Ends the session. * **Grounding with Google Search**: Check this box to allow Gemini to search the web for answers (e.g., current news, weather). * *Note: This checkbox is hidden while a conversation is active. Stop the conversation to change it.* - * **Use session memory**: Keep temporary memory for the current conversation session only. - * **Clear memory now**: Clear temporary memory before starting (or after stopping) a conversation. + * **Thinking level**: Choose `No Thinking`, `Low`, `Medium`, or `High`. * **Microphone Toggle**: Mute/Unmute your microphone. * **Volume**: Adjust the AI's playback volume. @@ -231,7 +237,7 @@ If you want to develop or modify this add-on, follow the steps below. ### Additional Dependencies -Install the Gemini SDK and pyaudio directly into the add-on library path: +Install the audio-only Talk With AI dependencies directly into the add-on library path: ``` python.exe -m pip install google-genai pyaudio --target "D:/myAdd-on/Native-Speech-Generation/addon/globalPlugins/NativeSpeechGeneration/lib" @@ -239,6 +245,8 @@ python.exe -m pip install google-genai pyaudio --target "D:/myAdd-on/Native-Spee Adjust the path according to your local add-on source directory. +For the current audio-only Talk With AI implementation, you do not need `opencv-python`, `pillow`, or `mss`. + Then copy the following from your Python installation into: ```