feat: parity — brightness, window switch, spelling, network, summarize (100 commands)
Mirrors the rust tier-3 batch. 90 → 100 commands (round number — feels
right for an "имба" milestone).
main.py:
+ do_brightness: WMI WmiMonitorBrightness read + WmiSetBrightness write,
±20% step or 100/10 absolute, "no_wmi" sentinel for desktops.
+ do_window_switch: same alias map as process_kill (xrom→chrome/...),
PS finds process by name OR window-title substring, picks newest
StartTime, AppActivate via WScript.Shell.
+ do_spell_out: split chars, join with ". ", _speak.
+ do_network_settings: action.section = wifi|bluetooth|network →
matching ms-settings:* URI via cmd /c start "".
+ do_my_ip: Get-NetIPAddress filtered to non-loopback non-APIPA IPv4,
WAN via Invoke-RestMethod ipify, speaks "Локальный X. Внешний Y".
+ do_summarize_selection: pyautogui.hotkey(ctrl, c) → 250ms wait →
Get-Clipboard → Groq @ temp 0.3 ("3-5 sentence retell") → speak +
clipboard.
commands.yaml +10 entries: brightness_up/down/max/min, switch_to_window,
spell_out, open_wifi_settings, open_bluetooth_settings, my_ip,
summarize_selection.
Backlog: closed all "Tier-3 still TODO" pure-Lua items. Remaining
TODOs (pomodoro, currency-math, stocks, translate-clipboard, daily
briefing, etc.) are even smaller LOC and can roll in the next batch.
This commit is contained in:
parent
b3a5bcabe5
commit
ef8da9cba8
2 changed files with 249 additions and 0 deletions
|
|
@ -740,6 +740,83 @@ random_choice:
|
|||
- выбрать
|
||||
action: {type: random_choice}
|
||||
|
||||
brightness_up:
|
||||
phrases:
|
||||
- ярче
|
||||
- сделай ярче
|
||||
- увеличь яркость
|
||||
- поярче
|
||||
action: {type: brightness, op: up}
|
||||
|
||||
brightness_down:
|
||||
phrases:
|
||||
- темнее
|
||||
- сделай темнее
|
||||
- уменьши яркость
|
||||
- потемнее
|
||||
action: {type: brightness, op: down}
|
||||
|
||||
brightness_max:
|
||||
phrases:
|
||||
- максимальная яркость
|
||||
- яркость на максимум
|
||||
action: {type: brightness, op: max}
|
||||
|
||||
brightness_min:
|
||||
phrases:
|
||||
- минимальная яркость
|
||||
- яркость на минимум
|
||||
action: {type: brightness, op: min}
|
||||
|
||||
switch_to_window:
|
||||
phrases:
|
||||
- переключись на
|
||||
- переключи на
|
||||
- активируй окно
|
||||
- покажи окно
|
||||
- перейди в
|
||||
action: {type: window_switch}
|
||||
|
||||
spell_out:
|
||||
phrases:
|
||||
- произнеси по буквам
|
||||
- продиктуй по буквам
|
||||
- произнеси буквы
|
||||
- по буквам
|
||||
action: {type: spell_out}
|
||||
|
||||
open_wifi_settings:
|
||||
phrases:
|
||||
- настройки wifi
|
||||
- настройки вайфай
|
||||
- wifi настройки
|
||||
- сеть
|
||||
action: {type: network_settings, section: wifi}
|
||||
|
||||
open_bluetooth_settings:
|
||||
phrases:
|
||||
- настройки блютуз
|
||||
- блютуз
|
||||
action: {type: network_settings, section: bluetooth}
|
||||
|
||||
my_ip:
|
||||
phrases:
|
||||
- мой ай пи
|
||||
- мой ip
|
||||
- какой у меня айпи
|
||||
- айпишник
|
||||
action: {type: my_ip}
|
||||
|
||||
summarize_selection:
|
||||
phrases:
|
||||
- суммируй
|
||||
- перескажи выделенное
|
||||
- кратко перескажи
|
||||
- о чём это
|
||||
- что выделил
|
||||
- перескажи это
|
||||
action: {type: summarize_selection}
|
||||
|
||||
thanks:
|
||||
phrases:
|
||||
- спасибо
|
||||
|
|
|
|||
172
main.py
172
main.py
|
|
@ -1193,6 +1193,166 @@ def do_random_choice(action, voice: str):
|
|||
_speak(f"Я выбираю {random.choice(options)}.")
|
||||
|
||||
|
||||
_BRIGHTNESS_PS = (
|
||||
"$ErrorActionPreference='SilentlyContinue'; "
|
||||
"try { $cur = (Get-WmiObject -Namespace root\\WMI -Class WmiMonitorBrightness -EA Stop).CurrentBrightness } "
|
||||
"catch { Write-Output 'no_wmi'; exit 0 } "
|
||||
"$target = switch ('__OP__') { 'up'{[Math]::Min(100,$cur+20)} 'down'{[Math]::Max(0,$cur-20)} 'max'{100} 'min'{10} default{$cur} } "
|
||||
"(Get-WmiObject -Namespace root\\WMI -Class WmiMonitorBrightnessMethods).WmiSetBrightness(1,$target) | Out-Null; "
|
||||
"Write-Output $target"
|
||||
)
|
||||
|
||||
|
||||
def do_brightness(action, voice: str):
|
||||
op = action.get('op', 'up')
|
||||
ps = _BRIGHTNESS_PS.replace('__OP__', op)
|
||||
res = subprocess.run(
|
||||
['powershell', '-NoProfile', '-Command', ps],
|
||||
capture_output=True, text=True, encoding='utf-8', timeout=10,
|
||||
)
|
||||
out = (res.stdout or '').strip()
|
||||
if out == 'no_wmi' or not out:
|
||||
_speak("Не поддерживается на этом дисплее.")
|
||||
return
|
||||
_speak(f"Яркость {out} процентов.")
|
||||
|
||||
|
||||
_WINDOW_ALIASES = {
|
||||
'хром': 'chrome', 'хрома': 'chrome', 'едж': 'msedge', 'эдж': 'msedge',
|
||||
'файрфокс': 'firefox', 'фаерфокс': 'firefox',
|
||||
'вскод': 'code', 'vs code': 'code',
|
||||
'дискорд': 'discord', 'телега': 'telegram', 'телеграм': 'telegram',
|
||||
'стим': 'steam', 'обс': 'obs64',
|
||||
'проводник': 'explorer', 'блокнот': 'notepad',
|
||||
'терминал': 'windowsterminal', 'спотифай': 'spotify',
|
||||
'яндекс': 'browser', 'браузер': 'browser',
|
||||
}
|
||||
_WINDOW_TRIGGERS = ('переключись на', 'переключи на', 'активируй окно',
|
||||
'покажи окно', 'перейди в',
|
||||
'switch to', 'activate window', 'focus window',
|
||||
'перемкни на')
|
||||
|
||||
|
||||
def do_window_switch(action, voice: str):
|
||||
query = _strip_trigger(voice, _WINDOW_TRIGGERS).strip().lower()
|
||||
if not query:
|
||||
_speak("Какое окно?")
|
||||
return
|
||||
target = _WINDOW_ALIASES.get(query, query).replace("'", "")
|
||||
ps = (
|
||||
f"$procs = Get-Process | Where-Object {{ $_.MainWindowTitle -and "
|
||||
f"($_.ProcessName -like '*{target}*' -or $_.MainWindowTitle -like '*{target}*') }} "
|
||||
f"| Sort-Object -Property StartTime -Descending; "
|
||||
f"if ($procs.Count -eq 0) {{ Write-Output 'NOT_FOUND'; exit 0 }}; "
|
||||
f"$p = $procs[0]; "
|
||||
f"$wsh = New-Object -ComObject WScript.Shell; "
|
||||
f"$null = $wsh.AppActivate($p.Id); "
|
||||
f"Write-Output $p.MainWindowTitle"
|
||||
)
|
||||
res = subprocess.run(['powershell', '-NoProfile', '-Command', ps],
|
||||
capture_output=True, text=True, encoding='utf-8', timeout=10)
|
||||
out = (res.stdout or '').strip()
|
||||
if out == 'NOT_FOUND' or not out:
|
||||
_speak(f"Не нашёл окно {query}.")
|
||||
return
|
||||
_speak(f"Переключаюсь на {out[:60]}.")
|
||||
|
||||
|
||||
_SPELLING_TRIGGERS = ('продиктуй по буквам', 'произнеси по буквам',
|
||||
'произнеси буквы', 'по буквам',
|
||||
'spell out', 'spell it', 'вимов по буквах')
|
||||
|
||||
|
||||
def do_spell_out(action, voice: str):
|
||||
word = _strip_trigger(voice, _SPELLING_TRIGGERS).strip()
|
||||
if not word:
|
||||
_speak("Что произнести?")
|
||||
return
|
||||
letters = [ch for ch in word if not ch.isspace()]
|
||||
if not letters:
|
||||
return
|
||||
_speak('. '.join(letters) + '.')
|
||||
|
||||
|
||||
_NETWORK_URIS = {
|
||||
'wifi': 'ms-settings:network-wifi',
|
||||
'bluetooth': 'ms-settings:bluetooth',
|
||||
'network': 'ms-settings:network',
|
||||
}
|
||||
|
||||
|
||||
def do_network_settings(action, voice: str):
|
||||
section = action.get('section', 'network')
|
||||
uri = _NETWORK_URIS.get(section, _NETWORK_URIS['network'])
|
||||
subprocess.Popen(['cmd', '/c', 'start', '', uri], shell=False)
|
||||
label = {'wifi': 'Wi-Fi', 'bluetooth': 'Bluetooth', 'network': 'сети'}.get(section, 'сети')
|
||||
_speak(f"Открываю настройки {label}.")
|
||||
|
||||
|
||||
def do_my_ip(action, voice: str):
|
||||
ps = (
|
||||
"$lan = (Get-NetIPAddress -AddressFamily IPv4 -EA SilentlyContinue | "
|
||||
"Where-Object { $_.PrefixOrigin -in 'Dhcp','Manual' } | "
|
||||
"Where-Object { $_.IPAddress -notmatch '^127\\.' -and $_.IPAddress -notmatch '^169\\.254\\.' } | "
|
||||
"Select-Object -First 1).IPAddress; "
|
||||
"if (-not $lan) { $lan = '(none)' }; "
|
||||
"$wan = try { (Invoke-RestMethod -Uri 'https://api.ipify.org?format=json' -TimeoutSec 5).ip } catch { 'unavailable' }; "
|
||||
"Write-Output ('LAN ' + $lan + ' WAN ' + $wan)"
|
||||
)
|
||||
res = subprocess.run(['powershell', '-NoProfile', '-Command', ps],
|
||||
capture_output=True, text=True, encoding='utf-8', timeout=15)
|
||||
out = (res.stdout or '').strip()
|
||||
if not out:
|
||||
_speak("Не получил IP.")
|
||||
return
|
||||
say = out.replace('LAN', 'Локальный').replace('WAN', '. Внешний')
|
||||
_speak(say)
|
||||
|
||||
|
||||
_SUMMARIZE_TRIGGERS = ('суммируй', 'перескажи выделенное', 'кратко перескажи',
|
||||
'о чём это', 'что выделил', 'перескажи это',
|
||||
'summarise this', 'summarize selection', 'tldr',
|
||||
'перекажи це', 'стисло перекажи')
|
||||
|
||||
|
||||
def do_summarize_selection(action, voice: str):
|
||||
if not config.GROQ_TOKEN:
|
||||
_speak("Groq токен не задан.")
|
||||
return
|
||||
try:
|
||||
import pyautogui
|
||||
pyautogui.hotkey('ctrl', 'c')
|
||||
except Exception as e:
|
||||
print(f"summarize copy failed: {e}")
|
||||
_speak("Не удалось скопировать выделение.")
|
||||
return
|
||||
time.sleep(0.25)
|
||||
res = subprocess.run(['powershell', '-NoProfile', '-Command', 'Get-Clipboard'],
|
||||
capture_output=True, text=True, encoding='utf-8', timeout=10)
|
||||
text = (res.stdout or '').strip()
|
||||
if len(text) < 20:
|
||||
_speak("Сначала выдели текст и повтори.")
|
||||
return
|
||||
if len(text) > 8000:
|
||||
text = text[:8000] + " ..."
|
||||
sys_prompt = ("Перескажи следующий текст одним абзацем (3-5 предложений). "
|
||||
"Сохрани ключевые факты и имена, без 'вот суммарно'.")
|
||||
try:
|
||||
resp = client.chat.completions.create(
|
||||
model=config.GROQ_MODEL,
|
||||
messages=[{'role': 'system', 'content': sys_prompt},
|
||||
{'role': 'user', 'content': text}],
|
||||
max_tokens=400, temperature=0.3,
|
||||
)
|
||||
summary = resp.choices[0].message.content.strip()
|
||||
except Exception as e:
|
||||
print(f"summarize LLM failed: {e}")
|
||||
_speak("LLM не ответила.")
|
||||
return
|
||||
_set_clipboard(summary)
|
||||
_speak(summary)
|
||||
|
||||
|
||||
def do_stopwatch(action, voice: str):
|
||||
op = action.get('op', 'check')
|
||||
state = _stopwatch_state_path()
|
||||
|
|
@ -1365,6 +1525,18 @@ def run_action(action):
|
|||
do_mouse(action, _current_voice or '')
|
||||
elif t == 'random_choice':
|
||||
do_random_choice(action, _current_voice or '')
|
||||
elif t == 'brightness':
|
||||
do_brightness(action, _current_voice or '')
|
||||
elif t == 'window_switch':
|
||||
do_window_switch(action, _current_voice or '')
|
||||
elif t == 'spell_out':
|
||||
do_spell_out(action, _current_voice or '')
|
||||
elif t == 'network_settings':
|
||||
do_network_settings(action, _current_voice or '')
|
||||
elif t == 'my_ip':
|
||||
do_my_ip(action, _current_voice or '')
|
||||
elif t == 'summarize_selection':
|
||||
do_summarize_selection(action, _current_voice or '')
|
||||
|
||||
|
||||
_current_voice: str = ''
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue