lollms-client 1.4.1__py3-none-any.whl → 1.7.10__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. lollms_client/__init__.py +1 -1
  2. lollms_client/llm_bindings/azure_openai/__init__.py +2 -2
  3. lollms_client/llm_bindings/claude/__init__.py +125 -34
  4. lollms_client/llm_bindings/gemini/__init__.py +261 -159
  5. lollms_client/llm_bindings/grok/__init__.py +52 -14
  6. lollms_client/llm_bindings/groq/__init__.py +2 -2
  7. lollms_client/llm_bindings/hugging_face_inference_api/__init__.py +2 -2
  8. lollms_client/llm_bindings/litellm/__init__.py +1 -1
  9. lollms_client/llm_bindings/llamacpp/__init__.py +18 -11
  10. lollms_client/llm_bindings/lollms/__init__.py +151 -32
  11. lollms_client/llm_bindings/lollms_webui/__init__.py +1 -1
  12. lollms_client/llm_bindings/mistral/__init__.py +2 -2
  13. lollms_client/llm_bindings/novita_ai/__init__.py +439 -0
  14. lollms_client/llm_bindings/ollama/__init__.py +309 -93
  15. lollms_client/llm_bindings/open_router/__init__.py +2 -2
  16. lollms_client/llm_bindings/openai/__init__.py +148 -29
  17. lollms_client/llm_bindings/openllm/__init__.py +362 -506
  18. lollms_client/llm_bindings/openwebui/__init__.py +465 -0
  19. lollms_client/llm_bindings/perplexity/__init__.py +326 -0
  20. lollms_client/llm_bindings/pythonllamacpp/__init__.py +3 -3
  21. lollms_client/llm_bindings/tensor_rt/__init__.py +1 -1
  22. lollms_client/llm_bindings/transformers/__init__.py +428 -632
  23. lollms_client/llm_bindings/vllm/__init__.py +1 -1
  24. lollms_client/lollms_agentic.py +4 -2
  25. lollms_client/lollms_base_binding.py +61 -0
  26. lollms_client/lollms_core.py +516 -1890
  27. lollms_client/lollms_discussion.py +55 -18
  28. lollms_client/lollms_llm_binding.py +112 -261
  29. lollms_client/lollms_mcp_binding.py +34 -75
  30. lollms_client/lollms_personality.py +5 -2
  31. lollms_client/lollms_stt_binding.py +85 -52
  32. lollms_client/lollms_tti_binding.py +23 -37
  33. lollms_client/lollms_ttm_binding.py +24 -42
  34. lollms_client/lollms_tts_binding.py +28 -17
  35. lollms_client/lollms_ttv_binding.py +24 -42
  36. lollms_client/lollms_types.py +4 -2
  37. lollms_client/stt_bindings/whisper/__init__.py +108 -23
  38. lollms_client/stt_bindings/whispercpp/__init__.py +7 -1
  39. lollms_client/tti_bindings/diffusers/__init__.py +418 -810
  40. lollms_client/tti_bindings/diffusers/server/main.py +1051 -0
  41. lollms_client/tti_bindings/gemini/__init__.py +182 -239
  42. lollms_client/tti_bindings/leonardo_ai/__init__.py +127 -0
  43. lollms_client/tti_bindings/lollms/__init__.py +4 -1
  44. lollms_client/tti_bindings/novita_ai/__init__.py +105 -0
  45. lollms_client/tti_bindings/openai/__init__.py +10 -11
  46. lollms_client/tti_bindings/stability_ai/__init__.py +178 -0
  47. lollms_client/ttm_bindings/audiocraft/__init__.py +7 -12
  48. lollms_client/ttm_bindings/beatoven_ai/__init__.py +129 -0
  49. lollms_client/ttm_bindings/lollms/__init__.py +4 -17
  50. lollms_client/ttm_bindings/replicate/__init__.py +115 -0
  51. lollms_client/ttm_bindings/stability_ai/__init__.py +117 -0
  52. lollms_client/ttm_bindings/topmediai/__init__.py +96 -0
  53. lollms_client/tts_bindings/bark/__init__.py +7 -10
  54. lollms_client/tts_bindings/lollms/__init__.py +6 -1
  55. lollms_client/tts_bindings/piper_tts/__init__.py +8 -11
  56. lollms_client/tts_bindings/xtts/__init__.py +157 -74
  57. lollms_client/tts_bindings/xtts/server/main.py +241 -280
  58. {lollms_client-1.4.1.dist-info → lollms_client-1.7.10.dist-info}/METADATA +316 -6
  59. lollms_client-1.7.10.dist-info/RECORD +89 -0
  60. lollms_client/ttm_bindings/bark/__init__.py +0 -339
  61. lollms_client-1.4.1.dist-info/RECORD +0 -78
  62. {lollms_client-1.4.1.dist-info → lollms_client-1.7.10.dist-info}/WHEEL +0 -0
  63. {lollms_client-1.4.1.dist-info → lollms_client-1.7.10.dist-info}/licenses/LICENSE +0 -0
  64. {lollms_client-1.4.1.dist-info → lollms_client-1.7.10.dist-info}/top_level.txt +0 -0
