fix: eliminate 400 Bad Request errors for persona avatar thumbnails
- Add getSafeThumbnailUrl() helper function with comprehensive error handling - Replace all getThumbnailUrl() calls with safe wrapper that validates results - Use SVG data URI placeholder instead of 'img/user-default.png' to avoid 400 errors - Update img onerror handlers to fade opacity instead of trying invalid fallback paths - Add detailed console logging for debugging avatar loading issues - Improve updatePersonaAvatar() to only update src when valid URL is available This fixes persistent 400 errors on some Ubuntu systems where directory names with spaces (e.g., "User Avatars") caused thumbnail URL construction to fail. Affected functions: - getSafeThumbnailUrl() (new) - updatePersonaAvatar() - renderUserStats() - renderCharacterThoughts()
This commit is contained in:
@@ -93,6 +93,46 @@ let $userStatsContainer = null;
|
||||
let $infoBoxContainer = null;
|
||||
let $thoughtsContainer = null;
|
||||
|
||||
/**
|
||||
* Safely attempts to get a thumbnail URL with proper error handling.
|
||||
* Returns null if the URL cannot be generated to avoid 400 Bad Request errors.
|
||||
*
|
||||
* @param {string} type - The type of thumbnail ('persona' or 'avatar')
|
||||
* @param {string} filename - The filename to get thumbnail for
|
||||
* @returns {string|null} - The thumbnail URL or null if it fails
|
||||
*/
|
||||
function getSafeThumbnailUrl(type, filename) {
|
||||
// Return null if no filename provided
|
||||
if (!filename || filename === 'none') {
|
||||
console.log(`[RPG Companion] No valid filename provided for ${type} thumbnail`);
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
// Attempt to get thumbnail URL from SillyTavern API
|
||||
const url = getThumbnailUrl(type, filename);
|
||||
|
||||
// Validate that we got a string back
|
||||
if (typeof url !== 'string' || url.trim() === '') {
|
||||
console.warn(`[RPG Companion] getThumbnailUrl returned invalid result for ${type}:`, filename);
|
||||
return null;
|
||||
}
|
||||
|
||||
console.log(`[RPG Companion] Successfully generated ${type} thumbnail URL for: ${filename}`);
|
||||
return url;
|
||||
} catch (error) {
|
||||
// Log detailed error information for debugging
|
||||
console.error(`[RPG Companion] Failed to get ${type} thumbnail for "${filename}":`, error);
|
||||
console.error('[RPG Companion] Error details:', {
|
||||
type,
|
||||
filename,
|
||||
errorMessage: error.message,
|
||||
errorStack: error.stack
|
||||
});
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the extension settings from the global settings object.
|
||||
*/
|
||||
@@ -2739,14 +2779,15 @@ function renderUserStats() {
|
||||
}
|
||||
|
||||
// Get user portrait - handle both default-user and custom persona folders
|
||||
let userPortrait = 'img/user-default.png'; // fallback
|
||||
// Use a transparent placeholder as fallback to avoid 400 errors
|
||||
const transparentPixel = 'data:image/svg+xml,%3Csvg xmlns="http://www.w3.org/2000/svg" width="100" height="100"%3E%3Crect width="100" height="100" fill="%23cccccc" opacity="0.3"/%3E%3Ctext x="50%25" y="50%25" text-anchor="middle" dy=".3em" fill="%23666" font-size="40"%3E%3F%3C/text%3E%3C/svg%3E';
|
||||
let userPortrait = transparentPixel;
|
||||
|
||||
if (user_avatar) {
|
||||
// Try to get the thumbnail, but have a fallback
|
||||
try {
|
||||
userPortrait = getThumbnailUrl('persona', user_avatar) || 'img/user-default.png';
|
||||
} catch (e) {
|
||||
console.warn('[RPG Companion] Could not load user avatar, using default', e);
|
||||
userPortrait = 'img/user-default.png';
|
||||
// Try to get the thumbnail using our safe helper
|
||||
const thumbnailUrl = getSafeThumbnailUrl('persona', user_avatar);
|
||||
if (thumbnailUrl) {
|
||||
userPortrait = thumbnailUrl;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2757,7 +2798,7 @@ function renderUserStats() {
|
||||
<div class="rpg-stats-content">
|
||||
<div class="rpg-stats-left">
|
||||
<div style="display: flex; gap: clamp(4px, 0.8vh, 8px); align-items: center; flex-shrink: 0;">
|
||||
<img src="${userPortrait}" alt="${userName}" class="rpg-user-portrait" onerror="this.src='img/user-default.png'" />
|
||||
<img src="${userPortrait}" alt="${userName}" class="rpg-user-portrait" onerror="this.style.opacity='0.5';this.onerror=null;" />
|
||||
<div class="rpg-inventory-box">
|
||||
<div class="rpg-inventory-items rpg-editable" contenteditable="true" data-field="inventory" title="Click to edit">
|
||||
${stats.inventory || 'None'}
|
||||
@@ -3251,12 +3292,17 @@ function renderThoughts() {
|
||||
// If no characters parsed, show a placeholder editable card
|
||||
if (presentCharacters.length === 0) {
|
||||
// Get default character portrait (try to use the current character if in 1-on-1 chat)
|
||||
let defaultPortrait = 'img/user-default.png';
|
||||
// Use a transparent placeholder as fallback to avoid 400 errors
|
||||
const transparentPixel = 'data:image/svg+xml,%3Csvg xmlns="http://www.w3.org/2000/svg" width="100" height="100"%3E%3Crect width="100" height="100" fill="%23cccccc" opacity="0.3"/%3E%3Ctext x="50%25" y="50%25" text-anchor="middle" dy=".3em" fill="%23666" font-size="40"%3E%3F%3C/text%3E%3C/svg%3E';
|
||||
let defaultPortrait = transparentPixel;
|
||||
let defaultName = 'Character';
|
||||
|
||||
if (this_chid !== undefined && characters[this_chid]) {
|
||||
if (characters[this_chid].avatar && characters[this_chid].avatar !== 'none') {
|
||||
defaultPortrait = getThumbnailUrl('avatar', characters[this_chid].avatar);
|
||||
const thumbnailUrl = getSafeThumbnailUrl('avatar', characters[this_chid].avatar);
|
||||
if (thumbnailUrl) {
|
||||
defaultPortrait = thumbnailUrl;
|
||||
}
|
||||
}
|
||||
defaultName = characters[this_chid].name || 'Character';
|
||||
}
|
||||
@@ -3265,7 +3311,7 @@ function renderThoughts() {
|
||||
html += `
|
||||
<div class="rpg-character-card" data-character-name="${defaultName}">
|
||||
<div class="rpg-character-avatar">
|
||||
<img src="${defaultPortrait}" alt="${defaultName}" onerror="this.src='img/user-default.png'" />
|
||||
<img src="${defaultPortrait}" alt="${defaultName}" onerror="this.style.opacity='0.5';this.onerror=null;" />
|
||||
<div class="rpg-relationship-badge rpg-editable" contenteditable="true" data-character="${defaultName}" data-field="relationship" title="Click to edit (use emoji: ⚔️ ⚖️ ⭐ ❤️)">⚖️</div>
|
||||
</div>
|
||||
<div class="rpg-character-info">
|
||||
@@ -3282,7 +3328,9 @@ function renderThoughts() {
|
||||
html += '<div class="rpg-thoughts-content">';
|
||||
for (const char of presentCharacters) {
|
||||
// Find character portrait
|
||||
let characterPortrait = 'img/user-default.png';
|
||||
// Use a transparent placeholder as fallback to avoid 400 errors
|
||||
const transparentPixel = 'data:image/svg+xml,%3Csvg xmlns="http://www.w3.org/2000/svg" width="100" height="100"%3E%3Crect width="100" height="100" fill="%23cccccc" opacity="0.3"/%3E%3Ctext x="50%25" y="50%25" text-anchor="middle" dy=".3em" fill="%23666" font-size="40"%3E%3F%3C/text%3E%3C/svg%3E';
|
||||
let characterPortrait = transparentPixel;
|
||||
|
||||
// console.log('[RPG Companion] Looking for avatar for:', char.name);
|
||||
|
||||
@@ -3294,25 +3342,34 @@ function renderThoughts() {
|
||||
);
|
||||
|
||||
if (matchingMember && matchingMember.avatar && matchingMember.avatar !== 'none') {
|
||||
characterPortrait = getThumbnailUrl('avatar', matchingMember.avatar);
|
||||
const thumbnailUrl = getSafeThumbnailUrl('avatar', matchingMember.avatar);
|
||||
if (thumbnailUrl) {
|
||||
characterPortrait = thumbnailUrl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// For regular chats or if not found in group, search all characters
|
||||
if (characterPortrait === 'img/user-default.png' && characters && characters.length > 0) {
|
||||
if (characterPortrait === transparentPixel && characters && characters.length > 0) {
|
||||
const matchingCharacter = characters.find(c =>
|
||||
c && c.name && c.name.toLowerCase() === char.name.toLowerCase()
|
||||
);
|
||||
|
||||
if (matchingCharacter && matchingCharacter.avatar && matchingCharacter.avatar !== 'none') {
|
||||
characterPortrait = getThumbnailUrl('avatar', matchingCharacter.avatar);
|
||||
const thumbnailUrl = getSafeThumbnailUrl('avatar', matchingCharacter.avatar);
|
||||
if (thumbnailUrl) {
|
||||
characterPortrait = thumbnailUrl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If this is the current character in a 1-on-1 chat, use their portrait
|
||||
if (this_chid !== undefined && characters[this_chid] &&
|
||||
characters[this_chid].name && characters[this_chid].name.toLowerCase() === char.name.toLowerCase()) {
|
||||
characterPortrait = getThumbnailUrl('avatar', characters[this_chid].avatar);
|
||||
const thumbnailUrl = getSafeThumbnailUrl('avatar', characters[this_chid].avatar);
|
||||
if (thumbnailUrl) {
|
||||
characterPortrait = thumbnailUrl;
|
||||
}
|
||||
}
|
||||
|
||||
// Get relationship emoji
|
||||
@@ -3321,7 +3378,7 @@ function renderThoughts() {
|
||||
html += `
|
||||
<div class="rpg-character-card" data-character-name="${char.name}">
|
||||
<div class="rpg-character-avatar">
|
||||
<img src="${characterPortrait}" alt="${char.name}" onerror="this.src='img/user-default.png'" />
|
||||
<img src="${characterPortrait}" alt="${char.name}" onerror="this.style.opacity='0.5';this.onerror=null;" />
|
||||
<div class="rpg-relationship-badge rpg-editable" contenteditable="true" data-character="${char.name}" data-field="relationship" title="Click to edit (use emoji: ⚔️ ⚖️ ⭐ ❤️)">${relationshipEmoji}</div>
|
||||
</div>
|
||||
<div class="rpg-character-info">
|
||||
@@ -4594,23 +4651,33 @@ async function ensureHtmlCleaningRegex() {
|
||||
*/
|
||||
function updatePersonaAvatar() {
|
||||
const portraitImg = document.querySelector('.rpg-user-portrait');
|
||||
if (!portraitImg) return;
|
||||
if (!portraitImg) {
|
||||
console.log('[RPG Companion] Portrait image element not found in DOM');
|
||||
return;
|
||||
}
|
||||
|
||||
// Get current user_avatar from context instead of using imported value
|
||||
const context = getContext();
|
||||
const currentUserAvatar = context.user_avatar || user_avatar;
|
||||
|
||||
let userPortrait = 'img/user-default.png';
|
||||
if (currentUserAvatar) {
|
||||
try {
|
||||
userPortrait = getThumbnailUrl('persona', currentUserAvatar) || 'img/user-default.png';
|
||||
} catch (e) {
|
||||
console.warn('[RPG Companion] Could not load user avatar, using default', e);
|
||||
userPortrait = 'img/user-default.png';
|
||||
}
|
||||
}
|
||||
console.log('[RPG Companion] Attempting to update persona avatar:', currentUserAvatar);
|
||||
|
||||
portraitImg.src = userPortrait;
|
||||
// Try to get a valid thumbnail URL using our safe helper
|
||||
if (currentUserAvatar) {
|
||||
const thumbnailUrl = getSafeThumbnailUrl('persona', currentUserAvatar);
|
||||
|
||||
if (thumbnailUrl) {
|
||||
// Only update the src if we got a valid URL
|
||||
portraitImg.src = thumbnailUrl;
|
||||
console.log('[RPG Companion] Persona avatar updated successfully');
|
||||
} else {
|
||||
// Don't update the src if we couldn't get a valid URL
|
||||
// This prevents 400 errors and keeps the existing image
|
||||
console.warn('[RPG Companion] Could not get valid thumbnail URL for persona avatar, keeping existing image');
|
||||
}
|
||||
} else {
|
||||
console.log('[RPG Companion] No user avatar configured, keeping existing image');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user