mirror of
https://github.com/xtekky/gpt4free.git
synced 2025-12-15 14:51:19 -08:00
Refactor Yupp provider to use asyncio and aiohttp for asynchronous operations
This commit is contained in:
parent
e724893a29
commit
0bc0ce7df1
1 changed files with 167 additions and 230 deletions
|
|
@ -3,11 +3,11 @@ import time
|
|||
import uuid
|
||||
import re
|
||||
import os
|
||||
from typing import Iterable, Optional, Dict, Any, Generator, List
|
||||
import threading
|
||||
import requests
|
||||
import asyncio
|
||||
import aiohttp
|
||||
|
||||
from ..providers.base_provider import AbstractProvider, ProviderModelMixin
|
||||
from ..typing import AsyncResult, Messages, Optional, Dict, Any, List
|
||||
from ..providers.base_provider import AsyncGeneratorProvider, ProviderModelMixin
|
||||
from ..providers.response import Reasoning, PlainTextResponse, PreviewResponse, JsonConversation, ImageResponse, ProviderInfo
|
||||
from ..errors import RateLimitError, ProviderException, MissingAuthError
|
||||
from ..cookies import get_cookies
|
||||
|
|
@ -15,13 +15,12 @@ from ..tools.auth import AuthManager
|
|||
from ..tools.media import merge_media
|
||||
from ..image import is_accepted_format, to_bytes
|
||||
from .yupp.models import YuppModelManager
|
||||
from .helper import get_last_user_message
|
||||
from .helper import get_last_user_message, format_prompt
|
||||
from ..debug import log
|
||||
|
||||
# Global variables to manage Yupp accounts (should be set by your main application)
|
||||
# Global variables to manage Yupp accounts
|
||||
YUPP_ACCOUNTS: List[Dict[str, Any]] = []
|
||||
YUPP_MODELS: List[Dict[str, Any]] = []
|
||||
account_rotation_lock = threading.Lock()
|
||||
account_rotation_lock = asyncio.Lock()
|
||||
|
||||
class YuppAccount:
|
||||
"""Yupp account representation"""
|
||||
|
|
@ -32,7 +31,7 @@ class YuppAccount:
|
|||
self.last_used = last_used
|
||||
|
||||
def load_yupp_accounts(tokens_str: str):
|
||||
"""Load Yupp accounts from token string (compatible with your existing system)"""
|
||||
"""Load Yupp accounts from token string"""
|
||||
global YUPP_ACCOUNTS
|
||||
if not tokens_str:
|
||||
return
|
||||
|
|
@ -48,11 +47,9 @@ def load_yupp_accounts(tokens_str: str):
|
|||
for token in tokens
|
||||
]
|
||||
|
||||
def create_requests_session():
|
||||
"""Create a requests session with proper headers"""
|
||||
import requests
|
||||
session = requests.Session()
|
||||
session.headers.update({
|
||||
def create_headers() -> Dict[str, str]:
|
||||
"""Create headers for requests"""
|
||||
return {
|
||||
"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",
|
||||
"Accept": "text/x-component, */*",
|
||||
"Accept-Encoding": "gzip, deflate, br, zstd",
|
||||
|
|
@ -60,15 +57,14 @@ def create_requests_session():
|
|||
"Sec-Fetch-Dest": "empty",
|
||||
"Sec-Fetch-Mode": "cors",
|
||||
"Sec-Fetch-Site": "same-origin",
|
||||
})
|
||||
return session
|
||||
}
|
||||
|
||||
def get_best_yupp_account() -> Optional[Dict[str, Any]]:
|
||||
"""Get the best available Yupp account using a smart selection algorithm."""
|
||||
async def get_best_yupp_account() -> Optional[Dict[str, Any]]:
|
||||
"""Get the best available Yupp account using smart selection algorithm"""
|
||||
max_error_count = int(os.getenv("MAX_ERROR_COUNT", "3"))
|
||||
error_cooldown = int(os.getenv("ERROR_COOLDOWN", "300"))
|
||||
|
||||
with account_rotation_lock:
|
||||
async with account_rotation_lock:
|
||||
now = time.time()
|
||||
valid_accounts = [
|
||||
acc
|
||||
|
|
@ -83,7 +79,7 @@ def get_best_yupp_account() -> Optional[Dict[str, Any]]:
|
|||
if not valid_accounts:
|
||||
return None
|
||||
|
||||
# Reset error count for accounts that have been in cooldown
|
||||
# Reset error count for accounts in cooldown
|
||||
for acc in valid_accounts:
|
||||
if (
|
||||
acc["error_count"] >= max_error_count
|
||||
|
|
@ -91,16 +87,15 @@ def get_best_yupp_account() -> Optional[Dict[str, Any]]:
|
|||
):
|
||||
acc["error_count"] = 0
|
||||
|
||||
# Sort by last used (oldest first) and error count (lowest first)
|
||||
# Sort by last used and error count
|
||||
valid_accounts.sort(key=lambda x: (x["last_used"], x["error_count"]))
|
||||
account = valid_accounts[0]
|
||||
account["last_used"] = now
|
||||
return account
|
||||
|
||||
def claim_yupp_reward(account: Dict[str, Any], reward_id: str):
|
||||
"""Claim Yupp reward synchronously"""
|
||||
async def claim_yupp_reward(session: aiohttp.ClientSession, account: Dict[str, Any], reward_id: str):
|
||||
"""Claim Yupp reward asynchronously"""
|
||||
try:
|
||||
import requests
|
||||
log_debug(f"Claiming reward {reward_id}...")
|
||||
url = "https://yupp.ai/api/trpc/reward.claim?batch=1"
|
||||
payload = {"0": {"json": {"rewardId": reward_id}}}
|
||||
|
|
@ -108,22 +103,18 @@ def claim_yupp_reward(account: Dict[str, Any], reward_id: str):
|
|||
"Content-Type": "application/json",
|
||||
"Cookie": f"__Secure-yupp.session-token={account['token']}",
|
||||
}
|
||||
session = create_requests_session()
|
||||
response = session.post(url, json=payload, headers=headers)
|
||||
response.raise_for_status()
|
||||
data = response.json()
|
||||
balance = data[0]["result"]["data"]["json"]["currentCreditBalance"]
|
||||
log_debug(f"Reward claimed successfully. New balance: {balance}")
|
||||
return balance
|
||||
async with session.post(url, json=payload, headers=headers) as response:
|
||||
response.raise_for_status()
|
||||
data = await response.json()
|
||||
balance = data[0]["result"]["data"]["json"]["currentCreditBalance"]
|
||||
log_debug(f"Reward claimed successfully. New balance: {balance}")
|
||||
return balance
|
||||
except Exception as e:
|
||||
log_debug(f"Failed to claim reward {reward_id}. Error: {e}")
|
||||
return None
|
||||
|
||||
def make_chat_private(account: Dict[str, Any], chat_id: str) -> bool:
|
||||
"""
|
||||
Set a Yupp chat's sharing status to PRIVATE.
|
||||
Returns True if successful, False otherwise.
|
||||
"""
|
||||
async def make_chat_private(session: aiohttp.ClientSession, account: Dict[str, Any], chat_id: str) -> bool:
|
||||
"""Set a Yupp chat's sharing status to PRIVATE"""
|
||||
try:
|
||||
log_debug(f"Setting chat {chat_id} to PRIVATE...")
|
||||
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:
|
|||
"Cookie": f"__Secure-yupp.session-token={account['token']}",
|
||||
}
|
||||
|
||||
session = create_requests_session()
|
||||
response = session.post(url, json=payload, headers=headers)
|
||||
response.raise_for_status()
|
||||
async with session.post(url, json=payload, headers=headers) as response:
|
||||
response.raise_for_status()
|
||||
data = await response.json()
|
||||
if (
|
||||
isinstance(data, list) and len(data) > 0
|
||||
and "json" in data[0].get("result", {}).get("data", {})
|
||||
):
|
||||
log_debug(f"Chat {chat_id} is now PRIVATE ✅")
|
||||
return True
|
||||
|
||||
data = response.json()
|
||||
# Expected: [{"result":{"data":{"json":{}}}}]
|
||||
if (
|
||||
isinstance(data, list) and len(data) > 0
|
||||
and "json" in data[0].get("result", {}).get("data", {})
|
||||
):
|
||||
log_debug(f"Chat {chat_id} is now PRIVATE ✅")
|
||||
return True
|
||||
|
||||
log_debug(f"Unexpected response while setting chat private: {data}")
|
||||
return False
|
||||
log_debug(f"Unexpected response while setting chat private: {data}")
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
log_debug(f"Failed to make chat {chat_id} private: {e}")
|
||||
return False
|
||||
|
||||
def log_debug(message: str):
|
||||
"""Debug logging (can be replaced with your logging system)"""
|
||||
"""Debug logging"""
|
||||
if os.getenv("DEBUG_MODE", "false").lower() == "true":
|
||||
print(f"[DEBUG] {message}")
|
||||
else:
|
||||
log(f"[Yupp] {message}")
|
||||
|
||||
def format_messages_for_yupp(messages: List[Dict[str, str]]) -> str:
|
||||
def format_messages_for_yupp(messages: Messages) -> str:
|
||||
"""Format multi-turn conversation for Yupp single-turn format"""
|
||||
if not messages:
|
||||
return ""
|
||||
|
|
@ -198,13 +186,12 @@ def format_messages_for_yupp(messages: List[Dict[str, str]]) -> str:
|
|||
formatted.append("\n\nAssistant:")
|
||||
|
||||
result = "".join(formatted)
|
||||
# Remove leading \n\n if present
|
||||
if result.startswith("\n\n"):
|
||||
result = result[2:]
|
||||
|
||||
return result
|
||||
|
||||
class Yupp(AbstractProvider, ProviderModelMixin):
|
||||
class Yupp(AsyncGeneratorProvider, ProviderModelMixin):
|
||||
"""
|
||||
Yupp.ai Provider for g4f
|
||||
Uses multiple account rotation and smart error handling
|
||||
|
|
@ -215,6 +202,7 @@ class Yupp(AbstractProvider, ProviderModelMixin):
|
|||
working = True
|
||||
active_by_default = True
|
||||
supports_stream = True
|
||||
|
||||
@classmethod
|
||||
def get_models(cls, api_key: str = None, **kwargs) -> List[str]:
|
||||
if not cls.models:
|
||||
|
|
@ -234,25 +222,18 @@ class Yupp(AbstractProvider, ProviderModelMixin):
|
|||
return cls.models
|
||||
|
||||
@classmethod
|
||||
def create_completion(
|
||||
async def create_async_generator(
|
||||
cls,
|
||||
model: str,
|
||||
messages: List[Dict[str, str]] = None,
|
||||
stream: bool = False,
|
||||
api_key: Optional[str] = None,
|
||||
prompt: Optional[str] = None,
|
||||
conversation: JsonConversation = None,
|
||||
mode:str = "text",
|
||||
timeout:float = 60,
|
||||
messages: Messages,
|
||||
proxy: str = None,
|
||||
**kwargs,
|
||||
) -> Generator[str, Any, None]:
|
||||
) -> AsyncResult:
|
||||
"""
|
||||
Create completion using Yupp.ai API with account rotation
|
||||
:mode: Mode can be 'text' or 'image'
|
||||
|
||||
|
||||
Create async completion using Yupp.ai API with account rotation
|
||||
"""
|
||||
# Initialize Yupp accounts and models
|
||||
# Initialize Yupp accounts
|
||||
api_key = kwargs.get("api_key")
|
||||
if not api_key:
|
||||
api_key = get_cookies("yupp.ai", False).get("__Secure-yupp.session-token")
|
||||
if api_key:
|
||||
|
|
@ -261,135 +242,136 @@ class Yupp(AbstractProvider, ProviderModelMixin):
|
|||
else:
|
||||
raise MissingAuthError("No Yupp accounts configured. Set YUPP_API_KEY environment variable.")
|
||||
|
||||
if messages is None:
|
||||
messages = []
|
||||
|
||||
# Format messages - use the new format_messages_for_yupp function
|
||||
url_uuid = None
|
||||
if conversation is not None:
|
||||
url_uuid = conversation.url_uuid
|
||||
|
||||
# Determine the prompt based on conversation context
|
||||
# Format messages
|
||||
conversation = kwargs.get("conversation")
|
||||
url_uuid = conversation.url_uuid if conversation else None
|
||||
is_new_conversation = url_uuid is None
|
||||
|
||||
prompt = kwargs.get("prompt")
|
||||
if prompt is None:
|
||||
if is_new_conversation:
|
||||
# New conversation - format all messages
|
||||
prompt = format_messages_for_yupp(messages)
|
||||
else:
|
||||
# Continuing conversation - use only the last user message
|
||||
prompt = get_last_user_message(messages, prompt)
|
||||
|
||||
|
||||
log_debug(f"Use url_uuid: {url_uuid}, Formatted prompt length: {len(prompt)}, Is new conversation: {is_new_conversation}")
|
||||
|
||||
# Try all accounts with rotation
|
||||
max_attempts = len(YUPP_ACCOUNTS)
|
||||
for attempt in range(max_attempts):
|
||||
account = get_best_yupp_account()
|
||||
account = await get_best_yupp_account()
|
||||
if not account:
|
||||
raise ProviderException("No valid Yupp accounts available")
|
||||
|
||||
try:
|
||||
# Prepare the request
|
||||
session = create_requests_session()
|
||||
turn_id = str(uuid.uuid4())
|
||||
files = []
|
||||
async with aiohttp.ClientSession() as session:
|
||||
turn_id = str(uuid.uuid4())
|
||||
files = []
|
||||
|
||||
# Handle media attachments if any
|
||||
media = kwargs.get("media", None)
|
||||
if media:
|
||||
for file, name in list(merge_media(media, messages)):
|
||||
data = to_bytes(file)
|
||||
presigned_resp = session.post(
|
||||
"https://yupp.ai/api/trpc/chat.createPresignedURLForUpload?batch=1",
|
||||
json={"0": {"json": {"fileName": name, "fileSize": len(data), "contentType": is_accepted_format(data)}}},
|
||||
headers={"Content-Type": "application/json", "Cookie": f"__Secure-yupp.session-token={account['token']}"}
|
||||
)
|
||||
presigned_resp.raise_for_status()
|
||||
upload_info = presigned_resp.json()[0]["result"]["data"]["json"]
|
||||
upload_url = upload_info["signedUrl"]
|
||||
session.put(upload_url, data=data, headers={"Content-Type": is_accepted_format(data), "Content-Length": str(len(data))})
|
||||
attachment_resp = session.post(
|
||||
"https://yupp.ai/api/trpc/chat.createAttachmentForUploadedFile?batch=1",
|
||||
json={"0": {"json": {"fileName": name, "contentType": is_accepted_format(data), "fileId": upload_info["fileId"]}}},
|
||||
cookies={"__Secure-yupp.session-token": account["token"]}
|
||||
)
|
||||
attachment_resp.raise_for_status()
|
||||
attachment = attachment_resp.json()[0]["result"]["data"]["json"]
|
||||
files.append({
|
||||
"fileName": attachment["file_name"],
|
||||
"contentType": attachment["content_type"],
|
||||
"attachmentId": attachment["attachment_id"],
|
||||
"chatMessageId": ""
|
||||
})
|
||||
# Handle media attachments
|
||||
media = kwargs.get("media")
|
||||
if media:
|
||||
for file, name in list(merge_media(media, messages)):
|
||||
data = to_bytes(file)
|
||||
presigned_resp = await session.post(
|
||||
"https://yupp.ai/api/trpc/chat.createPresignedURLForUpload?batch=1",
|
||||
json={"0": {"json": {"fileName": name, "fileSize": len(data), "contentType": is_accepted_format(data)}}},
|
||||
headers={"Content-Type": "application/json", "Cookie": f"__Secure-yupp.session-token={account['token']}"}
|
||||
)
|
||||
presigned_resp.raise_for_status()
|
||||
upload_info = (await presigned_resp.json())[0]["result"]["data"]["json"]
|
||||
upload_url = upload_info["signedUrl"]
|
||||
|
||||
await session.put(
|
||||
upload_url,
|
||||
data=data,
|
||||
headers={
|
||||
"Content-Type": is_accepted_format(data),
|
||||
"Content-Length": str(len(data))
|
||||
}
|
||||
)
|
||||
|
||||
attachment_resp = await session.post(
|
||||
"https://yupp.ai/api/trpc/chat.createAttachmentForUploadedFile?batch=1",
|
||||
json={"0": {"json": {"fileName": name, "contentType": is_accepted_format(data), "fileId": upload_info["fileId"]}}},
|
||||
cookies={"__Secure-yupp.session-token": account["token"]}
|
||||
)
|
||||
attachment_resp.raise_for_status()
|
||||
attachment = (await attachment_resp.json())[0]["result"]["data"]["json"]
|
||||
files.append({
|
||||
"fileName": attachment["file_name"],
|
||||
"contentType": attachment["content_type"],
|
||||
"attachmentId": attachment["attachment_id"],
|
||||
"chatMessageId": ""
|
||||
})
|
||||
mode = "image" if model in cls.image_models else "text"
|
||||
|
||||
# Build payload and URL - FIXED: Use consistent url_uuid handling
|
||||
if is_new_conversation:
|
||||
url_uuid = str(uuid.uuid4())
|
||||
payload = [
|
||||
url_uuid,
|
||||
turn_id,
|
||||
prompt,
|
||||
"$undefined",
|
||||
"$undefined",
|
||||
files,
|
||||
"$undefined",
|
||||
[{"modelName": model, "promptModifierId": "$undefined"}] if model else "none",
|
||||
mode,
|
||||
True,
|
||||
"$undefined",
|
||||
]
|
||||
url = f"https://yupp.ai/chat/{url_uuid}?stream=true"
|
||||
# Yield the conversation info first
|
||||
yield JsonConversation(url_uuid=url_uuid)
|
||||
next_action = kwargs.get("next_action", "7f2a2308b5fc462a2c26df714cb2cccd02a9c10fbb")
|
||||
else:
|
||||
# Continuing existing conversation
|
||||
payload = [
|
||||
url_uuid,
|
||||
turn_id,
|
||||
prompt,
|
||||
False,
|
||||
[],
|
||||
[{"modelName": model, "promptModifierId": "$undefined"}] if model else [],
|
||||
mode,
|
||||
files
|
||||
]
|
||||
url = f"https://yupp.ai/chat/{url_uuid}?stream=true"
|
||||
next_action = kwargs.get("next_action", "7f1e9eec4ab22c8bfc73a50c026db603cd8380f87d")
|
||||
# Build payload and URL - FIXED: Use consistent url_uuid handling
|
||||
if is_new_conversation:
|
||||
url_uuid = str(uuid.uuid4())
|
||||
payload = [
|
||||
url_uuid,
|
||||
turn_id,
|
||||
prompt,
|
||||
"$undefined",
|
||||
"$undefined",
|
||||
files,
|
||||
"$undefined",
|
||||
[{"modelName": model, "promptModifierId": "$undefined"}] if model else "none",
|
||||
mode,
|
||||
True,
|
||||
"$undefined",
|
||||
]
|
||||
url = f"https://yupp.ai/chat/{url_uuid}?stream=true"
|
||||
# Yield the conversation info first
|
||||
yield JsonConversation(url_uuid=url_uuid)
|
||||
next_action = kwargs.get("next_action", "7f2a2308b5fc462a2c26df714cb2cccd02a9c10fbb")
|
||||
else:
|
||||
# Continuing existing conversation
|
||||
payload = [
|
||||
url_uuid,
|
||||
turn_id,
|
||||
prompt,
|
||||
False,
|
||||
[],
|
||||
[{"modelName": model, "promptModifierId": "$undefined"}] if model else [],
|
||||
mode,
|
||||
files
|
||||
]
|
||||
url = f"https://yupp.ai/chat/{url_uuid}?stream=true"
|
||||
next_action = kwargs.get("next_action", "7f1e9eec4ab22c8bfc73a50c026db603cd8380f87d")
|
||||
|
||||
headers = {
|
||||
"accept": "text/x-component",
|
||||
"content-type": "text/plain;charset=UTF-8",
|
||||
"next-action": next_action,
|
||||
"cookie": f"__Secure-yupp.session-token={account['token']}",
|
||||
}
|
||||
headers = {
|
||||
"accept": "text/x-component",
|
||||
"content-type": "text/plain;charset=UTF-8",
|
||||
"next-action": next_action,
|
||||
"cookie": f"__Secure-yupp.session-token={account['token']}",
|
||||
}
|
||||
|
||||
log_debug(f"Sending request to: {url}")
|
||||
log_debug(f"Payload structure: {type(payload)}, length: {len(str(payload))}")
|
||||
log_debug(f"Sending request to: {url}")
|
||||
log_debug(f"Payload structure: {type(payload)}, length: {len(str(payload))}")
|
||||
|
||||
# Send request
|
||||
response = session.post(url, data=json.dumps(payload), headers=headers, stream=True, timeout=timeout)
|
||||
response.raise_for_status()
|
||||
# Send request
|
||||
async with session.post(url, json=payload, headers=headers, proxy=proxy) as response:
|
||||
response.raise_for_status()
|
||||
|
||||
# Attempt to make chat private
|
||||
try:
|
||||
make_chat_private(account, url_uuid)
|
||||
except Exception as e:
|
||||
log_debug(f"Failed to set chat private for {url_uuid}: {e}")
|
||||
# Make chat private in background
|
||||
asyncio.create_task(make_chat_private(session, account, url_uuid))
|
||||
|
||||
# Yield streaming responses
|
||||
yield from cls._process_stream_response(response.iter_lines(), account, session, prompt, model)
|
||||
# Process stream
|
||||
async for chunk in cls._process_stream_response(response.content, account, session, prompt, model):
|
||||
yield chunk
|
||||
|
||||
return # Exit after successful completion
|
||||
return
|
||||
|
||||
except RateLimitError:
|
||||
log_debug(f"Account ...{account['token'][-4:]} hit rate limit, rotating")
|
||||
with account_rotation_lock:
|
||||
async with account_rotation_lock:
|
||||
account["error_count"] += 1
|
||||
continue
|
||||
except ProviderException as e:
|
||||
log_debug(f"Account ...{account['token'][-4:]} failed: {str(e)}")
|
||||
with account_rotation_lock:
|
||||
async with account_rotation_lock:
|
||||
if "auth" in str(e).lower() or "401" in str(e) or "403" in str(e):
|
||||
account["is_valid"] = False
|
||||
else:
|
||||
|
|
@ -397,25 +379,24 @@ class Yupp(AbstractProvider, ProviderModelMixin):
|
|||
continue
|
||||
except Exception as e:
|
||||
log_debug(f"Unexpected error with account ...{account['token'][-4:]}: {str(e)}")
|
||||
with account_rotation_lock:
|
||||
async with account_rotation_lock:
|
||||
account["error_count"] += 1
|
||||
raise ProviderException(f"Yupp request failed: {str(e)}") from e
|
||||
|
||||
raise ProviderException("All Yupp accounts failed after rotation attempts")
|
||||
|
||||
@classmethod
|
||||
def _process_stream_response(
|
||||
async def _process_stream_response(
|
||||
cls,
|
||||
response_lines: Iterable[bytes],
|
||||
response_content,
|
||||
account: Dict[str, Any],
|
||||
session: requests.Session,
|
||||
session: aiohttp.ClientSession,
|
||||
prompt: str,
|
||||
model_id: str
|
||||
) -> Generator[str, Any, None]:
|
||||
"""Process Yupp stream response and convert to OpenAI format"""
|
||||
) -> AsyncResult:
|
||||
"""Process Yupp stream response asynchronously"""
|
||||
|
||||
line_pattern = re.compile(b"^([0-9a-fA-F]+):(.*)")
|
||||
chunks = {}
|
||||
target_stream_id = None
|
||||
reward_info = None
|
||||
is_thinking = False
|
||||
|
|
@ -426,15 +407,13 @@ class Yupp(AbstractProvider, ProviderModelMixin):
|
|||
def extract_ref_id(ref):
|
||||
"""Extract ID from reference string, e.g., from '$@123' extract '123'"""
|
||||
return ref[2:] if ref and isinstance(ref, str) and ref.startswith("$@") else None
|
||||
|
||||
def is_valid_content(content: str) -> bool:
|
||||
"""Check if content is valid, avoid over-filtering"""
|
||||
"""Check if content is valid"""
|
||||
if not content or content in [None, "", "$undefined"]:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def process_content_chunk(content: str, chunk_id: str, line_count: int):
|
||||
async def process_content_chunk(content: str, chunk_id: str, line_count: int):
|
||||
"""Process single content chunk"""
|
||||
nonlocal is_thinking, thinking_content, normal_content, session
|
||||
|
||||
|
|
@ -443,10 +422,11 @@ class Yupp(AbstractProvider, ProviderModelMixin):
|
|||
|
||||
if '<yapp class="image-gen">' in content:
|
||||
content = content.split('<yapp class="image-gen">').pop().split('</yapp>')[0]
|
||||
url = f"https://yupp.ai/api/trpc/chat.getSignedImage"
|
||||
response = session.get(url, params={"batch": "1", "input": json.dumps({"0": {"json": {"imageId": json.loads(content).get("image_id")}}})})
|
||||
response.raise_for_status()
|
||||
yield ImageResponse(response.json()[0]["result"]["data"]["json"]["signed_url"], prompt)
|
||||
url = "https://yupp.ai/api/trpc/chat.getSignedImage"
|
||||
async with session.get(url, params={"batch": "1", "input": json.dumps({"0": {"json": {"imageId": json.loads(content).get("image_id")}}})}) as resp:
|
||||
resp.raise_for_status()
|
||||
data = await resp.json()
|
||||
yield ImageResponse(data[0]["result"]["data"]["json"]["signed_url"], prompt)
|
||||
return
|
||||
|
||||
# log_debug(f"Processing chunk #{line_count} with content: '{content[:50]}...'")
|
||||
|
|
@ -458,7 +438,6 @@ class Yupp(AbstractProvider, ProviderModelMixin):
|
|||
yield content
|
||||
|
||||
try:
|
||||
# log_debug("Starting to process Yupp stream response...")
|
||||
line_count = 0
|
||||
quick_response_id = None
|
||||
variant_stream_id = None
|
||||
|
|
@ -466,13 +445,11 @@ class Yupp(AbstractProvider, ProviderModelMixin):
|
|||
variant_image: Optional[ImageResponse] = None
|
||||
variant_text = ""
|
||||
|
||||
for line in response_lines:
|
||||
|
||||
async for line in response_content:
|
||||
line_count += 1
|
||||
|
||||
match = line_pattern.match(line)
|
||||
if not match:
|
||||
log_debug(f"Line {line_count}: No pattern match")
|
||||
continue
|
||||
|
||||
chunk_id, chunk_data = match.groups()
|
||||
|
|
@ -480,9 +457,7 @@ class Yupp(AbstractProvider, ProviderModelMixin):
|
|||
|
||||
try:
|
||||
data = json.loads(chunk_data) if chunk_data != b"{}" else {}
|
||||
chunks[chunk_id] = data
|
||||
except json.JSONDecodeError:
|
||||
log_debug(f"Failed to parse JSON for chunk {chunk_id}")
|
||||
continue
|
||||
|
||||
# Process reward info
|
||||
|
|
@ -524,7 +499,7 @@ class Yupp(AbstractProvider, ProviderModelMixin):
|
|||
target_stream_id = extract_ref_id(data.get("next"))
|
||||
content = data.get("curr", "")
|
||||
if content:
|
||||
for chunk in process_content_chunk(content, chunk_id, line_count):
|
||||
async for chunk in process_content_chunk(content, chunk_id, line_count):
|
||||
is_started = True
|
||||
yield chunk
|
||||
|
||||
|
|
@ -534,10 +509,9 @@ class Yupp(AbstractProvider, ProviderModelMixin):
|
|||
variant_stream_id = extract_ref_id(data.get("next"))
|
||||
content = data.get("curr", "")
|
||||
if content:
|
||||
for chunk in process_content_chunk(content, chunk_id, line_count):
|
||||
async for chunk in process_content_chunk(content, chunk_id, line_count):
|
||||
if isinstance(chunk, ImageResponse):
|
||||
variant_image = chunk
|
||||
yield PreviewResponse(str(variant_image))
|
||||
yield PreviewResponse(str(chunk))
|
||||
else:
|
||||
variant_text += str(chunk)
|
||||
if not is_started:
|
||||
|
|
@ -550,7 +524,6 @@ class Yupp(AbstractProvider, ProviderModelMixin):
|
|||
if content:
|
||||
yield PreviewResponse(content)
|
||||
|
||||
# Fallback: process any chunk with "curr"
|
||||
elif isinstance(data, dict) and "curr" in data:
|
||||
content = data.get("curr", "")
|
||||
if content:
|
||||
|
|
@ -571,40 +544,4 @@ class Yupp(AbstractProvider, ProviderModelMixin):
|
|||
if reward_info and "unclaimedRewardInfo" in reward_info:
|
||||
reward_id = reward_info["unclaimedRewardInfo"].get("rewardId")
|
||||
if reward_id:
|
||||
try:
|
||||
claim_yupp_reward(account, reward_id)
|
||||
except Exception as e:
|
||||
log_debug(f"Failed to claim reward: {e}")
|
||||
|
||||
# log_debug(f"Stream completed. Content length: {len(normal_content)}")
|
||||
|
||||
# Initialize the provider
|
||||
def init_yupp_provider():
|
||||
"""Initialize Yupp provider with environment configuration"""
|
||||
tokens = os.getenv("YUPP_TOKENS", "")
|
||||
if tokens:
|
||||
load_yupp_accounts(tokens)
|
||||
|
||||
log_debug(f"Yupp provider initialized: {len(YUPP_ACCOUNTS)} accounts, {len(YUPP_MODELS)} models")
|
||||
return Yupp
|
||||
|
||||
# Example usage and testing
|
||||
if __name__ == "__main__":
|
||||
# Set up environment for testing
|
||||
os.environ["DEBUG_MODE"] = "true"
|
||||
|
||||
# Initialize provider
|
||||
provider = init_yupp_provider()
|
||||
|
||||
# Test stream completion
|
||||
try:
|
||||
print("\nTesting stream completion...")
|
||||
for chunk in provider.create_completion(
|
||||
model="claude-sonnet-4-5-20250929<>thinking",
|
||||
messages=[{"role": "user", "content": "What is Python?"}],
|
||||
stream=True
|
||||
):
|
||||
if isinstance(chunk, str) and chunk.strip():
|
||||
print(chunk, end="")
|
||||
except Exception as e:
|
||||
print(f"\nStream test failed: {e}")
|
||||
await claim_yupp_reward(session, account, reward_id)
|
||||
Loading…
Add table
Add a link
Reference in a new issue