@surfmate.team/digital-human-runninghub 0.3.8 → 0.3.10

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 CHANGED
@@ -149,6 +149,9 @@ type DialogueVideoInput = {
149
149
  readonly fullAudio: File;
150
150
  /** Optional action / camera prompt (e.g. 固定镜头). Omit → empty. */
151
151
  readonly prompt?: string | undefined;
152
+ /** Conditioning resolution — the LONGEST side. Workflow recommends ≤ 1280 and
153
+ * ≥ 832; omit → 832 (an 832×480 clip). Raise to follow a higher-res image. */
154
+ readonly resolution?: number | undefined;
152
155
  };
153
156
  type DialogueVideoProgress = {
154
157
  readonly message: string;
@@ -162,6 +165,26 @@ type RunningHubDialogueVideoConfig = {
162
165
  };
163
166
  declare function createRunningHubDialogueVideo(config: RunningHubDialogueVideoConfig): DialogueVideoPort;
164
167
 
168
+ type SoloDialogueVideoInput = {
169
+ /** Reference image — needs ONE detectable face (the speaker). */
170
+ readonly image: File;
171
+ /** The speech track the person lip-syncs to (also the output soundtrack). */
172
+ readonly audio: File;
173
+ /** Optional action / camera prompt. Omit → a neutral "speaks naturally" prompt. */
174
+ readonly prompt?: string | undefined;
175
+ };
176
+ type SoloDialogueVideoProgress = {
177
+ readonly message: string;
178
+ };
179
+ interface SoloDialogueVideoPort {
180
+ /** Generate the one-person video; resolves to the output video URL (~24h CDN). */
181
+ generate(input: SoloDialogueVideoInput, onProgress?: (p: SoloDialogueVideoProgress) => void): Promise<string>;
182
+ }
183
+ type RunningHubSoloDialogueVideoConfig = {
184
+ readonly client: RunningHubClient;
185
+ };
186
+ declare function createRunningHubSoloDialogueVideo(config: RunningHubSoloDialogueVideoConfig): SoloDialogueVideoPort;
187
+
165
188
  declare const DIALOGUE_APP_ID = "2062039217370394626";
166
189
  declare const DIALOGUE_NODE_IDS: {
167
190
  /** Reference image (the two people). */
@@ -174,12 +197,18 @@ declare const DIALOGUE_NODE_IDS: {
174
197
  readonly AUDIO_FULL: "58";
175
198
  /** Action / camera prompt (e.g. 固定镜头). Empty by default. */
176
199
  readonly PROMPT: "86";
200
+ /** Conditioning resolution — the LONGEST side (easy int). Workflow note:
201
+ * keep ≤ 1280 and ≥ 832; default 832 → an 832×480 clip. Raise for higher res. */
202
+ readonly RESOLUTION: "56";
177
203
  };
178
204
  /** Like the other I2V apps, this runs on the shared pool — personal queue → 803. */
179
205
  declare const DIALOGUE_RUN_OPTIONS: {
180
206
  readonly usePersonalQueue: false;
181
207
  readonly instanceType: "default";
182
208
  };
209
+ /** Default action prompt for the 单人对话 (solo) binding's node 216 — neutral
210
+ * "speaks naturally" (greeting's default says 看着镜头, wrong for a dialogue shot). */
211
+ declare const SOLO_DIALOGUE_DEFAULT_PROMPT = "\u4EBA\u7269\u81EA\u7136\u5730\u8BF4\u8BDD\uFF0C\u4FDD\u6301\u59FF\u52BF\uFF0C\u81EA\u7136\u7728\u773C\u548C\u547C\u5438\uFF0C\u8F7B\u5FAE\u7684\u5934\u90E8\u52A8\u4F5C";
183
212
 
184
213
  type TextToImageInput = {
185
214
  /** The image prompt. */
@@ -391,4 +420,4 @@ declare const FLF_RUN_OPTIONS: {
391
420
  declare const FLF_DEFAULT_RESOLUTION = 1024;
392
421
  declare const FLF_DEFAULT_FRAMES = 41;
393
422
 
394
- export { DIALOGUE_APP_ID, DIALOGUE_NODE_IDS, DIALOGUE_RUN_OPTIONS, type DialogueVideoInput, type DialogueVideoPort, type DialogueVideoProgress, FLF_APP_ID, FLF_DEFAULT_FRAMES, FLF_DEFAULT_RESOLUTION, FLF_NODE_IDS, FLF_RUN_OPTIONS, FLUX2_KLEIN_SPEC, FLUX_EDIT_SPEC, type FirstLastFrameInput, type FirstLastFramePort, type FirstLastFrameProgress, 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 RunningHubFirstLastFrameConfig, 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, createRunningHubFirstLastFrame, createRunningHubFlux2Klein, createRunningHubFluxEdit, createRunningHubGreetingVideo, createRunningHubQwen2511, createRunningHubQwen2512, createRunningHubQwenEdit2511, createRunningHubQwenEdit2511Dual, createRunningHubStaticWaitingVideo, createRunningHubTalkingVideo, createText2ImagePort, fetchAvatarAsImageFile, framesForSeconds, parsePollResponse, silenceAudioFile };
423
+ export { DIALOGUE_APP_ID, DIALOGUE_NODE_IDS, DIALOGUE_RUN_OPTIONS, type DialogueVideoInput, type DialogueVideoPort, type DialogueVideoProgress, FLF_APP_ID, FLF_DEFAULT_FRAMES, FLF_DEFAULT_RESOLUTION, FLF_NODE_IDS, FLF_RUN_OPTIONS, FLUX2_KLEIN_SPEC, FLUX_EDIT_SPEC, type FirstLastFrameInput, type FirstLastFramePort, type FirstLastFrameProgress, 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 RunningHubFirstLastFrameConfig, type RunningHubFlux2KleinConfig, type RunningHubFluxEditConfig, type RunningHubGreetingVideoConfig, type RunningHubQwen2511Config, type RunningHubQwen2512Config, type RunningHubQwenEdit2511Config, type RunningHubQwenEdit2511DualConfig, type RunningHubSoloDialogueVideoConfig, type RunningHubStaticWaitingVideoConfig, type RunningHubTalkingVideoConfig, SOLO_DIALOGUE_DEFAULT_PROMPT, type SoloDialogueVideoInput, type SoloDialogueVideoPort, type SoloDialogueVideoProgress, 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, createRunningHubFirstLastFrame, createRunningHubFlux2Klein, createRunningHubFluxEdit, createRunningHubGreetingVideo, createRunningHubQwen2511, createRunningHubQwen2512, createRunningHubQwenEdit2511, createRunningHubQwenEdit2511Dual, createRunningHubSoloDialogueVideo, createRunningHubStaticWaitingVideo, createRunningHubTalkingVideo, createText2ImagePort, fetchAvatarAsImageFile, framesForSeconds, parsePollResponse, silenceAudioFile };
package/dist/index.js CHANGED
@@ -433,9 +433,14 @@ var DIALOGUE_NODE_IDS = {
433
433
  /** Full conversation — the output soundtrack + drives total video length. */
434
434
  AUDIO_FULL: "58",
435
435
  /** Action / camera prompt (e.g. 固定镜头). Empty by default. */
436
- PROMPT: "86"
436
+ PROMPT: "86",
437
+ /** Conditioning resolution — the LONGEST side (easy int). Workflow note:
438
+ * keep ≤ 1280 and ≥ 832; default 832 → an 832×480 clip. Raise for higher res. */
439
+ RESOLUTION: "56"
437
440
  };
441
+ var DIALOGUE_DEFAULT_RESOLUTION = 832;
438
442
  var DIALOGUE_RUN_OPTIONS = { usePersonalQueue: false, instanceType: "default" };
443
+ var SOLO_DIALOGUE_DEFAULT_PROMPT = "\u4EBA\u7269\u81EA\u7136\u5730\u8BF4\u8BDD\uFF0C\u4FDD\u6301\u59FF\u52BF\uFF0C\u81EA\u7136\u7728\u773C\u548C\u547C\u5438\uFF0C\u8F7B\u5FAE\u7684\u5934\u90E8\u52A8\u4F5C";
439
444
 
440
445
  // src/dialogue/createRunningHubDialogueVideo.ts
441
446
  function createRunningHubDialogueVideo(config) {
@@ -458,7 +463,8 @@ function createRunningHubDialogueVideo(config) {
458
463
  { nodeId: DIALOGUE_NODE_IDS.AUDIO_LEFT, fieldName: "audio", fieldValue: n.left ?? "" },
459
464
  { nodeId: DIALOGUE_NODE_IDS.AUDIO_RIGHT, fieldName: "audio", fieldValue: n.right ?? "" },
460
465
  { nodeId: DIALOGUE_NODE_IDS.AUDIO_FULL, fieldName: "audio", fieldValue: n.full ?? "" },
461
- { nodeId: DIALOGUE_NODE_IDS.PROMPT, fieldName: "text", fieldValue: input.prompt ?? "" }
466
+ { nodeId: DIALOGUE_NODE_IDS.PROMPT, fieldName: "text", fieldValue: input.prompt ?? "" },
467
+ { nodeId: DIALOGUE_NODE_IDS.RESOLUTION, fieldName: "value", fieldValue: String(input.resolution ?? DIALOGUE_DEFAULT_RESOLUTION) }
462
468
  ],
463
469
  onMessage: msg
464
470
  });
@@ -466,6 +472,34 @@ function createRunningHubDialogueVideo(config) {
466
472
  };
467
473
  }
468
474
 
475
+ // src/dialogue/createRunningHubSoloDialogueVideo.ts
476
+ function createRunningHubSoloDialogueVideo(config) {
477
+ const { client } = config;
478
+ return {
479
+ async generate(input, onProgress) {
480
+ const msg = (message) => onProgress?.({ message });
481
+ return runVideoApp({
482
+ client,
483
+ appId: GREETING_APP_ID,
484
+ runOptions: GREETING_RUN_OPTIONS,
485
+ uploads: [
486
+ { key: "image", file: input.image, fileType: "image" },
487
+ { key: "audio", file: input.audio, fileType: "audio" },
488
+ // Schema-required secondary slot (node 209) — omitting it → 803.
489
+ { key: "silence", file: silenceAudioFile(), fileType: "audio" }
490
+ ],
491
+ buildNodes: (n) => buildGreetingNodeInfoList({
492
+ imageFileName: n.image ?? "",
493
+ audioFileName: n.audio ?? "",
494
+ silenceAudioFileName: n.silence ?? "",
495
+ positivePrompt: input.prompt?.trim() || SOLO_DIALOGUE_DEFAULT_PROMPT
496
+ }),
497
+ onMessage: msg
498
+ });
499
+ }
500
+ };
501
+ }
502
+
469
503
  // src/text2image/run.ts
470
504
  var IMAGE_RE = /\.(png|jpe?g|webp)$/i;
471
505
  var SHARED_POOL = { usePersonalQueue: false, instanceType: "default" };
@@ -727,6 +761,7 @@ export {
727
761
  QWEN2512_SPEC,
728
762
  QWEN_EDIT_2511_DUAL_SPEC,
729
763
  QWEN_EDIT_2511_SPEC,
764
+ SOLO_DIALOGUE_DEFAULT_PROMPT,
730
765
  TALKING_VIDEO_APP_ID,
731
766
  TALKING_VIDEO_DEFAULT_FRAMES,
732
767
  TALKING_VIDEO_DEFAULT_RESOLUTION,
@@ -745,6 +780,7 @@ export {
745
780
  createRunningHubQwen2512,
746
781
  createRunningHubQwenEdit2511,
747
782
  createRunningHubQwenEdit2511Dual,
783
+ createRunningHubSoloDialogueVideo,
748
784
  createRunningHubStaticWaitingVideo,
749
785
  createRunningHubTalkingVideo,
750
786
  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","../src/firstlastframe/constants.ts","../src/firstlastframe/createRunningHubFirstLastFrame.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 // ⚠️ The workflow CROSSES the LoadAudio nodes into MultiTalkWav2VecEmbeds:\n // node 59 → audio_1 → mask[0] = the LEFT face; node 64 → audio_2 → mask[1] =\n // the RIGHT face. So the LEFT person's track must go to node 59, the RIGHT to\n // node 64 (the opposite of what the node titles suggest). Verified by tracing\n // the .json — earlier this was reversed and the wrong mouth moved.\n /** LEFT person's isolated track → node 59 (audio_1 → mask[0] = left face). */\n AUDIO_LEFT: '59',\n /** RIGHT person's isolated track → node 64 (audio_2 → mask[1] = right face). */\n AUDIO_RIGHT: '64',\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","// RunningHub 首尾帧 (first-last-frame → video) AI App config. Two images — a\n// FIRST frame + a LAST frame — plus a resolution and a frame count → one MP4\n// that smoothly transitions from the first image to the last (a \"transition\"\n// clip between two shots). (Published app 2062876888187621378.)\n\nexport const FLF_APP_ID = '2062876888187621378'\n\nexport const FLF_NODE_IDS = {\n /** First frame (start image). */\n FIRST: '322',\n /** Last frame (end image). */\n LAST: '323',\n /** Output resolution (longest side), e.g. 1024. */\n RESOLUTION: '319',\n /** Total frame count, e.g. 41. */\n FRAMES: '333',\n} as const\n\n/** Like the other I2V apps, this runs on the shared pool. */\nexport const FLF_RUN_OPTIONS = { usePersonalQueue: false, instanceType: 'default' } as const\n\nexport const FLF_DEFAULT_RESOLUTION = 1024\nexport const FLF_DEFAULT_FRAMES = 41\n","// RunningHub 首尾帧 binding: a first-last-frame transition video. Give a FIRST\n// frame image and a LAST frame image (e.g. the previous shot's last frame and\n// the next shot's first frame) plus a resolution + frame count; the app renders\n// an MP4 that interpolates from the first image to the last. Self-contained —\n// owns its input/port types and composes the generic client via runVideoApp.\n//\n// Returns the RunningHub CDN URL (~24h expiry) — persist/mirror what you need.\n\nimport type { NodeInfo, RunningHubClient } from '../client'\nimport { runVideoApp } from '../shared/runVideoApp'\nimport {\n FLF_APP_ID,\n FLF_NODE_IDS,\n FLF_RUN_OPTIONS,\n FLF_DEFAULT_RESOLUTION,\n FLF_DEFAULT_FRAMES,\n} from './constants'\n\nexport type FirstLastFrameInput = {\n /** First frame (start image). */\n readonly first: File\n /** Last frame (end image). */\n readonly last: File\n /** Output resolution (longest side); defaults to 1024. */\n readonly resolution?: number | undefined\n /** Total frame count; defaults to 41. */\n readonly frames?: number | undefined\n}\n\nexport type FirstLastFrameProgress = { readonly message: string }\n\nexport interface FirstLastFramePort {\n /** Generate the transition video; resolves to the output video URL (~24h CDN). */\n generate(\n input: FirstLastFrameInput,\n onProgress?: (p: FirstLastFrameProgress) => void,\n ): Promise<string>\n}\n\nexport type RunningHubFirstLastFrameConfig = {\n readonly client: RunningHubClient\n}\n\nexport function createRunningHubFirstLastFrame(\n config: RunningHubFirstLastFrameConfig,\n): FirstLastFramePort {\n const { client } = config\n return {\n async generate(\n input: FirstLastFrameInput,\n onProgress?: (p: FirstLastFrameProgress) => void,\n ): Promise<string> {\n const msg = (message: string) => onProgress?.({ message })\n return runVideoApp({\n client,\n appId: FLF_APP_ID,\n runOptions: FLF_RUN_OPTIONS,\n uploads: [\n { key: 'first', file: input.first, fileType: 'image' },\n { key: 'last', file: input.last, fileType: 'image' },\n ],\n buildNodes: (n): NodeInfo[] => [\n { nodeId: FLF_NODE_IDS.FIRST, fieldName: 'image', fieldValue: n.first ?? '' },\n { nodeId: FLF_NODE_IDS.LAST, fieldName: 'image', fieldValue: n.last ?? '' },\n { nodeId: FLF_NODE_IDS.RESOLUTION, fieldName: 'value', fieldValue: String(input.resolution ?? FLF_DEFAULT_RESOLUTION) },\n { nodeId: FLF_NODE_IDS.FRAMES, fieldName: 'value', fieldValue: String(input.frames ?? FLF_DEFAULT_FRAMES) },\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/dialogue/createRunningHubSoloDialogueVideo.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","../src/firstlastframe/constants.ts","../src/firstlastframe/createRunningHubFirstLastFrame.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 // ⚠️ The workflow CROSSES the LoadAudio nodes into MultiTalkWav2VecEmbeds:\n // node 59 → audio_1 → mask[0] = the LEFT face; node 64 → audio_2 → mask[1] =\n // the RIGHT face. So the LEFT person's track must go to node 59, the RIGHT to\n // node 64 (the opposite of what the node titles suggest). Verified by tracing\n // the .json — earlier this was reversed and the wrong mouth moved.\n /** LEFT person's isolated track → node 59 (audio_1 → mask[0] = left face). */\n AUDIO_LEFT: '59',\n /** RIGHT person's isolated track → node 64 (audio_2 → mask[1] = right face). */\n AUDIO_RIGHT: '64',\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 /** Conditioning resolution — the LONGEST side (easy int). Workflow note:\n * keep ≤ 1280 and ≥ 832; default 832 → an 832×480 clip. Raise for higher res. */\n RESOLUTION: '56',\n} as const\n\n/** Workflow default for RESOLUTION (node 56) — an 832×480 clip. */\nexport const DIALOGUE_DEFAULT_RESOLUTION = 832\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\n/** Default action prompt for the 单人对话 (solo) binding's node 216 — neutral\n * \"speaks naturally\" (greeting's default says 看着镜头, wrong for a dialogue shot). */\nexport const SOLO_DIALOGUE_DEFAULT_PROMPT =\n '人物自然地说话,保持姿势,自然眨眼和呼吸,轻微的头部动作'\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, DIALOGUE_DEFAULT_RESOLUTION } 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 /** Conditioning resolution — the LONGEST side. Workflow recommends ≤ 1280 and\n * ≥ 832; omit → 832 (an 832×480 clip). Raise to follow a higher-res image. */\n readonly resolution?: number | 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 { nodeId: DIALOGUE_NODE_IDS.RESOLUTION, fieldName: 'value', fieldValue: String(input.resolution ?? DIALOGUE_DEFAULT_RESOLUTION) },\n ],\n onMessage: msg,\n })\n },\n }\n}\n","// RunningHub 单人对话 binding: one-person lip-sync — the single-person sibling\n// of createRunningHubDialogueVideo. Same caller shape (image + ready-made audio\n// File(s) → MP4 URL), but runs the single-person InfiniteTalk app the greeting\n// binding wraps (App 2048355544552968194), so it reuses greeting's pinned node\n// constants + node-list builder. Unlike the greeting port there is NO TTS here:\n// the caller hands the finished speech track (e.g. the dialogue page's full\n// conversation track for a one-visible-face shot).\n//\n// Why it exists: the 多人对话 app derives per-person mouth masks from FACE\n// DETECTION on the reference image — a shot where one character is back-to-\n// camera detects only one face and renders black. This app needs just one face.\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 { GREETING_APP_ID, GREETING_RUN_OPTIONS } from '../greeting/constants'\nimport { buildGreetingNodeInfoList } from '../greeting/buildNodeInfoList'\nimport { silenceAudioFile } from '../greeting/silence'\nimport { SOLO_DIALOGUE_DEFAULT_PROMPT } from './constants'\n\nexport type SoloDialogueVideoInput = {\n /** Reference image — needs ONE detectable face (the speaker). */\n readonly image: File\n /** The speech track the person lip-syncs to (also the output soundtrack). */\n readonly audio: File\n /** Optional action / camera prompt. Omit → a neutral \"speaks naturally\" prompt. */\n readonly prompt?: string | undefined\n}\n\nexport type SoloDialogueVideoProgress = { readonly message: string }\n\nexport interface SoloDialogueVideoPort {\n /** Generate the one-person video; resolves to the output video URL (~24h CDN). */\n generate(\n input: SoloDialogueVideoInput,\n onProgress?: (p: SoloDialogueVideoProgress) => void,\n ): Promise<string>\n}\n\nexport type RunningHubSoloDialogueVideoConfig = {\n readonly client: RunningHubClient\n}\n\nexport function createRunningHubSoloDialogueVideo(\n config: RunningHubSoloDialogueVideoConfig,\n): SoloDialogueVideoPort {\n const { client } = config\n return {\n async generate(\n input: SoloDialogueVideoInput,\n onProgress?: (p: SoloDialogueVideoProgress) => void,\n ): Promise<string> {\n const msg = (message: string) => onProgress?.({ message })\n return runVideoApp({\n client,\n appId: GREETING_APP_ID,\n runOptions: GREETING_RUN_OPTIONS,\n uploads: [\n { key: 'image', file: input.image, fileType: 'image' },\n { key: 'audio', file: input.audio, fileType: 'audio' },\n // Schema-required secondary slot (node 209) — omitting it → 803.\n { key: 'silence', file: silenceAudioFile(), fileType: 'audio' },\n ],\n buildNodes: (n): NodeInfo[] =>\n buildGreetingNodeInfoList({\n imageFileName: n.image ?? '',\n audioFileName: n.audio ?? '',\n silenceAudioFileName: n.silence ?? '',\n positivePrompt: input.prompt?.trim() || SOLO_DIALOGUE_DEFAULT_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","// RunningHub 首尾帧 (first-last-frame → video) AI App config. Two images — a\n// FIRST frame + a LAST frame — plus a resolution and a frame count → one MP4\n// that smoothly transitions from the first image to the last (a \"transition\"\n// clip between two shots). (Published app 2062876888187621378.)\n\nexport const FLF_APP_ID = '2062876888187621378'\n\nexport const FLF_NODE_IDS = {\n /** First frame (start image). */\n FIRST: '322',\n /** Last frame (end image). */\n LAST: '323',\n /** Output resolution (longest side), e.g. 1024. */\n RESOLUTION: '319',\n /** Total frame count, e.g. 41. */\n FRAMES: '333',\n} as const\n\n/** Like the other I2V apps, this runs on the shared pool. */\nexport const FLF_RUN_OPTIONS = { usePersonalQueue: false, instanceType: 'default' } as const\n\nexport const FLF_DEFAULT_RESOLUTION = 1024\nexport const FLF_DEFAULT_FRAMES = 41\n","// RunningHub 首尾帧 binding: a first-last-frame transition video. Give a FIRST\n// frame image and a LAST frame image (e.g. the previous shot's last frame and\n// the next shot's first frame) plus a resolution + frame count; the app renders\n// an MP4 that interpolates from the first image to the last. Self-contained —\n// owns its input/port types and composes the generic client via runVideoApp.\n//\n// Returns the RunningHub CDN URL (~24h expiry) — persist/mirror what you need.\n\nimport type { NodeInfo, RunningHubClient } from '../client'\nimport { runVideoApp } from '../shared/runVideoApp'\nimport {\n FLF_APP_ID,\n FLF_NODE_IDS,\n FLF_RUN_OPTIONS,\n FLF_DEFAULT_RESOLUTION,\n FLF_DEFAULT_FRAMES,\n} from './constants'\n\nexport type FirstLastFrameInput = {\n /** First frame (start image). */\n readonly first: File\n /** Last frame (end image). */\n readonly last: File\n /** Output resolution (longest side); defaults to 1024. */\n readonly resolution?: number | undefined\n /** Total frame count; defaults to 41. */\n readonly frames?: number | undefined\n}\n\nexport type FirstLastFrameProgress = { readonly message: string }\n\nexport interface FirstLastFramePort {\n /** Generate the transition video; resolves to the output video URL (~24h CDN). */\n generate(\n input: FirstLastFrameInput,\n onProgress?: (p: FirstLastFrameProgress) => void,\n ): Promise<string>\n}\n\nexport type RunningHubFirstLastFrameConfig = {\n readonly client: RunningHubClient\n}\n\nexport function createRunningHubFirstLastFrame(\n config: RunningHubFirstLastFrameConfig,\n): FirstLastFramePort {\n const { client } = config\n return {\n async generate(\n input: FirstLastFrameInput,\n onProgress?: (p: FirstLastFrameProgress) => void,\n ): Promise<string> {\n const msg = (message: string) => onProgress?.({ message })\n return runVideoApp({\n client,\n appId: FLF_APP_ID,\n runOptions: FLF_RUN_OPTIONS,\n uploads: [\n { key: 'first', file: input.first, fileType: 'image' },\n { key: 'last', file: input.last, fileType: 'image' },\n ],\n buildNodes: (n): NodeInfo[] => [\n { nodeId: FLF_NODE_IDS.FIRST, fieldName: 'image', fieldValue: n.first ?? '' },\n { nodeId: FLF_NODE_IDS.LAST, fieldName: 'image', fieldValue: n.last ?? '' },\n { nodeId: FLF_NODE_IDS.RESOLUTION, fieldName: 'value', fieldValue: String(input.resolution ?? FLF_DEFAULT_RESOLUTION) },\n { nodeId: FLF_NODE_IDS.FRAMES, fieldName: 'value', fieldValue: String(input.frames ?? FLF_DEFAULT_FRAMES) },\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.8",
3
+ "version": "0.3.10",
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",