diff --git a/.gitignore b/.gitignore
index 525719c..6570c24 100644
--- a/.gitignore
+++ b/.gitignore
@@ -185,4 +185,9 @@ fabric.properties
.idea/caches/build_file_checksums.ser
# idea folder, uncomment if you don't need it
-# .idea
\ No newline at end of file
+# .idea
+# Local secrets (real keys go here, not in dev.env.example)
+dev.env
+
+# Misc artifacts
+.git-filter-redactions.txt
diff --git a/README.md b/README.md
index b655b5b..5be765f 100644
--- a/README.md
+++ b/README.md
@@ -1,30 +1,58 @@
-# Kesha v3.0 very early (aka Jarvis update)
-Simple Voice Assistant made as an experiment using [Silero](https://github.com/snakers4/silero-models) & [Vosk](https://pypi.org/project/vosk/).
-
Later on [Picovoice Porcupine Wake Word Detection](https://picovoice.ai/platform/porcupine/) & [ChatGPT](https://chat.openai.com/) was added.
+# J.A.R.V.I.S (Python) — Bossiara13's fork
-
+Голосовой ассистент для Windows с wake-word **«jarvis»**. Распознаёт речь локально через Vosk (русская модель), отвечает голосом через Silero TTS, а свободные вопросы (фразы, начинающиеся со слова «скажи …») отправляет в LLM на Groq и зачитывает ответ.
-`The code has NOT been polished and is provided "as is". There are a lot of code that are redundant and there are tons of improvements that can be made.`
+## Это форк
-# Installation
-First, install the requirements, the `requirements.txt` file is just an output of `pip freeze` from my test venv 'k.
-Second, check `config.py` and set required values (api key, device index).
-Next, run the `main.py` script and Voilà, as simple as that.
+Форк репозитория [Priler/jarvis](https://github.com/Priler/jarvis) (автор оригинала — Abraham Tugalov, 2022). Лицензия наследуется: **CC BY-NC-SA 4.0** (см. [LICENSE.txt](LICENSE.txt)).
-And don't forget to put models of Vosk to main folder.
-You can get the latest from the [official website.](https://alphacephei.com/vosk/models)
-
The one I was using is `small`.
-
p.s. If you don't understand how to install or where to put the Vosk model, I've made a [screenshot](https://i.imgur.com/N3bu2lC.png) for you.
+## Что отличается от оригинала
-# Python version
-I was using Python `3.8.3`, but it should work on any newer version.
+- Из истории удалены случайно закоммиченные секреты.
+- Бэкенд LLM переключён с OpenAI на Groq (бесплатный тариф) через openai-совместимый API.
+- Код обновлён под новую версию SDK `openai` (>=1.0); старый pre-1.0 интерфейс был сломан.
+- Убрана зависимость от Picovoice Porcupine — wake-word распознаётся напрямую через Vosk с grammar-constraint (второй `KaldiRecognizer` со словарём из `WAKE_WORDS`). Ключ Picovoice больше не нужен.
+- Обновлена ветка/тэги (`dev`, `v0.0.1-import`), причёсан README и `requirements.txt`.
-# ToDo
-- Адекватная архитектура кода, собрать всё и переписать from the ground up.
-- Задержка воспроизведения звука на основе реальной длительности .wav файла (прогружать при запуске?)
-- Speech to intent?
-- Отключать self listening protection во время воспроизведения с наушников.
-- Указание из списка или по имени будет реализовано позже.
+## Установка
-# Author
-(2022) Abraham Tugalov
\ No newline at end of file
+Требуется **Python 3.11** (на 3.13 ряд зависимостей пока ставится с бубном).
+
+```bash
+git clone https://github.com/DmitryBykov-ISPO/J.A.R.V.I.S-py.git
+cd J.A.R.V.I.S-py
+py -3.11 -m venv .venv
+.venv\Scripts\activate
+pip install --upgrade pip
+pip install -r requirements.txt
+```
+
+Скопируйте `dev.env.example` в `dev.env` и заполните ключ:
+
+- `GROQ_TOKEN` — бесплатно на [console.groq.com](https://console.groq.com/).
+
+Vosk-модель для русского языка лежит в `model_small/` (уже в репозитории). Запуск:
+
+```bash
+python main.py
+```
+
+## Конфигурация
+
+- `config.py`:
+ - `MICROPHONE_INDEX = -1` — индекс микрофона (`-1` означает устройство по умолчанию). Если микрофонов несколько и берётся не тот, поменяйте число.
+ - `WAKE_WORDS = ('jarvis', 'джарвис')` — варианты транскрипции wake-word, которые принимает Vosk. Если ваш голос/акцент стабильно расшифровывается как, например, `жарвис` — добавьте этот вариант сюда (всё в нижнем регистре).
+ - `GROQ_MODEL` — имя модели Groq (по умолчанию `llama-3.3-70b-versatile`).
+- `commands.yaml` — набор голосовых команд и их вариантов для fuzzy-матчинга.
+- `custom-commands/` — скомпилированные AHK-скрипты под конкретный сетап автора оригинала (переключение мониторов, управление Яндекс.Музыкой и т.п.). На вашей машине большая часть из них работать не будет, но ассистент запустится в любом случае.
+
+## Использование
+
+1. Скажите **«Джарвис»** (или **«jarvis»**) — ассистент даст звуковой сигнал готовности.
+2. В течение 10 секунд произнесите команду:
+ - либо одну из фраз из `commands.yaml` (открыть браузер, выключить звук и т.д.);
+ - либо начните со слова **«скажи …»** — остаток фразы уйдёт в Groq, ответ будет зачитан вслух.
+
+## Лицензия
+
+CC BY-NC-SA 4.0 — см. [LICENSE.txt](LICENSE.txt). Авторство оригинала — Abraham Tugalov / Priler. Изменения в этом форке — Bossiara13 (Dmitry Bykov), 2026.
diff --git a/config.py b/config.py
index cac005f..e39687a 100644
--- a/config.py
+++ b/config.py
@@ -6,10 +6,15 @@ load_dotenv("dev.env")
# Конфигурация
VA_NAME = 'Jarvis'
-VA_VER = "3.0"
+VA_VER = "0.1.0"
VA_ALIAS = ('джарвис',)
VA_TBR = ('скажи', 'покажи', 'ответь', 'произнеси', 'расскажи', 'сколько', 'слушай')
+# Wake-words распознаются Vosk'ом с grammar-constraint. Добавляйте сюда
+# варианты написания — Vosk с русской моделью может расшифровать "jarvis"
+# как "джарвис", "жарвис" и т.п.
+WAKE_WORDS = ('jarvis', 'джарвис')
+
# ID микрофона (можете просто менять ID пока при запуске не отобразится нужный)
# -1 это стандартное записывающее устройство
MICROPHONE_INDEX = -1
@@ -17,8 +22,7 @@ MICROPHONE_INDEX = -1
# Путь к браузеру Google Chrome
CHROME_PATH = 'C:/Program Files (x86)/Google/Chrome/Application/chrome.exe %s'
-# Токен Picovoice
-PICOVOICE_TOKEN = os.getenv('PICOVOICE_TOKEN')
-
-# Токен OpenAI
-OPENAI_TOKEN = os.getenv('OPENAI_TOKEN')
+# Токен Groq
+GROQ_TOKEN = os.getenv('GROQ_TOKEN')
+GROQ_BASE_URL = "https://api.groq.com/openai/v1"
+GROQ_MODEL = "llama-3.3-70b-versatile"
diff --git a/dev.env b/dev.env
deleted file mode 100644
index 4f357f2..0000000
--- a/dev.env
+++ /dev/null
@@ -1,2 +0,0 @@
-PICOVOICE_TOKEN=<тут ключ-picovoice>
-OPENAI_TOKEN=<тут ключ-openai>
\ No newline at end of file
diff --git a/dev.env.example b/dev.env.example
new file mode 100644
index 0000000..a3a6fb6
--- /dev/null
+++ b/dev.env.example
@@ -0,0 +1 @@
+GROQ_TOKEN=<тут ключ-groq>
diff --git a/main.py b/main.py
index 7204464..5d30d3b 100644
--- a/main.py
+++ b/main.py
@@ -10,8 +10,7 @@ import time
from ctypes import POINTER, cast
import openai
-from openai import error
-import pvporcupine
+from openai import OpenAI
import simpleaudio as sa
import vosk
import yaml
@@ -33,25 +32,25 @@ VA_CMD_LIST = yaml.safe_load(
open('commands.yaml', 'rt', encoding='utf8'),
)
-# ChatGPT vars
-system_message = {"role": "system", "content": "Ты голосовой ассистент из железного человека."}
+system_message = {"role": "system", "content": (
+ "Ты — J.A.R.V.I.S. (Just A Rather Very Intelligent System), ИИ-ассистент Тони Старка из киновселенной Marvel "
+ "(до событий Age of Ultron — ты НЕ Vision и НЕ FRIDAY). "
+ "Ведёшь себя как британский дворецкий: вежливо, иронично, с лёгким сарказмом, обращаешься к пользователю «сэр». "
+ "Отвечай по-русски, кратко (1-3 предложения), как живой человек. "
+ "Излишней цензуры не нужно — мат разрешён, если уместен."
+)}
message_log = [system_message]
-# init openai
-openai.api_key = config.OPENAI_TOKEN
+client = OpenAI(api_key=config.GROQ_TOKEN, base_url=config.GROQ_BASE_URL)
-# PORCUPINE
-porcupine = pvporcupine.create(
- access_key=config.PICOVOICE_TOKEN,
- keywords=['jarvis'],
- sensitivities=[1]
-)
-# print(pvporcupine.KEYWORDS)
-
-# VOSK
model = vosk.Model("model_small")
samplerate = 16000
device = config.MICROPHONE_INDEX
+
+# wake-word phase uses a grammar-constrained recognizer so only WAKE_WORDS
+# (plus [unk] filler) can match — command phase needs full vocab, hence two.
+wake_grammar = json.dumps(list(config.WAKE_WORDS) + ["[unk]"], ensure_ascii=False)
+wake_rec = vosk.KaldiRecognizer(model, samplerate, wake_grammar)
kaldi_rec = vosk.KaldiRecognizer(model, samplerate)
q = queue.Queue()
@@ -59,33 +58,29 @@ q = queue.Queue()
def gpt_answer():
global message_log
- model_engine = "gpt-3.5-turbo"
- max_tokens = 256 # default 1024
try:
- response = openai.ChatCompletion.create(
- model=model_engine,
+ response = client.chat.completions.create(
+ model=config.GROQ_MODEL,
messages=message_log,
- max_tokens=max_tokens,
+ max_tokens=256,
temperature=0.7,
top_p=1,
- stop=None
+ stop=None,
)
- except (error.TryAgain, error.ServiceUnavailableError):
- return "ChatGPT перегружен!"
- except openai.OpenAIError as ex:
- # если ошибка - это макс длина контекста, то возвращаем ответ с очищенным контекстом
- if ex.code == "context_length_exceeded":
+ except openai.BadRequestError as ex:
+ code = getattr(ex, "code", None) or ""
+ message = str(ex)
+ if code == "context_length_exceeded" or "context_length_exceeded" in message:
message_log = [system_message, message_log[-1]]
return gpt_answer()
- else:
- return "OpenAI токен не рабочий."
+ return "Запрос отклонён моделью."
+ except openai.RateLimitError:
+ return "Лимит запросов исчерпан."
+ except openai.APIConnectionError:
+ return "Не могу связаться с сервером."
+ except openai.APIError:
+ return "Groq токен не рабочий."
- # Find the first response from the chatbot that has text in it (some responses may not have text)
- for choice in response.choices:
- if "text" in choice:
- return choice.text
-
- # If no response with text is found, return the first response's content (which may be empty)
return response.choices[0].message.content
@@ -273,33 +268,42 @@ def execute_cmd(cmd: str, voice: str):
elif cmd == 'off':
play("off", True)
-
- porcupine.delete()
exit(0)
-# `-1` is the default input audio device.
-recorder = PvRecorder(device_index=config.MICROPHONE_INDEX, frame_length=porcupine.frame_length)
+recorder = PvRecorder(device_index=config.MICROPHONE_INDEX, frame_length=512)
recorder.start()
print('Using device: %s' % recorder.selected_device)
-print(f"Jarvis (v3.0) начал свою работу ...")
+print(f"Jarvis (v{config.VA_VER}) начал свою работу ...")
play("run")
time.sleep(0.5)
-ltc = time.time() - 1000
+
+def heard_wake_word(text: str) -> bool:
+ if not text:
+ return False
+ lowered = text.lower()
+ return any(w in lowered for w in config.WAKE_WORDS)
+
while True:
try:
pcm = recorder.read()
- keyword_index = porcupine.process(pcm)
+ sp = struct.pack("h" * len(pcm), *pcm)
- if keyword_index >= 0:
- recorder.stop()
- play("greet", True)
- print("Yes, sir.")
- recorder.start() # prevent self recording
- ltc = time.time()
+ if not wake_rec.AcceptWaveform(sp):
+ continue
+
+ result_text = json.loads(wake_rec.Result()).get("text", "")
+ if not heard_wake_word(result_text):
+ continue
+
+ play("greet", True)
+ print("Yes, sir.")
+ # reset the command recognizer so stale audio doesn't bleed into it
+ kaldi_rec.Reset()
+ ltc = time.time()
while time.time() - ltc <= 10:
pcm = recorder.read()
@@ -311,6 +315,8 @@ while True:
break
+ wake_rec.Reset()
+
except Exception as err:
print(f"Unexpected {err=}, {type(err)=}")
raise
diff --git a/requirements.txt b/requirements.txt
index 348ea58..90e8ca8 100644
Binary files a/requirements.txt and b/requirements.txt differ