@@ -2,319 +2,262 @@
2
2
  import sys
3
3
  from typing import Optional, List, Dict, Any, Union
4
4
  import os
5
+ import io
6
+ import base64
7
+ import requests
8
+ import binascii
9
+ import time
5
10
 
6
11
  from lollms_client.lollms_tti_binding import LollmsTTIBinding
7
12
  from ascii_colors import trace_exception, ASCIIColors
8
- import math
9
13
 
10
14
  # --- SDK & Dependency Management ---
11
15
  try:
12
16
  import pipmaster as pm
13
- # Ensure both potential SDKs and Pillow are available
14
- pm.ensure_packages(['google-cloud-aiplatform', 'google-generativeai', 'Pillow'])
17
+ pm.ensure_packages(['google-cloud-aiplatform', 'google-generativeai', 'Pillow', 'requests'])
15
18
  except ImportError:
16
- pass # pipmaster is optional
19
+ pass
17
20
 
18
- # Attempt to import Vertex AI (google-cloud-aiplatform)
21
+ # Attempt to import Vertex AI
19
22
  try:
20
23
  import vertexai
21
- from vertexai.preview.vision_models import ImageGenerationModel
24
+ from vertexai.preview.vision_models import ImageGenerationModel as VertexImageGenerationModel, Image as VertexImage
22
25
  from google.api_core import exceptions as google_exceptions
26
+ from PIL import Image as PILImage
23
27
  VERTEX_AI_AVAILABLE = True
24
28
  except ImportError:
25
29
  VERTEX_AI_AVAILABLE = False
26
30
 
27
- # Attempt to import Gemini API (google-generativeai)
31
+ # Attempt to import Gemini API
28
32
  try:
29
- from google import genai
30
- from google.genai import types as genai_types
33
+ import google.generativeai as genai
34
+ from google.api_core.exceptions import ResourceExhausted
31
35
  GEMINI_API_AVAILABLE = True
32
36
  except ImportError:
33
37
  GEMINI_API_AVAILABLE = False
38
+ ResourceExhausted = type('ResourceExhausted', (Exception,), {}) # Define dummy exception if import fails
34
39
 
35
40
  # Defines the binding name for the manager
36
41
  BindingName = "GeminiTTIBinding_Impl"
37
42
 
38
- # Known Imagen models for each service
39
- IMAGEN_VERTEX_MODELS = ["imagegeneration@006", "imagegeneration@005", "imagegeneration@002"]
40
- IMAGEN_GEMINI_API_MODELS = ["imagen-3", "gemini-1.5-flash-preview-0514"] # Short names are often aliases
43
+ # Static list for Vertex AI, as it's project-based and more predictable
44
+ IMAGEN_VERTEX_MODELS = ["imagegeneration@006", "imagen-3.0-generate-002", "gemini-2.5-flash-image"]
41
45
  GEMINI_API_KEY_ENV_VAR = "GEMINI_API_KEY"
42
46
 
43
- class GeminiTTIBinding_Impl(LollmsTTIBinding):
44
- """
45
- Concrete implementation of LollmsTTIBinding for Google's Imagen models.
46
- Supports both Vertex AI (project_id) and Gemini API (api_key) authentication.
47
- """
48
- def __init__(self, **kwargs):
49
- """
50
- Initialize the Gemini (Vertex AI / API) TTI binding.
51
-
52
- Args:
53
- **kwargs: Configuration parameters.
54
- - auth_method (str): "vertex_ai" or "api_key". (Required)
55
- - project_id (str): Google Cloud project ID (for vertex_ai).
56
- - location (str): Google Cloud region (for vertex_ai).
57
- - service_key (str): Gemini API Key (for api_key).
58
- - model_name (str): The Imagen model to use.
59
- - default_seed (int): Default seed for generation (-1 for random).
60
- - default_guidance_scale (float): Default guidance scale (CFG).
61
- """
62
- super().__init__(binding_name="gemini")
63
47
 
64
- # Core settings
65
- self.auth_method = kwargs.get("auth_method", "vertex_ai") # Default to vertex_ai for backward compatibility
48
+ def _is_base64(s):
49
+ try:
50
+ base64.b64decode(s.split(',')[-1], validate=True)
51
+ return True
52
+ except (TypeError, ValueError, binascii.Error):
53
+ return False
66
54
 
67
- # Vertex AI specific settings
68
- self.project_id = kwargs.get("project_id")
69
- self.location = kwargs.get("location", "us-central1")
55
+ def _load_image_from_str(image_str: str) -> bytes:
56
+ if image_str.startswith(('http://', 'https://')):
57
+ try:
58
+ response = requests.get(image_str)
59
+ response.raise_for_status()
60
+ return response.content
61
+ except requests.exceptions.RequestException as e:
62
+ raise IOError(f"Failed to download image from URL: {image_str}") from e
63
+ elif _is_base64(image_str):
64
+ header, encoded = image_str.split(',', 1)
65
+ return base64.b64decode(encoded)
66
+ else:
67
+ raise ValueError("Image string is not a valid URL or base64 string.")
70
68
 
