100 lines
3.7 KiB
Python
100 lines
3.7 KiB
Python
"""Hermes Hub — Asset and Icon Manager with Provider Icon Caching."""
|
|
from __future__ import annotations
|
|
|
|
import os
|
|
from pathlib import Path
|
|
from typing import Dict, Optional, Tuple
|
|
from PIL import Image
|
|
import customtkinter as ctk
|
|
|
|
|
|
class AssetManager:
|
|
_instance: Optional[AssetManager] = None
|
|
_image_cache: Dict[str, ctk.CTkImage] = {}
|
|
_pil_cache: Dict[str, Image.Image] = {}
|
|
|
|
def __init__(self):
|
|
self.root_dir = self._find_repo_root()
|
|
self.branding_dir = self.root_dir / "assets" / "branding"
|
|
self.providers_dir = self.root_dir / "assets" / "providers"
|
|
self.logo_dir = self.branding_dir / "logo"
|
|
self.app_dir = self.branding_dir / "app"
|
|
self.splash_dir = self.branding_dir / "splash"
|
|
self.icons_dir = self.branding_dir / "icons"
|
|
|
|
@classmethod
|
|
def get(cls) -> AssetManager:
|
|
if cls._instance is None:
|
|
cls._instance = cls()
|
|
return cls._instance
|
|
|
|
def _find_repo_root(self) -> Path:
|
|
cur = Path(__file__).resolve()
|
|
for p in [cur.parents[4], cur.parents[3], cur.parents[2], cur.parents[1]]:
|
|
if (p / "assets" / "branding").exists():
|
|
return p
|
|
local_app = Path(os.environ.get("LOCALAPPDATA", "")) / "hermes" / "plugins" / "antigravity-provider"
|
|
if (local_app / "assets" / "branding").exists():
|
|
return local_app
|
|
return Path("E:/Agent projects/hermes-hub")
|
|
|
|
def get_ico_path(self) -> str:
|
|
ico = self.app_dir / "HermesHub.ico"
|
|
if ico.exists():
|
|
return str(ico)
|
|
alt = self.root_dir / "launcher" / "HermesHub.ico"
|
|
if alt.exists():
|
|
return str(alt)
|
|
return ""
|
|
|
|
@classmethod
|
|
def clear_cache(cls):
|
|
cls._image_cache.clear()
|
|
cls._pil_cache.clear()
|
|
|
|
def get_logo_image(self, size: Tuple[int, int] = (120, 120)) -> Optional[ctk.CTkImage]:
|
|
logo_path = self.logo_dir / "logo_256.png"
|
|
if not logo_path.exists():
|
|
logo_path = self.logo_dir / "logo_master.png"
|
|
if not logo_path.exists():
|
|
logo_path = self.branding_dir / "source" / "Hermes Hub.png"
|
|
|
|
if logo_path.exists():
|
|
try:
|
|
pil_img = Image.open(logo_path).convert("RGBA")
|
|
return ctk.CTkImage(light_image=pil_img, dark_image=pil_img, size=size)
|
|
except Exception:
|
|
return None
|
|
return None
|
|
|
|
def get_provider_image(self, provider: str, size: Tuple[int, int] = (20, 20)) -> Optional[ctk.CTkImage]:
|
|
"""Fetch cached CTkImage for AI providers (antigravity, openai, opencode)."""
|
|
prov_clean = provider.lower()
|
|
if "antigravity" in prov_clean:
|
|
key_name = "antigravity"
|
|
elif "codex" in prov_clean or "openai" in prov_clean:
|
|
key_name = "openai"
|
|
elif "opencode" in prov_clean:
|
|
key_name = "opencode"
|
|
else:
|
|
key_name = "antigravity"
|
|
|
|
cache_key = f"prov_{key_name}_{size[0]}x{size[1]}"
|
|
if cache_key in self._image_cache:
|
|
return self._image_cache[cache_key]
|
|
|
|
prov_folder = self.providers_dir / key_name
|
|
# Find best size matching
|
|
target_file = prov_folder / f"{key_name}_{size[0]}.png"
|
|
if not target_file.exists():
|
|
target_file = prov_folder / f"{key_name}_32.png"
|
|
if not target_file.exists():
|
|
target_file = prov_folder / f"{key_name}_master.png"
|
|
|
|
if target_file.exists():
|
|
try:
|
|
pil_img = Image.open(target_file).convert("RGBA")
|
|
return ctk.CTkImage(light_image=pil_img, dark_image=pil_img, size=size)
|
|
except Exception:
|
|
return None
|
|
return None
|