239 lines
6.6 KiB
Python
239 lines
6.6 KiB
Python
# raw_session.py
|
|
"""
|
|
Modèle RawSession v1 pour l'agent_v0.
|
|
|
|
Ce module définit la structure des données RawSession et
|
|
la sérialisation en JSON au format rawsession_v1.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import os
|
|
import time
|
|
import uuid
|
|
from dataclasses import dataclass, field
|
|
from typing import Any, Dict, List, Optional
|
|
|
|
from config import AGENT_VERSION, SESSIONS_ROOT
|
|
|
|
|
|
@dataclass
|
|
class ScreenshotMeta:
|
|
screenshot_id: str
|
|
relative_path: str
|
|
captured_at: str
|
|
|
|
|
|
@dataclass
|
|
class Event:
|
|
t: float
|
|
type: str
|
|
window: Dict[str, Any]
|
|
screenshot_id: Optional[str] = None
|
|
# champs optionnels selon type
|
|
button: Optional[str] = None
|
|
pos: Optional[List[int]] = None
|
|
keys: Optional[List[str]] = None
|
|
idle_ms: Optional[int] = None
|
|
scroll_delta: Optional[List[int]] = None # [dx, dy] pour la molette
|
|
|
|
|
|
@dataclass
|
|
class RawSession:
|
|
schema_version: str
|
|
session_id: str
|
|
agent_version: str
|
|
environment: Dict[str, Any]
|
|
user: Dict[str, Any]
|
|
context: Dict[str, Any]
|
|
started_at: str
|
|
ended_at: Optional[str] = None
|
|
|
|
events: List[Event] = field(default_factory=list)
|
|
screenshots: List[ScreenshotMeta] = field(default_factory=list)
|
|
|
|
_start_time_monotonic: float = field(default_factory=time.monotonic, repr=False)
|
|
|
|
@classmethod
|
|
def create(
|
|
cls,
|
|
user_id: str,
|
|
user_label: str = "",
|
|
customer: str = "",
|
|
training_label: str = "",
|
|
notes: str = "",
|
|
platform: str = "unknown",
|
|
hostname: str = "unknown",
|
|
screen_resolution: Optional[List[int]] = None,
|
|
display_scale: float = 1.0,
|
|
) -> "RawSession":
|
|
"""Fabrique une nouvelle session avec les champs de base remplis."""
|
|
session_id = f"sess_{time.strftime('%Y%m%dT%H%M%S')}_{uuid.uuid4().hex[:8]}"
|
|
started_at = time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())
|
|
|
|
environment = {
|
|
"platform": platform,
|
|
"hostname": hostname,
|
|
"screen": {
|
|
"primary_resolution": screen_resolution or [0, 0],
|
|
"display_scale": display_scale,
|
|
},
|
|
}
|
|
user = {
|
|
"id": user_id,
|
|
"label": user_label or user_id,
|
|
}
|
|
context = {
|
|
"customer": customer,
|
|
"training_label": training_label,
|
|
"notes": notes,
|
|
}
|
|
|
|
return cls(
|
|
schema_version="rawsession_v1",
|
|
session_id=session_id,
|
|
agent_version=AGENT_VERSION,
|
|
environment=environment,
|
|
user=user,
|
|
context=context,
|
|
started_at=started_at,
|
|
)
|
|
|
|
# --- API temps relatif ---
|
|
|
|
def now_relative(self) -> float:
|
|
"""Temps relatif (en secondes) depuis le début de la session."""
|
|
return time.monotonic() - self._start_time_monotonic
|
|
|
|
# --- API screenshots ---
|
|
|
|
def add_screenshot(
|
|
self,
|
|
screenshot_id: str,
|
|
relative_path: str,
|
|
captured_at: Optional[str] = None,
|
|
) -> None:
|
|
"""Enregistre les métadonnées d'un screenshot."""
|
|
captured_at = captured_at or time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())
|
|
self.screenshots.append(
|
|
ScreenshotMeta(
|
|
screenshot_id=screenshot_id,
|
|
relative_path=relative_path,
|
|
captured_at=captured_at,
|
|
)
|
|
)
|
|
|
|
# --- API événements ---
|
|
|
|
def add_mouse_click_event(
|
|
self,
|
|
button: str,
|
|
pos: List[int],
|
|
window_title: str,
|
|
app_name: str,
|
|
screenshot_id: Optional[str],
|
|
) -> None:
|
|
self.events.append(
|
|
Event(
|
|
t=self.now_relative(),
|
|
type="mouse_click",
|
|
button=button,
|
|
pos=pos,
|
|
window={"title": window_title, "app_name": app_name},
|
|
screenshot_id=screenshot_id,
|
|
)
|
|
)
|
|
|
|
def add_key_combo_event(
|
|
self,
|
|
keys: List[str],
|
|
window_title: str,
|
|
app_name: str,
|
|
screenshot_id: Optional[str],
|
|
) -> None:
|
|
self.events.append(
|
|
Event(
|
|
t=self.now_relative(),
|
|
type="key_combo",
|
|
keys=keys,
|
|
window={"title": window_title, "app_name": app_name},
|
|
screenshot_id=screenshot_id,
|
|
)
|
|
)
|
|
|
|
def add_hover_idle_event(
|
|
self,
|
|
pos: List[int],
|
|
idle_ms: int,
|
|
window_title: str,
|
|
app_name: str,
|
|
screenshot_id: Optional[str],
|
|
) -> None:
|
|
self.events.append(
|
|
Event(
|
|
t=self.now_relative(),
|
|
type="hover_idle",
|
|
pos=pos,
|
|
idle_ms=idle_ms,
|
|
window={"title": window_title, "app_name": app_name},
|
|
screenshot_id=screenshot_id,
|
|
)
|
|
)
|
|
|
|
def add_scroll_event(
|
|
self,
|
|
pos: List[int],
|
|
delta: List[int],
|
|
window_title: str,
|
|
app_name: str,
|
|
screenshot_id: Optional[str],
|
|
) -> None:
|
|
"""delta = [dx, dy]"""
|
|
self.events.append(
|
|
Event(
|
|
t=self.now_relative(),
|
|
type="scroll",
|
|
pos=pos,
|
|
scroll_delta=delta,
|
|
window={"title": window_title, "app_name": app_name},
|
|
screenshot_id=screenshot_id,
|
|
)
|
|
)
|
|
|
|
def close(self) -> None:
|
|
"""Marque la fin de la session."""
|
|
self.ended_at = time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())
|
|
|
|
# --- Sérialisation ---
|
|
|
|
def to_dict(self) -> Dict[str, Any]:
|
|
return {
|
|
"schema_version": self.schema_version,
|
|
"session_id": self.session_id,
|
|
"agent_version": self.agent_version,
|
|
"environment": self.environment,
|
|
"user": self.user,
|
|
"context": self.context,
|
|
"started_at": self.started_at,
|
|
"ended_at": self.ended_at,
|
|
"events": [e.__dict__ for e in self.events],
|
|
"screenshots": [s.__dict__ for s in self.screenshots],
|
|
}
|
|
|
|
def save_json(self, base_dir: Optional[str] = None) -> str:
|
|
"""
|
|
Sauvegarde le JSON rawsession_v1 dans un sous-dossier de SESSIONS_ROOT.
|
|
|
|
Retourne le chemin du fichier JSON.
|
|
"""
|
|
base_dir = base_dir or SESSIONS_ROOT
|
|
session_dir = os.path.join(base_dir, self.session_id)
|
|
os.makedirs(session_dir, exist_ok=True)
|
|
json_path = os.path.join(session_dir, f"{self.session_id}.json")
|
|
|
|
with open(json_path, "w", encoding="utf-8") as f:
|
|
json.dump(self.to_dict(), f, ensure_ascii=False, indent=2)
|
|
|
|
return json_path
|