71
- # Gemini API specific settings
72
- self.gemini_api_key = kwargs.get("service_key")
73
69
 
74
- # Common settings
75
- self.model_name = kwargs.get("model_name")
76
- self.default_seed = int(kwargs.get("default_seed", -1))
77
- self.default_guidance_scale = float(kwargs.get("default_guidance_scale", 7.5))
78
- self.client_id = kwargs.get("client_id", "gemini_client_user")
79
-
80
- # The actual client/model instance
70
+ class GeminiTTIBinding_Impl(LollmsTTIBinding):
71
+ def __init__(self, **kwargs):
72
+ # Prioritize 'model_name' but accept 'model' as an alias from config files.
73
+ if 'model' in kwargs and 'model_name' not in kwargs:
74
+ kwargs['model_name'] = kwargs.pop('model')
75
+ super().__init__(binding_name=BindingName, config=kwargs)
76
+ self.auth_method = kwargs.get("auth_method", "vertex_ai")
81
77
  self.client: Optional[Any] = None
78
+ self.available_models = []
82
79
 
83
- # --- Validation and Initialization ---
84
80
  if self.auth_method == "vertex_ai":
85
81
  if not VERTEX_AI_AVAILABLE:
86
- raise ImportError("Vertex AI authentication selected, but 'google-cloud-aiplatform' is not installed.")
82
+ raise ImportError("Vertex AI selected, but 'google-cloud-aiplatform' is not installed.")
83
+ self.project_id = kwargs.get("project_id")
84
+ self.location = kwargs.get("location", "us-central1")
87
85
  if not self.project_id:
88
- raise ValueError("For 'vertex_ai' auth, a Google Cloud 'project_id' is required.")
89
- if not self.model_name:
90
- self.model_name = IMAGEN_VERTEX_MODELS[0]
86
+ raise ValueError("For 'vertex_ai' auth, 'project_id' is required.")
87
+ self.model_name = kwargs.get("model_name") # Can be None initially
88
+ self.available_models = IMAGEN_VERTEX_MODELS
89
+
91
90
  elif self.auth_method == "api_key":
92
91
  if not GEMINI_API_AVAILABLE:
93
- raise ImportError("API Key authentication selected, but 'google-generativeai' is not installed.")
94
-
95
- # Resolve API key from kwargs or environment variable
96
- if not self.gemini_api_key:
97
- ASCIIColors.info(f"API key not provided directly, checking environment variable '{GEMINI_API_KEY_ENV_VAR}'...")
98
- self.gemini_api_key = os.environ.get(GEMINI_API_KEY_ENV_VAR)
99
-
92
+ raise ImportError("API Key selected, but 'google-generativeai' is not installed.")
93
+ self.gemini_api_key = kwargs.get("service_key") or os.environ.get(GEMINI_API_KEY_ENV_VAR)
100
94
  if not self.gemini_api_key:
101
- raise ValueError(f"For 'api_key' auth, a Gemini API Key is required. Provide it as 'service_key' or set the '{GEMINI_API_KEY_ENV_VAR}' environment variable.")
102
-
103
- if not self.model_name:
104
- self.model_name = IMAGEN_GEMINI_API_MODELS[0]
95
+ raise ValueError(f"For 'api_key' auth, 'service_key' or env var '{GEMINI_API_KEY_ENV_VAR}' is required.")
96
+ self.model_name = kwargs.get("model_name") # Can be None initially
105
97
  else:
106
98
  raise ValueError(f"Invalid auth_method: '{self.auth_method}'. Must be 'vertex_ai' or 'api_key'.")
107
99
 
100
+ self.default_seed = int(kwargs.get("default_seed", -1))
101
+ self.default_guidance_scale = float(kwargs.get("default_guidance_scale", 7.5))
108
102
  self._initialize_client()
109
103
 
110
104
  def _initialize_client(self):
111
- """Initializes the appropriate client based on the selected auth_method."""
112
105
  ASCIIColors.info(f"Initializing Google client with auth method: '{self.auth_method}'...")
113
106
  try:
114
107
  if self.auth_method == "vertex_ai":
115
108
  vertexai.init(project=self.project_id, location=self.location)
116
- self.client = ImageGenerationModel.from_pretrained(self.model_name)
109
+ if not self.model_name:
110
+ self.model_name = self.available_models[0]
111
+ self.client = VertexImageGenerationModel.from_pretrained(self.model_name)
117
112
  ASCIIColors.green(f"Vertex AI initialized successfully. Project: '{self.project_id}', Model: '{self.model_name}'")
113
+
118
114
  elif self.auth_method == "api_key":
119
115
  genai.configure(api_key=self.gemini_api_key)
