@surfmate.team/digital-human-runninghub 0.3.4 → 0.3.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +10 -1
- package/dist/index.js +14 -0
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -268,6 +268,10 @@ declare const QWEN2511_SPEC: ImageEditSpec;
|
|
|
268
268
|
type RunningHubQwen2511Config = {
|
|
269
269
|
readonly client: RunningHubClient;
|
|
270
270
|
};
|
|
271
|
+
/**
|
|
272
|
+
* @deprecated Use {@link createRunningHubQwenEdit2511} (app 2062743091349647362)
|
|
273
|
+
* instead — it adds an output-resolution control and is the current Qwen edit app.
|
|
274
|
+
*/
|
|
271
275
|
declare function createRunningHubQwen2511(config: RunningHubQwen2511Config): ImageEditPort;
|
|
272
276
|
|
|
273
277
|
declare const FLUX_EDIT_SPEC: ImageEditSpec;
|
|
@@ -281,6 +285,11 @@ type RunningHubQwenEdit2511Config = {
|
|
|
281
285
|
readonly client: RunningHubClient;
|
|
282
286
|
};
|
|
283
287
|
declare function createRunningHubQwenEdit2511(config: RunningHubQwenEdit2511Config): ImageEditPort;
|
|
288
|
+
declare const QWEN_EDIT_2511_DUAL_SPEC: ImageEditSpec;
|
|
289
|
+
type RunningHubQwenEdit2511DualConfig = {
|
|
290
|
+
readonly client: RunningHubClient;
|
|
291
|
+
};
|
|
292
|
+
declare function createRunningHubQwenEdit2511Dual(config: RunningHubQwenEdit2511DualConfig): ImageEditPort;
|
|
284
293
|
|
|
285
294
|
type TalkingVideoInput = {
|
|
286
295
|
/** The single reference image (the people in frame). */
|
|
@@ -341,4 +350,4 @@ declare const TALKING_VIDEO_DEFAULT_FRAMES = 502;
|
|
|
341
350
|
/** Frame count for a target duration at the workflow's fps. */
|
|
342
351
|
declare const framesForSeconds: (seconds: number) => number;
|
|
343
352
|
|
|
344
|
-
export { DIALOGUE_APP_ID, DIALOGUE_NODE_IDS, DIALOGUE_RUN_OPTIONS, type DialogueVideoInput, type DialogueVideoPort, type DialogueVideoProgress, FLUX2_KLEIN_SPEC, FLUX_EDIT_SPEC, GREETING_APP_ID, GREETING_CROP_POSITION, GREETING_DEFAULT_TTS_MODEL, GREETING_LORA, GREETING_LORA_STRENGTH, GREETING_MODEL, GREETING_NEGATIVE_PROMPT, GREETING_NODE_IDS, GREETING_POSITIVE_PROMPT_DEFAULT, GREETING_RUN_OPTIONS, GREETING_STEPS, type ImageEditInput, type ImageEditPort, type ImageEditProgress, type ImageEditSpec, type NodeInfo, QWEN2511_SPEC, QWEN2512_SPEC, QWEN_EDIT_2511_SPEC, type RunAppOptions, type RunningHubClient, type RunningHubConfig, type RunningHubDialogueVideoConfig, type RunningHubFlux2KleinConfig, type RunningHubFluxEditConfig, type RunningHubGreetingVideoConfig, type RunningHubQwen2511Config, type RunningHubQwen2512Config, type RunningHubQwenEdit2511Config, type RunningHubStaticWaitingVideoConfig, type RunningHubTalkingVideoConfig, TALKING_VIDEO_APP_ID, TALKING_VIDEO_DEFAULT_FRAMES, TALKING_VIDEO_DEFAULT_RESOLUTION, TALKING_VIDEO_FPS, TALKING_VIDEO_NODE_IDS, TALKING_VIDEO_RUN_OPTIONS, type TalkingVideoInput, type TalkingVideoPort, type TalkingVideoProgress, type TaskOutputFile, type TaskPollResult, type TaskProgress, type Text2ImageSpec, type TextToImageInput, type TextToImagePort, type TextToImageProgress, type WatchOptions, buildGreetingNodeInfoList, createImageEditPort, createRunningHubClient, createRunningHubDialogueVideo, createRunningHubFlux2Klein, createRunningHubFluxEdit, createRunningHubGreetingVideo, createRunningHubQwen2511, createRunningHubQwen2512, createRunningHubQwenEdit2511, createRunningHubStaticWaitingVideo, createRunningHubTalkingVideo, createText2ImagePort, fetchAvatarAsImageFile, framesForSeconds, parsePollResponse, silenceAudioFile };
|
|
353
|
+
export { DIALOGUE_APP_ID, DIALOGUE_NODE_IDS, DIALOGUE_RUN_OPTIONS, type DialogueVideoInput, type DialogueVideoPort, type DialogueVideoProgress, FLUX2_KLEIN_SPEC, FLUX_EDIT_SPEC, GREETING_APP_ID, GREETING_CROP_POSITION, GREETING_DEFAULT_TTS_MODEL, GREETING_LORA, GREETING_LORA_STRENGTH, GREETING_MODEL, GREETING_NEGATIVE_PROMPT, GREETING_NODE_IDS, GREETING_POSITIVE_PROMPT_DEFAULT, GREETING_RUN_OPTIONS, GREETING_STEPS, type ImageEditInput, type ImageEditPort, type ImageEditProgress, type ImageEditSpec, type NodeInfo, QWEN2511_SPEC, QWEN2512_SPEC, QWEN_EDIT_2511_DUAL_SPEC, QWEN_EDIT_2511_SPEC, type RunAppOptions, type RunningHubClient, type RunningHubConfig, type RunningHubDialogueVideoConfig, type RunningHubFlux2KleinConfig, type RunningHubFluxEditConfig, type RunningHubGreetingVideoConfig, type RunningHubQwen2511Config, type RunningHubQwen2512Config, type RunningHubQwenEdit2511Config, type RunningHubQwenEdit2511DualConfig, type RunningHubStaticWaitingVideoConfig, type RunningHubTalkingVideoConfig, TALKING_VIDEO_APP_ID, TALKING_VIDEO_DEFAULT_FRAMES, TALKING_VIDEO_DEFAULT_RESOLUTION, TALKING_VIDEO_FPS, TALKING_VIDEO_NODE_IDS, TALKING_VIDEO_RUN_OPTIONS, type TalkingVideoInput, type TalkingVideoPort, type TalkingVideoProgress, type TaskOutputFile, type TaskPollResult, type TaskProgress, type Text2ImageSpec, type TextToImageInput, type TextToImagePort, type TextToImageProgress, type WatchOptions, buildGreetingNodeInfoList, createImageEditPort, createRunningHubClient, createRunningHubDialogueVideo, createRunningHubFlux2Klein, createRunningHubFluxEdit, createRunningHubGreetingVideo, createRunningHubQwen2511, createRunningHubQwen2512, createRunningHubQwenEdit2511, createRunningHubQwenEdit2511Dual, createRunningHubStaticWaitingVideo, createRunningHubTalkingVideo, createText2ImagePort, fetchAvatarAsImageFile, framesForSeconds, parsePollResponse, silenceAudioFile };
|
package/dist/index.js
CHANGED
|
@@ -584,6 +584,18 @@ var QWEN_EDIT_2511_SPEC = {
|
|
|
584
584
|
function createRunningHubQwenEdit2511(config) {
|
|
585
585
|
return createImageEditPort(config.client, QWEN_EDIT_2511_SPEC);
|
|
586
586
|
}
|
|
587
|
+
var QWEN_EDIT_2511_DUAL_SPEC = {
|
|
588
|
+
appId: "2062751684262195202",
|
|
589
|
+
promptNodeId: "111",
|
|
590
|
+
// Text
|
|
591
|
+
imageNodeIds: ["61", "69"],
|
|
592
|
+
// 图像1 (primary), 图像2 (extra)
|
|
593
|
+
resolutionNodeId: "70"
|
|
594
|
+
// INTConstant
|
|
595
|
+
};
|
|
596
|
+
function createRunningHubQwenEdit2511Dual(config) {
|
|
597
|
+
return createImageEditPort(config.client, QWEN_EDIT_2511_DUAL_SPEC);
|
|
598
|
+
}
|
|
587
599
|
|
|
588
600
|
// src/talkingvideo/constants.ts
|
|
589
601
|
var TALKING_VIDEO_APP_ID = "2062483522136395777";
|
|
@@ -661,6 +673,7 @@ export {
|
|
|
661
673
|
GREETING_STEPS,
|
|
662
674
|
QWEN2511_SPEC,
|
|
663
675
|
QWEN2512_SPEC,
|
|
676
|
+
QWEN_EDIT_2511_DUAL_SPEC,
|
|
664
677
|
QWEN_EDIT_2511_SPEC,
|
|
665
678
|
TALKING_VIDEO_APP_ID,
|
|
666
679
|
TALKING_VIDEO_DEFAULT_FRAMES,
|
|
@@ -678,6 +691,7 @@ export {
|
|
|
678
691
|
createRunningHubQwen2511,
|
|
679
692
|
createRunningHubQwen2512,
|
|
680
693
|
createRunningHubQwenEdit2511,
|
|
694
|
+
createRunningHubQwenEdit2511Dual,
|
|
681
695
|
createRunningHubStaticWaitingVideo,
|
|
682
696
|
createRunningHubTalkingVideo,
|
|
683
697
|
createText2ImagePort,
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/client/parsePollResponse.ts","../src/client/createRunningHubClient.ts","../src/greeting/constants.ts","../src/greeting/buildNodeInfoList.ts","../src/greeting/silence.ts","../src/shared/media.ts","../src/shared/runVideoApp.ts","../src/greeting/createRunningHubGreetingVideo.ts","../src/waiting/createRunningHubStaticWaitingVideo.ts","../src/dialogue/constants.ts","../src/dialogue/createRunningHubDialogueVideo.ts","../src/text2image/run.ts","../src/text2image/qwen2512.ts","../src/text2image/flux2klein.ts","../src/imageedit/run.ts","../src/imageedit/qwen2511.ts","../src/imageedit/fluxedit.ts","../src/imageedit/qwenEdit2511.ts","../src/talkingvideo/constants.ts","../src/talkingvideo/createRunningHubTalkingVideo.ts"],"sourcesContent":["// Pure: normalize RunningHub's several poll-response shapes into TaskPollResult.\n// Isolated from the HTTP client so it can be tested with fixed data.\n// (Faithful port of mate's runningHubService.parsePollResponse.)\n\nimport type { TaskOutputFile, TaskPollResult } from './types'\n\nexport function parsePollResponse(data: unknown): TaskPollResult {\n const obj = data as Record<string, unknown>\n\n // Format A: { code, data, msg }\n if (obj.code !== undefined) {\n if (obj.code === 804 || obj.msg === 'APIKEY_TASK_IS_RUNNING') {\n const inner = obj.data as Record<string, unknown> | undefined\n const wsUrl = typeof inner?.netWssUrl === 'string' ? (inner.netWssUrl as string) : undefined\n return wsUrl ? { status: 'RUNNING', wsUrl } : { status: 'RUNNING' }\n }\n if (obj.code !== 0) {\n return { status: 'FAILED', msg: String(obj.msg ?? 'Unknown error') }\n }\n const payload = obj.data\n if (Array.isArray(payload)) {\n return { status: 'SUCCESS', files: payload as TaskOutputFile[] }\n }\n const inner = payload as Record<string, unknown> | undefined\n const taskStatus = inner?.taskStatus as string | undefined\n if (taskStatus === 'RUNNING') return { status: 'RUNNING' }\n if (taskStatus === 'QUEUED') return { status: 'QUEUED' }\n if (taskStatus === 'SUCCESS' || taskStatus === 'SUCCEDD') {\n const fileUrl = inner?.fileUrl as string | undefined\n if (fileUrl) return { status: 'SUCCESS', files: [{ fileUrl, fileType: 'video' }] }\n return { status: 'SUCCESS', files: [] }\n }\n if (taskStatus === 'FAILED') {\n return { status: 'FAILED', msg: String(inner?.errorInfo ?? 'Task failed') }\n }\n return { status: 'FAILED', msg: `Unexpected taskStatus: ${taskStatus}` }\n }\n\n // Format B: flat { taskStatus, fileUrl, outputs, ... }\n const taskStatus = obj.taskStatus as string | undefined\n if (taskStatus === 'RUNNING') return { status: 'RUNNING' }\n if (taskStatus === 'QUEUED') return { status: 'QUEUED' }\n if (taskStatus === 'SUCCESS' || taskStatus === 'SUCCEDD') {\n const fileUrl = obj.fileUrl as string | undefined\n const outputs = obj.outputs as TaskOutputFile[] | undefined\n if (Array.isArray(outputs) && outputs.length > 0) {\n return { status: 'SUCCESS', files: outputs }\n }\n if (fileUrl) return { status: 'SUCCESS', files: [{ fileUrl, fileType: 'video' }] }\n return { status: 'SUCCESS', files: [] }\n }\n if (taskStatus === 'FAILED') {\n return { status: 'FAILED', msg: String(obj.errorMessage ?? obj.errorInfo ?? 'Task failed') }\n }\n return { status: 'FAILED', msg: `Unexpected response: ${JSON.stringify(obj).slice(0, 200)}` }\n}\n","// RunningHub OpenAPI client — action layer (HTTP + optional WebSocket).\n// Generic and workflow-agnostic: upload files, run an AI App, watch a task.\n// (Faithful port of mate's runningHubService, minus localStorage task\n// persistence — that's an app concern, kept out of the library.)\n\nimport type {\n NodeInfo,\n RunAppOptions,\n RunningHubClient,\n RunningHubConfig,\n TaskOutputFile,\n TaskPollResult,\n TaskProgress,\n WatchOptions,\n} from './types'\nimport { parsePollResponse } from './parsePollResponse'\n\nconst DEFAULT_BASE = '/api/runninghub'\nconst DEFAULT_POLL_INTERVAL_MS = 3000\n// Long ceiling to bound runaway polling; normal end is SUCCESS/FAILED.\nconst DEFAULT_TIMEOUT_MS = 15 * 60 * 1000\n\nconst sleep = (ms: number): Promise<void> => new Promise((r) => setTimeout(r, ms))\n\ntype UploadResponse = { code: number; msg: string; data: { fileName: string } }\ntype CreateTaskResponse = {\n taskId: string\n status: string\n errorCode: string\n errorMessage: string\n}\n\n// Attach a ComfyUI-style WebSocket to stream step-level progress. Best-effort:\n// parse 'progress' frames and mutate `latest` in place; the poll loop forwards\n// the latest value each tick. Any failure degrades to poll-only (no percent).\nfunction attachProgressWs(\n url: string,\n latest: { percent?: number; step?: number; total?: number },\n): WebSocket | null {\n try {\n const ws = new WebSocket(url)\n ws.onmessage = (event) => {\n if (typeof event.data !== 'string') return // binary previews — ignore\n try {\n const msg = JSON.parse(event.data) as { type?: string; data?: Record<string, unknown> }\n if (msg.type !== 'progress' || !msg.data) return\n const value = msg.data.value\n const max = msg.data.max\n if (typeof value === 'number' && typeof max === 'number' && max > 0) {\n latest.step = value\n latest.total = max\n latest.percent = Math.round((value / max) * 100)\n }\n } catch {\n /* malformed frame — keep listening */\n }\n }\n ws.onerror = () => {\n /* best-effort; polling carries the flow */\n }\n return ws\n } catch {\n return null\n }\n}\n\nfunction closeWs(ws: WebSocket | null): void {\n if (!ws) return\n if (ws.readyState === WebSocket.OPEN || ws.readyState === WebSocket.CONNECTING) ws.close()\n}\n\nexport function createRunningHubClient(config: RunningHubConfig): RunningHubClient {\n const base = config.baseUrl ?? DEFAULT_BASE\n const apiKey = config.apiKey\n const doFetch = config.fetchImpl ?? fetch\n\n async function uploadFile(file: File, fileType: 'image' | 'audio'): Promise<string> {\n const form = new FormData()\n form.append('file', file)\n form.append('apiKey', apiKey)\n form.append('fileType', fileType)\n const res = await doFetch(`${base}/task/openapi/upload`, { method: 'POST', body: form })\n const json = (await res.json()) as UploadResponse\n if (json.code !== 0) throw new Error(json.msg || 'RunningHub upload failed')\n return json.data.fileName\n }\n\n async function runApp(\n appId: string,\n nodeInfoList: NodeInfo[],\n options: RunAppOptions = {},\n ): Promise<string> {\n const body: Record<string, unknown> = {\n nodeInfoList,\n usePersonalQueue: options.usePersonalQueue ?? true,\n }\n if (options.instanceType) body.instanceType = options.instanceType\n const res = await doFetch(`${base}/openapi/v2/run/ai-app/${appId}`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${apiKey}` },\n body: JSON.stringify(body),\n })\n const json = (await res.json()) as CreateTaskResponse\n if (json.errorCode) {\n const detail = json.errorMessage ? ` ${json.errorMessage}` : ''\n throw new Error(`RunningHub ${json.errorCode}${detail}`)\n }\n return json.taskId\n }\n\n async function pollTask(taskId: string): Promise<TaskPollResult> {\n const res = await doFetch(`${base}/task/openapi/outputs`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ taskId, apiKey }),\n })\n return parsePollResponse(await res.json())\n }\n\n async function watchTask(\n taskId: string,\n onProgress?: (p: TaskProgress) => void,\n options: WatchOptions = {},\n ): Promise<TaskOutputFile[]> {\n const interval = options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS\n const timeout = options.timeoutMs ?? DEFAULT_TIMEOUT_MS\n const signal = options.signal\n let ws: WebSocket | null = null\n const latest: { percent?: number; step?: number; total?: number } = {}\n const abortIfNeeded = async (): Promise<void> => {\n if (!signal?.aborted) return\n await cancelTask(taskId).catch(() => {})\n throw new Error('aborted')\n }\n try {\n const deadline = Date.now() + timeout\n while (Date.now() < deadline) {\n await abortIfNeeded()\n await sleep(interval)\n await abortIfNeeded()\n const result = await pollTask(taskId)\n switch (result.status) {\n case 'QUEUED':\n onProgress?.({ tag: 'queued' })\n break\n case 'RUNNING':\n if (!ws && result.wsUrl) ws = attachProgressWs(result.wsUrl, latest)\n onProgress?.({ tag: 'running', ...latest })\n break\n case 'SUCCESS':\n return result.files\n case 'FAILED':\n // Transient right after creation, before status is queryable — retry.\n if (result.msg === 'APIKEY_TASK_STATUS_ERROR') {\n onProgress?.({ tag: 'queued' })\n break\n }\n throw new Error(result.msg)\n }\n }\n throw new Error(`RunningHub task timed out (${Math.round(timeout / 1000)}s)`)\n } finally {\n closeWs(ws)\n }\n }\n\n // Cancel endpoint accepts { taskId, apiKey } (one task) or { apiKey } (all of\n // this key's tasks). Best-effort: we don't surface the body (often empty).\n async function cancelTask(taskId: string): Promise<void> {\n await doFetch(`${base}/task/openapi/cancel`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ taskId, apiKey }),\n })\n }\n\n async function cancelByApiKey(): Promise<void> {\n await doFetch(`${base}/task/openapi/cancel`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ apiKey }),\n })\n }\n\n return { uploadFile, runApp, pollTask, watchTask, cancelTask, cancelByApiKey }\n}\n","// RunningHub greeting-video AI App config (Wan2.1 I2V 720p with audio sync).\n// One image + one speech audio + a fixed action prompt → MP4 with baked-in\n// audio in a single task. Values pinned here to avoid drift (mirror mate's\n// types/greetingTalkingVideo.ts).\n\nexport const GREETING_APP_ID = '2048355544552968194'\n\nexport const GREETING_NODE_IDS = {\n // Variable per request\n IMAGE: '133',\n AUDIO: '125',\n // Secondary audio slot — always filled with 1s of silence (see silence.ts).\n // Omitting it triggers a RunningHub 803 \"invalid node info\" error.\n AUDIO_SECONDARY: '209',\n POSITIVE_PROMPT: '216',\n // Fixed knobs\n CROP_POSITION: '171',\n SAVE_OUTPUT: '131',\n NEGATIVE_PROMPT: '135',\n LORA: '138',\n MODEL: '122',\n STEPS: '201',\n} as const\n\nexport const GREETING_MODEL = 'Wan2_1-I2V-14B-720p_fp8_e4m3fn_scaled_KJ.safetensors'\nexport const GREETING_LORA = 'Wan21_I2V_14B_lightx2v_cfg_step_distill_lora_rank64.safetensors'\nexport const GREETING_LORA_STRENGTH = 0.8\nexport const GREETING_STEPS = 25\nexport const GREETING_CROP_POSITION = 'center'\n\nexport const GREETING_NEGATIVE_PROMPT =\n 'bright tones, overexposed, static, blurred details, subtitles, style, works, paintings, images, static, overall gray, worst quality, low quality, JPEG compression residue, ugly, incomplete, extra fingers, poorly drawn hands, poorly drawn faces, deformed, disfigured, misshapen limbs, fused fingers, still picture, messy background, three legs, many people in the background, walking backwards'\n\n// Default action prompt (node 216) — \"she looks at the camera, holds the pose,\n// natural blink/breathing, stays still\". Used when the caller passes none.\nexport const GREETING_POSITIVE_PROMPT_DEFAULT =\n '她看着镜头说话,保持姿势,保持表情,自然眨眼和呼吸,保持不动'\n\n/** The 720p I2V app runs on the shared pool — the personal queue returns 803. */\nexport const GREETING_RUN_OPTIONS = { usePersonalQueue: false, instanceType: 'default' } as const\n\n/** TTS model used to synthesize the greeting speech when the caller gives none. */\nexport const GREETING_DEFAULT_TTS_MODEL = 'speech-02-turbo'\n","// Pure: build the node-override list for the greeting AI App from the uploaded\n// file names + prompt. Extracted for fixed-data testing (mirrors mate's\n// buildGreetingTalkingNodeInfoList).\n\nimport type { NodeInfo } from '../client'\nimport {\n GREETING_NODE_IDS as N,\n GREETING_MODEL,\n GREETING_LORA,\n GREETING_LORA_STRENGTH,\n GREETING_STEPS,\n GREETING_CROP_POSITION,\n GREETING_NEGATIVE_PROMPT,\n} from './constants'\n\nexport function buildGreetingNodeInfoList(args: {\n imageFileName: string\n audioFileName: string\n silenceAudioFileName: string\n positivePrompt: string\n}): NodeInfo[] {\n return [\n // Variable per request\n { nodeId: N.IMAGE, fieldName: 'image', fieldValue: args.imageFileName },\n { nodeId: N.AUDIO, fieldName: 'audio', fieldValue: args.audioFileName },\n { nodeId: N.AUDIO_SECONDARY, fieldName: 'audio', fieldValue: args.silenceAudioFileName },\n { nodeId: N.POSITIVE_PROMPT, fieldName: 'text', fieldValue: args.positivePrompt },\n // Fixed knobs\n { nodeId: N.CROP_POSITION, fieldName: 'crop_position', fieldValue: GREETING_CROP_POSITION },\n { nodeId: N.SAVE_OUTPUT, fieldName: 'save_output', fieldValue: 'true' },\n { nodeId: N.NEGATIVE_PROMPT, fieldName: 'negative_prompt', fieldValue: GREETING_NEGATIVE_PROMPT },\n { nodeId: N.LORA, fieldName: 'lora', fieldValue: GREETING_LORA },\n { nodeId: N.LORA, fieldName: 'strength', fieldValue: String(GREETING_LORA_STRENGTH) },\n { nodeId: N.MODEL, fieldName: 'model', fieldValue: GREETING_MODEL },\n { nodeId: N.STEPS, fieldName: 'value', fieldValue: String(GREETING_STEPS) },\n ]\n}\n","// The workflow's secondary audio slot (node 209) is schema-required; we always\n// fill it with 1 second of silence. mate ships a 4 KB silence_1s.mp3 asset — to\n// keep this package asset-free (batteries-included), the same file is embedded\n// here as base64 and decoded to a File at runtime.\n\nconst SILENCE_1S_MP3_BASE64 =\n 'SUQzBAAAAAAAIlRTU0UAAAAOAAADTGF2ZjYxLjcuMTAwAAAAAAAAAAAAAAD/+0DAAAAAAAAAAAAAAAAAAAAAAABJbmZvAAAADwAAACgAABD2ABAQFhYdHR0jIykpKS8vNTU1OztBQUFISE5OTlRUWlpaYGBmZmZsbHJycnl5f39/hYWLi4uRkZeXl52dpKSkqqqwsLC2try8vMLCyMjIzs7V1dXb2+Hh4efn7e3t8/P5+fn//wAAAABMYXZjNjEuMTkAAAAAAAAAAAAAAAAkBXwAAAAAAAAQ9in4b6YAAAAAAP/7EMQAA8AAAaQAAAAgAAA0gAAABExBTUUzLjEwMFVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVMQU1FMy4xMDBVVVVV//sQxCmDwAABpAAAACAAADSAAAAEVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUxBTUUzLjEwMFVVVVX/+xDEUwPAAAGkAAAAIAAANIAAAARVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVTEFNRTMuMTAwVVVVVf/7EMR8g8AAAaQAAAAgAAA0gAAABFVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVMQU1FMy4xMDBVVVVV//sQxKYDwAABpAAAACAAADSAAAAEVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUxBTUUzLjEwMFVVVVX/+xDEz4PAAAGkAAAAIAAANIAAAARVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVTEFNRTMuMTAwVVVVVf/7EMTWA8AAAaQAAAAgAAA0gAAABFVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVMQU1FMy4xMDBVVVVV//sQxNYDwAABpAAAACAAADSAAAAEVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUxBTUUzLjEwMFVVVVX/+xDE1gPAAAGkAAAAIAAANIAAAARVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVTEFNRTMuMTAwVVVVVf/7EMTWA8AAAaQAAAAgAAA0gAAABFVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVMQU1FMy4xMDBVVVVV//sQxNYDwAABpAAAACAAADSAAAAEVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUxBTUUzLjEwMFVVVVX/+xDE1gPAAAGkAAAAIAAANIAAAARVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVTEFNRTMuMTAwVVVVVf/7EMTWA8AAAaQAAAAgAAA0gAAABFVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVMQU1FMy4xMDBVVVVV//sQxNYDwAABpAAAACAAADSAAAAEVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUxBTUUzLjEwMFVVVVX/+xDE1gPAAAGkAAAAIAAANIAAAARVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVTEFNRTMuMTAwVVVVVf/7EMTWA8AAAaQAAAAgAAA0gAAABFVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVMQU1FMy4xMDBVVVVV//sQxNYDwAABpAAAACAAADSAAAAEVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUxBTUUzLjEwMFVVVVX/+xDE1gPAAAGkAAAAIAAANIAAAARVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVTEFNRTMuMTAwVVVVVf/7EMTWA8AAAaQAAAAgAAA0gAAABFVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVMQU1FMy4xMDBVVVVV//sQxNYDwAABpAAAACAAADSAAAAEVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUxBTUUzLjEwMFVVVVX/+xDE1gPAAAGkAAAAIAAANIAAAARVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVTEFNRTMuMTAwVVVVVf/7EMTWA8AAAaQAAAAgAAA0gAAABFVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVMQU1FMy4xMDBVVVVV//sQxNYDwAABpAAAACAAADSAAAAEVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUxBTUUzLjEwMFVVVVX/+xDE1gPAAAGkAAAAIAAANIAAAARVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVTEFNRTMuMTAwVVVVVf/7EMTWA8AAAaQAAAAgAAA0gAAABFVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVMQU1FMy4xMDBVVVVV//sQxNYDwAABpAAAACAAADSAAAAEVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUxBTUUzLjEwMFVVVVX/+xDE1gPAAAGkAAAAIAAANIAAAARVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVTEFNRTMuMTAwVVVVVf/7EMTWA8AAAaQAAAAgAAA0gAAABFVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVMQU1FMy4xMDBVVVVV//sQxNYDwAABpAAAACAAADSAAAAEVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUxBTUUzLjEwMFVVVVX/+xDE1gPAAAGkAAAAIAAANIAAAARVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVTEFNRTMuMTAwVVVVVf/7EMTWA8AAAaQAAAAgAAA0gAAABFVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVMQU1FMy4xMDBVVVVV//sQxNYDwAABpAAAACAAADSAAAAEVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVX/+xDE1gPAAAGkAAAAIAAANIAAAARVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVf/7EMTWA8AAAaQAAAAgAAA0gAAABFVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV//sQxNYDwAABpAAAACAAADSAAAAEVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVX/+xDE1gPAAAGkAAAAIAAANIAAAARVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVf/7EMTWA8AAAaQAAAAgAAA0gAAABFVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV'\n\nlet cached: File | null = null\n\n/** A 1-second silent MP3 as a File (cached for the page lifetime). */\nexport function silenceAudioFile(): File {\n if (cached) return cached\n const bin = atob(SILENCE_1S_MP3_BASE64)\n const bytes = new Uint8Array(bin.length)\n for (let i = 0; i < bin.length; i++) bytes[i] = bin.charCodeAt(i)\n cached = new File([bytes], 'silence_1s.mp3', { type: 'audio/mpeg' })\n return cached\n}\n","// Shared media helpers used by the greeting + waiting bindings.\n\n// ── Avatar → raster image File ──\n// DiceBear (the default avatar provider) returns SVG; Wan2.1 I2V needs a raster\n// image, so SVG is rasterized to PNG via an offscreen canvas.\n\nasync function rasterizeSvg(svgBlob: Blob, size = 512): Promise<Blob> {\n const svgUrl = URL.createObjectURL(svgBlob)\n try {\n const img = new Image()\n img.crossOrigin = 'anonymous'\n img.src = svgUrl\n await new Promise<void>((resolve, reject) => {\n img.onload = () => resolve()\n img.onerror = () => reject(new Error('Failed to load SVG into <img>'))\n })\n const canvas = document.createElement('canvas')\n canvas.width = size\n canvas.height = size\n const ctx = canvas.getContext('2d')\n if (!ctx) throw new Error('Canvas 2D context unavailable')\n ctx.fillStyle = '#ffffff'\n ctx.fillRect(0, 0, size, size)\n ctx.drawImage(img, 0, 0, size, size)\n return await new Promise<Blob>((resolve, reject) => {\n canvas.toBlob(\n (blob) => (blob ? resolve(blob) : reject(new Error('Canvas toBlob returned null'))),\n 'image/png',\n )\n })\n } finally {\n URL.revokeObjectURL(svgUrl)\n }\n}\n\nexport async function fetchAvatarAsImageFile(url: string, filename: string): Promise<File> {\n const res = await fetch(url)\n if (!res.ok) throw new Error(`Failed to fetch avatar: ${res.status}`)\n const blob = await res.blob()\n if (blob.type === 'image/svg+xml' || url.toLowerCase().endsWith('.svg')) {\n const png = await rasterizeSvg(blob)\n return new File([png], `${filename}.png`, { type: 'image/png' })\n }\n if (!blob.type.startsWith('image/')) {\n throw new Error(`Avatar URL did not return an image (got ${blob.type || 'unknown'})`)\n }\n return new File([blob], filename, { type: blob.type })\n}\n\n// ── Silent WAV ──\n// The static-waiting workflow has no length parameter — the result duration is\n// driven by a silent audio track. Build N seconds of 16 kHz mono 16-bit PCM\n// silence as a WAV Blob (mirrors mate's buildSilenceWavBlob).\n\nconst SAMPLE_RATE = 16000\nconst CHANNELS = 1\nconst BITS = 16\n\nexport function buildSilenceWavBlob(seconds: number): Blob {\n const sampleCount = Math.round(seconds * SAMPLE_RATE)\n const blockAlign = (CHANNELS * BITS) / 8\n const byteRate = SAMPLE_RATE * blockAlign\n const dataSize = sampleCount * blockAlign\n const buffer = new ArrayBuffer(44 + dataSize)\n const view = new DataView(buffer)\n const writeStr = (offset: number, s: string) => {\n for (let i = 0; i < s.length; i++) view.setUint8(offset + i, s.charCodeAt(i))\n }\n writeStr(0, 'RIFF')\n view.setUint32(4, 36 + dataSize, true)\n writeStr(8, 'WAVE')\n writeStr(12, 'fmt ')\n view.setUint32(16, 16, true) // PCM fmt chunk size\n view.setUint16(20, 1, true) // PCM\n view.setUint16(22, CHANNELS, true)\n view.setUint32(24, SAMPLE_RATE, true)\n view.setUint32(28, byteRate, true)\n view.setUint16(32, blockAlign, true)\n view.setUint16(34, BITS, true)\n writeStr(36, 'data')\n view.setUint32(40, dataSize, true)\n // PCM samples left zero-filled = silence.\n return new Blob([buffer], { type: 'audio/wav' })\n}\n","// Shared RunningHub image-to-video pipeline. The greeting and waiting bindings\n// differ ONLY in which files they prepare and how they map the uploaded names to\n// nodes; everything else — upload-all → runApp → watchTask → pick the video URL,\n// plus the progress wording — lives here once.\n\nimport type { NodeInfo, RunAppOptions, RunningHubClient } from '../client'\n\nexport type VideoUpload = {\n /** Key the buildNodes callback uses to reference this file's uploaded name. */\n readonly key: string\n readonly file: File\n readonly fileType: 'image' | 'audio'\n}\n\nexport type RunVideoAppArgs = {\n readonly client: RunningHubClient\n readonly appId: string\n readonly runOptions?: RunAppOptions | undefined\n /** Files to upload before submitting; their RunningHub names feed buildNodes. */\n readonly uploads: VideoUpload[]\n /** Build the node overrides from the uploaded file names (keyed by upload.key). */\n readonly buildNodes: (fileNames: Record<string, string>) => NodeInfo[]\n /** Surface a human progress message (上传素材中… / 提交任务中… / 排队中… / 生成中… N%). */\n readonly onMessage?: ((message: string) => void) | undefined\n}\n\nexport async function runVideoApp(args: RunVideoAppArgs): Promise<string> {\n args.onMessage?.('上传素材中…')\n const entries = await Promise.all(\n args.uploads.map(async (u) => [u.key, await args.client.uploadFile(u.file, u.fileType)] as const),\n )\n const fileNames: Record<string, string> = Object.fromEntries(entries)\n\n args.onMessage?.('提交任务中…')\n const taskId = await args.client.runApp(args.appId, args.buildNodes(fileNames), args.runOptions)\n\n const files = await args.client.watchTask(taskId, (p) => {\n if (p.tag === 'queued') args.onMessage?.('排队中…')\n else args.onMessage?.(p.percent != null ? `生成中… ${p.percent}%` : '生成中…')\n })\n\n const url = files.find((f) => /\\.(mp4|webm|mov)$/i.test(f.fileUrl))?.fileUrl ?? files[0]?.fileUrl\n if (!url) throw new Error('RunningHub task succeeded but returned no video URL')\n return url\n}\n","// RunningHub implementation of greeting's GreetingVideoPort.\n// Prepares the two greeting-specific inputs — TTS audio (via the injected\n// VoiceSynthesisPort) and the avatar image — then hands the upload→run→watch\n// pipeline to the shared runVideoApp. The schema-required secondary audio slot\n// (node 209) is filled with the embedded 1s silence.\n//\n// Returns the RunningHub CDN URL (~24h expiry) — persist via greeting's\n// GreetingVideoStoragePort / a storage mirror for anything long-lived.\n\nimport type {\n GreetingVideoPort,\n GreetingVideoInput,\n GreetingVideoProgress,\n} from '@surfmate.team/digital-human-greeting'\nimport type { VoiceSynthesisPort } from '@surfmate.team/digital-human-voice'\nimport type { RunningHubClient } from '../client'\nimport { runVideoApp } from '../shared/runVideoApp'\nimport { fetchAvatarAsImageFile } from '../shared/media'\nimport {\n GREETING_APP_ID,\n GREETING_RUN_OPTIONS,\n GREETING_POSITIVE_PROMPT_DEFAULT,\n GREETING_DEFAULT_TTS_MODEL,\n} from './constants'\nimport { buildGreetingNodeInfoList } from './buildNodeInfoList'\nimport { silenceAudioFile } from './silence'\n\nexport type RunningHubGreetingVideoConfig = {\n /** Generic RunningHub client (createRunningHubClient). */\n readonly client: RunningHubClient\n /** TTS backend that turns the greeting text into audio bytes (e.g. MiniMax). */\n readonly synth: VoiceSynthesisPort\n /** TTS model id. Defaults to MiniMax speech-02-turbo. */\n readonly modelId?: string\n /** Action prompt for node 216. Defaults to the \"looks at camera, holds pose\" prompt. */\n readonly positivePrompt?: string\n}\n\n// Decode the speech audio's real length (seconds). Browser-guarded; resolves to\n// undefined where Web Audio is unavailable (headless) or decoding fails.\nasync function decodeDurationSec(bytes: ArrayBuffer): Promise<number | undefined> {\n const g = globalThis as unknown as {\n AudioContext?: typeof AudioContext\n webkitAudioContext?: typeof AudioContext\n }\n const Ctx = g.AudioContext ?? g.webkitAudioContext\n if (!Ctx) return undefined\n const ctx = new Ctx()\n try {\n return (await ctx.decodeAudioData(bytes.slice(0))).duration\n } catch {\n return undefined\n } finally {\n void ctx.close()\n }\n}\n\nexport function createRunningHubGreetingVideo(\n config: RunningHubGreetingVideoConfig,\n): GreetingVideoPort {\n const { client, synth } = config\n const modelId = config.modelId ?? GREETING_DEFAULT_TTS_MODEL\n const defaultPrompt =\n (config.positivePrompt && config.positivePrompt.trim()) || GREETING_POSITIVE_PROMPT_DEFAULT\n\n return {\n async generate(\n input: GreetingVideoInput,\n onProgress?: (p: GreetingVideoProgress) => void,\n ): Promise<string> {\n const msg = (message: string) => onProgress?.({ message })\n const positivePrompt = input.positivePrompt?.trim() || defaultPrompt\n\n // Audio: a pre-recorded URL if given, else synthesize from text + voice.\n msg('合成语音中…')\n const audioBytes = input.audioUrl\n ? await (await fetch(input.audioUrl)).arrayBuffer()\n : await synth.synthesize({\n text: input.text,\n voiceId: input.voiceId,\n modelId,\n ...(input.emotion ? { emotion: input.emotion } : {}),\n })\n const audioFile = new File([audioBytes], 'greeting-speech.mp3', { type: 'audio/mpeg' })\n\n // Surface the REAL decoded spoken length (callers persist it for length-matching).\n if (input.onAudioDuration) {\n const sec = await decodeDurationSec(audioBytes)\n if (sec != null) input.onAudioDuration(sec)\n }\n\n msg('获取头像中…')\n const imageFile = await fetchAvatarAsImageFile(input.avatarUrl, 'greeting-avatar')\n\n return runVideoApp({\n client,\n appId: GREETING_APP_ID,\n runOptions: GREETING_RUN_OPTIONS,\n uploads: [\n { key: 'image', file: imageFile, fileType: 'image' },\n { key: 'audio', file: audioFile, fileType: 'audio' },\n { key: 'silence', file: silenceAudioFile(), fileType: 'audio' },\n ],\n buildNodes: (n) =>\n buildGreetingNodeInfoList({\n imageFileName: n.image ?? '',\n audioFileName: n.audio ?? '',\n silenceAudioFileName: n.silence ?? '',\n positivePrompt,\n }),\n onMessage: msg,\n })\n },\n }\n}\n","// RunningHub implementation of waiting's WaitingVideoGenerationPort (\"Generate\n// Static\"). Prepares the two waiting-specific inputs — the avatar image and N\n// seconds of silence (which drive the clip length, no TTS) — then hands the\n// upload→run→watch pipeline to the shared runVideoApp.\n//\n// Returns the RunningHub CDN URL (~24h expiry) — persist via the waiting storage\n// port / a mirror for anything long-lived.\n\nimport type {\n WaitingVideoGenerationPort,\n WaitingVideoGenerationInput,\n WaitingVideoProgress,\n} from '@surfmate.team/digital-human-waiting'\nimport type { NodeInfo, RunningHubClient } from '../client'\nimport { runVideoApp } from '../shared/runVideoApp'\nimport { fetchAvatarAsImageFile, buildSilenceWavBlob } from '../shared/media'\n\n// mate's static-waiting AI App + node ids.\nconst STATIC_WAITING_APP_ID = '2050552043860963330'\nconst NODE_IDS = { IMAGE: '34', PROMPT: '17', AUDIO: '38' } as const\nconst RUN_OPTIONS = { usePersonalQueue: false, instanceType: 'default' } as const\n\nexport type RunningHubStaticWaitingVideoConfig = {\n readonly client: RunningHubClient\n}\n\nexport function createRunningHubStaticWaitingVideo(\n config: RunningHubStaticWaitingVideoConfig,\n): WaitingVideoGenerationPort {\n const { client } = config\n return {\n async generateStatic(\n input: WaitingVideoGenerationInput,\n onProgress?: (p: WaitingVideoProgress) => void,\n ): Promise<string> {\n const msg = (message: string) => onProgress?.({ message })\n\n // Waiting-specific inputs: the avatar image + N seconds of silence.\n msg('获取头像中…')\n const imageFile = await fetchAvatarAsImageFile(input.avatarUrl, `${input.characterId}-avatar`)\n const silence = buildSilenceWavBlob(input.seconds)\n const audioFile = new File([silence], `silence-${input.seconds}s.wav`, { type: 'audio/wav' })\n\n return runVideoApp({\n client,\n appId: STATIC_WAITING_APP_ID,\n runOptions: RUN_OPTIONS,\n uploads: [\n { key: 'image', file: imageFile, fileType: 'image' },\n { key: 'audio', file: audioFile, fileType: 'audio' },\n ],\n buildNodes: (n): NodeInfo[] => [\n { nodeId: NODE_IDS.IMAGE, fieldName: 'image', fieldValue: n.image ?? '' },\n { nodeId: NODE_IDS.PROMPT, fieldName: 'prompt', fieldValue: input.prompt },\n { nodeId: NODE_IDS.AUDIO, fieldName: 'audio', fieldValue: n.audio ?? '' },\n ],\n onMessage: msg,\n })\n },\n }\n}\n","// RunningHub 多人对话 (multi-person lip-sync dialogue) AI App config.\n// InfiniteTalk dual-image driving: one reference image holding two people +\n// three audio tracks → one MP4 where each person lip-syncs to their own voice.\n//\n// The two per-person tracks drive the MOUTHS (each audio only animates its\n// person's face, assigned by the workflow's auto/painted masks); the full\n// conversation track is the OUTPUT soundtrack and sets the total length.\n// (See docs/workflows.md — node ids match the published app, App 2062039217370394626.)\n\nexport const DIALOGUE_APP_ID = '2062039217370394626'\n\nexport const DIALOGUE_NODE_IDS = {\n /** Reference image (the two people). */\n IMAGE: '57',\n /** audio_1 — isolated track that drives the LEFT person's mouth. */\n AUDIO_LEFT: '64',\n /** audio_2 — isolated track that drives the RIGHT person's mouth. */\n AUDIO_RIGHT: '59',\n /** Full conversation — the output soundtrack + drives total video length. */\n AUDIO_FULL: '58',\n /** Action / camera prompt (e.g. 固定镜头). Empty by default. */\n PROMPT: '86',\n} as const\n\n/** Like the other I2V apps, this runs on the shared pool — personal queue → 803. */\nexport const DIALOGUE_RUN_OPTIONS = { usePersonalQueue: false, instanceType: 'default' } as const\n","// RunningHub 多人对话 binding: a two-person lip-sync dialogue video. Self-\n// contained (no feature-package port yet) — it owns its input/port types and\n// composes the generic client via the shared runVideoApp pipeline.\n//\n// The caller prepares three time-aligned audio tracks (e.g. via Web Audio):\n// - leftAudio : only the LEFT person's voice (silence elsewhere) → audio_1\n// - rightAudio : only the RIGHT person's voice (silence elsewhere) → audio_2\n// - fullAudio : the complete conversation (both, mixed) → soundtrack + length\n// All three MUST be the same length and aligned on one timeline.\n//\n// Returns the RunningHub CDN URL (~24h expiry) — persist/mirror anything you\n// need long-lived.\n\nimport type { NodeInfo, RunningHubClient } from '../client'\nimport { runVideoApp } from '../shared/runVideoApp'\nimport { DIALOGUE_APP_ID, DIALOGUE_NODE_IDS, DIALOGUE_RUN_OPTIONS } from './constants'\n\nexport type DialogueVideoInput = {\n /** Reference image containing the two people. */\n readonly image: File\n /** Isolated audio for the LEFT person (drives audio_1). */\n readonly leftAudio: File\n /** Isolated audio for the RIGHT person (drives audio_2). */\n readonly rightAudio: File\n /** The full conversation track — output soundtrack + total length. */\n readonly fullAudio: File\n /** Optional action / camera prompt (e.g. 固定镜头). Omit → empty. */\n readonly prompt?: string | undefined\n}\n\nexport type DialogueVideoProgress = { readonly message: string }\n\nexport interface DialogueVideoPort {\n /** Generate the dialogue video; resolves to the output video URL (~24h CDN). */\n generate(\n input: DialogueVideoInput,\n onProgress?: (p: DialogueVideoProgress) => void,\n ): Promise<string>\n}\n\nexport type RunningHubDialogueVideoConfig = {\n readonly client: RunningHubClient\n}\n\nexport function createRunningHubDialogueVideo(\n config: RunningHubDialogueVideoConfig,\n): DialogueVideoPort {\n const { client } = config\n return {\n async generate(\n input: DialogueVideoInput,\n onProgress?: (p: DialogueVideoProgress) => void,\n ): Promise<string> {\n const msg = (message: string) => onProgress?.({ message })\n return runVideoApp({\n client,\n appId: DIALOGUE_APP_ID,\n runOptions: DIALOGUE_RUN_OPTIONS,\n uploads: [\n { key: 'image', file: input.image, fileType: 'image' },\n { key: 'left', file: input.leftAudio, fileType: 'audio' },\n { key: 'right', file: input.rightAudio, fileType: 'audio' },\n { key: 'full', file: input.fullAudio, fileType: 'audio' },\n ],\n buildNodes: (n): NodeInfo[] => [\n { nodeId: DIALOGUE_NODE_IDS.IMAGE, fieldName: 'image', fieldValue: n.image ?? '' },\n { nodeId: DIALOGUE_NODE_IDS.AUDIO_LEFT, fieldName: 'audio', fieldValue: n.left ?? '' },\n { nodeId: DIALOGUE_NODE_IDS.AUDIO_RIGHT, fieldName: 'audio', fieldValue: n.right ?? '' },\n { nodeId: DIALOGUE_NODE_IDS.AUDIO_FULL, fieldName: 'audio', fieldValue: n.full ?? '' },\n { nodeId: DIALOGUE_NODE_IDS.PROMPT, fieldName: 'text', fieldValue: input.prompt ?? '' },\n ],\n onMessage: msg,\n })\n },\n }\n}\n","// Shared text-to-image runner. Every RunningHub text-to-image app has the same\n// shape — prompt + width + height + batch → image URL(s), no uploads — and\n// differs ONLY in its app id, the four node ids, and the default dimensions.\n// Each model file (qwen2512, flux2klein, …) supplies that spec; this builds the\n// TextToImagePort once.\n\nimport type { NodeInfo, RunAppOptions, RunningHubClient } from '../client'\nimport type { TextToImageInput, TextToImagePort, TextToImageProgress } from './types'\n\nconst IMAGE_RE = /\\.(png|jpe?g|webp)$/i\n\n/** Per-model spec: the app id, its four node ids, and default dimensions. */\nexport type Text2ImageSpec = {\n readonly appId: string\n readonly nodeIds: {\n readonly prompt: string\n readonly width: string\n readonly height: string\n readonly batch: string\n }\n readonly defaults: { readonly width: number; readonly height: number; readonly batchSize: number }\n /** Defaults to the shared-pool options (personal queue → 803 on most apps). */\n readonly runOptions?: RunAppOptions\n}\n\nconst SHARED_POOL: RunAppOptions = { usePersonalQueue: false, instanceType: 'default' }\n\nexport function createText2ImagePort(client: RunningHubClient, spec: Text2ImageSpec): TextToImagePort {\n return {\n async generate(\n input: TextToImageInput,\n onProgress?: (p: TextToImageProgress) => void,\n ): Promise<string[]> {\n const msg = (message: string) => onProgress?.({ message })\n const width = input.width ?? spec.defaults.width\n const height = input.height ?? spec.defaults.height\n const batchSize = input.batchSize ?? spec.defaults.batchSize\n\n const nodes: NodeInfo[] = [\n { nodeId: spec.nodeIds.prompt, fieldName: 'text', fieldValue: input.prompt },\n { nodeId: spec.nodeIds.width, fieldName: 'value', fieldValue: String(width) },\n { nodeId: spec.nodeIds.height, fieldName: 'value', fieldValue: String(height) },\n { nodeId: spec.nodeIds.batch, fieldName: 'value', fieldValue: String(batchSize) },\n ]\n\n msg('提交任务中…')\n const taskId = await client.runApp(spec.appId, nodes, spec.runOptions ?? SHARED_POOL)\n\n const files = await client.watchTask(taskId, (p) => {\n if (p.tag === 'queued') msg('排队中…')\n else msg(p.percent != null ? `生成中… ${p.percent}%` : '生成中…')\n })\n\n const images = files.filter((f) => IMAGE_RE.test(f.fileUrl)).map((f) => f.fileUrl)\n const urls = images.length > 0 ? images : files.map((f) => f.fileUrl)\n if (urls.length === 0) throw new Error('RunningHub task succeeded but returned no image URL')\n return urls\n },\n }\n}\n","// RunningHub 文生图 — Qwen-Image 2512 binding. Just the per-model spec; the\n// shared runner builds the TextToImagePort. (Published app 2052988750950617090.)\n\nimport type { RunningHubClient } from '../client'\nimport type { TextToImagePort } from './types'\nimport { createText2ImagePort, type Text2ImageSpec } from './run'\n\nexport const QWEN2512_SPEC: Text2ImageSpec = {\n appId: '2052988750950617090',\n // node 6 prompt · 80 width · 81 height · 117 batch size\n nodeIds: { prompt: '6', width: '80', height: '81', batch: '117' },\n defaults: { width: 1920, height: 1080, batchSize: 1 },\n}\n\nexport type RunningHubQwen2512Config = {\n readonly client: RunningHubClient\n}\n\nexport function createRunningHubQwen2512(config: RunningHubQwen2512Config): TextToImagePort {\n return createText2ImagePort(config.client, QWEN2512_SPEC)\n}\n","// RunningHub 文生图 — Flux2 Klein binding. Same shape as Qwen2512, different\n// app + node ids. (Published app 2026466502756536322.)\n\nimport type { RunningHubClient } from '../client'\nimport type { TextToImagePort } from './types'\nimport { createText2ImagePort, type Text2ImageSpec } from './run'\n\nexport const FLUX2_KLEIN_SPEC: Text2ImageSpec = {\n appId: '2026466502756536322',\n // node 64 prompt · 99 width · 101 height · 103 batch size\n nodeIds: { prompt: '64', width: '99', height: '101', batch: '103' },\n defaults: { width: 1920, height: 1088, batchSize: 1 },\n}\n\nexport type RunningHubFlux2KleinConfig = {\n readonly client: RunningHubClient\n}\n\nexport function createRunningHubFlux2Klein(config: RunningHubFlux2KleinConfig): TextToImagePort {\n return createText2ImagePort(config.client, FLUX2_KLEIN_SPEC)\n}\n","// Shared image-edit runner. Every RunningHub image-edit app has the same shape:\n// upload 1–N source images → fill the prompt node + the image nodes in order →\n// run → watch → image URL(s). Each model file supplies the spec; this builds\n// the ImageEditPort once.\n\nimport type { NodeInfo, RunAppOptions, RunningHubClient } from '../client'\nimport type { ImageEditInput, ImageEditPort, ImageEditProgress } from './types'\n\nconst IMAGE_RE = /\\.(png|jpe?g|webp)$/i\nconst SHARED_POOL: RunAppOptions = { usePersonalQueue: false, instanceType: 'default' }\n\n/** Per-model spec: the app id, the prompt node, and the image nodes in fill order. */\nexport type ImageEditSpec = {\n readonly appId: string\n readonly promptNodeId: string\n /** Image node ids in fill order (primary first). The Nth image → imageNodeIds[N-1]. */\n readonly imageNodeIds: readonly string[]\n /** Optional INT node for output resolution (longest side); set if the app has one. */\n readonly resolutionNodeId?: string\n readonly runOptions?: RunAppOptions\n}\n\nconst toFile = (blob: Blob, i: number): File =>\n blob instanceof File ? blob : new File([blob], `image-${i}.png`, { type: blob.type || 'image/png' })\n\nexport function createImageEditPort(client: RunningHubClient, spec: ImageEditSpec): ImageEditPort {\n return {\n async generate(\n input: ImageEditInput,\n onProgress?: (p: ImageEditProgress) => void,\n ): Promise<string[]> {\n const msg = (message: string) => onProgress?.({ message })\n // Primary is required by the type; extras are optional and capped to the\n // model's remaining image slots.\n const images = [input.image, ...(input.extraImages ?? [])].slice(0, spec.imageNodeIds.length)\n\n msg('上传素材中…')\n const names = await Promise.all(images.map((img, i) => client.uploadFile(toFile(img, i), 'image')))\n\n const nodes: NodeInfo[] = [\n { nodeId: spec.promptNodeId, fieldName: 'text', fieldValue: input.prompt },\n ]\n for (let i = 0; i < names.length; i++) {\n const nodeId = spec.imageNodeIds[i]\n const fileValue = names[i]\n if (nodeId && fileValue) nodes.push({ nodeId, fieldName: 'image', fieldValue: fileValue })\n }\n if (spec.resolutionNodeId && input.resolution && input.resolution > 0) {\n nodes.push({ nodeId: spec.resolutionNodeId, fieldName: 'value', fieldValue: String(input.resolution) })\n }\n\n msg('提交任务中…')\n const taskId = await client.runApp(spec.appId, nodes, spec.runOptions ?? SHARED_POOL)\n\n const files = await client.watchTask(taskId, (p) => {\n if (p.tag === 'queued') msg('排队中…')\n else msg(p.percent != null ? `生成中… ${p.percent}%` : '生成中…')\n })\n\n const out = files.filter((f) => IMAGE_RE.test(f.fileUrl)).map((f) => f.fileUrl)\n const urls = out.length > 0 ? out : files.map((f) => f.fileUrl)\n if (urls.length === 0) throw new Error('RunningHub task succeeded but returned no image URL')\n return urls\n },\n }\n}\n","// RunningHub 图像编辑 — Qwen-Image 2511 binding. Up to 3 source images + a\n// prompt. (Published app 2025738520135995394.)\n\nimport type { RunningHubClient } from '../client'\nimport type { ImageEditPort } from './types'\nimport { createImageEditPort, type ImageEditSpec } from './run'\n\nexport const QWEN2511_SPEC: ImageEditSpec = {\n appId: '2025738520135995394',\n promptNodeId: '182',\n // primary first: 1 img → 157; 2 → 157,180; 3 → 157,180,181\n imageNodeIds: ['157', '180', '181'],\n}\n\nexport type RunningHubQwen2511Config = {\n readonly client: RunningHubClient\n}\n\nexport function createRunningHubQwen2511(config: RunningHubQwen2511Config): ImageEditPort {\n return createImageEditPort(config.client, QWEN2511_SPEC)\n}\n","// RunningHub 图像编辑 — Flux image-edit binding. Two images (original + a\n// reference) + a Chinese instruction → an edited image. (Published app\n// 2052941353117597697.)\n\nimport type { RunningHubClient } from '../client'\nimport type { ImageEditPort } from './types'\nimport { createImageEditPort, type ImageEditSpec } from './run'\n\nexport const FLUX_EDIT_SPEC: ImageEditSpec = {\n appId: '2052941353117597697',\n promptNodeId: '178',\n // primary (original) → 175; the reference image → 159\n imageNodeIds: ['175', '159'],\n}\n\nexport type RunningHubFluxEditConfig = {\n readonly client: RunningHubClient\n}\n\nexport function createRunningHubFluxEdit(config: RunningHubFluxEditConfig): ImageEditPort {\n return createImageEditPort(config.client, FLUX_EDIT_SPEC)\n}\n","// RunningHub 图像编辑 — \"Qwen Edit 2511\" app (Single/Double Image Editing).\n// We drive it as a SINGLE-image editor: one source image + a prompt + an output\n// resolution. The app's double-image nodes (111 prompt / 70 size / 61 image2)\n// are left untouched. (Published app 2062743091349647362.)\n\nimport type { RunningHubClient } from '../client'\nimport type { ImageEditPort } from './types'\nimport { createImageEditPort, type ImageEditSpec } from './run'\n\nexport const QWEN_EDIT_2511_SPEC: ImageEditSpec = {\n appId: '2062743091349647362',\n promptNodeId: '110', // Text\n imageNodeIds: ['14'], // LoadImage (single image)\n resolutionNodeId: '112', // INTConstant (longest side, e.g. 2048)\n}\n\nexport type RunningHubQwenEdit2511Config = {\n readonly client: RunningHubClient\n}\n\nexport function createRunningHubQwenEdit2511(config: RunningHubQwenEdit2511Config): ImageEditPort {\n return createImageEditPort(config.client, QWEN_EDIT_2511_SPEC)\n}\n","// RunningHub 图生视频 (single-image talking / conversation video). One reference\n// image + an overall prompt + a TIMELINE prompt ([start-end] beats: action + who\n// says what, in order) + an output resolution → one MP4 in which the people in\n// the image act and speak the conversation across the timeline.\n// (Published app 2062483522136395777.)\n\nexport const TALKING_VIDEO_APP_ID = '2062483522136395777'\n\nexport const TALKING_VIDEO_NODE_IDS = {\n /** Reference image (the people in frame). */\n IMAGE: '584',\n /** Overall / global prompt — the scene in one line. */\n PROMPT: '620',\n /** Timeline prompt — \"[start-end] action + who says what\", in order. */\n TIMELINE: '621',\n /** Output resolution (longest side), e.g. 1280. */\n RESOLUTION: '625',\n /**\n * Total frame count — a JWInteger whose output is WIRED into BOTH the video\n * length (EmptyLTXVLatentVideo #577) and the audio frames (LTXVEmptyLatentAudio\n * #547). Set THIS to change the clip length; #577/#547 ignore their own widgets\n * because they're link-driven from here.\n */\n FRAMES: '618',\n} as const\n\n/** Like the other I2V apps, this runs on the shared pool. */\nexport const TALKING_VIDEO_RUN_OPTIONS = { usePersonalQueue: false, instanceType: 'default' } as const\n\nexport const TALKING_VIDEO_DEFAULT_RESOLUTION = 1280\n\n/** Output frame rate baked into the workflow (LTXVConditioning / VHS = 25 fps). */\nexport const TALKING_VIDEO_FPS = 25\n\n/** Workflow default frame count (node 618 = 502 ≈ ~20 s @ 25 fps). */\nexport const TALKING_VIDEO_DEFAULT_FRAMES = 502\n\n/** Frame count for a target duration at the workflow's fps. */\nexport const framesForSeconds = (seconds: number): number =>\n Math.max(1, Math.round(seconds * TALKING_VIDEO_FPS))\n","// RunningHub 图生视频 binding: a single-image talking/conversation video. Self-\n// contained (no feature-package port yet) — it owns its input/port types and\n// composes the generic client via the shared runVideoApp pipeline.\n//\n// The caller supplies ONE reference image (the people in frame), an overall\n// prompt (the scene in a line), and a TIMELINE prompt describing each beat in\n// order, e.g.:\n// [0-5] Lily lies in bed rubbing her eyes; Mom leans over.\n// Mom: \"Good morning, sweetheart.\" Lily: \"Mmm… already?\"\n// [5-10] Lily sits up; Mom strokes her hair.\n// Mom: \"Did you sleep well?\" Lily: \"Yes… I dreamed about bunnies.\"\n// The app drives the action + speech across that timeline from the one image.\n//\n// Returns the RunningHub CDN URL (~24h expiry) — persist/mirror anything you\n// need long-lived.\n\nimport type { NodeInfo, RunningHubClient } from '../client'\nimport { runVideoApp } from '../shared/runVideoApp'\nimport {\n TALKING_VIDEO_APP_ID,\n TALKING_VIDEO_NODE_IDS,\n TALKING_VIDEO_RUN_OPTIONS,\n TALKING_VIDEO_DEFAULT_RESOLUTION,\n} from './constants'\n\nexport type TalkingVideoInput = {\n /** The single reference image (the people in frame). */\n readonly image: File\n /** Overall prompt — the scene/setting in one line. */\n readonly prompt: string\n /** Timeline prompt — \"[start-end] action + who says what\", in order. */\n readonly timeline: string\n /** Output resolution (longest side); defaults to 1280. */\n readonly resolution?: number | undefined\n /**\n * Total VIDEO length in frames (LTXV needs 8·n + 1; use framesForSeconds()).\n * Omit to keep the workflow default (489 ≈ ~20 s @ 25 fps). Drives both the\n * video and the audio latent length so the clip matches your timeline.\n */\n readonly frames?: number | undefined\n}\n\nexport type TalkingVideoProgress = { readonly message: string }\n\nexport interface TalkingVideoPort {\n /** Generate the talking video; resolves to the output video URL (~24h CDN). */\n generate(\n input: TalkingVideoInput,\n onProgress?: (p: TalkingVideoProgress) => void,\n ): Promise<string>\n}\n\nexport type RunningHubTalkingVideoConfig = {\n readonly client: RunningHubClient\n}\n\nexport function createRunningHubTalkingVideo(\n config: RunningHubTalkingVideoConfig,\n): TalkingVideoPort {\n const { client } = config\n return {\n async generate(\n input: TalkingVideoInput,\n onProgress?: (p: TalkingVideoProgress) => void,\n ): Promise<string> {\n const msg = (message: string) => onProgress?.({ message })\n return runVideoApp({\n client,\n appId: TALKING_VIDEO_APP_ID,\n runOptions: TALKING_VIDEO_RUN_OPTIONS,\n uploads: [{ key: 'image', file: input.image, fileType: 'image' }],\n buildNodes: (n): NodeInfo[] => {\n const nodes: NodeInfo[] = [\n { nodeId: TALKING_VIDEO_NODE_IDS.IMAGE, fieldName: 'image', fieldValue: n.image ?? '' },\n { nodeId: TALKING_VIDEO_NODE_IDS.PROMPT, fieldName: 'prompt', fieldValue: input.prompt },\n { nodeId: TALKING_VIDEO_NODE_IDS.TIMELINE, fieldName: 'prompt', fieldValue: input.timeline },\n {\n nodeId: TALKING_VIDEO_NODE_IDS.RESOLUTION,\n fieldName: 'value',\n fieldValue: String(input.resolution ?? TALKING_VIDEO_DEFAULT_RESOLUTION),\n },\n ]\n // Set the frame-count source (node 618) so the clip matches the caller's\n // duration; it's wired into both the video + audio latent lengths.\n if (input.frames && input.frames > 0) {\n nodes.push({ nodeId: TALKING_VIDEO_NODE_IDS.FRAMES, fieldName: 'value', fieldValue: String(input.frames) })\n }\n return nodes\n },\n onMessage: msg,\n })\n },\n }\n}\n"],"mappings":"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","names":["inner","taskStatus","IMAGE_RE","SHARED_POOL"]}
|
|
1
|
+
{"version":3,"sources":["../src/client/parsePollResponse.ts","../src/client/createRunningHubClient.ts","../src/greeting/constants.ts","../src/greeting/buildNodeInfoList.ts","../src/greeting/silence.ts","../src/shared/media.ts","../src/shared/runVideoApp.ts","../src/greeting/createRunningHubGreetingVideo.ts","../src/waiting/createRunningHubStaticWaitingVideo.ts","../src/dialogue/constants.ts","../src/dialogue/createRunningHubDialogueVideo.ts","../src/text2image/run.ts","../src/text2image/qwen2512.ts","../src/text2image/flux2klein.ts","../src/imageedit/run.ts","../src/imageedit/qwen2511.ts","../src/imageedit/fluxedit.ts","../src/imageedit/qwenEdit2511.ts","../src/talkingvideo/constants.ts","../src/talkingvideo/createRunningHubTalkingVideo.ts"],"sourcesContent":["// Pure: normalize RunningHub's several poll-response shapes into TaskPollResult.\n// Isolated from the HTTP client so it can be tested with fixed data.\n// (Faithful port of mate's runningHubService.parsePollResponse.)\n\nimport type { TaskOutputFile, TaskPollResult } from './types'\n\nexport function parsePollResponse(data: unknown): TaskPollResult {\n const obj = data as Record<string, unknown>\n\n // Format A: { code, data, msg }\n if (obj.code !== undefined) {\n if (obj.code === 804 || obj.msg === 'APIKEY_TASK_IS_RUNNING') {\n const inner = obj.data as Record<string, unknown> | undefined\n const wsUrl = typeof inner?.netWssUrl === 'string' ? (inner.netWssUrl as string) : undefined\n return wsUrl ? { status: 'RUNNING', wsUrl } : { status: 'RUNNING' }\n }\n if (obj.code !== 0) {\n return { status: 'FAILED', msg: String(obj.msg ?? 'Unknown error') }\n }\n const payload = obj.data\n if (Array.isArray(payload)) {\n return { status: 'SUCCESS', files: payload as TaskOutputFile[] }\n }\n const inner = payload as Record<string, unknown> | undefined\n const taskStatus = inner?.taskStatus as string | undefined\n if (taskStatus === 'RUNNING') return { status: 'RUNNING' }\n if (taskStatus === 'QUEUED') return { status: 'QUEUED' }\n if (taskStatus === 'SUCCESS' || taskStatus === 'SUCCEDD') {\n const fileUrl = inner?.fileUrl as string | undefined\n if (fileUrl) return { status: 'SUCCESS', files: [{ fileUrl, fileType: 'video' }] }\n return { status: 'SUCCESS', files: [] }\n }\n if (taskStatus === 'FAILED') {\n return { status: 'FAILED', msg: String(inner?.errorInfo ?? 'Task failed') }\n }\n return { status: 'FAILED', msg: `Unexpected taskStatus: ${taskStatus}` }\n }\n\n // Format B: flat { taskStatus, fileUrl, outputs, ... }\n const taskStatus = obj.taskStatus as string | undefined\n if (taskStatus === 'RUNNING') return { status: 'RUNNING' }\n if (taskStatus === 'QUEUED') return { status: 'QUEUED' }\n if (taskStatus === 'SUCCESS' || taskStatus === 'SUCCEDD') {\n const fileUrl = obj.fileUrl as string | undefined\n const outputs = obj.outputs as TaskOutputFile[] | undefined\n if (Array.isArray(outputs) && outputs.length > 0) {\n return { status: 'SUCCESS', files: outputs }\n }\n if (fileUrl) return { status: 'SUCCESS', files: [{ fileUrl, fileType: 'video' }] }\n return { status: 'SUCCESS', files: [] }\n }\n if (taskStatus === 'FAILED') {\n return { status: 'FAILED', msg: String(obj.errorMessage ?? obj.errorInfo ?? 'Task failed') }\n }\n return { status: 'FAILED', msg: `Unexpected response: ${JSON.stringify(obj).slice(0, 200)}` }\n}\n","// RunningHub OpenAPI client — action layer (HTTP + optional WebSocket).\n// Generic and workflow-agnostic: upload files, run an AI App, watch a task.\n// (Faithful port of mate's runningHubService, minus localStorage task\n// persistence — that's an app concern, kept out of the library.)\n\nimport type {\n NodeInfo,\n RunAppOptions,\n RunningHubClient,\n RunningHubConfig,\n TaskOutputFile,\n TaskPollResult,\n TaskProgress,\n WatchOptions,\n} from './types'\nimport { parsePollResponse } from './parsePollResponse'\n\nconst DEFAULT_BASE = '/api/runninghub'\nconst DEFAULT_POLL_INTERVAL_MS = 3000\n// Long ceiling to bound runaway polling; normal end is SUCCESS/FAILED.\nconst DEFAULT_TIMEOUT_MS = 15 * 60 * 1000\n\nconst sleep = (ms: number): Promise<void> => new Promise((r) => setTimeout(r, ms))\n\ntype UploadResponse = { code: number; msg: string; data: { fileName: string } }\ntype CreateTaskResponse = {\n taskId: string\n status: string\n errorCode: string\n errorMessage: string\n}\n\n// Attach a ComfyUI-style WebSocket to stream step-level progress. Best-effort:\n// parse 'progress' frames and mutate `latest` in place; the poll loop forwards\n// the latest value each tick. Any failure degrades to poll-only (no percent).\nfunction attachProgressWs(\n url: string,\n latest: { percent?: number; step?: number; total?: number },\n): WebSocket | null {\n try {\n const ws = new WebSocket(url)\n ws.onmessage = (event) => {\n if (typeof event.data !== 'string') return // binary previews — ignore\n try {\n const msg = JSON.parse(event.data) as { type?: string; data?: Record<string, unknown> }\n if (msg.type !== 'progress' || !msg.data) return\n const value = msg.data.value\n const max = msg.data.max\n if (typeof value === 'number' && typeof max === 'number' && max > 0) {\n latest.step = value\n latest.total = max\n latest.percent = Math.round((value / max) * 100)\n }\n } catch {\n /* malformed frame — keep listening */\n }\n }\n ws.onerror = () => {\n /* best-effort; polling carries the flow */\n }\n return ws\n } catch {\n return null\n }\n}\n\nfunction closeWs(ws: WebSocket | null): void {\n if (!ws) return\n if (ws.readyState === WebSocket.OPEN || ws.readyState === WebSocket.CONNECTING) ws.close()\n}\n\nexport function createRunningHubClient(config: RunningHubConfig): RunningHubClient {\n const base = config.baseUrl ?? DEFAULT_BASE\n const apiKey = config.apiKey\n const doFetch = config.fetchImpl ?? fetch\n\n async function uploadFile(file: File, fileType: 'image' | 'audio'): Promise<string> {\n const form = new FormData()\n form.append('file', file)\n form.append('apiKey', apiKey)\n form.append('fileType', fileType)\n const res = await doFetch(`${base}/task/openapi/upload`, { method: 'POST', body: form })\n const json = (await res.json()) as UploadResponse\n if (json.code !== 0) throw new Error(json.msg || 'RunningHub upload failed')\n return json.data.fileName\n }\n\n async function runApp(\n appId: string,\n nodeInfoList: NodeInfo[],\n options: RunAppOptions = {},\n ): Promise<string> {\n const body: Record<string, unknown> = {\n nodeInfoList,\n usePersonalQueue: options.usePersonalQueue ?? true,\n }\n if (options.instanceType) body.instanceType = options.instanceType\n const res = await doFetch(`${base}/openapi/v2/run/ai-app/${appId}`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${apiKey}` },\n body: JSON.stringify(body),\n })\n const json = (await res.json()) as CreateTaskResponse\n if (json.errorCode) {\n const detail = json.errorMessage ? ` ${json.errorMessage}` : ''\n throw new Error(`RunningHub ${json.errorCode}${detail}`)\n }\n return json.taskId\n }\n\n async function pollTask(taskId: string): Promise<TaskPollResult> {\n const res = await doFetch(`${base}/task/openapi/outputs`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ taskId, apiKey }),\n })\n return parsePollResponse(await res.json())\n }\n\n async function watchTask(\n taskId: string,\n onProgress?: (p: TaskProgress) => void,\n options: WatchOptions = {},\n ): Promise<TaskOutputFile[]> {\n const interval = options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS\n const timeout = options.timeoutMs ?? DEFAULT_TIMEOUT_MS\n const signal = options.signal\n let ws: WebSocket | null = null\n const latest: { percent?: number; step?: number; total?: number } = {}\n const abortIfNeeded = async (): Promise<void> => {\n if (!signal?.aborted) return\n await cancelTask(taskId).catch(() => {})\n throw new Error('aborted')\n }\n try {\n const deadline = Date.now() + timeout\n while (Date.now() < deadline) {\n await abortIfNeeded()\n await sleep(interval)\n await abortIfNeeded()\n const result = await pollTask(taskId)\n switch (result.status) {\n case 'QUEUED':\n onProgress?.({ tag: 'queued' })\n break\n case 'RUNNING':\n if (!ws && result.wsUrl) ws = attachProgressWs(result.wsUrl, latest)\n onProgress?.({ tag: 'running', ...latest })\n break\n case 'SUCCESS':\n return result.files\n case 'FAILED':\n // Transient right after creation, before status is queryable — retry.\n if (result.msg === 'APIKEY_TASK_STATUS_ERROR') {\n onProgress?.({ tag: 'queued' })\n break\n }\n throw new Error(result.msg)\n }\n }\n throw new Error(`RunningHub task timed out (${Math.round(timeout / 1000)}s)`)\n } finally {\n closeWs(ws)\n }\n }\n\n // Cancel endpoint accepts { taskId, apiKey } (one task) or { apiKey } (all of\n // this key's tasks). Best-effort: we don't surface the body (often empty).\n async function cancelTask(taskId: string): Promise<void> {\n await doFetch(`${base}/task/openapi/cancel`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ taskId, apiKey }),\n })\n }\n\n async function cancelByApiKey(): Promise<void> {\n await doFetch(`${base}/task/openapi/cancel`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ apiKey }),\n })\n }\n\n return { uploadFile, runApp, pollTask, watchTask, cancelTask, cancelByApiKey }\n}\n","// RunningHub greeting-video AI App config (Wan2.1 I2V 720p with audio sync).\n// One image + one speech audio + a fixed action prompt → MP4 with baked-in\n// audio in a single task. Values pinned here to avoid drift (mirror mate's\n// types/greetingTalkingVideo.ts).\n\nexport const GREETING_APP_ID = '2048355544552968194'\n\nexport const GREETING_NODE_IDS = {\n // Variable per request\n IMAGE: '133',\n AUDIO: '125',\n // Secondary audio slot — always filled with 1s of silence (see silence.ts).\n // Omitting it triggers a RunningHub 803 \"invalid node info\" error.\n AUDIO_SECONDARY: '209',\n POSITIVE_PROMPT: '216',\n // Fixed knobs\n CROP_POSITION: '171',\n SAVE_OUTPUT: '131',\n NEGATIVE_PROMPT: '135',\n LORA: '138',\n MODEL: '122',\n STEPS: '201',\n} as const\n\nexport const GREETING_MODEL = 'Wan2_1-I2V-14B-720p_fp8_e4m3fn_scaled_KJ.safetensors'\nexport const GREETING_LORA = 'Wan21_I2V_14B_lightx2v_cfg_step_distill_lora_rank64.safetensors'\nexport const GREETING_LORA_STRENGTH = 0.8\nexport const GREETING_STEPS = 25\nexport const GREETING_CROP_POSITION = 'center'\n\nexport const GREETING_NEGATIVE_PROMPT =\n 'bright tones, overexposed, static, blurred details, subtitles, style, works, paintings, images, static, overall gray, worst quality, low quality, JPEG compression residue, ugly, incomplete, extra fingers, poorly drawn hands, poorly drawn faces, deformed, disfigured, misshapen limbs, fused fingers, still picture, messy background, three legs, many people in the background, walking backwards'\n\n// Default action prompt (node 216) — \"she looks at the camera, holds the pose,\n// natural blink/breathing, stays still\". Used when the caller passes none.\nexport const GREETING_POSITIVE_PROMPT_DEFAULT =\n '她看着镜头说话,保持姿势,保持表情,自然眨眼和呼吸,保持不动'\n\n/** The 720p I2V app runs on the shared pool — the personal queue returns 803. */\nexport const GREETING_RUN_OPTIONS = { usePersonalQueue: false, instanceType: 'default' } as const\n\n/** TTS model used to synthesize the greeting speech when the caller gives none. */\nexport const GREETING_DEFAULT_TTS_MODEL = 'speech-02-turbo'\n","// Pure: build the node-override list for the greeting AI App from the uploaded\n// file names + prompt. Extracted for fixed-data testing (mirrors mate's\n// buildGreetingTalkingNodeInfoList).\n\nimport type { NodeInfo } from '../client'\nimport {\n GREETING_NODE_IDS as N,\n GREETING_MODEL,\n GREETING_LORA,\n GREETING_LORA_STRENGTH,\n GREETING_STEPS,\n GREETING_CROP_POSITION,\n GREETING_NEGATIVE_PROMPT,\n} from './constants'\n\nexport function buildGreetingNodeInfoList(args: {\n imageFileName: string\n audioFileName: string\n silenceAudioFileName: string\n positivePrompt: string\n}): NodeInfo[] {\n return [\n // Variable per request\n { nodeId: N.IMAGE, fieldName: 'image', fieldValue: args.imageFileName },\n { nodeId: N.AUDIO, fieldName: 'audio', fieldValue: args.audioFileName },\n { nodeId: N.AUDIO_SECONDARY, fieldName: 'audio', fieldValue: args.silenceAudioFileName },\n { nodeId: N.POSITIVE_PROMPT, fieldName: 'text', fieldValue: args.positivePrompt },\n // Fixed knobs\n { nodeId: N.CROP_POSITION, fieldName: 'crop_position', fieldValue: GREETING_CROP_POSITION },\n { nodeId: N.SAVE_OUTPUT, fieldName: 'save_output', fieldValue: 'true' },\n { nodeId: N.NEGATIVE_PROMPT, fieldName: 'negative_prompt', fieldValue: GREETING_NEGATIVE_PROMPT },\n { nodeId: N.LORA, fieldName: 'lora', fieldValue: GREETING_LORA },\n { nodeId: N.LORA, fieldName: 'strength', fieldValue: String(GREETING_LORA_STRENGTH) },\n { nodeId: N.MODEL, fieldName: 'model', fieldValue: GREETING_MODEL },\n { nodeId: N.STEPS, fieldName: 'value', fieldValue: String(GREETING_STEPS) },\n ]\n}\n","// The workflow's secondary audio slot (node 209) is schema-required; we always\n// fill it with 1 second of silence. mate ships a 4 KB silence_1s.mp3 asset — to\n// keep this package asset-free (batteries-included), the same file is embedded\n// here as base64 and decoded to a File at runtime.\n\nconst SILENCE_1S_MP3_BASE64 =\n 'SUQzBAAAAAAAIlRTU0UAAAAOAAADTGF2ZjYxLjcuMTAwAAAAAAAAAAAAAAD/+0DAAAAAAAAAAAAAAAAAAAAAAABJbmZvAAAADwAAACgAABD2ABAQFhYdHR0jIykpKS8vNTU1OztBQUFISE5OTlRUWlpaYGBmZmZsbHJycnl5f39/hYWLi4uRkZeXl52dpKSkqqqwsLC2try8vMLCyMjIzs7V1dXb2+Hh4efn7e3t8/P5+fn//wAAAABMYXZjNjEuMTkAAAAAAAAAAAAAAAAkBXwAAAAAAAAQ9in4b6YAAAAAAP/7EMQAA8AAAaQAAAAgAAA0gAAABExBTUUzLjEwMFVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVMQU1FMy4xMDBVVVVV//sQxCmDwAABpAAAACAAADSAAAAEVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUxBTUUzLjEwMFVVVVX/+xDEUwPAAAGkAAAAIAAANIAAAARVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVTEFNRTMuMTAwVVVVVf/7EMR8g8AAAaQAAAAgAAA0gAAABFVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVMQU1FMy4xMDBVVVVV//sQxKYDwAABpAAAACAAADSAAAAEVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUxBTUUzLjEwMFVVVVX/+xDEz4PAAAGkAAAAIAAANIAAAARVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVTEFNRTMuMTAwVVVVVf/7EMTWA8AAAaQAAAAgAAA0gAAABFVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVMQU1FMy4xMDBVVVVV//sQxNYDwAABpAAAACAAADSAAAAEVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUxBTUUzLjEwMFVVVVX/+xDE1gPAAAGkAAAAIAAANIAAAARVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVTEFNRTMuMTAwVVVVVf/7EMTWA8AAAaQAAAAgAAA0gAAABFVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVMQU1FMy4xMDBVVVVV//sQxNYDwAABpAAAACAAADSAAAAEVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUxBTUUzLjEwMFVVVVX/+xDE1gPAAAGkAAAAIAAANIAAAARVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVTEFNRTMuMTAwVVVVVf/7EMTWA8AAAaQAAAAgAAA0gAAABFVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVMQU1FMy4xMDBVVVVV//sQxNYDwAABpAAAACAAADSAAAAEVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUxBTUUzLjEwMFVVVVX/+xDE1gPAAAGkAAAAIAAANIAAAARVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVTEFNRTMuMTAwVVVVVf/7EMTWA8AAAaQAAAAgAAA0gAAABFVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVMQU1FMy4xMDBVVVVV//sQxNYDwAABpAAAACAAADSAAAAEVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUxBTUUzLjEwMFVVVVX/+xDE1gPAAAGkAAAAIAAANIAAAARVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVTEFNRTMuMTAwVVVVVf/7EMTWA8AAAaQAAAAgAAA0gAAABFVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVMQU1FMy4xMDBVVVVV//sQxNYDwAABpAAAACAAADSAAAAEVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUxBTUUzLjEwMFVVVVX/+xDE1gPAAAGkAAAAIAAANIAAAARVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVTEFNRTMuMTAwVVVVVf/7EMTWA8AAAaQAAAAgAAA0gAAABFVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVMQU1FMy4xMDBVVVVV//sQxNYDwAABpAAAACAAADSAAAAEVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUxBTUUzLjEwMFVVVVX/+xDE1gPAAAGkAAAAIAAANIAAAARVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVTEFNRTMuMTAwVVVVVf/7EMTWA8AAAaQAAAAgAAA0gAAABFVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVMQU1FMy4xMDBVVVVV//sQxNYDwAABpAAAACAAADSAAAAEVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUxBTUUzLjEwMFVVVVX/+xDE1gPAAAGkAAAAIAAANIAAAARVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVTEFNRTMuMTAwVVVVVf/7EMTWA8AAAaQAAAAgAAA0gAAABFVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVMQU1FMy4xMDBVVVVV//sQxNYDwAABpAAAACAAADSAAAAEVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUxBTUUzLjEwMFVVVVX/+xDE1gPAAAGkAAAAIAAANIAAAARVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVTEFNRTMuMTAwVVVVVf/7EMTWA8AAAaQAAAAgAAA0gAAABFVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVMQU1FMy4xMDBVVVVV//sQxNYDwAABpAAAACAAADSAAAAEVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVX/+xDE1gPAAAGkAAAAIAAANIAAAARVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVf/7EMTWA8AAAaQAAAAgAAA0gAAABFVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV//sQxNYDwAABpAAAACAAADSAAAAEVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVX/+xDE1gPAAAGkAAAAIAAANIAAAARVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVf/7EMTWA8AAAaQAAAAgAAA0gAAABFVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV'\n\nlet cached: File | null = null\n\n/** A 1-second silent MP3 as a File (cached for the page lifetime). */\nexport function silenceAudioFile(): File {\n if (cached) return cached\n const bin = atob(SILENCE_1S_MP3_BASE64)\n const bytes = new Uint8Array(bin.length)\n for (let i = 0; i < bin.length; i++) bytes[i] = bin.charCodeAt(i)\n cached = new File([bytes], 'silence_1s.mp3', { type: 'audio/mpeg' })\n return cached\n}\n","// Shared media helpers used by the greeting + waiting bindings.\n\n// ── Avatar → raster image File ──\n// DiceBear (the default avatar provider) returns SVG; Wan2.1 I2V needs a raster\n// image, so SVG is rasterized to PNG via an offscreen canvas.\n\nasync function rasterizeSvg(svgBlob: Blob, size = 512): Promise<Blob> {\n const svgUrl = URL.createObjectURL(svgBlob)\n try {\n const img = new Image()\n img.crossOrigin = 'anonymous'\n img.src = svgUrl\n await new Promise<void>((resolve, reject) => {\n img.onload = () => resolve()\n img.onerror = () => reject(new Error('Failed to load SVG into <img>'))\n })\n const canvas = document.createElement('canvas')\n canvas.width = size\n canvas.height = size\n const ctx = canvas.getContext('2d')\n if (!ctx) throw new Error('Canvas 2D context unavailable')\n ctx.fillStyle = '#ffffff'\n ctx.fillRect(0, 0, size, size)\n ctx.drawImage(img, 0, 0, size, size)\n return await new Promise<Blob>((resolve, reject) => {\n canvas.toBlob(\n (blob) => (blob ? resolve(blob) : reject(new Error('Canvas toBlob returned null'))),\n 'image/png',\n )\n })\n } finally {\n URL.revokeObjectURL(svgUrl)\n }\n}\n\nexport async function fetchAvatarAsImageFile(url: string, filename: string): Promise<File> {\n const res = await fetch(url)\n if (!res.ok) throw new Error(`Failed to fetch avatar: ${res.status}`)\n const blob = await res.blob()\n if (blob.type === 'image/svg+xml' || url.toLowerCase().endsWith('.svg')) {\n const png = await rasterizeSvg(blob)\n return new File([png], `${filename}.png`, { type: 'image/png' })\n }\n if (!blob.type.startsWith('image/')) {\n throw new Error(`Avatar URL did not return an image (got ${blob.type || 'unknown'})`)\n }\n return new File([blob], filename, { type: blob.type })\n}\n\n// ── Silent WAV ──\n// The static-waiting workflow has no length parameter — the result duration is\n// driven by a silent audio track. Build N seconds of 16 kHz mono 16-bit PCM\n// silence as a WAV Blob (mirrors mate's buildSilenceWavBlob).\n\nconst SAMPLE_RATE = 16000\nconst CHANNELS = 1\nconst BITS = 16\n\nexport function buildSilenceWavBlob(seconds: number): Blob {\n const sampleCount = Math.round(seconds * SAMPLE_RATE)\n const blockAlign = (CHANNELS * BITS) / 8\n const byteRate = SAMPLE_RATE * blockAlign\n const dataSize = sampleCount * blockAlign\n const buffer = new ArrayBuffer(44 + dataSize)\n const view = new DataView(buffer)\n const writeStr = (offset: number, s: string) => {\n for (let i = 0; i < s.length; i++) view.setUint8(offset + i, s.charCodeAt(i))\n }\n writeStr(0, 'RIFF')\n view.setUint32(4, 36 + dataSize, true)\n writeStr(8, 'WAVE')\n writeStr(12, 'fmt ')\n view.setUint32(16, 16, true) // PCM fmt chunk size\n view.setUint16(20, 1, true) // PCM\n view.setUint16(22, CHANNELS, true)\n view.setUint32(24, SAMPLE_RATE, true)\n view.setUint32(28, byteRate, true)\n view.setUint16(32, blockAlign, true)\n view.setUint16(34, BITS, true)\n writeStr(36, 'data')\n view.setUint32(40, dataSize, true)\n // PCM samples left zero-filled = silence.\n return new Blob([buffer], { type: 'audio/wav' })\n}\n","// Shared RunningHub image-to-video pipeline. The greeting and waiting bindings\n// differ ONLY in which files they prepare and how they map the uploaded names to\n// nodes; everything else — upload-all → runApp → watchTask → pick the video URL,\n// plus the progress wording — lives here once.\n\nimport type { NodeInfo, RunAppOptions, RunningHubClient } from '../client'\n\nexport type VideoUpload = {\n /** Key the buildNodes callback uses to reference this file's uploaded name. */\n readonly key: string\n readonly file: File\n readonly fileType: 'image' | 'audio'\n}\n\nexport type RunVideoAppArgs = {\n readonly client: RunningHubClient\n readonly appId: string\n readonly runOptions?: RunAppOptions | undefined\n /** Files to upload before submitting; their RunningHub names feed buildNodes. */\n readonly uploads: VideoUpload[]\n /** Build the node overrides from the uploaded file names (keyed by upload.key). */\n readonly buildNodes: (fileNames: Record<string, string>) => NodeInfo[]\n /** Surface a human progress message (上传素材中… / 提交任务中… / 排队中… / 生成中… N%). */\n readonly onMessage?: ((message: string) => void) | undefined\n}\n\nexport async function runVideoApp(args: RunVideoAppArgs): Promise<string> {\n args.onMessage?.('上传素材中…')\n const entries = await Promise.all(\n args.uploads.map(async (u) => [u.key, await args.client.uploadFile(u.file, u.fileType)] as const),\n )\n const fileNames: Record<string, string> = Object.fromEntries(entries)\n\n args.onMessage?.('提交任务中…')\n const taskId = await args.client.runApp(args.appId, args.buildNodes(fileNames), args.runOptions)\n\n const files = await args.client.watchTask(taskId, (p) => {\n if (p.tag === 'queued') args.onMessage?.('排队中…')\n else args.onMessage?.(p.percent != null ? `生成中… ${p.percent}%` : '生成中…')\n })\n\n const url = files.find((f) => /\\.(mp4|webm|mov)$/i.test(f.fileUrl))?.fileUrl ?? files[0]?.fileUrl\n if (!url) throw new Error('RunningHub task succeeded but returned no video URL')\n return url\n}\n","// RunningHub implementation of greeting's GreetingVideoPort.\n// Prepares the two greeting-specific inputs — TTS audio (via the injected\n// VoiceSynthesisPort) and the avatar image — then hands the upload→run→watch\n// pipeline to the shared runVideoApp. The schema-required secondary audio slot\n// (node 209) is filled with the embedded 1s silence.\n//\n// Returns the RunningHub CDN URL (~24h expiry) — persist via greeting's\n// GreetingVideoStoragePort / a storage mirror for anything long-lived.\n\nimport type {\n GreetingVideoPort,\n GreetingVideoInput,\n GreetingVideoProgress,\n} from '@surfmate.team/digital-human-greeting'\nimport type { VoiceSynthesisPort } from '@surfmate.team/digital-human-voice'\nimport type { RunningHubClient } from '../client'\nimport { runVideoApp } from '../shared/runVideoApp'\nimport { fetchAvatarAsImageFile } from '../shared/media'\nimport {\n GREETING_APP_ID,\n GREETING_RUN_OPTIONS,\n GREETING_POSITIVE_PROMPT_DEFAULT,\n GREETING_DEFAULT_TTS_MODEL,\n} from './constants'\nimport { buildGreetingNodeInfoList } from './buildNodeInfoList'\nimport { silenceAudioFile } from './silence'\n\nexport type RunningHubGreetingVideoConfig = {\n /** Generic RunningHub client (createRunningHubClient). */\n readonly client: RunningHubClient\n /** TTS backend that turns the greeting text into audio bytes (e.g. MiniMax). */\n readonly synth: VoiceSynthesisPort\n /** TTS model id. Defaults to MiniMax speech-02-turbo. */\n readonly modelId?: string\n /** Action prompt for node 216. Defaults to the \"looks at camera, holds pose\" prompt. */\n readonly positivePrompt?: string\n}\n\n// Decode the speech audio's real length (seconds). Browser-guarded; resolves to\n// undefined where Web Audio is unavailable (headless) or decoding fails.\nasync function decodeDurationSec(bytes: ArrayBuffer): Promise<number | undefined> {\n const g = globalThis as unknown as {\n AudioContext?: typeof AudioContext\n webkitAudioContext?: typeof AudioContext\n }\n const Ctx = g.AudioContext ?? g.webkitAudioContext\n if (!Ctx) return undefined\n const ctx = new Ctx()\n try {\n return (await ctx.decodeAudioData(bytes.slice(0))).duration\n } catch {\n return undefined\n } finally {\n void ctx.close()\n }\n}\n\nexport function createRunningHubGreetingVideo(\n config: RunningHubGreetingVideoConfig,\n): GreetingVideoPort {\n const { client, synth } = config\n const modelId = config.modelId ?? GREETING_DEFAULT_TTS_MODEL\n const defaultPrompt =\n (config.positivePrompt && config.positivePrompt.trim()) || GREETING_POSITIVE_PROMPT_DEFAULT\n\n return {\n async generate(\n input: GreetingVideoInput,\n onProgress?: (p: GreetingVideoProgress) => void,\n ): Promise<string> {\n const msg = (message: string) => onProgress?.({ message })\n const positivePrompt = input.positivePrompt?.trim() || defaultPrompt\n\n // Audio: a pre-recorded URL if given, else synthesize from text + voice.\n msg('合成语音中…')\n const audioBytes = input.audioUrl\n ? await (await fetch(input.audioUrl)).arrayBuffer()\n : await synth.synthesize({\n text: input.text,\n voiceId: input.voiceId,\n modelId,\n ...(input.emotion ? { emotion: input.emotion } : {}),\n })\n const audioFile = new File([audioBytes], 'greeting-speech.mp3', { type: 'audio/mpeg' })\n\n // Surface the REAL decoded spoken length (callers persist it for length-matching).\n if (input.onAudioDuration) {\n const sec = await decodeDurationSec(audioBytes)\n if (sec != null) input.onAudioDuration(sec)\n }\n\n msg('获取头像中…')\n const imageFile = await fetchAvatarAsImageFile(input.avatarUrl, 'greeting-avatar')\n\n return runVideoApp({\n client,\n appId: GREETING_APP_ID,\n runOptions: GREETING_RUN_OPTIONS,\n uploads: [\n { key: 'image', file: imageFile, fileType: 'image' },\n { key: 'audio', file: audioFile, fileType: 'audio' },\n { key: 'silence', file: silenceAudioFile(), fileType: 'audio' },\n ],\n buildNodes: (n) =>\n buildGreetingNodeInfoList({\n imageFileName: n.image ?? '',\n audioFileName: n.audio ?? '',\n silenceAudioFileName: n.silence ?? '',\n positivePrompt,\n }),\n onMessage: msg,\n })\n },\n }\n}\n","// RunningHub implementation of waiting's WaitingVideoGenerationPort (\"Generate\n// Static\"). Prepares the two waiting-specific inputs — the avatar image and N\n// seconds of silence (which drive the clip length, no TTS) — then hands the\n// upload→run→watch pipeline to the shared runVideoApp.\n//\n// Returns the RunningHub CDN URL (~24h expiry) — persist via the waiting storage\n// port / a mirror for anything long-lived.\n\nimport type {\n WaitingVideoGenerationPort,\n WaitingVideoGenerationInput,\n WaitingVideoProgress,\n} from '@surfmate.team/digital-human-waiting'\nimport type { NodeInfo, RunningHubClient } from '../client'\nimport { runVideoApp } from '../shared/runVideoApp'\nimport { fetchAvatarAsImageFile, buildSilenceWavBlob } from '../shared/media'\n\n// mate's static-waiting AI App + node ids.\nconst STATIC_WAITING_APP_ID = '2050552043860963330'\nconst NODE_IDS = { IMAGE: '34', PROMPT: '17', AUDIO: '38' } as const\nconst RUN_OPTIONS = { usePersonalQueue: false, instanceType: 'default' } as const\n\nexport type RunningHubStaticWaitingVideoConfig = {\n readonly client: RunningHubClient\n}\n\nexport function createRunningHubStaticWaitingVideo(\n config: RunningHubStaticWaitingVideoConfig,\n): WaitingVideoGenerationPort {\n const { client } = config\n return {\n async generateStatic(\n input: WaitingVideoGenerationInput,\n onProgress?: (p: WaitingVideoProgress) => void,\n ): Promise<string> {\n const msg = (message: string) => onProgress?.({ message })\n\n // Waiting-specific inputs: the avatar image + N seconds of silence.\n msg('获取头像中…')\n const imageFile = await fetchAvatarAsImageFile(input.avatarUrl, `${input.characterId}-avatar`)\n const silence = buildSilenceWavBlob(input.seconds)\n const audioFile = new File([silence], `silence-${input.seconds}s.wav`, { type: 'audio/wav' })\n\n return runVideoApp({\n client,\n appId: STATIC_WAITING_APP_ID,\n runOptions: RUN_OPTIONS,\n uploads: [\n { key: 'image', file: imageFile, fileType: 'image' },\n { key: 'audio', file: audioFile, fileType: 'audio' },\n ],\n buildNodes: (n): NodeInfo[] => [\n { nodeId: NODE_IDS.IMAGE, fieldName: 'image', fieldValue: n.image ?? '' },\n { nodeId: NODE_IDS.PROMPT, fieldName: 'prompt', fieldValue: input.prompt },\n { nodeId: NODE_IDS.AUDIO, fieldName: 'audio', fieldValue: n.audio ?? '' },\n ],\n onMessage: msg,\n })\n },\n }\n}\n","// RunningHub 多人对话 (multi-person lip-sync dialogue) AI App config.\n// InfiniteTalk dual-image driving: one reference image holding two people +\n// three audio tracks → one MP4 where each person lip-syncs to their own voice.\n//\n// The two per-person tracks drive the MOUTHS (each audio only animates its\n// person's face, assigned by the workflow's auto/painted masks); the full\n// conversation track is the OUTPUT soundtrack and sets the total length.\n// (See docs/workflows.md — node ids match the published app, App 2062039217370394626.)\n\nexport const DIALOGUE_APP_ID = '2062039217370394626'\n\nexport const DIALOGUE_NODE_IDS = {\n /** Reference image (the two people). */\n IMAGE: '57',\n /** audio_1 — isolated track that drives the LEFT person's mouth. */\n AUDIO_LEFT: '64',\n /** audio_2 — isolated track that drives the RIGHT person's mouth. */\n AUDIO_RIGHT: '59',\n /** Full conversation — the output soundtrack + drives total video length. */\n AUDIO_FULL: '58',\n /** Action / camera prompt (e.g. 固定镜头). Empty by default. */\n PROMPT: '86',\n} as const\n\n/** Like the other I2V apps, this runs on the shared pool — personal queue → 803. */\nexport const DIALOGUE_RUN_OPTIONS = { usePersonalQueue: false, instanceType: 'default' } as const\n","// RunningHub 多人对话 binding: a two-person lip-sync dialogue video. Self-\n// contained (no feature-package port yet) — it owns its input/port types and\n// composes the generic client via the shared runVideoApp pipeline.\n//\n// The caller prepares three time-aligned audio tracks (e.g. via Web Audio):\n// - leftAudio : only the LEFT person's voice (silence elsewhere) → audio_1\n// - rightAudio : only the RIGHT person's voice (silence elsewhere) → audio_2\n// - fullAudio : the complete conversation (both, mixed) → soundtrack + length\n// All three MUST be the same length and aligned on one timeline.\n//\n// Returns the RunningHub CDN URL (~24h expiry) — persist/mirror anything you\n// need long-lived.\n\nimport type { NodeInfo, RunningHubClient } from '../client'\nimport { runVideoApp } from '../shared/runVideoApp'\nimport { DIALOGUE_APP_ID, DIALOGUE_NODE_IDS, DIALOGUE_RUN_OPTIONS } from './constants'\n\nexport type DialogueVideoInput = {\n /** Reference image containing the two people. */\n readonly image: File\n /** Isolated audio for the LEFT person (drives audio_1). */\n readonly leftAudio: File\n /** Isolated audio for the RIGHT person (drives audio_2). */\n readonly rightAudio: File\n /** The full conversation track — output soundtrack + total length. */\n readonly fullAudio: File\n /** Optional action / camera prompt (e.g. 固定镜头). Omit → empty. */\n readonly prompt?: string | undefined\n}\n\nexport type DialogueVideoProgress = { readonly message: string }\n\nexport interface DialogueVideoPort {\n /** Generate the dialogue video; resolves to the output video URL (~24h CDN). */\n generate(\n input: DialogueVideoInput,\n onProgress?: (p: DialogueVideoProgress) => void,\n ): Promise<string>\n}\n\nexport type RunningHubDialogueVideoConfig = {\n readonly client: RunningHubClient\n}\n\nexport function createRunningHubDialogueVideo(\n config: RunningHubDialogueVideoConfig,\n): DialogueVideoPort {\n const { client } = config\n return {\n async generate(\n input: DialogueVideoInput,\n onProgress?: (p: DialogueVideoProgress) => void,\n ): Promise<string> {\n const msg = (message: string) => onProgress?.({ message })\n return runVideoApp({\n client,\n appId: DIALOGUE_APP_ID,\n runOptions: DIALOGUE_RUN_OPTIONS,\n uploads: [\n { key: 'image', file: input.image, fileType: 'image' },\n { key: 'left', file: input.leftAudio, fileType: 'audio' },\n { key: 'right', file: input.rightAudio, fileType: 'audio' },\n { key: 'full', file: input.fullAudio, fileType: 'audio' },\n ],\n buildNodes: (n): NodeInfo[] => [\n { nodeId: DIALOGUE_NODE_IDS.IMAGE, fieldName: 'image', fieldValue: n.image ?? '' },\n { nodeId: DIALOGUE_NODE_IDS.AUDIO_LEFT, fieldName: 'audio', fieldValue: n.left ?? '' },\n { nodeId: DIALOGUE_NODE_IDS.AUDIO_RIGHT, fieldName: 'audio', fieldValue: n.right ?? '' },\n { nodeId: DIALOGUE_NODE_IDS.AUDIO_FULL, fieldName: 'audio', fieldValue: n.full ?? '' },\n { nodeId: DIALOGUE_NODE_IDS.PROMPT, fieldName: 'text', fieldValue: input.prompt ?? '' },\n ],\n onMessage: msg,\n })\n },\n }\n}\n","// Shared text-to-image runner. Every RunningHub text-to-image app has the same\n// shape — prompt + width + height + batch → image URL(s), no uploads — and\n// differs ONLY in its app id, the four node ids, and the default dimensions.\n// Each model file (qwen2512, flux2klein, …) supplies that spec; this builds the\n// TextToImagePort once.\n\nimport type { NodeInfo, RunAppOptions, RunningHubClient } from '../client'\nimport type { TextToImageInput, TextToImagePort, TextToImageProgress } from './types'\n\nconst IMAGE_RE = /\\.(png|jpe?g|webp)$/i\n\n/** Per-model spec: the app id, its four node ids, and default dimensions. */\nexport type Text2ImageSpec = {\n readonly appId: string\n readonly nodeIds: {\n readonly prompt: string\n readonly width: string\n readonly height: string\n readonly batch: string\n }\n readonly defaults: { readonly width: number; readonly height: number; readonly batchSize: number }\n /** Defaults to the shared-pool options (personal queue → 803 on most apps). */\n readonly runOptions?: RunAppOptions\n}\n\nconst SHARED_POOL: RunAppOptions = { usePersonalQueue: false, instanceType: 'default' }\n\nexport function createText2ImagePort(client: RunningHubClient, spec: Text2ImageSpec): TextToImagePort {\n return {\n async generate(\n input: TextToImageInput,\n onProgress?: (p: TextToImageProgress) => void,\n ): Promise<string[]> {\n const msg = (message: string) => onProgress?.({ message })\n const width = input.width ?? spec.defaults.width\n const height = input.height ?? spec.defaults.height\n const batchSize = input.batchSize ?? spec.defaults.batchSize\n\n const nodes: NodeInfo[] = [\n { nodeId: spec.nodeIds.prompt, fieldName: 'text', fieldValue: input.prompt },\n { nodeId: spec.nodeIds.width, fieldName: 'value', fieldValue: String(width) },\n { nodeId: spec.nodeIds.height, fieldName: 'value', fieldValue: String(height) },\n { nodeId: spec.nodeIds.batch, fieldName: 'value', fieldValue: String(batchSize) },\n ]\n\n msg('提交任务中…')\n const taskId = await client.runApp(spec.appId, nodes, spec.runOptions ?? SHARED_POOL)\n\n const files = await client.watchTask(taskId, (p) => {\n if (p.tag === 'queued') msg('排队中…')\n else msg(p.percent != null ? `生成中… ${p.percent}%` : '生成中…')\n })\n\n const images = files.filter((f) => IMAGE_RE.test(f.fileUrl)).map((f) => f.fileUrl)\n const urls = images.length > 0 ? images : files.map((f) => f.fileUrl)\n if (urls.length === 0) throw new Error('RunningHub task succeeded but returned no image URL')\n return urls\n },\n }\n}\n","// RunningHub 文生图 — Qwen-Image 2512 binding. Just the per-model spec; the\n// shared runner builds the TextToImagePort. (Published app 2052988750950617090.)\n\nimport type { RunningHubClient } from '../client'\nimport type { TextToImagePort } from './types'\nimport { createText2ImagePort, type Text2ImageSpec } from './run'\n\nexport const QWEN2512_SPEC: Text2ImageSpec = {\n appId: '2052988750950617090',\n // node 6 prompt · 80 width · 81 height · 117 batch size\n nodeIds: { prompt: '6', width: '80', height: '81', batch: '117' },\n defaults: { width: 1920, height: 1080, batchSize: 1 },\n}\n\nexport type RunningHubQwen2512Config = {\n readonly client: RunningHubClient\n}\n\nexport function createRunningHubQwen2512(config: RunningHubQwen2512Config): TextToImagePort {\n return createText2ImagePort(config.client, QWEN2512_SPEC)\n}\n","// RunningHub 文生图 — Flux2 Klein binding. Same shape as Qwen2512, different\n// app + node ids. (Published app 2026466502756536322.)\n\nimport type { RunningHubClient } from '../client'\nimport type { TextToImagePort } from './types'\nimport { createText2ImagePort, type Text2ImageSpec } from './run'\n\nexport const FLUX2_KLEIN_SPEC: Text2ImageSpec = {\n appId: '2026466502756536322',\n // node 64 prompt · 99 width · 101 height · 103 batch size\n nodeIds: { prompt: '64', width: '99', height: '101', batch: '103' },\n defaults: { width: 1920, height: 1088, batchSize: 1 },\n}\n\nexport type RunningHubFlux2KleinConfig = {\n readonly client: RunningHubClient\n}\n\nexport function createRunningHubFlux2Klein(config: RunningHubFlux2KleinConfig): TextToImagePort {\n return createText2ImagePort(config.client, FLUX2_KLEIN_SPEC)\n}\n","// Shared image-edit runner. Every RunningHub image-edit app has the same shape:\n// upload 1–N source images → fill the prompt node + the image nodes in order →\n// run → watch → image URL(s). Each model file supplies the spec; this builds\n// the ImageEditPort once.\n\nimport type { NodeInfo, RunAppOptions, RunningHubClient } from '../client'\nimport type { ImageEditInput, ImageEditPort, ImageEditProgress } from './types'\n\nconst IMAGE_RE = /\\.(png|jpe?g|webp)$/i\nconst SHARED_POOL: RunAppOptions = { usePersonalQueue: false, instanceType: 'default' }\n\n/** Per-model spec: the app id, the prompt node, and the image nodes in fill order. */\nexport type ImageEditSpec = {\n readonly appId: string\n readonly promptNodeId: string\n /** Image node ids in fill order (primary first). The Nth image → imageNodeIds[N-1]. */\n readonly imageNodeIds: readonly string[]\n /** Optional INT node for output resolution (longest side); set if the app has one. */\n readonly resolutionNodeId?: string\n readonly runOptions?: RunAppOptions\n}\n\nconst toFile = (blob: Blob, i: number): File =>\n blob instanceof File ? blob : new File([blob], `image-${i}.png`, { type: blob.type || 'image/png' })\n\nexport function createImageEditPort(client: RunningHubClient, spec: ImageEditSpec): ImageEditPort {\n return {\n async generate(\n input: ImageEditInput,\n onProgress?: (p: ImageEditProgress) => void,\n ): Promise<string[]> {\n const msg = (message: string) => onProgress?.({ message })\n // Primary is required by the type; extras are optional and capped to the\n // model's remaining image slots.\n const images = [input.image, ...(input.extraImages ?? [])].slice(0, spec.imageNodeIds.length)\n\n msg('上传素材中…')\n const names = await Promise.all(images.map((img, i) => client.uploadFile(toFile(img, i), 'image')))\n\n const nodes: NodeInfo[] = [\n { nodeId: spec.promptNodeId, fieldName: 'text', fieldValue: input.prompt },\n ]\n for (let i = 0; i < names.length; i++) {\n const nodeId = spec.imageNodeIds[i]\n const fileValue = names[i]\n if (nodeId && fileValue) nodes.push({ nodeId, fieldName: 'image', fieldValue: fileValue })\n }\n if (spec.resolutionNodeId && input.resolution && input.resolution > 0) {\n nodes.push({ nodeId: spec.resolutionNodeId, fieldName: 'value', fieldValue: String(input.resolution) })\n }\n\n msg('提交任务中…')\n const taskId = await client.runApp(spec.appId, nodes, spec.runOptions ?? SHARED_POOL)\n\n const files = await client.watchTask(taskId, (p) => {\n if (p.tag === 'queued') msg('排队中…')\n else msg(p.percent != null ? `生成中… ${p.percent}%` : '生成中…')\n })\n\n const out = files.filter((f) => IMAGE_RE.test(f.fileUrl)).map((f) => f.fileUrl)\n const urls = out.length > 0 ? out : files.map((f) => f.fileUrl)\n if (urls.length === 0) throw new Error('RunningHub task succeeded but returned no image URL')\n return urls\n },\n }\n}\n","// RunningHub 图像编辑 — Qwen-Image 2511 binding. Up to 3 source images + a\n// prompt. (Published app 2025738520135995394.)\n\nimport type { RunningHubClient } from '../client'\nimport type { ImageEditPort } from './types'\nimport { createImageEditPort, type ImageEditSpec } from './run'\n\nexport const QWEN2511_SPEC: ImageEditSpec = {\n appId: '2025738520135995394',\n promptNodeId: '182',\n // primary first: 1 img → 157; 2 → 157,180; 3 → 157,180,181\n imageNodeIds: ['157', '180', '181'],\n}\n\nexport type RunningHubQwen2511Config = {\n readonly client: RunningHubClient\n}\n\n/**\n * @deprecated Use {@link createRunningHubQwenEdit2511} (app 2062743091349647362)\n * instead — it adds an output-resolution control and is the current Qwen edit app.\n */\nexport function createRunningHubQwen2511(config: RunningHubQwen2511Config): ImageEditPort {\n return createImageEditPort(config.client, QWEN2511_SPEC)\n}\n","// RunningHub 图像编辑 — Flux image-edit binding. Two images (original + a\n// reference) + a Chinese instruction → an edited image. (Published app\n// 2052941353117597697.)\n\nimport type { RunningHubClient } from '../client'\nimport type { ImageEditPort } from './types'\nimport { createImageEditPort, type ImageEditSpec } from './run'\n\nexport const FLUX_EDIT_SPEC: ImageEditSpec = {\n appId: '2052941353117597697',\n promptNodeId: '178',\n // primary (original) → 175; the reference image → 159\n imageNodeIds: ['175', '159'],\n}\n\nexport type RunningHubFluxEditConfig = {\n readonly client: RunningHubClient\n}\n\nexport function createRunningHubFluxEdit(config: RunningHubFluxEditConfig): ImageEditPort {\n return createImageEditPort(config.client, FLUX_EDIT_SPEC)\n}\n","// RunningHub 图像编辑 — \"Qwen Edit 2511\" app (Single/Double Image Editing).\n// We drive it as a SINGLE-image editor: one source image + a prompt + an output\n// resolution. The app's double-image nodes (111 prompt / 70 size / 61 image2)\n// are left untouched. (Published app 2062743091349647362.)\n\nimport type { RunningHubClient } from '../client'\nimport type { ImageEditPort } from './types'\nimport { createImageEditPort, type ImageEditSpec } from './run'\n\nexport const QWEN_EDIT_2511_SPEC: ImageEditSpec = {\n appId: '2062743091349647362',\n promptNodeId: '110', // Text\n imageNodeIds: ['14'], // LoadImage (single image)\n resolutionNodeId: '112', // INTConstant (longest side, e.g. 2048)\n}\n\nexport type RunningHubQwenEdit2511Config = {\n readonly client: RunningHubClient\n}\n\nexport function createRunningHubQwenEdit2511(config: RunningHubQwenEdit2511Config): ImageEditPort {\n return createImageEditPort(config.client, QWEN_EDIT_2511_SPEC)\n}\n\n// ── Double-image variant ─────────────────────────────────────────────────────\n// \"Qwen Edit 2511\" DOUBLE-image editing: two source images + a prompt that can\n// reference them (e.g. 图1的人物抱着图2的猫咪) + an output resolution.\n// image → 图像1 (node 61), extraImages[0] → 图像2 (node 69).\n// (Published app 2062751684262195202.)\n\nexport const QWEN_EDIT_2511_DUAL_SPEC: ImageEditSpec = {\n appId: '2062751684262195202',\n promptNodeId: '111', // Text\n imageNodeIds: ['61', '69'], // 图像1 (primary), 图像2 (extra)\n resolutionNodeId: '70', // INTConstant\n}\n\nexport type RunningHubQwenEdit2511DualConfig = {\n readonly client: RunningHubClient\n}\n\nexport function createRunningHubQwenEdit2511Dual(config: RunningHubQwenEdit2511DualConfig): ImageEditPort {\n return createImageEditPort(config.client, QWEN_EDIT_2511_DUAL_SPEC)\n}\n","// RunningHub 图生视频 (single-image talking / conversation video). One reference\n// image + an overall prompt + a TIMELINE prompt ([start-end] beats: action + who\n// says what, in order) + an output resolution → one MP4 in which the people in\n// the image act and speak the conversation across the timeline.\n// (Published app 2062483522136395777.)\n\nexport const TALKING_VIDEO_APP_ID = '2062483522136395777'\n\nexport const TALKING_VIDEO_NODE_IDS = {\n /** Reference image (the people in frame). */\n IMAGE: '584',\n /** Overall / global prompt — the scene in one line. */\n PROMPT: '620',\n /** Timeline prompt — \"[start-end] action + who says what\", in order. */\n TIMELINE: '621',\n /** Output resolution (longest side), e.g. 1280. */\n RESOLUTION: '625',\n /**\n * Total frame count — a JWInteger whose output is WIRED into BOTH the video\n * length (EmptyLTXVLatentVideo #577) and the audio frames (LTXVEmptyLatentAudio\n * #547). Set THIS to change the clip length; #577/#547 ignore their own widgets\n * because they're link-driven from here.\n */\n FRAMES: '618',\n} as const\n\n/** Like the other I2V apps, this runs on the shared pool. */\nexport const TALKING_VIDEO_RUN_OPTIONS = { usePersonalQueue: false, instanceType: 'default' } as const\n\nexport const TALKING_VIDEO_DEFAULT_RESOLUTION = 1280\n\n/** Output frame rate baked into the workflow (LTXVConditioning / VHS = 25 fps). */\nexport const TALKING_VIDEO_FPS = 25\n\n/** Workflow default frame count (node 618 = 502 ≈ ~20 s @ 25 fps). */\nexport const TALKING_VIDEO_DEFAULT_FRAMES = 502\n\n/** Frame count for a target duration at the workflow's fps. */\nexport const framesForSeconds = (seconds: number): number =>\n Math.max(1, Math.round(seconds * TALKING_VIDEO_FPS))\n","// RunningHub 图生视频 binding: a single-image talking/conversation video. Self-\n// contained (no feature-package port yet) — it owns its input/port types and\n// composes the generic client via the shared runVideoApp pipeline.\n//\n// The caller supplies ONE reference image (the people in frame), an overall\n// prompt (the scene in a line), and a TIMELINE prompt describing each beat in\n// order, e.g.:\n// [0-5] Lily lies in bed rubbing her eyes; Mom leans over.\n// Mom: \"Good morning, sweetheart.\" Lily: \"Mmm… already?\"\n// [5-10] Lily sits up; Mom strokes her hair.\n// Mom: \"Did you sleep well?\" Lily: \"Yes… I dreamed about bunnies.\"\n// The app drives the action + speech across that timeline from the one image.\n//\n// Returns the RunningHub CDN URL (~24h expiry) — persist/mirror anything you\n// need long-lived.\n\nimport type { NodeInfo, RunningHubClient } from '../client'\nimport { runVideoApp } from '../shared/runVideoApp'\nimport {\n TALKING_VIDEO_APP_ID,\n TALKING_VIDEO_NODE_IDS,\n TALKING_VIDEO_RUN_OPTIONS,\n TALKING_VIDEO_DEFAULT_RESOLUTION,\n} from './constants'\n\nexport type TalkingVideoInput = {\n /** The single reference image (the people in frame). */\n readonly image: File\n /** Overall prompt — the scene/setting in one line. */\n readonly prompt: string\n /** Timeline prompt — \"[start-end] action + who says what\", in order. */\n readonly timeline: string\n /** Output resolution (longest side); defaults to 1280. */\n readonly resolution?: number | undefined\n /**\n * Total VIDEO length in frames (LTXV needs 8·n + 1; use framesForSeconds()).\n * Omit to keep the workflow default (489 ≈ ~20 s @ 25 fps). Drives both the\n * video and the audio latent length so the clip matches your timeline.\n */\n readonly frames?: number | undefined\n}\n\nexport type TalkingVideoProgress = { readonly message: string }\n\nexport interface TalkingVideoPort {\n /** Generate the talking video; resolves to the output video URL (~24h CDN). */\n generate(\n input: TalkingVideoInput,\n onProgress?: (p: TalkingVideoProgress) => void,\n ): Promise<string>\n}\n\nexport type RunningHubTalkingVideoConfig = {\n readonly client: RunningHubClient\n}\n\nexport function createRunningHubTalkingVideo(\n config: RunningHubTalkingVideoConfig,\n): TalkingVideoPort {\n const { client } = config\n return {\n async generate(\n input: TalkingVideoInput,\n onProgress?: (p: TalkingVideoProgress) => void,\n ): Promise<string> {\n const msg = (message: string) => onProgress?.({ message })\n return runVideoApp({\n client,\n appId: TALKING_VIDEO_APP_ID,\n runOptions: TALKING_VIDEO_RUN_OPTIONS,\n uploads: [{ key: 'image', file: input.image, fileType: 'image' }],\n buildNodes: (n): NodeInfo[] => {\n const nodes: NodeInfo[] = [\n { nodeId: TALKING_VIDEO_NODE_IDS.IMAGE, fieldName: 'image', fieldValue: n.image ?? '' },\n { nodeId: TALKING_VIDEO_NODE_IDS.PROMPT, fieldName: 'prompt', fieldValue: input.prompt },\n { nodeId: TALKING_VIDEO_NODE_IDS.TIMELINE, fieldName: 'prompt', fieldValue: input.timeline },\n {\n nodeId: TALKING_VIDEO_NODE_IDS.RESOLUTION,\n fieldName: 'value',\n fieldValue: String(input.resolution ?? TALKING_VIDEO_DEFAULT_RESOLUTION),\n },\n ]\n // Set the frame-count source (node 618) so the clip matches the caller's\n // duration; it's wired into both the video + audio latent lengths.\n if (input.frames && input.frames > 0) {\n nodes.push({ nodeId: TALKING_VIDEO_NODE_IDS.FRAMES, fieldName: 'value', fieldValue: String(input.frames) })\n }\n return nodes\n },\n onMessage: msg,\n })\n },\n }\n}\n"],"mappings":"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","names":["inner","taskStatus","IMAGE_RE","SHARED_POOL"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@surfmate.team/digital-human-runninghub",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.6",
|
|
4
4
|
"description": "RunningHub (ComfyUI-as-a-service) adapter. A generic, workflow-agnostic client (upload / run AI app / watch task) plus a greeting-video binding that implements greeting's GreetingVideoPort by composing the client with an injected TTS VoiceSynthesisPort. The client is reusable for ANY RunningHub workflow (waiting videos, image edit, …).",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|