feat: tier-1 PC tools parity — window mgmt, clipboard, notes, process killer, web search
Mirrors the new rust packs from the same iteration. Total commands in
commands.yaml is now 49 (was 33). yaml.safe_load + ast.parse both pass.
Window management (8 commands) — uses the existing `keys` action with
pyautogui hotkey strings: winleft+d (show_desktop), winleft+up/down
(maximize/minimize), winleft+left/right (snap left/right), alt+f4
(close_window), winleft+shift+m (restore_all), winleft+tab (task_view).
No new action type needed.
Clipboard read aloud — new clipboard_read action. Shells to
PowerShell Get-Clipboard, caps preview at 400 chars, speaks via the
existing tts.va_speak (which already handles recorder.stop/start).
Notes — new notes_add + notes_open actions. notes_add strips the
trigger phrase, appends "[YYYY-MM-DD HH:MM] body\n" to
%USERPROFILE%\Documents\jarvis-notes.txt, creates the dir if needed.
notes_open shells "cmd /c start" so .txt opens in the user-default
editor.
Process killer — new process_kill action. Strips the trigger ("убей
процесс"/"закрой программу"/etc.), looks up the remainder in the same
alias map as the rust version (хром→chrome, телега→telegram, спотифай
→spotify, едж/эдж/edge→msedge, дискорд→discord, стим→steam, обс→obs64,
проводник/блокнот/калькулятор/диспетчер задач, vs code→code, etc.),
appends .exe if needed, runs taskkill /F /IM, speaks success or
"не нашёл процесс N".
Web search — new websearch action with a `service` param. Bases:
google → https://www.google.com/search?q=
youtube → https://www.youtube.com/results?search_query=
wiki → https://ru.wikipedia.org/wiki/Special:Search?search=
yandex → https://yandex.ru/search/?text=
Strips trigger, urllib.parse.quote-s the rest, opens via webbrowser.open
(respects the user'\''s default browser; config.BROWSER_PATHS only kicks in
for the older url-action commands).
Portability: USERPROFILE used for notes path, no other hardcoded paths.
This commit is contained in:
parent
80a685230c
commit
7d83bf8299
2 changed files with 256 additions and 0 deletions
130
commands.yaml
130
commands.yaml
|
|
@ -277,6 +277,136 @@ read_screen:
|
|||
- сними текст с экрана
|
||||
action: {type: ocr_screen}
|
||||
|
||||
# Window management via pyautogui hotkeys
|
||||
show_desktop:
|
||||
phrases:
|
||||
- покажи рабочий стол
|
||||
- свернуть всё
|
||||
- сверни все окна
|
||||
- покажи десктоп
|
||||
action: {type: keys, sequence: 'winleft+d'}
|
||||
|
||||
maximize_window:
|
||||
phrases:
|
||||
- разверни окно
|
||||
- развернуть окно
|
||||
- на весь экран
|
||||
- раскрой окно
|
||||
action: {type: keys, sequence: 'winleft+up'}
|
||||
|
||||
minimize_window:
|
||||
phrases:
|
||||
- сверни окно
|
||||
- свернуть окно
|
||||
- убери окно
|
||||
action: {type: keys, sequence: 'winleft+down'}
|
||||
|
||||
snap_left:
|
||||
phrases:
|
||||
- окно влево
|
||||
- сдвинь влево
|
||||
- прижми влево
|
||||
action: {type: keys, sequence: 'winleft+left'}
|
||||
|
||||
snap_right:
|
||||
phrases:
|
||||
- окно вправо
|
||||
- сдвинь вправо
|
||||
- прижми вправо
|
||||
action: {type: keys, sequence: 'winleft+right'}
|
||||
|
||||
close_window:
|
||||
phrases:
|
||||
- закрой окно
|
||||
- закрой вкладку
|
||||
- закрой текущее
|
||||
action: {type: keys, sequence: 'alt+f4'}
|
||||
|
||||
restore_all:
|
||||
phrases:
|
||||
- восстанови окна
|
||||
- верни окна
|
||||
- разверни окна
|
||||
action: {type: keys, sequence: 'winleft+shift+m'}
|
||||
|
||||
task_view:
|
||||
phrases:
|
||||
- открой все окна
|
||||
- обзор окон
|
||||
- таск вью
|
||||
- покажи все окна
|
||||
action: {type: keys, sequence: 'winleft+tab'}
|
||||
|
||||
# Clipboard read aloud
|
||||
read_clipboard:
|
||||
phrases:
|
||||
- прочитай буфер
|
||||
- что в буфере
|
||||
- прочитай из буфера
|
||||
- озвучь буфер
|
||||
action: {type: clipboard_read}
|
||||
|
||||
# Notes
|
||||
add_note:
|
||||
phrases:
|
||||
- запиши
|
||||
- запиши заметку
|
||||
- запиши идею
|
||||
- запомни
|
||||
- запомни идею
|
||||
- сделай заметку
|
||||
action: {type: notes_add}
|
||||
|
||||
open_notes:
|
||||
phrases:
|
||||
- покажи заметки
|
||||
- открой заметки
|
||||
- что я записал
|
||||
action: {type: notes_open}
|
||||
|
||||
# Process killer
|
||||
kill_process:
|
||||
phrases:
|
||||
- убей процесс
|
||||
- закрой программу
|
||||
- завершить процесс
|
||||
- выруби процесс
|
||||
action: {type: process_kill}
|
||||
|
||||
# Web search shortcuts
|
||||
search_google:
|
||||
phrases:
|
||||
- загугли
|
||||
- поиск в гугл
|
||||
- поищи в гугл
|
||||
- найди в гугл
|
||||
- ищи в гугл
|
||||
action: {type: websearch, service: google}
|
||||
|
||||
search_youtube:
|
||||
phrases:
|
||||
- найди на ютубе
|
||||
- поиск на ютубе
|
||||
- ищи на ютубе
|
||||
- ютуб поиск
|
||||
action: {type: websearch, service: youtube}
|
||||
|
||||
search_wiki:
|
||||
phrases:
|
||||
- найди в википедии
|
||||
- поиск в википедии
|
||||
- википедия
|
||||
- вики поиск
|
||||
action: {type: websearch, service: wiki}
|
||||
|
||||
search_yandex:
|
||||
phrases:
|
||||
- поищи в яндекс
|
||||
- найди в яндекс
|
||||
- яндекс поиск
|
||||
- пробей в яндекс
|
||||
action: {type: websearch, service: yandex}
|
||||
|
||||
thanks:
|
||||
phrases:
|
||||
- спасибо
|
||||
|
|
|
|||
126
main.py
126
main.py
|
|
@ -395,6 +395,122 @@ def do_file_search(action, voice: str):
|
|||
_speak(f"Найдено {len(lines)}, открываю первое: {os.path.basename(first)}.")
|
||||
|
||||
|
||||
_NOTES_TRIGGERS = (
|
||||
'запиши заметку', 'запиши идею', 'сделай заметку', 'запомни идею',
|
||||
'запиши', 'запомни',
|
||||
)
|
||||
|
||||
_PROCESS_KILL_TRIGGERS = (
|
||||
'завершить процесс', 'выруби процесс', 'убей процесс', 'kill процесс',
|
||||
'закрой программу',
|
||||
)
|
||||
|
||||
_WEBSEARCH_TRIGGERS = (
|
||||
'загугли', 'поиск в гугл', 'поищи в гугл', 'найди в гугл', 'ищи в гугл',
|
||||
'найди на ютубе', 'поиск на ютубе', 'ищи на ютубе', 'ютуб поиск',
|
||||
'найди в википедии', 'поиск в википедии', 'википедия', 'вики поиск',
|
||||
'поищи в яндекс', 'найди в яндекс', 'яндекс поиск', 'пробей в яндекс',
|
||||
)
|
||||
|
||||
_PROCESS_ALIASES = {
|
||||
'хром': 'chrome', 'хрома': 'chrome',
|
||||
'спотифай': 'spotify',
|
||||
'едж': 'msedge', 'эдж': 'msedge', 'edge': 'msedge',
|
||||
'файрфокс': 'firefox', 'фаерфокс': 'firefox',
|
||||
'вскод': 'code', 'vs code': 'code',
|
||||
'дискорд': 'discord',
|
||||
'телега': 'telegram', 'телеграм': 'telegram',
|
||||
'стим': 'steam',
|
||||
'обс': 'obs64',
|
||||
'проводник': 'explorer',
|
||||
'блокнот': 'notepad',
|
||||
'калькулятор': 'calculatorapp',
|
||||
'диспетчер задач': 'taskmgr',
|
||||
}
|
||||
|
||||
_WEBSEARCH_BASES = {
|
||||
'google': 'https://www.google.com/search?q=',
|
||||
'youtube': 'https://www.youtube.com/results?search_query=',
|
||||
'wiki': 'https://ru.wikipedia.org/wiki/Special:Search?search=',
|
||||
'yandex': 'https://yandex.ru/search/?text=',
|
||||
}
|
||||
|
||||
|
||||
def do_clipboard_read(action, voice: str):
|
||||
res = subprocess.run(
|
||||
['powershell', '-NoProfile', '-Command', 'Get-Clipboard'],
|
||||
capture_output=True, text=True, encoding='utf-8', timeout=10,
|
||||
)
|
||||
text = (res.stdout or '').strip()
|
||||
if not text:
|
||||
_speak("Буфер пуст.")
|
||||
return
|
||||
to_speak = text[:400]
|
||||
if len(text) > 400:
|
||||
to_speak += " ... и так далее"
|
||||
_speak(to_speak.replace('\r', ' ').replace('\n', ' '))
|
||||
|
||||
|
||||
def do_notes_add(action, voice: str):
|
||||
body = _strip_trigger(voice, _NOTES_TRIGGERS).lstrip(' ,.:').strip()
|
||||
if not body:
|
||||
_speak("Что записать, сэр?")
|
||||
return
|
||||
user_profile = os.environ.get('USERPROFILE', os.path.expanduser('~'))
|
||||
notes_path = os.path.join(user_profile, 'Documents', 'jarvis-notes.txt')
|
||||
stamp = datetime.datetime.now().strftime('%Y-%m-%d %H:%M')
|
||||
line = f"[{stamp}] {body}\n"
|
||||
try:
|
||||
os.makedirs(os.path.dirname(notes_path), exist_ok=True)
|
||||
with open(notes_path, 'a', encoding='utf-8') as f:
|
||||
f.write(line)
|
||||
except OSError as e:
|
||||
print(f"notes append failed: {e}")
|
||||
_speak("Не удалось записать.")
|
||||
return
|
||||
_speak(f"Записал: {body[:120]}")
|
||||
|
||||
|
||||
def do_notes_open(action, voice: str):
|
||||
user_profile = os.environ.get('USERPROFILE', os.path.expanduser('~'))
|
||||
notes_path = os.path.join(user_profile, 'Documents', 'jarvis-notes.txt')
|
||||
if not os.path.isfile(notes_path):
|
||||
_speak("Заметок пока нет.")
|
||||
return
|
||||
subprocess.Popen(['cmd', '/c', 'start', '', notes_path], shell=False)
|
||||
_speak("Открываю заметки.")
|
||||
|
||||
|
||||
def do_process_kill(action, voice: str):
|
||||
name = _strip_trigger(voice, _PROCESS_KILL_TRIGGERS).strip().lower()
|
||||
target = _PROCESS_ALIASES.get(name, name)
|
||||
if not target:
|
||||
_speak("Какой процесс?")
|
||||
return
|
||||
exe = target if target.endswith('.exe') else target + '.exe'
|
||||
res = subprocess.run(
|
||||
['taskkill', '/F', '/IM', exe],
|
||||
capture_output=True, text=True, timeout=10,
|
||||
)
|
||||
if res.returncode == 0:
|
||||
_speak(f"Закрыл {exe}.")
|
||||
else:
|
||||
_speak(f"Не нашёл процесс {exe}.")
|
||||
|
||||
|
||||
def do_websearch(action, voice: str):
|
||||
import urllib.parse
|
||||
svc = action.get('service', 'google')
|
||||
base = _WEBSEARCH_BASES.get(svc, _WEBSEARCH_BASES['google'])
|
||||
query = _strip_trigger(voice, _WEBSEARCH_TRIGGERS).strip()
|
||||
if not query:
|
||||
_speak(f"Что искать в {svc}?")
|
||||
return
|
||||
url = base + urllib.parse.quote(query)
|
||||
webbrowser.open(url)
|
||||
_speak(f"Открываю {svc}: {query[:100]}")
|
||||
|
||||
|
||||
def do_audio_devices_panel(action, voice: str):
|
||||
# Full programmatic switching needs IPolicyConfig + IMMDeviceEnumerator
|
||||
# via comtypes; out of scope for this pass. Opens Sound control panel so
|
||||
|
|
@ -489,6 +605,16 @@ def run_action(action):
|
|||
do_file_search(action, _current_voice or '')
|
||||
elif t == 'audio_devices_panel':
|
||||
do_audio_devices_panel(action, _current_voice or '')
|
||||
elif t == 'clipboard_read':
|
||||
do_clipboard_read(action, _current_voice or '')
|
||||
elif t == 'notes_add':
|
||||
do_notes_add(action, _current_voice or '')
|
||||
elif t == 'notes_open':
|
||||
do_notes_open(action, _current_voice or '')
|
||||
elif t == 'process_kill':
|
||||
do_process_kill(action, _current_voice or '')
|
||||
elif t == 'websearch':
|
||||
do_websearch(action, _current_voice or '')
|
||||
|
||||
|
||||
_current_voice: str = ''
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue