Break expression sync cycle and guard portrait lookup
This commit is contained in:
@@ -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)
|
||||||
|
|
||||||
|
|||||||
@@ -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,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,
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
|||||||
Reference in New Issue
Block a user