arch + features: jarvis.text API + brightness/window_switch/spelling/network/summarize (41 packs)
Architecture — close the trigger-strip duplication:
lua/api/text.rs (new):
+ jarvis.text.strip_trigger(phrase, triggers) — case-insensitive
"longest trigger wins" stripping, returns trimmed remainder. The
11 packs that previously inlined a `for _, t in ipairs(triggers) do
string.find ... s == 1 then sub` loop can drop to one call.
Demonstrated in spelling/, window_switch/, summarize/.
+ jarvis.text.contains_any(phrase, needles) — boolean check for any
of N substrings, case-insensitive. Useful for keyword routing.
5 new portable Lua packs (sandbox=full). 36 → 41 total. cargo test
commands::tests still 3/3.
brightness/ — brightness_up / down / max / min. WMI WmiMonitorBrightness
(read CurrentBrightness) + WmiMonitorBrightnessMethods.WmiSetBrightness
(write). ±20% step for up/down; max=100, min=10. Desktop monitors that
don'\''t expose the WMI namespace get a friendly "не поддерживается"
toast instead of a silent failure.
window_switch/ — switch_to_window. Strip trigger ("переключись на" /
"switch to" / etc.), apply the same alias map as process_kill
(хром→chrome, телега→telegram, ...), then PowerShell finds processes
by name OR window-title substring sorted by StartTime desc and uses
(New-Object -ComObject WScript.Shell).AppActivate($pid) on the top
match. Speaks the window title that got focus.
spelling/ — spell_out. Strips "произнеси по буквам" / "spell out" /
etc., iterates `word:gmatch(".")`, joins letters with ". " so SAPI
gives each one a natural micro-pause. Uses jarvis.text.strip_trigger
+ jarvis.speak — 24 lines total.
network/ — open_wifi_settings (ms-settings:network-wifi) +
open_bluetooth_settings (ms-settings:bluetooth) + my_ip. my_ip pulls
the first non-loopback, non-APIPA IPv4 with Get-NetIPAddress and
fetches WAN IP from api.ipify.org (5s timeout); speaks "Локальный X.
Внешний Y".
summarize/ — summarize_selection. Synthesises Ctrl+C through user32!
keybd_event (so the focused window copies its selection), waits
220 ms for the clipboard to populate, reads via
jarvis.system.clipboard.get(), bails if < 20 chars, truncates at
8000, sends to Groq @ temp 0.3 with a "3-5 sentences, keep names"
prompt, drops the summary back into the clipboard and speaks it.
Lets you select ANY text in ANY app (browser, PDF, IDE) and say
"суммируй" to get a spoken TL;DR.
The new packs explicitly use the new jarvis.text + jarvis.llm +
jarvis.speak API surface — no inline PS-SAPI boilerplate, no inline
Groq plumbing. Code is now intent-first.
This commit is contained in:
parent
a16d2401e7
commit
a7c002c9d4
14 changed files with 432 additions and 1 deletions
|
|
@ -6,4 +6,5 @@ pub mod fs;
|
|||
pub mod state;
|
||||
pub mod system;
|
||||
pub mod tts;
|
||||
pub mod llm;
|
||||
pub mod llm;
|
||||
pub mod text;
|
||||
65
crates/jarvis-core/src/lua/api/text.rs
Normal file
65
crates/jarvis-core/src/lua/api/text.rs
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
use mlua::{Lua, Table, Value};
|
||||
|
||||
// Tiny text helpers that every trigger-driven pack reinvents. Centralising
|
||||
// here removes ~10 lines of identical boilerplate per pack.
|
||||
pub fn register(lua: &Lua, jarvis: &Table) -> mlua::Result<()> {
|
||||
let text = lua.create_table()?;
|
||||
|
||||
// jarvis.text.strip_trigger(phrase, triggers) -> remainder
|
||||
//
|
||||
// Lowercases `phrase`, finds the FIRST trigger from `triggers` that
|
||||
// matches at the start (case-insensitive substring), and returns
|
||||
// everything after it trimmed. If nothing matches, returns the original
|
||||
// phrase trimmed. Empty input returns "".
|
||||
//
|
||||
// Triggers should be ordered longest-first so "напомни мне через" wins
|
||||
// over "напомни" — the function does NOT re-sort for the caller.
|
||||
let strip_fn = lua.create_function(|lua, (phrase, triggers): (String, Table)| {
|
||||
let lowered = phrase.to_lowercase();
|
||||
let mut chosen_end: Option<usize> = None;
|
||||
|
||||
for v in triggers.sequence_values::<Value>() {
|
||||
let t = match v {
|
||||
Ok(Value::String(s)) => s.to_str()?.to_lowercase(),
|
||||
_ => continue,
|
||||
};
|
||||
if t.is_empty() {
|
||||
continue;
|
||||
}
|
||||
if lowered.starts_with(&t) {
|
||||
let after = lowered[t.len()..].chars().next();
|
||||
if after.map_or(true, |c| !c.is_alphanumeric()) {
|
||||
chosen_end = Some(t.len());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let rest = match chosen_end {
|
||||
Some(end) => &phrase[end..],
|
||||
None => phrase.as_str(),
|
||||
};
|
||||
Ok(lua.create_string(rest.trim())?)
|
||||
})?;
|
||||
text.set("strip_trigger", strip_fn)?;
|
||||
|
||||
// jarvis.text.contains_any(phrase, needles) -> boolean
|
||||
// case-insensitive "does any needle appear anywhere in the phrase".
|
||||
let contains_fn = lua.create_function(|_, (phrase, needles): (String, Table)| {
|
||||
let lowered = phrase.to_lowercase();
|
||||
for v in needles.sequence_values::<Value>() {
|
||||
let n = match v {
|
||||
Ok(Value::String(s)) => s.to_str()?.to_lowercase(),
|
||||
_ => continue,
|
||||
};
|
||||
if !n.is_empty() && lowered.contains(&n) {
|
||||
return Ok(true);
|
||||
}
|
||||
}
|
||||
Ok(false)
|
||||
})?;
|
||||
text.set("contains_any", contains_fn)?;
|
||||
|
||||
jarvis.set("text", text)?;
|
||||
Ok(())
|
||||
}
|
||||
|
|
@ -77,6 +77,7 @@ impl LuaEngine {
|
|||
api::audio::register(&self.lua, &jarvis)?;
|
||||
api::context::register(&self.lua, &jarvis, context)?;
|
||||
api::tts::register(&self.lua, &jarvis)?;
|
||||
api::text::register(&self.lua, &jarvis)?;
|
||||
|
||||
// sandbox-controlled APIs
|
||||
if self.sandbox.allows_http() {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue