1- from typing import Callable , Optional
1+ from typing import Callable , Optional , Union
22import asyncio
33import base64
44import logging
55import uuid
6+ from pathlib import Path
7+ from urllib .parse import urlparse
68import aiohttp
79from aiortc import MediaStreamTrack
10+ from pydantic import BaseModel
811
912from .webrtc_manager import WebRTCManager , WebRTCConfiguration
10- from .messages import PromptMessage , SetAvatarImageMessage
13+ from .messages import PromptMessage
1114from .types import ConnectionState , RealtimeConnectOptions
1215from ..types import FileInput
1316from ..errors import DecartSDKError , InvalidInputError , WebRTCError
1417from ..process .request import file_input_to_bytes
1518
1619logger = logging .getLogger (__name__ )
1720
21+ PROMPT_TIMEOUT_S = 15.0
22+ UPDATE_TIMEOUT_S = 30.0
23+
24+
25+ class SetInput (BaseModel ):
26+ prompt : Optional [str ] = None
27+ enhance : bool = True
28+ image : Optional [Union [bytes , str ]] = None
29+
30+
31+ async def _image_to_base64 (
32+ image : Union [bytes , str ],
33+ http_session : aiohttp .ClientSession ,
34+ ) -> str :
35+ if isinstance (image , bytes ):
36+ return base64 .b64encode (image ).decode ("utf-8" )
37+
38+ if isinstance (image , str ):
39+ parsed = urlparse (image )
40+
41+ if parsed .scheme == "data" :
42+ return image .split ("," , 1 )[1 ]
43+
44+ if parsed .scheme in ("http" , "https" ):
45+ async with http_session .get (image ) as resp :
46+ resp .raise_for_status ()
47+ data = await resp .read ()
48+ return base64 .b64encode (data ).decode ("utf-8" )
49+
50+ if Path (image ).exists ():
51+ image_bytes , _ = await file_input_to_bytes (image , http_session )
52+ return base64 .b64encode (image_bytes ).decode ("utf-8" )
53+
54+ return image
55+
56+ image_bytes , _ = await file_input_to_bytes (image , http_session )
57+ return base64 .b64encode (image_bytes ).decode ("utf-8" )
58+
1859
1960class RealtimeClient :
2061 def __init__ (
@@ -124,6 +165,28 @@ def _emit_error(self, error: DecartSDKError) -> None:
124165 except Exception as e :
125166 logger .exception (f"Error in error callback: { e } " )
126167
168+ async def set (self , input : SetInput ) -> None :
169+ if input .prompt is None and input .image is None :
170+ raise InvalidInputError ("At least one of 'prompt' or 'image' must be provided" )
171+
172+ if input .prompt is not None and not input .prompt .strip ():
173+ raise InvalidInputError ("Prompt cannot be empty" )
174+
175+ image_base64 : Optional [str ] = None
176+ if input .image is not None :
177+ if not self ._http_session :
178+ raise InvalidInputError ("HTTP session not available" )
179+ image_base64 = await _image_to_base64 (input .image , self ._http_session )
180+
181+ await self ._manager .set_image (
182+ image_base64 ,
183+ {
184+ "prompt" : input .prompt ,
185+ "enhance" : input .enhance ,
186+ "timeout" : UPDATE_TIMEOUT_S ,
187+ },
188+ )
189+
127190 async def set_prompt (self , prompt : str , enrich : bool = True ) -> None :
128191 if not prompt or not prompt .strip ():
129192 raise InvalidInputError ("Prompt cannot be empty" )
@@ -136,7 +199,7 @@ async def set_prompt(self, prompt: str, enrich: bool = True) -> None:
136199 )
137200
138201 try :
139- await asyncio .wait_for (event .wait (), timeout = 15.0 )
202+ await asyncio .wait_for (event .wait (), timeout = PROMPT_TIMEOUT_S )
140203 except asyncio .TimeoutError :
141204 raise DecartSDKError ("Prompt acknowledgment timed out" )
142205
@@ -146,43 +209,16 @@ async def set_prompt(self, prompt: str, enrich: bool = True) -> None:
146209 self ._manager .unregister_prompt_wait (prompt )
147210
148211 async def set_image (self , image : FileInput ) -> None :
149- """Set or update the avatar image.
150-
151- Only available for avatar-live model.
152-
153- Args:
154- image: The image to set. Can be bytes, Path, URL string, or file-like object.
155-
156- Raises:
157- InvalidInputError: If not using avatar-live model or image is invalid.
158- DecartSDKError: If the server fails to acknowledge the image.
159- """
160212 if not self ._is_avatar_live :
161213 raise InvalidInputError ("set_image() is only available for avatar-live model" )
162214
163215 if not self ._http_session :
164216 raise InvalidInputError ("HTTP session not available" )
165217
166- # Convert image to base64
167218 image_bytes , _ = await file_input_to_bytes (image , self ._http_session )
168219 image_base64 = base64 .b64encode (image_bytes ).decode ("utf-8" )
169220
170- event , result = self ._manager .register_image_set_wait ()
171-
172- try :
173- await self ._manager .send_message (
174- SetAvatarImageMessage (type = "set_image" , image_data = image_base64 )
175- )
176-
177- try :
178- await asyncio .wait_for (event .wait (), timeout = 15.0 )
179- except asyncio .TimeoutError :
180- raise DecartSDKError ("Image set acknowledgment timed out" )
181-
182- if not result ["success" ]:
183- raise DecartSDKError (result .get ("error" ) or "Failed to set avatar image" )
184- finally :
185- self ._manager .unregister_image_set_wait ()
221+ await self ._manager .set_image (image_base64 )
186222
187223 def is_connected (self ) -> bool :
188224 return self ._manager .is_connected ()
0 commit comments