Files
xiaoli-cli/operit_py/minimax_draw.py
T
shuiyu1123 dc77dfd304 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 专属功能已降级处理(保留接口,返回不支持提示)
2026-06-23 12:27:18 +00:00

598 lines
26 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# METADATA
# {
# "name": "minimax_draw",
# "display_name": {
# "zh": "MiniMax 绘图",
# "en": "MiniMax Draw"
# },
# "description": {
# "zh": "使用 MiniMax 官方图像生成接口 (/v1/image_generation) 生成图片,支持文生图和带参考图的生图;结果保存到本地 /sdcard/Download/Operit/plugins/draw/minimax_draw/draws/ 目录,并返回 Markdown 图片提示。",
# "en": "Generate images with the official MiniMax image generation API (/v1/image_generation). Supports text-to-image and reference-image generation. Saves results to /sdcard/Download/Operit/plugins/draw/minimax_draw/draws/ and returns Markdown image hints."
# },
# "category": "Draw",
# "env": [
# { "name": "MINIMAX_API_KEY", "description": { "zh": "MiniMax API Key(必填)", "en": "MiniMax API key (required)" }, "required": true },
# { "name": "MINIMAX_API_BASE_URL", "description": { "zh": "MiniMax API Base URL(可选;默认 https://api.minimaxi.com,国际站可改为 https://api.minimax.io)", "en": "MiniMax API base URL (optional; defaults to https://api.minimaxi.com, international users can use https://api.minimax.io)" }, "required": false },
# { "name": "MINIMAX_IMAGE_MODEL", "description": { "zh": "默认图片模型(可选;未传 model 时使用,默认 image-01)", "en": "Default image model (optional; used when model is omitted, default image-01)" }, "required": false },
# { "name": "BEEIMG_API_KEY", "description": { "zh": "BeeIMG API Key(可选;仅当 image_paths 传本地参考图路径时需要,用于先上传到公网图床)", "en": "BeeIMG API key (optional; only needed when image_paths is used so local reference images can be uploaded first)" }, "required": false }
# ],
# "tools": [
# {
# "name": "draw_image",
# "description": { "zh": "根据提示词调用 MiniMax 官方绘图接口生成图片,支持文生图和参考图生图,保存到本地并返回 Markdown 图片提示。", "en": "Generate images with the official MiniMax image API using a prompt. Supports text-to-image and reference-image generation, saves locally, and returns Markdown image hints." },
# "parameters": [
# { "name": "prompt", "description": { "zh": "绘图提示词(中文或英文)", "en": "Image prompt (Chinese or English)" }, "type": "string", "required": true },
# { "name": "model", "description": { "zh": "模型名称(可选;不传则使用环境变量 MINIMAX_IMAGE_MODEL,否则默认 image-01)", "en": "Model name (optional; falls back to MINIMAX_IMAGE_MODEL, then image-01)" }, "type": "string", "required": false },
# { "name": "aspect_ratio", "description": { "zh": "输出比例,可选 1:1、16:9、4:3、3:2、2:3、3:4、9:16、21:9;21:9 仅 image-01 支持", "en": "Aspect ratio. Supported: 1:1, 16:9, 4:3, 3:2, 2:3, 3:4, 9:16, 21:9. 21:9 is only supported by image-01." }, "type": "string", "required": false },
# { "name": "width", "description": { "zh": "输出宽度(可选;仅 image-01 支持;需与 height 一起传,范围 512-2048 且为 8 的倍数)", "en": "Output width (optional; image-01 only; must be provided together with height, range 512-2048 and multiple of 8)" }, "type": "number", "required": false },
# { "name": "height", "description": { "zh": "输出高度(可选;仅 image-01 支持;需与 width 一起传,范围 512-2048 且为 8 的倍数)", "en": "Output height (optional; image-01 only; must be provided together with width, range 512-2048 and multiple of 8)" }, "type": "number", "required": false },
# { "name": "n", "description": { "zh": "生成图片数量,范围 1-9,默认 1", "en": "Number of images to generate, range 1-9, default 1" }, "type": "number", "required": false },
# { "name": "response_format", "description": { "zh": "返回格式,可选 url 或 base64;默认 base64(更适合直接保存到本地)", "en": "Response format: url or base64. Defaults to base64 for direct local saving." }, "type": "string", "required": false },
# { "name": "prompt_optimizer", "description": { "zh": "是否启用提示词优化(可选)", "en": "Enable prompt optimizer (optional)" }, "type": "boolean", "required": false },
# { "name": "aigc_watermark", "description": { "zh": "是否添加 AIGC 水印(可选)", "en": "Add AIGC watermark (optional)" }, "type": "boolean", "required": false },
# { "name": "seed", "description": { "zh": "随机种子(可选)", "en": "Seed (optional)" }, "type": "number", "required": false },
# { "name": "image_urls", "description": { "zh": "参考图公网 URL 数组(可选;图生图用)。支持字符串数组、JSON 字符串或逗号分隔字符串", "en": "Reference image public URL list (optional; for reference-image generation). Accepts string array, JSON string, or comma-separated string." }, "type": "array", "required": false },
# { "name": "image_paths", "description": { "zh": "参考图本地路径数组(可选;会先上传到图床再提交给 MiniMax)。支持字符串数组、JSON 字符串或逗号分隔字符串", "en": "Reference local image path list (optional; local images are uploaded first before calling MiniMax). Accepts string array, JSON string, or comma-separated string." }, "type": "array", "required": false },
# { "name": "file_name", "description": { "zh": "自定义本地文件名(不含扩展名)", "en": "Custom local output file name without extension" }, "type": "string", "required": false },
# { "name": "api_base_url", "description": { "zh": "自定义 MiniMax API Base URL(可选)", "en": "Custom MiniMax API base URL (optional)" }, "type": "string", "required": false }
# ]
# }
# ]
# }
import json
import math
import re
import time
import traceback
HTTP_TIMEOUT_MS = 600000
client = OkHttp.newBuilder().connectTimeout(HTTP_TIMEOUT_MS).readTimeout(HTTP_TIMEOUT_MS).writeTimeout(HTTP_TIMEOUT_MS).build()
DEFAULT_API_BASE_URL = "https://api.minimaxi.com"
DEFAULT_MODEL = "image-01"
DEFAULT_RESPONSE_FORMAT = "base64"
DEFAULT_IMAGE_COUNT = 1
DEFAULT_BASE64_EXTENSION = "jpeg"
SUPPORTED_ASPECT_RATIOS = ["1:1", "16:9", "4:3", "3:2", "2:3", "3:4", "9:16", "21:9"]
BEEIMG_UPLOAD_ENDPOINT = "https://beeimg.com/api/upload/file/json/"
DRAW_ROOT_DIR = getPluginConfigDir("draw")
STORAGE_DIR = f"{DRAW_ROOT_DIR}/minimax_draw"
DRAWS_DIR = f"{STORAGE_DIR}/draws"
def _is_finite(n):
return isinstance(n, (int, float)) and not isinstance(n, bool) and math.isfinite(n)
def _is_integer(n):
return _is_finite(n) and n == int(n)
def isRecord(value):
return isinstance(value, dict)
def getErrorMessage(error):
if isinstance(error, Exception):
return str(error)
return str(error)
def getApiKey():
apiKey = str(getEnv("MINIMAX_API_KEY") or "").strip()
if not apiKey:
raise Exception("MINIMAX_API_KEY 未配置,请先在环境变量中设置 MiniMax API Key。")
return apiKey
def joinUrl(baseUrl, path):
baseStr = str(baseUrl or "")
normalizedBase = baseStr if baseStr.endswith("/") else f"{baseStr}/"
pathStr = str(path or "")
normalizedPath = pathStr[1:] if pathStr.startswith("/") else pathStr
return f"{normalizedBase}{normalizedPath}"
def getApiBaseUrl(customBaseUrl=None):
fromParam = str(customBaseUrl or "").strip()
if fromParam:
return fromParam
fromEnv = str(getEnv("MINIMAX_API_BASE_URL") or "").strip()
if fromEnv:
return fromEnv
return DEFAULT_API_BASE_URL
def getImageEndpoint(baseUrl):
trimmed = str(baseUrl or "").strip()
if not trimmed:
return joinUrl(DEFAULT_API_BASE_URL, "v1/image_generation")
if "/v1/image_generation" in trimmed:
return trimmed
if trimmed.endswith("/v1"):
return joinUrl(trimmed, "image_generation")
if trimmed.endswith("/v1/"):
return joinUrl(trimmed, "image_generation")
return joinUrl(trimmed, "v1/image_generation")
def resolveModel(model):
fromParam = str(model or "").strip()
if fromParam:
return fromParam
fromEnv = str(getEnv("MINIMAX_IMAGE_MODEL") or "").strip()
if fromEnv:
return fromEnv
return DEFAULT_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 buildFileBaseName(prompt, customName, fallbackPrefix):
fromCustom = str(customName or "").strip()
if fromCustom:
return sanitizeFileName(fromCustom, fallbackPrefix)
rawPrompt = str(prompt or "").strip()
shortPrompt = f"{rawPrompt[:40]}..." if len(rawPrompt) > 40 else rawPrompt
return f"{sanitizeFileName(shortPrompt or fallbackPrefix, fallbackPrefix)}_{int(time.time() * 1000)}"
async def ensureDirectories():
dirs = [DRAW_ROOT_DIR, STORAGE_DIR, DRAWS_DIR]
for dir_ in dirs:
try:
result = await Tools.Files.mkdir(dir_)
if not result.get("successful"):
console.warn(f"创建目录失败(可能已存在): {dir_} -> {result.get('details')}")
except Exception as error:
console.warn(f"创建目录异常: {dir_} -> {getErrorMessage(error)}")
def parsePositiveInteger(value, fieldName, options=None):
hasDefault = options is not None and "defaultValue" in options
defaultValue = options["defaultValue"] if hasDefault else None
if value is None or value == "":
return defaultValue
numberValue = value if isinstance(value, (int, float)) and not isinstance(value, bool) else float(str(value))
if not _is_finite(numberValue) or numberValue <= 0 or not _is_integer(numberValue):
raise Exception(f"{fieldName} 必须是正整数。")
numberValue = int(numberValue)
if options and options.get("min") is not None and numberValue < options["min"]:
raise Exception(f"{fieldName} 不能小于 {options['min']}。")
if options and options.get("max") is not None and numberValue > options["max"]:
raise Exception(f"{fieldName} 不能大于 {options['max']}。")
return numberValue
def parseInteger(value, fieldName):
if value is None or value == "":
return None
numberValue = value if isinstance(value, (int, float)) and not isinstance(value, bool) else float(str(value))
if not _is_finite(numberValue) or not _is_integer(numberValue):
raise Exception(f"{fieldName} 必须是整数。")
return int(numberValue)
def parseBoolean(value, fieldName):
if value is None or value == "":
return None
if isinstance(value, bool):
return value
normalized = str(value).strip().lower()
if normalized in ("true", "1", "yes"):
return True
if normalized in ("false", "0", "no"):
return False
raise Exception(f"{fieldName} 必须是布尔值。")
def normalizeResponseFormat(value):
normalized = str(value or DEFAULT_RESPONSE_FORMAT).strip().lower()
if normalized != "url" and normalized != "base64":
raise Exception("response_format 仅支持 url 或 base64。")
return normalized
def normalizeAspectRatio(model, value):
normalized = str(value or "").strip()
if not normalized:
return None
if normalized not in SUPPORTED_ASPECT_RATIOS:
raise Exception(f"aspect_ratio 仅支持 {'、'.join(SUPPORTED_ASPECT_RATIOS)}。")
if normalized == "21:9" and str(model or "").strip() != "image-01":
raise Exception("aspect_ratio=21:9 仅支持 model=image-01。")
return normalized
def normalizeDimensions(model, widthValue, heightValue, aspectRatio):
if aspectRatio:
return {"width": None, "height": None}
width = parsePositiveInteger(widthValue, "width")
height = parsePositiveInteger(heightValue, "height")
if width is None and height is None:
return {"width": None, "height": None}
if width is None or height is None:
raise Exception("width 和 height 需要同时传入。")
if str(model or "").strip() != "image-01":
raise Exception("width 和 height 仅支持 model=image-01。")
if width < 512 or width > 2048 or width % 8 != 0:
raise Exception("width 需要在 512-2048 之间,且必须为 8 的倍数。")
if height < 512 or height > 2048 or height % 8 != 0:
raise Exception("height 需要在 512-2048 之间,且必须为 8 的倍数。")
return {"width": width, "height": height}
def parseStringList(value, fieldName):
if value is None or value == "":
return []
if isinstance(value, list):
return [str(item or "").strip() for item in value if str(item or "").strip()]
if isinstance(value, str):
trimmed = value.strip()
if not trimmed:
return []
try:
parsed = json.loads(trimmed)
if isinstance(parsed, list):
return [str(item or "").strip() for item in parsed if str(item or "").strip()]
except Exception:
# ignore and try comma-separated parsing
pass
return [item.strip() for item in trimmed.split(",") if item.strip()]
raise Exception(f"{fieldName} 必须是字符串数组、JSON 字符串或逗号分隔字符串。")
def isProbablyUrl(value):
return bool(re.match(r"^https?://", str(value or "").strip(), re.IGNORECASE))
def getBeeimgApiKey():
return str(getEnv("BEEIMG_API_KEY") or "").strip()
def guessMimeTypeFromPath(filePath):
lower = str(filePath or "").lower()
if lower.endswith(".png"):
return "image/png"
if lower.endswith(".jpg") or lower.endswith(".jpeg"):
return "image/jpeg"
if lower.endswith(".webp"):
return "image/webp"
if lower.endswith(".gif"):
return "image/gif"
return "application/octet-stream"
async def uploadImageToBeeimg(filePath):
exists = await Tools.Files.exists(filePath)
if not exists.get("exists"):
raise Exception(f"参考图文件不存在: {filePath}")
apiKey = getBeeimgApiKey()
if not apiKey:
raise Exception("使用 image_paths 需要配置 BEEIMG_API_KEY,用于先把本地参考图上传到公网图床。")
response = await Tools.Net.uploadFile({
"url": BEEIMG_UPLOAD_ENDPOINT,
"method": "POST",
"form_data": {
"apikey": apiKey
},
"files": [
{
"field_name": "file",
"file_path": filePath,
"content_type": guessMimeTypeFromPath(filePath)
}
]
})
if response.statusCode < 200 or response.statusCode >= 300:
raise Exception(f"BeeIMG 上传失败: HTTP {response.statusCode} - {response.content}")
try:
parsed = json.loads(response.content)
except Exception as error:
raise Exception(f"解析 BeeIMG 响应失败: {getErrorMessage(error)}")
files = parsed.get("files") if (isRecord(parsed) and isRecord(parsed.get("files"))) else None
fileUrl = str(files.get("url")).strip() if (files and files.get("url")) else ""
code = str(files.get("code")) if (files and files.get("code") is not None) else ""
status = str(files.get("status")) if (files and files.get("status")) else ""
if not fileUrl or (code != "200" and code != "" and status != "Success"):
raise Exception(f"BeeIMG 上传失败: {response.content}")
return fileUrl
async def resolveSubjectReference(imageUrls, imagePaths):
resolvedUrls = parseStringList(imageUrls, "image_urls")
resolvedPaths = parseStringList(imagePaths, "image_paths")
for url in resolvedUrls:
if not isProbablyUrl(url):
raise Exception(f"image_urls 中包含无效链接: {url}")
for filePath in resolvedPaths:
uploadedUrl = await uploadImageToBeeimg(filePath)
resolvedUrls.append(uploadedUrl)
if len(resolvedUrls) == 0:
return None
return [{"type": "character", "image_file": url} for url in resolvedUrls]
def normalizeBase64(base64):
raw = str(base64 or "").strip()
if not raw:
return raw
prefixIndex = raw.find("base64,")
if raw.startswith("data:") and prefixIndex >= 0:
return raw[prefixIndex + len("base64,"):].strip()
return raw
def guessExtensionFromUrl(url, fallbackExtension):
match = re.search(r"\.([a-z0-9]{2,5})(?:[?#].*)?$", str(url or ""), re.IGNORECASE)
if match and match.group(1):
return match.group(1).lower()
return fallbackExtension
def parseMiniMaxError(parsed, fallbackMessage):
baseResp = parsed.get("base_resp") if (isRecord(parsed) and isRecord(parsed.get("base_resp"))) else None
if not baseResp:
return
statusCode = float(baseResp.get("status_code")) if baseResp.get("status_code") is not None else 0
if _is_finite(statusCode) and statusCode != 0:
statusMsg = str(baseResp.get("status_msg")) if baseResp.get("status_msg") else fallbackMessage
raise Exception(f"MiniMax 图片接口返回错误: {statusMsg} (status_code={int(statusCode) if _is_integer(statusCode) else statusCode})")
async def callMiniMaxImageApi(params):
apiBaseUrl = getApiBaseUrl(params.get("api_base_url"))
endpoint = getImageEndpoint(apiBaseUrl)
effectiveModel = resolveModel(params.get("model"))
responseFormat = normalizeResponseFormat(params.get("response_format"))
aspectRatio = normalizeAspectRatio(effectiveModel, params.get("aspect_ratio"))
dimensions = normalizeDimensions(effectiveModel, params.get("width"), params.get("height"), aspectRatio)
imageCount = parsePositiveInteger(params.get("n"), "n", {"defaultValue": DEFAULT_IMAGE_COUNT, "min": 1, "max": 9})
promptOptimizer = parseBoolean(params.get("prompt_optimizer"), "prompt_optimizer")
aigcWatermark = parseBoolean(params.get("aigc_watermark"), "aigc_watermark")
seed = parseInteger(params.get("seed"), "seed")
subjectReference = await resolveSubjectReference(params.get("image_urls"), params.get("image_paths"))
body = {
"model": effectiveModel,
"prompt": str(params.get("prompt") or "").strip(),
"response_format": responseFormat,
"n": imageCount
}
if aspectRatio:
body["aspect_ratio"] = aspectRatio
if dimensions.get("width") is not None and dimensions.get("height") is not None:
body["width"] = dimensions["width"]
body["height"] = dimensions["height"]
if promptOptimizer is not None:
body["prompt_optimizer"] = promptOptimizer
if aigcWatermark is not None:
body["aigc_watermark"] = aigcWatermark
if seed is not None:
body["seed"] = seed
if subjectReference:
body["subject_reference"] = subjectReference
request = client.newRequest().url(endpoint).method("POST").headers({
"accept": "application/json",
"content-type": "application/json",
"Authorization": f"Bearer {getApiKey()}"
}).body(json.dumps(body), "json")
response = await request.build().execute()
if not response.isSuccessful():
raise Exception(f"MiniMax 图片 API 调用失败: {response.statusCode} - {response.content}")
try:
parsed = json.loads(response.content)
except Exception as error:
raise Exception(f"解析 MiniMax 响应失败: {getErrorMessage(error)}")
parseMiniMaxError(parsed, "MiniMax 接口返回错误")
data = parsed.get("data") if (isRecord(parsed) and isRecord(parsed.get("data"))) else None
if not data:
raise Exception("MiniMax 响应中未找到 data 对象。")
metadata = data.get("metadata") if isRecord(data.get("metadata")) else None
successCount = float(metadata.get("success_count")) if (metadata and metadata.get("success_count") is not None) else None
failedCount = float(metadata.get("failed_count")) if (metadata and metadata.get("failed_count") is not None) else None
requestId = str(parsed.get("id")) if (isRecord(parsed) and parsed.get("id")) else ""
if responseFormat == "base64":
imageBase64 = [str(item or "").strip() for item in data.get("image_base64")] if isinstance(data.get("image_base64"), list) else []
imageBase64 = [item for item in imageBase64 if len(item) > 0]
if len(imageBase64) == 0:
raise Exception("MiniMax 响应中未找到 data.image_base64。")
return {
"request_id": requestId,
"effective_model": effectiveModel,
"response_format": responseFormat,
"image_base64": imageBase64,
"image_urls": [],
"success_count": int(successCount) if _is_finite(successCount) else len(imageBase64),
"failed_count": int(failedCount) if _is_finite(failedCount) else 0,
"reference_count": len(subjectReference) if subjectReference else 0
}
imageUrls = [str(item or "").strip() for item in data.get("image_urls")] if isinstance(data.get("image_urls"), list) else []
imageUrls = [item for item in imageUrls if len(item) > 0]
if len(imageUrls) == 0:
raise Exception("MiniMax 响应中未找到 data.image_urls。")
return {
"request_id": requestId,
"effective_model": effectiveModel,
"response_format": responseFormat,
"image_base64": [],
"image_urls": imageUrls,
"success_count": int(successCount) if _is_finite(successCount) else len(imageUrls),
"failed_count": int(failedCount) if _is_finite(failedCount) else 0,
"reference_count": len(subjectReference) if subjectReference else 0
}
async def draw_image(params):
prompt = str(params.get("prompt") if params and params.get("prompt") else "").strip()
if not prompt:
raise Exception("参数 prompt 不能为空。")
await ensureDirectories()
apiResult = await callMiniMaxImageApi({
"prompt": prompt,
"model": params.get("model"),
"aspect_ratio": params.get("aspect_ratio"),
"width": params.get("width"),
"height": params.get("height"),
"n": params.get("n"),
"response_format": params.get("response_format"),
"prompt_optimizer": params.get("prompt_optimizer"),
"aigc_watermark": params.get("aigc_watermark"),
"seed": params.get("seed"),
"image_urls": params.get("image_urls"),
"image_paths": params.get("image_paths"),
"api_base_url": params.get("api_base_url")
})
baseName = buildFileBaseName(prompt, params.get("file_name"), "minimax_draw")
files = []
if apiResult["response_format"] == "base64":
for index in range(len(apiResult["image_base64"])):
suffix = f"_{index + 1}" if len(apiResult["image_base64"]) > 1 else ""
filePath = f"{DRAWS_DIR}/{baseName}{suffix}.{DEFAULT_BASE64_EXTENSION}"
writeResult = await Tools.Files.writeBinary(filePath, normalizeBase64(apiResult["image_base64"][index]))
if not writeResult.get("successful"):
raise Exception(f"保存图片失败: {writeResult.get('details')}")
fileUri = f"file://{filePath}"
files.append({
"file_path": filePath,
"file_uri": fileUri,
"markdown": f"![MiniMax 生成的图片{f' {index + 1}' if len(apiResult['image_base64']) > 1 else ''}]({fileUri})",
"remote_image_url": None
})
else:
for index in range(len(apiResult["image_urls"])):
imageUrl = apiResult["image_urls"][index]
extension = guessExtensionFromUrl(imageUrl, "png")
suffix = f"_{index + 1}" if len(apiResult["image_urls"]) > 1 else ""
filePath = f"{DRAWS_DIR}/{baseName}{suffix}.{extension}"
downloadResult = await Tools.Files.download(imageUrl, filePath)
if not downloadResult.get("successful"):
raise Exception(f"下载图片失败: {downloadResult.get('details')}")
fileUri = f"file://{filePath}"
files.append({
"file_path": filePath,
"file_uri": fileUri,
"markdown": f"![MiniMax 生成的图片{f' {index + 1}' if len(apiResult['image_urls']) > 1 else ''}]({fileUri})",
"remote_image_url": imageUrl
})
hintLines = []
hintLines.append(f"图片已生成并保存在本地 {DRAWS_DIR}。")
hintLines.append(f"共生成 {len(files)} 张。")
if apiResult["reference_count"] > 0:
hintLines.append(f"本次使用了 {apiResult['reference_count']} 张参考图。")
hintLines.append("")
hintLines.append("后续回答如果需要展示图片,请直接输出下面这些 Markdown:")
hintLines.append("")
for fileItem in files:
hintLines.append(fileItem["markdown"])
return {
"prompt": prompt,
"model": apiResult["effective_model"],
"request_id": apiResult["request_id"] or None,
"response_format": apiResult["response_format"],
"success_count": apiResult["success_count"],
"failed_count": apiResult["failed_count"],
"reference_count": apiResult["reference_count"],
"file_path": files[0]["file_path"],
"file_uri": files[0]["file_uri"],
"markdown": files[0]["markdown"],
"files": files,
"hint": "\n".join(hintLines)
}
async def draw_image_wrapper(params=None):
if params is None:
params = {}
try:
result = await draw_image(params)
complete({
"success": True,
"message": "MiniMax 图片生成成功,已保存到本地。",
"data": result
})
except Exception as error:
console.error("draw_image 执行失败:", error)
complete({
"success": False,
"message": f"MiniMax 图片生成失败: {getErrorMessage(error)}",
"error_stack": traceback.format_exc()
})
exports.draw_image = draw_image_wrapper