feat: 将 47 个 Operit TypeScript 脚本全面翻译为 Python

- 新增 operit_runtime.py: 跨平台 Python 运行时
  - 实现 Tools.System/Files/Net/UI/Memory/FFmpeg/Workflow/Chat/SoftwareSettings/Tasker
  - 实现 OkHttp 链式 HTTP 客户端兼容层
  - 实现 complete()/exports/console 全局对象
  - 实现 OperitScriptLoader 脚本加载器
  - 支持 Windows/macOS/Linux 三平台

- 新增 operit_py/ 目录: 47 个 Python 翻译脚本
  - 搜索类: duckduckgo, google_search, tavily, zhipu_search, various_search, crossref
  - 绘图类: openai_draw, qwen_draw, xai_draw, minimax_draw, siliconflow_draw, zhipu_draw, nanobanana_draw
  - 媒体类: ffmpeg, file_converter, douyin_download, bilibili_tools, jmcomic
  - 文件类: extended_file_tools, hex_editor, reader, beeimg_image_uploader
  - 网络类: extended_http_tools, network_test
  - 自动化类: browser, automatic_ui_base, automatic_ui_subagent, automatic_bilibili_assistant, automatic_xiaohongshu_assistant, automatic_baidu_map_assistant
  - 系统类: system_tools, super_admin, daily_life, operit_editor
  - 对话类: ai_chat, extended_chat, qq_intelligent
  - 工作流类: workflow, vflow_trigger, tasker
  - 工具类: time, code_runner, quick_start, extended_memory_tools, baidu_map, 12306, pdf_vision_parser

- 测试结果: 47/47 语法检查通过, 38 脚本加载成功, 280 个工具注册
- Android 专属功能已降级处理(保留接口,返回不支持提示)
This commit is contained in:
shuiyu1123 committed 2026-06-23 12:27:18 +00:00
1 parent 6fa759665d
commit dc77dfd304
48 files changed
+22273

No files matched your search

+580
View File
@@ -0,0 +1,580 @@
# METADATA
# {
# "name": "xai_draw",
# "display_name": {"zh": "xAI 图片与视频", "en": "xAI Images and Video"},
# "description": {"zh": "使用 xAI 官方接口生成图片和视频,并保存到本地。", "en": "Generate images and videos with the official xAI APIs and save them locally."},
# "env": [
# {"name": "XAI_API_KEY", "description": {"zh": "xAI API Key(必填)", "en": "xAI API key (required)"}, "required": True},
# {"name": "XAI_IMAGE_MODEL", "description": {"zh": "默认图片模型(可选;未传 model 时使用,默认 grok-2-image-1212)", "en": "Default image model (optional; used when model is omitted, default grok-2-image-1212)"}, "required": False},
# {"name": "XAI_VIDEO_MODEL", "description": {"zh": "默认视频模型(可选;未传 model 时使用,默认 grok-imagine-video)", "en": "Default video model (optional; used when model is omitted, default grok-imagine-video)"}, "required": False}
# ],
# "category": "Draw",
# "tools": [
# {"name": "draw_image", "description": {"zh": "根据提示词调用 xAI 图像生成 API 生成图片,保存到本地并返回 Markdown 图片提示。", "en": "Generate an image via the xAI image generation API using a prompt, save it locally, and return a Markdown image reference."}, "parameters": [
# {"name": "prompt", "description": {"zh": "绘图提示词(英文或中文皆可)", "en": "Prompt for image generation (Chinese or English)"}, "type": "string", "required": True},
# {"name": "model", "description": {"zh": "xAI 图像模型名称;不传则优先取 XAI_IMAGE_MODEL,再用默认值 grok-2-image-1212", "en": "xAI image model name; falls back to XAI_IMAGE_MODEL, then grok-2-image-1212"}, "type": "string", "required": False},
# {"name": "size", "description": {"zh": "图片尺寸,例如 1024x1024(可选)", "en": "Image size, e.g. 1024x1024 (optional)"}, "type": "string", "required": False},
# {"name": "file_name", "description": {"zh": "自定义保存到本地的文件名(不含路径和扩展名)", "en": "Custom output file name (without path or extension)"}, "type": "string", "required": False}
# ]},
# {"name": "draw_video", "description": {"zh": "根据提示词调用 xAI 官方视频生成 API 生成视频,支持文生视频、图生视频和视频编辑,轮询完成后下载到本地并返回本地视频链接提示。", "en": "Generate a video with the official xAI video API. Supports text-to-video, image-to-video, and video editing. Polls until completion, downloads locally, and returns local video link hints."}, "parameters": [
# {"name": "prompt", "description": {"zh": "视频提示词", "en": "Video prompt"}, "type": "string", "required": True},
# {"name": "model", "description": {"zh": "视频模型;不传则优先取 XAI_VIDEO_MODEL,再用默认值 grok-imagine-video", "en": "Video model; falls back to XAI_VIDEO_MODEL, then grok-imagine-video"}, "type": "string", "required": False},
# {"name": "aspect_ratio", "description": {"zh": "输出比例,可选 1:1、16:9、9:16、4:3、3:4、3:2、2:3;默认 16:9;视频编辑模式不支持", "en": "Output aspect ratio. Supported: 1:1, 16:9, 9:16, 4:3, 3:4, 3:2, 2:3. Defaults to 16:9; not supported for video editing."}, "type": "string", "required": False},
# {"name": "resolution", "description": {"zh": "输出分辨率,仅支持 480p 或 720p;默认 480p;视频编辑模式不支持", "en": "Output resolution, only 480p or 720p. Defaults to 480p; not supported for video editing."}, "type": "string", "required": False},
# {"name": "duration", "description": {"zh": "输出时长,支持 1-15 秒;默认 5;视频编辑模式不支持", "en": "Output duration from 1 to 15 seconds. Defaults to 5; not supported for video editing."}, "type": "number", "required": False},
# {"name": "image_url", "description": {"zh": "图生视频输入图 URL(可选)", "en": "Input image URL for image-to-video (optional)"}, "type": "string", "required": False},
# {"name": "image_path", "description": {"zh": "图生视频输入图本地路径(可选,会转成 data URL)", "en": "Local input image path for image-to-video (optional; converted to a data URL)"}, "type": "string", "required": False},
# {"name": "video_url", "description": {"zh": "视频编辑输入视频 URL(可选)", "en": "Input video URL for video editing (optional)"}, "type": "string", "required": False},
# {"name": "file_name", "description": {"zh": "自定义保存到本地的文件名(不含路径和扩展名)", "en": "Custom output file name (without path or extension)"}, "type": "string", "required": False},
# {"name": "poll_interval_ms", "description": {"zh": "轮询间隔毫秒数,默认 5000", "en": "Polling interval in milliseconds, default 5000"}, "type": "number", "required": False},
# {"name": "max_wait_time_ms", "description": {"zh": "最大等待毫秒数,默认 600000", "en": "Maximum wait time in milliseconds, default 600000"}, "type": "number", "required": False}
# ]}
# ]
# }
import json
import re
import time
import math
from urllib.parse import quote, unquote
Error = Exception
HTTP_TIMEOUT_MS = 600000
client = (
OkHttp.newBuilder()
.connectTimeout(HTTP_TIMEOUT_MS)
.readTimeout(HTTP_TIMEOUT_MS)
.writeTimeout(HTTP_TIMEOUT_MS)
.build()
)
DEFAULT_IMAGE_MODEL = "grok-2-image-1212"
DEFAULT_VIDEO_MODEL = "grok-imagine-video"
DEFAULT_POLL_INTERVAL_MS = 5000
DEFAULT_MAX_WAIT_TIME_MS = 600000
DEFAULT_VIDEO_ASPECT_RATIO = "16:9"
DEFAULT_VIDEO_RESOLUTION = "480p"
DEFAULT_VIDEO_DURATION = 5
VIDEO_ASPECT_RATIOS = ["1:1", "16:9", "9:16", "4:3", "3:4", "3:2", "2:3"]
VIDEO_RESOLUTIONS = ["480p", "720p"]
MIN_VIDEO_DURATION = 1
MAX_VIDEO_DURATION = 15
API_BASE_URL = "https://api.x.ai/v1"
IMAGE_API_ENDPOINT = f"{API_BASE_URL}/images/generations"
VIDEO_GENERATION_ENDPOINT = f"{API_BASE_URL}/videos/generations"
DRAW_ROOT_DIR = getPluginConfigDir("draw")
STORAGE_DIR = f"{DRAW_ROOT_DIR}/xai_draw"
DRAWS_DIR = f"{STORAGE_DIR}/draws"
VIDEOS_DIR = f"{STORAGE_DIR}/videos"
def getErrorMessage(error):
if isinstance(error, Exception):
return str(error)
return str(error)
def getErrorStack(error):
if isinstance(error, Exception):
import traceback
return "".join(traceback.format_exception(type(error), error, error.__traceback__))
return None
def isRecord(value):
return isinstance(value, dict)
def getApiKey():
apiKey = getEnv("XAI_API_KEY")
if not apiKey:
raise Error("XAI_API_KEY 未配置,请在环境变量中设置 xAI 的 API Key。")
return apiKey
def getDefaultImageModel():
fromEnv = str(getEnv("XAI_IMAGE_MODEL") or "").strip()
return fromEnv or DEFAULT_IMAGE_MODEL
def getDefaultVideoModel():
fromEnv = str(getEnv("XAI_VIDEO_MODEL") or "").strip()
return fromEnv or DEFAULT_VIDEO_MODEL
def sanitizeFileName(name, fallbackPrefix):
safe = re.sub(r'[\\/:*?"<>|]', "_", str(name or "")).strip()
if not safe:
return f"{fallbackPrefix}_{int(time.time() * 1000)}"
return safe[:80]
def buildFileName(prompt, customName, fallbackPrefix):
if customName and customName.strip():
return sanitizeFileName(customName, fallbackPrefix)
shortPrompt = f"{prompt[:40]}..." if len(prompt) > 40 else prompt
base = sanitizeFileName(shortPrompt or fallbackPrefix, fallbackPrefix)
return f"{base}_{int(time.time() * 1000)}"
def guessExtensionFromUrl(url, fallback):
match = re.search(r"\.(png|jpg|jpeg|webp|gif|mp4|mov|webm|mkv)(?:\?|#|$)", str(url or ""), re.IGNORECASE)
if match and match.group(1):
return match.group(1).lower()
return fallback
def guessMimeTypeFromPath(path):
normalized = str(path or "").strip().lower()
if normalized.endswith(".png"):
return "image/png"
if normalized.endswith(".webp"):
return "image/webp"
if normalized.endswith(".gif"):
return "image/gif"
if normalized.endswith(".jpg") or normalized.endswith(".jpeg"):
return "image/jpeg"
return "application/octet-stream"
def isProbablyUrl(value):
return bool(re.match(r"^https?://", str(value or "").strip(), re.IGNORECASE))
def normalizePositiveInteger(value, fallback):
if value is None or value == "":
return fallback
parsed = value if isinstance(value, (int, float)) else int(str(value), 10)
if not math.isfinite(parsed) or parsed <= 0:
return fallback
return int(parsed)
def normalizeVideoAspectRatio(value):
raw = str(value or "").strip()
if not raw:
return DEFAULT_VIDEO_ASPECT_RATIO
if raw not in VIDEO_ASPECT_RATIOS:
raise Error(f"aspect_ratio 仅支持 {' 或 '.join(VIDEO_ASPECT_RATIOS)}。")
return raw
def normalizeVideoResolution(value):
raw = str(value or "").strip().lower()
if not raw:
return DEFAULT_VIDEO_RESOLUTION
if raw not in VIDEO_RESOLUTIONS:
raise Error(f"resolution 仅支持 {' 或 '.join(VIDEO_RESOLUTIONS)}。")
return raw
def normalizeVideoDuration(value):
if value is None or value == "":
return DEFAULT_VIDEO_DURATION
parsed = value if isinstance(value, (int, float)) else int(str(value), 10)
if not math.isfinite(parsed):
raise Error("duration 必须是数字。")
normalized = int(parsed)
if normalized < MIN_VIDEO_DURATION or normalized > MAX_VIDEO_DURATION:
raise Error(f"duration 仅支持 {MIN_VIDEO_DURATION}-{MAX_VIDEO_DURATION} 秒。")
return normalized
async def ensureDirectories():
dirs = [DRAW_ROOT_DIR, STORAGE_DIR, DRAWS_DIR, VIDEOS_DIR]
for dir_ in dirs:
try:
result = await Tools.Files.mkdir(dir_)
if not result.successful:
console.warn(f"创建目录失败(可能已存在): {dir_} -> {result.details}")
except Exception as error:
console.warn(f"创建目录异常: {dir_} -> {getErrorMessage(error)}")
async def parseJsonResponse(response, label):
try:
parsed = json.loads(response.content)
if not isRecord(parsed):
raise Error("响应不是对象")
return parsed
except Exception as error:
raise Error(f"解析 {label} 响应失败: {getErrorMessage(error)}")
def extractApiErrorMessage(payload):
directError = payload.get("error")
if isinstance(directError, str) and directError.strip():
return directError.strip()
if isRecord(directError):
if isinstance(directError.get("message"), str) and directError["message"].strip():
return directError["message"].strip()
if isinstance(directError.get("code"), str) and directError["code"].strip():
return directError["code"].strip()
return ""
async def readLocalImageAsDataUrl(filePath):
trimmedPath = str(filePath or "").strip()
if not trimmedPath:
raise Error("image_path 不能为空。")
existsResult = await Tools.Files.exists(trimmedPath)
if not existsResult.exists:
raise Error(f"本地图片不存在: {trimmedPath}")
binaryResult = await Tools.Files.readBinary(trimmedPath)
base64Content = (
str(binaryResult.contentBase64).strip()
if binaryResult and getattr(binaryResult, "contentBase64", None)
else ""
)
if not base64Content:
raise Error(f"读取本地图片失败: {trimmedPath}")
return f"data:{guessMimeTypeFromPath(trimmedPath)};base64,{base64Content}"
async def resolveVideoInputs(imageUrl, imagePath, videoUrl):
trimmedImageUrl = str(imageUrl or "").strip()
trimmedImagePath = str(imagePath or "").strip()
trimmedVideoUrl = str(videoUrl or "").strip()
imageInputCount = (1 if trimmedImageUrl else 0) + (1 if trimmedImagePath else 0)
if imageInputCount > 1:
raise Error("image_url 和 image_path 只能二选一,请不要同时传。")
if trimmedVideoUrl and imageInputCount > 0:
raise Error("视频生成一次只能使用一种输入源:纯文本、图片或视频。请不要同时传 image_* 和 video_url。")
if trimmedImageUrl:
if not isProbablyUrl(trimmedImageUrl):
raise Error("image_url 必须是 http 或 https 链接。")
return {"image": trimmedImageUrl, "input_type": "image_url"}
if trimmedImagePath:
return {"image": await readLocalImageAsDataUrl(trimmedImagePath), "input_type": "image_path"}
if trimmedVideoUrl:
if not isProbablyUrl(trimmedVideoUrl):
raise Error("video_url 必须是 http 或 https 链接。")
return {"video_url": trimmedVideoUrl, "input_type": "video_url"}
return {"input_type": "text"}
async def callXaiImageApi(params):
apiKey = getApiKey()
modelFromParam = str(params.get("model") or "").strip()
effectiveModel = modelFromParam or getDefaultImageModel()
body = {
"model": effectiveModel,
"prompt": params.get("prompt"),
}
size = str(params.get("size") or "").strip()
if size:
body["size"] = size
request = (
client.newRequest()
.url(IMAGE_API_ENDPOINT)
.method("POST")
.headers({
"accept": "application/json",
"content-type": "application/json",
"Authorization": f"Bearer {apiKey}",
})
.body(json.dumps(body), "json")
)
response = await request.build().execute()
if not response.isSuccessful():
raise Error(f"xAI 图片 API 调用失败: {response.statusCode} - {response.content}")
parsed = await parseJsonResponse(response, "xAI 图片生成")
data = parsed.get("data") if isinstance(parsed.get("data"), list) else []
first = data[0] if len(data) > 0 and isRecord(data[0]) else None
imageUrl = str(first.get("url")).strip() if first and isinstance(first.get("url"), str) else ""
if not imageUrl:
raise Error("xAI 响应中未找到图片 URL,请检查模型和参数是否正确。")
return {"image_url": imageUrl, "effective_model": effectiveModel}
async def createVideoTask(params):
apiKey = getApiKey()
modelFromParam = str(params.get("model") or "").strip()
effectiveModel = modelFromParam or getDefaultVideoModel()
body = {
"model": effectiveModel,
"prompt": str(params.get("prompt") or "").strip(),
}
isVideoEdit = bool(params.get("video_url"))
if not isVideoEdit:
body["aspect_ratio"] = normalizeVideoAspectRatio(params.get("aspect_ratio"))
body["resolution"] = normalizeVideoResolution(params.get("resolution"))
body["duration"] = normalizeVideoDuration(params.get("duration"))
if params.get("image"):
body["image"] = {"url": params.get("image")}
if params.get("video_url"):
body["video_url"] = params.get("video_url")
request = (
client.newRequest()
.url(VIDEO_GENERATION_ENDPOINT)
.method("POST")
.headers({
"accept": "application/json",
"content-type": "application/json",
"Authorization": f"Bearer {apiKey}",
})
.body(json.dumps(body), "json")
)
response = await request.build().execute()
if not response.isSuccessful():
raise Error(f"xAI 视频创建任务失败: {response.statusCode} - {response.content}")
parsed = await parseJsonResponse(response, "xAI 视频创建")
requestId = str(parsed.get("request_id")).strip() if isinstance(parsed.get("request_id"), str) else ""
status = str(parsed.get("status")).strip() if isinstance(parsed.get("status"), str) else ""
if not requestId:
raise Error(f"xAI 视频创建响应中未找到 request_id: {response.content}")
return {
"request_id": requestId,
"status": status,
"effective_model": effectiveModel,
"aspect_ratio": body.get("aspect_ratio") if isinstance(body.get("aspect_ratio"), str) else None,
"resolution": body.get("resolution") if isinstance(body.get("resolution"), str) else None,
"duration": body.get("duration") if isinstance(body.get("duration"), (int, float)) else None,
}
async def queryVideoTaskStatus(requestId):
apiKey = getApiKey()
endpoint = f"{API_BASE_URL}/videos/{quote(requestId)}"
request = (
client.newRequest()
.url(endpoint)
.method("GET")
.headers({
"accept": "application/json",
"Authorization": f"Bearer {apiKey}",
})
)
response = await request.build().execute()
if not response.isSuccessful():
raise Error(f"xAI 视频查询失败: {response.statusCode} - {response.content}")
parsed = await parseJsonResponse(response, "xAI 视频查询")
status = str(parsed.get("status")).strip() if isinstance(parsed.get("status"), str) else ""
video = parsed.get("video") if isRecord(parsed.get("video")) else None
videoUrl = str(video.get("url")).strip() if video and isinstance(video.get("url"), str) else ""
contentType = str(video.get("content_type")).strip() if video and isinstance(video.get("content_type"), str) else ""
expiresAt = str(video.get("expires_at")).strip() if video and isinstance(video.get("expires_at"), str) else ""
duration = video.get("duration") if video and isinstance(video.get("duration"), (int, float)) else None
respectModeration = str(video.get("respect_moderation")).strip() if video and isinstance(video.get("respect_moderation"), str) else ""
errorMessage = extractApiErrorMessage(parsed)
return {
"status": status,
"video_url": videoUrl,
"content_type": contentType,
"expires_at": expiresAt,
"duration": duration,
"respect_moderation": respectModeration,
"error_message": errorMessage,
}
def normalizeVideoTaskStatus(status):
normalized = str(status or "").strip().lower()
if normalized == "done":
return "completed"
if normalized == "expired":
return "failed"
return "processing"
async def draw_image(params):
if not params or not params.get("prompt") or not str(params.get("prompt")).strip():
raise Error("参数 prompt 不能为空。")
prompt = str(params.get("prompt")).strip()
await ensureDirectories()
apiResult = await callXaiImageApi({
"prompt": prompt,
"model": params.get("model"),
"size": params.get("size"),
})
ext = guessExtensionFromUrl(apiResult["image_url"], "png")
baseName = buildFileName(prompt, params.get("file_name"), "xai_draw")
filePath = f"{DRAWS_DIR}/{baseName}.{ext}"
downloadResult = await Tools.Files.download(apiResult["image_url"], filePath)
if not downloadResult.successful:
raise Error(f"下载图片失败: {downloadResult.details}")
fileUri = f"file://{filePath}"
markdown = f"![AI生成的图片]({fileUri})"
hintLines = []
hintLines.append(f"图片已生成并保存在本地 {DRAWS_DIR}。")
hintLines.append(f"本地路径: {filePath}")
hintLines.append("")
hintLines.append("在后续回答中,请直接输出下面这一行 Markdown 来展示这张图片:")
hintLines.append("")
hintLines.append(markdown)
return {
"prompt": prompt,
"model": apiResult["effective_model"],
"remote_image_url": apiResult["image_url"],
"file_path": filePath,
"file_uri": fileUri,
"markdown": markdown,
"hint": "\n".join(hintLines),
}
async def draw_video(params):
prompt = str(params.get("prompt") if params else "").strip()
if not prompt:
raise Error("prompt 不能为空。")
await ensureDirectories()
resolvedInputs = await resolveVideoInputs(params.get("image_url"), params.get("image_path"), params.get("video_url"))
mergedParams = dict(params)
mergedParams["prompt"] = prompt
mergedParams["image"] = resolvedInputs.get("image")
mergedParams["video_url"] = resolvedInputs.get("video_url")
createdTask = await createVideoTask(mergedParams)
pollIntervalMs = normalizePositiveInteger(params.get("poll_interval_ms"), DEFAULT_POLL_INTERVAL_MS)
maxWaitTimeMs = normalizePositiveInteger(params.get("max_wait_time_ms"), DEFAULT_MAX_WAIT_TIME_MS)
deadline = time.time() * 1000 + maxWaitTimeMs
latestStatus = createdTask["status"]
latestErrorMessage = ""
remoteVideoUrl = ""
remoteContentType = ""
expiresAt = ""
remoteDuration = createdTask["duration"]
remoteRespectModeration = ""
while time.time() * 1000 <= deadline:
statusResult = await queryVideoTaskStatus(createdTask["request_id"])
latestStatus = statusResult["status"]
latestErrorMessage = statusResult["error_message"]
remoteContentType = statusResult["content_type"]
expiresAt = statusResult["expires_at"]
remoteDuration = statusResult["duration"]
remoteRespectModeration = statusResult["respect_moderation"]
normalizedStatus = normalizeVideoTaskStatus(statusResult["status"])
if normalizedStatus == "completed":
remoteVideoUrl = statusResult["video_url"]
if not remoteVideoUrl:
raise Error("视频任务已完成,但响应中未找到 video.url。")
break
if normalizedStatus == "failed":
raise Error(f"视频生成失败或过期: {latestErrorMessage or latestStatus or '接口未返回失败原因'}")
await Tools.System.sleep(pollIntervalMs)
if not remoteVideoUrl:
raise Error(
f"视频生成超时,最后状态为 {latestStatus or '未知'}{(',原因:' + latestErrorMessage) if latestErrorMessage else ''}"
)
extension = guessExtensionFromUrl(remoteVideoUrl, "mp4")
baseName = buildFileName(prompt, params.get("file_name"), "xai_video")
filePath = f"{VIDEOS_DIR}/{baseName}.{extension}"
downloadResult = await Tools.Files.download(remoteVideoUrl, filePath)
if not downloadResult.successful:
raise Error(f"下载视频失败: {downloadResult.details}")
fileUri = f"file://{filePath}"
markdownLink = f"[点击查看生成的视频]({fileUri})"
htmlVideo = f'<video controls src="{fileUri}"></video>'
hintLines = []
hintLines.append(f"视频已生成并保存在本地 {VIDEOS_DIR}。")
hintLines.append(f"本地路径: {filePath}")
hintLines.append("")
hintLines.append("后续回答如果要给出视频,请优先返回这个本地链接:")
hintLines.append(markdownLink)
hintLines.append("")
hintLines.append("如果当前渲染环境支持 HTML 视频标签,也可以使用:")
hintLines.append(htmlVideo)
return {
"prompt": prompt,
"model": createdTask["effective_model"],
"request_id": createdTask["request_id"],
"status": latestStatus,
"input_type": resolvedInputs["input_type"],
"aspect_ratio": createdTask["aspect_ratio"],
"resolution": createdTask["resolution"],
"duration": remoteDuration,
"remote_video_url": remoteVideoUrl,
"remote_content_type": remoteContentType,
"remote_expires_at": expiresAt,
"remote_respect_moderation": remoteRespectModeration,
"file_path": filePath,
"file_uri": fileUri,
"markdown_link": markdownLink,
"html_video": htmlVideo,
"hint": "\n".join(hintLines),
}
async def draw_image_wrapper(params):
try:
result = await draw_image(params)
complete({
"success": True,
"message": "xAI 图片生成成功,已下载到本地。",
"data": result,
})
except Exception as error:
console.error("draw_image 执行失败:", error)
complete({
"success": False,
"message": f"xAI 图片生成失败: {getErrorMessage(error)}",
"error_stack": getErrorStack(error),
})
async def draw_video_wrapper(params):
try:
result = await draw_video(params if params else {})
complete({
"success": True,
"message": "xAI 视频生成成功,已下载到本地。",
"data": result,
})
except Exception as error:
console.error("draw_video 执行失败:", error)
complete({
"success": False,
"message": f"xAI 视频生成失败: {getErrorMessage(error)}",
"error_stack": getErrorStack(error),
})
exports.draw_image = draw_image_wrapper
exports.draw_video = draw_video_wrapper