@ai-game-assets/phaser 0.7.0 → 0.8.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/dist/debug-client.d.ts +3 -0
- package/dist/debug-client.d.ts.map +1 -1
- package/dist/debug-client.js +11 -0
- package/dist/debug-client.js.map +1 -1
- package/dist/designer-support.d.ts +1 -0
- package/dist/designer-support.d.ts.map +1 -1
- package/dist/designer-support.js +6 -1
- package/dist/designer-support.js.map +1 -1
- package/dist/designer.d.ts.map +1 -1
- package/dist/designer.js +351 -9
- package/dist/designer.js.map +1 -1
- package/dist/first-drafts.d.ts.map +1 -1
- package/dist/first-drafts.js +47 -31
- package/dist/first-drafts.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/voice-line-regeneration.d.ts +47 -0
- package/dist/voice-line-regeneration.d.ts.map +1 -0
- package/dist/voice-line-regeneration.js +185 -0
- package/dist/voice-line-regeneration.js.map +1 -0
- package/package.json +2 -2
package/dist/designer.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"designer.d.ts","sourceRoot":"","sources":["../src/designer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAGV,iBAAiB,EAOjB,eAAe,EACf,cAAc,EACd,sBAAsB,EAEvB,MAAM,sBAAsB,CAAC;AAQ9B,OAAO,EACL,kBAAkB,EAElB,KAAK,oBAAoB,EAE1B,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"designer.d.ts","sourceRoot":"","sources":["../src/designer.ts"],"names":[],"mappings":"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"}
|
package/dist/designer.js
CHANGED
|
@@ -4,6 +4,7 @@ import { ensureMissingAiAssetFirstDrafts } from "./first-drafts.js";
|
|
|
4
4
|
import { createLocalDerivedOption, openDeriveDialog, referenceImageForCandidate } from "./derive-dialog.js";
|
|
5
5
|
import { createMixedTilesetOption, isDesignerTilesetAsset, openTilesetBaseMixerDialog, openTilesetAnimationEditor, openTilesetAnimationMixerDialog, openTilesetEditor, tilesetAnimationForKey, resolveTilesetBaseMixCurrent, tilesetMetadataForAsset, tilesetTileGenerationOverride } from "./tileset-dialog.js";
|
|
6
6
|
import { aiTilesetAnimationTextureKey } from "./keys.js";
|
|
7
|
+
import { promotedVoiceId, regenerateAndPromoteVoiceLines, voiceLineRegenerationPlan } from "./voice-line-regeneration.js";
|
|
7
8
|
/**
|
|
8
9
|
* Resolves the canonical, untransformed source used by Edit Tileset. A pending
|
|
9
10
|
* option is its own editing lineage: when it has no saved source metadata, its
|
|
@@ -63,6 +64,13 @@ export function installAiAssetDesigner(options) {
|
|
|
63
64
|
let panelRevision = 0;
|
|
64
65
|
let promotionId = 0;
|
|
65
66
|
let activePromotionId;
|
|
67
|
+
let activeVoiceLineRegeneration;
|
|
68
|
+
let activeFirstDrafts = options.autoFirstDrafts !== false;
|
|
69
|
+
let destroyed = false;
|
|
70
|
+
let deferredVoiceLineManifestSync = false;
|
|
71
|
+
const deferredVoiceLinePendingAssetIds = new Set();
|
|
72
|
+
let deferredVoiceLineManifestSyncPromise;
|
|
73
|
+
let voiceLineBatchControlStates;
|
|
66
74
|
let mixingTileset = false;
|
|
67
75
|
const regenerateTilesetTile = async (assetId, tile, tileset, currentTileSrc) => {
|
|
68
76
|
const guide = await styleGuideRequest(styleGuideDraft);
|
|
@@ -100,11 +108,87 @@ export function installAiAssetDesigner(options) {
|
|
|
100
108
|
const mount = options.mount ?? document.body;
|
|
101
109
|
mount.append(elements.root);
|
|
102
110
|
bindKeyboardCapture(elements.root, options.scene);
|
|
111
|
+
const setVoiceLineBatchControlsLocked = (locked) => {
|
|
112
|
+
if (locked) {
|
|
113
|
+
if (voiceLineBatchControlStates)
|
|
114
|
+
return;
|
|
115
|
+
const controls = Array.from(elements.panel.querySelectorAll("button, input, select, textarea")).filter((control) => control !== elements.regenerateAllLinesButton);
|
|
116
|
+
voiceLineBatchControlStates = controls.map((control) => [control, control.disabled]);
|
|
117
|
+
for (const control of controls)
|
|
118
|
+
control.disabled = true;
|
|
119
|
+
elements.assetBrowser.setAttribute("inert", "");
|
|
120
|
+
elements.currentPreview.setAttribute("inert", "");
|
|
121
|
+
elements.options.setAttribute("inert", "");
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
for (const [control, wasDisabled] of voiceLineBatchControlStates ?? []) {
|
|
125
|
+
control.disabled = wasDisabled;
|
|
126
|
+
}
|
|
127
|
+
voiceLineBatchControlStates = undefined;
|
|
128
|
+
elements.assetBrowser.removeAttribute("inert");
|
|
129
|
+
elements.currentPreview.removeAttribute("inert");
|
|
130
|
+
elements.options.removeAttribute("inert");
|
|
131
|
+
};
|
|
132
|
+
const syncRegenerateAllLinesButton = () => {
|
|
133
|
+
if (activeVoiceLineRegeneration) {
|
|
134
|
+
const stopping = activeVoiceLineRegeneration.controller.signal.aborted;
|
|
135
|
+
elements.regenerateAllLinesButton.hidden = false;
|
|
136
|
+
elements.regenerateAllLinesButton.disabled = stopping;
|
|
137
|
+
elements.regenerateAllLinesButton.textContent = stopping
|
|
138
|
+
? "Stopping after current line..."
|
|
139
|
+
: "Stop after current line";
|
|
140
|
+
elements.regenerateAllLinesButton.title = stopping
|
|
141
|
+
? "The batch will stop after the current line is promoted."
|
|
142
|
+
: "Finish and promote the current line, then stop before generating another.";
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
const selectedAsset = manifest.assets[selectedAssetId];
|
|
146
|
+
const plan = voiceLineRegenerationPlan(manifest, selectedAssetId, selectedTargetId);
|
|
147
|
+
const { baseVoiceAssetId, lineAssetIds, missingTargetLineAssetIds } = plan;
|
|
148
|
+
const isBaseVoiceView = selectedAsset?.kind === "voice" &&
|
|
149
|
+
selectedTargetAssetId === baseVoiceAssetId &&
|
|
150
|
+
!selectedTilesetAnimationKey;
|
|
151
|
+
const hasPromotedVoice = Boolean(promotedVoiceId(manifest, baseVoiceAssetId));
|
|
152
|
+
const hasPendingBaseVoice = pendingOptions.has(baseVoiceAssetId);
|
|
153
|
+
const pendingLineCount = lineAssetIds.filter((assetId) => pendingOptions.has(assetId)).length;
|
|
154
|
+
const hasAnyPendingOption = pendingOptions.size > 0;
|
|
155
|
+
elements.regenerateAllLinesButton.textContent = "Regenerate all lines";
|
|
156
|
+
elements.regenerateAllLinesButton.hidden = !isBaseVoiceView;
|
|
157
|
+
elements.regenerateAllLinesButton.disabled =
|
|
158
|
+
!isBaseVoiceView ||
|
|
159
|
+
!hasPromotedVoice ||
|
|
160
|
+
hasPendingBaseVoice ||
|
|
161
|
+
pendingLineCount > 0 ||
|
|
162
|
+
hasAnyPendingOption ||
|
|
163
|
+
missingTargetLineAssetIds.length > 0 ||
|
|
164
|
+
lineAssetIds.length === 0 ||
|
|
165
|
+
activeFirstDrafts ||
|
|
166
|
+
Boolean(activeGeneration) ||
|
|
167
|
+
activePromotionId !== undefined;
|
|
168
|
+
elements.regenerateAllLinesButton.title = !isBaseVoiceView
|
|
169
|
+
? ""
|
|
170
|
+
: hasPendingBaseVoice
|
|
171
|
+
? "Promote the selected base voice before regenerating its lines."
|
|
172
|
+
: missingTargetLineAssetIds.length > 0
|
|
173
|
+
? `Create target variants for ${missingTargetLineAssetIds.length} linked line${missingTargetLineAssetIds.length === 1 ? "" : "s"} before regenerating this target voice.`
|
|
174
|
+
: pendingLineCount > 0
|
|
175
|
+
? `Promote or revert the ${pendingLineCount} pending linked line${pendingLineCount === 1 ? "" : "s"} before regenerating all lines.`
|
|
176
|
+
: hasAnyPendingOption
|
|
177
|
+
? "Promote or revert all pending asset options before regenerating voice lines."
|
|
178
|
+
: activeFirstDrafts
|
|
179
|
+
? "Wait for automatic first drafts to finish before regenerating voice lines."
|
|
180
|
+
: !hasPromotedVoice
|
|
181
|
+
? "Promote a base voice before regenerating its lines."
|
|
182
|
+
: lineAssetIds.length === 0
|
|
183
|
+
? "This voice has no linked lines to regenerate."
|
|
184
|
+
: `Generate and promote one new option for ${lineAssetIds.length} linked line${lineAssetIds.length === 1 ? "" : "s"}.`;
|
|
185
|
+
};
|
|
103
186
|
const syncPromoteAllButton = () => {
|
|
104
187
|
elements.promoteAllButton.disabled =
|
|
105
188
|
pendingOptions.size === 0 ||
|
|
106
189
|
Boolean(activeGeneration) ||
|
|
107
190
|
activePromotionId !== undefined;
|
|
191
|
+
syncRegenerateAllLinesButton();
|
|
108
192
|
};
|
|
109
193
|
const rememberPendingOption = (assetId, option, pending = {}) => {
|
|
110
194
|
pendingOptions.set(assetId, {
|
|
@@ -116,8 +200,53 @@ export function installAiAssetDesigner(options) {
|
|
|
116
200
|
});
|
|
117
201
|
syncPromoteAllButton();
|
|
118
202
|
};
|
|
203
|
+
const flushDeferredVoiceLineManifest = () => {
|
|
204
|
+
if (!deferredVoiceLineManifestSync)
|
|
205
|
+
return Promise.resolve(true);
|
|
206
|
+
if ([...deferredVoiceLinePendingAssetIds].some((assetId) => pendingOptions.has(assetId))) {
|
|
207
|
+
return Promise.resolve(false);
|
|
208
|
+
}
|
|
209
|
+
if (deferredVoiceLineManifestSyncPromise) {
|
|
210
|
+
return deferredVoiceLineManifestSyncPromise;
|
|
211
|
+
}
|
|
212
|
+
deferredVoiceLineManifestSyncPromise = (async () => {
|
|
213
|
+
try {
|
|
214
|
+
const syncedManifest = await client.syncManifestModule();
|
|
215
|
+
manifest = syncedManifest;
|
|
216
|
+
deferredVoiceLineManifestSync = false;
|
|
217
|
+
deferredVoiceLinePendingAssetIds.clear();
|
|
218
|
+
if (!destroyed) {
|
|
219
|
+
try {
|
|
220
|
+
options.onManifestUpdated?.(manifest);
|
|
221
|
+
}
|
|
222
|
+
catch (error) {
|
|
223
|
+
setStatus(elements, `Manifest refreshed, but the live game could not adopt it. ${errorMessage(error)}`, "error");
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
return true;
|
|
227
|
+
}
|
|
228
|
+
catch (error) {
|
|
229
|
+
if (!destroyed) {
|
|
230
|
+
setStatus(elements, `Could not refresh the generated manifest module. ${errorMessage(error)}`, "error");
|
|
231
|
+
}
|
|
232
|
+
return false;
|
|
233
|
+
}
|
|
234
|
+
finally {
|
|
235
|
+
deferredVoiceLineManifestSyncPromise = undefined;
|
|
236
|
+
}
|
|
237
|
+
})();
|
|
238
|
+
return deferredVoiceLineManifestSyncPromise;
|
|
239
|
+
};
|
|
240
|
+
const resolveDeferredVoiceLinePendingOption = (assetId) => {
|
|
241
|
+
if (!deferredVoiceLinePendingAssetIds.delete(assetId))
|
|
242
|
+
return;
|
|
243
|
+
if (deferredVoiceLinePendingAssetIds.size === 0) {
|
|
244
|
+
void flushDeferredVoiceLineManifest();
|
|
245
|
+
}
|
|
246
|
+
};
|
|
119
247
|
const forgetPendingOption = (assetId) => {
|
|
120
248
|
pendingOptions.delete(assetId);
|
|
249
|
+
resolveDeferredVoiceLinePendingOption(assetId);
|
|
121
250
|
syncPromoteAllButton();
|
|
122
251
|
};
|
|
123
252
|
const hasEditableTilesetAnimationFrames = (assetId, animationKey) => {
|
|
@@ -1417,6 +1546,189 @@ export function installAiAssetDesigner(options) {
|
|
|
1417
1546
|
finishGeneration(currentGenerationId);
|
|
1418
1547
|
}
|
|
1419
1548
|
});
|
|
1549
|
+
elements.regenerateAllLinesButton.addEventListener("click", async () => {
|
|
1550
|
+
if (activeVoiceLineRegeneration) {
|
|
1551
|
+
if (!activeVoiceLineRegeneration.controller.signal.aborted) {
|
|
1552
|
+
activeVoiceLineRegeneration.controller.abort();
|
|
1553
|
+
syncRegenerateAllLinesButton();
|
|
1554
|
+
setStatus(elements, "Stopping after the current line is promoted...", "busy");
|
|
1555
|
+
}
|
|
1556
|
+
return;
|
|
1557
|
+
}
|
|
1558
|
+
const voiceAssetId = selectedAssetId;
|
|
1559
|
+
const voice = manifest.assets[voiceAssetId];
|
|
1560
|
+
const plan = voiceLineRegenerationPlan(manifest, voiceAssetId, selectedTargetId);
|
|
1561
|
+
const { baseVoiceAssetId, lineAssetIds, missingTargetLineAssetIds } = plan;
|
|
1562
|
+
if (voice?.kind !== "voice" ||
|
|
1563
|
+
selectedTargetAssetId !== baseVoiceAssetId ||
|
|
1564
|
+
selectedTilesetAnimationKey ||
|
|
1565
|
+
activeFirstDrafts ||
|
|
1566
|
+
activeGeneration ||
|
|
1567
|
+
activePromotionId !== undefined) {
|
|
1568
|
+
return;
|
|
1569
|
+
}
|
|
1570
|
+
if (pendingOptions.has(baseVoiceAssetId)) {
|
|
1571
|
+
setStatus(elements, "Promote the selected base voice before regenerating its lines.", "error");
|
|
1572
|
+
return;
|
|
1573
|
+
}
|
|
1574
|
+
if (!promotedVoiceId(manifest, baseVoiceAssetId)) {
|
|
1575
|
+
setStatus(elements, "Promote a base voice before regenerating its lines.", "error");
|
|
1576
|
+
return;
|
|
1577
|
+
}
|
|
1578
|
+
if (missingTargetLineAssetIds.length > 0) {
|
|
1579
|
+
setStatus(elements, `Create target variants for ${missingTargetLineAssetIds.map(readableAssetName).join(", ")} before regenerating this target voice.`, "error");
|
|
1580
|
+
return;
|
|
1581
|
+
}
|
|
1582
|
+
if (lineAssetIds.length === 0) {
|
|
1583
|
+
setStatus(elements, "This voice has no linked lines to regenerate.", "error");
|
|
1584
|
+
return;
|
|
1585
|
+
}
|
|
1586
|
+
const pendingLineAssetIds = lineAssetIds.filter((assetId) => pendingOptions.has(assetId));
|
|
1587
|
+
if (pendingLineAssetIds.length > 0) {
|
|
1588
|
+
setStatus(elements, `Promote or revert pending options for ${pendingLineAssetIds.map(readableAssetName).join(", ")} before regenerating all lines.`, "error");
|
|
1589
|
+
return;
|
|
1590
|
+
}
|
|
1591
|
+
if (pendingOptions.size > 0) {
|
|
1592
|
+
setStatus(elements, "Promote or revert all pending asset options before regenerating voice lines.", "error");
|
|
1593
|
+
return;
|
|
1594
|
+
}
|
|
1595
|
+
const targetLabel = selectedTargetId
|
|
1596
|
+
? manifest.targets?.[selectedTargetId]?.label ?? readableAssetName(selectedTargetId)
|
|
1597
|
+
: "Default";
|
|
1598
|
+
const confirmed = window.confirm(`Regenerate and promote one new option for all ${lineAssetIds.length} linked line${lineAssetIds.length === 1 ? "" : "s"} of ${readableAssetName(baseVoiceAssetId)} (${targetLabel})?\n\nThis uses paid generation and replaces each line's active version as it completes.`);
|
|
1599
|
+
if (!confirmed)
|
|
1600
|
+
return;
|
|
1601
|
+
const currentPromotionId = promotionId + 1;
|
|
1602
|
+
const controller = new AbortController();
|
|
1603
|
+
promotionId = currentPromotionId;
|
|
1604
|
+
activePromotionId = currentPromotionId;
|
|
1605
|
+
activeVoiceLineRegeneration = { controller, id: currentPromotionId };
|
|
1606
|
+
elements.panel.setAttribute("aria-busy", "true");
|
|
1607
|
+
setVoiceLineBatchControlsLocked(true);
|
|
1608
|
+
syncPromoteAllButton();
|
|
1609
|
+
syncMixTilesetButton();
|
|
1610
|
+
let promotedCount = 0;
|
|
1611
|
+
let liveRefreshError;
|
|
1612
|
+
let batchCompletedCleanly = false;
|
|
1613
|
+
try {
|
|
1614
|
+
const result = await regenerateAndPromoteVoiceLines({
|
|
1615
|
+
manifest,
|
|
1616
|
+
voiceAssetId,
|
|
1617
|
+
targetId: selectedTargetId,
|
|
1618
|
+
client,
|
|
1619
|
+
signal: controller.signal,
|
|
1620
|
+
onProgress(progress) {
|
|
1621
|
+
if (activeVoiceLineRegeneration?.id !== currentPromotionId)
|
|
1622
|
+
return;
|
|
1623
|
+
const action = controller.signal.aborted
|
|
1624
|
+
? "Finishing"
|
|
1625
|
+
: progress.phase === "generation" ? "Generating" : "Promoting";
|
|
1626
|
+
setStatus(elements, `${action} ${readableAssetName(progress.assetId)} (${progress.index + 1}/${progress.total})...`, "busy");
|
|
1627
|
+
}
|
|
1628
|
+
});
|
|
1629
|
+
manifest = result.manifest;
|
|
1630
|
+
promotedCount = result.promoted.length;
|
|
1631
|
+
if (destroyed || activeVoiceLineRegeneration?.id !== currentPromotionId) {
|
|
1632
|
+
return;
|
|
1633
|
+
}
|
|
1634
|
+
if (result.manifestModuleSyncDeferred || result.manifestModuleSyncError) {
|
|
1635
|
+
deferredVoiceLineManifestSync = true;
|
|
1636
|
+
}
|
|
1637
|
+
if (result.manifestModuleSyncDeferred) {
|
|
1638
|
+
for (const failure of result.failures) {
|
|
1639
|
+
if (failure.option)
|
|
1640
|
+
deferredVoiceLinePendingAssetIds.add(failure.assetId);
|
|
1641
|
+
}
|
|
1642
|
+
if (result.cancelled?.option) {
|
|
1643
|
+
deferredVoiceLinePendingAssetIds.add(result.cancelled.assetId);
|
|
1644
|
+
}
|
|
1645
|
+
}
|
|
1646
|
+
for (const saved of result.promoted) {
|
|
1647
|
+
pendingOptions.delete(saved.asset.id);
|
|
1648
|
+
resolveDeferredVoiceLinePendingOption(saved.asset.id);
|
|
1649
|
+
formatDrafts.delete(saved.asset.id);
|
|
1650
|
+
try {
|
|
1651
|
+
(options.onAssetReady ?? options.onPreview)(saved.asset.id, resolveAssetUrl(saved.file), saved.asset);
|
|
1652
|
+
}
|
|
1653
|
+
catch (error) {
|
|
1654
|
+
liveRefreshError ??= { assetId: saved.asset.id, error };
|
|
1655
|
+
}
|
|
1656
|
+
}
|
|
1657
|
+
for (const failure of result.failures) {
|
|
1658
|
+
if (failure.option) {
|
|
1659
|
+
rememberPendingOption(failure.assetId, failure.option);
|
|
1660
|
+
}
|
|
1661
|
+
}
|
|
1662
|
+
if (result.cancelled?.option) {
|
|
1663
|
+
rememberPendingOption(result.cancelled.assetId, result.cancelled.option);
|
|
1664
|
+
}
|
|
1665
|
+
batchCompletedCleanly =
|
|
1666
|
+
promotedCount > 0 &&
|
|
1667
|
+
result.failures.length === 0 &&
|
|
1668
|
+
!result.cancelled &&
|
|
1669
|
+
!result.manifestModuleSyncError &&
|
|
1670
|
+
!result.manifestModuleSyncDeferred &&
|
|
1671
|
+
pendingOptions.size === 0;
|
|
1672
|
+
if (promotedCount > 0) {
|
|
1673
|
+
try {
|
|
1674
|
+
options.onManifestUpdated?.(manifest);
|
|
1675
|
+
}
|
|
1676
|
+
catch (error) {
|
|
1677
|
+
liveRefreshError ??= { assetId: baseVoiceAssetId, error };
|
|
1678
|
+
}
|
|
1679
|
+
}
|
|
1680
|
+
syncTargetAsset(baseVoiceAssetId);
|
|
1681
|
+
renderAssetBrowser();
|
|
1682
|
+
const firstFailure = result.failures[0];
|
|
1683
|
+
const processedCount = result.promoted.length + result.failures.length;
|
|
1684
|
+
if (result.cancelled) {
|
|
1685
|
+
setStatus(elements, `Stopped after ${processedCount}/${lineAssetIds.length} lines; ${promotedCount} promoted` +
|
|
1686
|
+
`${result.failures.length > 0 ? ` and ${result.failures.length} failed` : ""}.`, result.failures.length > 0 ? "error" : "success");
|
|
1687
|
+
}
|
|
1688
|
+
else if (firstFailure) {
|
|
1689
|
+
const unattemptedCount = lineAssetIds.length - processedCount;
|
|
1690
|
+
setStatus(elements, `Regenerated and promoted ${promotedCount}/${lineAssetIds.length} lines. ` +
|
|
1691
|
+
`${result.failures.length} failed; first failure was ${readableAssetName(firstFailure.assetId)} ` +
|
|
1692
|
+
`during ${firstFailure.phase}: ${errorMessage(firstFailure.error)}` +
|
|
1693
|
+
`${firstFailure.phase === "promotion" && unattemptedCount > 0
|
|
1694
|
+
? ` ${unattemptedCount} remaining line${unattemptedCount === 1 ? " was" : "s were"} not generated to avoid further paid requests while promotion is unavailable.`
|
|
1695
|
+
: ""}` +
|
|
1696
|
+
`${result.manifestModuleSyncDeferred
|
|
1697
|
+
? " The generated option remains selected; promote or revert it before restarting."
|
|
1698
|
+
: ""}`, "error");
|
|
1699
|
+
}
|
|
1700
|
+
else if (result.manifestModuleSyncError) {
|
|
1701
|
+
setStatus(elements, `Regenerated and promoted all ${promotedCount} lines, but the generated manifest module could not be refreshed. ${errorMessage(result.manifestModuleSyncError)}`, "error");
|
|
1702
|
+
}
|
|
1703
|
+
else if (liveRefreshError) {
|
|
1704
|
+
setStatus(elements, `Regenerated all ${promotedCount} lines, but ${readableAssetName(liveRefreshError.assetId)} ` +
|
|
1705
|
+
`could not refresh live. Restart to load it. ${errorMessage(liveRefreshError.error)}`, "error");
|
|
1706
|
+
}
|
|
1707
|
+
else {
|
|
1708
|
+
setStatus(elements, `Regenerated and promoted all ${promotedCount} lines.`, "success");
|
|
1709
|
+
}
|
|
1710
|
+
}
|
|
1711
|
+
catch (error) {
|
|
1712
|
+
setStatus(elements, `Could not regenerate voice lines. ${errorMessage(error)}`, "error");
|
|
1713
|
+
}
|
|
1714
|
+
finally {
|
|
1715
|
+
if (activeVoiceLineRegeneration?.id === currentPromotionId) {
|
|
1716
|
+
activeVoiceLineRegeneration = undefined;
|
|
1717
|
+
}
|
|
1718
|
+
if (activePromotionId === currentPromotionId) {
|
|
1719
|
+
activePromotionId = undefined;
|
|
1720
|
+
}
|
|
1721
|
+
if (!destroyed) {
|
|
1722
|
+
elements.panel.removeAttribute("aria-busy");
|
|
1723
|
+
setVoiceLineBatchControlsLocked(false);
|
|
1724
|
+
syncPromoteAllButton();
|
|
1725
|
+
syncMixTilesetButton();
|
|
1726
|
+
}
|
|
1727
|
+
}
|
|
1728
|
+
if (!destroyed && options.restartOnPromote && batchCompletedCleanly) {
|
|
1729
|
+
window.location.reload();
|
|
1730
|
+
}
|
|
1731
|
+
});
|
|
1420
1732
|
elements.uploadButton.addEventListener("click", async () => {
|
|
1421
1733
|
const asset = manifest.assets[selectedTargetAssetId];
|
|
1422
1734
|
try {
|
|
@@ -1865,6 +2177,7 @@ export function installAiAssetDesigner(options) {
|
|
|
1865
2177
|
liveRefreshErrors.push({ assetId, error });
|
|
1866
2178
|
}
|
|
1867
2179
|
pendingOptions.delete(assetId);
|
|
2180
|
+
resolveDeferredVoiceLinePendingOption(assetId);
|
|
1868
2181
|
formatDrafts.delete(assetId);
|
|
1869
2182
|
promotedCount += 1;
|
|
1870
2183
|
}
|
|
@@ -1904,8 +2217,19 @@ export function installAiAssetDesigner(options) {
|
|
|
1904
2217
|
window.location.reload();
|
|
1905
2218
|
}
|
|
1906
2219
|
});
|
|
1907
|
-
elements.restartButton.addEventListener("click", () => {
|
|
1908
|
-
|
|
2220
|
+
elements.restartButton.addEventListener("click", async () => {
|
|
2221
|
+
if (deferredVoiceLineManifestSync &&
|
|
2222
|
+
[...deferredVoiceLinePendingAssetIds].some((assetId) => pendingOptions.has(assetId))) {
|
|
2223
|
+
setStatus(elements, "Promote or revert the pending generated voice line before restarting.", "error");
|
|
2224
|
+
return;
|
|
2225
|
+
}
|
|
2226
|
+
if (deferredVoiceLineManifestSync) {
|
|
2227
|
+
setStatus(elements, "Refreshing the generated manifest module...", "busy");
|
|
2228
|
+
if (!await flushDeferredVoiceLineManifest())
|
|
2229
|
+
return;
|
|
2230
|
+
}
|
|
2231
|
+
if (!destroyed)
|
|
2232
|
+
window.location.reload();
|
|
1909
2233
|
});
|
|
1910
2234
|
elements.versionsButton.addEventListener("click", () => {
|
|
1911
2235
|
const asset = manifest.assets[selectedTargetAssetId];
|
|
@@ -2419,22 +2743,38 @@ export function installAiAssetDesigner(options) {
|
|
|
2419
2743
|
continueOnError: true,
|
|
2420
2744
|
onManifestUpdated: (updatedManifest) => {
|
|
2421
2745
|
manifest = updatedManifest;
|
|
2422
|
-
|
|
2423
|
-
|
|
2746
|
+
if (!destroyed) {
|
|
2747
|
+
options.onManifestUpdated?.(updatedManifest);
|
|
2748
|
+
syncAsset(selectedAssetId);
|
|
2749
|
+
}
|
|
2424
2750
|
},
|
|
2425
2751
|
onAssetReady: (assetId, textureKey, asset) => {
|
|
2426
|
-
(
|
|
2752
|
+
if (!destroyed) {
|
|
2753
|
+
(options.onAssetReady ?? options.onPreview)(assetId, textureKey, asset);
|
|
2754
|
+
}
|
|
2755
|
+
},
|
|
2756
|
+
onProgress: (progress) => {
|
|
2757
|
+
if (!destroyed)
|
|
2758
|
+
handleFirstDraftProgress(progress);
|
|
2427
2759
|
},
|
|
2428
|
-
onProgress: handleFirstDraftProgress,
|
|
2429
2760
|
onError: (error, assetId) => {
|
|
2430
|
-
|
|
2761
|
+
if (!destroyed) {
|
|
2762
|
+
setStatus(elements, `First draft failed for ${readableAssetName(assetId)}: ${errorMessage(error)}`, "error");
|
|
2763
|
+
}
|
|
2431
2764
|
}
|
|
2432
2765
|
}).then((result) => {
|
|
2433
|
-
if (result.generatedAssetIds.length > 0 && result.errors.length === 0) {
|
|
2766
|
+
if (!destroyed && result.generatedAssetIds.length > 0 && result.errors.length === 0) {
|
|
2434
2767
|
setStatus(elements, "First drafts generated.", "success");
|
|
2435
2768
|
}
|
|
2436
2769
|
}).catch((error) => {
|
|
2437
|
-
|
|
2770
|
+
if (!destroyed) {
|
|
2771
|
+
setStatus(elements, `First draft failed: ${errorMessage(error)}`, "error");
|
|
2772
|
+
}
|
|
2773
|
+
}).finally(() => {
|
|
2774
|
+
activeFirstDrafts = false;
|
|
2775
|
+
if (!destroyed) {
|
|
2776
|
+
syncPromoteAllButton();
|
|
2777
|
+
}
|
|
2438
2778
|
});
|
|
2439
2779
|
}
|
|
2440
2780
|
return {
|
|
@@ -2442,7 +2782,9 @@ export function installAiAssetDesigner(options) {
|
|
|
2442
2782
|
open: () => setOpen(true),
|
|
2443
2783
|
close: () => setOpen(false),
|
|
2444
2784
|
destroy: () => {
|
|
2785
|
+
destroyed = true;
|
|
2445
2786
|
activeGeneration?.controller.abort();
|
|
2787
|
+
activeVoiceLineRegeneration?.controller.abort();
|
|
2446
2788
|
stopStatusAnimation(elements.status);
|
|
2447
2789
|
dockPanel.destroy();
|
|
2448
2790
|
elements.root.remove();
|