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}
|
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:
|
thanks:
|
||||||
phrases:
|
phrases:
|
||||||
- спасибо
|
- спасибо
|
||||||
|
|
|
||||||
172
main.py
172
main.py
|
|
@ -1193,6 +1193,166 @@ def do_random_choice(action, voice: str):
|
||||||
_speak(f"Я выбираю {random.choice(options)}.")
|
_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):
|
def do_stopwatch(action, voice: str):
|
||||||
op = action.get('op', 'check')
|
op = action.get('op', 'check')
|
||||||
state = _stopwatch_state_path()
|
state = _stopwatch_state_path()
|
||||||
|
|
@ -1365,6 +1525,18 @@ def run_action(action):
|
||||||
do_mouse(action, _current_voice or '')
|
do_mouse(action, _current_voice or '')
|
||||||
elif t == 'random_choice':
|
elif t == 'random_choice':
|
||||||
do_random_choice(action, _current_voice or '')
|
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 = ''
|
_current_voice: str = ''
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue