fix: separate video payload from images in task API

This commit is contained in:
JOJO 2026-04-05 19:00:19 +08:00
parent a5412dae34
commit a5a3eb9974
3 changed files with 65 additions and 11 deletions

View File

@ -1,5 +1,6 @@
"""简单任务 API将聊天任务与 WebSocket 解耦,支持后台运行与轮询。""" """简单任务 API将聊天任务与 WebSocket 解耦,支持后台运行与轮询。"""
from __future__ import annotations from __future__ import annotations
import mimetypes
import time import time
import threading import threading
import uuid import uuid
@ -101,6 +102,7 @@ class TaskManager:
message: str, message: str,
images: List[Any], images: List[Any],
conversation_id: Optional[str], conversation_id: Optional[str],
videos: Optional[List[Any]] = None,
model_key: Optional[str] = None, model_key: Optional[str] = None,
thinking_mode: Optional[bool] = None, thinking_mode: Optional[bool] = None,
run_mode: Optional[str] = None, run_mode: Optional[str] = None,
@ -137,7 +139,7 @@ class TaskManager:
record.session_data = {} record.session_data = {}
with self._lock: with self._lock:
self._tasks[task_id] = record self._tasks[task_id] = record
thread = threading.Thread(target=self._run_chat_task, args=(record, images), daemon=True) thread = threading.Thread(target=self._run_chat_task, args=(record, images, videos or []), daemon=True)
record.thread = thread record.thread = thread
record.status = "running" record.status = "running"
record.updated_at = time.time() record.updated_at = time.time()
@ -199,7 +201,7 @@ class TaskManager:
}) })
rec.updated_at = time.time() rec.updated_at = time.time()
def _run_chat_task(self, rec: TaskRecord, images: List[Any]): def _run_chat_task(self, rec: TaskRecord, images: List[Any], videos: List[Any]):
username = rec.username username = rec.username
workspace_id = rec.workspace_id workspace_id = rec.workspace_id
terminal = None terminal = None
@ -269,6 +271,7 @@ class TaskManager:
client_sid=rec.task_id, client_sid=rec.task_id,
workspace=workspace, workspace=workspace,
username=username, username=username,
videos=videos,
) )
# 结束状态 # 结束状态
@ -347,8 +350,7 @@ def create_task_api():
workspace_id = session.get("workspace_id") or "default" workspace_id = session.get("workspace_id") or "default"
payload = request.get_json() or {} payload = request.get_json() or {}
message = (payload.get("message") or "").strip() message = (payload.get("message") or "").strip()
images = payload.get("images") or [] images, videos = _normalize_media_payload(payload.get("images") or [], payload.get("videos") or [])
videos = payload.get("videos") or []
conversation_id = payload.get("conversation_id") conversation_id = payload.get("conversation_id")
if not message and not images and not videos: if not message and not images and not videos:
return jsonify({"success": False, "error": "消息不能为空"}), 400 return jsonify({"success": False, "error": "消息不能为空"}), 400
@ -357,16 +359,14 @@ def create_task_api():
run_mode = payload.get("run_mode") run_mode = payload.get("run_mode")
max_iterations = payload.get("max_iterations") max_iterations = payload.get("max_iterations")
# 合并 images 和 videos 到 images 参数(后端统一处理)
all_media = images + videos
try: try:
rec = task_manager.create_chat_task( rec = task_manager.create_chat_task(
username, username,
workspace_id, workspace_id,
message, message,
all_media, images,
conversation_id, conversation_id,
videos=videos,
model_key=model_key, model_key=model_key,
thinking_mode=thinking_mode, thinking_mode=thinking_mode,
run_mode=run_mode, run_mode=run_mode,
@ -385,6 +385,48 @@ def create_task_api():
}), 202 }), 202
def _media_path(item: Any) -> str:
if isinstance(item, dict):
return str(item.get("path") or "")
return str(item or "")
def _is_video_item(item: Any) -> bool:
path = _media_path(item)
if not path:
return False
mime, _ = mimetypes.guess_type(path)
return bool(mime and mime.startswith("video/"))
def _is_image_item(item: Any) -> bool:
path = _media_path(item)
if not path:
return False
mime, _ = mimetypes.guess_type(path)
return bool(mime and mime.startswith("image/"))
def _normalize_media_payload(images: List[Any], videos: List[Any]) -> tuple[List[Any], List[Any]]:
"""纠偏媒体字段:把误传到 images 的视频项自动归入 videos。"""
fixed_images: List[Any] = []
fixed_videos: List[Any] = []
for item in images or []:
if _is_video_item(item):
fixed_videos.append(item)
else:
fixed_images.append(item)
for item in videos or []:
if _is_image_item(item):
fixed_images.append(item)
else:
fixed_videos.append(item)
return fixed_images, fixed_videos
@tasks_bp.route("/api/tasks/<task_id>", methods=["GET"]) @tasks_bp.route("/api/tasks/<task_id>", methods=["GET"])
@api_login_required @api_login_required
def get_task_api(task_id: str): def get_task_api(task_id: str):

