webscout 2025.10.16__py3-none-any.whl → 2025.10.18__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.
Potentially problematic release.
This version of webscout might be problematic. Click here for more details.
- webscout/Provider/ClaudeOnline.py +350 -0
- webscout/Provider/Gradient.py +231 -0
- webscout/Provider/TTI/claudeonline.py +315 -0
- webscout/Provider/TogetherAI.py +139 -199
- webscout/version.py +1 -1
- webscout/version.py.bak +1 -1
- {webscout-2025.10.16.dist-info → webscout-2025.10.18.dist-info}/METADATA +2 -3
- {webscout-2025.10.16.dist-info → webscout-2025.10.18.dist-info}/RECORD +12 -10
- webscout/Extra/weather.md +0 -281
- {webscout-2025.10.16.dist-info → webscout-2025.10.18.dist-info}/WHEEL +0 -0
- {webscout-2025.10.16.dist-info → webscout-2025.10.18.dist-info}/entry_points.txt +0 -0
- {webscout-2025.10.16.dist-info → webscout-2025.10.18.dist-info}/licenses/LICENSE.md +0 -0
- {webscout-2025.10.16.dist-info → webscout-2025.10.18.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
import base64
|
|
2
|
+
import json
|
|
3
|
+
import os
|
|
4
|
+
import tempfile
|
|
5
|
+
import time
|
|
6
|
+
from io import BytesIO
|
|
7
|
+
from typing import Optional
|
|
8
|
+
|
|
9
|
+
import requests
|
|
10
|
+
from requests.exceptions import RequestException
|
|
11
|
+
|
|
12
|
+
from webscout.litagent import LitAgent
|
|
13
|
+
from webscout.Provider.TTI.base import BaseImages, TTICompatibleProvider
|
|
14
|
+
from webscout.Provider.TTI.utils import ImageData, ImageResponse
|
|
15
|
+
|
|
16
|
+
try:
|
|
17
|
+
from PIL import Image
|
|
18
|
+
except ImportError:
|
|
19
|
+
Image = None
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class Images(BaseImages):
|
|
23
|
+
def __init__(self, client):
|
|
24
|
+
self._client = client
|
|
25
|
+
|
|
26
|
+
def create(
|
|
27
|
+
self,
|
|
28
|
+
*,
|
|
29
|
+
model: str,
|
|
30
|
+
prompt: str,
|
|
31
|
+
n: int = 1,
|
|
32
|
+
size: str = "1024x1024",
|
|
33
|
+
response_format: str = "url",
|
|
34
|
+
user: Optional[str] = None,
|
|
35
|
+
style: str = "none",
|
|
36
|
+
aspect_ratio: str = "1:1",
|
|
37
|
+
timeout: int = 60,
|
|
38
|
+
image_format: str = "png",
|
|
39
|
+
seed: Optional[int] = None,
|
|
40
|
+
**kwargs,
|
|
41
|
+
) -> ImageResponse:
|
|
42
|
+
"""
|
|
43
|
+
Generate images using Claude Online's /imagine feature via Pollinations.ai.
|
|
44
|
+
|
|
45
|
+
Args:
|
|
46
|
+
model: Model to use (ignored, uses Pollinations.ai)
|
|
47
|
+
prompt: The image generation prompt
|
|
48
|
+
n: Number of images to generate (max 1 for Claude Online)
|
|
49
|
+
size: Image size (supports various sizes)
|
|
50
|
+
response_format: "url" or "b64_json"
|
|
51
|
+
timeout: Request timeout in seconds
|
|
52
|
+
image_format: Output format "png" or "jpeg"
|
|
53
|
+
**kwargs: Additional parameters
|
|
54
|
+
|
|
55
|
+
Returns:
|
|
56
|
+
ImageResponse with generated image data
|
|
57
|
+
"""
|
|
58
|
+
if Image is None:
|
|
59
|
+
raise ImportError("Pillow (PIL) is required for image format conversion.")
|
|
60
|
+
|
|
61
|
+
# Claude Online only supports 1 image per request
|
|
62
|
+
if n > 1:
|
|
63
|
+
raise ValueError("Claude Online only supports generating 1 image per request")
|
|
64
|
+
|
|
65
|
+
# Parse size parameter
|
|
66
|
+
width, height = self._parse_size(size)
|
|
67
|
+
|
|
68
|
+
try:
|
|
69
|
+
# Clean the prompt (remove command words if present)
|
|
70
|
+
clean_prompt = self._clean_prompt(prompt)
|
|
71
|
+
|
|
72
|
+
# Generate image using Pollinations.ai API
|
|
73
|
+
timestamp = int(time.time() * 1000) # Use timestamp as seed for uniqueness
|
|
74
|
+
seed_value = seed if seed is not None else timestamp
|
|
75
|
+
|
|
76
|
+
# Build the Pollinations.ai URL
|
|
77
|
+
base_url = "https://image.pollinations.ai/prompt"
|
|
78
|
+
params = {
|
|
79
|
+
"width": width,
|
|
80
|
+
"height": height,
|
|
81
|
+
"nologo": "true",
|
|
82
|
+
"seed": seed_value
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
image_url = f"{base_url}/{clean_prompt}"
|
|
86
|
+
query_params = "&".join([f"{k}={v}" for k, v in params.items()])
|
|
87
|
+
full_image_url = f"{image_url}?{query_params}"
|
|
88
|
+
|
|
89
|
+
# Download the image
|
|
90
|
+
response = requests.get(full_image_url, timeout=timeout, stream=True)
|
|
91
|
+
response.raise_for_status()
|
|
92
|
+
|
|
93
|
+
img_bytes = response.content
|
|
94
|
+
|
|
95
|
+
# Convert image format if needed
|
|
96
|
+
with BytesIO(img_bytes) as input_io:
|
|
97
|
+
with Image.open(input_io) as im:
|
|
98
|
+
out_io = BytesIO()
|
|
99
|
+
if image_format.lower() == "jpeg":
|
|
100
|
+
im = im.convert("RGB")
|
|
101
|
+
im.save(out_io, format="JPEG")
|
|
102
|
+
else:
|
|
103
|
+
im.save(out_io, format="PNG")
|
|
104
|
+
processed_img_bytes = out_io.getvalue()
|
|
105
|
+
|
|
106
|
+
# Handle response format
|
|
107
|
+
if response_format == "url":
|
|
108
|
+
# Upload to image hosting service
|
|
109
|
+
uploaded_url = self._upload_image(processed_img_bytes, image_format)
|
|
110
|
+
if not uploaded_url:
|
|
111
|
+
raise RuntimeError("Failed to upload generated image")
|
|
112
|
+
result_data = [ImageData(url=uploaded_url)]
|
|
113
|
+
elif response_format == "b64_json":
|
|
114
|
+
b64 = base64.b64encode(processed_img_bytes).decode("utf-8")
|
|
115
|
+
result_data = [ImageData(b64_json=b64)]
|
|
116
|
+
else:
|
|
117
|
+
raise ValueError("response_format must be 'url' or 'b64_json'")
|
|
118
|
+
|
|
119
|
+
return ImageResponse(created=int(time.time()), data=result_data)
|
|
120
|
+
|
|
121
|
+
except RequestException as e:
|
|
122
|
+
raise RuntimeError(f"Failed to generate image with Claude Online: {e}")
|
|
123
|
+
except Exception as e:
|
|
124
|
+
raise RuntimeError(f"Unexpected error during image generation: {e}")
|
|
125
|
+
|
|
126
|
+
def _parse_size(self, size: str) -> tuple[int, int]:
|
|
127
|
+
"""Parse size string into width and height."""
|
|
128
|
+
size = size.lower().strip()
|
|
129
|
+
|
|
130
|
+
# Handle common size formats
|
|
131
|
+
size_map = {
|
|
132
|
+
"256x256": (256, 256),
|
|
133
|
+
"512x512": (512, 512),
|
|
134
|
+
"1024x1024": (1024, 1024),
|
|
135
|
+
"1024x768": (1024, 768),
|
|
136
|
+
"768x1024": (768, 1024),
|
|
137
|
+
"1280x720": (1280, 720),
|
|
138
|
+
"720x1280": (720, 1280),
|
|
139
|
+
"1920x1080": (1920, 1080),
|
|
140
|
+
"1080x1920": (1080, 1920),
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if size in size_map:
|
|
144
|
+
return size_map[size]
|
|
145
|
+
|
|
146
|
+
# Try to parse custom size (e.g., "800x600")
|
|
147
|
+
try:
|
|
148
|
+
width, height = size.split("x")
|
|
149
|
+
return int(width), int(height)
|
|
150
|
+
except (ValueError, AttributeError):
|
|
151
|
+
# Default to 1024x1024
|
|
152
|
+
return 1024, 1024
|
|
153
|
+
|
|
154
|
+
def _clean_prompt(self, prompt: str) -> str:
|
|
155
|
+
"""Clean the prompt by removing command prefixes."""
|
|
156
|
+
# Remove common image generation command prefixes
|
|
157
|
+
prefixes_to_remove = [
|
|
158
|
+
r'^/imagine\s*',
|
|
159
|
+
r'^/image\s*',
|
|
160
|
+
r'^/picture\s*',
|
|
161
|
+
r'^/draw\s*',
|
|
162
|
+
r'^/create\s*',
|
|
163
|
+
r'^/generate\s*',
|
|
164
|
+
r'^создай изображение\s*',
|
|
165
|
+
r'^нарисуй\s*',
|
|
166
|
+
r'^сгенерируй картинку\s*',
|
|
167
|
+
]
|
|
168
|
+
|
|
169
|
+
import re
|
|
170
|
+
clean_prompt = prompt
|
|
171
|
+
for prefix in prefixes_to_remove:
|
|
172
|
+
clean_prompt = re.sub(prefix, '', clean_prompt, flags=re.IGNORECASE)
|
|
173
|
+
|
|
174
|
+
return clean_prompt.strip()
|
|
175
|
+
|
|
176
|
+
def _upload_image(self, img_bytes: bytes, image_format: str, max_retries: int = 3) -> Optional[str]:
|
|
177
|
+
"""Upload image to hosting service and return URL"""
|
|
178
|
+
|
|
179
|
+
def upload_to_catbox(img_bytes, image_format):
|
|
180
|
+
"""Upload to catbox.moe"""
|
|
181
|
+
ext = "jpg" if image_format.lower() == "jpeg" else "png"
|
|
182
|
+
tmp_path = None
|
|
183
|
+
try:
|
|
184
|
+
with tempfile.NamedTemporaryFile(suffix=f".{ext}", delete=False) as tmp:
|
|
185
|
+
tmp.write(img_bytes)
|
|
186
|
+
tmp.flush()
|
|
187
|
+
tmp_path = tmp.name
|
|
188
|
+
|
|
189
|
+
with open(tmp_path, "rb") as f:
|
|
190
|
+
files = {"fileToUpload": (f"image.{ext}", f, f"image/{ext}")}
|
|
191
|
+
data = {"reqtype": "fileupload", "json": "true"}
|
|
192
|
+
headers = {"User-Agent": LitAgent().random()}
|
|
193
|
+
|
|
194
|
+
resp = requests.post(
|
|
195
|
+
"https://catbox.moe/user/api.php",
|
|
196
|
+
files=files,
|
|
197
|
+
data=data,
|
|
198
|
+
headers=headers,
|
|
199
|
+
timeout=30,
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
if resp.status_code == 200 and resp.text.strip():
|
|
203
|
+
text = resp.text.strip()
|
|
204
|
+
if text.startswith("http"):
|
|
205
|
+
return text
|
|
206
|
+
try:
|
|
207
|
+
result = resp.json()
|
|
208
|
+
if "url" in result:
|
|
209
|
+
return result["url"]
|
|
210
|
+
except json.JSONDecodeError:
|
|
211
|
+
pass
|
|
212
|
+
except Exception:
|
|
213
|
+
pass
|
|
214
|
+
finally:
|
|
215
|
+
if tmp_path and os.path.isfile(tmp_path):
|
|
216
|
+
try:
|
|
217
|
+
os.remove(tmp_path)
|
|
218
|
+
except Exception:
|
|
219
|
+
pass
|
|
220
|
+
return None
|
|
221
|
+
|
|
222
|
+
def upload_to_0x0(img_bytes, image_format):
|
|
223
|
+
"""Upload to 0x0.st as fallback"""
|
|
224
|
+
ext = "jpg" if image_format.lower() == "jpeg" else "png"
|
|
225
|
+
tmp_path = None
|
|
226
|
+
try:
|
|
227
|
+
with tempfile.NamedTemporaryFile(suffix=f".{ext}", delete=False) as tmp:
|
|
228
|
+
tmp.write(img_bytes)
|
|
229
|
+
tmp.flush()
|
|
230
|
+
tmp_path = tmp.name
|
|
231
|
+
|
|
232
|
+
with open(tmp_path, "rb") as img_file:
|
|
233
|
+
files = {"file": img_file}
|
|
234
|
+
response = requests.post("https://0x0.st", files=files, timeout=30)
|
|
235
|
+
response.raise_for_status()
|
|
236
|
+
image_url = response.text.strip()
|
|
237
|
+
if image_url.startswith("http"):
|
|
238
|
+
return image_url
|
|
239
|
+
except Exception:
|
|
240
|
+
pass
|
|
241
|
+
finally:
|
|
242
|
+
if tmp_path and os.path.isfile(tmp_path):
|
|
243
|
+
try:
|
|
244
|
+
os.remove(tmp_path)
|
|
245
|
+
except Exception:
|
|
246
|
+
pass
|
|
247
|
+
return None
|
|
248
|
+
|
|
249
|
+
# Try primary upload method
|
|
250
|
+
for attempt in range(max_retries):
|
|
251
|
+
uploaded_url = upload_to_catbox(img_bytes, image_format)
|
|
252
|
+
if uploaded_url:
|
|
253
|
+
return uploaded_url
|
|
254
|
+
time.sleep(1 * (attempt + 1))
|
|
255
|
+
|
|
256
|
+
# Try fallback method
|
|
257
|
+
for attempt in range(max_retries):
|
|
258
|
+
uploaded_url = upload_to_0x0(img_bytes, image_format)
|
|
259
|
+
if uploaded_url:
|
|
260
|
+
return uploaded_url
|
|
261
|
+
time.sleep(1 * (attempt + 1))
|
|
262
|
+
|
|
263
|
+
return None
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
class ClaudeOnlineTTI(TTICompatibleProvider):
|
|
267
|
+
"""
|
|
268
|
+
Claude Online Text-to-Image Provider
|
|
269
|
+
|
|
270
|
+
Uses Claude Online's /imagine feature with Pollinations.ai backend.
|
|
271
|
+
Supports high-quality image generation with various styles and sizes.
|
|
272
|
+
"""
|
|
273
|
+
|
|
274
|
+
AVAILABLE_MODELS = ["claude-imagine"]
|
|
275
|
+
|
|
276
|
+
def __init__(self):
|
|
277
|
+
self.api_endpoint = "https://image.pollinations.ai/prompt"
|
|
278
|
+
self.session = requests.Session()
|
|
279
|
+
self.user_agent = LitAgent().random()
|
|
280
|
+
self.headers = {
|
|
281
|
+
"accept": "image/*",
|
|
282
|
+
"accept-language": "en-US,en;q=0.9",
|
|
283
|
+
"user-agent": self.user_agent,
|
|
284
|
+
}
|
|
285
|
+
self.session.headers.update(self.headers)
|
|
286
|
+
self.images = Images(self)
|
|
287
|
+
|
|
288
|
+
@property
|
|
289
|
+
def models(self):
|
|
290
|
+
class _ModelList:
|
|
291
|
+
def list(inner_self):
|
|
292
|
+
return type(self).AVAILABLE_MODELS
|
|
293
|
+
|
|
294
|
+
return _ModelList()
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
if __name__ == "__main__":
|
|
298
|
+
from rich import print
|
|
299
|
+
|
|
300
|
+
# Test the Claude Online TTI provider
|
|
301
|
+
client = ClaudeOnlineTTI()
|
|
302
|
+
|
|
303
|
+
try:
|
|
304
|
+
response = client.images.create(
|
|
305
|
+
model="claude-imagine",
|
|
306
|
+
prompt="a beautiful sunset over mountains with vibrant colors",
|
|
307
|
+
response_format="url",
|
|
308
|
+
timeout=60,
|
|
309
|
+
)
|
|
310
|
+
print("✅ Image generation successful!")
|
|
311
|
+
print(response)
|
|
312
|
+
except Exception as e:
|
|
313
|
+
print(f"❌ Image generation failed: {e}")
|
|
314
|
+
import traceback
|
|
315
|
+
traceback.print_exc()
|