feat: support uploaded images in debug voice preview
Allow paste/drag temporary image assets so vision turns work without a live camera frame. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -50,9 +50,14 @@ class UserInput:
|
||||
input_id: str
|
||||
text: str
|
||||
has_camera_frame: bool
|
||||
image_asset_token: str | None
|
||||
run_immediately: bool
|
||||
interrupt: bool
|
||||
|
||||
@property
|
||||
def has_image(self) -> bool:
|
||||
return self.has_camera_frame or bool(self.image_asset_token)
|
||||
|
||||
@property
|
||||
def prompt_text(self) -> str:
|
||||
if self.text:
|
||||
@@ -61,10 +66,9 @@ class UserInput:
|
||||
|
||||
@property
|
||||
def transcript_text(self) -> str:
|
||||
# Images are represented as structured media records. A synthetic
|
||||
# sentence would be shown as chat text and persisted as if the user had
|
||||
# said it, while adding no visual information for the model.
|
||||
return self.text
|
||||
# Image turns are persisted as one structured media message, including
|
||||
# any optional text caption. Do not create a duplicate transcript row.
|
||||
return "" if self.has_image else self.text
|
||||
|
||||
|
||||
class UserInputError(ValueError):
|
||||
@@ -155,6 +159,7 @@ def parse_user_input(message) -> UserInput | None:
|
||||
|
||||
text = ""
|
||||
has_camera_frame = False
|
||||
image_asset_token: str | None = None
|
||||
for part in parts:
|
||||
if not isinstance(part, dict):
|
||||
raise UserInputError("user-input part 格式不正确", input_id=input_id)
|
||||
@@ -166,23 +171,30 @@ def parse_user_input(message) -> UserInput | None:
|
||||
if not text:
|
||||
raise UserInputError("input_text 不能为空", input_id=input_id)
|
||||
elif part_type == "input_image":
|
||||
if has_camera_frame:
|
||||
if has_camera_frame or image_asset_token:
|
||||
raise UserInputError("P0 只支持一张图片", input_id=input_id)
|
||||
source = part.get("source")
|
||||
if (
|
||||
not isinstance(source, dict)
|
||||
or source.get("type") != "camera_frame"
|
||||
or source.get("frame") != "current"
|
||||
):
|
||||
if not isinstance(source, dict):
|
||||
raise UserInputError("input_image source 格式不正确", input_id=input_id)
|
||||
if source.get("type") == "camera_frame" and source.get("frame") == "current":
|
||||
has_camera_frame = True
|
||||
elif source.get("type") == "uploaded_asset":
|
||||
token = str(source.get("asset_token") or "").strip()
|
||||
if not token or len(token) > 4096:
|
||||
raise UserInputError(
|
||||
"上传图片缺少有效的 asset_token",
|
||||
input_id=input_id,
|
||||
)
|
||||
image_asset_token = token
|
||||
else:
|
||||
raise UserInputError(
|
||||
"P0 只支持当前摄像头画面",
|
||||
"仅支持当前摄像头画面或已上传图片",
|
||||
input_id=input_id,
|
||||
)
|
||||
has_camera_frame = True
|
||||
else:
|
||||
raise UserInputError(f"不支持的输入类型: {part_type}", input_id=input_id)
|
||||
|
||||
if not text and not has_camera_frame:
|
||||
if not text and not (has_camera_frame or image_asset_token):
|
||||
raise UserInputError("user-input 没有有效内容", input_id=input_id)
|
||||
|
||||
options = message.get("options")
|
||||
@@ -193,6 +205,7 @@ def parse_user_input(message) -> UserInput | None:
|
||||
input_id=input_id,
|
||||
text=text,
|
||||
has_camera_frame=has_camera_frame,
|
||||
image_asset_token=image_asset_token,
|
||||
run_immediately=run_immediately,
|
||||
interrupt=interrupt,
|
||||
)
|
||||
@@ -565,7 +578,7 @@ class RealtimeUserInputProcessor(FrameProcessor):
|
||||
"当前工作流节点暂不接收用户输入",
|
||||
)
|
||||
return
|
||||
if user_input.has_camera_frame:
|
||||
if user_input.has_image:
|
||||
await self._emit_error(
|
||||
user_input.input_id,
|
||||
"Realtime 模式暂不支持图片输入",
|
||||
|
||||
Reference in New Issue
Block a user