mirror of
https://github.com/vegu-ai/talemate.git
synced 2026-09-01 19:48:52 +02:00
fix: resolve infinite loop in character visuals asset search and improve reference handling
This commit is contained in:
@@ -36,6 +36,7 @@
|
||||
- "Advance Time: Fixed toolbar time advancement being completely non-functional due to a missing websocket handler. Also fixed invalid ISO 8601 duration strings for the 1 week and 2 weeks options."
|
||||
- "Prompts UI: Fixed the template preview CodeMirror editor being clipped at the bottom across the Active, group, and LLM prompt template tabs, so the bottom scrollbar is fully visible."
|
||||
- "Node Graph: Fixed `data/MakeDict` and `data/MakeList` leaking their initial value as a shared mutable reference across graph executions. Downstream in-place mutations (e.g., writing into a dict fed into SetState) would persist on the node and appear on the next run as stale data. Both nodes now deep-copy their configured default before emitting it."
|
||||
- "Character Visuals: Fixed an infinite loop in the cover image and portrait tabs that spammed `scene_assets/search` requests at the backend whenever a character had no scene assets to fall back on as references. The reference-asset search now runs at most once per character, breaking the watcher feedback loop that previously kept firing throughout the time the Generate dialog was open."
|
||||
- "macOS Modifiers: Ctrl+click affordances throughout the UI now also accept Cmd (Meta) on macOS, where the OS reserves Ctrl+click for the system context menu. Tooltips, hints, and docs label the modifier as Cmd on Mac and Ctrl elsewhere. Affects regenerate, scene tools (narrator/director/actor/creative), world state manager, contextual generate, message asset image, autocomplete (Ctrl+Enter), and history navigation. (#261)"
|
||||
improvements:
|
||||
- "System Prompt Override Indicators: The system prompt override list now shows a pencil icon next to entries that have an active override, making it easy to see which prompts have been customized."
|
||||
|
||||
@@ -532,7 +532,12 @@ export default {
|
||||
},
|
||||
watch: {
|
||||
character: {
|
||||
handler(newVal) {
|
||||
handler(newVal, oldVal) {
|
||||
// Must precede checkReferenceAssets() — the assets watcher (immediate: true)
|
||||
// will also call it and relies on the gate already being reset.
|
||||
if (newVal?.name !== oldVal?.name) {
|
||||
this.hasCheckedReferences = false;
|
||||
}
|
||||
// Update local reactive references to default and current avatar
|
||||
this.defaultAvatarId = newVal?.avatar || null;
|
||||
this.currentAvatarId = newVal?.current_avatar || null;
|
||||
@@ -684,50 +689,28 @@ export default {
|
||||
this.userChangedReference = false;
|
||||
this.hasCheckedReferences = true;
|
||||
} else {
|
||||
// No local assets found, try searching
|
||||
// No local assets found
|
||||
this.referenceAssetIds = [];
|
||||
this.selectedReferenceAssetId = null;
|
||||
this.referenceSelectionReason = null;
|
||||
this.userChangedReference = false;
|
||||
|
||||
// Search for CHARACTER_PORTRAIT assets that can be used as references
|
||||
this.getWebsocket().send(JSON.stringify({
|
||||
type: 'scene_assets',
|
||||
action: 'search',
|
||||
vis_type: targetVisType,
|
||||
character_name: this.character.name,
|
||||
reference_vis_types: [targetVisType],
|
||||
}));
|
||||
|
||||
// Search once per character. Mark as checked BEFORE sending so re-entry
|
||||
// (e.g. via watchers firing while waiting for the response) doesn't spam
|
||||
// the backend with identical search requests.
|
||||
if (!this.hasCheckedReferences) {
|
||||
this.hasCheckedReferences = true;
|
||||
this.getWebsocket().send(JSON.stringify({
|
||||
type: 'scene_assets',
|
||||
action: 'search',
|
||||
vis_type: targetVisType,
|
||||
character_name: this.character.name,
|
||||
reference_vis_types: [targetVisType],
|
||||
}));
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
setReferenceAsset(assetId, reason) {
|
||||
// Legacy method for backward compatibility with search results
|
||||
// This sets a single asset, but we'll convert to ordered list
|
||||
const asset = this.anyCharacterAssets.find(a => a.id === assetId);
|
||||
if (asset) {
|
||||
const targetVisType = 'CHARACTER_PORTRAIT';
|
||||
const coverImageId = this.character?.cover_image;
|
||||
const { selectedId, orderedIds } = computeCharacterReferenceOptions(
|
||||
targetVisType,
|
||||
this.anyCharacterAssets,
|
||||
assetId,
|
||||
this.assets, // same-vis-type assets
|
||||
coverImageId // fallback
|
||||
);
|
||||
this.referenceAssetIds = orderedIds;
|
||||
this.selectedReferenceAssetId = selectedId || assetId;
|
||||
this.referenceSelectionReason = reason ? { reason } : null;
|
||||
this.userChangedReference = false;
|
||||
} else {
|
||||
this.referenceAssetIds = [assetId];
|
||||
this.selectedReferenceAssetId = assetId;
|
||||
this.referenceSelectionReason = reason ? { reason } : null;
|
||||
this.userChangedReference = false;
|
||||
}
|
||||
this.hasCheckedReferences = true;
|
||||
},
|
||||
|
||||
onReferenceSelectionChange(newId) {
|
||||
// Track that user manually changed the selection
|
||||
if (newId !== this.selectedReferenceAssetId && this.referenceSelectionReason) {
|
||||
@@ -911,18 +894,22 @@ export default {
|
||||
|
||||
// Handle asset search results
|
||||
if (data.type === 'asset_search_results') {
|
||||
if (data.character_name === this.character?.name &&
|
||||
if (data.character_name === this.character?.name &&
|
||||
data.vis_type === 'CHARACTER_PORTRAIT') {
|
||||
const assetIds = data.asset_ids || [];
|
||||
|
||||
// Use explicit reference assets if found from search
|
||||
|
||||
// Apply results directly. Calling checkReferenceAssets() here would
|
||||
// re-run the same logic, find no local assets, and fire another
|
||||
// search — looping until the dialog is closed. Any local-asset
|
||||
// changes that arrive after this request are picked up by the
|
||||
// assets / character.cover_image / character.avatar watchers.
|
||||
if (assetIds.length > 0) {
|
||||
// Recompute with search results included
|
||||
this.checkReferenceAssets();
|
||||
} else {
|
||||
// Fallback to local assets if search returned no explicit references
|
||||
this.checkReferenceAssets();
|
||||
this.referenceAssetIds = assetIds;
|
||||
this.selectedReferenceAssetId = assetIds[0];
|
||||
this.referenceSelectionReason = { reason: 'Found via asset search' };
|
||||
this.userChangedReference = false;
|
||||
}
|
||||
this.hasCheckedReferences = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -498,7 +498,12 @@ export default {
|
||||
},
|
||||
watch: {
|
||||
character: {
|
||||
handler(newVal) {
|
||||
handler(newVal, oldVal) {
|
||||
// Must precede checkReferenceAssets() — the assets watcher (immediate: true)
|
||||
// will also call it and relies on the gate already being reset.
|
||||
if (newVal?.name !== oldVal?.name) {
|
||||
this.hasCheckedReferences = false;
|
||||
}
|
||||
// Set selection to current cover image when character changes
|
||||
const coverImageId = newVal?.cover_image || null;
|
||||
this.selectedAssetId = coverImageId;
|
||||
@@ -613,50 +618,28 @@ export default {
|
||||
this.userChangedReference = false;
|
||||
this.hasCheckedReferences = true;
|
||||
} else {
|
||||
// No local assets found, try searching
|
||||
// No local assets found
|
||||
this.referenceAssetIds = [];
|
||||
this.selectedReferenceAssetId = null;
|
||||
this.referenceSelectionReason = null;
|
||||
this.userChangedReference = false;
|
||||
|
||||
// Search for CHARACTER_CARD assets that can be used as references
|
||||
this.getWebsocket().send(JSON.stringify({
|
||||
type: 'scene_assets',
|
||||
action: 'search',
|
||||
vis_type: targetVisType,
|
||||
character_name: this.character.name,
|
||||
reference_vis_types: [targetVisType],
|
||||
}));
|
||||
|
||||
// Search once per character. Mark as checked BEFORE sending so re-entry
|
||||
// (e.g. via watchers firing while waiting for the response) doesn't spam
|
||||
// the backend with identical search requests.
|
||||
if (!this.hasCheckedReferences) {
|
||||
this.hasCheckedReferences = true;
|
||||
this.getWebsocket().send(JSON.stringify({
|
||||
type: 'scene_assets',
|
||||
action: 'search',
|
||||
vis_type: targetVisType,
|
||||
character_name: this.character.name,
|
||||
reference_vis_types: [targetVisType],
|
||||
}));
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
setReferenceAsset(assetId, reason) {
|
||||
// Legacy method for backward compatibility with search results
|
||||
// This sets a single asset, but we'll convert to ordered list
|
||||
const asset = this.anyCharacterAssets.find(a => a.id === assetId);
|
||||
if (asset) {
|
||||
const targetVisType = 'CHARACTER_CARD';
|
||||
const avatarId = this.character?.avatar;
|
||||
const { selectedId, orderedIds } = computeCharacterReferenceOptions(
|
||||
targetVisType,
|
||||
this.anyCharacterAssets,
|
||||
assetId,
|
||||
this.assets, // same-vis-type assets
|
||||
avatarId // fallback
|
||||
);
|
||||
this.referenceAssetIds = orderedIds;
|
||||
this.selectedReferenceAssetId = selectedId || assetId;
|
||||
this.referenceSelectionReason = reason ? { reason } : null;
|
||||
this.userChangedReference = false;
|
||||
} else {
|
||||
this.referenceAssetIds = [assetId];
|
||||
this.selectedReferenceAssetId = assetId;
|
||||
this.referenceSelectionReason = reason ? { reason } : null;
|
||||
this.userChangedReference = false;
|
||||
}
|
||||
this.hasCheckedReferences = true;
|
||||
},
|
||||
|
||||
onReferenceSelectionChange(newId) {
|
||||
// Track that user manually changed the selection
|
||||
if (newId !== this.selectedReferenceAssetId && this.referenceSelectionReason) {
|
||||
@@ -824,18 +807,22 @@ export default {
|
||||
|
||||
// Handle asset search results
|
||||
if (data.type === 'asset_search_results') {
|
||||
if (data.character_name === this.character?.name &&
|
||||
if (data.character_name === this.character?.name &&
|
||||
data.vis_type === 'CHARACTER_CARD') {
|
||||
const assetIds = data.asset_ids || [];
|
||||
|
||||
// Use explicit reference assets if found from search
|
||||
|
||||
// Apply results directly. Calling checkReferenceAssets() here would
|
||||
// re-run the same logic, find no local assets, and fire another
|
||||
// search — looping until the dialog is closed. Any local-asset
|
||||
// changes that arrive after this request are picked up by the
|
||||
// assets / character.cover_image / character.avatar watchers.
|
||||
if (assetIds.length > 0) {
|
||||
// Recompute with search results included
|
||||
this.checkReferenceAssets();
|
||||
} else {
|
||||
// Fallback to local assets if search returned no explicit references
|
||||
this.checkReferenceAssets();
|
||||
this.referenceAssetIds = assetIds;
|
||||
this.selectedReferenceAssetId = assetIds[0];
|
||||
this.referenceSelectionReason = { reason: 'Found via asset search' };
|
||||
this.userChangedReference = false;
|
||||
}
|
||||
this.hasCheckedReferences = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user