@surfmate.team/digital-human-runninghub 0.2.2 → 0.2.3

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
@@ -46,6 +46,8 @@ type RunAppOptions = {
46
46
  type WatchOptions = {
47
47
  readonly pollIntervalMs?: number;
48
48
  readonly timeoutMs?: number;
49
+ /** Abort the watch; on abort the running task is cancelled and the call rejects with Error('aborted'). */
50
+ readonly signal?: AbortSignal;
49
51
  };
50
52
  type RunningHubConfig = {
51
53
  /** OpenAPI key. Sent in upload form / poll body and as the run Bearer token. */
package/dist/index.js CHANGED
@@ -123,12 +123,21 @@ function createRunningHubClient(config) {
123
123
  async function watchTask(taskId, onProgress, options = {}) {
124
124
  const interval = options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
125
125
  const timeout = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
126
+ const signal = options.signal;
126
127
  let ws = null;
127
128
  const latest = {};
129
+ const abortIfNeeded = async () => {
130
+ if (!signal?.aborted) return;
131
+ await cancelTask(taskId).catch(() => {
132
+ });
133
+ throw new Error("aborted");
134
+ };
128
135
  try {
129
136
  const deadline = Date.now() + timeout;
130
137
  while (Date.now() < deadline) {
138
+ await abortIfNeeded();
131
139
  await sleep(interval);
140
+ await abortIfNeeded();
132
141
  const result = await pollTask(taskId);
133
142
  switch (result.status) {
134
143
  case "QUEUED":
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"],"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 let ws: WebSocket | null = null\n const latest: { percent?: number; step?: number; total?: number } = {}\n try {\n const deadline = Date.now() + timeout\n while (Date.now() < deadline) {\n await sleep(interval)\n const result = await pollTask(taskId)\n switch (result.status) {\n case 'QUEUED':\n onProgress?.({ tag: 'queued' })\n break\n case 'RUNNING':\n if (!ws && result.wsUrl) ws = attachProgressWs(result.wsUrl, latest)\n onProgress?.({ tag: 'running', ...latest })\n break\n case 'SUCCESS':\n return result.files\n case 'FAILED':\n // Transient right after creation, before status is queryable — retry.\n if (result.msg === 'APIKEY_TASK_STATUS_ERROR') {\n onProgress?.({ tag: 'queued' })\n break\n }\n throw new Error(result.msg)\n }\n }\n throw new Error(`RunningHub task timed out (${Math.round(timeout / 1000)}s)`)\n } finally {\n closeWs(ws)\n }\n }\n\n // Cancel endpoint accepts { taskId, apiKey } (one task) or { apiKey } (all of\n // this key's tasks). Best-effort: we don't surface the body (often empty).\n async function cancelTask(taskId: string): Promise<void> {\n await doFetch(`${base}/task/openapi/cancel`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ taskId, apiKey }),\n })\n }\n\n async function cancelByApiKey(): Promise<void> {\n await doFetch(`${base}/task/openapi/cancel`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ apiKey }),\n })\n }\n\n return { uploadFile, runApp, pollTask, watchTask, cancelTask, cancelByApiKey }\n}\n","// RunningHub greeting-video AI App config (Wan2.1 I2V 720p with audio sync).\n// One image + one speech audio + a fixed action prompt → MP4 with baked-in\n// audio in a single task. Values pinned here to avoid drift (mirror mate's\n// types/greetingTalkingVideo.ts).\n\nexport const GREETING_APP_ID = '2048355544552968194'\n\nexport const GREETING_NODE_IDS = {\n // Variable per request\n IMAGE: '133',\n AUDIO: '125',\n // Secondary audio slot — always filled with 1s of silence (see silence.ts).\n // Omitting it triggers a RunningHub 803 \"invalid node info\" error.\n AUDIO_SECONDARY: '209',\n POSITIVE_PROMPT: '216',\n // Fixed knobs\n CROP_POSITION: '171',\n SAVE_OUTPUT: '131',\n NEGATIVE_PROMPT: '135',\n LORA: '138',\n MODEL: '122',\n STEPS: '201',\n} as const\n\nexport const GREETING_MODEL = 'Wan2_1-I2V-14B-720p_fp8_e4m3fn_scaled_KJ.safetensors'\nexport const GREETING_LORA = 'Wan21_I2V_14B_lightx2v_cfg_step_distill_lora_rank64.safetensors'\nexport const GREETING_LORA_STRENGTH = 0.8\nexport const GREETING_STEPS = 25\nexport const GREETING_CROP_POSITION = 'center'\n\nexport const GREETING_NEGATIVE_PROMPT =\n 'bright tones, overexposed, static, blurred details, subtitles, style, works, paintings, images, static, overall gray, worst quality, low quality, JPEG compression residue, ugly, incomplete, extra fingers, poorly drawn hands, poorly drawn faces, deformed, disfigured, misshapen limbs, fused fingers, still picture, messy background, three legs, many people in the background, walking backwards'\n\n// Default action prompt (node 216) — \"she looks at the camera, holds the pose,\n// natural blink/breathing, stays still\". Used when the caller passes none.\nexport const GREETING_POSITIVE_PROMPT_DEFAULT =\n '她看着镜头说话,保持姿势,保持表情,自然眨眼和呼吸,保持不动'\n\n/** The 720p I2V app runs on the shared pool — the personal queue returns 803. */\nexport const GREETING_RUN_OPTIONS = { usePersonalQueue: false, instanceType: 'default' } as const\n\n/** TTS model used to synthesize the greeting speech when the caller gives none. */\nexport const GREETING_DEFAULT_TTS_MODEL = 'speech-02-turbo'\n","// Pure: build the node-override list for the greeting AI App from the uploaded\n// file names + prompt. Extracted for fixed-data testing (mirrors mate's\n// buildGreetingTalkingNodeInfoList).\n\nimport type { NodeInfo } from '../client'\nimport {\n GREETING_NODE_IDS as N,\n GREETING_MODEL,\n GREETING_LORA,\n GREETING_LORA_STRENGTH,\n GREETING_STEPS,\n GREETING_CROP_POSITION,\n GREETING_NEGATIVE_PROMPT,\n} from './constants'\n\nexport function buildGreetingNodeInfoList(args: {\n imageFileName: string\n audioFileName: string\n silenceAudioFileName: string\n positivePrompt: string\n}): NodeInfo[] {\n return [\n // Variable per request\n { nodeId: N.IMAGE, fieldName: 'image', fieldValue: args.imageFileName },\n { nodeId: N.AUDIO, fieldName: 'audio', fieldValue: args.audioFileName },\n { nodeId: N.AUDIO_SECONDARY, fieldName: 'audio', fieldValue: args.silenceAudioFileName },\n { nodeId: N.POSITIVE_PROMPT, fieldName: 'text', fieldValue: args.positivePrompt },\n // Fixed knobs\n { nodeId: N.CROP_POSITION, fieldName: 'crop_position', fieldValue: GREETING_CROP_POSITION },\n { nodeId: N.SAVE_OUTPUT, fieldName: 'save_output', fieldValue: 'true' },\n { nodeId: N.NEGATIVE_PROMPT, fieldName: 'negative_prompt', fieldValue: GREETING_NEGATIVE_PROMPT },\n { nodeId: N.LORA, fieldName: 'lora', fieldValue: GREETING_LORA },\n { nodeId: N.LORA, fieldName: 'strength', fieldValue: String(GREETING_LORA_STRENGTH) },\n { nodeId: N.MODEL, fieldName: 'model', fieldValue: GREETING_MODEL },\n { nodeId: N.STEPS, fieldName: 'value', fieldValue: String(GREETING_STEPS) },\n ]\n}\n","// The workflow's secondary audio slot (node 209) is schema-required; we always\n// fill it with 1 second of silence. mate ships a 4 KB silence_1s.mp3 asset — to\n// keep this package asset-free (batteries-included), the same file is embedded\n// here as base64 and decoded to a File at runtime.\n\nconst SILENCE_1S_MP3_BASE64 =\n 'SUQzBAAAAAAAIlRTU0UAAAAOAAADTGF2ZjYxLjcuMTAwAAAAAAAAAAAAAAD/+0DAAAAAAAAAAAAAAAAAAAAAAABJbmZvAAAADwAAACgAABD2ABAQFhYdHR0jIykpKS8vNTU1OztBQUFISE5OTlRUWlpaYGBmZmZsbHJycnl5f39/hYWLi4uRkZeXl52dpKSkqqqwsLC2try8vMLCyMjIzs7V1dXb2+Hh4efn7e3t8/P5+fn//wAAAABMYXZjNjEuMTkAAAAAAAAAAAAAAAAkBXwAAAAAAAAQ9in4b6YAAAAAAP/7EMQAA8AAAaQAAAAgAAA0gAAABExBTUUzLjEwMFVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVMQU1FMy4xMDBVVVVV//sQxCmDwAABpAAAACAAADSAAAAEVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUxBTUUzLjEwMFVVVVX/+xDEUwPAAAGkAAAAIAAANIAAAARVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVTEFNRTMuMTAwVVVVVf/7EMR8g8AAAaQAAAAgAAA0gAAABFVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVMQU1FMy4xMDBVVVVV//sQxKYDwAABpAAAACAAADSAAAAEVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUxBTUUzLjEwMFVVVVX/+xDEz4PAAAGkAAAAIAAANIAAAARVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVTEFNRTMuMTAwVVVVVf/7EMTWA8AAAaQAAAAgAAA0gAAABFVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVMQU1FMy4xMDBVVVVV//sQxNYDwAABpAAAACAAADSAAAAEVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUxBTUUzLjEwMFVVVVX/+xDE1gPAAAGkAAAAIAAANIAAAARVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVTEFNRTMuMTAwVVVVVf/7EMTWA8AAAaQAAAAgAAA0gAAABFVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVMQU1FMy4xMDBVVVVV//sQxNYDwAABpAAAACAAADSAAAAEVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUxBTUUzLjEwMFVVVVX/+xDE1gPAAAGkAAAAIAAANIAAAARVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVTEFNRTMuMTAwVVVVVf/7EMTWA8AAAaQAAAAgAAA0gAAABFVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVMQU1FMy4xMDBVVVVV//sQxNYDwAABpAAAACAAADSAAAAEVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUxBTUUzLjEwMFVVVVX/+xDE1gPAAAGkAAAAIAAANIAAAARVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVTEFNRTMuMTAwVVVVVf/7EMTWA8AAAaQAAAAgAAA0gAAABFVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVMQU1FMy4xMDBVVVVV//sQxNYDwAABpAAAACAAADSAAAAEVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUxBTUUzLjEwMFVVVVX/+xDE1gPAAAGkAAAAIAAANIAAAARVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVTEFNRTMuMTAwVVVVVf/7EMTWA8AAAaQAAAAgAAA0gAAABFVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVMQU1FMy4xMDBVVVVV//sQxNYDwAABpAAAACAAADSAAAAEVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUxBTUUzLjEwMFVVVVX/+xDE1gPAAAGkAAAAIAAANIAAAARVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVTEFNRTMuMTAwVVVVVf/7EMTWA8AAAaQAAAAgAAA0gAAABFVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVMQU1FMy4xMDBVVVVV//sQxNYDwAABpAAAACAAADSAAAAEVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUxBTUUzLjEwMFVVVVX/+xDE1gPAAAGkAAAAIAAANIAAAARVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVTEFNRTMuMTAwVVVVVf/7EMTWA8AAAaQAAAAgAAA0gAAABFVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVMQU1FMy4xMDBVVVVV//sQxNYDwAABpAAAACAAADSAAAAEVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUxBTUUzLjEwMFVVVVX/+xDE1gPAAAGkAAAAIAAANIAAAARVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVTEFNRTMuMTAwVVVVVf/7EMTWA8AAAaQAAAAgAAA0gAAABFVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVMQU1FMy4xMDBVVVVV//sQxNYDwAABpAAAACAAADSAAAAEVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUxBTUUzLjEwMFVVVVX/+xDE1gPAAAGkAAAAIAAANIAAAARVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVTEFNRTMuMTAwVVVVVf/7EMTWA8AAAaQAAAAgAAA0gAAABFVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVMQU1FMy4xMDBVVVVV//sQxNYDwAABpAAAACAAADSAAAAEVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVX/+xDE1gPAAAGkAAAAIAAANIAAAARVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVf/7EMTWA8AAAaQAAAAgAAA0gAAABFVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV//sQxNYDwAABpAAAACAAADSAAAAEVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVX/+xDE1gPAAAGkAAAAIAAANIAAAARVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVf/7EMTWA8AAAaQAAAAgAAA0gAAABFVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV'\n\nlet cached: File | null = null\n\n/** A 1-second silent MP3 as a File (cached for the page lifetime). */\nexport function silenceAudioFile(): File {\n if (cached) return cached\n const bin = atob(SILENCE_1S_MP3_BASE64)\n const bytes = new Uint8Array(bin.length)\n for (let i = 0; i < bin.length; i++) bytes[i] = bin.charCodeAt(i)\n cached = new File([bytes], 'silence_1s.mp3', { type: 'audio/mpeg' })\n return cached\n}\n","// Shared media helpers used by the greeting + waiting bindings.\n\n// ── Avatar → raster image File ──\n// DiceBear (the default avatar provider) returns SVG; Wan2.1 I2V needs a raster\n// image, so SVG is rasterized to PNG via an offscreen canvas.\n\nasync function rasterizeSvg(svgBlob: Blob, size = 512): Promise<Blob> {\n const svgUrl = URL.createObjectURL(svgBlob)\n try {\n const img = new Image()\n img.crossOrigin = 'anonymous'\n img.src = svgUrl\n await new Promise<void>((resolve, reject) => {\n img.onload = () => resolve()\n img.onerror = () => reject(new Error('Failed to load SVG into <img>'))\n })\n const canvas = document.createElement('canvas')\n canvas.width = size\n canvas.height = size\n const ctx = canvas.getContext('2d')\n if (!ctx) throw new Error('Canvas 2D context unavailable')\n ctx.fillStyle = '#ffffff'\n ctx.fillRect(0, 0, size, size)\n ctx.drawImage(img, 0, 0, size, size)\n return await new Promise<Blob>((resolve, reject) => {\n canvas.toBlob(\n (blob) => (blob ? resolve(blob) : reject(new Error('Canvas toBlob returned null'))),\n 'image/png',\n )\n })\n } finally {\n URL.revokeObjectURL(svgUrl)\n }\n}\n\nexport async function fetchAvatarAsImageFile(url: string, filename: string): Promise<File> {\n const res = await fetch(url)\n if (!res.ok) throw new Error(`Failed to fetch avatar: ${res.status}`)\n const blob = await res.blob()\n if (blob.type === 'image/svg+xml' || url.toLowerCase().endsWith('.svg')) {\n const png = await rasterizeSvg(blob)\n return new File([png], `${filename}.png`, { type: 'image/png' })\n }\n if (!blob.type.startsWith('image/')) {\n throw new Error(`Avatar URL did not return an image (got ${blob.type || 'unknown'})`)\n }\n return new File([blob], filename, { type: blob.type })\n}\n\n// ── Silent WAV ──\n// The static-waiting workflow has no length parameter — the result duration is\n// driven by a silent audio track. Build N seconds of 16 kHz mono 16-bit PCM\n// silence as a WAV Blob (mirrors mate's buildSilenceWavBlob).\n\nconst SAMPLE_RATE = 16000\nconst CHANNELS = 1\nconst BITS = 16\n\nexport function buildSilenceWavBlob(seconds: number): Blob {\n const sampleCount = Math.round(seconds * SAMPLE_RATE)\n const blockAlign = (CHANNELS * BITS) / 8\n const byteRate = SAMPLE_RATE * blockAlign\n const dataSize = sampleCount * blockAlign\n const buffer = new ArrayBuffer(44 + dataSize)\n const view = new DataView(buffer)\n const writeStr = (offset: number, s: string) => {\n for (let i = 0; i < s.length; i++) view.setUint8(offset + i, s.charCodeAt(i))\n }\n writeStr(0, 'RIFF')\n view.setUint32(4, 36 + dataSize, true)\n writeStr(8, 'WAVE')\n writeStr(12, 'fmt ')\n view.setUint32(16, 16, true) // PCM fmt chunk size\n view.setUint16(20, 1, true) // PCM\n view.setUint16(22, CHANNELS, true)\n view.setUint32(24, SAMPLE_RATE, true)\n view.setUint32(28, byteRate, true)\n view.setUint16(32, blockAlign, true)\n view.setUint16(34, BITS, true)\n writeStr(36, 'data')\n view.setUint32(40, dataSize, true)\n // PCM samples left zero-filled = silence.\n return new Blob([buffer], { type: 'audio/wav' })\n}\n","// Shared RunningHub image-to-video pipeline. The greeting and waiting bindings\n// differ ONLY in which files they prepare and how they map the uploaded names to\n// nodes; everything else — upload-all → runApp → watchTask → pick the video URL,\n// plus the progress wording — lives here once.\n\nimport type { NodeInfo, RunAppOptions, RunningHubClient } from '../client'\n\nexport type VideoUpload = {\n /** Key the buildNodes callback uses to reference this file's uploaded name. */\n readonly key: string\n readonly file: File\n readonly fileType: 'image' | 'audio'\n}\n\nexport type RunVideoAppArgs = {\n readonly client: RunningHubClient\n readonly appId: string\n readonly runOptions?: RunAppOptions | undefined\n /** Files to upload before submitting; their RunningHub names feed buildNodes. */\n readonly uploads: VideoUpload[]\n /** Build the node overrides from the uploaded file names (keyed by upload.key). */\n readonly buildNodes: (fileNames: Record<string, string>) => NodeInfo[]\n /** Surface a human progress message (上传素材中… / 提交任务中… / 排队中… / 生成中… N%). */\n readonly onMessage?: ((message: string) => void) | undefined\n}\n\nexport async function runVideoApp(args: RunVideoAppArgs): Promise<string> {\n args.onMessage?.('上传素材中…')\n const entries = await Promise.all(\n args.uploads.map(async (u) => [u.key, await args.client.uploadFile(u.file, u.fileType)] as const),\n )\n const fileNames: Record<string, string> = Object.fromEntries(entries)\n\n args.onMessage?.('提交任务中…')\n const taskId = await args.client.runApp(args.appId, args.buildNodes(fileNames), args.runOptions)\n\n const files = await args.client.watchTask(taskId, (p) => {\n if (p.tag === 'queued') args.onMessage?.('排队中…')\n else args.onMessage?.(p.percent != null ? `生成中… ${p.percent}%` : '生成中…')\n })\n\n const url = files.find((f) => /\\.(mp4|webm|mov)$/i.test(f.fileUrl))?.fileUrl ?? files[0]?.fileUrl\n if (!url) throw new Error('RunningHub task succeeded but returned no video URL')\n return url\n}\n","// RunningHub implementation of greeting's GreetingVideoPort.\n// Prepares the two greeting-specific inputs — TTS audio (via the injected\n// VoiceSynthesisPort) and the avatar image — then hands the upload→run→watch\n// pipeline to the shared runVideoApp. The schema-required secondary audio slot\n// (node 209) is filled with the embedded 1s silence.\n//\n// Returns the RunningHub CDN URL (~24h expiry) — persist via greeting's\n// GreetingVideoStoragePort / a storage mirror for anything long-lived.\n\nimport type {\n GreetingVideoPort,\n GreetingVideoInput,\n GreetingVideoProgress,\n} from '@surfmate.team/digital-human-greeting'\nimport type { VoiceSynthesisPort } from '@surfmate.team/digital-human-voice'\nimport type { RunningHubClient } from '../client'\nimport { runVideoApp } from '../shared/runVideoApp'\nimport { fetchAvatarAsImageFile } from '../shared/media'\nimport {\n GREETING_APP_ID,\n GREETING_RUN_OPTIONS,\n GREETING_POSITIVE_PROMPT_DEFAULT,\n GREETING_DEFAULT_TTS_MODEL,\n} from './constants'\nimport { buildGreetingNodeInfoList } from './buildNodeInfoList'\nimport { silenceAudioFile } from './silence'\n\nexport type RunningHubGreetingVideoConfig = {\n /** Generic RunningHub client (createRunningHubClient). */\n readonly client: RunningHubClient\n /** TTS backend that turns the greeting text into audio bytes (e.g. MiniMax). */\n readonly synth: VoiceSynthesisPort\n /** TTS model id. Defaults to MiniMax speech-02-turbo. */\n readonly modelId?: string\n /** Action prompt for node 216. Defaults to the \"looks at camera, holds pose\" prompt. */\n readonly positivePrompt?: string\n}\n\n// Decode the speech audio's real length (seconds). Browser-guarded; resolves to\n// undefined where Web Audio is unavailable (headless) or decoding fails.\nasync function decodeDurationSec(bytes: ArrayBuffer): Promise<number | undefined> {\n const g = globalThis as unknown as {\n AudioContext?: typeof AudioContext\n webkitAudioContext?: typeof AudioContext\n }\n const Ctx = g.AudioContext ?? g.webkitAudioContext\n if (!Ctx) return undefined\n const ctx = new Ctx()\n try {\n return (await ctx.decodeAudioData(bytes.slice(0))).duration\n } catch {\n return undefined\n } finally {\n void ctx.close()\n }\n}\n\nexport function createRunningHubGreetingVideo(\n config: RunningHubGreetingVideoConfig,\n): GreetingVideoPort {\n const { client, synth } = config\n const modelId = config.modelId ?? GREETING_DEFAULT_TTS_MODEL\n const defaultPrompt =\n (config.positivePrompt && config.positivePrompt.trim()) || GREETING_POSITIVE_PROMPT_DEFAULT\n\n return {\n async generate(\n input: GreetingVideoInput,\n onProgress?: (p: GreetingVideoProgress) => void,\n ): Promise<string> {\n const msg = (message: string) => onProgress?.({ message })\n const positivePrompt = input.positivePrompt?.trim() || defaultPrompt\n\n // Audio: a pre-recorded URL if given, else synthesize from text + voice.\n msg('合成语音中…')\n const audioBytes = input.audioUrl\n ? await (await fetch(input.audioUrl)).arrayBuffer()\n : await synth.synthesize({\n text: input.text,\n voiceId: input.voiceId,\n modelId,\n ...(input.emotion ? { emotion: input.emotion } : {}),\n })\n const audioFile = new File([audioBytes], 'greeting-speech.mp3', { type: 'audio/mpeg' })\n\n // Surface the REAL decoded spoken length (callers persist it for length-matching).\n if (input.onAudioDuration) {\n const sec = await decodeDurationSec(audioBytes)\n if (sec != null) input.onAudioDuration(sec)\n }\n\n msg('获取头像中…')\n const imageFile = await fetchAvatarAsImageFile(input.avatarUrl, 'greeting-avatar')\n\n return runVideoApp({\n client,\n appId: GREETING_APP_ID,\n runOptions: GREETING_RUN_OPTIONS,\n uploads: [\n { key: 'image', file: imageFile, fileType: 'image' },\n { key: 'audio', file: audioFile, fileType: 'audio' },\n { key: 'silence', file: silenceAudioFile(), fileType: 'audio' },\n ],\n buildNodes: (n) =>\n buildGreetingNodeInfoList({\n imageFileName: n.image ?? '',\n audioFileName: n.audio ?? '',\n silenceAudioFileName: n.silence ?? '',\n positivePrompt,\n }),\n onMessage: msg,\n })\n },\n }\n}\n","// RunningHub implementation of waiting's WaitingVideoGenerationPort (\"Generate\n// Static\"). Prepares the two waiting-specific inputs — the avatar image and N\n// seconds of silence (which drive the clip length, no TTS) — then hands the\n// upload→run→watch pipeline to the shared runVideoApp.\n//\n// Returns the RunningHub CDN URL (~24h expiry) — persist via the waiting storage\n// port / a mirror for anything long-lived.\n\nimport type {\n WaitingVideoGenerationPort,\n WaitingVideoGenerationInput,\n WaitingVideoProgress,\n} from '@surfmate.team/digital-human-waiting'\nimport type { NodeInfo, RunningHubClient } from '../client'\nimport { runVideoApp } from '../shared/runVideoApp'\nimport { fetchAvatarAsImageFile, buildSilenceWavBlob } from '../shared/media'\n\n// mate's static-waiting AI App + node ids.\nconst STATIC_WAITING_APP_ID = '2050552043860963330'\nconst NODE_IDS = { IMAGE: '34', PROMPT: '17', AUDIO: '38' } as const\nconst RUN_OPTIONS = { usePersonalQueue: false, instanceType: 'default' } as const\n\nexport type RunningHubStaticWaitingVideoConfig = {\n readonly client: RunningHubClient\n}\n\nexport function createRunningHubStaticWaitingVideo(\n config: RunningHubStaticWaitingVideoConfig,\n): WaitingVideoGenerationPort {\n const { client } = config\n return {\n async generateStatic(\n input: WaitingVideoGenerationInput,\n onProgress?: (p: WaitingVideoProgress) => void,\n ): Promise<string> {\n const msg = (message: string) => onProgress?.({ message })\n\n // Waiting-specific inputs: the avatar image + N seconds of silence.\n msg('获取头像中…')\n const imageFile = await fetchAvatarAsImageFile(input.avatarUrl, `${input.characterId}-avatar`)\n const silence = buildSilenceWavBlob(input.seconds)\n const audioFile = new File([silence], `silence-${input.seconds}s.wav`, { type: 'audio/wav' })\n\n return runVideoApp({\n client,\n appId: STATIC_WAITING_APP_ID,\n runOptions: RUN_OPTIONS,\n uploads: [\n { key: 'image', file: imageFile, fileType: 'image' },\n { key: 'audio', file: audioFile, fileType: 'audio' },\n ],\n buildNodes: (n): NodeInfo[] => [\n { nodeId: NODE_IDS.IMAGE, fieldName: 'image', fieldValue: n.image ?? '' },\n { nodeId: NODE_IDS.PROMPT, fieldName: 'prompt', fieldValue: input.prompt },\n { nodeId: NODE_IDS.AUDIO, fieldName: 'audio', fieldValue: n.audio ?? '' },\n ],\n onMessage: msg,\n })\n },\n }\n}\n","// RunningHub 多人对话 (multi-person lip-sync dialogue) AI App config.\n// InfiniteTalk dual-image driving: one reference image holding two people +\n// three audio tracks → one MP4 where each person lip-syncs to their own voice.\n//\n// The two per-person tracks drive the MOUTHS (each audio only animates its\n// person's face, assigned by the workflow's auto/painted masks); the full\n// conversation track is the OUTPUT soundtrack and sets the total length.\n// (See docs/workflows.md — node ids match the published app, App 2062039217370394626.)\n\nexport const DIALOGUE_APP_ID = '2062039217370394626'\n\nexport const DIALOGUE_NODE_IDS = {\n /** Reference image (the two people). */\n IMAGE: '57',\n /** audio_1 — isolated track that drives the LEFT person's mouth. */\n AUDIO_LEFT: '64',\n /** audio_2 — isolated track that drives the RIGHT person's mouth. */\n AUDIO_RIGHT: '59',\n /** Full conversation — the output soundtrack + drives total video length. */\n AUDIO_FULL: '58',\n /** Action / camera prompt (e.g. 固定镜头). Empty by default. */\n PROMPT: '86',\n} as const\n\n/** Like the other I2V apps, this runs on the shared pool — personal queue → 803. */\nexport const DIALOGUE_RUN_OPTIONS = { usePersonalQueue: false, instanceType: 'default' } as const\n","// RunningHub 多人对话 binding: a two-person lip-sync dialogue video. Self-\n// contained (no feature-package port yet) — it owns its input/port types and\n// composes the generic client via the shared runVideoApp pipeline.\n//\n// The caller prepares three time-aligned audio tracks (e.g. via Web Audio):\n// - leftAudio : only the LEFT person's voice (silence elsewhere) → audio_1\n// - rightAudio : only the RIGHT person's voice (silence elsewhere) → audio_2\n// - fullAudio : the complete conversation (both, mixed) → soundtrack + length\n// All three MUST be the same length and aligned on one timeline.\n//\n// Returns the RunningHub CDN URL (~24h expiry) — persist/mirror anything you\n// need long-lived.\n\nimport type { NodeInfo, RunningHubClient } from '../client'\nimport { runVideoApp } from '../shared/runVideoApp'\nimport { DIALOGUE_APP_ID, DIALOGUE_NODE_IDS, DIALOGUE_RUN_OPTIONS } from './constants'\n\nexport type DialogueVideoInput = {\n /** Reference image containing the two people. */\n readonly image: File\n /** Isolated audio for the LEFT person (drives audio_1). */\n readonly leftAudio: File\n /** Isolated audio for the RIGHT person (drives audio_2). */\n readonly rightAudio: File\n /** The full conversation track — output soundtrack + total length. */\n readonly fullAudio: File\n /** Optional action / camera prompt (e.g. 固定镜头). Omit → empty. */\n readonly prompt?: string | undefined\n}\n\nexport type DialogueVideoProgress = { readonly message: string }\n\nexport interface DialogueVideoPort {\n /** Generate the dialogue video; resolves to the output video URL (~24h CDN). */\n generate(\n input: DialogueVideoInput,\n onProgress?: (p: DialogueVideoProgress) => void,\n ): Promise<string>\n}\n\nexport type RunningHubDialogueVideoConfig = {\n readonly client: RunningHubClient\n}\n\nexport function createRunningHubDialogueVideo(\n config: RunningHubDialogueVideoConfig,\n): DialogueVideoPort {\n const { client } = config\n return {\n async generate(\n input: DialogueVideoInput,\n onProgress?: (p: DialogueVideoProgress) => void,\n ): Promise<string> {\n const msg = (message: string) => onProgress?.({ message })\n return runVideoApp({\n client,\n appId: DIALOGUE_APP_ID,\n runOptions: DIALOGUE_RUN_OPTIONS,\n uploads: [\n { key: 'image', file: input.image, fileType: 'image' },\n { key: 'left', file: input.leftAudio, fileType: 'audio' },\n { key: 'right', file: input.rightAudio, fileType: 'audio' },\n { key: 'full', file: input.fullAudio, fileType: 'audio' },\n ],\n buildNodes: (n): NodeInfo[] => [\n { nodeId: DIALOGUE_NODE_IDS.IMAGE, fieldName: 'image', fieldValue: n.image ?? '' },\n { nodeId: DIALOGUE_NODE_IDS.AUDIO_LEFT, fieldName: 'audio', fieldValue: n.left ?? '' },\n { nodeId: DIALOGUE_NODE_IDS.AUDIO_RIGHT, fieldName: 'audio', fieldValue: n.right ?? '' },\n { nodeId: DIALOGUE_NODE_IDS.AUDIO_FULL, fieldName: 'audio', fieldValue: n.full ?? '' },\n { nodeId: DIALOGUE_NODE_IDS.PROMPT, fieldName: 'text', fieldValue: input.prompt ?? '' },\n ],\n onMessage: msg,\n })\n },\n }\n}\n","// Shared text-to-image runner. Every RunningHub text-to-image app has the same\n// shape — prompt + width + height + batch → image URL(s), no uploads — and\n// differs ONLY in its app id, the four node ids, and the default dimensions.\n// Each model file (qwen2512, flux2klein, …) supplies that spec; this builds the\n// TextToImagePort once.\n\nimport type { NodeInfo, RunAppOptions, RunningHubClient } from '../client'\nimport type { TextToImageInput, TextToImagePort, TextToImageProgress } from './types'\n\nconst IMAGE_RE = /\\.(png|jpe?g|webp)$/i\n\n/** Per-model spec: the app id, its four node ids, and default dimensions. */\nexport type Text2ImageSpec = {\n readonly appId: string\n readonly nodeIds: {\n readonly prompt: string\n readonly width: string\n readonly height: string\n readonly batch: string\n }\n readonly defaults: { readonly width: number; readonly height: number; readonly batchSize: number }\n /** Defaults to the shared-pool options (personal queue → 803 on most apps). */\n readonly runOptions?: RunAppOptions\n}\n\nconst SHARED_POOL: RunAppOptions = { usePersonalQueue: false, instanceType: 'default' }\n\nexport function createText2ImagePort(client: RunningHubClient, spec: Text2ImageSpec): TextToImagePort {\n return {\n async generate(\n input: TextToImageInput,\n onProgress?: (p: TextToImageProgress) => void,\n ): Promise<string[]> {\n const msg = (message: string) => onProgress?.({ message })\n const width = input.width ?? spec.defaults.width\n const height = input.height ?? spec.defaults.height\n const batchSize = input.batchSize ?? spec.defaults.batchSize\n\n const nodes: NodeInfo[] = [\n { nodeId: spec.nodeIds.prompt, fieldName: 'text', fieldValue: input.prompt },\n { nodeId: spec.nodeIds.width, fieldName: 'value', fieldValue: String(width) },\n { nodeId: spec.nodeIds.height, fieldName: 'value', fieldValue: String(height) },\n { nodeId: spec.nodeIds.batch, fieldName: 'value', fieldValue: String(batchSize) },\n ]\n\n msg('提交任务中…')\n const taskId = await client.runApp(spec.appId, nodes, spec.runOptions ?? SHARED_POOL)\n\n const files = await client.watchTask(taskId, (p) => {\n if (p.tag === 'queued') msg('排队中…')\n else msg(p.percent != null ? `生成中… ${p.percent}%` : '生成中…')\n })\n\n const images = files.filter((f) => IMAGE_RE.test(f.fileUrl)).map((f) => f.fileUrl)\n const urls = images.length > 0 ? images : files.map((f) => f.fileUrl)\n if (urls.length === 0) throw new Error('RunningHub task succeeded but returned no image URL')\n return urls\n },\n }\n}\n","// RunningHub 文生图 — Qwen-Image 2512 binding. Just the per-model spec; the\n// shared runner builds the TextToImagePort. (Published app 2052988750950617090.)\n\nimport type { RunningHubClient } from '../client'\nimport type { TextToImagePort } from './types'\nimport { createText2ImagePort, type Text2ImageSpec } from './run'\n\nexport const QWEN2512_SPEC: Text2ImageSpec = {\n appId: '2052988750950617090',\n // node 6 prompt · 80 width · 81 height · 117 batch size\n nodeIds: { prompt: '6', width: '80', height: '81', batch: '117' },\n defaults: { width: 1920, height: 1080, batchSize: 1 },\n}\n\nexport type RunningHubQwen2512Config = {\n readonly client: RunningHubClient\n}\n\nexport function createRunningHubQwen2512(config: RunningHubQwen2512Config): TextToImagePort {\n return createText2ImagePort(config.client, QWEN2512_SPEC)\n}\n","// RunningHub 文生图 — Flux2 Klein binding. Same shape as Qwen2512, different\n// app + node ids. (Published app 2026466502756536322.)\n\nimport type { RunningHubClient } from '../client'\nimport type { TextToImagePort } from './types'\nimport { createText2ImagePort, type Text2ImageSpec } from './run'\n\nexport const FLUX2_KLEIN_SPEC: Text2ImageSpec = {\n appId: '2026466502756536322',\n // node 64 prompt · 99 width · 101 height · 103 batch size\n nodeIds: { prompt: '64', width: '99', height: '101', batch: '103' },\n defaults: { width: 1920, height: 1088, batchSize: 1 },\n}\n\nexport type RunningHubFlux2KleinConfig = {\n readonly client: RunningHubClient\n}\n\nexport function createRunningHubFlux2Klein(config: RunningHubFlux2KleinConfig): TextToImagePort {\n return createText2ImagePort(config.client, FLUX2_KLEIN_SPEC)\n}\n","// Shared image-edit runner. Every RunningHub image-edit app has the same shape:\n// upload 1–N source images → fill the prompt node + the image nodes in order →\n// run → watch → image URL(s). Each model file supplies the spec; this builds\n// the ImageEditPort once.\n\nimport type { NodeInfo, RunAppOptions, RunningHubClient } from '../client'\nimport type { ImageEditInput, ImageEditPort, ImageEditProgress } from './types'\n\nconst IMAGE_RE = /\\.(png|jpe?g|webp)$/i\nconst SHARED_POOL: RunAppOptions = { usePersonalQueue: false, instanceType: 'default' }\n\n/** Per-model spec: the app id, the prompt node, and the image nodes in fill order. */\nexport type ImageEditSpec = {\n readonly appId: string\n readonly promptNodeId: string\n /** Image node ids in fill order (primary first). The Nth image → imageNodeIds[N-1]. */\n readonly imageNodeIds: readonly string[]\n 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\n msg('提交任务中…')\n const taskId = await client.runApp(spec.appId, nodes, spec.runOptions ?? SHARED_POOL)\n\n const files = await client.watchTask(taskId, (p) => {\n if (p.tag === 'queued') msg('排队中…')\n else msg(p.percent != null ? `生成中… ${p.percent}%` : '生成中…')\n })\n\n const out = files.filter((f) => IMAGE_RE.test(f.fileUrl)).map((f) => f.fileUrl)\n const urls = out.length > 0 ? out : files.map((f) => f.fileUrl)\n if (urls.length === 0) throw new Error('RunningHub task succeeded but returned no image URL')\n return urls\n },\n }\n}\n","// RunningHub 图像编辑 — Qwen-Image 2511 binding. Up to 3 source images + a\n// prompt. (Published app 2025738520135995394.)\n\nimport type { RunningHubClient } from '../client'\nimport type { ImageEditPort } from './types'\nimport { createImageEditPort, type ImageEditSpec } from './run'\n\nexport const QWEN2511_SPEC: ImageEditSpec = {\n appId: '2025738520135995394',\n promptNodeId: '182',\n // primary first: 1 img → 157; 2 → 157,180; 3 → 157,180,181\n imageNodeIds: ['157', '180', '181'],\n}\n\nexport type RunningHubQwen2511Config = {\n readonly client: RunningHubClient\n}\n\nexport function createRunningHubQwen2511(config: RunningHubQwen2511Config): ImageEditPort {\n return createImageEditPort(config.client, QWEN2511_SPEC)\n}\n","// RunningHub 图像编辑 — Flux image-edit binding. Two images (original + a\n// reference) + a Chinese instruction → an edited image. (Published app\n// 2052941353117597697.)\n\nimport type { RunningHubClient } from '../client'\nimport type { ImageEditPort } from './types'\nimport { createImageEditPort, type ImageEditSpec } from './run'\n\nexport const FLUX_EDIT_SPEC: ImageEditSpec = {\n appId: '2052941353117597697',\n promptNodeId: '178',\n // primary (original) → 175; the reference image → 159\n imageNodeIds: ['175', '159'],\n}\n\nexport type RunningHubFluxEditConfig = {\n readonly client: RunningHubClient\n}\n\nexport function createRunningHubFluxEdit(config: RunningHubFluxEditConfig): ImageEditPort {\n return createImageEditPort(config.client, FLUX_EDIT_SPEC)\n}\n"],"mappings":"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","names":["inner","taskStatus","IMAGE_RE","SHARED_POOL"]}
1
+ {"version":3,"sources":["../src/client/parsePollResponse.ts","../src/client/createRunningHubClient.ts","../src/greeting/constants.ts","../src/greeting/buildNodeInfoList.ts","../src/greeting/silence.ts","../src/shared/media.ts","../src/shared/runVideoApp.ts","../src/greeting/createRunningHubGreetingVideo.ts","../src/waiting/createRunningHubStaticWaitingVideo.ts","../src/dialogue/constants.ts","../src/dialogue/createRunningHubDialogueVideo.ts","../src/text2image/run.ts","../src/text2image/qwen2512.ts","../src/text2image/flux2klein.ts","../src/imageedit/run.ts","../src/imageedit/qwen2511.ts","../src/imageedit/fluxedit.ts"],"sourcesContent":["// Pure: normalize RunningHub's several poll-response shapes into TaskPollResult.\n// Isolated from the HTTP client so it can be tested with fixed data.\n// (Faithful port of mate's runningHubService.parsePollResponse.)\n\nimport type { TaskOutputFile, TaskPollResult } from './types'\n\nexport function parsePollResponse(data: unknown): TaskPollResult {\n const obj = data as Record<string, unknown>\n\n // Format A: { code, data, msg }\n if (obj.code !== undefined) {\n if (obj.code === 804 || obj.msg === 'APIKEY_TASK_IS_RUNNING') {\n const inner = obj.data as Record<string, unknown> | undefined\n const wsUrl = typeof inner?.netWssUrl === 'string' ? (inner.netWssUrl as string) : undefined\n return wsUrl ? { status: 'RUNNING', wsUrl } : { status: 'RUNNING' }\n }\n if (obj.code !== 0) {\n return { status: 'FAILED', msg: String(obj.msg ?? 'Unknown error') }\n }\n const payload = obj.data\n if (Array.isArray(payload)) {\n return { status: 'SUCCESS', files: payload as TaskOutputFile[] }\n }\n const inner = payload as Record<string, unknown> | undefined\n const taskStatus = inner?.taskStatus as string | undefined\n if (taskStatus === 'RUNNING') return { status: 'RUNNING' }\n if (taskStatus === 'QUEUED') return { status: 'QUEUED' }\n if (taskStatus === 'SUCCESS' || taskStatus === 'SUCCEDD') {\n const fileUrl = inner?.fileUrl as string | undefined\n if (fileUrl) return { status: 'SUCCESS', files: [{ fileUrl, fileType: 'video' }] }\n return { status: 'SUCCESS', files: [] }\n }\n if (taskStatus === 'FAILED') {\n return { status: 'FAILED', msg: String(inner?.errorInfo ?? 'Task failed') }\n }\n return { status: 'FAILED', msg: `Unexpected taskStatus: ${taskStatus}` }\n }\n\n // Format B: flat { taskStatus, fileUrl, outputs, ... }\n const taskStatus = obj.taskStatus as string | undefined\n if (taskStatus === 'RUNNING') return { status: 'RUNNING' }\n if (taskStatus === 'QUEUED') return { status: 'QUEUED' }\n if (taskStatus === 'SUCCESS' || taskStatus === 'SUCCEDD') {\n const fileUrl = obj.fileUrl as string | undefined\n const outputs = obj.outputs as TaskOutputFile[] | undefined\n if (Array.isArray(outputs) && outputs.length > 0) {\n return { status: 'SUCCESS', files: outputs }\n }\n if (fileUrl) return { status: 'SUCCESS', files: [{ fileUrl, fileType: 'video' }] }\n return { status: 'SUCCESS', files: [] }\n }\n if (taskStatus === 'FAILED') {\n return { status: 'FAILED', msg: String(obj.errorMessage ?? obj.errorInfo ?? 'Task failed') }\n }\n return { status: 'FAILED', msg: `Unexpected response: ${JSON.stringify(obj).slice(0, 200)}` }\n}\n","// RunningHub OpenAPI client — action layer (HTTP + optional WebSocket).\n// Generic and workflow-agnostic: upload files, run an AI App, watch a task.\n// (Faithful port of mate's runningHubService, minus localStorage task\n// persistence — that's an app concern, kept out of the library.)\n\nimport type {\n NodeInfo,\n RunAppOptions,\n RunningHubClient,\n RunningHubConfig,\n TaskOutputFile,\n TaskPollResult,\n TaskProgress,\n WatchOptions,\n} from './types'\nimport { parsePollResponse } from './parsePollResponse'\n\nconst DEFAULT_BASE = '/api/runninghub'\nconst DEFAULT_POLL_INTERVAL_MS = 3000\n// Long ceiling to bound runaway polling; normal end is SUCCESS/FAILED.\nconst DEFAULT_TIMEOUT_MS = 15 * 60 * 1000\n\nconst sleep = (ms: number): Promise<void> => new Promise((r) => setTimeout(r, ms))\n\ntype UploadResponse = { code: number; msg: string; data: { fileName: string } }\ntype CreateTaskResponse = {\n taskId: string\n status: string\n errorCode: string\n errorMessage: string\n}\n\n// Attach a ComfyUI-style WebSocket to stream step-level progress. Best-effort:\n// parse 'progress' frames and mutate `latest` in place; the poll loop forwards\n// the latest value each tick. Any failure degrades to poll-only (no percent).\nfunction attachProgressWs(\n url: string,\n latest: { percent?: number; step?: number; total?: number },\n): WebSocket | null {\n try {\n const ws = new WebSocket(url)\n ws.onmessage = (event) => {\n if (typeof event.data !== 'string') return // binary previews — ignore\n try {\n const msg = JSON.parse(event.data) as { type?: string; data?: Record<string, unknown> }\n if (msg.type !== 'progress' || !msg.data) return\n const value = msg.data.value\n const max = msg.data.max\n if (typeof value === 'number' && typeof max === 'number' && max > 0) {\n latest.step = value\n latest.total = max\n latest.percent = Math.round((value / max) * 100)\n }\n } catch {\n /* malformed frame — keep listening */\n }\n }\n ws.onerror = () => {\n /* best-effort; polling carries the flow */\n }\n return ws\n } catch {\n return null\n }\n}\n\nfunction closeWs(ws: WebSocket | null): void {\n if (!ws) return\n if (ws.readyState === WebSocket.OPEN || ws.readyState === WebSocket.CONNECTING) ws.close()\n}\n\nexport function createRunningHubClient(config: RunningHubConfig): RunningHubClient {\n const base = config.baseUrl ?? DEFAULT_BASE\n const apiKey = config.apiKey\n const doFetch = config.fetchImpl ?? fetch\n\n async function uploadFile(file: File, fileType: 'image' | 'audio'): Promise<string> {\n const form = new FormData()\n form.append('file', file)\n form.append('apiKey', apiKey)\n form.append('fileType', fileType)\n const res = await doFetch(`${base}/task/openapi/upload`, { method: 'POST', body: form })\n const json = (await res.json()) as UploadResponse\n if (json.code !== 0) throw new Error(json.msg || 'RunningHub upload failed')\n return json.data.fileName\n }\n\n async function runApp(\n appId: string,\n nodeInfoList: NodeInfo[],\n options: RunAppOptions = {},\n ): Promise<string> {\n const body: Record<string, unknown> = {\n nodeInfoList,\n usePersonalQueue: options.usePersonalQueue ?? true,\n }\n if (options.instanceType) body.instanceType = options.instanceType\n const res = await doFetch(`${base}/openapi/v2/run/ai-app/${appId}`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${apiKey}` },\n body: JSON.stringify(body),\n })\n const json = (await res.json()) as CreateTaskResponse\n if (json.errorCode) {\n const detail = json.errorMessage ? ` ${json.errorMessage}` : ''\n throw new Error(`RunningHub ${json.errorCode}${detail}`)\n }\n return json.taskId\n }\n\n async function pollTask(taskId: string): Promise<TaskPollResult> {\n const res = await doFetch(`${base}/task/openapi/outputs`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ taskId, apiKey }),\n })\n return parsePollResponse(await res.json())\n }\n\n async function watchTask(\n taskId: string,\n onProgress?: (p: TaskProgress) => void,\n options: WatchOptions = {},\n ): Promise<TaskOutputFile[]> {\n const interval = options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS\n const timeout = options.timeoutMs ?? DEFAULT_TIMEOUT_MS\n const signal = options.signal\n let ws: WebSocket | null = null\n const latest: { percent?: number; step?: number; total?: number } = {}\n const abortIfNeeded = async (): Promise<void> => {\n if (!signal?.aborted) return\n await cancelTask(taskId).catch(() => {})\n throw new Error('aborted')\n }\n try {\n const deadline = Date.now() + timeout\n while (Date.now() < deadline) {\n await abortIfNeeded()\n await sleep(interval)\n await abortIfNeeded()\n const result = await pollTask(taskId)\n switch (result.status) {\n case 'QUEUED':\n onProgress?.({ tag: 'queued' })\n break\n case 'RUNNING':\n if (!ws && result.wsUrl) ws = attachProgressWs(result.wsUrl, latest)\n onProgress?.({ tag: 'running', ...latest })\n break\n case 'SUCCESS':\n return result.files\n case 'FAILED':\n // Transient right after creation, before status is queryable — retry.\n if (result.msg === 'APIKEY_TASK_STATUS_ERROR') {\n onProgress?.({ tag: 'queued' })\n break\n }\n throw new Error(result.msg)\n }\n }\n throw new Error(`RunningHub task timed out (${Math.round(timeout / 1000)}s)`)\n } finally {\n closeWs(ws)\n }\n }\n\n // Cancel endpoint accepts { taskId, apiKey } (one task) or { apiKey } (all of\n // this key's tasks). Best-effort: we don't surface the body (often empty).\n async function cancelTask(taskId: string): Promise<void> {\n await doFetch(`${base}/task/openapi/cancel`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ taskId, apiKey }),\n })\n }\n\n async function cancelByApiKey(): Promise<void> {\n await doFetch(`${base}/task/openapi/cancel`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ apiKey }),\n })\n }\n\n return { uploadFile, runApp, pollTask, watchTask, cancelTask, cancelByApiKey }\n}\n","// RunningHub greeting-video AI App config (Wan2.1 I2V 720p with audio sync).\n// One image + one speech audio + a fixed action prompt → MP4 with baked-in\n// audio in a single task. Values pinned here to avoid drift (mirror mate's\n// types/greetingTalkingVideo.ts).\n\nexport const GREETING_APP_ID = '2048355544552968194'\n\nexport const GREETING_NODE_IDS = {\n // Variable per request\n IMAGE: '133',\n AUDIO: '125',\n // Secondary audio slot — always filled with 1s of silence (see silence.ts).\n // Omitting it triggers a RunningHub 803 \"invalid node info\" error.\n AUDIO_SECONDARY: '209',\n POSITIVE_PROMPT: '216',\n // Fixed knobs\n CROP_POSITION: '171',\n SAVE_OUTPUT: '131',\n NEGATIVE_PROMPT: '135',\n LORA: '138',\n MODEL: '122',\n STEPS: '201',\n} as const\n\nexport const GREETING_MODEL = 'Wan2_1-I2V-14B-720p_fp8_e4m3fn_scaled_KJ.safetensors'\nexport const GREETING_LORA = 'Wan21_I2V_14B_lightx2v_cfg_step_distill_lora_rank64.safetensors'\nexport const GREETING_LORA_STRENGTH = 0.8\nexport const GREETING_STEPS = 25\nexport const GREETING_CROP_POSITION = 'center'\n\nexport const GREETING_NEGATIVE_PROMPT =\n 'bright tones, overexposed, static, blurred details, subtitles, style, works, paintings, images, static, overall gray, worst quality, low quality, JPEG compression residue, ugly, incomplete, extra fingers, poorly drawn hands, poorly drawn faces, deformed, disfigured, misshapen limbs, fused fingers, still picture, messy background, three legs, many people in the background, walking backwards'\n\n// Default action prompt (node 216) — \"she looks at the camera, holds the pose,\n// natural blink/breathing, stays still\". Used when the caller passes none.\nexport const GREETING_POSITIVE_PROMPT_DEFAULT =\n '她看着镜头说话,保持姿势,保持表情,自然眨眼和呼吸,保持不动'\n\n/** The 720p I2V app runs on the shared pool — the personal queue returns 803. */\nexport const GREETING_RUN_OPTIONS = { usePersonalQueue: false, instanceType: 'default' } as const\n\n/** TTS model used to synthesize the greeting speech when the caller gives none. */\nexport const GREETING_DEFAULT_TTS_MODEL = 'speech-02-turbo'\n","// Pure: build the node-override list for the greeting AI App from the uploaded\n// file names + prompt. Extracted for fixed-data testing (mirrors mate's\n// buildGreetingTalkingNodeInfoList).\n\nimport type { NodeInfo } from '../client'\nimport {\n GREETING_NODE_IDS as N,\n GREETING_MODEL,\n GREETING_LORA,\n GREETING_LORA_STRENGTH,\n GREETING_STEPS,\n GREETING_CROP_POSITION,\n GREETING_NEGATIVE_PROMPT,\n} from './constants'\n\nexport function buildGreetingNodeInfoList(args: {\n imageFileName: string\n audioFileName: string\n silenceAudioFileName: string\n positivePrompt: string\n}): NodeInfo[] {\n return [\n // Variable per request\n { nodeId: N.IMAGE, fieldName: 'image', fieldValue: args.imageFileName },\n { nodeId: N.AUDIO, fieldName: 'audio', fieldValue: args.audioFileName },\n { nodeId: N.AUDIO_SECONDARY, fieldName: 'audio', fieldValue: args.silenceAudioFileName },\n { nodeId: N.POSITIVE_PROMPT, fieldName: 'text', fieldValue: args.positivePrompt },\n // Fixed knobs\n { nodeId: N.CROP_POSITION, fieldName: 'crop_position', fieldValue: GREETING_CROP_POSITION },\n { nodeId: N.SAVE_OUTPUT, fieldName: 'save_output', fieldValue: 'true' },\n { nodeId: N.NEGATIVE_PROMPT, fieldName: 'negative_prompt', fieldValue: GREETING_NEGATIVE_PROMPT },\n { nodeId: N.LORA, fieldName: 'lora', fieldValue: GREETING_LORA },\n { nodeId: N.LORA, fieldName: 'strength', fieldValue: String(GREETING_LORA_STRENGTH) },\n { nodeId: N.MODEL, fieldName: 'model', fieldValue: GREETING_MODEL },\n { nodeId: N.STEPS, fieldName: 'value', fieldValue: String(GREETING_STEPS) },\n ]\n}\n","// The workflow's secondary audio slot (node 209) is schema-required; we always\n// fill it with 1 second of silence. mate ships a 4 KB silence_1s.mp3 asset — to\n// keep this package asset-free (batteries-included), the same file is embedded\n// here as base64 and decoded to a File at runtime.\n\nconst SILENCE_1S_MP3_BASE64 =\n 'SUQzBAAAAAAAIlRTU0UAAAAOAAADTGF2ZjYxLjcuMTAwAAAAAAAAAAAAAAD/+0DAAAAAAAAAAAAAAAAAAAAAAABJbmZvAAAADwAAACgAABD2ABAQFhYdHR0jIykpKS8vNTU1OztBQUFISE5OTlRUWlpaYGBmZmZsbHJycnl5f39/hYWLi4uRkZeXl52dpKSkqqqwsLC2try8vMLCyMjIzs7V1dXb2+Hh4efn7e3t8/P5+fn//wAAAABMYXZjNjEuMTkAAAAAAAAAAAAAAAAkBXwAAAAAAAAQ9in4b6YAAAAAAP/7EMQAA8AAAaQAAAAgAAA0gAAABExBTUUzLjEwMFVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVMQU1FMy4xMDBVVVVV//sQxCmDwAABpAAAACAAADSAAAAEVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUxBTUUzLjEwMFVVVVX/+xDEUwPAAAGkAAAAIAAANIAAAARVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVTEFNRTMuMTAwVVVVVf/7EMR8g8AAAaQAAAAgAAA0gAAABFVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVMQU1FMy4xMDBVVVVV//sQxKYDwAABpAAAACAAADSAAAAEVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUxBTUUzLjEwMFVVVVX/+xDEz4PAAAGkAAAAIAAANIAAAARVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVTEFNRTMuMTAwVVVVVf/7EMTWA8AAAaQAAAAgAAA0gAAABFVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVMQU1FMy4xMDBVVVVV//sQxNYDwAABpAAAACAAADSAAAAEVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUxBTUUzLjEwMFVVVVX/+xDE1gPAAAGkAAAAIAAANIAAAARVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVTEFNRTMuMTAwVVVVVf/7EMTWA8AAAaQAAAAgAAA0gAAABFVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVMQU1FMy4xMDBVVVVV//sQxNYDwAABpAAAACAAADSAAAAEVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUxBTUUzLjEwMFVVVVX/+xDE1gPAAAGkAAAAIAAANIAAAARVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVTEFNRTMuMTAwVVVVVf/7EMTWA8AAAaQAAAAgAAA0gAAABFVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVMQU1FMy4xMDBVVVVV//sQxNYDwAABpAAAACAAADSAAAAEVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUxBTUUzLjEwMFVVVVX/+xDE1gPAAAGkAAAAIAAANIAAAARVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVTEFNRTMuMTAwVVVVVf/7EMTWA8AAAaQAAAAgAAA0gAAABFVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVMQU1FMy4xMDBVVVVV//sQxNYDwAABpAAAACAAADSAAAAEVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUxBTUUzLjEwMFVVVVX/+xDE1gPAAAGkAAAAIAAANIAAAARVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVTEFNRTMuMTAwVVVVVf/7EMTWA8AAAaQAAAAgAAA0gAAABFVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVMQU1FMy4xMDBVVVVV//sQxNYDwAABpAAAACAAADSAAAAEVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUxBTUUzLjEwMFVVVVX/+xDE1gPAAAGkAAAAIAAANIAAAARVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVTEFNRTMuMTAwVVVVVf/7EMTWA8AAAaQAAAAgAAA0gAAABFVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVMQU1FMy4xMDBVVVVV//sQxNYDwAABpAAAACAAADSAAAAEVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUxBTUUzLjEwMFVVVVX/+xDE1gPAAAGkAAAAIAAANIAAAARVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVTEFNRTMuMTAwVVVVVf/7EMTWA8AAAaQAAAAgAAA0gAAABFVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVMQU1FMy4xMDBVVVVV//sQxNYDwAABpAAAACAAADSAAAAEVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUxBTUUzLjEwMFVVVVX/+xDE1gPAAAGkAAAAIAAANIAAAARVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVTEFNRTMuMTAwVVVVVf/7EMTWA8AAAaQAAAAgAAA0gAAABFVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVMQU1FMy4xMDBVVVVV//sQxNYDwAABpAAAACAAADSAAAAEVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUxBTUUzLjEwMFVVVVX/+xDE1gPAAAGkAAAAIAAANIAAAARVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVTEFNRTMuMTAwVVVVVf/7EMTWA8AAAaQAAAAgAAA0gAAABFVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVMQU1FMy4xMDBVVVVV//sQxNYDwAABpAAAACAAADSAAAAEVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVX/+xDE1gPAAAGkAAAAIAAANIAAAARVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVf/7EMTWA8AAAaQAAAAgAAA0gAAABFVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV//sQxNYDwAABpAAAACAAADSAAAAEVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVX/+xDE1gPAAAGkAAAAIAAANIAAAARVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVf/7EMTWA8AAAaQAAAAgAAA0gAAABFVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV'\n\nlet cached: File | null = null\n\n/** A 1-second silent MP3 as a File (cached for the page lifetime). */\nexport function silenceAudioFile(): File {\n if (cached) return cached\n const bin = atob(SILENCE_1S_MP3_BASE64)\n const bytes = new Uint8Array(bin.length)\n for (let i = 0; i < bin.length; i++) bytes[i] = bin.charCodeAt(i)\n cached = new File([bytes], 'silence_1s.mp3', { type: 'audio/mpeg' })\n return cached\n}\n","// Shared media helpers used by the greeting + waiting bindings.\n\n// ── Avatar → raster image File ──\n// DiceBear (the default avatar provider) returns SVG; Wan2.1 I2V needs a raster\n// image, so SVG is rasterized to PNG via an offscreen canvas.\n\nasync function rasterizeSvg(svgBlob: Blob, size = 512): Promise<Blob> {\n const svgUrl = URL.createObjectURL(svgBlob)\n try {\n const img = new Image()\n img.crossOrigin = 'anonymous'\n img.src = svgUrl\n await new Promise<void>((resolve, reject) => {\n img.onload = () => resolve()\n img.onerror = () => reject(new Error('Failed to load SVG into <img>'))\n })\n const canvas = document.createElement('canvas')\n canvas.width = size\n canvas.height = size\n const ctx = canvas.getContext('2d')\n if (!ctx) throw new Error('Canvas 2D context unavailable')\n ctx.fillStyle = '#ffffff'\n ctx.fillRect(0, 0, size, size)\n ctx.drawImage(img, 0, 0, size, size)\n return await new Promise<Blob>((resolve, reject) => {\n canvas.toBlob(\n (blob) => (blob ? resolve(blob) : reject(new Error('Canvas toBlob returned null'))),\n 'image/png',\n )\n })\n } finally {\n URL.revokeObjectURL(svgUrl)\n }\n}\n\nexport async function fetchAvatarAsImageFile(url: string, filename: string): Promise<File> {\n const res = await fetch(url)\n if (!res.ok) throw new Error(`Failed to fetch avatar: ${res.status}`)\n const blob = await res.blob()\n if (blob.type === 'image/svg+xml' || url.toLowerCase().endsWith('.svg')) {\n const png = await rasterizeSvg(blob)\n return new File([png], `${filename}.png`, { type: 'image/png' })\n }\n if (!blob.type.startsWith('image/')) {\n throw new Error(`Avatar URL did not return an image (got ${blob.type || 'unknown'})`)\n }\n return new File([blob], filename, { type: blob.type })\n}\n\n// ── Silent WAV ──\n// The static-waiting workflow has no length parameter — the result duration is\n// driven by a silent audio track. Build N seconds of 16 kHz mono 16-bit PCM\n// silence as a WAV Blob (mirrors mate's buildSilenceWavBlob).\n\nconst SAMPLE_RATE = 16000\nconst CHANNELS = 1\nconst BITS = 16\n\nexport function buildSilenceWavBlob(seconds: number): Blob {\n const sampleCount = Math.round(seconds * SAMPLE_RATE)\n const blockAlign = (CHANNELS * BITS) / 8\n const byteRate = SAMPLE_RATE * blockAlign\n const dataSize = sampleCount * blockAlign\n const buffer = new ArrayBuffer(44 + dataSize)\n const view = new DataView(buffer)\n const writeStr = (offset: number, s: string) => {\n for (let i = 0; i < s.length; i++) view.setUint8(offset + i, s.charCodeAt(i))\n }\n writeStr(0, 'RIFF')\n view.setUint32(4, 36 + dataSize, true)\n writeStr(8, 'WAVE')\n writeStr(12, 'fmt ')\n view.setUint32(16, 16, true) // PCM fmt chunk size\n view.setUint16(20, 1, true) // PCM\n view.setUint16(22, CHANNELS, true)\n view.setUint32(24, SAMPLE_RATE, true)\n view.setUint32(28, byteRate, true)\n view.setUint16(32, blockAlign, true)\n view.setUint16(34, BITS, true)\n writeStr(36, 'data')\n view.setUint32(40, dataSize, true)\n // PCM samples left zero-filled = silence.\n return new Blob([buffer], { type: 'audio/wav' })\n}\n","// Shared RunningHub image-to-video pipeline. The greeting and waiting bindings\n// differ ONLY in which files they prepare and how they map the uploaded names to\n// nodes; everything else — upload-all → runApp → watchTask → pick the video URL,\n// plus the progress wording — lives here once.\n\nimport type { NodeInfo, RunAppOptions, RunningHubClient } from '../client'\n\nexport type VideoUpload = {\n /** Key the buildNodes callback uses to reference this file's uploaded name. */\n readonly key: string\n readonly file: File\n readonly fileType: 'image' | 'audio'\n}\n\nexport type RunVideoAppArgs = {\n readonly client: RunningHubClient\n readonly appId: string\n readonly runOptions?: RunAppOptions | undefined\n /** Files to upload before submitting; their RunningHub names feed buildNodes. */\n readonly uploads: VideoUpload[]\n /** Build the node overrides from the uploaded file names (keyed by upload.key). */\n readonly buildNodes: (fileNames: Record<string, string>) => NodeInfo[]\n /** Surface a human progress message (上传素材中… / 提交任务中… / 排队中… / 生成中… N%). */\n readonly onMessage?: ((message: string) => void) | undefined\n}\n\nexport async function runVideoApp(args: RunVideoAppArgs): Promise<string> {\n args.onMessage?.('上传素材中…')\n const entries = await Promise.all(\n args.uploads.map(async (u) => [u.key, await args.client.uploadFile(u.file, u.fileType)] as const),\n )\n const fileNames: Record<string, string> = Object.fromEntries(entries)\n\n args.onMessage?.('提交任务中…')\n const taskId = await args.client.runApp(args.appId, args.buildNodes(fileNames), args.runOptions)\n\n const files = await args.client.watchTask(taskId, (p) => {\n if (p.tag === 'queued') args.onMessage?.('排队中…')\n else args.onMessage?.(p.percent != null ? `生成中… ${p.percent}%` : '生成中…')\n })\n\n const url = files.find((f) => /\\.(mp4|webm|mov)$/i.test(f.fileUrl))?.fileUrl ?? files[0]?.fileUrl\n if (!url) throw new Error('RunningHub task succeeded but returned no video URL')\n return url\n}\n","// RunningHub implementation of greeting's GreetingVideoPort.\n// Prepares the two greeting-specific inputs — TTS audio (via the injected\n// VoiceSynthesisPort) and the avatar image — then hands the upload→run→watch\n// pipeline to the shared runVideoApp. The schema-required secondary audio slot\n// (node 209) is filled with the embedded 1s silence.\n//\n// Returns the RunningHub CDN URL (~24h expiry) — persist via greeting's\n// GreetingVideoStoragePort / a storage mirror for anything long-lived.\n\nimport type {\n GreetingVideoPort,\n GreetingVideoInput,\n GreetingVideoProgress,\n} from '@surfmate.team/digital-human-greeting'\nimport type { VoiceSynthesisPort } from '@surfmate.team/digital-human-voice'\nimport type { RunningHubClient } from '../client'\nimport { runVideoApp } from '../shared/runVideoApp'\nimport { fetchAvatarAsImageFile } from '../shared/media'\nimport {\n GREETING_APP_ID,\n GREETING_RUN_OPTIONS,\n GREETING_POSITIVE_PROMPT_DEFAULT,\n GREETING_DEFAULT_TTS_MODEL,\n} from './constants'\nimport { buildGreetingNodeInfoList } from './buildNodeInfoList'\nimport { silenceAudioFile } from './silence'\n\nexport type RunningHubGreetingVideoConfig = {\n /** Generic RunningHub client (createRunningHubClient). */\n readonly client: RunningHubClient\n /** TTS backend that turns the greeting text into audio bytes (e.g. MiniMax). */\n readonly synth: VoiceSynthesisPort\n /** TTS model id. Defaults to MiniMax speech-02-turbo. */\n readonly modelId?: string\n /** Action prompt for node 216. Defaults to the \"looks at camera, holds pose\" prompt. */\n readonly positivePrompt?: string\n}\n\n// Decode the speech audio's real length (seconds). Browser-guarded; resolves to\n// undefined where Web Audio is unavailable (headless) or decoding fails.\nasync function decodeDurationSec(bytes: ArrayBuffer): Promise<number | undefined> {\n const g = globalThis as unknown as {\n AudioContext?: typeof AudioContext\n webkitAudioContext?: typeof AudioContext\n }\n const Ctx = g.AudioContext ?? g.webkitAudioContext\n if (!Ctx) return undefined\n const ctx = new Ctx()\n try {\n return (await ctx.decodeAudioData(bytes.slice(0))).duration\n } catch {\n return undefined\n } finally {\n void ctx.close()\n }\n}\n\nexport function createRunningHubGreetingVideo(\n config: RunningHubGreetingVideoConfig,\n): GreetingVideoPort {\n const { client, synth } = config\n const modelId = config.modelId ?? GREETING_DEFAULT_TTS_MODEL\n const defaultPrompt =\n (config.positivePrompt && config.positivePrompt.trim()) || GREETING_POSITIVE_PROMPT_DEFAULT\n\n return {\n async generate(\n input: GreetingVideoInput,\n onProgress?: (p: GreetingVideoProgress) => void,\n ): Promise<string> {\n const msg = (message: string) => onProgress?.({ message })\n const positivePrompt = input.positivePrompt?.trim() || defaultPrompt\n\n // Audio: a pre-recorded URL if given, else synthesize from text + voice.\n msg('合成语音中…')\n const audioBytes = input.audioUrl\n ? await (await fetch(input.audioUrl)).arrayBuffer()\n : await synth.synthesize({\n text: input.text,\n voiceId: input.voiceId,\n modelId,\n ...(input.emotion ? { emotion: input.emotion } : {}),\n })\n const audioFile = new File([audioBytes], 'greeting-speech.mp3', { type: 'audio/mpeg' })\n\n // Surface the REAL decoded spoken length (callers persist it for length-matching).\n if (input.onAudioDuration) {\n const sec = await decodeDurationSec(audioBytes)\n if (sec != null) input.onAudioDuration(sec)\n }\n\n msg('获取头像中…')\n const imageFile = await fetchAvatarAsImageFile(input.avatarUrl, 'greeting-avatar')\n\n return runVideoApp({\n client,\n appId: GREETING_APP_ID,\n runOptions: GREETING_RUN_OPTIONS,\n uploads: [\n { key: 'image', file: imageFile, fileType: 'image' },\n { key: 'audio', file: audioFile, fileType: 'audio' },\n { key: 'silence', file: silenceAudioFile(), fileType: 'audio' },\n ],\n buildNodes: (n) =>\n buildGreetingNodeInfoList({\n imageFileName: n.image ?? '',\n audioFileName: n.audio ?? '',\n silenceAudioFileName: n.silence ?? '',\n positivePrompt,\n }),\n onMessage: msg,\n })\n },\n }\n}\n","// RunningHub implementation of waiting's WaitingVideoGenerationPort (\"Generate\n// Static\"). Prepares the two waiting-specific inputs — the avatar image and N\n// seconds of silence (which drive the clip length, no TTS) — then hands the\n// upload→run→watch pipeline to the shared runVideoApp.\n//\n// Returns the RunningHub CDN URL (~24h expiry) — persist via the waiting storage\n// port / a mirror for anything long-lived.\n\nimport type {\n WaitingVideoGenerationPort,\n WaitingVideoGenerationInput,\n WaitingVideoProgress,\n} from '@surfmate.team/digital-human-waiting'\nimport type { NodeInfo, RunningHubClient } from '../client'\nimport { runVideoApp } from '../shared/runVideoApp'\nimport { fetchAvatarAsImageFile, buildSilenceWavBlob } from '../shared/media'\n\n// mate's static-waiting AI App + node ids.\nconst STATIC_WAITING_APP_ID = '2050552043860963330'\nconst NODE_IDS = { IMAGE: '34', PROMPT: '17', AUDIO: '38' } as const\nconst RUN_OPTIONS = { usePersonalQueue: false, instanceType: 'default' } as const\n\nexport type RunningHubStaticWaitingVideoConfig = {\n readonly client: RunningHubClient\n}\n\nexport function createRunningHubStaticWaitingVideo(\n config: RunningHubStaticWaitingVideoConfig,\n): WaitingVideoGenerationPort {\n const { client } = config\n return {\n async generateStatic(\n input: WaitingVideoGenerationInput,\n onProgress?: (p: WaitingVideoProgress) => void,\n ): Promise<string> {\n const msg = (message: string) => onProgress?.({ message })\n\n // Waiting-specific inputs: the avatar image + N seconds of silence.\n msg('获取头像中…')\n const imageFile = await fetchAvatarAsImageFile(input.avatarUrl, `${input.characterId}-avatar`)\n const silence = buildSilenceWavBlob(input.seconds)\n const audioFile = new File([silence], `silence-${input.seconds}s.wav`, { type: 'audio/wav' })\n\n return runVideoApp({\n client,\n appId: STATIC_WAITING_APP_ID,\n runOptions: RUN_OPTIONS,\n uploads: [\n { key: 'image', file: imageFile, fileType: 'image' },\n { key: 'audio', file: audioFile, fileType: 'audio' },\n ],\n buildNodes: (n): NodeInfo[] => [\n { nodeId: NODE_IDS.IMAGE, fieldName: 'image', fieldValue: n.image ?? '' },\n { nodeId: NODE_IDS.PROMPT, fieldName: 'prompt', fieldValue: input.prompt },\n { nodeId: NODE_IDS.AUDIO, fieldName: 'audio', fieldValue: n.audio ?? '' },\n ],\n onMessage: msg,\n })\n },\n }\n}\n","// RunningHub 多人对话 (multi-person lip-sync dialogue) AI App config.\n// InfiniteTalk dual-image driving: one reference image holding two people +\n// three audio tracks → one MP4 where each person lip-syncs to their own voice.\n//\n// The two per-person tracks drive the MOUTHS (each audio only animates its\n// person's face, assigned by the workflow's auto/painted masks); the full\n// conversation track is the OUTPUT soundtrack and sets the total length.\n// (See docs/workflows.md — node ids match the published app, App 2062039217370394626.)\n\nexport const DIALOGUE_APP_ID = '2062039217370394626'\n\nexport const DIALOGUE_NODE_IDS = {\n /** Reference image (the two people). */\n IMAGE: '57',\n /** audio_1 — isolated track that drives the LEFT person's mouth. */\n AUDIO_LEFT: '64',\n /** audio_2 — isolated track that drives the RIGHT person's mouth. */\n AUDIO_RIGHT: '59',\n /** Full conversation — the output soundtrack + drives total video length. */\n AUDIO_FULL: '58',\n /** Action / camera prompt (e.g. 固定镜头). Empty by default. */\n PROMPT: '86',\n} as const\n\n/** Like the other I2V apps, this runs on the shared pool — personal queue → 803. */\nexport const DIALOGUE_RUN_OPTIONS = { usePersonalQueue: false, instanceType: 'default' } as const\n","// RunningHub 多人对话 binding: a two-person lip-sync dialogue video. Self-\n// contained (no feature-package port yet) — it owns its input/port types and\n// composes the generic client via the shared runVideoApp pipeline.\n//\n// The caller prepares three time-aligned audio tracks (e.g. via Web Audio):\n// - leftAudio : only the LEFT person's voice (silence elsewhere) → audio_1\n// - rightAudio : only the RIGHT person's voice (silence elsewhere) → audio_2\n// - fullAudio : the complete conversation (both, mixed) → soundtrack + length\n// All three MUST be the same length and aligned on one timeline.\n//\n// Returns the RunningHub CDN URL (~24h expiry) — persist/mirror anything you\n// need long-lived.\n\nimport type { NodeInfo, RunningHubClient } from '../client'\nimport { runVideoApp } from '../shared/runVideoApp'\nimport { DIALOGUE_APP_ID, DIALOGUE_NODE_IDS, DIALOGUE_RUN_OPTIONS } from './constants'\n\nexport type DialogueVideoInput = {\n /** Reference image containing the two people. */\n readonly image: File\n /** Isolated audio for the LEFT person (drives audio_1). */\n readonly leftAudio: File\n /** Isolated audio for the RIGHT person (drives audio_2). */\n readonly rightAudio: File\n /** The full conversation track — output soundtrack + total length. */\n readonly fullAudio: File\n /** Optional action / camera prompt (e.g. 固定镜头). Omit → empty. */\n readonly prompt?: string | undefined\n}\n\nexport type DialogueVideoProgress = { readonly message: string }\n\nexport interface DialogueVideoPort {\n /** Generate the dialogue video; resolves to the output video URL (~24h CDN). */\n generate(\n input: DialogueVideoInput,\n onProgress?: (p: DialogueVideoProgress) => void,\n ): Promise<string>\n}\n\nexport type RunningHubDialogueVideoConfig = {\n readonly client: RunningHubClient\n}\n\nexport function createRunningHubDialogueVideo(\n config: RunningHubDialogueVideoConfig,\n): DialogueVideoPort {\n const { client } = config\n return {\n async generate(\n input: DialogueVideoInput,\n onProgress?: (p: DialogueVideoProgress) => void,\n ): Promise<string> {\n const msg = (message: string) => onProgress?.({ message })\n return runVideoApp({\n client,\n appId: DIALOGUE_APP_ID,\n runOptions: DIALOGUE_RUN_OPTIONS,\n uploads: [\n { key: 'image', file: input.image, fileType: 'image' },\n { key: 'left', file: input.leftAudio, fileType: 'audio' },\n { key: 'right', file: input.rightAudio, fileType: 'audio' },\n { key: 'full', file: input.fullAudio, fileType: 'audio' },\n ],\n buildNodes: (n): NodeInfo[] => [\n { nodeId: DIALOGUE_NODE_IDS.IMAGE, fieldName: 'image', fieldValue: n.image ?? '' },\n { nodeId: DIALOGUE_NODE_IDS.AUDIO_LEFT, fieldName: 'audio', fieldValue: n.left ?? '' },\n { nodeId: DIALOGUE_NODE_IDS.AUDIO_RIGHT, fieldName: 'audio', fieldValue: n.right ?? '' },\n { nodeId: DIALOGUE_NODE_IDS.AUDIO_FULL, fieldName: 'audio', fieldValue: n.full ?? '' },\n { nodeId: DIALOGUE_NODE_IDS.PROMPT, fieldName: 'text', fieldValue: input.prompt ?? '' },\n ],\n onMessage: msg,\n })\n },\n }\n}\n","// Shared text-to-image runner. Every RunningHub text-to-image app has the same\n// shape — prompt + width + height + batch → image URL(s), no uploads — and\n// differs ONLY in its app id, the four node ids, and the default dimensions.\n// Each model file (qwen2512, flux2klein, …) supplies that spec; this builds the\n// TextToImagePort once.\n\nimport type { NodeInfo, RunAppOptions, RunningHubClient } from '../client'\nimport type { TextToImageInput, TextToImagePort, TextToImageProgress } from './types'\n\nconst IMAGE_RE = /\\.(png|jpe?g|webp)$/i\n\n/** Per-model spec: the app id, its four node ids, and default dimensions. */\nexport type Text2ImageSpec = {\n readonly appId: string\n readonly nodeIds: {\n readonly prompt: string\n readonly width: string\n readonly height: string\n readonly batch: string\n }\n readonly defaults: { readonly width: number; readonly height: number; readonly batchSize: number }\n /** Defaults to the shared-pool options (personal queue → 803 on most apps). */\n readonly runOptions?: RunAppOptions\n}\n\nconst SHARED_POOL: RunAppOptions = { usePersonalQueue: false, instanceType: 'default' }\n\nexport function createText2ImagePort(client: RunningHubClient, spec: Text2ImageSpec): TextToImagePort {\n return {\n async generate(\n input: TextToImageInput,\n onProgress?: (p: TextToImageProgress) => void,\n ): Promise<string[]> {\n const msg = (message: string) => onProgress?.({ message })\n const width = input.width ?? spec.defaults.width\n const height = input.height ?? spec.defaults.height\n const batchSize = input.batchSize ?? spec.defaults.batchSize\n\n const nodes: NodeInfo[] = [\n { nodeId: spec.nodeIds.prompt, fieldName: 'text', fieldValue: input.prompt },\n { nodeId: spec.nodeIds.width, fieldName: 'value', fieldValue: String(width) },\n { nodeId: spec.nodeIds.height, fieldName: 'value', fieldValue: String(height) },\n { nodeId: spec.nodeIds.batch, fieldName: 'value', fieldValue: String(batchSize) },\n ]\n\n msg('提交任务中…')\n const taskId = await client.runApp(spec.appId, nodes, spec.runOptions ?? SHARED_POOL)\n\n const files = await client.watchTask(taskId, (p) => {\n if (p.tag === 'queued') msg('排队中…')\n else msg(p.percent != null ? `生成中… ${p.percent}%` : '生成中…')\n })\n\n const images = files.filter((f) => IMAGE_RE.test(f.fileUrl)).map((f) => f.fileUrl)\n const urls = images.length > 0 ? images : files.map((f) => f.fileUrl)\n if (urls.length === 0) throw new Error('RunningHub task succeeded but returned no image URL')\n return urls\n },\n }\n}\n","// RunningHub 文生图 — Qwen-Image 2512 binding. Just the per-model spec; the\n// shared runner builds the TextToImagePort. (Published app 2052988750950617090.)\n\nimport type { RunningHubClient } from '../client'\nimport type { TextToImagePort } from './types'\nimport { createText2ImagePort, type Text2ImageSpec } from './run'\n\nexport const QWEN2512_SPEC: Text2ImageSpec = {\n appId: '2052988750950617090',\n // node 6 prompt · 80 width · 81 height · 117 batch size\n nodeIds: { prompt: '6', width: '80', height: '81', batch: '117' },\n defaults: { width: 1920, height: 1080, batchSize: 1 },\n}\n\nexport type RunningHubQwen2512Config = {\n readonly client: RunningHubClient\n}\n\nexport function createRunningHubQwen2512(config: RunningHubQwen2512Config): TextToImagePort {\n return createText2ImagePort(config.client, QWEN2512_SPEC)\n}\n","// RunningHub 文生图 — Flux2 Klein binding. Same shape as Qwen2512, different\n// app + node ids. (Published app 2026466502756536322.)\n\nimport type { RunningHubClient } from '../client'\nimport type { TextToImagePort } from './types'\nimport { createText2ImagePort, type Text2ImageSpec } from './run'\n\nexport const FLUX2_KLEIN_SPEC: Text2ImageSpec = {\n appId: '2026466502756536322',\n // node 64 prompt · 99 width · 101 height · 103 batch size\n nodeIds: { prompt: '64', width: '99', height: '101', batch: '103' },\n defaults: { width: 1920, height: 1088, batchSize: 1 },\n}\n\nexport type RunningHubFlux2KleinConfig = {\n readonly client: RunningHubClient\n}\n\nexport function createRunningHubFlux2Klein(config: RunningHubFlux2KleinConfig): TextToImagePort {\n return createText2ImagePort(config.client, FLUX2_KLEIN_SPEC)\n}\n","// Shared image-edit runner. Every RunningHub image-edit app has the same shape:\n// upload 1–N source images → fill the prompt node + the image nodes in order →\n// run → watch → image URL(s). Each model file supplies the spec; this builds\n// the ImageEditPort once.\n\nimport type { NodeInfo, RunAppOptions, RunningHubClient } from '../client'\nimport type { ImageEditInput, ImageEditPort, ImageEditProgress } from './types'\n\nconst IMAGE_RE = /\\.(png|jpe?g|webp)$/i\nconst SHARED_POOL: RunAppOptions = { usePersonalQueue: false, instanceType: 'default' }\n\n/** Per-model spec: the app id, the prompt node, and the image nodes in fill order. */\nexport type ImageEditSpec = {\n readonly appId: string\n readonly promptNodeId: string\n /** Image node ids in fill order (primary first). The Nth image → imageNodeIds[N-1]. */\n readonly imageNodeIds: readonly string[]\n 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\n msg('提交任务中…')\n const taskId = await client.runApp(spec.appId, nodes, spec.runOptions ?? SHARED_POOL)\n\n const files = await client.watchTask(taskId, (p) => {\n if (p.tag === 'queued') msg('排队中…')\n else msg(p.percent != null ? `生成中… ${p.percent}%` : '生成中…')\n })\n\n const out = files.filter((f) => IMAGE_RE.test(f.fileUrl)).map((f) => f.fileUrl)\n const urls = out.length > 0 ? out : files.map((f) => f.fileUrl)\n if (urls.length === 0) throw new Error('RunningHub task succeeded but returned no image URL')\n return urls\n },\n }\n}\n","// RunningHub 图像编辑 — Qwen-Image 2511 binding. Up to 3 source images + a\n// prompt. (Published app 2025738520135995394.)\n\nimport type { RunningHubClient } from '../client'\nimport type { ImageEditPort } from './types'\nimport { createImageEditPort, type ImageEditSpec } from './run'\n\nexport const QWEN2511_SPEC: ImageEditSpec = {\n appId: '2025738520135995394',\n promptNodeId: '182',\n // primary first: 1 img → 157; 2 → 157,180; 3 → 157,180,181\n imageNodeIds: ['157', '180', '181'],\n}\n\nexport type RunningHubQwen2511Config = {\n readonly client: RunningHubClient\n}\n\nexport function createRunningHubQwen2511(config: RunningHubQwen2511Config): ImageEditPort {\n return createImageEditPort(config.client, QWEN2511_SPEC)\n}\n","// RunningHub 图像编辑 — Flux image-edit binding. Two images (original + a\n// reference) + a Chinese instruction → an edited image. (Published app\n// 2052941353117597697.)\n\nimport type { RunningHubClient } from '../client'\nimport type { ImageEditPort } from './types'\nimport { createImageEditPort, type ImageEditSpec } from './run'\n\nexport const FLUX_EDIT_SPEC: ImageEditSpec = {\n appId: '2052941353117597697',\n promptNodeId: '178',\n // primary (original) → 175; the reference image → 159\n imageNodeIds: ['175', '159'],\n}\n\nexport type RunningHubFluxEditConfig = {\n readonly client: RunningHubClient\n}\n\nexport function createRunningHubFluxEdit(config: RunningHubFluxEditConfig): ImageEditPort {\n return createImageEditPort(config.client, FLUX_EDIT_SPEC)\n}\n"],"mappings":";AAMO,SAAS,kBAAkB,MAA+B;AAC/D,QAAM,MAAM;AAGZ,MAAI,IAAI,SAAS,QAAW;AAC1B,QAAI,IAAI,SAAS,OAAO,IAAI,QAAQ,0BAA0B;AAC5D,YAAMA,SAAQ,IAAI;AAClB,YAAM,QAAQ,OAAOA,QAAO,cAAc,WAAYA,OAAM,YAAuB;AACnF,aAAO,QAAQ,EAAE,QAAQ,WAAW,MAAM,IAAI,EAAE,QAAQ,UAAU;AAAA,IACpE;AACA,QAAI,IAAI,SAAS,GAAG;AAClB,aAAO,EAAE,QAAQ,UAAU,KAAK,OAAO,IAAI,OAAO,eAAe,EAAE;AAAA,IACrE;AACA,UAAM,UAAU,IAAI;AACpB,QAAI,MAAM,QAAQ,OAAO,GAAG;AAC1B,aAAO,EAAE,QAAQ,WAAW,OAAO,QAA4B;AAAA,IACjE;AACA,UAAM,QAAQ;AACd,UAAMC,cAAa,OAAO;AAC1B,QAAIA,gBAAe,UAAW,QAAO,EAAE,QAAQ,UAAU;AACzD,QAAIA,gBAAe,SAAU,QAAO,EAAE,QAAQ,SAAS;AACvD,QAAIA,gBAAe,aAAaA,gBAAe,WAAW;AACxD,YAAM,UAAU,OAAO;AACvB,UAAI,QAAS,QAAO,EAAE,QAAQ,WAAW,OAAO,CAAC,EAAE,SAAS,UAAU,QAAQ,CAAC,EAAE;AACjF,aAAO,EAAE,QAAQ,WAAW,OAAO,CAAC,EAAE;AAAA,IACxC;AACA,QAAIA,gBAAe,UAAU;AAC3B,aAAO,EAAE,QAAQ,UAAU,KAAK,OAAO,OAAO,aAAa,aAAa,EAAE;AAAA,IAC5E;AACA,WAAO,EAAE,QAAQ,UAAU,KAAK,0BAA0BA,WAAU,GAAG;AAAA,EACzE;AAGA,QAAM,aAAa,IAAI;AACvB,MAAI,eAAe,UAAW,QAAO,EAAE,QAAQ,UAAU;AACzD,MAAI,eAAe,SAAU,QAAO,EAAE,QAAQ,SAAS;AACvD,MAAI,eAAe,aAAa,eAAe,WAAW;AACxD,UAAM,UAAU,IAAI;AACpB,UAAM,UAAU,IAAI;AACpB,QAAI,MAAM,QAAQ,OAAO,KAAK,QAAQ,SAAS,GAAG;AAChD,aAAO,EAAE,QAAQ,WAAW,OAAO,QAAQ;AAAA,IAC7C;AACA,QAAI,QAAS,QAAO,EAAE,QAAQ,WAAW,OAAO,CAAC,EAAE,SAAS,UAAU,QAAQ,CAAC,EAAE;AACjF,WAAO,EAAE,QAAQ,WAAW,OAAO,CAAC,EAAE;AAAA,EACxC;AACA,MAAI,eAAe,UAAU;AAC3B,WAAO,EAAE,QAAQ,UAAU,KAAK,OAAO,IAAI,gBAAgB,IAAI,aAAa,aAAa,EAAE;AAAA,EAC7F;AACA,SAAO,EAAE,QAAQ,UAAU,KAAK,wBAAwB,KAAK,UAAU,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC,GAAG;AAC9F;;;ACtCA,IAAM,eAAe;AACrB,IAAM,2BAA2B;AAEjC,IAAM,qBAAqB,KAAK,KAAK;AAErC,IAAM,QAAQ,CAAC,OAA8B,IAAI,QAAQ,CAAC,MAAM,WAAW,GAAG,EAAE,CAAC;AAajF,SAAS,iBACP,KACA,QACkB;AAClB,MAAI;AACF,UAAM,KAAK,IAAI,UAAU,GAAG;AAC5B,OAAG,YAAY,CAAC,UAAU;AACxB,UAAI,OAAO,MAAM,SAAS,SAAU;AACpC,UAAI;AACF,cAAM,MAAM,KAAK,MAAM,MAAM,IAAI;AACjC,YAAI,IAAI,SAAS,cAAc,CAAC,IAAI,KAAM;AAC1C,cAAM,QAAQ,IAAI,KAAK;AACvB,cAAM,MAAM,IAAI,KAAK;AACrB,YAAI,OAAO,UAAU,YAAY,OAAO,QAAQ,YAAY,MAAM,GAAG;AACnE,iBAAO,OAAO;AACd,iBAAO,QAAQ;AACf,iBAAO,UAAU,KAAK,MAAO,QAAQ,MAAO,GAAG;AAAA,QACjD;AAAA,MACF,QAAQ;AAAA,MAER;AAAA,IACF;AACA,OAAG,UAAU,MAAM;AAAA,IAEnB;AACA,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,SAAS,QAAQ,IAA4B;AAC3C,MAAI,CAAC,GAAI;AACT,MAAI,GAAG,eAAe,UAAU,QAAQ,GAAG,eAAe,UAAU,WAAY,IAAG,MAAM;AAC3F;AAEO,SAAS,uBAAuB,QAA4C;AACjF,QAAM,OAAO,OAAO,WAAW;AAC/B,QAAM,SAAS,OAAO;AACtB,QAAM,UAAU,OAAO,aAAa;AAEpC,iBAAe,WAAW,MAAY,UAA8C;AAClF,UAAM,OAAO,IAAI,SAAS;AAC1B,SAAK,OAAO,QAAQ,IAAI;AACxB,SAAK,OAAO,UAAU,MAAM;AAC5B,SAAK,OAAO,YAAY,QAAQ;AAChC,UAAM,MAAM,MAAM,QAAQ,GAAG,IAAI,wBAAwB,EAAE,QAAQ,QAAQ,MAAM,KAAK,CAAC;AACvF,UAAM,OAAQ,MAAM,IAAI,KAAK;AAC7B,QAAI,KAAK,SAAS,EAAG,OAAM,IAAI,MAAM,KAAK,OAAO,0BAA0B;AAC3E,WAAO,KAAK,KAAK;AAAA,EACnB;AAEA,iBAAe,OACb,OACA,cACA,UAAyB,CAAC,GACT;AACjB,UAAM,OAAgC;AAAA,MACpC;AAAA,MACA,kBAAkB,QAAQ,oBAAoB;AAAA,IAChD;AACA,QAAI,QAAQ,aAAc,MAAK,eAAe,QAAQ;AACtD,UAAM,MAAM,MAAM,QAAQ,GAAG,IAAI,0BAA0B,KAAK,IAAI;AAAA,MAClE,QAAQ;AAAA,MACR,SAAS,EAAE,gBAAgB,oBAAoB,eAAe,UAAU,MAAM,GAAG;AAAA,MACjF,MAAM,KAAK,UAAU,IAAI;AAAA,IAC3B,CAAC;AACD,UAAM,OAAQ,MAAM,IAAI,KAAK;AAC7B,QAAI,KAAK,WAAW;AAClB,YAAM,SAAS,KAAK,eAAe,IAAI,KAAK,YAAY,KAAK;AAC7D,YAAM,IAAI,MAAM,cAAc,KAAK,SAAS,GAAG,MAAM,EAAE;AAAA,IACzD;AACA,WAAO,KAAK;AAAA,EACd;AAEA,iBAAe,SAAS,QAAyC;AAC/D,UAAM,MAAM,MAAM,QAAQ,GAAG,IAAI,yBAAyB;AAAA,MACxD,QAAQ;AAAA,MACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,MAC9C,MAAM,KAAK,UAAU,EAAE,QAAQ,OAAO,CAAC;AAAA,IACzC,CAAC;AACD,WAAO,kBAAkB,MAAM,IAAI,KAAK,CAAC;AAAA,EAC3C;AAEA,iBAAe,UACb,QACA,YACA,UAAwB,CAAC,GACE;AAC3B,UAAM,WAAW,QAAQ,kBAAkB;AAC3C,UAAM,UAAU,QAAQ,aAAa;AACrC,UAAM,SAAS,QAAQ;AACvB,QAAI,KAAuB;AAC3B,UAAM,SAA8D,CAAC;AACrE,UAAM,gBAAgB,YAA2B;AAC/C,UAAI,CAAC,QAAQ,QAAS;AACtB,YAAM,WAAW,MAAM,EAAE,MAAM,MAAM;AAAA,MAAC,CAAC;AACvC,YAAM,IAAI,MAAM,SAAS;AAAA,IAC3B;AACA,QAAI;AACF,YAAM,WAAW,KAAK,IAAI,IAAI;AAC9B,aAAO,KAAK,IAAI,IAAI,UAAU;AAC5B,cAAM,cAAc;AACpB,cAAM,MAAM,QAAQ;AACpB,cAAM,cAAc;AACpB,cAAM,SAAS,MAAM,SAAS,MAAM;AACpC,gBAAQ,OAAO,QAAQ;AAAA,UACrB,KAAK;AACH,yBAAa,EAAE,KAAK,SAAS,CAAC;AAC9B;AAAA,UACF,KAAK;AACH,gBAAI,CAAC,MAAM,OAAO,MAAO,MAAK,iBAAiB,OAAO,OAAO,MAAM;AACnE,yBAAa,EAAE,KAAK,WAAW,GAAG,OAAO,CAAC;AAC1C;AAAA,UACF,KAAK;AACH,mBAAO,OAAO;AAAA,UAChB,KAAK;AAEH,gBAAI,OAAO,QAAQ,4BAA4B;AAC7C,2BAAa,EAAE,KAAK,SAAS,CAAC;AAC9B;AAAA,YACF;AACA,kBAAM,IAAI,MAAM,OAAO,GAAG;AAAA,QAC9B;AAAA,MACF;AACA,YAAM,IAAI,MAAM,8BAA8B,KAAK,MAAM,UAAU,GAAI,CAAC,IAAI;AAAA,IAC9E,UAAE;AACA,cAAQ,EAAE;AAAA,IACZ;AAAA,EACF;AAIA,iBAAe,WAAW,QAA+B;AACvD,UAAM,QAAQ,GAAG,IAAI,wBAAwB;AAAA,MAC3C,QAAQ;AAAA,MACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,MAC9C,MAAM,KAAK,UAAU,EAAE,QAAQ,OAAO,CAAC;AAAA,IACzC,CAAC;AAAA,EACH;AAEA,iBAAe,iBAAgC;AAC7C,UAAM,QAAQ,GAAG,IAAI,wBAAwB;AAAA,MAC3C,QAAQ;AAAA,MACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,MAC9C,MAAM,KAAK,UAAU,EAAE,OAAO,CAAC;AAAA,IACjC,CAAC;AAAA,EACH;AAEA,SAAO,EAAE,YAAY,QAAQ,UAAU,WAAW,YAAY,eAAe;AAC/E;;;ACpLO,IAAM,kBAAkB;AAExB,IAAM,oBAAoB;AAAA;AAAA,EAE/B,OAAO;AAAA,EACP,OAAO;AAAA;AAAA;AAAA,EAGP,iBAAiB;AAAA,EACjB,iBAAiB;AAAA;AAAA,EAEjB,eAAe;AAAA,EACf,aAAa;AAAA,EACb,iBAAiB;AAAA,EACjB,MAAM;AAAA,EACN,OAAO;AAAA,EACP,OAAO;AACT;AAEO,IAAM,iBAAiB;AACvB,IAAM,gBAAgB;AACtB,IAAM,yBAAyB;AAC/B,IAAM,iBAAiB;AACvB,IAAM,yBAAyB;AAE/B,IAAM,2BACX;AAIK,IAAM,mCACX;AAGK,IAAM,uBAAuB,EAAE,kBAAkB,OAAO,cAAc,UAAU;AAGhF,IAAM,6BAA6B;;;AC3BnC,SAAS,0BAA0B,MAK3B;AACb,SAAO;AAAA;AAAA,IAEL,EAAE,QAAQ,kBAAE,OAAO,WAAW,SAAS,YAAY,KAAK,cAAc;AAAA,IACtE,EAAE,QAAQ,kBAAE,OAAO,WAAW,SAAS,YAAY,KAAK,cAAc;AAAA,IACtE,EAAE,QAAQ,kBAAE,iBAAiB,WAAW,SAAS,YAAY,KAAK,qBAAqB;AAAA,IACvF,EAAE,QAAQ,kBAAE,iBAAiB,WAAW,QAAQ,YAAY,KAAK,eAAe;AAAA;AAAA,IAEhF,EAAE,QAAQ,kBAAE,eAAe,WAAW,iBAAiB,YAAY,uBAAuB;AAAA,IAC1F,EAAE,QAAQ,kBAAE,aAAa,WAAW,eAAe,YAAY,OAAO;AAAA,IACtE,EAAE,QAAQ,kBAAE,iBAAiB,WAAW,mBAAmB,YAAY,yBAAyB;AAAA,IAChG,EAAE,QAAQ,kBAAE,MAAM,WAAW,QAAQ,YAAY,cAAc;AAAA,IAC/D,EAAE,QAAQ,kBAAE,MAAM,WAAW,YAAY,YAAY,OAAO,sBAAsB,EAAE;AAAA,IACpF,EAAE,QAAQ,kBAAE,OAAO,WAAW,SAAS,YAAY,eAAe;AAAA,IAClE,EAAE,QAAQ,kBAAE,OAAO,WAAW,SAAS,YAAY,OAAO,cAAc,EAAE;AAAA,EAC5E;AACF;;;AC/BA,IAAM,wBACJ;AAEF,IAAI,SAAsB;AAGnB,SAAS,mBAAyB;AACvC,MAAI,OAAQ,QAAO;AACnB,QAAM,MAAM,KAAK,qBAAqB;AACtC,QAAM,QAAQ,IAAI,WAAW,IAAI,MAAM;AACvC,WAAS,IAAI,GAAG,IAAI,IAAI,QAAQ,IAAK,OAAM,CAAC,IAAI,IAAI,WAAW,CAAC;AAChE,WAAS,IAAI,KAAK,CAAC,KAAK,GAAG,kBAAkB,EAAE,MAAM,aAAa,CAAC;AACnE,SAAO;AACT;;;ACZA,eAAe,aAAa,SAAe,OAAO,KAAoB;AACpE,QAAM,SAAS,IAAI,gBAAgB,OAAO;AAC1C,MAAI;AACF,UAAM,MAAM,IAAI,MAAM;AACtB,QAAI,cAAc;AAClB,QAAI,MAAM;AACV,UAAM,IAAI,QAAc,CAAC,SAAS,WAAW;AAC3C,UAAI,SAAS,MAAM,QAAQ;AAC3B,UAAI,UAAU,MAAM,OAAO,IAAI,MAAM,+BAA+B,CAAC;AAAA,IACvE,CAAC;AACD,UAAM,SAAS,SAAS,cAAc,QAAQ;AAC9C,WAAO,QAAQ;AACf,WAAO,SAAS;AAChB,UAAM,MAAM,OAAO,WAAW,IAAI;AAClC,QAAI,CAAC,IAAK,OAAM,IAAI,MAAM,+BAA+B;AACzD,QAAI,YAAY;AAChB,QAAI,SAAS,GAAG,GAAG,MAAM,IAAI;AAC7B,QAAI,UAAU,KAAK,GAAG,GAAG,MAAM,IAAI;AACnC,WAAO,MAAM,IAAI,QAAc,CAAC,SAAS,WAAW;AAClD,aAAO;AAAA,QACL,CAAC,SAAU,OAAO,QAAQ,IAAI,IAAI,OAAO,IAAI,MAAM,6BAA6B,CAAC;AAAA,QACjF;AAAA,MACF;AAAA,IACF,CAAC;AAAA,EACH,UAAE;AACA,QAAI,gBAAgB,MAAM;AAAA,EAC5B;AACF;AAEA,eAAsB,uBAAuB,KAAa,UAAiC;AACzF,QAAM,MAAM,MAAM,MAAM,GAAG;AAC3B,MAAI,CAAC,IAAI,GAAI,OAAM,IAAI,MAAM,2BAA2B,IAAI,MAAM,EAAE;AACpE,QAAM,OAAO,MAAM,IAAI,KAAK;AAC5B,MAAI,KAAK,SAAS,mBAAmB,IAAI,YAAY,EAAE,SAAS,MAAM,GAAG;AACvE,UAAM,MAAM,MAAM,aAAa,IAAI;AACnC,WAAO,IAAI,KAAK,CAAC,GAAG,GAAG,GAAG,QAAQ,QAAQ,EAAE,MAAM,YAAY,CAAC;AAAA,EACjE;AACA,MAAI,CAAC,KAAK,KAAK,WAAW,QAAQ,GAAG;AACnC,UAAM,IAAI,MAAM,2CAA2C,KAAK,QAAQ,SAAS,GAAG;AAAA,EACtF;AACA,SAAO,IAAI,KAAK,CAAC,IAAI,GAAG,UAAU,EAAE,MAAM,KAAK,KAAK,CAAC;AACvD;AAOA,IAAM,cAAc;AACpB,IAAM,WAAW;AACjB,IAAM,OAAO;AAEN,SAAS,oBAAoB,SAAuB;AACzD,QAAM,cAAc,KAAK,MAAM,UAAU,WAAW;AACpD,QAAM,aAAc,WAAW,OAAQ;AACvC,QAAM,WAAW,cAAc;AAC/B,QAAM,WAAW,cAAc;AAC/B,QAAM,SAAS,IAAI,YAAY,KAAK,QAAQ;AAC5C,QAAM,OAAO,IAAI,SAAS,MAAM;AAChC,QAAM,WAAW,CAAC,QAAgB,MAAc;AAC9C,aAAS,IAAI,GAAG,IAAI,EAAE,QAAQ,IAAK,MAAK,SAAS,SAAS,GAAG,EAAE,WAAW,CAAC,CAAC;AAAA,EAC9E;AACA,WAAS,GAAG,MAAM;AAClB,OAAK,UAAU,GAAG,KAAK,UAAU,IAAI;AACrC,WAAS,GAAG,MAAM;AAClB,WAAS,IAAI,MAAM;AACnB,OAAK,UAAU,IAAI,IAAI,IAAI;AAC3B,OAAK,UAAU,IAAI,GAAG,IAAI;AAC1B,OAAK,UAAU,IAAI,UAAU,IAAI;AACjC,OAAK,UAAU,IAAI,aAAa,IAAI;AACpC,OAAK,UAAU,IAAI,UAAU,IAAI;AACjC,OAAK,UAAU,IAAI,YAAY,IAAI;AACnC,OAAK,UAAU,IAAI,MAAM,IAAI;AAC7B,WAAS,IAAI,MAAM;AACnB,OAAK,UAAU,IAAI,UAAU,IAAI;AAEjC,SAAO,IAAI,KAAK,CAAC,MAAM,GAAG,EAAE,MAAM,YAAY,CAAC;AACjD;;;ACzDA,eAAsB,YAAY,MAAwC;AACxE,OAAK,YAAY,sCAAQ;AACzB,QAAM,UAAU,MAAM,QAAQ;AAAA,IAC5B,KAAK,QAAQ,IAAI,OAAO,MAAM,CAAC,EAAE,KAAK,MAAM,KAAK,OAAO,WAAW,EAAE,MAAM,EAAE,QAAQ,CAAC,CAAU;AAAA,EAClG;AACA,QAAM,YAAoC,OAAO,YAAY,OAAO;AAEpE,OAAK,YAAY,sCAAQ;AACzB,QAAM,SAAS,MAAM,KAAK,OAAO,OAAO,KAAK,OAAO,KAAK,WAAW,SAAS,GAAG,KAAK,UAAU;AAE/F,QAAM,QAAQ,MAAM,KAAK,OAAO,UAAU,QAAQ,CAAC,MAAM;AACvD,QAAI,EAAE,QAAQ,SAAU,MAAK,YAAY,0BAAM;AAAA,QAC1C,MAAK,YAAY,EAAE,WAAW,OAAO,4BAAQ,EAAE,OAAO,MAAM,0BAAM;AAAA,EACzE,CAAC;AAED,QAAM,MAAM,MAAM,KAAK,CAAC,MAAM,qBAAqB,KAAK,EAAE,OAAO,CAAC,GAAG,WAAW,MAAM,CAAC,GAAG;AAC1F,MAAI,CAAC,IAAK,OAAM,IAAI,MAAM,qDAAqD;AAC/E,SAAO;AACT;;;ACJA,eAAe,kBAAkB,OAAiD;AAChF,QAAM,IAAI;AAIV,QAAM,MAAM,EAAE,gBAAgB,EAAE;AAChC,MAAI,CAAC,IAAK,QAAO;AACjB,QAAM,MAAM,IAAI,IAAI;AACpB,MAAI;AACF,YAAQ,MAAM,IAAI,gBAAgB,MAAM,MAAM,CAAC,CAAC,GAAG;AAAA,EACrD,QAAQ;AACN,WAAO;AAAA,EACT,UAAE;AACA,SAAK,IAAI,MAAM;AAAA,EACjB;AACF;AAEO,SAAS,8BACd,QACmB;AACnB,QAAM,EAAE,QAAQ,MAAM,IAAI;AAC1B,QAAM,UAAU,OAAO,WAAW;AAClC,QAAM,gBACH,OAAO,kBAAkB,OAAO,eAAe,KAAK,KAAM;AAE7D,SAAO;AAAA,IACL,MAAM,SACJ,OACA,YACiB;AACjB,YAAM,MAAM,CAAC,YAAoB,aAAa,EAAE,QAAQ,CAAC;AACzD,YAAM,iBAAiB,MAAM,gBAAgB,KAAK,KAAK;AAGvD,UAAI,sCAAQ;AACZ,YAAM,aAAa,MAAM,WACrB,OAAO,MAAM,MAAM,MAAM,QAAQ,GAAG,YAAY,IAChD,MAAM,MAAM,WAAW;AAAA,QACrB,MAAM,MAAM;AAAA,QACZ,SAAS,MAAM;AAAA,QACf;AAAA,QACA,GAAI,MAAM,UAAU,EAAE,SAAS,MAAM,QAAQ,IAAI,CAAC;AAAA,MACpD,CAAC;AACL,YAAM,YAAY,IAAI,KAAK,CAAC,UAAU,GAAG,uBAAuB,EAAE,MAAM,aAAa,CAAC;AAGtF,UAAI,MAAM,iBAAiB;AACzB,cAAM,MAAM,MAAM,kBAAkB,UAAU;AAC9C,YAAI,OAAO,KAAM,OAAM,gBAAgB,GAAG;AAAA,MAC5C;AAEA,UAAI,sCAAQ;AACZ,YAAM,YAAY,MAAM,uBAAuB,MAAM,WAAW,iBAAiB;AAEjF,aAAO,YAAY;AAAA,QACjB;AAAA,QACA,OAAO;AAAA,QACP,YAAY;AAAA,QACZ,SAAS;AAAA,UACP,EAAE,KAAK,SAAS,MAAM,WAAW,UAAU,QAAQ;AAAA,UACnD,EAAE,KAAK,SAAS,MAAM,WAAW,UAAU,QAAQ;AAAA,UACnD,EAAE,KAAK,WAAW,MAAM,iBAAiB,GAAG,UAAU,QAAQ;AAAA,QAChE;AAAA,QACA,YAAY,CAAC,MACX,0BAA0B;AAAA,UACxB,eAAe,EAAE,SAAS;AAAA,UAC1B,eAAe,EAAE,SAAS;AAAA,UAC1B,sBAAsB,EAAE,WAAW;AAAA,UACnC;AAAA,QACF,CAAC;AAAA,QACH,WAAW;AAAA,MACb,CAAC;AAAA,IACH;AAAA,EACF;AACF;;;AChGA,IAAM,wBAAwB;AAC9B,IAAM,WAAW,EAAE,OAAO,MAAM,QAAQ,MAAM,OAAO,KAAK;AAC1D,IAAM,cAAc,EAAE,kBAAkB,OAAO,cAAc,UAAU;AAMhE,SAAS,mCACd,QAC4B;AAC5B,QAAM,EAAE,OAAO,IAAI;AACnB,SAAO;AAAA,IACL,MAAM,eACJ,OACA,YACiB;AACjB,YAAM,MAAM,CAAC,YAAoB,aAAa,EAAE,QAAQ,CAAC;AAGzD,UAAI,sCAAQ;AACZ,YAAM,YAAY,MAAM,uBAAuB,MAAM,WAAW,GAAG,MAAM,WAAW,SAAS;AAC7F,YAAM,UAAU,oBAAoB,MAAM,OAAO;AACjD,YAAM,YAAY,IAAI,KAAK,CAAC,OAAO,GAAG,WAAW,MAAM,OAAO,SAAS,EAAE,MAAM,YAAY,CAAC;AAE5F,aAAO,YAAY;AAAA,QACjB;AAAA,QACA,OAAO;AAAA,QACP,YAAY;AAAA,QACZ,SAAS;AAAA,UACP,EAAE,KAAK,SAAS,MAAM,WAAW,UAAU,QAAQ;AAAA,UACnD,EAAE,KAAK,SAAS,MAAM,WAAW,UAAU,QAAQ;AAAA,QACrD;AAAA,QACA,YAAY,CAAC,MAAkB;AAAA,UAC7B,EAAE,QAAQ,SAAS,OAAO,WAAW,SAAS,YAAY,EAAE,SAAS,GAAG;AAAA,UACxE,EAAE,QAAQ,SAAS,QAAQ,WAAW,UAAU,YAAY,MAAM,OAAO;AAAA,UACzE,EAAE,QAAQ,SAAS,OAAO,WAAW,SAAS,YAAY,EAAE,SAAS,GAAG;AAAA,QAC1E;AAAA,QACA,WAAW;AAAA,MACb,CAAC;AAAA,IACH;AAAA,EACF;AACF;;;ACnDO,IAAM,kBAAkB;AAExB,IAAM,oBAAoB;AAAA;AAAA,EAE/B,OAAO;AAAA;AAAA,EAEP,YAAY;AAAA;AAAA,EAEZ,aAAa;AAAA;AAAA,EAEb,YAAY;AAAA;AAAA,EAEZ,QAAQ;AACV;AAGO,IAAM,uBAAuB,EAAE,kBAAkB,OAAO,cAAc,UAAU;;;ACmBhF,SAAS,8BACd,QACmB;AACnB,QAAM,EAAE,OAAO,IAAI;AACnB,SAAO;AAAA,IACL,MAAM,SACJ,OACA,YACiB;AACjB,YAAM,MAAM,CAAC,YAAoB,aAAa,EAAE,QAAQ,CAAC;AACzD,aAAO,YAAY;AAAA,QACjB;AAAA,QACA,OAAO;AAAA,QACP,YAAY;AAAA,QACZ,SAAS;AAAA,UACP,EAAE,KAAK,SAAS,MAAM,MAAM,OAAO,UAAU,QAAQ;AAAA,UACrD,EAAE,KAAK,QAAQ,MAAM,MAAM,WAAW,UAAU,QAAQ;AAAA,UACxD,EAAE,KAAK,SAAS,MAAM,MAAM,YAAY,UAAU,QAAQ;AAAA,UAC1D,EAAE,KAAK,QAAQ,MAAM,MAAM,WAAW,UAAU,QAAQ;AAAA,QAC1D;AAAA,QACA,YAAY,CAAC,MAAkB;AAAA,UAC7B,EAAE,QAAQ,kBAAkB,OAAO,WAAW,SAAS,YAAY,EAAE,SAAS,GAAG;AAAA,UACjF,EAAE,QAAQ,kBAAkB,YAAY,WAAW,SAAS,YAAY,EAAE,QAAQ,GAAG;AAAA,UACrF,EAAE,QAAQ,kBAAkB,aAAa,WAAW,SAAS,YAAY,EAAE,SAAS,GAAG;AAAA,UACvF,EAAE,QAAQ,kBAAkB,YAAY,WAAW,SAAS,YAAY,EAAE,QAAQ,GAAG;AAAA,UACrF,EAAE,QAAQ,kBAAkB,QAAQ,WAAW,QAAQ,YAAY,MAAM,UAAU,GAAG;AAAA,QACxF;AAAA,QACA,WAAW;AAAA,MACb,CAAC;AAAA,IACH;AAAA,EACF;AACF;;;AClEA,IAAM,WAAW;AAgBjB,IAAM,cAA6B,EAAE,kBAAkB,OAAO,cAAc,UAAU;AAE/E,SAAS,qBAAqB,QAA0B,MAAuC;AACpG,SAAO;AAAA,IACL,MAAM,SACJ,OACA,YACmB;AACnB,YAAM,MAAM,CAAC,YAAoB,aAAa,EAAE,QAAQ,CAAC;AACzD,YAAM,QAAQ,MAAM,SAAS,KAAK,SAAS;AAC3C,YAAM,SAAS,MAAM,UAAU,KAAK,SAAS;AAC7C,YAAM,YAAY,MAAM,aAAa,KAAK,SAAS;AAEnD,YAAM,QAAoB;AAAA,QACxB,EAAE,QAAQ,KAAK,QAAQ,QAAQ,WAAW,QAAQ,YAAY,MAAM,OAAO;AAAA,QAC3E,EAAE,QAAQ,KAAK,QAAQ,OAAO,WAAW,SAAS,YAAY,OAAO,KAAK,EAAE;AAAA,QAC5E,EAAE,QAAQ,KAAK,QAAQ,QAAQ,WAAW,SAAS,YAAY,OAAO,MAAM,EAAE;AAAA,QAC9E,EAAE,QAAQ,KAAK,QAAQ,OAAO,WAAW,SAAS,YAAY,OAAO,SAAS,EAAE;AAAA,MAClF;AAEA,UAAI,sCAAQ;AACZ,YAAM,SAAS,MAAM,OAAO,OAAO,KAAK,OAAO,OAAO,KAAK,cAAc,WAAW;AAEpF,YAAM,QAAQ,MAAM,OAAO,UAAU,QAAQ,CAAC,MAAM;AAClD,YAAI,EAAE,QAAQ,SAAU,KAAI,0BAAM;AAAA,YAC7B,KAAI,EAAE,WAAW,OAAO,4BAAQ,EAAE,OAAO,MAAM,0BAAM;AAAA,MAC5D,CAAC;AAED,YAAM,SAAS,MAAM,OAAO,CAAC,MAAM,SAAS,KAAK,EAAE,OAAO,CAAC,EAAE,IAAI,CAAC,MAAM,EAAE,OAAO;AACjF,YAAM,OAAO,OAAO,SAAS,IAAI,SAAS,MAAM,IAAI,CAAC,MAAM,EAAE,OAAO;AACpE,UAAI,KAAK,WAAW,EAAG,OAAM,IAAI,MAAM,qDAAqD;AAC5F,aAAO;AAAA,IACT;AAAA,EACF;AACF;;;ACpDO,IAAM,gBAAgC;AAAA,EAC3C,OAAO;AAAA;AAAA,EAEP,SAAS,EAAE,QAAQ,KAAK,OAAO,MAAM,QAAQ,MAAM,OAAO,MAAM;AAAA,EAChE,UAAU,EAAE,OAAO,MAAM,QAAQ,MAAM,WAAW,EAAE;AACtD;AAMO,SAAS,yBAAyB,QAAmD;AAC1F,SAAO,qBAAqB,OAAO,QAAQ,aAAa;AAC1D;;;ACbO,IAAM,mBAAmC;AAAA,EAC9C,OAAO;AAAA;AAAA,EAEP,SAAS,EAAE,QAAQ,MAAM,OAAO,MAAM,QAAQ,OAAO,OAAO,MAAM;AAAA,EAClE,UAAU,EAAE,OAAO,MAAM,QAAQ,MAAM,WAAW,EAAE;AACtD;AAMO,SAAS,2BAA2B,QAAqD;AAC9F,SAAO,qBAAqB,OAAO,QAAQ,gBAAgB;AAC7D;;;ACZA,IAAMC,YAAW;AACjB,IAAMC,eAA6B,EAAE,kBAAkB,OAAO,cAAc,UAAU;AAWtF,IAAM,SAAS,CAAC,MAAY,MAC1B,gBAAgB,OAAO,OAAO,IAAI,KAAK,CAAC,IAAI,GAAG,SAAS,CAAC,QAAQ,EAAE,MAAM,KAAK,QAAQ,YAAY,CAAC;AAE9F,SAAS,oBAAoB,QAA0B,MAAoC;AAChG,SAAO;AAAA,IACL,MAAM,SACJ,OACA,YACmB;AACnB,YAAM,MAAM,CAAC,YAAoB,aAAa,EAAE,QAAQ,CAAC;AAGzD,YAAM,SAAS,CAAC,MAAM,OAAO,GAAI,MAAM,eAAe,CAAC,CAAE,EAAE,MAAM,GAAG,KAAK,aAAa,MAAM;AAE5F,UAAI,sCAAQ;AACZ,YAAM,QAAQ,MAAM,QAAQ,IAAI,OAAO,IAAI,CAAC,KAAK,MAAM,OAAO,WAAW,OAAO,KAAK,CAAC,GAAG,OAAO,CAAC,CAAC;AAElG,YAAM,QAAoB;AAAA,QACxB,EAAE,QAAQ,KAAK,cAAc,WAAW,QAAQ,YAAY,MAAM,OAAO;AAAA,MAC3E;AACA,eAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;AACrC,cAAM,SAAS,KAAK,aAAa,CAAC;AAClC,cAAM,YAAY,MAAM,CAAC;AACzB,YAAI,UAAU,UAAW,OAAM,KAAK,EAAE,QAAQ,WAAW,SAAS,YAAY,UAAU,CAAC;AAAA,MAC3F;AAEA,UAAI,sCAAQ;AACZ,YAAM,SAAS,MAAM,OAAO,OAAO,KAAK,OAAO,OAAO,KAAK,cAAcA,YAAW;AAEpF,YAAM,QAAQ,MAAM,OAAO,UAAU,QAAQ,CAAC,MAAM;AAClD,YAAI,EAAE,QAAQ,SAAU,KAAI,0BAAM;AAAA,YAC7B,KAAI,EAAE,WAAW,OAAO,4BAAQ,EAAE,OAAO,MAAM,0BAAM;AAAA,MAC5D,CAAC;AAED,YAAM,MAAM,MAAM,OAAO,CAAC,MAAMD,UAAS,KAAK,EAAE,OAAO,CAAC,EAAE,IAAI,CAAC,MAAM,EAAE,OAAO;AAC9E,YAAM,OAAO,IAAI,SAAS,IAAI,MAAM,MAAM,IAAI,CAAC,MAAM,EAAE,OAAO;AAC9D,UAAI,KAAK,WAAW,EAAG,OAAM,IAAI,MAAM,qDAAqD;AAC5F,aAAO;AAAA,IACT;AAAA,EACF;AACF;;;ACrDO,IAAM,gBAA+B;AAAA,EAC1C,OAAO;AAAA,EACP,cAAc;AAAA;AAAA,EAEd,cAAc,CAAC,OAAO,OAAO,KAAK;AACpC;AAMO,SAAS,yBAAyB,QAAiD;AACxF,SAAO,oBAAoB,OAAO,QAAQ,aAAa;AACzD;;;ACZO,IAAM,iBAAgC;AAAA,EAC3C,OAAO;AAAA,EACP,cAAc;AAAA;AAAA,EAEd,cAAc,CAAC,OAAO,KAAK;AAC7B;AAMO,SAAS,yBAAyB,QAAiD;AACxF,SAAO,oBAAoB,OAAO,QAAQ,cAAc;AAC1D;","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.2.2",
3
+ "version": "0.2.3",
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",
@@ -17,9 +17,9 @@
17
17
  }
18
18
  },
19
19
  "dependencies": {
20
+ "@surfmate.team/digital-human-greeting": "0.2.0",
20
21
  "@surfmate.team/digital-human-voice": "0.1.0",
21
- "@surfmate.team/digital-human-waiting": "0.1.0",
22
- "@surfmate.team/digital-human-greeting": "0.2.0"
22
+ "@surfmate.team/digital-human-waiting": "0.1.0"
23
23
  },
24
24
  "devDependencies": {
25
25
  "tsup": "^8.5.0",