tongflow 0.2.0 → 1.0.0
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/LICENSE +661 -0
- package/README.md +3 -2
- package/abi/tongflow.abi.json +49 -4
- package/dist/canvas-messages.cjs +71 -63
- package/dist/canvas-messages.js +71 -63
- package/dist/canvas.cjs +860 -260
- package/dist/canvas.css +1 -1
- package/dist/canvas.d.cts +158 -12
- package/dist/canvas.d.ts +158 -12
- package/dist/canvas.js +856 -261
- package/dist/index.cjs +659 -12
- package/dist/index.d.cts +317 -22
- package/dist/index.d.ts +317 -22
- package/dist/index.js +651 -13
- package/package.json +235 -233
package/dist/canvas.cjs
CHANGED
|
@@ -63,12 +63,12 @@ let _radix_ui_react_dropdown_menu = require("@radix-ui/react-dropdown-menu");
|
|
|
63
63
|
_radix_ui_react_dropdown_menu = __toESM(_radix_ui_react_dropdown_menu, 1);
|
|
64
64
|
let _radix_ui_react_label = require("@radix-ui/react-label");
|
|
65
65
|
_radix_ui_react_label = __toESM(_radix_ui_react_label, 1);
|
|
66
|
+
let _radix_ui_react_switch = require("@radix-ui/react-switch");
|
|
67
|
+
_radix_ui_react_switch = __toESM(_radix_ui_react_switch, 1);
|
|
66
68
|
let _radix_ui_react_progress = require("@radix-ui/react-progress");
|
|
67
69
|
_radix_ui_react_progress = __toESM(_radix_ui_react_progress, 1);
|
|
68
70
|
let _radix_ui_react_slider = require("@radix-ui/react-slider");
|
|
69
71
|
_radix_ui_react_slider = __toESM(_radix_ui_react_slider, 1);
|
|
70
|
-
let _radix_ui_react_switch = require("@radix-ui/react-switch");
|
|
71
|
-
_radix_ui_react_switch = __toESM(_radix_ui_react_switch, 1);
|
|
72
72
|
let react_dom = require("react-dom");
|
|
73
73
|
let masonic = require("masonic");
|
|
74
74
|
let three = require("three");
|
|
@@ -579,7 +579,11 @@ const ABI_NODES = {
|
|
|
579
579
|
properties: {
|
|
580
580
|
success: { type: "boolean" },
|
|
581
581
|
error: { type: "string" },
|
|
582
|
-
text: { type: "string" }
|
|
582
|
+
text: { type: "string" },
|
|
583
|
+
images: {
|
|
584
|
+
type: "array",
|
|
585
|
+
items: { $ref: "#/$defs/ImageRef" }
|
|
586
|
+
}
|
|
583
587
|
},
|
|
584
588
|
additionalProperties: false
|
|
585
589
|
}
|
|
@@ -695,6 +699,34 @@ const ABI_NODES = {
|
|
|
695
699
|
additionalProperties: false
|
|
696
700
|
}
|
|
697
701
|
},
|
|
702
|
+
"text-gen-audio": {
|
|
703
|
+
inputs: {
|
|
704
|
+
type: "object",
|
|
705
|
+
required: ["text"],
|
|
706
|
+
properties: {
|
|
707
|
+
text: {
|
|
708
|
+
type: "string",
|
|
709
|
+
minLength: 1
|
|
710
|
+
},
|
|
711
|
+
ref_audios: {
|
|
712
|
+
type: "array",
|
|
713
|
+
items: { $ref: "#/$defs/Asset" }
|
|
714
|
+
},
|
|
715
|
+
seed: { type: "integer" }
|
|
716
|
+
},
|
|
717
|
+
additionalProperties: false
|
|
718
|
+
},
|
|
719
|
+
outputs: {
|
|
720
|
+
type: "object",
|
|
721
|
+
required: ["success"],
|
|
722
|
+
properties: {
|
|
723
|
+
success: { type: "boolean" },
|
|
724
|
+
error: { type: "string" },
|
|
725
|
+
audio: { $ref: "#/$defs/AudioRef" }
|
|
726
|
+
},
|
|
727
|
+
additionalProperties: false
|
|
728
|
+
}
|
|
729
|
+
},
|
|
698
730
|
"text-gen-speech-preset": {
|
|
699
731
|
inputs: {
|
|
700
732
|
type: "object",
|
|
@@ -1564,10 +1596,10 @@ const ABI_NODES = {
|
|
|
1564
1596
|
convert_voice: {
|
|
1565
1597
|
inputs: {
|
|
1566
1598
|
type: "object",
|
|
1567
|
-
required: ["
|
|
1599
|
+
required: ["audio", "ref_audio"],
|
|
1568
1600
|
properties: {
|
|
1569
|
-
|
|
1570
|
-
|
|
1601
|
+
audio: { $ref: "#/$defs/Asset" },
|
|
1602
|
+
ref_audio: { $ref: "#/$defs/Asset" }
|
|
1571
1603
|
},
|
|
1572
1604
|
additionalProperties: false
|
|
1573
1605
|
},
|
|
@@ -2174,6 +2206,7 @@ const NODE_TYPE_TO_ABI_FEATURE = {
|
|
|
2174
2206
|
imageGenImageUpscaleNode: "image-upscale",
|
|
2175
2207
|
textGenImageNode: "image-gen",
|
|
2176
2208
|
textGenMusicNode: "gen-music",
|
|
2209
|
+
textGenAudioNode: "text-gen-audio",
|
|
2177
2210
|
textGenSpeechCloneNode: "text-gen-speech-clone",
|
|
2178
2211
|
textGenSpeechCloneComposeNode: "text-gen-speech-clone",
|
|
2179
2212
|
imageGenVideoComposeNode: "image-gen-video",
|
|
@@ -2297,6 +2330,18 @@ const NODE_TYPE_SOURCE_SPEC = {
|
|
|
2297
2330
|
manual: true
|
|
2298
2331
|
})
|
|
2299
2332
|
},
|
|
2333
|
+
musicCoverNode: {
|
|
2334
|
+
text: handle({
|
|
2335
|
+
nodeType: "textNode",
|
|
2336
|
+
path: "texts[0]",
|
|
2337
|
+
manual: true
|
|
2338
|
+
}),
|
|
2339
|
+
lyrics: handle({
|
|
2340
|
+
nodeType: "textNode",
|
|
2341
|
+
path: "texts[0]",
|
|
2342
|
+
manual: true
|
|
2343
|
+
})
|
|
2344
|
+
},
|
|
2300
2345
|
textGenSpeechCloneComposeNode: { text: batchOn({
|
|
2301
2346
|
nodeType: "textNode",
|
|
2302
2347
|
path: "texts"
|
|
@@ -2334,7 +2379,10 @@ const NODE_TYPE_SOURCE_SPEC = {
|
|
|
2334
2379
|
audioGenTextSpeechRecognizeNode: { audio: batchOn() },
|
|
2335
2380
|
concatVideoComposeNode: { videos: collectAll() },
|
|
2336
2381
|
concatVideoNode: { videos: collectAll() },
|
|
2337
|
-
convertVoiceNode: {
|
|
2382
|
+
convertVoiceNode: {
|
|
2383
|
+
audio: batchOn({ nodeType: "audioNode" }),
|
|
2384
|
+
ref_audio: handle({ nodeType: "audioNode" })
|
|
2385
|
+
},
|
|
2338
2386
|
denoiseAudioSubtitleNode: { fileKey: batchOn({ nodeType: "audioNode" }) },
|
|
2339
2387
|
dropVideoNode: { videos: collectAll() },
|
|
2340
2388
|
extractAudioNode: { video: batchOn() },
|
|
@@ -2382,6 +2430,14 @@ const NODE_TYPE_SOURCE_SPEC = {
|
|
|
2382
2430
|
path: "texts[0]"
|
|
2383
2431
|
})
|
|
2384
2432
|
},
|
|
2433
|
+
textGenAudioNode: {
|
|
2434
|
+
text: handle({
|
|
2435
|
+
nodeType: "textNode",
|
|
2436
|
+
path: "texts[0]",
|
|
2437
|
+
manual: true
|
|
2438
|
+
}),
|
|
2439
|
+
ref_audios: collectAll({ nodeType: "audioNode" })
|
|
2440
|
+
},
|
|
2385
2441
|
textGenSpeechCloneNode: {
|
|
2386
2442
|
text: batchOn({
|
|
2387
2443
|
nodeType: "textNode",
|
|
@@ -3865,7 +3921,11 @@ var tongflow_abi_default = {
|
|
|
3865
3921
|
"properties": {
|
|
3866
3922
|
"success": { "type": "boolean" },
|
|
3867
3923
|
"error": { "type": "string" },
|
|
3868
|
-
"text": { "type": "string" }
|
|
3924
|
+
"text": { "type": "string" },
|
|
3925
|
+
"images": {
|
|
3926
|
+
"type": "array",
|
|
3927
|
+
"items": { "$ref": "#/$defs/ImageRef" }
|
|
3928
|
+
}
|
|
3869
3929
|
},
|
|
3870
3930
|
"additionalProperties": false
|
|
3871
3931
|
}
|
|
@@ -3985,6 +4045,35 @@ var tongflow_abi_default = {
|
|
|
3985
4045
|
"additionalProperties": false
|
|
3986
4046
|
}
|
|
3987
4047
|
},
|
|
4048
|
+
{
|
|
4049
|
+
"nodeSlot": "text-gen-audio",
|
|
4050
|
+
"inputs": {
|
|
4051
|
+
"type": "object",
|
|
4052
|
+
"required": ["text"],
|
|
4053
|
+
"properties": {
|
|
4054
|
+
"text": {
|
|
4055
|
+
"type": "string",
|
|
4056
|
+
"minLength": 1
|
|
4057
|
+
},
|
|
4058
|
+
"ref_audios": {
|
|
4059
|
+
"type": "array",
|
|
4060
|
+
"items": { "$ref": "#/$defs/Asset" }
|
|
4061
|
+
},
|
|
4062
|
+
"seed": { "type": "integer" }
|
|
4063
|
+
},
|
|
4064
|
+
"additionalProperties": false
|
|
4065
|
+
},
|
|
4066
|
+
"outputs": {
|
|
4067
|
+
"type": "object",
|
|
4068
|
+
"required": ["success"],
|
|
4069
|
+
"properties": {
|
|
4070
|
+
"success": { "type": "boolean" },
|
|
4071
|
+
"error": { "type": "string" },
|
|
4072
|
+
"audio": { "$ref": "#/$defs/AudioRef" }
|
|
4073
|
+
},
|
|
4074
|
+
"additionalProperties": false
|
|
4075
|
+
}
|
|
4076
|
+
},
|
|
3988
4077
|
{
|
|
3989
4078
|
"nodeSlot": "text-gen-speech-preset",
|
|
3990
4079
|
"inputs": {
|
|
@@ -4887,10 +4976,10 @@ var tongflow_abi_default = {
|
|
|
4887
4976
|
"nodeSlot": "convert_voice",
|
|
4888
4977
|
"inputs": {
|
|
4889
4978
|
"type": "object",
|
|
4890
|
-
"required": ["
|
|
4979
|
+
"required": ["audio", "ref_audio"],
|
|
4891
4980
|
"properties": {
|
|
4892
|
-
"
|
|
4893
|
-
"
|
|
4981
|
+
"audio": { "$ref": "#/$defs/Asset" },
|
|
4982
|
+
"ref_audio": { "$ref": "#/$defs/Asset" }
|
|
4894
4983
|
},
|
|
4895
4984
|
"additionalProperties": false
|
|
4896
4985
|
},
|
|
@@ -5173,6 +5262,103 @@ parsed.nodes;
|
|
|
5173
5262
|
function getAbiNodeBySlot(nodeSlot) {
|
|
5174
5263
|
return bySlot.get(nodeSlot);
|
|
5175
5264
|
}
|
|
5265
|
+
//#endregion
|
|
5266
|
+
//#region src/core/registry/model-catalog.ts
|
|
5267
|
+
/** Read a dot-path (`a.b.c`) off an unknown JSON value; `undefined` when absent. */
|
|
5268
|
+
function getPath(value, path) {
|
|
5269
|
+
let cur = value;
|
|
5270
|
+
for (const key of path.split(".")) {
|
|
5271
|
+
if (cur === null || typeof cur !== "object") return void 0;
|
|
5272
|
+
cur = cur[key];
|
|
5273
|
+
}
|
|
5274
|
+
return cur;
|
|
5275
|
+
}
|
|
5276
|
+
/** Substring match against the field's JSON serialization (strings as-is). */
|
|
5277
|
+
function fieldContains(value, token) {
|
|
5278
|
+
if (value === void 0 || value === null) return false;
|
|
5279
|
+
return (typeof value === "string" ? value : JSON.stringify(value)).includes(token);
|
|
5280
|
+
}
|
|
5281
|
+
/**
|
|
5282
|
+
* Filter a fetched catalog payload into `slot -> model ids`, applying the
|
|
5283
|
+
* plugin's declared `exclude` and per-slot `slots` rules (substring tokens,
|
|
5284
|
+
* `!`-prefixed = must be absent). Ids keep catalog order; malformed records
|
|
5285
|
+
* are skipped. Pure — the fetch lives in the canvas.
|
|
5286
|
+
*/
|
|
5287
|
+
function filterModelCatalog(catalog, payload) {
|
|
5288
|
+
const out = {};
|
|
5289
|
+
for (const slot of Object.keys(catalog.slots)) out[slot] = [];
|
|
5290
|
+
const items = getPath(payload, catalog.items);
|
|
5291
|
+
if (!Array.isArray(items)) return out;
|
|
5292
|
+
const exclude = Object.entries(catalog.exclude ?? {});
|
|
5293
|
+
for (const record of items) {
|
|
5294
|
+
const id = getPath(record, catalog.id);
|
|
5295
|
+
if (typeof id !== "string" || !id.trim()) continue;
|
|
5296
|
+
if (exclude.some(([field, literal]) => getPath(record, field) === literal)) continue;
|
|
5297
|
+
for (const [slot, rules] of Object.entries(catalog.slots)) if (Object.entries(rules).every(([field, tokens]) => {
|
|
5298
|
+
const value = getPath(record, field);
|
|
5299
|
+
return (Array.isArray(tokens) ? tokens : [tokens]).every((token) => token.startsWith("!") ? !fieldContains(value, token.slice(1)) : fieldContains(value, token));
|
|
5300
|
+
}) && !out[slot].includes(id)) out[slot].push(id);
|
|
5301
|
+
}
|
|
5302
|
+
return out;
|
|
5303
|
+
}
|
|
5304
|
+
//#endregion
|
|
5305
|
+
//#region src/core/registry/plugin-params.ts
|
|
5306
|
+
/**
|
|
5307
|
+
* The params a node offers for the active plugin + model: every declared entry
|
|
5308
|
+
* whose `models` gate is absent or names the selected model.
|
|
5309
|
+
*/
|
|
5310
|
+
function visiblePluginParams(params, model) {
|
|
5311
|
+
if (!params) return [];
|
|
5312
|
+
const out = [];
|
|
5313
|
+
for (const [name, spec] of Object.entries(params)) {
|
|
5314
|
+
if (spec.models && !(model && spec.models.includes(model))) continue;
|
|
5315
|
+
out.push({
|
|
5316
|
+
name,
|
|
5317
|
+
spec
|
|
5318
|
+
});
|
|
5319
|
+
}
|
|
5320
|
+
return out;
|
|
5321
|
+
}
|
|
5322
|
+
/** True when `value` is a legal setting for `spec` (type + range/options). */
|
|
5323
|
+
function isValidPluginParamValue(spec, value) {
|
|
5324
|
+
switch (spec.type) {
|
|
5325
|
+
case "select": return (spec.options ?? []).some((o) => o === value);
|
|
5326
|
+
case "boolean": return typeof value === "boolean";
|
|
5327
|
+
case "text": return typeof value === "string";
|
|
5328
|
+
case "number":
|
|
5329
|
+
case "integer":
|
|
5330
|
+
if (typeof value !== "number" || !Number.isFinite(value)) return false;
|
|
5331
|
+
if (spec.type === "integer" && !Number.isInteger(value)) return false;
|
|
5332
|
+
if (spec.min !== void 0 && value < spec.min) return false;
|
|
5333
|
+
if (spec.max !== void 0 && value > spec.max) return false;
|
|
5334
|
+
return true;
|
|
5335
|
+
default: return false;
|
|
5336
|
+
}
|
|
5337
|
+
}
|
|
5338
|
+
/**
|
|
5339
|
+
* Keep only the entries of `values` that the visible params accept and that
|
|
5340
|
+
* differ from the declared default. Unset means "plugin default", so a value
|
|
5341
|
+
* equal to it is dropped; the result is what travels to the plugin.
|
|
5342
|
+
*/
|
|
5343
|
+
function normalizePluginParams(entries, values) {
|
|
5344
|
+
const out = {};
|
|
5345
|
+
if (!values) return out;
|
|
5346
|
+
for (const { name, spec } of entries) {
|
|
5347
|
+
if (!(name in values)) continue;
|
|
5348
|
+
const v = values[name];
|
|
5349
|
+
if (!isValidPluginParamValue(spec, v)) continue;
|
|
5350
|
+
if (spec.default !== void 0 && v === spec.default) continue;
|
|
5351
|
+
out[name] = v;
|
|
5352
|
+
}
|
|
5353
|
+
return out;
|
|
5354
|
+
}
|
|
5355
|
+
/** Shallow key/value equality for two param records. */
|
|
5356
|
+
function samePluginParams(a, b) {
|
|
5357
|
+
const ka = Object.keys(a ?? {});
|
|
5358
|
+
const kb = Object.keys(b ?? {});
|
|
5359
|
+
if (ka.length !== kb.length) return false;
|
|
5360
|
+
return ka.every((k) => Object.is(a?.[k], b?.[k]));
|
|
5361
|
+
}
|
|
5176
5362
|
/**
|
|
5177
5363
|
* Deep-copy the current canvas state for the history stack, stripping
|
|
5178
5364
|
* transient interaction flags so undo never restores a stale selection
|
|
@@ -5267,6 +5453,10 @@ const NODE_SIZE_TABLE = {
|
|
|
5267
5453
|
w: 520,
|
|
5268
5454
|
h: 480
|
|
5269
5455
|
},
|
|
5456
|
+
textGenAudioNode: {
|
|
5457
|
+
w: 480,
|
|
5458
|
+
h: 480
|
|
5459
|
+
},
|
|
5270
5460
|
musicExtractNode: {
|
|
5271
5461
|
w: 420,
|
|
5272
5462
|
h: 400
|
|
@@ -5771,7 +5961,7 @@ function createFlowSlice(set, get, options = {}) {
|
|
|
5771
5961
|
if (newlyCreatedIds.length > 0) get().nodeCreatedCallbacks.forEach((cb) => cb(newlyCreatedIds));
|
|
5772
5962
|
return ids;
|
|
5773
5963
|
},
|
|
5774
|
-
compose: ({ type, data }) => {
|
|
5964
|
+
compose: ({ type, data, sourceOrder }) => {
|
|
5775
5965
|
get().commitHistory();
|
|
5776
5966
|
const { comboSelectedIds, nodes, edges } = get();
|
|
5777
5967
|
const nodeId = createId();
|
|
@@ -5799,7 +5989,7 @@ function createFlowSlice(set, get, options = {}) {
|
|
|
5799
5989
|
data: data ?? {}
|
|
5800
5990
|
};
|
|
5801
5991
|
const usedTargetHandles = /* @__PURE__ */ new Set();
|
|
5802
|
-
const newEdges = Array.from(comboSelectedIds).map((id) => {
|
|
5992
|
+
const newEdges = (sourceOrder ? [...sourceOrder.filter((id) => comboSelectedIds.has(id)), ...Array.from(comboSelectedIds).filter((id) => !sourceOrder.includes(id))] : Array.from(comboSelectedIds)).map((id) => {
|
|
5803
5993
|
const node = nodes.find((n) => n.id === id);
|
|
5804
5994
|
if (!node) return null;
|
|
5805
5995
|
const { sourceHandle, targetHandle } = resolveEdgeHandles({
|
|
@@ -6005,6 +6195,7 @@ var en_default = {
|
|
|
6005
6195
|
"separateAudio": "Separate Vocals",
|
|
6006
6196
|
"separateSpeaker": "Speaker Separation",
|
|
6007
6197
|
"separateSound": "Extract Sound",
|
|
6198
|
+
"speakWithStyle": "Speak with Style",
|
|
6008
6199
|
"denoise": "Denoise",
|
|
6009
6200
|
"convertVoice": "Voice Replacement",
|
|
6010
6201
|
"filter": "Filter",
|
|
@@ -6142,6 +6333,9 @@ var en_default = {
|
|
|
6142
6333
|
"modalDeployProgressHint": "Deployed {done}/{total}",
|
|
6143
6334
|
"pluginImplementationTitle": "Implementation",
|
|
6144
6335
|
"pluginModelTitle": "Model",
|
|
6336
|
+
"pluginParamsTitle": "Advanced",
|
|
6337
|
+
"pluginParamsReset": "Reset",
|
|
6338
|
+
"pluginParamDefault": "default {value}",
|
|
6145
6339
|
"pluginImplementationLoading": "Loading plugin implementations…",
|
|
6146
6340
|
"pluginImplementationEmpty": "No plugin implementations were scanned for this capability. Add Modal or API plugins under the repo's plugins/ directory (see README), then reload this page.",
|
|
6147
6341
|
"pluginRegistryLoadError": "Could not load the plugin registry: {message}",
|
|
@@ -6267,11 +6461,7 @@ var en_default = {
|
|
|
6267
6461
|
"referenceTextPlaceholder": "Optional: transcript of the reference audio — improves clone quality",
|
|
6268
6462
|
"referenceTextCloneHint": "Leave empty for fast x-vector clone; paste the reference transcript for higher-quality ICL clone",
|
|
6269
6463
|
"language": "Language",
|
|
6270
|
-
"inputText": "Input Text"
|
|
6271
|
-
"voiceOptions": {
|
|
6272
|
-
"female": "Female Voice",
|
|
6273
|
-
"male": "Male Voice"
|
|
6274
|
-
}
|
|
6464
|
+
"inputText": "Input Text"
|
|
6275
6465
|
},
|
|
6276
6466
|
"titles": {
|
|
6277
6467
|
"imageGenVideo": "Image to Video",
|
|
@@ -6335,7 +6525,8 @@ var en_default = {
|
|
|
6335
6525
|
"musicExtract": "Extract Stem",
|
|
6336
6526
|
"musicLego": "Add Track",
|
|
6337
6527
|
"musicComplete": "Complete Arrangement",
|
|
6338
|
-
"musicBrief": "Music Brief"
|
|
6528
|
+
"musicBrief": "Music Brief",
|
|
6529
|
+
"textGenAudio": "Text to Audio"
|
|
6339
6530
|
},
|
|
6340
6531
|
"options": {
|
|
6341
6532
|
"portrait": "Portrait",
|
|
@@ -6642,15 +6833,9 @@ var en_default = {
|
|
|
6642
6833
|
"audioPromptPlaceholder": "Enter audio dubbing prompt"
|
|
6643
6834
|
},
|
|
6644
6835
|
"convertVoice": {
|
|
6645
|
-
"
|
|
6646
|
-
"
|
|
6647
|
-
"
|
|
6648
|
-
"uploadVoice": "Upload Voice",
|
|
6649
|
-
"recordVoice": "Record Voice",
|
|
6650
|
-
"recordAudio": "Record Audio",
|
|
6651
|
-
"recordHint": "You can record audio here.",
|
|
6652
|
-
"recordNeedImpl": "Audio recording feature needs implementation",
|
|
6653
|
-
"done": "Done"
|
|
6836
|
+
"sourceAudio": "Source audio",
|
|
6837
|
+
"referenceVoice": "Reference voice",
|
|
6838
|
+
"connectHint": "Connect the audio to convert and a reference voice clip"
|
|
6654
6839
|
},
|
|
6655
6840
|
"removeWatermark": { "hint": "Automatically detect and remove Sora video watermarks" },
|
|
6656
6841
|
"videoGenText": {
|
|
@@ -6844,6 +7029,12 @@ var en_default = {
|
|
|
6844
7029
|
"separateSound": {
|
|
6845
7030
|
"textLabel": "Sound to isolate",
|
|
6846
7031
|
"textPlaceholder": "e.g. dog barking, piano, sirens in the background"
|
|
7032
|
+
},
|
|
7033
|
+
"textGenAudio": {
|
|
7034
|
+
"voiceReference": "Reference voices",
|
|
7035
|
+
"promptLabel": "Audio prompt",
|
|
7036
|
+
"promptPlaceholder": "Describe the whole scene: who speaks and how, background ambience, sound effects. Quote the lines, e.g. @voice1 says: \"It's sunny today.\" Rain patters on the window.",
|
|
7037
|
+
"hint": "One pass renders speech, sound effects and ambience together. Connect up to 3 audio clips as reference voices and click one to insert @voice1…@voice3."
|
|
6847
7038
|
}
|
|
6848
7039
|
}
|
|
6849
7040
|
},
|
|
@@ -6906,6 +7097,7 @@ var en_default = {
|
|
|
6906
7097
|
"copy": "Copy",
|
|
6907
7098
|
"copied": "Copied",
|
|
6908
7099
|
"close": "Close",
|
|
7100
|
+
"openModalBilling": "Open Modal billing",
|
|
6909
7101
|
"needHelp": "Stuck? Come find us:",
|
|
6910
7102
|
"wechatGroup": "WeChat group",
|
|
6911
7103
|
"wechatQrAlt": "WeChat group QR code"
|
|
@@ -6992,7 +7184,7 @@ var ja_default = {
|
|
|
6992
7184
|
"saveSuccess": "保存しました!",
|
|
6993
7185
|
"saveFailed": "保存に失敗しました。もう一度お試しください",
|
|
6994
7186
|
"generateVideo": "動画生成",
|
|
6995
|
-
"generateAudio": "
|
|
7187
|
+
"generateAudio": "オーディオ生成",
|
|
6996
7188
|
"generateText": "テキスト生成",
|
|
6997
7189
|
"generateImage": "画像生成",
|
|
6998
7190
|
"generateMusic": "音楽生成",
|
|
@@ -7037,6 +7229,7 @@ var ja_default = {
|
|
|
7037
7229
|
"separateAudio": "ボーカル分離",
|
|
7038
7230
|
"separateSpeaker": "話者分離",
|
|
7039
7231
|
"separateSound": "音を抽出",
|
|
7232
|
+
"speakWithStyle": "感情付き音声",
|
|
7040
7233
|
"denoise": "ノイズ除去",
|
|
7041
7234
|
"convertVoice": "音声置換",
|
|
7042
7235
|
"filter": "フィルター",
|
|
@@ -7174,6 +7367,9 @@ var ja_default = {
|
|
|
7174
7367
|
"modalDeployProgressHint": "{done}/{total} をデプロイ済み",
|
|
7175
7368
|
"pluginImplementationTitle": "実装",
|
|
7176
7369
|
"pluginModelTitle": "モデル",
|
|
7370
|
+
"pluginParamsTitle": "詳細設定",
|
|
7371
|
+
"pluginParamsReset": "リセット",
|
|
7372
|
+
"pluginParamDefault": "既定 {value}",
|
|
7177
7373
|
"pluginImplementationLoading": "プラグイン実装一覧を読み込み中…",
|
|
7178
7374
|
"pluginImplementationEmpty": "この機能向けのプラグイン実装がスキャン結果にありません。リポジトリの plugins/ に Modal または API プラグインを追加してください(README 参照)。ページを再読み込みしてください。",
|
|
7179
7375
|
"pluginRegistryLoadError": "プラグイン登録情報を読み込めませんでした:{message}",
|
|
@@ -7299,11 +7495,7 @@ var ja_default = {
|
|
|
7299
7495
|
"referenceTextPlaceholder": "任意:参照音声の書き起こしテキスト。クローン品質の向上に役立ちます",
|
|
7300
7496
|
"referenceTextCloneHint": "空欄の場合は x-vector クローン。転写テキストを入力すると ICL 高品質クローンになります",
|
|
7301
7497
|
"language": "言語",
|
|
7302
|
-
"inputText": "テキスト入力"
|
|
7303
|
-
"voiceOptions": {
|
|
7304
|
-
"female": "女性の声",
|
|
7305
|
-
"male": "男性の声"
|
|
7306
|
-
}
|
|
7498
|
+
"inputText": "テキスト入力"
|
|
7307
7499
|
},
|
|
7308
7500
|
"titles": {
|
|
7309
7501
|
"imageGenVideo": "画像から動画",
|
|
@@ -7367,7 +7559,8 @@ var ja_default = {
|
|
|
7367
7559
|
"musicExtract": "ステム抽出",
|
|
7368
7560
|
"musicLego": "トラック追加",
|
|
7369
7561
|
"musicComplete": "編曲補完",
|
|
7370
|
-
"musicBrief": "音楽ブリーフ"
|
|
7562
|
+
"musicBrief": "音楽ブリーフ",
|
|
7563
|
+
"textGenAudio": "テキストからオーディオ"
|
|
7371
7564
|
},
|
|
7372
7565
|
"options": {
|
|
7373
7566
|
"portrait": "縦向き",
|
|
@@ -7390,7 +7583,7 @@ var ja_default = {
|
|
|
7390
7583
|
"describeVideo": "動画を説明",
|
|
7391
7584
|
"recognizeSpeech": "音声を認識",
|
|
7392
7585
|
"generateMusic": "音楽を生成",
|
|
7393
|
-
"generateAudio": "
|
|
7586
|
+
"generateAudio": "オーディオを生成",
|
|
7394
7587
|
"startReplace": "置換を開始",
|
|
7395
7588
|
"startDenoise": "ノイズ除去を開始",
|
|
7396
7589
|
"parseDocument": "ドキュメントを解析",
|
|
@@ -7588,15 +7781,9 @@ var ja_default = {
|
|
|
7588
7781
|
"audioPromptPlaceholder": "音声吹き替えのプロンプトを入力"
|
|
7589
7782
|
},
|
|
7590
7783
|
"convertVoice": {
|
|
7591
|
-
"
|
|
7592
|
-
"
|
|
7593
|
-
"
|
|
7594
|
-
"uploadVoice": "音声をアップロード",
|
|
7595
|
-
"recordVoice": "音声を録音",
|
|
7596
|
-
"recordAudio": "音声を録音",
|
|
7597
|
-
"recordHint": "ここで音声を録音できます。",
|
|
7598
|
-
"recordNeedImpl": "音声録音機能は実装が必要です",
|
|
7599
|
-
"done": "完了"
|
|
7784
|
+
"sourceAudio": "元の音声",
|
|
7785
|
+
"referenceVoice": "参考ボイス",
|
|
7786
|
+
"connectHint": "変換したい音声と、参考にする声の音声をつないでください"
|
|
7600
7787
|
},
|
|
7601
7788
|
"removeWatermark": { "hint": "Sora動画の透かしを自動検出して削除" },
|
|
7602
7789
|
"videoGenText": {
|
|
@@ -7790,6 +7977,12 @@ var ja_default = {
|
|
|
7790
7977
|
"separateSound": {
|
|
7791
7978
|
"textLabel": "抽出したい音",
|
|
7792
7979
|
"textPlaceholder": "例:犬の鳴き声、ピアノ、背景のサイレン"
|
|
7980
|
+
},
|
|
7981
|
+
"textGenAudio": {
|
|
7982
|
+
"voiceReference": "参照ボイス",
|
|
7983
|
+
"promptLabel": "オーディオの説明",
|
|
7984
|
+
"promptPlaceholder": "場面をまるごと書きます:誰がどう話すか、背景の環境音、効果音。セリフは引用符で囲みます。例:@voice1 が言う:「今日はいい天気だね。」窓の外で小雨が降っている。",
|
|
7985
|
+
"hint": "話し声・効果音・環境音を一度に生成します。参照ボイスとして音声を最大 3 本つなぎ、クリックすると @voice1…@voice3 が挿入されます。"
|
|
7793
7986
|
}
|
|
7794
7987
|
}
|
|
7795
7988
|
},
|
|
@@ -7852,6 +8045,7 @@ var ja_default = {
|
|
|
7852
8045
|
"copy": "コピー",
|
|
7853
8046
|
"copied": "コピーしました",
|
|
7854
8047
|
"close": "閉じる",
|
|
8048
|
+
"openModalBilling": "Modal の請求設定を開く",
|
|
7855
8049
|
"needHelp": "解決しない場合はこちらへ:",
|
|
7856
8050
|
"wechatGroup": "WeChat グループ",
|
|
7857
8051
|
"wechatQrAlt": "WeChat グループの QR コード"
|
|
@@ -7983,6 +8177,7 @@ var ko_default = {
|
|
|
7983
8177
|
"separateAudio": "보컬 분리",
|
|
7984
8178
|
"separateSpeaker": "화자 분리",
|
|
7985
8179
|
"separateSound": "소리 추출",
|
|
8180
|
+
"speakWithStyle": "감정 음성 생성",
|
|
7986
8181
|
"denoise": "노이즈 제거",
|
|
7987
8182
|
"convertVoice": "음성 교체",
|
|
7988
8183
|
"filter": "필터",
|
|
@@ -8120,6 +8315,9 @@ var ko_default = {
|
|
|
8120
8315
|
"modalDeployProgressHint": "{done}/{total} 배포됨",
|
|
8121
8316
|
"pluginImplementationTitle": "구현",
|
|
8122
8317
|
"pluginModelTitle": "모델",
|
|
8318
|
+
"pluginParamsTitle": "고급 설정",
|
|
8319
|
+
"pluginParamsReset": "초기화",
|
|
8320
|
+
"pluginParamDefault": "기본 {value}",
|
|
8123
8321
|
"pluginImplementationLoading": "플러그인 구현을 불러오는 중…",
|
|
8124
8322
|
"pluginImplementationEmpty": "이 기능에 대해 스캔된 플러그인 구현이 없습니다. 저장소의 plugins/ 디렉터리 아래에 Modal 또는 API 플러그인을 추가한 후(README 참조) 이 페이지를 새로 고치세요.",
|
|
8125
8323
|
"pluginRegistryLoadError": "플러그인 레지스트리를 불러올 수 없습니다: {message}",
|
|
@@ -8245,11 +8443,7 @@ var ko_default = {
|
|
|
8245
8443
|
"referenceTextPlaceholder": "선택 사항: 참조 오디오의 전사본 — 복제 품질을 향상시킵니다",
|
|
8246
8444
|
"referenceTextCloneHint": "빠른 x-vector 복제는 비워 두고, 더 높은 품질의 ICL 복제를 위해서는 참조 전사본을 붙여넣으세요",
|
|
8247
8445
|
"language": "언어",
|
|
8248
|
-
"inputText": "입력 텍스트"
|
|
8249
|
-
"voiceOptions": {
|
|
8250
|
-
"female": "여성 목소리",
|
|
8251
|
-
"male": "남성 목소리"
|
|
8252
|
-
}
|
|
8446
|
+
"inputText": "입력 텍스트"
|
|
8253
8447
|
},
|
|
8254
8448
|
"titles": {
|
|
8255
8449
|
"imageGenVideo": "이미지-비디오",
|
|
@@ -8313,7 +8507,8 @@ var ko_default = {
|
|
|
8313
8507
|
"musicExtract": "스템 추출",
|
|
8314
8508
|
"musicLego": "트랙 추가",
|
|
8315
8509
|
"musicComplete": "편곡 완성",
|
|
8316
|
-
"musicBrief": "음악 브리프"
|
|
8510
|
+
"musicBrief": "음악 브리프",
|
|
8511
|
+
"textGenAudio": "텍스트로 오디오 생성"
|
|
8317
8512
|
},
|
|
8318
8513
|
"options": {
|
|
8319
8514
|
"portrait": "세로",
|
|
@@ -8620,15 +8815,9 @@ var ko_default = {
|
|
|
8620
8815
|
"audioPromptPlaceholder": "오디오 더빙 프롬프트 입력"
|
|
8621
8816
|
},
|
|
8622
8817
|
"convertVoice": {
|
|
8623
|
-
"
|
|
8624
|
-
"
|
|
8625
|
-
"
|
|
8626
|
-
"uploadVoice": "음성 업로드",
|
|
8627
|
-
"recordVoice": "음성 녹음",
|
|
8628
|
-
"recordAudio": "오디오 녹음",
|
|
8629
|
-
"recordHint": "여기에서 오디오를 녹음할 수 있습니다.",
|
|
8630
|
-
"recordNeedImpl": "오디오 녹음 기능 구현이 필요합니다",
|
|
8631
|
-
"done": "완료"
|
|
8818
|
+
"sourceAudio": "원본 오디오",
|
|
8819
|
+
"referenceVoice": "참고 목소리",
|
|
8820
|
+
"connectHint": "바꿀 오디오와 참고할 목소리 오디오를 연결하세요"
|
|
8632
8821
|
},
|
|
8633
8822
|
"removeWatermark": { "hint": "Sora 비디오 워터마크를 자동으로 감지하고 제거합니다" },
|
|
8634
8823
|
"videoGenText": {
|
|
@@ -8822,6 +9011,12 @@ var ko_default = {
|
|
|
8822
9011
|
"separateSound": {
|
|
8823
9012
|
"textLabel": "추출할 소리",
|
|
8824
9013
|
"textPlaceholder": "예: 개 짖는 소리, 피아노, 배경 사이렌"
|
|
9014
|
+
},
|
|
9015
|
+
"textGenAudio": {
|
|
9016
|
+
"voiceReference": "참조 음색",
|
|
9017
|
+
"promptLabel": "오디오 설명",
|
|
9018
|
+
"promptPlaceholder": "장면 전체를 적어 주세요. 누가 어떻게 말하는지, 배경 소리, 효과음까지. 대사는 따옴표로 묶어요. 예: @voice1이 말한다: \"오늘 날씨 좋다.\" 창밖에 가랑비가 내린다.",
|
|
9019
|
+
"hint": "말소리, 효과음, 배경음을 한 번에 만들어요. 참조 음색으로 오디오를 최대 3개까지 연결하고, 누르면 @voice1…@voice3이 설명에 들어가요."
|
|
8825
9020
|
}
|
|
8826
9021
|
}
|
|
8827
9022
|
},
|
|
@@ -8884,6 +9079,7 @@ var ko_default = {
|
|
|
8884
9079
|
"copy": "복사",
|
|
8885
9080
|
"copied": "복사됨",
|
|
8886
9081
|
"close": "닫기",
|
|
9082
|
+
"openModalBilling": "Modal 결제 설정 열기",
|
|
8887
9083
|
"needHelp": "해결이 안 되면 커뮤니티로 오세요:",
|
|
8888
9084
|
"wechatGroup": "WeChat 그룹",
|
|
8889
9085
|
"wechatQrAlt": "WeChat 그룹 QR 코드"
|
|
@@ -8970,7 +9166,7 @@ var zh_default = {
|
|
|
8970
9166
|
"saveSuccess": "保存成功!",
|
|
8971
9167
|
"saveFailed": "保存失败,请重试",
|
|
8972
9168
|
"generateVideo": "生成视频",
|
|
8973
|
-
"generateAudio": "
|
|
9169
|
+
"generateAudio": "生成音频",
|
|
8974
9170
|
"generateText": "生成文本",
|
|
8975
9171
|
"generateImage": "生成图片",
|
|
8976
9172
|
"generateMusic": "生成音乐",
|
|
@@ -9015,6 +9211,7 @@ var zh_default = {
|
|
|
9015
9211
|
"separateAudio": "人声分离",
|
|
9016
9212
|
"separateSpeaker": "说话人分离",
|
|
9017
9213
|
"separateSound": "提取声音",
|
|
9214
|
+
"speakWithStyle": "按情绪配音",
|
|
9018
9215
|
"denoise": "降噪",
|
|
9019
9216
|
"convertVoice": "人声替换",
|
|
9020
9217
|
"filter": "筛选",
|
|
@@ -9152,6 +9349,9 @@ var zh_default = {
|
|
|
9152
9349
|
"modalDeployProgressHint": "已部署 {done}/{total}",
|
|
9153
9350
|
"pluginImplementationTitle": "实现",
|
|
9154
9351
|
"pluginModelTitle": "模型",
|
|
9352
|
+
"pluginParamsTitle": "高级参数",
|
|
9353
|
+
"pluginParamsReset": "重置",
|
|
9354
|
+
"pluginParamDefault": "默认 {value}",
|
|
9155
9355
|
"pluginImplementationLoading": "正在加载插件实现列表…",
|
|
9156
9356
|
"pluginImplementationEmpty": "未扫描到适用于该能力的插件实现。请在仓库的 plugins/ 目录下添加 Modal 或 API 插件(见 README),然后刷新页面。",
|
|
9157
9357
|
"pluginRegistryLoadError": "无法加载插件注册表:{message}",
|
|
@@ -9277,11 +9477,7 @@ var zh_default = {
|
|
|
9277
9477
|
"referenceTextPlaceholder": "可选:参考音频的转写文本,有助于提升克隆质量",
|
|
9278
9478
|
"referenceTextCloneHint": "不填则使用 x-vector 快速克隆;填写参考音频转写文本可启用更高质量的 ICL 克隆",
|
|
9279
9479
|
"language": "语言",
|
|
9280
|
-
"inputText": "输入文本"
|
|
9281
|
-
"voiceOptions": {
|
|
9282
|
-
"female": "普通女声",
|
|
9283
|
-
"male": "普通男声"
|
|
9284
|
-
}
|
|
9480
|
+
"inputText": "输入文本"
|
|
9285
9481
|
},
|
|
9286
9482
|
"titles": {
|
|
9287
9483
|
"imageGenVideo": "图片生成视频",
|
|
@@ -9345,7 +9541,8 @@ var zh_default = {
|
|
|
9345
9541
|
"musicExtract": "分离音轨",
|
|
9346
9542
|
"musicLego": "添加乐轨",
|
|
9347
9543
|
"musicComplete": "补全编曲",
|
|
9348
|
-
"musicBrief": "音乐企划"
|
|
9544
|
+
"musicBrief": "音乐企划",
|
|
9545
|
+
"textGenAudio": "文本生成音频"
|
|
9349
9546
|
},
|
|
9350
9547
|
"options": {
|
|
9351
9548
|
"portrait": "竖屏",
|
|
@@ -9652,15 +9849,9 @@ var zh_default = {
|
|
|
9652
9849
|
"audioPromptPlaceholder": "输入音频配音提示词"
|
|
9653
9850
|
},
|
|
9654
9851
|
"convertVoice": {
|
|
9655
|
-
"
|
|
9656
|
-
"
|
|
9657
|
-
"
|
|
9658
|
-
"uploadVoice": "上传音色",
|
|
9659
|
-
"recordVoice": "录制音色",
|
|
9660
|
-
"recordAudio": "录制音频",
|
|
9661
|
-
"recordHint": "您可以在这里录制音频。",
|
|
9662
|
-
"recordNeedImpl": "音频录制功能需要实现",
|
|
9663
|
-
"done": "完成"
|
|
9852
|
+
"sourceAudio": "源音频",
|
|
9853
|
+
"referenceVoice": "参考音色",
|
|
9854
|
+
"connectHint": "连上要换声的音频,再连一段参考音色"
|
|
9664
9855
|
},
|
|
9665
9856
|
"removeWatermark": { "hint": "自动识别并去除 Sora 视频水印" },
|
|
9666
9857
|
"videoGenText": {
|
|
@@ -9854,6 +10045,12 @@ var zh_default = {
|
|
|
9854
10045
|
"separateSound": {
|
|
9855
10046
|
"textLabel": "要提取的声音",
|
|
9856
10047
|
"textPlaceholder": "如:狗叫、钢琴、背景里的警笛"
|
|
10048
|
+
},
|
|
10049
|
+
"textGenAudio": {
|
|
10050
|
+
"voiceReference": "参考音色",
|
|
10051
|
+
"promptLabel": "音频描述",
|
|
10052
|
+
"promptPlaceholder": "把整段场景写出来:谁在说话、怎么说,背景环境,音效。台词用引号,例如:@voice1 说:“今天天气真好。”窗外下着小雨。",
|
|
10053
|
+
"hint": "一次生成人声、音效和环境声。最多连 3 段音频作参考音色,点一下就把 @voice1…@voice3 插进描述里。"
|
|
9857
10054
|
}
|
|
9858
10055
|
}
|
|
9859
10056
|
},
|
|
@@ -9916,6 +10113,7 @@ var zh_default = {
|
|
|
9916
10113
|
"copy": "复制",
|
|
9917
10114
|
"copied": "已复制",
|
|
9918
10115
|
"close": "关闭",
|
|
10116
|
+
"openModalBilling": "打开 Modal 账单设置",
|
|
9919
10117
|
"needHelp": "解决不了?来社群里找我们:",
|
|
9920
10118
|
"wechatGroup": "微信群",
|
|
9921
10119
|
"wechatQrAlt": "微信群二维码"
|
|
@@ -10006,6 +10204,7 @@ function hostFetch(input, init) {
|
|
|
10006
10204
|
}
|
|
10007
10205
|
/** URL for an asset `file_key` (private uploads endpoint by default). */
|
|
10008
10206
|
function assetUrl(fileKey) {
|
|
10207
|
+
if (/^https?:\/\//.test(fileKey)) return fileKey;
|
|
10009
10208
|
return host.resolveAssetUrl ? host.resolveAssetUrl(fileKey) : `${host.apiBaseUrl}/api/uploads/${fileKey}`;
|
|
10010
10209
|
}
|
|
10011
10210
|
/**
|
|
@@ -12711,12 +12910,84 @@ function dedupeIds(list) {
|
|
|
12711
12910
|
function useNodePluginIds(nodeSlot) {
|
|
12712
12911
|
return dedupeIds(usePluginsRegistryStore((s) => s.registry)?.nodePluginMap?.[nodeSlot] ?? []);
|
|
12713
12912
|
}
|
|
12913
|
+
const useLiveModelsStore = (0, zustand.create)(() => ({ byPlugin: {} }));
|
|
12914
|
+
const MODEL_CATALOG_TTL_MS = 6e5;
|
|
12915
|
+
const catalogInflight = /* @__PURE__ */ new Map();
|
|
12714
12916
|
/**
|
|
12715
|
-
*
|
|
12716
|
-
*
|
|
12917
|
+
* Fetch (or refresh, once the TTL elapsed) a plugin's live model catalog. Safe
|
|
12918
|
+
* to call eagerly — it is a no-op for plugins without a catalog and while a
|
|
12919
|
+
* fresh result is cached.
|
|
12920
|
+
*/
|
|
12921
|
+
async function loadPluginModelCatalog(pluginId) {
|
|
12922
|
+
const catalog = usePluginsRegistryStore.getState().registry?.plugins?.[pluginId]?.modelCatalog;
|
|
12923
|
+
if (!catalog) return;
|
|
12924
|
+
const cached = useLiveModelsStore.getState().byPlugin[pluginId];
|
|
12925
|
+
if (cached && Date.now() - cached.fetchedAt < MODEL_CATALOG_TTL_MS) return;
|
|
12926
|
+
const inflight = catalogInflight.get(pluginId);
|
|
12927
|
+
if (inflight) return inflight;
|
|
12928
|
+
const job = (async () => {
|
|
12929
|
+
try {
|
|
12930
|
+
const res = catalog.authEnv ? await hostFetch(apiUrl(`/api/plugins/model-catalog?pluginId=${encodeURIComponent(pluginId)}`), {
|
|
12931
|
+
cache: "no-store",
|
|
12932
|
+
credentials: "same-origin"
|
|
12933
|
+
}) : await fetch(catalog.url, {
|
|
12934
|
+
method: "GET",
|
|
12935
|
+
cache: "no-store"
|
|
12936
|
+
});
|
|
12937
|
+
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
12938
|
+
const bySlot = filterModelCatalog(catalog, await res.json());
|
|
12939
|
+
useLiveModelsStore.setState((s) => ({ byPlugin: {
|
|
12940
|
+
...s.byPlugin,
|
|
12941
|
+
[pluginId]: {
|
|
12942
|
+
fetchedAt: Date.now(),
|
|
12943
|
+
bySlot
|
|
12944
|
+
}
|
|
12945
|
+
} }));
|
|
12946
|
+
} catch (e) {
|
|
12947
|
+
console.warn(`[tongflow] model catalog for ${pluginId} unavailable:`, e);
|
|
12948
|
+
} finally {
|
|
12949
|
+
catalogInflight.delete(pluginId);
|
|
12950
|
+
}
|
|
12951
|
+
})();
|
|
12952
|
+
catalogInflight.set(pluginId, job);
|
|
12953
|
+
return job;
|
|
12954
|
+
}
|
|
12955
|
+
/**
|
|
12956
|
+
* Synchronous read of a plugin's model ids for one slot: the static shortlist
|
|
12957
|
+
* (declared order, first = default) followed by any live-catalog extras.
|
|
12958
|
+
*/
|
|
12959
|
+
function getNodePluginModels(nodeSlot, pluginId) {
|
|
12960
|
+
const declared = usePluginsRegistryStore.getState().registry?.plugins?.[pluginId]?.methodsByNodeSlot?.[nodeSlot]?.models ?? [];
|
|
12961
|
+
const live = useLiveModelsStore.getState().byPlugin[pluginId]?.bySlot[nodeSlot] ?? [];
|
|
12962
|
+
return dedupeIds([...declared, ...live]);
|
|
12963
|
+
}
|
|
12964
|
+
/**
|
|
12965
|
+
* Model ids a plugin offers for one ABI `nodeSlot` (empty for single-model
|
|
12966
|
+
* plugins — the model dropdown is hidden in that case): the static shortlist
|
|
12967
|
+
* plus live-catalog extras, which this hook fetches on first use.
|
|
12717
12968
|
*/
|
|
12718
12969
|
function useNodePluginModels(nodeSlot, pluginId) {
|
|
12719
|
-
|
|
12970
|
+
const registry = usePluginsRegistryStore((s) => s.registry);
|
|
12971
|
+
const declared = registry?.plugins?.[pluginId]?.methodsByNodeSlot?.[nodeSlot]?.models ?? [];
|
|
12972
|
+
const hasCatalog = Boolean(registry?.plugins?.[pluginId]?.modelCatalog);
|
|
12973
|
+
const live = useLiveModelsStore((s) => s.byPlugin[pluginId]?.bySlot[nodeSlot]);
|
|
12974
|
+
(0, react.useEffect)(() => {
|
|
12975
|
+
if (hasCatalog) loadPluginModelCatalog(pluginId);
|
|
12976
|
+
}, [hasCatalog, pluginId]);
|
|
12977
|
+
return dedupeIds([...declared, ...live ?? []]);
|
|
12978
|
+
}
|
|
12979
|
+
/**
|
|
12980
|
+
* Synchronous read of the params a plugin offers for one slot given the
|
|
12981
|
+
* selected model, in declaration order.
|
|
12982
|
+
*/
|
|
12983
|
+
function getNodePluginParams(nodeSlot, pluginId, model) {
|
|
12984
|
+
const registry = usePluginsRegistryStore.getState().registry;
|
|
12985
|
+
return visiblePluginParams(registry?.plugins?.[pluginId]?.methodsByNodeSlot?.[nodeSlot]?.params, model);
|
|
12986
|
+
}
|
|
12987
|
+
/** Reactive form of {@link getNodePluginParams}. */
|
|
12988
|
+
function useNodePluginParams(nodeSlot, pluginId, model) {
|
|
12989
|
+
const declared = usePluginsRegistryStore((s) => s.registry?.plugins?.[pluginId]?.methodsByNodeSlot?.[nodeSlot]?.params);
|
|
12990
|
+
return (0, react.useMemo)(() => visiblePluginParams(declared, model), [declared, model]);
|
|
12720
12991
|
}
|
|
12721
12992
|
/** Presentation metadata (name/description/icon) for a single plugin. */
|
|
12722
12993
|
function usePluginMeta(pluginId) {
|
|
@@ -12775,7 +13046,7 @@ function PluginIcon({ icon, label }) {
|
|
|
12775
13046
|
/**
|
|
12776
13047
|
* Shared plugin implementation selector (`plugins/<id>` directory name from registry).
|
|
12777
13048
|
*/
|
|
12778
|
-
function NodePluginSelect({ value, onValueChange, options, title }) {
|
|
13049
|
+
function NodePluginSelect({ value, onValueChange, options, title, onOpenChange }) {
|
|
12779
13050
|
const t = (0, use_intl.useTranslations)("Workspace.nodes.base");
|
|
12780
13051
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Card, {
|
|
12781
13052
|
className: "p-3",
|
|
@@ -12787,6 +13058,7 @@ function NodePluginSelect({ value, onValueChange, options, title }) {
|
|
|
12787
13058
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Select, {
|
|
12788
13059
|
value,
|
|
12789
13060
|
onValueChange,
|
|
13061
|
+
onOpenChange,
|
|
12790
13062
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(SelectTrigger, {
|
|
12791
13063
|
className: "w-full",
|
|
12792
13064
|
size: "sm",
|
|
@@ -12904,10 +13176,12 @@ function NodePluginIdSelect({ nodeSlot, data, dataKey = "pluginId" }) {
|
|
|
12904
13176
|
//#region src/canvas/nodes/base/node-plugin-model-select.tsx
|
|
12905
13177
|
/**
|
|
12906
13178
|
* Model selector for router-style plugins that declare per-slot model lists
|
|
12907
|
-
* (`TONGFLOW_SLOT_MODELS
|
|
12908
|
-
*
|
|
12909
|
-
*
|
|
12910
|
-
*
|
|
13179
|
+
* (`TONGFLOW_SLOT_MODELS`, optionally extended live via
|
|
13180
|
+
* `TONGFLOW_MODEL_CATALOG` — re-checked each time the dropdown opens). Renders
|
|
13181
|
+
* nothing when the active plugin declares no models, so single-model plugins
|
|
13182
|
+
* are visually unchanged. The selection is stored as `data.pluginModel` and
|
|
13183
|
+
* travels top-level (like `pluginId`) through the create-task API and workflow
|
|
13184
|
+
* export.
|
|
12911
13185
|
*/
|
|
12912
13186
|
function NodePluginModelSelect({ nodeSlot, data }) {
|
|
12913
13187
|
const id = (0, _xyflow_react.useNodeId)();
|
|
@@ -12942,10 +13216,218 @@ function NodePluginModelSelect({ nodeSlot, data }) {
|
|
|
12942
13216
|
pluginModel: value
|
|
12943
13217
|
}),
|
|
12944
13218
|
options,
|
|
12945
|
-
title: t("pluginModelTitle")
|
|
13219
|
+
title: t("pluginModelTitle"),
|
|
13220
|
+
onOpenChange: (open) => {
|
|
13221
|
+
if (open) loadPluginModelCatalog(pluginId);
|
|
13222
|
+
}
|
|
13223
|
+
});
|
|
13224
|
+
}
|
|
13225
|
+
//#endregion
|
|
13226
|
+
//#region src/canvas/ui/input.tsx
|
|
13227
|
+
function Input({ className, type, ...props }) {
|
|
13228
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
13229
|
+
type,
|
|
13230
|
+
"data-slot": "input",
|
|
13231
|
+
className: cn("file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground dark:bg-input/30 border-input h-9 w-full min-w-0 rounded-md border bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm", "focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]", "aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive", className),
|
|
13232
|
+
...props
|
|
13233
|
+
});
|
|
13234
|
+
}
|
|
13235
|
+
//#endregion
|
|
13236
|
+
//#region src/canvas/ui/switch.tsx
|
|
13237
|
+
function Switch({ className, ...props }) {
|
|
13238
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_radix_ui_react_switch.Root, {
|
|
13239
|
+
"data-slot": "switch",
|
|
13240
|
+
className: cn("peer data-[state=checked]:bg-primary data-[state=unchecked]:bg-input focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] dark:data-[state=unchecked]:bg-input/80 inline-flex h-[1.15rem] w-8 shrink-0 items-center rounded-full border border-transparent shadow-xs transition-all outline-none disabled:cursor-not-allowed disabled:opacity-50", className),
|
|
13241
|
+
...props,
|
|
13242
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_radix_ui_react_switch.Thumb, {
|
|
13243
|
+
"data-slot": "switch-thumb",
|
|
13244
|
+
className: cn("bg-background pointer-events-none block size-4 rounded-full ring-0 shadow-sm transition-transform data-[state=checked]:translate-x-[calc(100%-2px)] data-[state=unchecked]:translate-x-0")
|
|
13245
|
+
})
|
|
12946
13246
|
});
|
|
12947
13247
|
}
|
|
12948
13248
|
//#endregion
|
|
13249
|
+
//#region src/canvas/nodes/base/node-plugin-params.tsx
|
|
13250
|
+
function readParams(data) {
|
|
13251
|
+
const raw = data.pluginParams;
|
|
13252
|
+
return raw && typeof raw === "object" && !Array.isArray(raw) ? raw : {};
|
|
13253
|
+
}
|
|
13254
|
+
/**
|
|
13255
|
+
* Collapsed "Advanced" section listing the run-time knobs the active plugin
|
|
13256
|
+
* declares for this slot (`TONGFLOW_SLOT_PARAMS`, optionally gated per router
|
|
13257
|
+
* model). Renders nothing for plugins that declare none. Choices live in
|
|
13258
|
+
* `data.pluginParams` as a diff against the declared defaults — an unset key
|
|
13259
|
+
* means "plugin default" — and travel top-level as `params` through the
|
|
13260
|
+
* create-task API and the workflow export, never inside the ABI prompt.
|
|
13261
|
+
*/
|
|
13262
|
+
function NodePluginParams({ nodeSlot, data }) {
|
|
13263
|
+
const id = (0, _xyflow_react.useNodeId)();
|
|
13264
|
+
const updates = useFlow((s) => s.updates);
|
|
13265
|
+
const t = (0, use_intl.useTranslations)("Workspace.nodes.base");
|
|
13266
|
+
const [open, setOpen] = (0, react.useState)(false);
|
|
13267
|
+
const { resolved: pluginId } = useResolvedPluginId(nodeSlot, data);
|
|
13268
|
+
const models = useNodePluginModels(nodeSlot, pluginId);
|
|
13269
|
+
const currentModel = String(data.pluginModel ?? "").trim();
|
|
13270
|
+
const entries = useNodePluginParams(nodeSlot, pluginId, models.includes(currentModel) ? currentModel : models[0] ?? void 0);
|
|
13271
|
+
const values = (0, react.useMemo)(() => readParams(data), [data]);
|
|
13272
|
+
(0, react.useEffect)(() => {
|
|
13273
|
+
const normalized = normalizePluginParams(entries, values);
|
|
13274
|
+
if (samePluginParams(normalized, data.pluginParams)) return;
|
|
13275
|
+
if (Object.keys(normalized).length === 0 && data.pluginParams === void 0) return;
|
|
13276
|
+
const next = { ...data };
|
|
13277
|
+
if (Object.keys(normalized).length === 0) delete next.pluginParams;
|
|
13278
|
+
else next.pluginParams = normalized;
|
|
13279
|
+
updates(id, next, { history: false });
|
|
13280
|
+
}, [
|
|
13281
|
+
id,
|
|
13282
|
+
data,
|
|
13283
|
+
entries,
|
|
13284
|
+
values,
|
|
13285
|
+
updates
|
|
13286
|
+
]);
|
|
13287
|
+
const setParam = (0, react.useCallback)((name, value) => {
|
|
13288
|
+
const next = { ...values };
|
|
13289
|
+
if (value === void 0) delete next[name];
|
|
13290
|
+
else next[name] = value;
|
|
13291
|
+
const normalized = normalizePluginParams(entries, next);
|
|
13292
|
+
const nextData = { ...data };
|
|
13293
|
+
if (Object.keys(normalized).length === 0) delete nextData.pluginParams;
|
|
13294
|
+
else nextData.pluginParams = normalized;
|
|
13295
|
+
updates(id, nextData);
|
|
13296
|
+
}, [
|
|
13297
|
+
id,
|
|
13298
|
+
data,
|
|
13299
|
+
entries,
|
|
13300
|
+
values,
|
|
13301
|
+
updates
|
|
13302
|
+
]);
|
|
13303
|
+
const reset = (0, react.useCallback)(() => {
|
|
13304
|
+
const nextData = { ...data };
|
|
13305
|
+
delete nextData.pluginParams;
|
|
13306
|
+
updates(id, nextData);
|
|
13307
|
+
}, [
|
|
13308
|
+
id,
|
|
13309
|
+
data,
|
|
13310
|
+
updates
|
|
13311
|
+
]);
|
|
13312
|
+
if (entries.length === 0) return null;
|
|
13313
|
+
const changed = Object.keys(values).length;
|
|
13314
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Card, {
|
|
13315
|
+
className: "p-3",
|
|
13316
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
13317
|
+
className: "space-y-2",
|
|
13318
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
13319
|
+
className: "flex items-center gap-1",
|
|
13320
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
13321
|
+
type: "button",
|
|
13322
|
+
onClick: () => setOpen((o) => !o),
|
|
13323
|
+
className: "nodrag flex flex-1 items-center gap-1 text-left text-sm font-medium text-muted-foreground hover:text-foreground",
|
|
13324
|
+
"aria-expanded": open,
|
|
13325
|
+
children: [
|
|
13326
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.ChevronRight, { className: `size-3.5 transition-transform ${open ? "rotate-90" : ""}` }),
|
|
13327
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("pluginParamsTitle") }),
|
|
13328
|
+
changed > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
13329
|
+
className: "ml-1 rounded-full bg-primary/10 px-1.5 text-[10px] font-semibold text-primary",
|
|
13330
|
+
children: changed
|
|
13331
|
+
})
|
|
13332
|
+
]
|
|
13333
|
+
}), changed > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
13334
|
+
type: "button",
|
|
13335
|
+
onClick: reset,
|
|
13336
|
+
title: t("pluginParamsReset"),
|
|
13337
|
+
"aria-label": t("pluginParamsReset"),
|
|
13338
|
+
className: "nodrag rounded p-1 text-muted-foreground hover:text-foreground",
|
|
13339
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.RotateCcw, { className: "size-3.5" })
|
|
13340
|
+
})]
|
|
13341
|
+
}), open && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
13342
|
+
className: "space-y-2",
|
|
13343
|
+
children: entries.map((entry) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ParamRow, {
|
|
13344
|
+
entry,
|
|
13345
|
+
value: values[entry.name],
|
|
13346
|
+
onChange: (v) => setParam(entry.name, v)
|
|
13347
|
+
}, entry.name))
|
|
13348
|
+
})]
|
|
13349
|
+
})
|
|
13350
|
+
});
|
|
13351
|
+
}
|
|
13352
|
+
function ParamRow({ entry, value, onChange }) {
|
|
13353
|
+
const t = (0, use_intl.useTranslations)("Workspace.nodes.base");
|
|
13354
|
+
const { name, spec } = entry;
|
|
13355
|
+
const label = spec.label ?? name;
|
|
13356
|
+
const inline = spec.type === "boolean";
|
|
13357
|
+
const changed = value !== void 0 && spec.default !== void 0;
|
|
13358
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
13359
|
+
className: inline ? "flex items-center justify-between gap-2" : "space-y-1",
|
|
13360
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Label, {
|
|
13361
|
+
className: "flex items-baseline gap-1.5 text-xs text-muted-foreground",
|
|
13362
|
+
title: spec.description,
|
|
13363
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: label }), changed && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
13364
|
+
className: "text-[10px] text-muted-foreground/60",
|
|
13365
|
+
children: t("pluginParamDefault", { value: String(spec.default) })
|
|
13366
|
+
})]
|
|
13367
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ParamControl, {
|
|
13368
|
+
spec,
|
|
13369
|
+
value,
|
|
13370
|
+
onChange
|
|
13371
|
+
})]
|
|
13372
|
+
});
|
|
13373
|
+
}
|
|
13374
|
+
function ParamControl({ spec, value, onChange }) {
|
|
13375
|
+
const effective = value === void 0 ? spec.default : value;
|
|
13376
|
+
switch (spec.type) {
|
|
13377
|
+
case "select": {
|
|
13378
|
+
const options = spec.options ?? [];
|
|
13379
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Select, {
|
|
13380
|
+
value: effective === void 0 ? "" : String(effective),
|
|
13381
|
+
onValueChange: (v) => {
|
|
13382
|
+
onChange(options.find((o) => String(o) === v));
|
|
13383
|
+
},
|
|
13384
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(SelectTrigger, {
|
|
13385
|
+
className: "nodrag w-full",
|
|
13386
|
+
size: "sm",
|
|
13387
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SelectValue, {})
|
|
13388
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SelectContent, { children: options.map((o) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SelectItem, {
|
|
13389
|
+
value: String(o),
|
|
13390
|
+
children: String(o)
|
|
13391
|
+
}, String(o))) })]
|
|
13392
|
+
});
|
|
13393
|
+
}
|
|
13394
|
+
case "boolean": return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Switch, {
|
|
13395
|
+
className: "nodrag",
|
|
13396
|
+
checked: effective === true,
|
|
13397
|
+
onCheckedChange: (checked) => onChange(checked)
|
|
13398
|
+
});
|
|
13399
|
+
case "number":
|
|
13400
|
+
case "integer": return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Input, {
|
|
13401
|
+
type: "number",
|
|
13402
|
+
className: "nodrag h-8",
|
|
13403
|
+
value: typeof effective === "number" ? String(effective) : "",
|
|
13404
|
+
min: spec.min,
|
|
13405
|
+
max: spec.max,
|
|
13406
|
+
step: spec.step ?? (spec.type === "integer" ? 1 : "any"),
|
|
13407
|
+
onChange: (e) => {
|
|
13408
|
+
const raw = e.target.value.trim();
|
|
13409
|
+
if (raw === "") {
|
|
13410
|
+
onChange(void 0);
|
|
13411
|
+
return;
|
|
13412
|
+
}
|
|
13413
|
+
const n = Number(raw);
|
|
13414
|
+
if (!Number.isFinite(n)) return;
|
|
13415
|
+
onChange(spec.type === "integer" ? Math.round(n) : n);
|
|
13416
|
+
}
|
|
13417
|
+
});
|
|
13418
|
+
case "text": return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Input, {
|
|
13419
|
+
type: "text",
|
|
13420
|
+
className: "nodrag h-8",
|
|
13421
|
+
value: typeof effective === "string" ? effective : "",
|
|
13422
|
+
onChange: (e) => {
|
|
13423
|
+
const v = e.target.value;
|
|
13424
|
+
onChange(v === "" ? void 0 : v);
|
|
13425
|
+
}
|
|
13426
|
+
});
|
|
13427
|
+
default: return null;
|
|
13428
|
+
}
|
|
13429
|
+
}
|
|
13430
|
+
//#endregion
|
|
12949
13431
|
//#region src/canvas/nodes/base/node-thinking-bubble.tsx
|
|
12950
13432
|
/**
|
|
12951
13433
|
* A transient, auto-scrolling bubble anchored to the right of a node that
|
|
@@ -13049,13 +13531,20 @@ const BaseNodeShell = (0, react.forwardRef)(({ className, selected, count, data,
|
|
|
13049
13531
|
] }),
|
|
13050
13532
|
renderPluginSelect && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
13051
13533
|
className: "p-4 pb-0 space-y-2",
|
|
13052
|
-
children: [
|
|
13053
|
-
|
|
13054
|
-
|
|
13055
|
-
|
|
13056
|
-
|
|
13057
|
-
|
|
13058
|
-
|
|
13534
|
+
children: [
|
|
13535
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(NodePluginIdSelect, {
|
|
13536
|
+
nodeSlot: feature,
|
|
13537
|
+
data: data ?? { feature }
|
|
13538
|
+
}),
|
|
13539
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(NodePluginModelSelect, {
|
|
13540
|
+
nodeSlot: feature,
|
|
13541
|
+
data: data ?? { feature }
|
|
13542
|
+
}),
|
|
13543
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(NodePluginParams, {
|
|
13544
|
+
nodeSlot: feature,
|
|
13545
|
+
data: data ?? { feature }
|
|
13546
|
+
})
|
|
13547
|
+
]
|
|
13059
13548
|
}),
|
|
13060
13549
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
13061
13550
|
className: "relative z-0",
|
|
@@ -14054,16 +14543,6 @@ function useNodeState(initialState, nodeData) {
|
|
|
14054
14543
|
}, [id, flowUpdates])];
|
|
14055
14544
|
}
|
|
14056
14545
|
//#endregion
|
|
14057
|
-
//#region src/canvas/ui/input.tsx
|
|
14058
|
-
function Input({ className, type, ...props }) {
|
|
14059
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
14060
|
-
type,
|
|
14061
|
-
"data-slot": "input",
|
|
14062
|
-
className: cn("file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground dark:bg-input/30 border-input h-9 w-full min-w-0 rounded-md border bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm", "focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]", "aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive", className),
|
|
14063
|
-
...props
|
|
14064
|
-
});
|
|
14065
|
-
}
|
|
14066
|
-
//#endregion
|
|
14067
14546
|
//#region src/canvas/nodes/add/add-link-node.tsx
|
|
14068
14547
|
const AddLinkNode = ({ selected, data }) => {
|
|
14069
14548
|
const t = (0, use_intl.useTranslations)("Workspace.nodes.add");
|
|
@@ -16152,24 +16631,47 @@ function useNodePluginResolver(feature) {
|
|
|
16152
16631
|
defaultPluginIdFromRegistry,
|
|
16153
16632
|
pluginOptions
|
|
16154
16633
|
]);
|
|
16634
|
+
/**
|
|
16635
|
+
* Model for router-style plugins: the node's `pluginModel` when it is one
|
|
16636
|
+
* of the active plugin's models (declared shortlist or live catalog), else
|
|
16637
|
+
* that plugin's default (first declared model). `undefined` when the
|
|
16638
|
+
* plugin declares none — the create-task API then omits the field entirely.
|
|
16639
|
+
*/
|
|
16640
|
+
const resolveActiveModel = (0, react.useCallback)(() => {
|
|
16641
|
+
if (!feature) return void 0;
|
|
16642
|
+
const pluginId = resolveActivePluginId();
|
|
16643
|
+
if (!pluginId) return void 0;
|
|
16644
|
+
const models = getNodePluginModels(feature, pluginId);
|
|
16645
|
+
if (models.length === 0) return void 0;
|
|
16646
|
+
const n = nodeId ? getNode(nodeId) : void 0;
|
|
16647
|
+
const fromData = String((n?.data)?.pluginModel ?? "").trim();
|
|
16648
|
+
return models.includes(fromData) ? fromData : models[0];
|
|
16649
|
+
}, [
|
|
16650
|
+
feature,
|
|
16651
|
+
nodeId,
|
|
16652
|
+
getNode,
|
|
16653
|
+
resolveActivePluginId
|
|
16654
|
+
]);
|
|
16155
16655
|
return {
|
|
16156
16656
|
pluginOptions,
|
|
16157
16657
|
defaultPluginIdFromRegistry,
|
|
16158
16658
|
resolveActivePluginId,
|
|
16159
|
-
resolveActiveModel
|
|
16659
|
+
resolveActiveModel,
|
|
16660
|
+
resolveActiveParams: (0, react.useCallback)(() => {
|
|
16160
16661
|
if (!feature) return void 0;
|
|
16161
16662
|
const pluginId = resolveActivePluginId();
|
|
16162
16663
|
if (!pluginId) return void 0;
|
|
16163
|
-
const
|
|
16164
|
-
if (
|
|
16165
|
-
const
|
|
16166
|
-
const
|
|
16167
|
-
return
|
|
16664
|
+
const entries = getNodePluginParams(feature, pluginId, resolveActiveModel());
|
|
16665
|
+
if (entries.length === 0) return void 0;
|
|
16666
|
+
const raw = ((nodeId ? getNode(nodeId) : void 0)?.data)?.pluginParams;
|
|
16667
|
+
const params = normalizePluginParams(entries, raw && typeof raw === "object" && !Array.isArray(raw) ? raw : void 0);
|
|
16668
|
+
return Object.keys(params).length > 0 ? params : void 0;
|
|
16168
16669
|
}, [
|
|
16169
16670
|
feature,
|
|
16170
16671
|
nodeId,
|
|
16171
16672
|
getNode,
|
|
16172
|
-
resolveActivePluginId
|
|
16673
|
+
resolveActivePluginId,
|
|
16674
|
+
resolveActiveModel
|
|
16173
16675
|
])
|
|
16174
16676
|
};
|
|
16175
16677
|
}
|
|
@@ -16265,7 +16767,7 @@ function useAbiExecution(opts) {
|
|
|
16265
16767
|
const expands = useFlow((s) => s.expands);
|
|
16266
16768
|
const { isLoading: taskLoading, createBatchTasks, cancelTasks } = useBatchTaskManager();
|
|
16267
16769
|
const loading = taskLoading || nodeExecutionStatus === "running";
|
|
16268
|
-
const { pluginOptions, resolveActivePluginId, resolveActiveModel } = useNodePluginResolver(feature);
|
|
16770
|
+
const { pluginOptions, resolveActivePluginId, resolveActiveModel, resolveActiveParams } = useNodePluginResolver(feature);
|
|
16269
16771
|
const { isLoaded: pluginsRegistryLoaded } = usePluginsRegistry();
|
|
16270
16772
|
const handleTaskUpdate = (0, react.useCallback)(async (task) => {
|
|
16271
16773
|
if (!nodeId) return;
|
|
@@ -16371,11 +16873,13 @@ function useAbiExecution(opts) {
|
|
|
16371
16873
|
prompt: prompts.length === 1 ? prompts[0] : prompts
|
|
16372
16874
|
});
|
|
16373
16875
|
const model = resolveActiveModel();
|
|
16876
|
+
const params = resolveActiveParams();
|
|
16374
16877
|
try {
|
|
16375
16878
|
const taskConfigs = prompts.map((prompt) => ({
|
|
16376
16879
|
feature,
|
|
16377
16880
|
pluginId,
|
|
16378
16881
|
...model ? { model } : {},
|
|
16882
|
+
...params ? { params } : {},
|
|
16379
16883
|
prompt,
|
|
16380
16884
|
nodeId
|
|
16381
16885
|
}));
|
|
@@ -16392,6 +16896,7 @@ function useAbiExecution(opts) {
|
|
|
16392
16896
|
createBatchTasks,
|
|
16393
16897
|
resolveActivePluginId,
|
|
16394
16898
|
resolveActiveModel,
|
|
16899
|
+
resolveActiveParams,
|
|
16395
16900
|
pluginOptions,
|
|
16396
16901
|
pluginsRegistryLoaded,
|
|
16397
16902
|
transformPrompts
|
|
@@ -17517,30 +18022,63 @@ var merge_video_audio_default = (0, react.memo)(MergeVideoAudioNode);
|
|
|
17517
18022
|
//#endregion
|
|
17518
18023
|
//#region src/canvas/nodes/compose/music-cover.tsx
|
|
17519
18024
|
const DEFAULT_STRENGTH$1 = .8;
|
|
18025
|
+
function UpstreamTextPreview({ label, text }) {
|
|
18026
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Card, {
|
|
18027
|
+
className: "p-3",
|
|
18028
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
18029
|
+
className: "space-y-2",
|
|
18030
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Label, {
|
|
18031
|
+
className: "text-sm font-medium text-muted-foreground",
|
|
18032
|
+
children: label
|
|
18033
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
18034
|
+
className: "text-sm text-foreground p-2 bg-background rounded border border-border/50 line-clamp-4 whitespace-pre-line",
|
|
18035
|
+
children: text
|
|
18036
|
+
})]
|
|
18037
|
+
})
|
|
18038
|
+
});
|
|
18039
|
+
}
|
|
17520
18040
|
const MusicCoverNode = ({ selected, data }) => {
|
|
17521
18041
|
const t = (0, use_intl.useTranslations)("Workspace.nodes");
|
|
17522
18042
|
const form = useAbiForm("music-cover");
|
|
17523
18043
|
const nodeId = (0, _xyflow_react.useNodeId)();
|
|
17524
18044
|
const edges = (0, _xyflow_react.useStore)((state) => state.edges);
|
|
17525
|
-
const { audioSourceId, refAudioSourceId } = (0, react.useMemo)(() => {
|
|
17526
|
-
|
|
18045
|
+
const { audioSourceId, refAudioSourceId, textSourceId, lyricsSourceId } = (0, react.useMemo)(() => {
|
|
18046
|
+
const ids = {
|
|
17527
18047
|
audioSourceId: null,
|
|
17528
|
-
refAudioSourceId: null
|
|
18048
|
+
refAudioSourceId: null,
|
|
18049
|
+
textSourceId: null,
|
|
18050
|
+
lyricsSourceId: null
|
|
17529
18051
|
};
|
|
17530
|
-
|
|
17531
|
-
let refAudioSrc = null;
|
|
18052
|
+
if (!nodeId) return ids;
|
|
17532
18053
|
for (const e of edges) {
|
|
17533
18054
|
if (e.target !== nodeId) continue;
|
|
17534
|
-
if (e.targetHandle === "in:audio")
|
|
17535
|
-
else if (e.targetHandle === "in:ref_audio")
|
|
18055
|
+
if (e.targetHandle === "in:audio") ids.audioSourceId = e.source;
|
|
18056
|
+
else if (e.targetHandle === "in:ref_audio") ids.refAudioSourceId = e.source;
|
|
18057
|
+
else if (e.targetHandle === "in:text") ids.textSourceId = e.source;
|
|
18058
|
+
else if (e.targetHandle === "in:lyrics") ids.lyricsSourceId = e.source;
|
|
17536
18059
|
}
|
|
17537
|
-
return
|
|
17538
|
-
audioSourceId: audioSrc,
|
|
17539
|
-
refAudioSourceId: refAudioSrc
|
|
17540
|
-
};
|
|
18060
|
+
return ids;
|
|
17541
18061
|
}, [edges, nodeId]);
|
|
17542
|
-
const upstreamIds = (0, react.useMemo)(() => [
|
|
18062
|
+
const upstreamIds = (0, react.useMemo)(() => [
|
|
18063
|
+
audioSourceId,
|
|
18064
|
+
refAudioSourceId,
|
|
18065
|
+
textSourceId,
|
|
18066
|
+
lyricsSourceId
|
|
18067
|
+
].filter((v) => !!v), [
|
|
18068
|
+
audioSourceId,
|
|
18069
|
+
refAudioSourceId,
|
|
18070
|
+
textSourceId,
|
|
18071
|
+
lyricsSourceId
|
|
18072
|
+
]);
|
|
17543
18073
|
const upstreamNodes = (0, _xyflow_react.useNodesData)(upstreamIds);
|
|
18074
|
+
const upstreamText = (sourceId) => {
|
|
18075
|
+
if (!sourceId) return void 0;
|
|
18076
|
+
const n = upstreamNodes.find((u) => u.id === sourceId);
|
|
18077
|
+
if (!n || n.type !== "textNode") return void 0;
|
|
18078
|
+
return coerceBaseNodeData(n.data).texts?.[0] ?? "";
|
|
18079
|
+
};
|
|
18080
|
+
const promptFromUpstream = upstreamText(textSourceId);
|
|
18081
|
+
const lyricsFromUpstream = upstreamText(lyricsSourceId);
|
|
17544
18082
|
const audioKey = (0, react.useMemo)(() => {
|
|
17545
18083
|
if (!audioSourceId) return void 0;
|
|
17546
18084
|
const n = upstreamNodes.find((u) => u.id === audioSourceId);
|
|
@@ -17603,14 +18141,20 @@ const MusicCoverNode = ({ selected, data }) => {
|
|
|
17603
18141
|
})]
|
|
17604
18142
|
})
|
|
17605
18143
|
}),
|
|
17606
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(
|
|
18144
|
+
promptFromUpstream !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(UpstreamTextPreview, {
|
|
18145
|
+
label: t("musicTasks.promptLabel"),
|
|
18146
|
+
text: promptFromUpstream
|
|
18147
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(NodeTextarea, {
|
|
17607
18148
|
label: t("musicTasks.promptLabel"),
|
|
17608
18149
|
icon: lucide_react.MessageSquare,
|
|
17609
18150
|
placeholder: t("musicTasks.promptPlaceholder"),
|
|
17610
18151
|
...form.bind("text"),
|
|
17611
18152
|
rows: 3
|
|
17612
18153
|
}),
|
|
17613
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(
|
|
18154
|
+
lyricsFromUpstream !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(UpstreamTextPreview, {
|
|
18155
|
+
label: t("musicTasks.lyricsLabel"),
|
|
18156
|
+
text: lyricsFromUpstream
|
|
18157
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(NodeTextarea, {
|
|
17614
18158
|
label: t("musicTasks.lyricsLabel"),
|
|
17615
18159
|
placeholder: t("musicTasks.lyricsPlaceholder"),
|
|
17616
18160
|
...form.bind("lyrics"),
|
|
@@ -18914,19 +19458,6 @@ const VideoImageGenVideoMoveNode = ({ selected, data }) => {
|
|
|
18914
19458
|
VideoImageGenVideoMoveNode.displayName = "VideoImageGenVideoMoveNode";
|
|
18915
19459
|
var video_image_gen_video_move_default = (0, react.memo)(VideoImageGenVideoMoveNode);
|
|
18916
19460
|
//#endregion
|
|
18917
|
-
//#region src/canvas/ui/switch.tsx
|
|
18918
|
-
function Switch({ className, ...props }) {
|
|
18919
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_radix_ui_react_switch.Root, {
|
|
18920
|
-
"data-slot": "switch",
|
|
18921
|
-
className: cn("peer data-[state=checked]:bg-primary data-[state=unchecked]:bg-input focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] dark:data-[state=unchecked]:bg-input/80 inline-flex h-[1.15rem] w-8 shrink-0 items-center rounded-full border border-transparent shadow-xs transition-all outline-none disabled:cursor-not-allowed disabled:opacity-50", className),
|
|
18922
|
-
...props,
|
|
18923
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_radix_ui_react_switch.Thumb, {
|
|
18924
|
-
"data-slot": "switch-thumb",
|
|
18925
|
-
className: cn("bg-background pointer-events-none block size-4 rounded-full ring-0 shadow-sm transition-transform data-[state=checked]:translate-x-[calc(100%-2px)] data-[state=unchecked]:translate-x-0")
|
|
18926
|
-
})
|
|
18927
|
-
});
|
|
18928
|
-
}
|
|
18929
|
-
//#endregion
|
|
18930
19461
|
//#region src/canvas/nodes/decompose/music-brief.tsx
|
|
18931
19462
|
function buildLanguageOptions$1(tLang) {
|
|
18932
19463
|
return [
|
|
@@ -21336,143 +21867,102 @@ AudioGenTextSpeechRecognizeNode.displayName = "AudioGenTextSpeechRecognizeNode";
|
|
|
21336
21867
|
var audio_gen_text_speech_recognize_default = (0, react.memo)(AudioGenTextSpeechRecognizeNode);
|
|
21337
21868
|
//#endregion
|
|
21338
21869
|
//#region src/canvas/nodes/transfer/convert-voice.tsx
|
|
21339
|
-
const
|
|
21340
|
-
|
|
21341
|
-
|
|
21342
|
-
}
|
|
21343
|
-
key: "male",
|
|
21344
|
-
value: "zh_male_1.wav"
|
|
21345
|
-
}];
|
|
21870
|
+
const firstKey = (value) => {
|
|
21871
|
+
const v = Array.isArray(value) ? value[0] : value;
|
|
21872
|
+
return typeof v === "string" && v ? v : void 0;
|
|
21873
|
+
};
|
|
21346
21874
|
const ConvertVoiceNode = ({ selected, data }) => {
|
|
21347
21875
|
const t = (0, use_intl.useTranslations)("Workspace.nodes");
|
|
21348
21876
|
const form = useAbiForm("convert_voice");
|
|
21349
|
-
const
|
|
21350
|
-
const
|
|
21351
|
-
const
|
|
21352
|
-
const
|
|
21877
|
+
const nodeId = (0, _xyflow_react.useNodeId)();
|
|
21878
|
+
const nodeLookup = (0, _xyflow_react.useStore)((state) => state.nodeLookup);
|
|
21879
|
+
const edges = (0, _xyflow_react.useStore)((state) => state.edges);
|
|
21880
|
+
const resolvedSpec = useNodeAbiSpec("convert_voice");
|
|
21881
|
+
const { sourceKey, refKey } = (0, react.useMemo)(() => {
|
|
21882
|
+
if (!nodeId) return {
|
|
21883
|
+
sourceKey: void 0,
|
|
21884
|
+
refKey: void 0
|
|
21885
|
+
};
|
|
21886
|
+
const values = collectHandleValues(nodeId, resolvedSpec, Array.from(nodeLookup.values()), edges);
|
|
21887
|
+
return {
|
|
21888
|
+
sourceKey: firstKey(values.audio),
|
|
21889
|
+
refKey: firstKey(values.ref_audio)
|
|
21890
|
+
};
|
|
21891
|
+
}, [
|
|
21892
|
+
nodeId,
|
|
21893
|
+
resolvedSpec,
|
|
21894
|
+
nodeLookup,
|
|
21895
|
+
edges
|
|
21896
|
+
]);
|
|
21353
21897
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AbiNodeShell, {
|
|
21354
21898
|
feature: "convert_voice",
|
|
21355
21899
|
form,
|
|
21356
21900
|
selected,
|
|
21901
|
+
className: "min-w-[360px]",
|
|
21357
21902
|
data,
|
|
21358
21903
|
title: t("titles.convertVoice"),
|
|
21359
21904
|
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.Atom, { className: "h-5 w-5" }),
|
|
21360
21905
|
executeLabel: t("actions.startReplace"),
|
|
21361
|
-
executeDisabled: !
|
|
21362
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(
|
|
21363
|
-
className: "p-
|
|
21364
|
-
|
|
21365
|
-
|
|
21366
|
-
|
|
21367
|
-
|
|
21368
|
-
|
|
21369
|
-
|
|
21370
|
-
|
|
21371
|
-
|
|
21372
|
-
}),
|
|
21373
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Select, {
|
|
21374
|
-
value: targetKey,
|
|
21375
|
-
onValueChange: (value) => form.set("targetKey", value),
|
|
21376
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(SelectTrigger, {
|
|
21377
|
-
id: "voice-select",
|
|
21378
|
-
className: "w-36 h-9",
|
|
21379
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SelectValue, { placeholder: t("convertVoice.selectVoice") })
|
|
21380
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SelectContent, { children: speakers.map((opt) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SelectItem, {
|
|
21381
|
-
value: opt.value,
|
|
21382
|
-
children: opt.key ? t(`common.voiceOptions.${opt.key}`) : opt.value
|
|
21383
|
-
}, opt.value)) })]
|
|
21384
|
-
}),
|
|
21385
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(SpeakerVoiceUploader, {
|
|
21386
|
-
trigger: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
|
|
21387
|
-
variant: "outline",
|
|
21388
|
-
size: "icon",
|
|
21389
|
-
className: "ml-1",
|
|
21390
|
-
title: t("convertVoice.uploadVoice"),
|
|
21391
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.Upload, { className: "w-4 h-4" })
|
|
21906
|
+
executeDisabled: !sourceKey || !refKey,
|
|
21907
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
21908
|
+
className: "p-4",
|
|
21909
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Card, {
|
|
21910
|
+
className: "p-3",
|
|
21911
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
21912
|
+
className: "space-y-2",
|
|
21913
|
+
children: [
|
|
21914
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Label, {
|
|
21915
|
+
className: "text-sm font-medium text-muted-foreground",
|
|
21916
|
+
children: t("compose.inputData")
|
|
21392
21917
|
}),
|
|
21393
|
-
|
|
21394
|
-
|
|
21395
|
-
|
|
21396
|
-
|
|
21397
|
-
|
|
21398
|
-
|
|
21399
|
-
|
|
21400
|
-
|
|
21401
|
-
|
|
21402
|
-
|
|
21403
|
-
|
|
21404
|
-
|
|
21405
|
-
|
|
21406
|
-
|
|
21918
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
21919
|
+
className: "flex gap-4",
|
|
21920
|
+
children: [sourceKey ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(MediaThumbnail, {
|
|
21921
|
+
fileKey: sourceKey,
|
|
21922
|
+
label: t("convertVoice.sourceAudio"),
|
|
21923
|
+
type: "audio"
|
|
21924
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
21925
|
+
className: "flex flex-col items-center gap-1.5",
|
|
21926
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
21927
|
+
className: "relative w-16 h-16 rounded-md border-2 border-gray-300 overflow-hidden bg-gray-100",
|
|
21928
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
21929
|
+
className: "flex items-center justify-center h-full w-full bg-orange-50",
|
|
21930
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.Music, { className: "w-6 h-6 text-orange-600" })
|
|
21931
|
+
})
|
|
21932
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
21933
|
+
className: "px-1.5 py-0.5 bg-orange-100 text-orange-700 text-xs font-medium rounded",
|
|
21934
|
+
children: t("convertVoice.sourceAudio")
|
|
21935
|
+
})]
|
|
21936
|
+
}), refKey ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(MediaThumbnail, {
|
|
21937
|
+
fileKey: refKey,
|
|
21938
|
+
label: t("convertVoice.referenceVoice"),
|
|
21939
|
+
type: "audio"
|
|
21940
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
21941
|
+
className: "flex flex-col items-center gap-1.5",
|
|
21942
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
21943
|
+
className: "relative w-16 h-16 rounded-md border-2 border-gray-300 overflow-hidden bg-gray-100",
|
|
21944
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
21945
|
+
className: "flex items-center justify-center h-full w-full bg-blue-50",
|
|
21946
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.Mic, { className: "w-6 h-6 text-blue-600" })
|
|
21947
|
+
})
|
|
21948
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
21949
|
+
className: "px-1.5 py-0.5 bg-blue-100 text-blue-700 text-xs font-medium rounded",
|
|
21950
|
+
children: t("convertVoice.referenceVoice")
|
|
21951
|
+
})]
|
|
21952
|
+
})]
|
|
21407
21953
|
}),
|
|
21408
|
-
|
|
21409
|
-
|
|
21410
|
-
|
|
21411
|
-
|
|
21412
|
-
|
|
21413
|
-
|
|
21414
|
-
})
|
|
21415
|
-
]
|
|
21954
|
+
(!sourceKey || !refKey) && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
21955
|
+
className: "text-xs text-red-500",
|
|
21956
|
+
children: t("convertVoice.connectHint")
|
|
21957
|
+
})
|
|
21958
|
+
]
|
|
21959
|
+
})
|
|
21416
21960
|
})
|
|
21417
21961
|
})
|
|
21418
21962
|
});
|
|
21419
21963
|
};
|
|
21964
|
+
ConvertVoiceNode.displayName = "ConvertVoiceNode";
|
|
21420
21965
|
var convert_voice_default = (0, react.memo)(ConvertVoiceNode);
|
|
21421
|
-
const SpeakerVoiceUploader = ({ trigger, onChange: _onChange }) => {
|
|
21422
|
-
const [_uploaded, setUploaded] = (0, react.useState)(false);
|
|
21423
|
-
const doUpload = async (files) => {
|
|
21424
|
-
logger.debug("Uploading files:", files);
|
|
21425
|
-
setUploaded(true);
|
|
21426
|
-
};
|
|
21427
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
21428
|
-
className: "cursor-pointer",
|
|
21429
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
21430
|
-
type: "file",
|
|
21431
|
-
multiple: true,
|
|
21432
|
-
hidden: true,
|
|
21433
|
-
onChange: (e) => {
|
|
21434
|
-
const files = Array.from(e.target.files || []);
|
|
21435
|
-
if (files.length > 0) doUpload(files);
|
|
21436
|
-
}
|
|
21437
|
-
}), trigger]
|
|
21438
|
-
}) });
|
|
21439
|
-
};
|
|
21440
|
-
const SpeakerVoiceRecorder$1 = ({ trigger, onChange: _onChange }) => {
|
|
21441
|
-
const t = (0, use_intl.useTranslations)("Workspace.nodes.convertVoice");
|
|
21442
|
-
const [file, _setFile] = (0, react.useState)();
|
|
21443
|
-
const onFinish = async () => {
|
|
21444
|
-
if (!file) return;
|
|
21445
|
-
logger.debug("Recording file:", file);
|
|
21446
|
-
};
|
|
21447
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Dialog, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(DialogTrigger, {
|
|
21448
|
-
asChild: true,
|
|
21449
|
-
children: trigger
|
|
21450
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(DialogContent, {
|
|
21451
|
-
className: "sm:max-w-md",
|
|
21452
|
-
children: [
|
|
21453
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(DialogHeader, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(DialogTitle, { children: t("recordAudio") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DialogDescription, { children: t("recordHint") })] }),
|
|
21454
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
21455
|
-
className: "overflow-auto scroll-smooth w-80 max-h-80",
|
|
21456
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
21457
|
-
className: "text-sm text-gray-500",
|
|
21458
|
-
children: t("recordNeedImpl")
|
|
21459
|
-
})
|
|
21460
|
-
}),
|
|
21461
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(DialogFooter, {
|
|
21462
|
-
className: "sm:justify-start",
|
|
21463
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DialogClose, {
|
|
21464
|
-
asChild: true,
|
|
21465
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
|
|
21466
|
-
type: "button",
|
|
21467
|
-
variant: "secondary",
|
|
21468
|
-
onClick: () => onFinish(),
|
|
21469
|
-
children: t("done")
|
|
21470
|
-
})
|
|
21471
|
-
})
|
|
21472
|
-
})
|
|
21473
|
-
]
|
|
21474
|
-
})] });
|
|
21475
|
-
};
|
|
21476
21966
|
//#endregion
|
|
21477
21967
|
//#region src/canvas/nodes/transfer/denoise-audio.tsx
|
|
21478
21968
|
const DenoiseAudioNode = ({ selected, data }) => {
|
|
@@ -22257,7 +22747,7 @@ const RemoveWatermarkNode = ({ selected, data }) => {
|
|
|
22257
22747
|
executeLabel: t("actions.removeWatermark"),
|
|
22258
22748
|
executeDisabled: !fileKeys?.length,
|
|
22259
22749
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Card, {
|
|
22260
|
-
className: "p-
|
|
22750
|
+
className: "mx-4 mb-4 p-4 space-y-4",
|
|
22261
22751
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
22262
22752
|
className: "text-sm text-muted-foreground",
|
|
22263
22753
|
children: t("removeWatermark.hint")
|
|
@@ -22402,6 +22892,109 @@ const SpeechGenVideoNode = ({ selected, data }) => {
|
|
|
22402
22892
|
SpeechGenVideoNode.displayName = "SpeechGenVideoNode";
|
|
22403
22893
|
var speech_gen_video_default = (0, react.memo)(SpeechGenVideoNode);
|
|
22404
22894
|
//#endregion
|
|
22895
|
+
//#region src/canvas/nodes/transfer/text-gen-audio.tsx
|
|
22896
|
+
const MAX_REF_AUDIOS = 3;
|
|
22897
|
+
const VOICE_TAG = "@voice";
|
|
22898
|
+
const TextGenAudioNode = ({ selected, data }) => {
|
|
22899
|
+
const t = (0, use_intl.useTranslations)("Workspace.nodes");
|
|
22900
|
+
const form = useAbiForm("text-gen-audio");
|
|
22901
|
+
const ids = useUpstreamNodeIds(data.ids);
|
|
22902
|
+
const fromNodes = (0, _xyflow_react.useNodesData)(ids);
|
|
22903
|
+
const refAudios = fromNodes.filter((node) => node.type === "audioNode").map((node) => coerceBaseNodeData(node.data).fileKeys).filter((keys) => !!keys && keys.length > 0);
|
|
22904
|
+
const textNode = fromNodes.find((node) => node.type === "textNode");
|
|
22905
|
+
const upstreamTexts = (0, react.useMemo)(() => textNode ? coerceBaseNodeData(textNode.data).texts || [] : [], [textNode]);
|
|
22906
|
+
const hasUpstreamTexts = upstreamTexts.length > 0;
|
|
22907
|
+
const prompt = form.state.text ?? "";
|
|
22908
|
+
const textareaRef = (0, react.useRef)(null);
|
|
22909
|
+
const insertVoiceTag = (0, react.useCallback)((tag) => {
|
|
22910
|
+
const textarea = textareaRef.current;
|
|
22911
|
+
if (!textarea) return;
|
|
22912
|
+
const start = textarea.selectionStart;
|
|
22913
|
+
const end = textarea.selectionEnd;
|
|
22914
|
+
form.set("text", prompt.substring(0, start) + tag + prompt.substring(end));
|
|
22915
|
+
setTimeout(() => {
|
|
22916
|
+
textarea.focus();
|
|
22917
|
+
const cursor = start + tag.length;
|
|
22918
|
+
textarea.setSelectionRange(cursor, cursor);
|
|
22919
|
+
}, 0);
|
|
22920
|
+
}, [prompt, form]);
|
|
22921
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AbiNodeShell, {
|
|
22922
|
+
feature: "text-gen-audio",
|
|
22923
|
+
form,
|
|
22924
|
+
selected,
|
|
22925
|
+
className: "min-w-[480px]",
|
|
22926
|
+
data,
|
|
22927
|
+
title: t("titles.textGenAudio"),
|
|
22928
|
+
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.AudioWaveform, { className: "h-5 w-5" }),
|
|
22929
|
+
executeLabel: t("actions.generateAudio"),
|
|
22930
|
+
executeDisabled: !hasUpstreamTexts && !prompt.trim(),
|
|
22931
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
22932
|
+
className: "p-4 space-y-4",
|
|
22933
|
+
children: [
|
|
22934
|
+
refAudios.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
22935
|
+
className: "space-y-2",
|
|
22936
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
22937
|
+
className: "text-sm font-medium text-muted-foreground",
|
|
22938
|
+
children: [t("textGenAudio.voiceReference"), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
22939
|
+
className: "ml-2 text-xs font-normal",
|
|
22940
|
+
children: [
|
|
22941
|
+
"(",
|
|
22942
|
+
refAudios.length,
|
|
22943
|
+
"/",
|
|
22944
|
+
MAX_REF_AUDIOS,
|
|
22945
|
+
")"
|
|
22946
|
+
]
|
|
22947
|
+
})]
|
|
22948
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
22949
|
+
className: "flex gap-3 flex-wrap",
|
|
22950
|
+
children: refAudios.slice(0, MAX_REF_AUDIOS).map((keys, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(MediaThumbnail, {
|
|
22951
|
+
fileKey: keys[0],
|
|
22952
|
+
label: `${VOICE_TAG}${index + 1}`,
|
|
22953
|
+
type: "audio",
|
|
22954
|
+
onClick: () => insertVoiceTag(`${VOICE_TAG}${index + 1}`)
|
|
22955
|
+
}, keys[0]))
|
|
22956
|
+
})]
|
|
22957
|
+
}),
|
|
22958
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
22959
|
+
className: "text-xs text-muted-foreground",
|
|
22960
|
+
children: t("textGenAudio.hint")
|
|
22961
|
+
}),
|
|
22962
|
+
hasUpstreamTexts ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
22963
|
+
className: "space-y-2",
|
|
22964
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
22965
|
+
className: "flex items-center gap-2 text-sm font-medium text-muted-foreground",
|
|
22966
|
+
children: [
|
|
22967
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.Sparkles, { className: "h-4 w-4" }),
|
|
22968
|
+
t("textGenAudio.promptLabel"),
|
|
22969
|
+
t("imageEdit.fromUpstream")
|
|
22970
|
+
]
|
|
22971
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
22972
|
+
className: "space-y-1 max-h-32 overflow-y-auto",
|
|
22973
|
+
children: upstreamTexts.map((text, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
22974
|
+
className: "text-sm text-foreground p-2 bg-background rounded border border-border/50 line-clamp-3",
|
|
22975
|
+
children: text
|
|
22976
|
+
}, `${index}-${text.slice(0, 48)}`))
|
|
22977
|
+
})]
|
|
22978
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
22979
|
+
className: "space-y-2",
|
|
22980
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
22981
|
+
className: "flex items-center gap-2 text-sm font-medium text-muted-foreground",
|
|
22982
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.Sparkles, { className: "h-4 w-4" }), t("textGenAudio.promptLabel")]
|
|
22983
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(NodeTextarea, {
|
|
22984
|
+
ref: textareaRef,
|
|
22985
|
+
showCard: false,
|
|
22986
|
+
placeholder: t("textGenAudio.promptPlaceholder"),
|
|
22987
|
+
...form.bind("text"),
|
|
22988
|
+
rows: 5
|
|
22989
|
+
})]
|
|
22990
|
+
})
|
|
22991
|
+
]
|
|
22992
|
+
})
|
|
22993
|
+
});
|
|
22994
|
+
};
|
|
22995
|
+
TextGenAudioNode.displayName = "TextGenAudioNode";
|
|
22996
|
+
var text_gen_audio_default = (0, react.memo)(TextGenAudioNode);
|
|
22997
|
+
//#endregion
|
|
22405
22998
|
//#region src/canvas/nodes/base/resolution-picker.tsx
|
|
22406
22999
|
function ResolutionPicker({ tiers, value, onChange }) {
|
|
22407
23000
|
const t = (0, use_intl.useTranslations)("Workspace.nodes");
|
|
@@ -22966,7 +23559,7 @@ const TextGenSpeechCloneNode = ({ selected, data }) => {
|
|
|
22966
23559
|
executeLabel: t("actions.generateSpeech"),
|
|
22967
23560
|
executeDisabled: !texts?.length || !refAudio,
|
|
22968
23561
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Card, {
|
|
22969
|
-
className: "p-
|
|
23562
|
+
className: "mx-4 mb-4 p-4 nodrag",
|
|
22970
23563
|
onPointerDown: (e) => e.stopPropagation(),
|
|
22971
23564
|
children: [
|
|
22972
23565
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
@@ -23080,7 +23673,7 @@ const TextGenSpeechInstructNode = ({ selected, data }) => {
|
|
|
23080
23673
|
executeLabel: t("actions.generateSpeech"),
|
|
23081
23674
|
executeDisabled: !texts?.length,
|
|
23082
23675
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Card, {
|
|
23083
|
-
className: "p-
|
|
23676
|
+
className: "mx-4 mb-4 p-4 nodrag",
|
|
23084
23677
|
onPointerDown: (e) => e.stopPropagation(),
|
|
23085
23678
|
children: [
|
|
23086
23679
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
@@ -23170,7 +23763,7 @@ const TextGenSpeechPresetNode = ({ selected, data }) => {
|
|
|
23170
23763
|
executeLabel: t("actions.generateSpeech"),
|
|
23171
23764
|
executeDisabled: !texts?.length,
|
|
23172
23765
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Card, {
|
|
23173
|
-
className: "p-
|
|
23766
|
+
className: "mx-4 mb-4 p-4 nodrag",
|
|
23174
23767
|
onPointerDown: (e) => e.stopPropagation(),
|
|
23175
23768
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
23176
23769
|
className: "mb-4 flex flex-wrap items-center gap-3",
|
|
@@ -23669,6 +24262,7 @@ const NODE_TYPES = {
|
|
|
23669
24262
|
genTextNode: text_gen_text_default,
|
|
23670
24263
|
textGenImageNode: text_gen_image_default,
|
|
23671
24264
|
textGenMusicNode: text_gen_music_default,
|
|
24265
|
+
textGenAudioNode: text_gen_audio_default,
|
|
23672
24266
|
textGenSpeechCloneNode: text_gen_speech_clone_default,
|
|
23673
24267
|
textGenSpeechCloneComposeNode: text_gen_speech_clone_compose_default,
|
|
23674
24268
|
textGenSpeechPresetNode: text_gen_speech_preset_default,
|
|
@@ -23760,6 +24354,7 @@ const NODE_CATEGORIES = {
|
|
|
23760
24354
|
"genTextNode",
|
|
23761
24355
|
"textGenImageNode",
|
|
23762
24356
|
"textGenMusicNode",
|
|
24357
|
+
"textGenAudioNode",
|
|
23763
24358
|
"textGenSpeechCloneNode",
|
|
23764
24359
|
"textGenSpeechPresetNode",
|
|
23765
24360
|
"textGenSpeechInstructNode",
|
|
@@ -24659,6 +25254,8 @@ exports.getGlobalFileCache = getGlobalFileCache;
|
|
|
24659
25254
|
exports.getGlobalFileQueue = getGlobalFileQueue;
|
|
24660
25255
|
exports.getImageDimensions = getImageDimensions;
|
|
24661
25256
|
exports.getMaterialFileUrls = getMaterialFileUrls;
|
|
25257
|
+
exports.getNodePluginModels = getNodePluginModels;
|
|
25258
|
+
exports.getNodePluginParams = getNodePluginParams;
|
|
24662
25259
|
exports.getPresignedUploadUrl = getPresignedUploadUrl;
|
|
24663
25260
|
exports.getPrivateFileUrl = getPrivateFileUrl;
|
|
24664
25261
|
exports.getSharedFileUrl = getSharedFileUrl;
|
|
@@ -24671,6 +25268,7 @@ exports.isImageFile = isImageFile;
|
|
|
24671
25268
|
exports.isVideoFile = isVideoFile;
|
|
24672
25269
|
exports.listMaterials = listMaterials;
|
|
24673
25270
|
exports.listTasks = listTasks;
|
|
25271
|
+
exports.loadPluginModelCatalog = loadPluginModelCatalog;
|
|
24674
25272
|
exports.normalizeTaskPayloadData = normalizeTaskPayloadData;
|
|
24675
25273
|
exports.pluginDisplayName = pluginDisplayName;
|
|
24676
25274
|
exports.refreshPluginsRegistry = refreshPluginsRegistry;
|
|
@@ -24690,11 +25288,13 @@ exports.useFileAsyncLoaderBatch = useFileAsyncLoaderBatch;
|
|
|
24690
25288
|
exports.useFileLoaderStats = useFileLoaderStats;
|
|
24691
25289
|
exports.useFileUrl = useFileUrl;
|
|
24692
25290
|
exports.useFlow = useFlow;
|
|
25291
|
+
exports.useLiveModelsStore = useLiveModelsStore;
|
|
24693
25292
|
exports.useMultipleUpload = useMultipleUpload;
|
|
24694
25293
|
exports.useNodeAbiSpec = useNodeAbiSpec;
|
|
24695
25294
|
exports.useNodePluginIds = useNodePluginIds;
|
|
24696
25295
|
exports.useNodePluginIdsUnion = useNodePluginIdsUnion;
|
|
24697
25296
|
exports.useNodePluginModels = useNodePluginModels;
|
|
25297
|
+
exports.useNodePluginParams = useNodePluginParams;
|
|
24698
25298
|
exports.useNodePluginResolver = useNodePluginResolver;
|
|
24699
25299
|
exports.useNodeState = useNodeState;
|
|
24700
25300
|
exports.useNodeTaskUpdate = useNodeTaskUpdate;
|