120
- # For the genai SDK, the "client" is the configured module itself,
121
- # and we specify the model per-call. Let's store the genai module.
122
- self.client = genai
123
- ASCIIColors.green(f"Gemini API configured successfully. Model to be used: '{self.model_name}'")
124
- except google_exceptions.PermissionDenied as e:
125
- trace_exception(e)
126
- raise Exception(
127
- "Authentication failed. For Vertex AI, run 'gcloud auth application-default login'. For API Key, check if the key is valid and has permissions."
128
- ) from e
116
+
117
+ # --- DYNAMIC MODEL DISCOVERY ---
118
+ ASCIIColors.info("Discovering available image models for your API key...")
119
+ self.available_models = [
120
+ m.name for m in genai.list_models()
121
+ if 'imagen' in m.name and 'generateContent' in m.supported_generation_methods
122
+ ]
123
+
124
+ if not self.available_models:
125
+ raise Exception("Your API key does not have access to any compatible image generation models. Please check your Google AI Studio project settings.")
126
+
127
+ ASCIIColors.green(f"Found available models: {self.available_models}")
128
+
129
+ # Validate or set the model_name
130
+ if self.model_name and self.model_name not in self.available_models:
131
+ ASCIIColors.warning(f"Model '{self.model_name}' is not available for your key. Falling back to default.")
132
+ self.model_name = None
133
+
134
+ if not self.model_name:
135
+ self.model_name = self.available_models[0]
136
+
137
+ self.client = genai.GenerativeModel(self.model_name)
138
+ ASCIIColors.green(f"Gemini API configured successfully. Using Model: '{self.model_name}'")
139
+
129
140
  except Exception as e:
130
141
  trace_exception(e)
131
142
  raise Exception(f"Failed to initialize Google client: {e}") from e
132
143
 
133
- def _validate_dimensions_vertex(self, width: int, height: int) -> None:
134
- """Validates image dimensions against Vertex AI Imagen constraints."""
135
- if not (256 <= width <= 1536 and width % 8 == 0):
136
- raise ValueError(f"Invalid width for Vertex AI: {width}. Must be 256-1536 and a multiple of 8.")
137
- if not (256 <= height <= 1536 and height % 8 == 0):
138
- raise ValueError(f"Invalid height for Vertex AI: {height}. Must be 256-1536 and a multiple of 8.")
139
-
140
- def _get_aspect_ratio_for_api(self, width: int, height: int) -> str:
141
- """Finds the closest supported aspect ratio string for the Gemini API."""
142
- ratios = {"1:1": 1.0, "16:9": 16/9, "9:16": 9/16, "4:3": 4/3, "3:4": 3/4}
143
- target_ratio = width / height
144
- closest_ratio_name = min(ratios, key=lambda r: abs(ratios[r] - target_ratio))
145
- ASCIIColors.info(f"Converted {width}x{height} to closest aspect ratio: '{closest_ratio_name}' for Gemini API.")
146
- return closest_ratio_name
147
-
148
- def generate_image(self,
149
- prompt: str,
150
- negative_prompt: Optional[str] = "",
151
- width: int = 1024,
152
- height: int = 1024,
153
- **kwargs) -> bytes:
154
- """
155
- Generates image data using the configured Google Imagen model.
156
- """
144
+ def generate_image(self, prompt: str, negative_prompt: Optional[str] = "", width: int = 1024, height: int = 1024, **kwargs) -> bytes:
157
145
  if not self.client:
158
- raise RuntimeError("Google client is not initialized. Cannot generate image.")
159
-
160
- # Use overrides from kwargs, otherwise instance defaults
161
- seed = kwargs.get("seed", self.default_seed)
162
- guidance_scale = kwargs.get("guidance_scale", self.default_guidance_scale)
163
- gen_seed = seed if seed != -1 else None
146
+ raise RuntimeError("Google client is not initialized.")
147
+
148
+ ASCIIColors.info(f"Generating image with prompt: '{prompt[:100]}...'")
149
+
150
+ try:
151
+ if self.auth_method == "vertex_ai":
152
+ return self._generate_with_vertex_ai(prompt, negative_prompt, width, height, **kwargs)
153
+ elif self.auth_method == "api_key":
154
+ return self._generate_with_api_key(prompt, negative_prompt, width, height, **kwargs)
155
+ except Exception as e:
156
+ if "quota" in str(e).lower():
157
+ raise Exception(f"Image generation failed due to a quota error. This means you have exceeded the free tier limit for your API key. To fix this, please enable billing on your Google Cloud project. Original error: {e}")
158
+ raise Exception(f"Image generation failed: {e}")
164
159
 
165
- final_prompt = prompt
160
+ def _generate_with_api_key(self, prompt, negative_prompt, width, height, **kwargs):
161
+ full_prompt = f"Generate an image of: {prompt}"
166
162
  if negative_prompt:
167
- final_prompt = f"{prompt}. Do not include: {negative_prompt}."
163
+ full_prompt += f". Do not include: {negative_prompt}."
168
164
 
169
- ASCIIColors.info(f"Generating image with prompt: '{final_prompt[:100]}...'")
165
+ max_retries = 3
166
+ initial_delay = 5
170
167
 
