Files
2026-05-10 17:23:10 +03:00

583 lines
18 KiB
Python
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.
# actions/code_helper.py
# AI-powered code assistant — writes, edits, explains, runs, builds, debugs, and optimizes code.
#
# Actions:
# write → Describe what you want, Gemini writes it, saves to file
# edit → Read existing file, apply natural language change
# explain → Explain what a piece of code or file does
# run → Execute a script file, return output
# build → Write → Run → Fix loop (max 3 attempts), speaks when done
# screen_debug → Screenshot al, ekrandaki kodu/hatayı Gemini ile analiz et ve düzelt
# optimize → Mevcut kodu Gemini ile optimize et (performans, okunabilirlik, best practices)
# auto → (default) Intent auto-detected from context
import subprocess
import sys
import json
import re
import time
from pathlib import Path
def get_base_dir():
if getattr(sys, "frozen", False):
return Path(sys.executable).parent
return Path(__file__).resolve().parent.parent
BASE_DIR = get_base_dir()
API_CONFIG_PATH = BASE_DIR / "config" / "api_keys.json"
DESKTOP = Path.home() / "Desktop"
MAX_BUILD_ATTEMPTS = 3
GEMINI_MODEL = "gemini-2.5-flash"
def _get_api_key() -> str:
with open(API_CONFIG_PATH, "r", encoding="utf-8") as f:
return json.load(f)["gemini_api_key"]
def _get_gemini(model: str = GEMINI_MODEL):
import google.generativeai as genai
genai.configure(api_key=_get_api_key())
return genai.GenerativeModel(model)
def _clean_code(text: str) -> str:
text = text.strip()
text = re.sub(r"^```[a-zA-Z]*\n?", "", text)
text = re.sub(r"\n?```$", "", text)
return text.strip()
def _resolve_save_path(output_path: str, language: str) -> Path:
ext_map = {
"python": ".py", "py": ".py",
"javascript": ".js", "js": ".js",
"typescript": ".ts", "ts": ".ts",
"html": ".html", "css": ".css",
"java": ".java", "cpp": ".cpp", "c": ".c",
"bash": ".sh", "shell": ".sh", "powershell": ".ps1",
"sql": ".sql", "json": ".json", "rust": ".rs", "go": ".go",
}
if output_path:
p = Path(output_path)
return p if p.is_absolute() else DESKTOP / p
ext = ext_map.get((language or "python").lower(), ".py")
return DESKTOP / f"jarvis_code{ext}"
def _read_file(file_path: str) -> tuple[str, str]:
if not file_path:
return "", "No file path provided."
p = Path(file_path)
if not p.exists():
return "", f"File not found: {file_path}"
try:
return p.read_text(encoding="utf-8"), ""
except Exception as e:
return "", f"Could not read file: {e}"
def _save_file(path: Path, content: str) -> str:
try:
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(content, encoding="utf-8")
return f"Saved to: {path}"
except Exception as e:
return f"Could not save: {e}"
def _preview(code: str, lines: int = 10) -> str:
all_lines = code.splitlines()
preview = "\n".join(all_lines[:lines])
suffix = f"\n... ({len(all_lines) - lines} more lines)" if len(all_lines) > lines else ""
return preview + suffix
def _has_error(output: str) -> bool:
error_signals = ["error", "exception", "traceback", "syntaxerror",
"nameerror", "typeerror", "stderr", "failed", "crash"]
return any(s in output.lower() for s in error_signals)
def _take_screenshot() -> Path | None:
try:
import pyautogui
screenshot_path = Path.home() / "Desktop" / f"jarvis_debug_{int(time.time())}.png"
screenshot = pyautogui.screenshot()
screenshot.save(str(screenshot_path))
print(f"[Code] 📸 Screenshot: {screenshot_path}")
return screenshot_path
except Exception as e:
print(f"[Code] ⚠️ Screenshot failed: {e}")
return None
def _image_to_base64(path: Path) -> str:
import base64
return base64.b64encode(path.read_bytes()).decode("utf-8")
def _detect_intent(description: str, file_path: str, code: str) -> str:
desc = (description or "").lower()
screen_kw = ["ekrandaki", "screen", "ekranda", "bu hatayı", "why am i getting",
"neden hata", "what's wrong", "ne yanlış", "screenshot", "görüntü"]
if any(k in desc for k in screen_kw):
return "screen_debug"
# Optimize keywords
optimize_kw = ["optimize", "refactor", "clean up", "improve", "temizle",
"iyileştir", "daha iyi", "make it better", "hızlandır"]
if any(k in desc for k in optimize_kw) and (code or file_path):
return "optimize"
if file_path:
p = Path(file_path)
edit_kw = ["edit", "update", "modify", "change", "add", "remove",
"refactor", "fix", "rename", "replace", "düzenle", "değiştir"]
run_kw = ["run", "execute", "launch", "start", "çalıştır"]
build_kw = ["build", "make it work", "try", "attempt"]
if p.exists() and any(k in desc for k in edit_kw):
return "edit"
if p.exists() and any(k in desc for k in run_kw):
return "run"
if any(k in desc for k in build_kw):
return "build"
if p.exists():
return "explain"
explain_kw = ["explain", "what does", "describe", "analyze", "açıkla", "ne yapıyor"]
if any(k in desc for k in explain_kw) and (code or file_path):
return "explain"
build_kw = ["build", "make it work", "try and", "attempt"]
if any(k in desc for k in build_kw):
return "build"
return "write"
def _write(description: str, language: str, output_path: str, player=None) -> tuple[str, Path]:
lang = language or "python"
model = _get_gemini()
prompt = f"""You are an expert {lang} developer.
Write clean, working, well-commented {lang} code for the description below.
Rules:
- Output ONLY the code. No explanation, no markdown, no backticks.
- Add helpful inline comments.
- Handle errors and edge cases properly.
- Use modern best practices.
Description: {description}
Code:"""
response = model.generate_content(prompt)
code = _clean_code(response.text)
path = _resolve_save_path(output_path, lang)
_save_file(path, code)
return code, path
def _fix_code(code: str, error_output: str, description: str) -> str:
model = _get_gemini()
prompt = f"""You are an expert debugger.
The code below failed with the following error. Fix it.
Return ONLY the corrected code — no explanation, no markdown, no backticks.
Original goal: {description}
Error:
{error_output[:2000]}
Broken code:
{code}
Fixed code:"""
response = model.generate_content(prompt)
return _clean_code(response.text)
def _run_file(path: Path, args: list, timeout: int) -> str:
interpreters = {
".py": [sys.executable],
".js": ["node"],
".ts": ["ts-node"],
".sh": ["bash"],
".ps1": ["powershell", "-File"],
".rb": ["ruby"],
".php": ["php"],
}
interp = interpreters.get(path.suffix.lower())
if not interp:
return f"No interpreter for {path.suffix}."
try:
result = subprocess.run(
interp + [str(path)] + (args or []),
capture_output=True, text=True,
encoding="utf-8", errors="replace",
timeout=timeout, cwd=str(path.parent)
)
output = result.stdout.strip()
error = result.stderr.strip()
parts = []
if output: parts.append(f"Output:\n{output}")
if error: parts.append(f"Stderr:\n{error}")
return "\n\n".join(parts) if parts else "Executed with no output."
except subprocess.TimeoutExpired:
return f"Timed out after {timeout}s."
except FileNotFoundError:
return f"Interpreter not found: {interp[0]}."
except Exception as e:
return f"Execution error: {e}"
def _build(description, language, output_path, args, timeout, speak=None, player=None) -> str:
if not description:
return "Please describe what you want me to build, sir."
if player:
player.write_log("[Code] Build started...")
lang = language or "python"
try:
code, path = _write(description, lang, output_path, player)
print(f"[Code] ✅ Written: {path}")
except Exception as e:
msg = f"Could not write initial code: {e}"
if speak: speak(msg)
return msg
last_output = ""
for attempt in range(1, MAX_BUILD_ATTEMPTS + 1):
print(f"[Code] 🔄 Attempt {attempt}/{MAX_BUILD_ATTEMPTS}")
if player:
player.write_log(f"[Code] Attempt {attempt}...")
last_output = _run_file(path, args, timeout)
if not _has_error(last_output):
msg = (
f"Build complete, sir. "
f"The code is working after {attempt} attempt{'s' if attempt > 1 else ''}. "
f"Saved to {path}."
)
if speak: speak(msg)
return f"{msg}\n\nOutput:\n{last_output}"
print(f"[Code] ⚠️ Error on attempt {attempt}, fixing...")
if player:
player.write_log(f"[Code] Fixing (attempt {attempt})...")
try:
code = _fix_code(code, last_output, description)
_save_file(path, code)
except Exception as e:
msg = f"Could not fix code on attempt {attempt}: {e}"
if speak: speak(msg)
return msg
msg = (
f"I was unable to build a working version after {MAX_BUILD_ATTEMPTS} attempts, sir. "
f"The last error was: {last_output[:200]}"
)
if speak: speak(msg)
return f"{msg}\n\nLast code saved to: {path}"
def _write_action(description, language, output_path, player) -> str:
if not description:
return "Please describe what you want me to write, sir."
if player:
player.write_log("[Code] Writing code...")
try:
code, path = _write(description, language, output_path, player)
print(f"[Code] ✅ Written: {path}")
return f"Code written. Saved to: {path}\n\nPreview:\n{_preview(code)}"
except Exception as e:
return f"Could not generate code: {e}"
def _edit_action(file_path, instruction, player) -> str:
if not file_path:
return "Please provide a file path to edit, sir."
if not instruction:
return "Please describe what change to make, sir."
content, err = _read_file(file_path)
if err:
return err
if player:
player.write_log("[Code] Editing file...")
model = _get_gemini()
prompt = f"""You are an expert code editor.
Apply the following change to the code below.
Return ONLY the complete updated code — no explanation, no markdown, no backticks.
Change: {instruction}
Original code:
{content}
Updated code:"""
try:
response = model.generate_content(prompt)
edited = _clean_code(response.text)
except Exception as e:
return f"Could not edit code: {e}"
status = _save_file(Path(file_path), edited)
print(f"[Code] ✅ Edited: {file_path}")
return f"File edited. {status}\n\nPreview:\n{_preview(edited)}"
def _explain_action(file_path, code, player) -> str:
if file_path and not code:
code, err = _read_file(file_path)
if err:
return err
if not code:
return "Please provide code or a file path to explain, sir."
if player:
player.write_log("[Code] Analyzing code...")
model = _get_gemini()
prompt = f"""Explain what this code does in simple, clear language.
Focus on: what it does, how it works, and any important details.
Be concise — 3 to 6 sentences maximum.
Code:
{code[:4000]}
Explanation:"""
try:
response = model.generate_content(prompt)
return response.text.strip()
except Exception as e:
return f"Could not explain code: {e}"
def _run_action(file_path, args, timeout, player) -> str:
if not file_path:
return "Please provide a file path to run, sir."
p = Path(file_path)
if not p.exists():
return f"File not found: {file_path}"
if player:
player.write_log(f"[Code] Running {p.name}...")
return _run_file(p, args, timeout)
def _optimize_action(file_path, code, language, output_path, player) -> str:
if file_path and not code:
code, err = _read_file(file_path)
if err:
return err
if not code:
return "Please provide code or a file path to optimize, sir."
if player:
player.write_log("[Code] Optimizing code...")
lang = language or "python"
model = _get_gemini()
prompt = f"""You are an expert {lang} developer and code reviewer.
Optimize the following code for:
1. Performance — eliminate unnecessary operations, use efficient data structures
2. Readability — clear variable names, proper formatting, logical structure
3. Best practices — modern {lang} patterns, error handling, type hints if applicable
4. Remove dead code, redundant comments, and unnecessary complexity
Return ONLY the optimized code — no explanation, no markdown, no backticks.
Original code:
{code[:6000]}
Optimized code:"""
try:
response = model.generate_content(prompt)
optimized = _clean_code(response.text)
except Exception as e:
return f"Could not optimize code: {e}"
# Kaydet
if file_path:
save_path = Path(file_path)
else:
save_path = _resolve_save_path(output_path, lang)
status = _save_file(save_path, optimized)
print(f"[Code] ✅ Optimized: {save_path}")
original_lines = len(code.splitlines())
optimized_lines = len(optimized.splitlines())
diff = original_lines - optimized_lines
return (
f"Code optimized. {status}\n"
f"Lines: {original_lines} → {optimized_lines} "
f"({'−' if diff > 0 else '+'}{abs(diff)} lines)\n\n"
f"Preview:\n{_preview(optimized)}"
)
def _screen_debug_action(description, file_path, player, speak=None) -> str:
if player:
player.write_log("[Code] Taking screenshot for analysis...")
print("[Code] 📸 Capturing screen for debug...")
screenshot_path = _take_screenshot()
if not screenshot_path:
return "Could not take screenshot, sir. Please make sure PyAutoGUI is installed."
file_content = ""
if file_path:
file_content, err = _read_file(file_path)
if err:
print(f"[Code] ⚠️ Could not read file: {err}")
try:
from google import genai
from google.genai import types
client = genai.Client(api_key=_get_api_key())
image_bytes = screenshot_path.read_bytes()
image_base64 = _image_to_base64(screenshot_path)
user_question = description or "What error or problem do you see on the screen? How can it be fixed?"
context = ""
if file_content:
context = f"\n\nAdditionally, here is the related file content:\n```\n{file_content[:4000]}\n```"
analysis_prompt = f"""You are an expert programmer and debugger analyzing a screenshot.
User's question: {user_question}{context}
Please:
1. Identify any errors, exceptions, or problems visible on the screen
2. Explain what is causing the problem in simple terms
3. Provide a concrete fix or solution
4. If there's code visible, show the corrected version
Be specific and actionable. If you see an error message, quote it exactly."""
contents = [
types.Part.from_bytes(data=image_bytes, mime_type="image/png"),
analysis_prompt,
]
response = client.models.generate_content(
model="gemini-2.5-flash",
contents=contents,
)
analysis = response.text.strip()
print(f"[Code] ✅ Screen analysis complete")
try:
screenshot_path.unlink()
except Exception:
pass
if file_path and file_content:
code_match = re.search(r"```[a-zA-Z]*\n(.*?)```", analysis, re.DOTALL)
if code_match:
fixed_code = code_match.group(1).strip()
save_path = Path(file_path)
_save_file(save_path, fixed_code)
analysis += f"\n\n✅ Fixed code has been saved to: {file_path}"
print(f"[Code] ✅ Fixed code saved: {file_path}")
return analysis
except Exception as e:
try:
screenshot_path.unlink()
except Exception:
pass
return f"Screen analysis failed: {e}"
def code_helper(
parameters: dict,
response=None,
player=None,
session_memory=None,
speak=None
) -> str:
"""
Called from main.py.
parameters:
action : write | edit | explain | run | build | screen_debug | optimize | auto
description : What the code should do / what change to make / what problem to analyze
language : Programming language (default: python)
output_path : Where to save — user specifies full path or filename
file_path : Path to existing file (edit / explain / run / build / optimize)
code : Raw code string (explain/optimize without a file)
args : CLI argument list for run/build
timeout : Execution timeout in seconds (default: 30)
"""
p = parameters or {}
action = p.get("action", "auto").lower().strip()
description = p.get("description", "").strip()
language = p.get("language", "python").strip()
output_path = p.get("output_path", "").strip()
file_path = p.get("file_path", "").strip()
code = p.get("code", "").strip()
args = p.get("args", [])
timeout = int(p.get("timeout", 30))
if action == "auto":
action = _detect_intent(description, file_path, code)
print(f"[Code] 🤖 Auto-detected: {action}")
if action == "write":
return _write_action(description, language, output_path, player)
elif action == "edit":
return _edit_action(
file_path,
description or p.get("instruction", ""),
player
)
elif action == "explain":
return _explain_action(file_path, code, player)
elif action == "run":
return _run_action(file_path, args, timeout, player)
elif action == "build":
return _build(description, language, output_path, args, timeout, speak, player)
elif action == "optimize":
return _optimize_action(file_path, code, language, output_path, player)
elif action == "screen_debug":
return _screen_debug_action(description, file_path, player, speak)
else:
return f"Unknown action: '{action}'. Use write, edit, explain, run, build, optimize, or screen_debug."