View File

@ -205,6 +205,7 @@ class DeepSeekClient:
return text return text
qwen_video_fps = 2 qwen_video_fps = 2
parts: List[Dict[str, Any]] = [] parts: List[Dict[str, Any]] = []
extra_videos: List[Any] = []
if text: if text:
parts.append({"type": "text", "text": text}) parts.append({"type": "text", "text": text})
base_path = Path(self.project_path or ".") base_path = Path(self.project_path or ".")
@ -214,6 +215,11 @@ class DeepSeekClient:
if not abs_path.exists() or not abs_path.is_file(): if not abs_path.exists() or not abs_path.is_file():
continue continue
mime, _ = mimetypes.guess_type(abs_path.name) mime, _ = mimetypes.guess_type(abs_path.name)
if mime and mime.startswith("video/"):
extra_videos.append(path)
continue
if mime and not mime.startswith("image/"):
continue
if not mime: if not mime:
mime = "image/png" mime = "image/png"
data = abs_path.read_bytes() data = abs_path.read_bytes()
@ -221,7 +227,7 @@ class DeepSeekClient:
parts.append({"type": "image_url", "image_url": {"url": f"data:{mime};base64,{b64}"}}) parts.append({"type": "image_url", "image_url": {"url": f"data:{mime};base64,{b64}"}})
except Exception: except Exception:
continue continue
for item in videos: for item in [*videos, *extra_videos]:
try: try:
if isinstance(item, dict): if isinstance(item, dict):
path = item.get("path") or "" path = item.get("path") or ""

View File

@ -1608,6 +1608,7 @@ class ContextManager:
if not images and not videos: if not images and not videos:
return text return text
parts: List[Dict[str, Any]] = [] parts: List[Dict[str, Any]] = []
extra_videos: List[Any] = []
supports_video_fps = getattr(getattr(self, "main_terminal", None), "model_key", None) == "qwen3-vl-plus" supports_video_fps = getattr(getattr(self, "main_terminal", None), "model_key", None) == "qwen3-vl-plus"
qwen_video_fps = 2 qwen_video_fps = 2
if text: if text:
@ -1617,9 +1618,14 @@ class ContextManager:
abs_path = Path(self.project_path) / path abs_path = Path(self.project_path) / path
if not abs_path.exists() or not abs_path.is_file(): if not abs_path.exists() or not abs_path.is_file():
continue continue
mime, _ = mimetypes.guess_type(abs_path.name)
if mime and mime.startswith("video/"):
extra_videos.append(path)
continue
if mime and not mime.startswith("image/"):
continue
data_url = self._compress_image_if_needed(abs_path) data_url = self._compress_image_if_needed(abs_path)
if not data_url: if not data_url:
mime, _ = mimetypes.guess_type(abs_path.name)
if not mime: if not mime:
mime = "image/png" mime = "image/png"
data = abs_path.read_bytes() data = abs_path.read_bytes()
@ -1628,7 +1634,7 @@ class ContextManager:
parts.append({"type": "image_url", "image_url": {"url": data_url}}) parts.append({"type": "image_url", "image_url": {"url": data_url}})
except Exception: except Exception:
continue continue
for item in videos: for item in [*videos, *extra_videos]:
try: try:
if isinstance(item, dict): if isinstance(item, dict):
path = item.get("path") or "" path = item.get("path") or ""