171
- try:
172
- if self.auth_method == "vertex_ai":
173
- self._validate_dimensions_vertex(width, height)
174
- gen_params = {
175
- "prompt": final_prompt,
176
- "number_of_images": 1,
177
- "width": width,
178
- "height": height,
179
- "guidance_scale": guidance_scale,
180
- }
181
- if gen_seed is not None:
182
- gen_params["seed"] = gen_seed
183
-
184
- ASCIIColors.debug(f"Vertex AI generation parameters: {gen_params}")
185
- response = self.client.generate_images(**gen_params)
168
+ for attempt in range(max_retries):
169
+ try:
170
+ response = self.client.generate_content(full_prompt)
186
171
 
187
- if not response.images:
188
- raise Exception("Image generation resulted in no images (Vertex AI). Check safety filters.")
172
+ if not response.parts or not hasattr(response.parts[0], 'file_data'):
173
+ raise Exception(f"API response did not contain image data. Check safety filters in your Google AI Studio. Response: {response.text}")
189
174
 
190
- return response.images[0]._image_bytes
191
-
192
- elif self.auth_method == "api_key":
193
- aspect_ratio = self._get_aspect_ratio_for_api(width, height)
194
- gen_params = {
195
- "model": self.model_name,
196
- "prompt": final_prompt,
197
- "number_of_images": 1,
198
- "aspect_ratio": aspect_ratio
199
- # Note: seed and guidance_scale are not standard in this simpler API call
200
- }
201
- ASCIIColors.debug(f"Gemini API generation parameters: {gen_params}")
202
- response = self.client.generate_image(**gen_params)
203
-
204
- if not response.images:
205
- raise Exception("Image generation resulted in no images (Gemini API). Check safety filters.")
206
-
207
- return response.images[0].image_bytes
175
+ return response.parts[0].file_data.data
176
+
177
+ except ResourceExhausted as e:
178
+ if attempt < max_retries - 1:
179
+ wait_time = initial_delay * (2 ** attempt)
180
+ ASCIIColors.warning(f"Rate limit exceeded. Waiting {wait_time}s... (Attempt {attempt + 1}/{max_retries})")
181
+ time.sleep(wait_time)
182
+ else:
183
+ ASCIIColors.error(f"Failed to generate image after {max_retries} attempts due to rate limiting.")
184
+ raise e
185
+ except Exception as e:
186
+ raise e
187
+
188
+ def _generate_with_vertex_ai(self, prompt, negative_prompt, width, height, **kwargs):
189
+ self._validate_dimensions_vertex(width, height)
190
+ gen_params = {
191
+ "prompt": prompt, "number_of_images": 1, "width": width, "height": height,
192
+ "guidance_scale": kwargs.get("guidance_scale", self.default_guidance_scale),
193
+ }
194
+ if negative_prompt: gen_params["negative_prompt"] = negative_prompt
195
+ seed = kwargs.get("seed", self.default_seed)
196
+ if seed != -1: gen_params["seed"] = seed
197
+
198
+ response = self.client.generate_images(**gen_params)
199
+ if not response.images: raise Exception("Generation resulted in no images (Vertex AI).")
200
+ return response.images[0]._image_bytes
208
201
 
209
- except (google_exceptions.InvalidArgument, AttributeError) as e:
210
- trace_exception(e)
211
- raise ValueError(f"Invalid argument sent to Google API: {e}") from e
212
- except google_exceptions.GoogleAPICallError as e:
213
- trace_exception(e)
214
- raise Exception(f"A Google API call error occurred: {e}") from e
202
+ def edit_image(self, images: Union[str, List[str]], prompt: str, negative_prompt: Optional[str] = "", mask: Optional[str] = None, **kwargs) -> bytes:
203
+ if self.auth_method != "vertex_ai":
204
+ raise NotImplementedError("Image editing is only supported via the 'vertex_ai' method.")
205
+
206
+ image_str = images[0] if isinstance(images, list) else images
207
+ ASCIIColors.info(f"Editing image with prompt: '{prompt[:100]}...'")
208
+
209
+ try:
210
+ base_image = VertexImage(image_bytes=_load_image_from_str(image_str))
211
+ mask_image = VertexImage(image_bytes=_load_image_from_str(mask)) if mask else None
212
+ edit_params = {"prompt": prompt, "base_image": base_image, "mask": mask_image, "negative_prompt": negative_prompt or None}
213
+ response = self.client.edit_image(**edit_params)
214
+ if not response.images: raise Exception("Image editing resulted in no images.")
215
+ return response.images[0]._image_bytes
215
216
  except Exception as e:
216
- trace_exception(e)
217
- raise Exception(f"Imagen image generation failed: {e}") from e
217
+ raise Exception(f"Imagen image editing failed: {e}") from e
218
+
219
+ def list_models(self) -> list:
220
+ return [{'model_name': name, 'display_name': f"Google ({name})"} for name in self.available_models]
218
221
 
219
222
  def list_services(self, **kwargs) -> List[Dict[str, str]]:
220
- """Lists available Imagen models for the current auth method."""
221
- models = IMAGEN_VERTEX_MODELS if self.auth_method == "vertex_ai" else IMAGEN_GEMINI_API_MODELS
222
223
  service_name = "Vertex AI" if self.auth_method == "vertex_ai" else "Gemini API"
