返回提交历史
Modified
g4f/Provider/Yupp.py
+175
-238
XFEstudio/gpt4free
Refactor Yupp provider to use asyncio and aiohttp for asynchronous operations
0bc0ce7d
代码差异
1 个文件
+175
-238
@@ -3,11 +3,11 @@ import time
3
3
import uuid
4
4
import re
5
5
import os
6
from typing import Iterable, Optional, Dict, Any, Generator, List
7
import threading
8
import requests
6
import asyncio
7
import aiohttp
9
8
10
from ..providers.base_provider import AbstractProvider, ProviderModelMixin
9
from ..typing import AsyncResult, Messages, Optional, Dict, Any, List
10
from ..providers.base_provider import AsyncGeneratorProvider, ProviderModelMixin
11
11
from ..providers.response import Reasoning, PlainTextResponse, PreviewResponse, JsonConversation, ImageResponse, ProviderInfo
12
12
from ..errors import RateLimitError, ProviderException, MissingAuthError
13
13
from ..cookies import get_cookies
@@ -15,13 +15,12 @@ from ..tools.auth import AuthManager
15
15
from ..tools.media import merge_media
16
16
from ..image import is_accepted_format, to_bytes
17
17
from .yupp.models import YuppModelManager
18
from .helper import get_last_user_message
18
from .helper import get_last_user_message, format_prompt
19
19
from ..debug import log
20
20
21
# Global variables to manage Yupp accounts (should be set by your main application)
21
# Global variables to manage Yupp accounts
22
22
YUPP_ACCOUNTS: List[Dict[str, Any]] = []
23
YUPP_MODELS: List[Dict[str, Any]] = []
24
account_rotation_lock = threading.Lock()
23
account_rotation_lock = asyncio.Lock()
25
24
26
25
class YuppAccount:
27
26
"""Yupp account representation"""
@@ -32,7 +31,7 @@ class YuppAccount:
32
31
self.last_used = last_used
33
32
34
33
def load_yupp_accounts(tokens_str: str):
35
"""Load Yupp accounts from token string (compatible with your existing system)"""
34
"""Load Yupp accounts from token string"""
36
35
global YUPP_ACCOUNTS
37
36
if not tokens_str:
38
37
return
@@ -48,11 +47,9 @@ def load_yupp_accounts(tokens_str: str):
48
47
for token in tokens
49
48
]
50
49
51
def create_requests_session():
52
"""Create a requests session with proper headers"""
53
import requests
54
session = requests.Session()
55
session.headers.update({
50
def create_headers() -> Dict[str, str]:
51
"""Create headers for requests"""
52
return {
56
53
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36 Edg/137.0.0.0",
57
54
"Accept": "text/x-component, */*",
58
55
"Accept-Encoding": "gzip, deflate, br, zstd",
@@ -60,15 +57,14 @@ def create_requests_session():
60
57
"Sec-Fetch-Dest": "empty",
61
58
"Sec-Fetch-Mode": "cors",
62
59
"Sec-Fetch-Site": "same-origin",
63
})
64
return session
60
}
65
61
66
def get_best_yupp_account() -> Optional[Dict[str, Any]]:
67
"""Get the best available Yupp account using a smart selection algorithm."""
62
async def get_best_yupp_account() -> Optional[Dict[str, Any]]:
63
"""Get the best available Yupp account using smart selection algorithm"""
68
64
max_error_count = int(os.getenv("MAX_ERROR_COUNT", "3"))
69
65
error_cooldown = int(os.getenv("ERROR_COOLDOWN", "300"))
70
66
71
with account_rotation_lock:
67
async with account_rotation_lock:
72
68
now = time.time()
73
69
valid_accounts = [
74
70
acc
@@ -83,7 +79,7 @@ def get_best_yupp_account() -> Optional[Dict[str, Any]]:
83
79
if not valid_accounts:
84
80
return None
85
81
86
# Reset error count for accounts that have been in cooldown
82
# Reset error count for accounts in cooldown
87
83
for acc in valid_accounts:
88
84
if (
89
85
acc["error_count"] >= max_error_count
@@ -91,16 +87,15 @@ def get_best_yupp_account() -> Optional[Dict[str, Any]]:
91
87
):
92
88
acc["error_count"] = 0
93
89
94
# Sort by last used (oldest first) and error count (lowest first)
90
# Sort by last used and error count
95
91
valid_accounts.sort(key=lambda x: (x["last_used"], x["error_count"]))
96
92
account = valid_accounts[0]
97
93
account["last_used"] = now
98
94
return account
99
95
100
def claim_yupp_reward(account: Dict[str, Any], reward_id: str):
101
"""Claim Yupp reward synchronously"""
96
async def claim_yupp_reward(session: aiohttp.ClientSession, account: Dict[str, Any], reward_id: str):
97
"""Claim Yupp reward asynchronously"""
102
98
try:
103
import requests
104
99
log_debug(f"Claiming reward {reward_id}...")
105
100
url = "https://yupp.ai/api/trpc/reward.claim?batch=1"
106
101
payload = {"0": {"json": {"rewardId": reward_id}}}
@@ -108,22 +103,18 @@ def claim_yupp_reward(account: Dict[str, Any], reward_id: str):
108
103
"Content-Type": "application/json",
109
104
"Cookie": f"__Secure-yupp.session-token={account['token']}",
110
105
}
111
session = create_requests_session()
112
response = session.post(url, json=payload, headers=headers)
113
response.raise_for_status()
114
data = response.json()
115
balance = data[0]["result"]["data"]["json"]["currentCreditBalance"]
116
log_debug(f"Reward claimed successfully. New balance: {balance}")
117
return balance
106
async with session.post(url, json=payload, headers=headers) as response:
107
response.raise_for_status()
108
data = await response.json()
109
balance = data[0]["result"]["data"]["json"]["currentCreditBalance"]
110
log_debug(f"Reward claimed successfully. New balance: {balance}")
111
return balance
118
112
except Exception as e:
119
113
log_debug(f"Failed to claim reward {reward_id}. Error: {e}")
120
114
return None
121
115
122
def make_chat_private(account: Dict[str, Any], chat_id: str) -> bool:
123
"""
124
Set a Yupp chat's sharing status to PRIVATE.
125
Returns True if successful, False otherwise.
126
"""
116
async def make_chat_private(session: aiohttp.ClientSession, account: Dict[str, Any], chat_id: str) -> bool:
117
"""Set a Yupp chat's sharing status to PRIVATE"""
127
118
try:
128
119
log_debug(f"Setting chat {chat_id} to PRIVATE...")
129
120
url = "https://yupp.ai/api/trpc/chat.updateSharingSettings?batch=1"
@@ -140,34 +131,31 @@ def make_chat_private(account: Dict[str, Any], chat_id: str) -> bool:
140
131
"Cookie": f"__Secure-yupp.session-token={account['token']}",
141
132
}
142
133
143
session = create_requests_session()
144
response = session.post(url, json=payload, headers=headers)
145
response.raise_for_status()
146
147
data = response.json()
148
# Expected: [{"result":{"data":{"json":{}}}}]
149
if (
150
isinstance(data, list) and len(data) > 0
151
and "json" in data[0].get("result", {}).get("data", {})
152
):
153
log_debug(f"Chat {chat_id} is now PRIVATE ✅")
154
return True
134
async with session.post(url, json=payload, headers=headers) as response:
135
response.raise_for_status()
136
data = await response.json()
137
if (
138
isinstance(data, list) and len(data) > 0
139
and "json" in data[0].get("result", {}).get("data", {})
140
):
141
log_debug(f"Chat {chat_id} is now PRIVATE ✅")
142
return True
155
143
156
log_debug(f"Unexpected response while setting chat private: {data}")
157
return False
144
log_debug(f"Unexpected response while setting chat private: {data}")
145
return False
158
146
159
147
except Exception as e:
160
148
log_debug(f"Failed to make chat {chat_id} private: {e}")
161
149
return False
162
150
163
151
def log_debug(message: str):
164
"""Debug logging (can be replaced with your logging system)"""
152
"""Debug logging"""
165
153
if os.getenv("DEBUG_MODE", "false").lower() == "true":
166
154
print(f"[DEBUG] {message}")
167
155
else:
168
156
log(f"[Yupp] {message}")
169
157
170
def format_messages_for_yupp(messages: List[Dict[str, str]]) -> str:
158
def format_messages_for_yupp(messages: Messages) -> str:
171
159
"""Format multi-turn conversation for Yupp single-turn format"""
172
160
if not messages:
173
161
return ""
@@ -198,13 +186,12 @@ def format_messages_for_yupp(messages: List[Dict[str, str]]) -> str:
198
186
formatted.append("\n\nAssistant:")
199
187
200
188
result = "".join(formatted)
201
# Remove leading \n\n if present
202
189
if result.startswith("\n\n"):
203
190
result = result[2:]
204
191
205
192
return result
206
193
207
class Yupp(AbstractProvider, ProviderModelMixin):
194
class Yupp(AsyncGeneratorProvider, ProviderModelMixin):
208
195
"""
209
196
Yupp.ai Provider for g4f
210
197
Uses multiple account rotation and smart error handling
@@ -215,6 +202,7 @@ class Yupp(AbstractProvider, ProviderModelMixin):
215
202
working = True
216
203
active_by_default = True
217
204
supports_stream = True
205
218
206
@classmethod
219
207
def get_models(cls, api_key: str = None, **kwargs) -> List[str]:
220
208
if not cls.models:
@@ -234,25 +222,18 @@ class Yupp(AbstractProvider, ProviderModelMixin):
234
222
return cls.models
235
223
236
224
@classmethod
237
def create_completion(
225
async def create_async_generator(
238
226
cls,
239
227
model: str,
240
messages: List[Dict[str, str]] = None,
241
stream: bool = False,
242
api_key: Optional[str] = None,
243
prompt: Optional[str] = None,
244
conversation: JsonConversation = None,
245
mode:str = "text",
246
timeout:float = 60,
228
messages: Messages,
229
proxy: str = None,
247
230
**kwargs,
248
) -> Generator[str, Any, None]:
231
) -> AsyncResult:
249
232
"""
250
Create completion using Yupp.ai API with account rotation
251
:mode: Mode can be 'text' or 'image'
252
253
233
Create async completion using Yupp.ai API with account rotation
254
234
"""
255
# Initialize Yupp accounts and models
235
# Initialize Yupp accounts
236
api_key = kwargs.get("api_key")
256
237
if not api_key:
257
238
api_key = get_cookies("yupp.ai", False).get("__Secure-yupp.session-token")
258
239
if api_key:
@@ -261,135 +242,136 @@ class Yupp(AbstractProvider, ProviderModelMixin):
261
242
else:
262
243
raise MissingAuthError("No Yupp accounts configured. Set YUPP_API_KEY environment variable.")
263
244
264
if messages is None:
265
messages = []
266
267
# Format messages - use the new format_messages_for_yupp function
268
url_uuid = None
269
if conversation is not None:
270
url_uuid = conversation.url_uuid
271
272
# Determine the prompt based on conversation context
245
# Format messages
246
conversation = kwargs.get("conversation")
247
url_uuid = conversation.url_uuid if conversation else None
273
248
is_new_conversation = url_uuid is None
249
250
prompt = kwargs.get("prompt")
274
251
if prompt is None:
275
252
if is_new_conversation:
276
# New conversation - format all messages
277
253
prompt = format_messages_for_yupp(messages)
278
254
else:
279
# Continuing conversation - use only the last user message
280
255
prompt = get_last_user_message(messages, prompt)
281
256
282
257
log_debug(f"Use url_uuid: {url_uuid}, Formatted prompt length: {len(prompt)}, Is new conversation: {is_new_conversation}")
283
258
284
259
# Try all accounts with rotation
285
260
max_attempts = len(YUPP_ACCOUNTS)
286
261
for attempt in range(max_attempts):
287
account = get_best_yupp_account()
262
account = await get_best_yupp_account()
288
263
if not account:
289
264
raise ProviderException("No valid Yupp accounts available")
290
265
291
266
try:
292
# Prepare the request
293
session = create_requests_session()
294
turn_id = str(uuid.uuid4())
295
files = []
296
297
# Handle media attachments if any
298
media = kwargs.get("media", None)
299
if media:
300
for file, name in list(merge_media(media, messages)):
301
data = to_bytes(file)
302
presigned_resp = session.post(
303
"https://yupp.ai/api/trpc/chat.createPresignedURLForUpload?batch=1",
304
json={"0": {"json": {"fileName": name, "fileSize": len(data), "contentType": is_accepted_format(data)}}},
305
headers={"Content-Type": "application/json", "Cookie": f"__Secure-yupp.session-token={account['token']}"}
306
)
307
presigned_resp.raise_for_status()
308
upload_info = presigned_resp.json()[0]["result"]["data"]["json"]
309
upload_url = upload_info["signedUrl"]
310
session.put(upload_url, data=data, headers={"Content-Type": is_accepted_format(data), "Content-Length": str(len(data))})
311
attachment_resp = session.post(
312
"https://yupp.ai/api/trpc/chat.createAttachmentForUploadedFile?batch=1",
313
json={"0": {"json": {"fileName": name, "contentType": is_accepted_format(data), "fileId": upload_info["fileId"]}}},
314
cookies={"__Secure-yupp.session-token": account["token"]}
315
)
316
attachment_resp.raise_for_status()
317
attachment = attachment_resp.json()[0]["result"]["data"]["json"]
318
files.append({
319
"fileName": attachment["file_name"],
320
"contentType": attachment["content_type"],
321
"attachmentId": attachment["attachment_id"],
322
"chatMessageId": ""
323
})
324
325
# Build payload and URL - FIXED: Use consistent url_uuid handling
326
if is_new_conversation:
327
url_uuid = str(uuid.uuid4())
328
payload = [
329
url_uuid,
330
turn_id,
331
prompt,
332
"$undefined",
333
"$undefined",
334
files,
335
"$undefined",
336
[{"modelName": model, "promptModifierId": "$undefined"}] if model else "none",
337
mode,
338
True,
339
"$undefined",
340
]
341
url = f"https://yupp.ai/chat/{url_uuid}?stream=true"
342
# Yield the conversation info first
343
yield JsonConversation(url_uuid=url_uuid)
344
next_action = kwargs.get("next_action", "7f2a2308b5fc462a2c26df714cb2cccd02a9c10fbb")
345
else:
346
# Continuing existing conversation
347
payload = [
348
url_uuid,
349
turn_id,
350
prompt,
351
False,
352
[],
353
[{"modelName": model, "promptModifierId": "$undefined"}] if model else [],
354
mode,
355
files
356
]
357
url = f"https://yupp.ai/chat/{url_uuid}?stream=true"
358
next_action = kwargs.get("next_action", "7f1e9eec4ab22c8bfc73a50c026db603cd8380f87d")
359
360
headers = {
361
"accept": "text/x-component",
362
"content-type": "text/plain;charset=UTF-8",
363
"next-action": next_action,
364
"cookie": f"__Secure-yupp.session-token={account['token']}",
365
}
366
367
log_debug(f"Sending request to: {url}")
368
log_debug(f"Payload structure: {type(payload)}, length: {len(str(payload))}")
369
370
# Send request
371
response = session.post(url, data=json.dumps(payload), headers=headers, stream=True, timeout=timeout)
372
response.raise_for_status()
373
374
# Attempt to make chat private
375
try:
376
make_chat_private(account, url_uuid)
377
except Exception as e:
378
log_debug(f"Failed to set chat private for {url_uuid}: {e}")
379
380
# Yield streaming responses
381
yield from cls._process_stream_response(response.iter_lines(), account, session, prompt, model)
382
383
return # Exit after successful completion
267
async with aiohttp.ClientSession() as session:
268
turn_id = str(uuid.uuid4())
269
files = []
270
271
# Handle media attachments
272
media = kwargs.get("media")
273
if media:
274
for file, name in list(merge_media(media, messages)):
275
data = to_bytes(file)
276
presigned_resp = await session.post(
277
"https://yupp.ai/api/trpc/chat.createPresignedURLForUpload?batch=1",
278
json={"0": {"json": {"fileName": name, "fileSize": len(data), "contentType": is_accepted_format(data)}}},
279
headers={"Content-Type": "application/json", "Cookie": f"__Secure-yupp.session-token={account['token']}"}
280
)
281
presigned_resp.raise_for_status()
282
upload_info = (await presigned_resp.json())[0]["result"]["data"]["json"]
283
upload_url = upload_info["signedUrl"]
284
285
await session.put(
286
upload_url,
287
data=data,
288
headers={
289
"Content-Type": is_accepted_format(data),
290
"Content-Length": str(len(data))
291
}
292
)
293
294
attachment_resp = await session.post(
295
"https://yupp.ai/api/trpc/chat.createAttachmentForUploadedFile?batch=1",
296
json={"0": {"json": {"fileName": name, "contentType": is_accepted_format(data), "fileId": upload_info["fileId"]}}},
297
cookies={"__Secure-yupp.session-token": account["token"]}
298
)
299
attachment_resp.raise_for_status()
300
attachment = (await attachment_resp.json())[0]["result"]["data"]["json"]
301
files.append({
302
"fileName": attachment["file_name"],
303
"contentType": attachment["content_type"],
304
"attachmentId": attachment["attachment_id"],
305
"chatMessageId": ""
306
})
307
mode = "image" if model in cls.image_models else "text"
308
309
# Build payload and URL - FIXED: Use consistent url_uuid handling
310
if is_new_conversation:
311
url_uuid = str(uuid.uuid4())
312
payload = [
313
url_uuid,
314
turn_id,
315
prompt,
316
"$undefined",
317
"$undefined",
318
files,
319
"$undefined",
320
[{"modelName": model, "promptModifierId": "$undefined"}] if model else "none",
321
mode,
322
True,
323
"$undefined",
324
]
325
url = f"https://yupp.ai/chat/{url_uuid}?stream=true"
326
# Yield the conversation info first
327
yield JsonConversation(url_uuid=url_uuid)
328
next_action = kwargs.get("next_action", "7f2a2308b5fc462a2c26df714cb2cccd02a9c10fbb")
329
else:
330
# Continuing existing conversation
331
payload = [
332
url_uuid,
333
turn_id,
334
prompt,
335
False,
336
[],
337
[{"modelName": model, "promptModifierId": "$undefined"}] if model else [],
338
mode,
339
files
340
]
341
url = f"https://yupp.ai/chat/{url_uuid}?stream=true"
342
next_action = kwargs.get("next_action", "7f1e9eec4ab22c8bfc73a50c026db603cd8380f87d")
343
344
headers = {
345
"accept": "text/x-component",
346
"content-type": "text/plain;charset=UTF-8",
347
"next-action": next_action,
348
"cookie": f"__Secure-yupp.session-token={account['token']}",
349
}
350
351
log_debug(f"Sending request to: {url}")
352
log_debug(f"Payload structure: {type(payload)}, length: {len(str(payload))}")
353
354
# Send request
355
async with session.post(url, json=payload, headers=headers, proxy=proxy) as response:
356
response.raise_for_status()
357
358
# Make chat private in background
359
asyncio.create_task(make_chat_private(session, account, url_uuid))
360
361
# Process stream
362
async for chunk in cls._process_stream_response(response.content, account, session, prompt, model):
363
yield chunk
364
365
return
384
366
385
367
except RateLimitError:
386
368
log_debug(f"Account ...{account['token'][-4:]} hit rate limit, rotating")
387
with account_rotation_lock:
369
async with account_rotation_lock:
388
370
account["error_count"] += 1
389
371
continue
390
372
except ProviderException as e:
391
373
log_debug(f"Account ...{account['token'][-4:]} failed: {str(e)}")
392
with account_rotation_lock:
374
async with account_rotation_lock:
393
375
if "auth" in str(e).lower() or "401" in str(e) or "403" in str(e):
394
376
account["is_valid"] = False
395
377
else:
@@ -397,25 +379,24 @@ class Yupp(AbstractProvider, ProviderModelMixin):
397
379
continue
398
380
except Exception as e:
399
381
log_debug(f"Unexpected error with account ...{account['token'][-4:]}: {str(e)}")
400
with account_rotation_lock:
382
async with account_rotation_lock:
401
383
account["error_count"] += 1
402
384
raise ProviderException(f"Yupp request failed: {str(e)}") from e
403
385
404
386
raise ProviderException("All Yupp accounts failed after rotation attempts")
405
387
406
388
@classmethod
407
def _process_stream_response(
389
async def _process_stream_response(
408
390
cls,
409
response_lines: Iterable[bytes],
391
response_content,
410
392
account: Dict[str, Any],
411
session: requests.Session,
393
session: aiohttp.ClientSession,
412
394
prompt: str,
413
395
model_id: str
414
) -> Generator[str, Any, None]:
415
"""Process Yupp stream response and convert to OpenAI format"""
396
) -> AsyncResult:
397
"""Process Yupp stream response asynchronously"""
416
398
417
399
line_pattern = re.compile(b"^([0-9a-fA-F]+):(.*)")
418
chunks = {}
419
400
target_stream_id = None
420
401
reward_info = None
421
402
is_thinking = False
@@ -426,15 +407,13 @@ class Yupp(AbstractProvider, ProviderModelMixin):
426
407
def extract_ref_id(ref):
427
408
"""Extract ID from reference string, e.g., from '$@123' extract '123'"""
428
409
return ref[2:] if ref and isinstance(ref, str) and ref.startswith("$@") else None
429
430
410
def is_valid_content(content: str) -> bool:
431
"""Check if content is valid, avoid over-filtering"""
411
"""Check if content is valid"""
432
412
if not content or content in [None, "", "$undefined"]:
433
413
return False
434
435
414
return True
436
415
437
def process_content_chunk(content: str, chunk_id: str, line_count: int):
416
async def process_content_chunk(content: str, chunk_id: str, line_count: int):
438
417
"""Process single content chunk"""
439
418
nonlocal is_thinking, thinking_content, normal_content, session
440
419
@@ -443,10 +422,11 @@ class Yupp(AbstractProvider, ProviderModelMixin):
443
422
444
423
if '<yapp class="image-gen">' in content:
445
424
content = content.split('<yapp class="image-gen">').pop().split('</yapp>')[0]
446
url = f"https://yupp.ai/api/trpc/chat.getSignedImage"
447
response = session.get(url, params={"batch": "1", "input": json.dumps({"0": {"json": {"imageId": json.loads(content).get("image_id")}}})})
448
response.raise_for_status()
449
yield ImageResponse(response.json()[0]["result"]["data"]["json"]["signed_url"], prompt)
425
url = "https://yupp.ai/api/trpc/chat.getSignedImage"
426
async with session.get(url, params={"batch": "1", "input": json.dumps({"0": {"json": {"imageId": json.loads(content).get("image_id")}}})}) as resp:
427
resp.raise_for_status()
428
data = await resp.json()
429
yield ImageResponse(data[0]["result"]["data"]["json"]["signed_url"], prompt)
450
430
return
451
431
452
432
# log_debug(f"Processing chunk #{line_count} with content: '{content[:50]}...'")
@@ -458,7 +438,6 @@ class Yupp(AbstractProvider, ProviderModelMixin):
458
438
yield content
459
439
460
440
try:
461
# log_debug("Starting to process Yupp stream response...")
462
441
line_count = 0
463
442
quick_response_id = None
464
443
variant_stream_id = None
@@ -466,13 +445,11 @@ class Yupp(AbstractProvider, ProviderModelMixin):
466
445
variant_image: Optional[ImageResponse] = None
467
446
variant_text = ""
468
447
469
for line in response_lines:
470
448
async for line in response_content:
471
449
line_count += 1
472
450
473
451
match = line_pattern.match(line)
474
452
if not match:
475
log_debug(f"Line {line_count}: No pattern match")
476
453
continue
477
454
478
455
chunk_id, chunk_data = match.groups()
@@ -480,9 +457,7 @@ class Yupp(AbstractProvider, ProviderModelMixin):
480
457
481
458
try:
482
459
data = json.loads(chunk_data) if chunk_data != b"{}" else {}
483
chunks[chunk_id] = data
484
460
except json.JSONDecodeError:
485
log_debug(f"Failed to parse JSON for chunk {chunk_id}")
486
461
continue
487
462
488
463
# Process reward info
@@ -524,7 +499,7 @@ class Yupp(AbstractProvider, ProviderModelMixin):
524
499
target_stream_id = extract_ref_id(data.get("next"))
525
500
content = data.get("curr", "")
526
501
if content:
527
for chunk in process_content_chunk(content, chunk_id, line_count):
502
async for chunk in process_content_chunk(content, chunk_id, line_count):
528
503
is_started = True
529
504
yield chunk
530
505
@@ -534,10 +509,9 @@ class Yupp(AbstractProvider, ProviderModelMixin):
534
509
variant_stream_id = extract_ref_id(data.get("next"))
535
510
content = data.get("curr", "")
536
511
if content:
537
for chunk in process_content_chunk(content, chunk_id, line_count):
512
async for chunk in process_content_chunk(content, chunk_id, line_count):
538
513
if isinstance(chunk, ImageResponse):
539
variant_image = chunk
540
yield PreviewResponse(str(variant_image))
514
yield PreviewResponse(str(chunk))
541
515
else:
542
516
variant_text += str(chunk)
543
517
if not is_started:
@@ -550,7 +524,6 @@ class Yupp(AbstractProvider, ProviderModelMixin):
550
524
if content:
551
525
yield PreviewResponse(content)
552
526
553
# Fallback: process any chunk with "curr"
554
527
elif isinstance(data, dict) and "curr" in data:
555
528
content = data.get("curr", "")
556
529
if content:
@@ -571,40 +544,4 @@ class Yupp(AbstractProvider, ProviderModelMixin):
571
544
if reward_info and "unclaimedRewardInfo" in reward_info:
572
545
reward_id = reward_info["unclaimedRewardInfo"].get("rewardId")
573
546
if reward_id:
574
try:
575
claim_yupp_reward(account, reward_id)
576
except Exception as e:
577
log_debug(f"Failed to claim reward: {e}")
578
579
# log_debug(f"Stream completed. Content length: {len(normal_content)}")
580
581
# Initialize the provider
582
def init_yupp_provider():
583
"""Initialize Yupp provider with environment configuration"""
584
tokens = os.getenv("YUPP_TOKENS", "")
585
if tokens:
586
load_yupp_accounts(tokens)
587
588
log_debug(f"Yupp provider initialized: {len(YUPP_ACCOUNTS)} accounts, {len(YUPP_MODELS)} models")
589
return Yupp
590
591
# Example usage and testing
592
if __name__ == "__main__":
593
# Set up environment for testing
594
os.environ["DEBUG_MODE"] = "true"
595
596
# Initialize provider
597
provider = init_yupp_provider()
598
599
# Test stream completion
600
try:
601
print("\nTesting stream completion...")
602
for chunk in provider.create_completion(
603
model="claude-sonnet-4-5-20250929<>thinking",
604
messages=[{"role": "user", "content": "What is Python?"}],
605
stream=True
606
):
607
if isinstance(chunk, str) and chunk.strip():
608
print(chunk, end="")
609
except Exception as e:
610
print(f"\nStream test failed: {e}")
547
await claim_yupp_reward(session, account, reward_id)