Initial ai typewriter implementation
This commit is contained in:
@@ -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()
|
||||
Reference in New Issue
Block a user