J.A.R.V.I.S-rust/resources/commands/codegen/codegen.lua

100 lines
3.6 KiB
Lua
Raw Normal View History

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
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 }