Break expression sync cycle and guard portrait lookup

This commit is contained in:
Tremendoussly
2026-03-15 17:38:39 +01:00
parent 1d297aeecf
commit 9ef5b16663
5 changed files with 101 additions and 74 deletions
+10 -2
View File
@@ -134,7 +134,10 @@ import {
removeDesktopTabs, removeDesktopTabs,
updateStripWidgets updateStripWidgets
} from './src/systems/ui/desktop.js'; } from './src/systems/ui/desktop.js';
import { removeAlternatePresentCharactersPanel } from './src/systems/ui/alternatePresentCharacters.js'; import {
removeAlternatePresentCharactersPanel,
renderAlternatePresentCharacters
} from './src/systems/ui/alternatePresentCharacters.js';
import { import {
initExpressionSync, initExpressionSync,
queueExpressionCaptureForSpeaker, queueExpressionCaptureForSpeaker,
@@ -142,7 +145,8 @@ import {
onAlternatePresentCharactersVisibilityChanged, onAlternatePresentCharactersVisibilityChanged,
onHideDefaultExpressionDisplaySettingChanged, onHideDefaultExpressionDisplaySettingChanged,
clearExpressionSyncCache, clearExpressionSyncCache,
onExpressionSyncChatChanged onExpressionSyncChatChanged,
setExpressionSyncRefreshHandler
} from './src/systems/integration/expressionSync.js'; } from './src/systems/integration/expressionSync.js';
// Feature modules // Feature modules
@@ -173,6 +177,10 @@ import {
// Old state variable declarations removed - now imported from core modules // Old state variable declarations removed - now imported from core modules
// (extensionSettings, lastGeneratedData, committedTrackerData, etc. are now in src/core/state.js) // (extensionSettings, lastGeneratedData, committedTrackerData, etc. are now in src/core/state.js)
setExpressionSyncRefreshHandler(() => {
renderAlternatePresentCharacters({ useCommittedFallback: true });
});
// Utility functions removed - now imported from src/utils/avatars.js // Utility functions removed - now imported from src/utils/avatars.js
// (getSafeThumbnailUrl) // (getSafeThumbnailUrl)
+16 -70
View File
@@ -11,12 +11,11 @@ import {
extensionSettings, extensionSettings,
syncedExpressionPortraits, syncedExpressionPortraits,
setSyncedExpressionPortrait, setSyncedExpressionPortrait,
getSyncedExpressionPortrait,
removeSyncedExpressionPortrait removeSyncedExpressionPortrait
} from '../../core/state.js'; } from '../../core/state.js';
import { saveChatData } from '../../core/persistence.js'; import { saveChatData } from '../../core/persistence.js';
import { isSafeImageSrc, normalizeImageSrc, resolveImageUrl } from '../../utils/imageUrls.js'; import { normalizeImageSrc } from '../../utils/imageUrls.js';
import { renderAlternatePresentCharacters } from '../ui/alternatePresentCharacters.js'; import { isUsableExpressionSrc } from '../../utils/expressionPortraits.js';
let expressionContainerObserver = null; let expressionContainerObserver = null;
let expressionImageObserver = null; let expressionImageObserver = null;
@@ -28,53 +27,26 @@ let lastCapturedExpressionSrc = null;
let scheduledCaptureTimers = []; let scheduledCaptureTimers = [];
let hiddenExpressionStyleElement = null; let hiddenExpressionStyleElement = null;
let pendingCaptureRequestId = 0; let pendingCaptureRequestId = 0;
let refreshExpressionConsumersHandler = null;
function normalizeName(name) { function normalizeName(name) {
return String(name || '').trim().toLowerCase(); return String(name || '').trim().toLowerCase();
} }
function namesMatch(a, b) {
const left = normalizeName(a);
const right = normalizeName(b);
if (!left || !right) {
return false;
}
return left === right || left.startsWith(right + ' ') || right.startsWith(left + ' ');
}
function normalizeExpressionSrc(src) { function normalizeExpressionSrc(src) {
return normalizeImageSrc(src); return normalizeImageSrc(src);
} }
function resolveExpressionUrl(src) {
return resolveImageUrl(src);
}
function isDocumentLikeUrl(src) {
const candidate = resolveExpressionUrl(src);
if (!candidate) {
return false;
}
const current = new URL(window.location.href);
return candidate.origin === current.origin
&& candidate.pathname === current.pathname
&& candidate.search === current.search;
}
function isUsableExpressionSrc(src) {
const normalized = normalizeExpressionSrc(src);
if (!normalized) {
return false;
}
const lower = normalized.toLowerCase();
if (lower.includes('/img/default-expressions/') || lower.includes('/default-expressions/')) {
return false;
}
if (isDocumentLikeUrl(normalized)) {
return false;
}
if (!isSafeImageSrc(normalized)) {
return false;
}
return true;
}
function purgeInvalidSyncedExpressionPortraits() { function purgeInvalidSyncedExpressionPortraits() {
let changed = false; let changed = false;
@@ -92,34 +64,8 @@ function purgeInvalidSyncedExpressionPortraits() {
return changed; return changed;
} }
function namesMatch(a, b) { export function setExpressionSyncRefreshHandler(handler) {
const left = normalizeName(a); refreshExpressionConsumersHandler = typeof handler === 'function' ? handler : null;
const right = normalizeName(b);
if (!left || !right) {
return false;
}
return left === right || left.startsWith(right + ' ') || right.startsWith(left + ' ');
}
export function getExpressionPortraitForCharacter(characterName) {
const target = normalizeName(characterName);
if (!target) {
return null;
}
const exact = getSyncedExpressionPortrait(target);
if (isUsableExpressionSrc(exact)) {
return exact;
}
for (const [storedName, src] of Object.entries(syncedExpressionPortraits)) {
if (namesMatch(storedName, target) && isUsableExpressionSrc(src)) {
return src;
}
}
return null;
} }
function getLatestAssistantSpeakerName() { function getLatestAssistantSpeakerName() {
@@ -325,7 +271,7 @@ function findExpressionImageElement(speakerName = null) {
} }
function refreshExpressionConsumers() { function refreshExpressionConsumers() {
renderAlternatePresentCharacters({ useCommittedFallback: true }); refreshExpressionConsumersHandler?.();
} }
function getHideStyleCss() { function getHideStyleCss() {
+1 -1
View File
@@ -1,7 +1,7 @@
import { extensionSettings } from '../../core/state.js'; import { extensionSettings } from '../../core/state.js';
import { i18n } from '../../core/i18n.js'; import { i18n } from '../../core/i18n.js';
import { getExpressionPortraitForCharacter } from '../../utils/expressionPortraits.js';
import { getSafeImageSrc } from '../../utils/imageUrls.js'; import { getSafeImageSrc } from '../../utils/imageUrls.js';
import { getExpressionPortraitForCharacter } from '../integration/expressionSync.js';
import { import {
getPresentCharactersTrackerData, getPresentCharactersTrackerData,
parsePresentCharacters, parsePresentCharacters,
+73
View File
@@ -0,0 +1,73 @@
import {
syncedExpressionPortraits,
getSyncedExpressionPortrait
} from '../core/state.js';
import {
isSafeImageSrc,
normalizeImageSrc,
resolveImageUrl
} from './imageUrls.js';
function normalizeName(name) {
return String(name || '').trim().toLowerCase();
}
function namesMatch(a, b) {
const left = normalizeName(a);
const right = normalizeName(b);
if (!left || !right) {
return false;
}
return left === right || left.startsWith(right + ' ') || right.startsWith(left + ' ');
}
function isDocumentLikeUrl(src) {
const candidate = resolveImageUrl(src);
if (!candidate) {
return false;
}
const current = new URL(window.location.href);
return candidate.origin === current.origin
&& candidate.pathname === current.pathname
&& candidate.search === current.search;
}
export function isUsableExpressionSrc(src) {
const normalized = normalizeImageSrc(src);
if (!normalized) {
return false;
}
const lower = normalized.toLowerCase();
if (lower.includes('/img/default-expressions/') || lower.includes('/default-expressions/')) {
return false;
}
if (isDocumentLikeUrl(normalized)) {
return false;
}
return isSafeImageSrc(normalized);
}
export function getExpressionPortraitForCharacter(characterName) {
const target = normalizeName(characterName);
if (!target) {
return null;
}
const exact = getSyncedExpressionPortrait(target);
if (isUsableExpressionSrc(exact)) {
return exact;
}
for (const [storedName, src] of Object.entries(syncedExpressionPortraits)) {
if (namesMatch(storedName, target) && isUsableExpressionSrc(src)) {
return src;
}
}
return null;
}
+1 -1
View File
@@ -226,7 +226,7 @@ export function resolvePresentCharacterPortrait(name) {
} }
} }
if (this_chid !== undefined && characters[this_chid]?.name && namesMatch(characters[this_chid].name, name)) { if (this_chid !== undefined && characters?.[this_chid]?.name && namesMatch(characters[this_chid].name, name)) {
const thumbnailUrl = getSafeThumbnailUrl('avatar', characters[this_chid].avatar); const thumbnailUrl = getSafeThumbnailUrl('avatar', characters[this_chid].avatar);
if (thumbnailUrl) { if (thumbnailUrl) {
return thumbnailUrl; return thumbnailUrl;