/* METADATA { "name": "siliconflow_draw", "display_name": { "zh": "硅基流动绘图", "en": "SiliconFlow Draw" }, "description": { "zh": "使用 SiliconFlow 官方图像与视频接口生成图片和视频。图片走 /v1/images/generations,视频走 /v1/video/submit + /v1/video/status;生成结果会立即下载到本地,避免官方临时链接过期。", "en": "Generate images and videos with SiliconFlow official image and video APIs. Images use /v1/images/generations; videos use /v1/video/submit + /v1/video/status. Generated assets are downloaded locally immediately before temporary URLs expire." }, "category": "Draw", "env": [ { "name": "SILICONFLOW_API_KEY", "description": { "zh": "SiliconFlow API Key(必填)", "en": "SiliconFlow API key (required)" }, "required": true }, { "name": "SILICONFLOW_API_BASE_URL", "description": { "zh": "SiliconFlow API Base URL(可选,不填默认 https://api.siliconflow.cn )", "en": "SiliconFlow API base URL (optional; defaults to https://api.siliconflow.cn)" }, "required": false }, { "name": "SILICONFLOW_IMAGE_MODEL", "description": { "zh": "默认生图模型(可选;例如 Kwai-Kolors/Kolors、Qwen/Qwen-Image)", "en": "Default image model (optional), e.g. Kwai-Kolors/Kolors or Qwen/Qwen-Image" }, "required": false }, { "name": "SILICONFLOW_VIDEO_MODEL", "description": { "zh": "默认生视频模型(可选;例如 Wan-AI/Wan2.2-T2V-A14B 或 Wan-AI/Wan2.2-I2V-A14B)", "en": "Default video model (optional), e.g. Wan-AI/Wan2.2-T2V-A14B or Wan-AI/Wan2.2-I2V-A14B" }, "required": false } ], "tools": [ { "name": "draw_image", "description": { "zh": "调用 SiliconFlow 官方图片生成接口生图,下载到本地并返回 Markdown 图片提示。", "en": "Generate images with SiliconFlow official image API, download locally, and return Markdown image hints." }, "parameters": [ { "name": "prompt", "description": { "zh": "生图提示词", "en": "Image prompt" }, "type": "string", "required": true }, { "name": "model", "description": { "zh": "模型名;不传则优先取 SILICONFLOW_IMAGE_MODEL,再取默认值 Kwai-Kolors/Kolors", "en": "Model name; falls back to SILICONFLOW_IMAGE_MODEL, then Kwai-Kolors/Kolors" }, "type": "string", "required": false }, { "name": "image_size", "description": { "zh": "图片尺寸,例如 1024x1024;未传时默认 1024x1024", "en": "Image size, e.g. 1024x1024; defaults to 1024x1024" }, "type": "string", "required": false }, { "name": "negative_prompt", "description": { "zh": "负面提示词(可选)", "en": "Negative prompt (optional)" }, "type": "string", "required": false }, { "name": "batch_size", "description": { "zh": "一次生成多少张图(可选)", "en": "Number of images to generate (optional)" }, "type": "number", "required": false }, { "name": "num_inference_steps", "description": { "zh": "推理步数(可选)", "en": "Inference steps (optional)" }, "type": "number", "required": false }, { "name": "guidance_scale", "description": { "zh": "提示词引导强度(可选)", "en": "Guidance scale (optional)" }, "type": "number", "required": false }, { "name": "seed", "description": { "zh": "随机种子(可选)", "en": "Seed (optional)" }, "type": "number", "required": false }, { "name": "file_name", "description": { "zh": "本地保存文件名(不含扩展名)", "en": "Local output filename without extension" }, "type": "string", "required": false }, { "name": "api_base_url", "description": { "zh": "自定义 API Base URL(可选)", "en": "Custom API base URL (optional)" }, "type": "string", "required": false } ] }, { "name": "draw_video", "description": { "zh": "调用 SiliconFlow 官方视频生成接口生视频,轮询状态,下载到本地并返回本地视频链接提示。", "en": "Generate videos with SiliconFlow official video API, poll status, download locally, and return local video link hints." }, "parameters": [ { "name": "prompt", "description": { "zh": "生视频提示词", "en": "Video prompt" }, "type": "string", "required": true }, { "name": "model", "description": { "zh": "模型名;不传则优先取 SILICONFLOW_VIDEO_MODEL,若传了 image_url/image_path 默认走 Wan-AI/Wan2.2-I2V-A14B,否则默认 Wan-AI/Wan2.2-T2V-A14B", "en": "Model name; falls back to SILICONFLOW_VIDEO_MODEL. If image_url/image_path is provided, defaults to Wan-AI/Wan2.2-I2V-A14B; otherwise Wan-AI/Wan2.2-T2V-A14B" }, "type": "string", "required": false }, { "name": "image_size", "description": { "zh": "视频尺寸,可选 1280x720、720x1280、960x960;默认 1280x720", "en": "Video size: 1280x720, 720x1280, or 960x960; defaults to 1280x720" }, "type": "string", "required": false }, { "name": "negative_prompt", "description": { "zh": "负面提示词(可选)", "en": "Negative prompt (optional)" }, "type": "string", "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 data URL)" }, "type": "string", "required": false }, { "name": "seed", "description": { "zh": "随机种子(可选)", "en": "Seed (optional)" }, "type": "number", "required": false }, { "name": "file_name", "description": { "zh": "本地保存文件名(不含扩展名)", "en": "Local output filename without extension" }, "type": "string", "required": false }, { "name": "api_base_url", "description": { "zh": "自定义 API Base URL(可选)", "en": "Custom API base URL (optional)" }, "type": "string", "required": false }, { "name": "poll_interval_ms", "description": { "zh": "轮询间隔毫秒数,默认 5000", "en": "Polling interval in ms, default 5000" }, "type": "number", "required": false }, { "name": "max_wait_time_ms", "description": { "zh": "最大等待毫秒数,默认 600000", "en": "Maximum wait time in ms, default 600000" }, "type": "number", "required": false } ] } ] }*/ /// const siliconflowDraw = (function () { const HTTP_TIMEOUT_MS = 600000; const client = OkHttp.newBuilder() .connectTimeout(HTTP_TIMEOUT_MS) .readTimeout(HTTP_TIMEOUT_MS) .writeTimeout(HTTP_TIMEOUT_MS) .build(); const DEFAULT_API_BASE_URL = "https://api.siliconflow.cn"; const DEFAULT_IMAGE_MODEL = "Kwai-Kolors/Kolors"; const DEFAULT_IMAGE_SIZE = "1024x1024"; const DEFAULT_VIDEO_TEXT_MODEL = "Wan-AI/Wan2.2-T2V-A14B"; const DEFAULT_VIDEO_IMAGE_MODEL = "Wan-AI/Wan2.2-I2V-A14B"; const DEFAULT_VIDEO_SIZE = "1280x720"; const DEFAULT_POLL_INTERVAL_MS = 5000; const DEFAULT_MAX_WAIT_TIME_MS = 600000; const VIDEO_SIZE_OPTIONS = ["1280x720", "720x1280", "960x960"]; const DRAW_ROOT_DIR = getPluginConfigDir("draw"); const STORAGE_DIR = `${DRAW_ROOT_DIR}/siliconflow_draw`; const DRAWS_DIR = `${STORAGE_DIR}/draws`; const VIDEOS_DIR = `${STORAGE_DIR}/videos`; type JsonMap = Record; type ImageParams = { prompt: string; model?: string; image_size?: string; negative_prompt?: string; batch_size?: number; num_inference_steps?: number; guidance_scale?: number; seed?: number; file_name?: string; api_base_url?: string; }; type VideoParams = { prompt: string; model?: string; image_size?: string; negative_prompt?: string; image_url?: string; image_path?: string; seed?: number; file_name?: string; api_base_url?: string; poll_interval_ms?: number; max_wait_time_ms?: number; }; type ImageApiResult = { effective_model: string; image_urls: string[]; seed: any; }; type VideoTaskResult = { api_base_url: string; effective_model: string; request_id: string; seed: any; }; type VideoStatusResult = { status: string; reason: string; video_url: string; seed: any; }; type ImageFileItem = { file_path: string; file_uri: string; markdown: string; }; function getApiKey() { const apiKey = getEnv("SILICONFLOW_API_KEY"); if (!apiKey) { throw new Error("SILICONFLOW_API_KEY 未配置,请先在环境变量中设置硅基流动 API Key。"); } return apiKey; } function getApiBaseUrl(customBaseUrl) { const fromParam = String(customBaseUrl || "").trim(); if (fromParam) return fromParam; const fromEnv = String(getEnv("SILICONFLOW_API_BASE_URL") || "").trim(); if (fromEnv) return fromEnv; return DEFAULT_API_BASE_URL; } function joinUrl(baseUrl, path) { const normalizedBase = baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`; const normalizedPath = path.startsWith("/") ? path.slice(1) : path; return `${normalizedBase}${normalizedPath}`; } function getImageEndpoint(baseUrl) { const trimmed = String(baseUrl || "").trim(); if (!trimmed) return joinUrl(DEFAULT_API_BASE_URL, "v1/images/generations"); if (trimmed.includes("/v1/images/generations")) return trimmed; if (trimmed.endsWith("/v1")) return joinUrl(trimmed, "images/generations"); if (trimmed.endsWith("/v1/")) return joinUrl(trimmed, "images/generations"); return joinUrl(trimmed, "v1/images/generations"); } function getVideoSubmitEndpoint(baseUrl) { const trimmed = String(baseUrl || "").trim(); if (!trimmed) return joinUrl(DEFAULT_API_BASE_URL, "v1/video/submit"); if (trimmed.includes("/v1/video/submit")) return trimmed; if (trimmed.endsWith("/v1")) return joinUrl(trimmed, "video/submit"); if (trimmed.endsWith("/v1/")) return joinUrl(trimmed, "video/submit"); return joinUrl(trimmed, "v1/video/submit"); } function getVideoStatusEndpoint(baseUrl) { const trimmed = String(baseUrl || "").trim(); if (!trimmed) return joinUrl(DEFAULT_API_BASE_URL, "v1/video/status"); if (trimmed.includes("/v1/video/status")) return trimmed; if (trimmed.endsWith("/v1")) return joinUrl(trimmed, "video/status"); if (trimmed.endsWith("/v1/")) return joinUrl(trimmed, "video/status"); return joinUrl(trimmed, "v1/video/status"); } function sanitizeFileName(name, prefix) { const safe = String(name || "").replace(/[\\/:*?"<>|]/g, "_").trim(); if (!safe) return `${prefix}_${Date.now()}`; return safe.substring(0, 80); } function buildFileBaseName(prompt, customName, prefix) { if (customName && String(customName).trim().length > 0) { return sanitizeFileName(customName, prefix); } const rawPrompt = String(prompt || "").trim(); const shortPrompt = rawPrompt.length > 40 ? `${rawPrompt.substring(0, 40)}...` : rawPrompt; return `${sanitizeFileName(shortPrompt || prefix, prefix)}_${Date.now()}`; } function getErrorMessage(error) { if (error instanceof Error) return error.message; return String(error); } function guessMimeTypeFromPath(filePath) { const lower = String(filePath || "").toLowerCase(); if (lower.endsWith(".png")) return "image/png"; if (lower.endsWith(".jpg") || lower.endsWith(".jpeg")) return "image/jpeg"; if (lower.endsWith(".webp")) return "image/webp"; if (lower.endsWith(".gif")) return "image/gif"; return "image/png"; } function guessExtensionFromUrl(url, fallbackExtension) { const match = String(url || "").match(/\.([a-z0-9]{2,5})(?:[?#].*)?$/i); if (match && match[1]) return match[1].toLowerCase(); return fallbackExtension; } function parsePositiveInteger(value, fieldName) { if (value === undefined || value === null || value === "") return undefined; const numberValue = Number(value); if (!Number.isFinite(numberValue) || numberValue <= 0) { throw new Error(`${fieldName} 必须是正整数。`); } return Math.floor(numberValue); } function parseFiniteNumber(value, fieldName) { if (value === undefined || value === null || value === "") return undefined; const numberValue = Number(value); if (!Number.isFinite(numberValue)) { throw new Error(`${fieldName} 必须是有效数字。`); } return numberValue; } function parseSeed(value) { if (value === undefined || value === null || value === "") return undefined; const numberValue = Number(value); if (!Number.isFinite(numberValue)) { throw new Error("seed 必须是有效数字。"); } return Math.floor(numberValue); } function normalizeImageSize(imageSize, model) { const trimmedModel = String(model || "").trim().toLowerCase(); const rawSize = String(imageSize || "").trim(); const isQwenEditModel = trimmedModel.startsWith("qwen/qwen-image-edit"); if (isQwenEditModel) { if (rawSize) { throw new Error("当前模型为 Qwen 图像编辑模型,官方文档标注不支持 image_size 参数,请去掉 image_size。"); } return undefined; } if (!rawSize) return DEFAULT_IMAGE_SIZE; if (!/^\d+x\d+$/i.test(rawSize)) { throw new Error("image_size 格式必须是 widthxheight,例如 1024x1024。"); } return rawSize.toLowerCase(); } function normalizeVideoSize(imageSize) { const rawSize = String(imageSize || "").trim(); if (!rawSize) return DEFAULT_VIDEO_SIZE; if (!VIDEO_SIZE_OPTIONS.includes(rawSize)) { throw new Error(`image_size 仅支持 ${VIDEO_SIZE_OPTIONS.join("、")}。`); } return rawSize; } function isProbablyUrl(value) { return /^https?:\/\//i.test(String(value || "").trim()); } async function ensureDirectories() { const dirs = [DRAW_ROOT_DIR, STORAGE_DIR, DRAWS_DIR, VIDEOS_DIR]; for (const dir of dirs) { try { const result = await Tools.Files.mkdir(dir); if (!result.successful) { console.warn(`创建目录失败(可能已存在): ${dir} -> ${result.details}`); } } catch (error) { console.warn(`创建目录异常: ${dir} -> ${getErrorMessage(error)}`); } } } async function readLocalImageAsDataUrl(filePath) { const trimmedPath = String(filePath || "").trim(); if (!trimmedPath) { throw new Error("image_path 不能为空。"); } const existsResult = await Tools.Files.exists(trimmedPath); if (!existsResult.exists) { throw new Error(`本地图片不存在: ${trimmedPath}`); } const binaryResult = await Tools.Files.readBinary(trimmedPath); const base64Content = binaryResult && binaryResult.contentBase64 ? String(binaryResult.contentBase64).trim() : ""; if (!base64Content) { throw new Error(`读取本地图片失败: ${trimmedPath}`); } return `data:${guessMimeTypeFromPath(trimmedPath)};base64,${base64Content}`; } async function resolveVideoImageInput(imageUrl, imagePath) { const trimmedUrl = String(imageUrl || "").trim(); const trimmedPath = String(imagePath || "").trim(); if (trimmedUrl && trimmedPath) { throw new Error("image_url 和 image_path 只能二选一,请不要同时传。"); } if (trimmedUrl) { if (!isProbablyUrl(trimmedUrl)) { throw new Error("image_url 必须是 http 或 https 链接。"); } return trimmedUrl; } if (trimmedPath) { return await readLocalImageAsDataUrl(trimmedPath); } return undefined; } async function executeJsonRequest(url: string, body: JsonMap): Promise { const apiKey = getApiKey(); const request = client .newRequest() .url(url) .method("POST") .headers({ "accept": "application/json", "content-type": "application/json", "Authorization": `Bearer ${apiKey}` }) .body(JSON.stringify(body), "json"); const response = await request.build().execute(); if (!response.isSuccessful()) { throw new Error(`HTTP ${response.statusCode}: ${response.content}`); } try { return JSON.parse(response.content); } catch (error) { throw new Error(`解析响应 JSON 失败: ${getErrorMessage(error)}`); } } async function callImageApi(params: ImageParams): Promise { const apiBaseUrl = getApiBaseUrl(params.api_base_url); const endpoint = getImageEndpoint(apiBaseUrl); const modelFromParam = String(params.model || "").trim(); const modelFromEnv = String(getEnv("SILICONFLOW_IMAGE_MODEL") || "").trim(); const effectiveModel = modelFromParam || modelFromEnv || DEFAULT_IMAGE_MODEL; const body: JsonMap = { model: effectiveModel, prompt: String(params.prompt || "").trim() }; const imageSize = normalizeImageSize(params.image_size, effectiveModel); if (imageSize) body.image_size = imageSize; const negativePrompt = String(params.negative_prompt || "").trim(); if (negativePrompt) body.negative_prompt = negativePrompt; const batchSize = parsePositiveInteger(params.batch_size, "batch_size"); if (batchSize !== undefined) body.batch_size = batchSize; const numInferenceSteps = parsePositiveInteger(params.num_inference_steps, "num_inference_steps"); if (numInferenceSteps !== undefined) body.num_inference_steps = numInferenceSteps; const guidanceScale = parseFiniteNumber(params.guidance_scale, "guidance_scale"); if (guidanceScale !== undefined) body.guidance_scale = guidanceScale; const seed = parseSeed(params.seed); if (seed !== undefined) body.seed = seed; const parsed: any = await executeJsonRequest(endpoint, body); const images = Array.isArray(parsed && parsed.images) ? parsed.images : []; const imageUrls = images .map(item => item && item.url ? String(item.url).trim() : "") .filter(item => item.length > 0); if (imageUrls.length === 0) { throw new Error("图片接口返回中未找到 images[].url,请检查模型与参数是否匹配。"); } return { effective_model: effectiveModel, image_urls: imageUrls, seed: parsed && parsed.seed !== undefined ? parsed.seed : (seed !== undefined ? seed : null) }; } async function createVideoTask(params: VideoParams, imageInput?: string): Promise { const apiBaseUrl = getApiBaseUrl(params.api_base_url); const endpoint = getVideoSubmitEndpoint(apiBaseUrl); const modelFromParam = String(params.model || "").trim(); const modelFromEnv = String(getEnv("SILICONFLOW_VIDEO_MODEL") || "").trim(); const defaultVideoModel = imageInput ? DEFAULT_VIDEO_IMAGE_MODEL : DEFAULT_VIDEO_TEXT_MODEL; const effectiveModel = modelFromParam || modelFromEnv || defaultVideoModel; if (effectiveModel === DEFAULT_VIDEO_IMAGE_MODEL && !imageInput) { throw new Error(`当前模型 ${DEFAULT_VIDEO_IMAGE_MODEL} 为图生视频模型,必须传 image_url 或 image_path。`); } const body: JsonMap = { model: effectiveModel, prompt: String(params.prompt || "").trim(), image_size: normalizeVideoSize(params.image_size) }; const negativePrompt = String(params.negative_prompt || "").trim(); if (negativePrompt) body.negative_prompt = negativePrompt; if (imageInput) body.image = imageInput; const seed = parseSeed(params.seed); if (seed !== undefined) body.seed = seed; const parsed: any = await executeJsonRequest(endpoint, body); const requestId = parsed && parsed.requestId ? String(parsed.requestId).trim() : ""; if (!requestId) { throw new Error("视频接口返回中未找到 requestId。"); } return { api_base_url: apiBaseUrl, effective_model: effectiveModel, request_id: requestId, seed: seed !== undefined ? seed : null }; } async function queryVideoStatus(apiBaseUrl: string, requestId: string): Promise { const endpoint = getVideoStatusEndpoint(apiBaseUrl); const parsed: any = await executeJsonRequest(endpoint, { requestId }); const status = parsed && parsed.status ? String(parsed.status).trim() : ""; const reason = parsed && parsed.reason ? String(parsed.reason).trim() : ""; const videos: any[] = parsed && parsed.results && Array.isArray(parsed.results.videos) ? parsed.results.videos : []; const videoUrl = videos.length > 0 && videos[0] && videos[0].url ? String(videos[0].url).trim() : ""; const seed = parsed && parsed.results && parsed.results.seed !== undefined ? parsed.results.seed : null; return { status, reason, video_url: videoUrl, seed }; } function normalizeVideoStatus(status) { return String(status || "").trim().toLowerCase(); } async function draw_image(params: ImageParams) { const prompt = String(params && params.prompt ? params.prompt : "").trim(); if (!prompt) { throw new Error("prompt 不能为空。"); } await ensureDirectories(); const apiResult = await callImageApi(params); const baseName = buildFileBaseName(prompt, params.file_name, "siliconflow_image"); const files: ImageFileItem[] = []; for (let index = 0; index < apiResult.image_urls.length; index += 1) { const imageUrl = apiResult.image_urls[index]; const extension = guessExtensionFromUrl(imageUrl, "png"); const suffix = apiResult.image_urls.length > 1 ? `_${index + 1}` : ""; const filePath = `${DRAWS_DIR}/${baseName}${suffix}.${extension}`; const downloadResult = await Tools.Files.download(imageUrl, filePath); if (!downloadResult.successful) { throw new Error(`下载图片失败: ${downloadResult.details}`); } const fileUri = `file://${filePath}`; files.push({ file_path: filePath, file_uri: fileUri, markdown: `![AI生成的图片${apiResult.image_urls.length > 1 ? ` ${index + 1}` : ""}](${fileUri})` }); } const hintLines: string[] = []; hintLines.push(`图片已生成并下载到 ${DRAWS_DIR}。`); hintLines.push(`共生成 ${files.length} 张。`); hintLines.push(""); hintLines.push("后续回答如果需要展示图片,请直接输出下面这些 Markdown:"); hintLines.push(""); for (const fileItem of files) { hintLines.push(fileItem.markdown); } return { prompt, model: apiResult.effective_model, seed: apiResult.seed, file_path: files[0].file_path, file_uri: files[0].file_uri, markdown: files[0].markdown, files, hint: hintLines.join("\n") }; } async function draw_video(params: VideoParams) { const prompt = String(params && params.prompt ? params.prompt : "").trim(); if (!prompt) { throw new Error("prompt 不能为空。"); } await ensureDirectories(); const imageInput = await resolveVideoImageInput(params.image_url, params.image_path); const createdTask = await createVideoTask(params, imageInput); const pollIntervalMs = parsePositiveInteger(params.poll_interval_ms, "poll_interval_ms") || DEFAULT_POLL_INTERVAL_MS; const maxWaitTimeMs = parsePositiveInteger(params.max_wait_time_ms, "max_wait_time_ms") || DEFAULT_MAX_WAIT_TIME_MS; const deadline = Date.now() + maxWaitTimeMs; let latestStatus = ""; let latestReason = ""; let remoteVideoUrl = ""; let resultSeed = createdTask.seed; while (Date.now() <= deadline) { const statusResult = await queryVideoStatus(createdTask.api_base_url, createdTask.request_id); latestStatus = statusResult.status; latestReason = statusResult.reason; resultSeed = statusResult.seed !== null && statusResult.seed !== undefined ? statusResult.seed : resultSeed; const normalizedStatus = normalizeVideoStatus(statusResult.status); if (normalizedStatus === "succeed") { remoteVideoUrl = statusResult.video_url; if (!remoteVideoUrl) { throw new Error("视频任务已成功,但返回中未找到 results.videos[0].url。"); } break; } if (normalizedStatus === "failed") { throw new Error(`视频生成失败: ${latestReason || "接口未返回失败原因"}`); } await Tools.System.sleep(pollIntervalMs); } if (!remoteVideoUrl) { throw new Error(`视频生成超时,最后状态为 ${latestStatus || "未知"}${latestReason ? `,原因:${latestReason}` : ""}`); } const baseName = buildFileBaseName(prompt, params.file_name, "siliconflow_video"); const extension = guessExtensionFromUrl(remoteVideoUrl, "mp4"); const filePath = `${VIDEOS_DIR}/${baseName}.${extension}`; const downloadResult = await Tools.Files.download(remoteVideoUrl, filePath); if (!downloadResult.successful) { throw new Error(`下载视频失败: ${downloadResult.details}`); } const fileUri = `file://${filePath}`; const markdownLink = `[点击查看生成的视频](${fileUri})`; const htmlVideo = ``; const hintLines: string[] = []; hintLines.push(`视频已生成并下载到 ${VIDEOS_DIR}。`); hintLines.push(`本地路径: ${filePath}`); hintLines.push(""); hintLines.push("后续回答如果要给出视频,请优先返回这个本地链接:"); hintLines.push(markdownLink); hintLines.push(""); hintLines.push("如果当前渲染环境支持 HTML 视频标签,也可以使用:"); hintLines.push(htmlVideo); return { prompt, model: createdTask.effective_model, request_id: createdTask.request_id, seed: resultSeed, file_path: filePath, file_uri: fileUri, remote_video_url: remoteVideoUrl, markdown_link: markdownLink, html_video: htmlVideo, hint: hintLines.join("\n") }; } async function draw_image_wrapper(params: ImageParams) { try { const result = await draw_image(params || {}); complete({ success: true, message: "SiliconFlow 图片生成成功,已下载到本地。", data: result }); } catch (error) { console.error("draw_image 执行失败:", error); complete({ success: false, message: `SiliconFlow 图片生成失败: ${getErrorMessage(error)}`, error_stack: error && error.stack ? error.stack : undefined }); } } async function draw_video_wrapper(params: VideoParams) { try { const result = await draw_video(params || {}); complete({ success: true, message: "SiliconFlow 视频生成成功,已下载到本地。", data: result }); } catch (error) { console.error("draw_video 执行失败:", error); complete({ success: false, message: `SiliconFlow 视频生成失败: ${getErrorMessage(error)}`, error_stack: error && error.stack ? error.stack : undefined }); } } return { draw_image: draw_image_wrapper, draw_video: draw_video_wrapper }; })(); exports.draw_image = siliconflowDraw.draw_image; exports.draw_video = siliconflowDraw.draw_video;