223
- return [
224
- {
225
- "name": name,
226
- "caption": f"Google Imagen ({name}) via {service_name}",
227
- "help": "High-quality text-to-image model from Google."
228
- } for name in models
229
- ]
230
-
231
- def get_settings(self, **kwargs) -> List[Dict[str, Any]]:
232
- """Retrieves the current configurable settings for the binding."""
233
- settings = [
234
- {"name": "auth_method", "type": "str", "value": self.auth_method, "description": "Authentication method to use.", "options": ["vertex_ai", "api_key"], "category": "Authentication"},
235
- ]
236
- if self.auth_method == "vertex_ai":
237
- settings.extend([
238
- {"name": "project_id", "type": "str", "value": self.project_id, "description": "Your Google Cloud project ID.", "category": "Authentication"},
239
- {"name": "location", "type": "str", "value": self.location, "description": "Google Cloud region (e.g., 'us-central1').", "category": "Authentication"},
240
- {"name": "model_name", "type": "str", "value": self.model_name, "description": "Default Imagen model for generation.", "options": IMAGEN_VERTEX_MODELS, "category": "Model Configuration"},
241
- ])
242
- elif self.auth_method == "api_key":
243
- settings.extend([
244
- {"name": "api_key_status", "type": "str", "value": "Set" if self.gemini_api_key else "Not Set", "description": f"Gemini API Key status (set at initialization via service_key or '{GEMINI_API_KEY_ENV_VAR}').", "category": "Authentication", "read_only": True},
245
- {"name": "model_name", "type": "str", "value": self.model_name, "description": "Default Imagen model for generation.", "options": IMAGEN_GEMINI_API_MODELS, "category": "Model Configuration"},
246
- ])
247
-
248
- settings.extend([
249
- {"name": "default_seed", "type": "int", "value": self.default_seed, "description": "Default seed (-1 for random).", "category": "Image Generation Defaults"},
250
- {"name": "default_guidance_scale", "type": "float", "value": self.default_guidance_scale, "description": "Default guidance scale (CFG). (Vertex AI only)", "category": "Image Generation Defaults"},
251
- ])
252
- return settings
253
-
254
- def set_settings(self, settings: Union[Dict[str, Any], List[Dict[str, Any]]], **kwargs) -> bool:
255
- """Applies new settings. Re-initializes the client if core settings change."""
256
- applied_some_settings = False
257
- settings_dict = {item["name"]: item["value"] for item in settings} if isinstance(settings, list) else settings
224
+ return [{"name": name, "caption": f"Google ({name}) via {service_name}"} for name in self.available_models]
258
225
 
226
+ def set_settings(self, settings: Dict[str, Any], **kwargs) -> bool:
227
+ # Simplified for clarity, full logic is complex and stateful
259
228
  needs_reinit = False
260
-
261
- # Phase 1: Check for auth_method or core credential changes
262
- if "auth_method" in settings_dict and self.auth_method != settings_dict["auth_method"]:
263
- self.auth_method = settings_dict["auth_method"]
264
- ASCIIColors.info(f"Authentication method changed to: {self.auth_method}")
265
- # Reset model to a valid default for the new method
266
- if self.auth_method == "vertex_ai":
267
- self.model_name = IMAGEN_VERTEX_MODELS[0]
268
- else:
269
- self.model_name = IMAGEN_GEMINI_API_MODELS[0]
270
- ASCIIColors.info(f"Model name reset to default for new auth method: {self.model_name}")
229
+ if "auth_method" in settings and self.auth_method != settings["auth_method"]:
230
+ self.auth_method = settings["auth_method"]
231
+ needs_reinit = True
232
+ if "project_id" in settings and self.project_id != settings.get("project_id"):
233
+ self.project_id = settings["project_id"]
234
+ needs_reinit = True
235
+ if "service_key" in settings and self.gemini_api_key != settings.get("service_key"):
236
+ self.gemini_api_key = settings["service_key"]
237
+ needs_reinit = True
238
+ if "model_name" in settings and self.model_name != settings.get("model_name"):
239
+ self.model_name = settings["model_name"]
271
240
  needs_reinit = True
272
- applied_some_settings = True
273
-
274
- if self.auth_method == "vertex_ai":
275
- if "project_id" in settings_dict and self.project_id != settings_dict["project_id"]:
276
- self.project_id = settings_dict["project_id"]
277
- needs_reinit = True; applied_some_settings = True
278
- if "location" in settings_dict and self.location != settings_dict["location"]:
279
- self.location = settings_dict["location"]
280
- needs_reinit = True; applied_some_settings = True
281
- # API key is not settable after init, so we don't check for it here.
282
-
283
- # Phase 2: Apply other settings
284
- current_models = IMAGEN_VERTEX_MODELS if self.auth_method == "vertex_ai" else IMAGEN_GEMINI_API_MODELS
285
- if "model_name" in settings_dict:
286
- new_model = settings_dict["model_name"]
287
- if new_model not in current_models:
288
- ASCIIColors.warning(f"Invalid model '{new_model}' for auth method '{self.auth_method}'. Keeping '{self.model_name}'.")
289
- elif self.model_name != new_model:
290
- self.model_name = new_model
291
- needs_reinit = True; applied_some_settings = True
292
-
293
- if "default_seed" in settings_dict and self.default_seed != int(settings_dict["default_seed"]):
294
- self.default_seed = int(settings_dict["default_seed"])
295
- applied_some_settings = True
296
- if "default_guidance_scale" in settings_dict and self.default_guidance_scale != float(settings_dict["default_guidance_scale"]):
297
- self.default_guidance_scale = float(settings_dict["default_guidance_scale"])
298
- applied_some_settings = True
299
-
300
- # Phase 3: Re-initialize if needed
301
241
  if needs_reinit:
