J.A.R.V.I.S-rust/resources/commands/codegen/codegen.lua
Bossiara13 69a38b0d89 feat: LLM auto-fallback + codegen pack + OCR pack
LLM auto-fallback (jarvis-app/src/app.rs + jarvis-core/src/config.rs):
- When neither intent classifier nor levenshtein finds a command match,
  route the utterance straight to the LLM instead of just playing the
  "not found" sound. Triggers ("скажи X", "answer Y") still work and
  take precedence — they short-circuit before command lookup.
- Two new config knobs:
    LLM_AUTO_FALLBACK = true        — master toggle
    LLM_AUTO_FALLBACK_MIN_CHARS = 4 — suppress for very short utterances
                                       so background noise doesn't burn
                                       Groq quota
- Requires GROQ_TOKEN; if absent, behaviour is unchanged (play_not_found).

codegen/ command pack:
- Phrases: "напиши код X", "сгенерируй скрипт Y", "write code Z", etc.
- Strips the trigger from the recognized phrase, sends what remains to
  Groq with a strict system prompt ("return ONLY code, no fences, no
  commentary") at temperature 0.2.
- Parses the JSON content, unescapes \n / \" / \\ / \t, strips any
  remaining ```lang ... ``` fences, drops the result into the clipboard
  via jarvis.system.clipboard.set.
- Notifies a 120-char preview + plays ok-sound. Works for any language
  the model handles (Python by default if unspecified).
- GROQ_TOKEN / GROQ_MODEL / GROQ_BASE_URL read from env at call time —
  same envvars the voice-loop fallback already uses.

ocr/ command pack:
- Phrases: "прочитай экран", "что на экране", "read screen", etc.
- Captures the primary screen via System.Windows.Forms + System.Drawing
  to a temp PNG, then shells to tesseract.exe (-l rus+eng for ru/ua,
  -l eng for en).
- Resolves Tesseract by PATH first, then C:\Program Files\Tesseract-OCR
  and the x86 install dir; if none works the user gets a friendly
  "winget install UB-Mannheim.TesseractOCR" hint in a notification.
- Recognized text goes to clipboard + a 200-char preview notification.

All three features are portable (env-var resolution, no hardcoded user
paths). Command-pack total is now 11. cargo test -p jarvis-core --lib
commands::tests passes 3/3.
2026-05-15 01:39:21 +03:00

99 lines
3.6 KiB
Lua
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

local lang = jarvis.context.language
local phrase = (jarvis.context.phrase or ""):lower()
local triggers = {
"сгенерируй скрипт", "напиши скрипт", "сделай скрипт",
"сгенерируй код", "набросай код", "напиши код",
"generate code", "write code", "make script", "write script",
"згенеруй код", "напиши код", "зроби скрипт",
}
local request = phrase
for _, t in ipairs(triggers) do
local s, e = string.find(request, t, 1, true)
if s == 1 then
request = request:sub(e + 1)
break
end
end
request = request:gsub("^%s+", ""):gsub("%s+$", "")
if request == "" then
jarvis.system.notify(
lang == "ru" and "Codegen" or "Codegen",
lang == "ru" and "Что писать?" or "What to write?"
)
jarvis.audio.play_error()
return { chain = false }
end
local token = jarvis.system.env("GROQ_TOKEN")
if not token or token == "" then
jarvis.log("error", "GROQ_TOKEN not set")
jarvis.system.notify("Codegen", "GROQ_TOKEN не задан")
jarvis.audio.play_error()
return { chain = false }
end
local base = jarvis.system.env("GROQ_BASE_URL")
if not base or base == "" then base = "https://api.groq.com/openai/v1" end
local model = jarvis.system.env("GROQ_MODEL")
if not model or model == "" then model = "llama-3.3-70b-versatile" end
local system_prompt = lang == "ru"
and "Ты Senior Engineer. На запрос пользователя верни ТОЛЬКО исходный код, без обрамления ``` и без объяснений. Если язык не задан — выбери самый подходящий (по умолчанию Python). Не добавляй комментарии-болтовню."
or "You are a Senior Engineer. Reply with ONLY source code, no ``` fences, no commentary. Pick the most fitting language if unspecified (Python by default). Skip chatty comments."
jarvis.log("info", "codegen request: " .. request)
local payload = {
model = model,
messages = {
{ role = "system", content = system_prompt },
{ role = "user", content = request },
},
max_tokens = 1024,
temperature = 0.2,
}
local headers = { Authorization = "Bearer " .. token }
local res = jarvis.http.post_json(base .. "/chat/completions", payload, headers)
if not res.ok then
jarvis.log("error", "codegen API failed: status=" .. tostring(res.status) .. " body=" .. tostring(res.body):sub(1, 200))
jarvis.system.notify("Codegen", "Ошибка API: " .. tostring(res.status))
jarvis.audio.play_error()
return { chain = false }
end
local body = res.body or ""
local content = body:match('"content"%s*:%s*"(.-[^\\])"')
if not content then
jarvis.log("error", "codegen: could not parse content from " .. body:sub(1, 200))
jarvis.system.notify("Codegen", "Не смог распарсить ответ")
jarvis.audio.play_error()
return { chain = false }
end
content = content:gsub('\\n', '\n')
content = content:gsub('\\"', '"')
content = content:gsub('\\\\', '\\')
content = content:gsub('\\t', '\t')
content = content:gsub("^```[%w]*\n?", "")
content = content:gsub("\n?```%s*$", "")
content = content:gsub("^%s+", ""):gsub("%s+$", "")
jarvis.system.clipboard.set(content)
jarvis.log("info", "codegen result copied to clipboard (" .. #content .. " bytes)")
local preview = content:sub(1, 120)
if #content > 120 then preview = preview .. "..." end
jarvis.system.notify(
lang == "ru" and "Код в буфере" or "Code in clipboard",
preview
)
jarvis.audio.play_ok()
return { chain = false }