Initial ai typewriter implementation

This commit is contained in:
Edern Deneuville
2026-09-11 11:59:09 +02:00
parent 7b4a17e370
commit 2d224b7ac8
+79
View File
@@ -0,0 +1,79 @@
from __future__ import annotations
import requests
from .config import AppConfig
class AIClientError(RuntimeError):
"""Raised when the configured AI backend cannot return text."""
def ask_ai(prompt: str, config: AppConfig) -> str:
if not prompt.strip():
raise AIClientError("Le texte capturé est vide.")
if config.provider == "ollama":
return _ask_ollama(prompt, config)
if config.provider == "gemini":
return _ask_gemini(prompt, config)
raise AIClientError(f"Provider non supporté: {config.provider}")
def _ask_ollama(prompt: str, config: AppConfig) -> str:
url = f"{config.server_url}/api/chat"
payload = {
"model": config.model,
"stream": False,
"messages": [
{"role": "system", "content": config.system_prompt},
{"role": "user", "content": prompt},
],
}
try:
response = requests.post(url, json=payload, timeout=config.request_timeout_seconds)
response.raise_for_status()
data = response.json()
except requests.RequestException as exc:
raise AIClientError(f"Erreur Ollama: {exc}") from exc
except ValueError as exc:
raise AIClientError("Réponse Ollama invalide: JSON illisible") from exc
content = data.get("message", {}).get("content")
if not isinstance(content, str) or not content.strip():
raise AIClientError("Réponse Ollama vide ou inattendue")
return content.strip()
def _ask_gemini(prompt: str, config: AppConfig) -> str:
if not config.api_key:
raise AIClientError("api_key est obligatoire pour provider='gemini'.")
base = config.server_url or "https://generativelanguage.googleapis.com"
url = f"{base}/v1beta/models/{config.model}:generateContent"
payload = {
"systemInstruction": {"parts": [{"text": config.system_prompt}]},
"contents": [{"role": "user", "parts": [{"text": prompt}]}],
"generationConfig": {"temperature": 0.2},
}
try:
response = requests.post(
url,
params={"key": config.api_key},
json=payload,
timeout=config.request_timeout_seconds,
)
response.raise_for_status()
data = response.json()
except requests.RequestException as exc:
raise AIClientError(f"Erreur Gemini: {exc}") from exc
except ValueError as exc:
raise AIClientError("Réponse Gemini invalide: JSON illisible") from exc
try:
text = data["candidates"][0]["content"]["parts"][0]["text"]
except (KeyError, IndexError, TypeError) as exc:
raise AIClientError("Réponse Gemini vide ou inattendue") from exc
if not isinstance(text, str) or not text.strip():
raise AIClientError("Réponse Gemini vide")
return text.strip()