302
242
  try:
303
243
  self._initialize_client()
304
244
  except Exception as e:
305
- ASCIIColors.error(f"Failed to re-initialize client with new settings: {e}")
245
+ ASCIIColors.error(f"Failed to re-initialize client: {e}")
306
246
  return False
247
+ return True
307
248
 
308
- return applied_some_settings
249
+ def get_settings(self, **kwargs) -> Optional[Dict[str, Any]]:
250
+ return {
251
+ "auth_method": self.auth_method,
252
+ "project_id": self.project_id if self.auth_method == "vertex_ai" else None,
253
+ "location": self.location if self.auth_method == "vertex_ai" else None,
254
+ "model_name": self.model_name,
255
+ "default_seed": self.default_seed,
256
+ "default_guidance_scale": self.default_guidance_scale
257
+ }
309
258
 
310
- def listModels(self) -> list:
311
- """Lists available Imagen models in a standardized format."""
312
- models = IMAGEN_VERTEX_MODELS if self.auth_method == "vertex_ai" else IMAGEN_GEMINI_API_MODELS
313
- return [
314
- {
315
- 'model_name': name,
316
- 'display_name': f"Imagen ({name})",
317
- 'description': f"Google's Imagen model, version {name}",
318
- 'owned_by': 'Google'
319
- } for name in models
320
- ]
259
+ def _validate_dimensions_vertex(self, width: int, height: int) -> None:
260
+ if not (256 <= width <= 1536 and width % 8 == 0):
261
+ raise ValueError(f"Invalid width for Vertex AI: {width}. Must be 256-1536 and a multiple of 8.")
262
+ if not (256 <= height <= 1536 and height % 8 == 0):
263
+ raise ValueError(f"Invalid height for Vertex AI: {height}. Must be 256-1536 and a multiple of 8.")
@@ -0,0 +1,127 @@
1
+ import os
2
+ import requests
3
+ import time
4
+ import base64
5
+ from io import BytesIO
6
+ from pathlib import Path
7
+ from typing import Optional, List, Dict, Any, Union
8
+
9
+ from lollms_client.lollms_tti_binding import LollmsTTIBinding
10
+ from ascii_colors import trace_exception, ASCIIColors
11
+ import pipmaster as pm
12
+
13
+ pm.ensure_packages(["requests", "Pillow"])
14
+ from PIL import Image
15
+
16
+ BindingName = "LeonardoAITTIBinding"
17
+
18
+ # Sourced from https://docs.leonardo.ai/docs/models
19
+ LEONARDO_AI_MODELS = [
20
+ {"model_name": "ac4f3991-8a40-42cd-b174-14a8e33738e4", "display_name": "Leonardo Phoenix", "description": "Fast, high-quality photorealism."},
21
+ {"model_name": "1e65d070-22c9-4aed-a5be-ce58a1b65b38", "display_name": "Leonardo Diffusion XL", "description": "The flagship general-purpose SDXL model."},
22
+ {"model_name": "b24e16ff-06e3-43eb-a255-db4322b0f345", "display_name": "AlbedoBase XL", "description": "Versatile model for photorealism and artistic styles."},
23
+ {"model_name": "6bef9f1b-29cb-40c7-b9df-32b51c1f67d3", "display_name": "Absolute Reality v1.6", "description": "Classic photorealistic model."},
24
+ {"model_name": "f3296a34-a868-4665-8b2f-f4313f8c8533", "display_name": "RPG v5", "description": "Specialized in RPG characters and assets."},
25
+ {"model_name": "2067ae58-a02e-4318-9742-2b55b2a4c813", "display_name": "DreamShaper v7", "description": "Popular versatile artistic model."},
26
+ ]
27
+
28
+ class LeonardoAITTIBinding(LollmsTTIBinding):
29
+ """Leonardo.ai TTI binding for LoLLMS"""
30
+
31
+ def __init__(self, **kwargs):
32
+ # Prioritize 'model_name' but accept 'model' as an alias from config files.
33
+ if 'model' in kwargs and 'model_name' not in kwargs:
34
+ kwargs['model_name'] = kwargs.pop('model')
35
+ super().__init__(binding_name=BindingName, config=kwargs)
36
+
37
+ self.api_key = self.config.get("api_key") or os.environ.get("LEONARDO_API_KEY")
38
+ if not self.api_key:
39
+ raise ValueError("Leonardo.ai API key is required.")
40
+ self.model_name = self.config.get("model_name", "ac4f3991-8a40-42cd-b174-14a8e33738e4")
41
+ self.base_url = "https://cloud.leonardo.ai/api/rest/v1"
42
+ self.headers = {"Authorization": f"Bearer {self.api_key}", "Content-Type": "application/json"}
43
+
44
+ def list_models(self) -> list:
45
+ # You could also fetch this dynamically from /models endpoint
46
+ return LEONARDO_AI_MODELS
47
+
48
+ def _wait_for_generation(self, generation_id: str) -> List[bytes]:
49
+ while True:
50
+ url = f"{self.base_url}/generations/{generation_id}"
51
+ response = requests.get(url, headers=self.headers)
52
+ response.raise_for_status()
53
+ data = response.json().get("generations_by_pk", {})
54
+ status = data.get("status")
55
+
56
+ if status == "COMPLETE":
57
+ ASCIIColors.green("Generation complete.")
58
+ images_data = []
59
+ for img in data.get("generated_images", []):
60
+ img_url = img.get("url")
61
+ if img_url:
62
+ img_response = requests.get(img_url)
63
+ img_response.raise_for_status()
64
+ images_data.append(img_response.content)
65
+ return images_data
66
+ elif status == "FAILED":
67
+ raise Exception("Leonardo.ai generation failed.")
68
+ else:
69
+ ASCIIColors.info(f"Generation status: {status}. Waiting...")
70
+ time.sleep(3)
71
+
72
+ def generate_image(self, prompt: str, negative_prompt: str = "", width: int = 1024, height: int = 1024, **kwargs) -> bytes:
73
+ url = f"{self.base_url}/generations"
74
+ payload = {
75
+ "prompt": prompt,
76
+ "negative_prompt": negative_prompt,
77
+ "modelId": self.model_name,
78
+ "width": width,
79
+ "height": height,
80
+ "num_images": 1,
81
+ "guidance_scale": kwargs.get("guidance_scale", 7),
82
+ "seed": kwargs.get("seed"),
83
+ "sd_version": "SDXL" # Most models are SDXL based
84
+ }
85
+
86
+ try:
87
+ ASCIIColors.info(f"Submitting generation job to Leonardo.ai ({self.model_name})...")
88
+ response = requests.post(url, json=payload, headers=self.headers)
89
+ response.raise_for_status()
90
+ generation_id = response.json()["sdGenerationJob"]["generationId"]
91
+ ASCIIColors.info(f"Job submitted with ID: {generation_id}")
92
+ images = self._wait_for_generation(generation_id)
93
+ return images[0]
94
+ except Exception as e:
95
+ trace_exception(e)
96
+ try:
97
+ error_msg = response.json()
98
+ raise Exception(f"Leonardo.ai API error: {error_msg}")
99
+ except:
100
+ raise Exception(f"Leonardo.ai API request failed: {e}")
101
+
102
+ def edit_image(self, **kwargs) -> bytes:
103
+ ASCIIColors.warning("Leonardo.ai edit_image (inpainting/img2img) is not yet implemented in this binding.")
104
+ raise NotImplementedError("This binding does not yet support image editing.")
105
+
106
+ if __name__ == '__main__':
107
+ ASCIIColors.magenta("--- Leonardo.ai TTI Binding Test ---")
108
+ if "LEONARDO_API_KEY" not in os.environ:
109
+ ASCIIColors.error("LEONARDO_API_KEY environment variable not set. Cannot run test.")
110
+ exit(1)
111
+
112
+ try:
113
+ binding = LeonardoAITTIBinding()
114
+
115
+ ASCIIColors.cyan("\n--- Test: Text-to-Image ---")
116
+ prompt = "A majestic lion wearing a crown, hyperrealistic, 8k"
117
+ img_bytes = binding.generate_image(prompt, width=1024, height=1024)
118
+
119
+ assert len(img_bytes) > 1000
120
+ output_path = Path(__file__).parent / "tmp_leonardo_t2i.png"
121
+ with open(output_path, "wb") as f:
122
+ f.write(img_bytes)
123
+ ASCIIColors.green(f"Text-to-Image generation OK. Image saved to {output_path}")
124
+
125
+ except Exception as e:
126
+ trace_exception(e)
127
+ ASCIIColors.error(f"Leonardo.ai binding test failed: {e}")
@@ -23,7 +23,10 @@ class LollmsWebuiTTIBinding_Impl(LollmsTTIBinding):
23
23
  service_key (Optional[str]): Authentication key (used for client_id verification).
24
24
  verify_ssl_certificate (bool): Whether to verify SSL certificates.
25
25
  """
26
- super().__init__(binding_name="lollms")
26
+ # Prioritize 'model_name' but accept 'model' as an alias from config files.
27
+ if 'model' in kwargs and 'model_name' not in kwargs:
28
+ kwargs['model_name'] = kwargs.pop('model')
29
+ super().__init__(binding_name=BindingName, config=kwargs)
27
30
 
28
31
  # Extract parameters from kwargs, providing defaults
29
32
  self.host_address = kwargs.get("host_address", "http://localhost:9600") # Default LOLLMS host