Deprecate legacy loader and migrate all callsites to action-loader system with informative toasts (#5326)
* Refactor loader.js to use action-loader system and move overlay management into action-loader module - Deprecate showLoader() and hideLoader() in favor of action-loader API - Implement legacy functions as thin wrappers around ActionLoaderHandle - Move overlay management (showOverlay, hideOverlay, isOverlayDisplayed) into action-loader.js - Move Popup-based loader implementation and preloader cleanup to action-loader - Add loader.isBlocking() method to check for active blocking overlays * Migrate from legacy loader functions to action-loader API throughout codebase - Replace showLoader()/hideLoader() imports with loader from action-loader.js - Update firstLoadInit() to use loader.show() with title, message, and ToastMode.STATIC - Pass initLoaderHandle to getSettings() for early hide during onboarding flow - Refactor renameGroupOrCharacterChat() to use loader.show() instead of boolean flag - Wrap handleDeleteChat() with loader.show() and proper error handling - Update BulkEditOver... * Update loader titles and remove redundant reload notification - Change bookmark loader title from "Bookmark" to "Chat History" for clarity - Remove loader notification before extensions reload (redundant with browser reload) * lint fix * Add splash screen support to action loader with custom overlay content - Add `overlayContent` option to ActionLoaderOptions for custom HTML in overlay - Implement splash screen styles with centered logo, spinner, and message - Update firstLoadInit() to use custom splash screen instead of static toast - Pass custom content through showOverlay() to replace default spinner - Adjust non-blocking loader warning to account for custom overlay content * Refactor loader overlay to use DOM elements instead of HTML strings - Add createDefaultLoaderOverlay() function to generate fresh loader overlay elements - Export createOverlay() method on loader utility API for external use - Change overlayContent parameter type from string-only to string|HTMLElement|null - Add getOverlayContent() helper to normalize custom content for Popup - Update firstLoadInit() to build splash screen using DOM manipulation instead of template literals - Add splash-logo class and * Use a true ellipsis * Adjust sizing for desktop * Even truer ellipsis * Add transition to splash screen and fix blur animation on hideOverlay (#5338) * Initial plan * Blur entire splash screen on hideOverlay, not just spinner Co-authored-by: Cohee1207 <18619528+Cohee1207@users.noreply.github.com> Agent-Logs-Url: https://github.com/SillyTavern/SillyTavern/sessions/eee6c06d-7c9d-4363-bc8f-2647ed390368 * Add transition to splash-screen and fix transition detection Co-authored-by: Cohee1207 <18619528+Cohee1207@users.noreply.github.com> Agent-Logs-Url: https://github.com/SillyTavern/SillyTavern/sessions/9368bc36-31a0-4a58-aebd-7b569696ff2e --------- Co-authored-by: anthropic-code-agent[bot] <242468646+Claude@users.noreply.github.com> Co-authored-by: Cohee1207 <18619528+Cohee1207@users.noreply.github.com> * Add translations to supported locales * Localize logo alt on welcome screen --------- Co-authored-by: Cohee <18619528+Cohee1207@users.noreply.github.com> Co-authored-by: Claude <242468646+Claude@users.noreply.github.com>
This commit is contained in:
@@ -53,3 +53,36 @@
|
||||
opacity: 1;
|
||||
color: color-mix(in srgb, currentColor 40%, #e74c3c 60%);
|
||||
}
|
||||
|
||||
/* Splash screen styles for branded loading */
|
||||
#loader.splash-screen {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 4rem;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
transition: all var(--animation-duration-2x) ease-out;
|
||||
}
|
||||
|
||||
#loader.splash-screen #load-spinner {
|
||||
position: static;
|
||||
top: unset;
|
||||
left: unset;
|
||||
}
|
||||
|
||||
.splash-logo {
|
||||
/* max original px, or 50% on mobile */
|
||||
width: min(150px, 50%);
|
||||
height: auto;
|
||||
filter: drop-shadow(0 4px 8px rgba(0, 0, 0, 0.3));
|
||||
}
|
||||
|
||||
.splash-message {
|
||||
margin: 0;
|
||||
font-size: 1.25rem;
|
||||
font-weight: 500;
|
||||
opacity: 0.9;
|
||||
letter-spacing: 0.02em;
|
||||
}
|
||||
|
||||
@@ -1444,5 +1444,15 @@
|
||||
"Interleaved Thinking": "التفكير المتداخل",
|
||||
"Since Last User Message": "منذ آخر رسالة من المستخدم",
|
||||
"Active Tool Chain": "سلسلة الأدوات النشطة",
|
||||
"openrouter_interleaved_thinking_hint": "يرسل الاستدلال من رسائل المساعد السابقة مع طلبات استدعاء الأدوات للحفاظ على سياق التفكير المتداخل."
|
||||
"openrouter_interleaved_thinking_hint": "يرسل الاستدلال من رسائل المساعد السابقة مع طلبات استدعاء الأدوات للحفاظ على سياق التفكير المتداخل.",
|
||||
"SillyTavern Logo": "شعار SillyTavern",
|
||||
"Initializing…": "جارٍ التهيئة…",
|
||||
"Name not accepted, as it is the same as before (ignoring case and accents).": "الاسم غير مقبول، لأنه نفسه كما كان من قبل (تجاهل حالة الأحرف والنبرات).",
|
||||
"Rename Chat": "إعادة تسمية المحادثة",
|
||||
"Renaming chat…": "جارٍ إعادة تسمية المحادثة…",
|
||||
"Delete Chat": "حذف المحادثة",
|
||||
"Deleting chat…": "جارٍ حذف المحادثة…",
|
||||
"Loading chat…": "جارٍ تحميل المحادثة…",
|
||||
"Bulk Delete": "حذف جماعي",
|
||||
"Deleting ${0} character(s)…": "جارٍ حذف ${0} شخصية (شخصيات)…"
|
||||
}
|
||||
|
||||
@@ -1444,5 +1444,15 @@
|
||||
"Interleaved Thinking": "Verschränktes Denken",
|
||||
"Since Last User Message": "Seit letzter Nutzernachricht",
|
||||
"Active Tool Chain": "Aktive Tool-Kette",
|
||||
"openrouter_interleaved_thinking_hint": "Sendet Begründungen aus vorherigen Assistentennachrichten mit Tool-Aufrufen, um den Kontext für verschränktes Denken zu erhalten."
|
||||
"openrouter_interleaved_thinking_hint": "Sendet Begründungen aus vorherigen Assistentennachrichten mit Tool-Aufrufen, um den Kontext für verschränktes Denken zu erhalten.",
|
||||
"SillyTavern Logo": "SillyTavern-Logo",
|
||||
"Initializing…": "Initialisierung…",
|
||||
"Name not accepted, as it is the same as before (ignoring case and accents).": "Name nicht akzeptiert, da er derselbe wie zuvor ist (Groß-/Kleinschreibung und Akzente ignoriert).",
|
||||
"Rename Chat": "Chat umbenennen",
|
||||
"Renaming chat…": "Chat wird umbenannt…",
|
||||
"Delete Chat": "Chat löschen",
|
||||
"Deleting chat…": "Chat wird gelöscht…",
|
||||
"Loading chat…": "Chat wird geladen…",
|
||||
"Bulk Delete": "Massenlöschung",
|
||||
"Deleting ${0} character(s)…": "${0} Charakter(e) werden gelöscht…"
|
||||
}
|
||||
|
||||
@@ -1551,5 +1551,15 @@
|
||||
"Interleaved Thinking": "Razonamiento intercalado",
|
||||
"Since Last User Message": "Desde el último mensaje del usuario",
|
||||
"Active Tool Chain": "Cadena de herramientas activa",
|
||||
"openrouter_interleaved_thinking_hint": "Envía razonamiento de turnos anteriores del asistente junto con solicitudes de herramientas para mantener el contexto del razonamiento intercalado."
|
||||
"openrouter_interleaved_thinking_hint": "Envía razonamiento de turnos anteriores del asistente junto con solicitudes de herramientas para mantener el contexto del razonamiento intercalado.",
|
||||
"SillyTavern Logo": "Logo de SillyTavern",
|
||||
"Initializing…": "Inicializando…",
|
||||
"Name not accepted, as it is the same as before (ignoring case and accents).": "Nombre no aceptado, ya que es el mismo que antes (ignorando mayúsculas y acentos).",
|
||||
"Rename Chat": "Renombrar chat",
|
||||
"Renaming chat…": "Renombrando chat…",
|
||||
"Delete Chat": "Eliminar chat",
|
||||
"Deleting chat…": "Eliminando chat…",
|
||||
"Loading chat…": "Cargando chat…",
|
||||
"Bulk Delete": "Eliminación masiva",
|
||||
"Deleting ${0} character(s)…": "Eliminando ${0} personaje(s)…"
|
||||
}
|
||||
|
||||
@@ -2052,5 +2052,15 @@
|
||||
"Interleaved Thinking": "Raisonnement intercalé",
|
||||
"Since Last User Message": "Depuis le dernier message utilisateur",
|
||||
"Active Tool Chain": "Chaîne d'outils active",
|
||||
"openrouter_interleaved_thinking_hint": "Envoie le raisonnement des tours précédents de l'assistant avec les requêtes d'outils pour conserver le contexte du raisonnement intercalé."
|
||||
"openrouter_interleaved_thinking_hint": "Envoie le raisonnement des tours précédents de l'assistant avec les requêtes d'outils pour conserver le contexte du raisonnement intercalé.",
|
||||
"SillyTavern Logo": "Logo SillyTavern",
|
||||
"Initializing…": "Initialisation…",
|
||||
"Name not accepted, as it is the same as before (ignoring case and accents).": "Nom non accepté, car il est identique à l'ancien (en ignorant la casse et les accents).",
|
||||
"Rename Chat": "Renommer la discussion",
|
||||
"Renaming chat…": "Renommage de la discussion…",
|
||||
"Delete Chat": "Supprimer la discussion",
|
||||
"Deleting chat…": "Suppression de la discussion…",
|
||||
"Loading chat…": "Chargement de la discussion…",
|
||||
"Bulk Delete": "Suppression groupée",
|
||||
"Deleting ${0} character(s)…": "Suppression de ${0} personnage(s)…"
|
||||
}
|
||||
|
||||
@@ -1442,5 +1442,15 @@
|
||||
"Interleaved Thinking": "Fléttuð hugsun",
|
||||
"Since Last User Message": "Frá síðustu notandaskilaboðum",
|
||||
"Active Tool Chain": "Virk verkfærakeðja",
|
||||
"openrouter_interleaved_thinking_hint": "Sendir röksemdir úr fyrri aðstoðarskilaboðum með verkfæraköllum til að viðhalda samhengi fléttaðrar hugsunar."
|
||||
"openrouter_interleaved_thinking_hint": "Sendir röksemdir úr fyrri aðstoðarskilaboðum með verkfæraköllum til að viðhalda samhengi fléttaðrar hugsunar.",
|
||||
"SillyTavern Logo": "SillyTavern merki",
|
||||
"Initializing…": "Ræsir…",
|
||||
"Name not accepted, as it is the same as before (ignoring case and accents).": "Nafn ekki samþykkt, því það er það sama og áður (hunsar há-/lágstafi og brottmerki).",
|
||||
"Rename Chat": "Endurnefna spjall",
|
||||
"Renaming chat…": "Endurnefni spjall…",
|
||||
"Delete Chat": "Eyða spjalli",
|
||||
"Deleting chat…": "Eyði spjalli…",
|
||||
"Loading chat…": "Hleð spjalli…",
|
||||
"Bulk Delete": "Fjöldaeyðing",
|
||||
"Deleting ${0} character(s)…": "Eyði ${0} persónu(m)…"
|
||||
}
|
||||
|
||||
@@ -1444,5 +1444,15 @@
|
||||
"Interleaved Thinking": "Ragionamento intercalato",
|
||||
"Since Last User Message": "Dall'ultimo messaggio utente",
|
||||
"Active Tool Chain": "Catena di strumenti attiva",
|
||||
"openrouter_interleaved_thinking_hint": "Invia il ragionamento dai turni precedenti dell'assistente con le richieste di strumenti per mantenere il contesto del ragionamento intercalato."
|
||||
"openrouter_interleaved_thinking_hint": "Invia il ragionamento dai turni precedenti dell'assistente con le richieste di strumenti per mantenere il contesto del ragionamento intercalato.",
|
||||
"SillyTavern Logo": "Logo SillyTavern",
|
||||
"Initializing…": "Inizializzazione…",
|
||||
"Name not accepted, as it is the same as before (ignoring case and accents).": "Nome non accettato, poiché è lo stesso di prima (ignorando maiuscole e accenti).",
|
||||
"Rename Chat": "Rinomina chat",
|
||||
"Renaming chat…": "Rinominazione chat…",
|
||||
"Delete Chat": "Elimina chat",
|
||||
"Deleting chat…": "Eliminazione chat…",
|
||||
"Loading chat…": "Caricamento chat…",
|
||||
"Bulk Delete": "Eliminazione multipla",
|
||||
"Deleting ${0} character(s)…": "Eliminazione di ${0} personaggio/i…"
|
||||
}
|
||||
|
||||
@@ -1449,5 +1449,15 @@
|
||||
"Interleaved Thinking": "インターリーブ思考",
|
||||
"Since Last User Message": "直前のユーザーメッセージ以降",
|
||||
"Active Tool Chain": "アクティブなツールチェーン",
|
||||
"openrouter_interleaved_thinking_hint": "インターリーブ思考の文脈を維持するため、直前のアシスタントターンの推論をツール呼び出しリクエストとともに送信します。"
|
||||
"openrouter_interleaved_thinking_hint": "インターリーブ思考の文脈を維持するため、直前のアシスタントターンの推論をツール呼び出しリクエストとともに送信します。",
|
||||
"SillyTavern Logo": "SillyTavernロゴ",
|
||||
"Initializing…": "初期化中…",
|
||||
"Name not accepted, as it is the same as before (ignoring case and accents).": "名前が受け入れられませんでした。以前と同じです(大文字小文字とアクセントを無視)。",
|
||||
"Rename Chat": "チャット名を変更",
|
||||
"Renaming chat…": "チャット名を変更中…",
|
||||
"Delete Chat": "チャットを削除",
|
||||
"Deleting chat…": "チャットを削除中…",
|
||||
"Loading chat…": "チャットを読み込み中…",
|
||||
"Bulk Delete": "一括削除",
|
||||
"Deleting ${0} character(s)…": "${0}人のキャラクターを削除中…"
|
||||
}
|
||||
|
||||
@@ -1623,5 +1623,15 @@
|
||||
"Interleaved Thinking": "인터리브드 사고",
|
||||
"Since Last User Message": "마지막 사용자 메시지 이후",
|
||||
"Active Tool Chain": "활성 도구 체인",
|
||||
"openrouter_interleaved_thinking_hint": "인터리브드 사고 컨텍스트를 유지하기 위해 이전 어시스턴트 턴의 추론을 도구 호출 요청과 함께 전송합니다."
|
||||
"openrouter_interleaved_thinking_hint": "인터리브드 사고 컨텍스트를 유지하기 위해 이전 어시스턴트 턴의 추론을 도구 호출 요청과 함께 전송합니다.",
|
||||
"SillyTavern Logo": "SillyTavern 로고",
|
||||
"Initializing…": "초기화 중…",
|
||||
"Name not accepted, as it is the same as before (ignoring case and accents).": "이름이 승인되지 않았습니다. 이전과 동일합니다(대소문자 및 악센트 무시).",
|
||||
"Rename Chat": "채팅 이름 변경",
|
||||
"Renaming chat…": "채팅 이름 변경 중…",
|
||||
"Delete Chat": "채팅 삭제",
|
||||
"Deleting chat…": "채팅 삭제 중…",
|
||||
"Loading chat…": "채팅 불러오는 중…",
|
||||
"Bulk Delete": "대량 삭제",
|
||||
"Deleting ${0} character(s)…": "${0}개 캐릭터 삭제 중…"
|
||||
}
|
||||
|
||||
@@ -1440,5 +1440,15 @@
|
||||
"Interleaved Thinking": "Verweven redenering",
|
||||
"Since Last User Message": "Sinds laatste gebruikersbericht",
|
||||
"Active Tool Chain": "Actieve toolketen",
|
||||
"openrouter_interleaved_thinking_hint": "Verstuurt redenering uit eerdere assistentbeurten met tool-aanvragen om de context van verweven redenering te behouden."
|
||||
"openrouter_interleaved_thinking_hint": "Verstuurt redenering uit eerdere assistentbeurten met tool-aanvragen om de context van verweven redenering te behouden.",
|
||||
"SillyTavern Logo": "SillyTavern-logo",
|
||||
"Initializing…": "Initialiseren…",
|
||||
"Name not accepted, as it is the same as before (ignoring case and accents).": "Naam niet geaccepteerd, omdat deze hetzelfde is als voorheen (hoofdletters en accenten genegeerd).",
|
||||
"Rename Chat": "Chat hernoemen",
|
||||
"Renaming chat…": "Chat hernoemen…",
|
||||
"Delete Chat": "Chat verwijderen",
|
||||
"Deleting chat…": "Chat verwijderen…",
|
||||
"Loading chat…": "Chat laden…",
|
||||
"Bulk Delete": "Bulkverwijdering",
|
||||
"Deleting ${0} character(s)…": "${0} personage(s) verwijderen…"
|
||||
}
|
||||
|
||||
@@ -1442,5 +1442,15 @@
|
||||
"Interleaved Thinking": "Raciocínio intercalado",
|
||||
"Since Last User Message": "Desde a última mensagem do utilizador",
|
||||
"Active Tool Chain": "Cadeia de ferramentas ativa",
|
||||
"openrouter_interleaved_thinking_hint": "Envia o raciocínio de turnos anteriores do assistente com pedidos de ferramentas para manter o contexto de raciocínio intercalado."
|
||||
"openrouter_interleaved_thinking_hint": "Envia o raciocínio de turnos anteriores do assistente com pedidos de ferramentas para manter o contexto de raciocínio intercalado.",
|
||||
"SillyTavern Logo": "Logótipo do SillyTavern",
|
||||
"Initializing…": "A inicializar…",
|
||||
"Name not accepted, as it is the same as before (ignoring case and accents).": "Nome não aceite, pois é o mesmo que antes (ignorando maiúsculas e acentos).",
|
||||
"Rename Chat": "Renomear chat",
|
||||
"Renaming chat…": "A renomear chat…",
|
||||
"Delete Chat": "Eliminar chat",
|
||||
"Deleting chat…": "A eliminar chat…",
|
||||
"Loading chat…": "A carregar chat…",
|
||||
"Bulk Delete": "Eliminação em massa",
|
||||
"Deleting ${0} character(s)…": "A eliminar ${0} personagem(ns)…"
|
||||
}
|
||||
|
||||
@@ -2744,5 +2744,15 @@
|
||||
"Interleaved Thinking": "Чередующееся рассуждение",
|
||||
"Since Last User Message": "С момента последнего сообщения пользователя",
|
||||
"Active Tool Chain": "Активная цепочка инструментов",
|
||||
"openrouter_interleaved_thinking_hint": "Отправляет рассуждение из предыдущих ходов ассистента вместе с запросами вызова инструментов, чтобы сохранять контекст чередующегося рассуждения."
|
||||
"openrouter_interleaved_thinking_hint": "Отправляет рассуждение из предыдущих ходов ассистента вместе с запросами вызова инструментов, чтобы сохранять контекст чередующегося рассуждения.",
|
||||
"SillyTavern Logo": "Логотип SillyTavern",
|
||||
"Initializing…": "Инициализация…",
|
||||
"Name not accepted, as it is the same as before (ignoring case and accents).": "Имя не принято, так как оно совпадает с предыдущим (игнорируя регистр и акценты).",
|
||||
"Rename Chat": "Переименовать чат",
|
||||
"Renaming chat…": "Переименование чата…",
|
||||
"Delete Chat": "Удалить чат",
|
||||
"Deleting chat…": "Удаление чата…",
|
||||
"Loading chat…": "Загрузка чата…",
|
||||
"Bulk Delete": "Массовое удаление",
|
||||
"Deleting ${0} character(s)…": "Удаление ${0} персонажа(ей)…"
|
||||
}
|
||||
|
||||
@@ -1461,5 +1461,15 @@
|
||||
"Interleaved Thinking": "การคิดแบบสลับขั้น",
|
||||
"Since Last User Message": "ตั้งแต่ข้อความล่าสุดของผู้ใช้",
|
||||
"Active Tool Chain": "สายโซ่เครื่องมือที่ใช้งานอยู่",
|
||||
"openrouter_interleaved_thinking_hint": "ส่งเหตุผลจากเทิร์นก่อนหน้าของผู้ช่วยไปพร้อมกับคำขอเรียกใช้เครื่องมือ เพื่อคงบริบทการคิดแบบสลับขั้นไว้"
|
||||
"openrouter_interleaved_thinking_hint": "ส่งเหตุผลจากเทิร์นก่อนหน้าของผู้ช่วยไปพร้อมกับคำขอเรียกใช้เครื่องมือ เพื่อคงบริบทการคิดแบบสลับขั้นไว้",
|
||||
"SillyTavern Logo": "โลโก้ SillyTavern",
|
||||
"Initializing…": "กำลังเริ่มต้น…",
|
||||
"Name not accepted, as it is the same as before (ignoring case and accents).": "ชื่อไม่ได้รับการยอมรับ เนื่องจากเหมือนกับก่อนหน้านี้ (ไม่สนใจตัวพิมพ์ใหญ่เล็กและเครื่องหมายเสียง)",
|
||||
"Rename Chat": "เปลี่ยนชื่อแชท",
|
||||
"Renaming chat…": "กำลังเปลี่ยนชื่อแชท…",
|
||||
"Delete Chat": "ลบแชท",
|
||||
"Deleting chat…": "กำลังลบแชท…",
|
||||
"Loading chat…": "กำลังโหลดแชท…",
|
||||
"Bulk Delete": "ลบจำนวนมาก",
|
||||
"Deleting ${0} character(s)…": "กำลังลบ ${0} ตัวละคร…"
|
||||
}
|
||||
|
||||
@@ -1442,5 +1442,15 @@
|
||||
"Interleaved Thinking": "Черговане мислення",
|
||||
"Since Last User Message": "Від останнього повідомлення користувача",
|
||||
"Active Tool Chain": "Активний ланцюжок інструментів",
|
||||
"openrouter_interleaved_thinking_hint": "Надсилає міркування з попередніх ходів асистента разом із запитами виклику інструментів, щоб зберегти контекст чергованого мислення."
|
||||
"openrouter_interleaved_thinking_hint": "Надсилає міркування з попередніх ходів асистента разом із запитами виклику інструментів, щоб зберегти контекст чергованого мислення.",
|
||||
"SillyTavern Logo": "Логотип SillyTavern",
|
||||
"Initializing…": "Ініціалізація…",
|
||||
"Name not accepted, as it is the same as before (ignoring case and accents).": "Ім'я не прийнято, оскільки воно збігається з попереднім (ігноруючи регістр та акценти).",
|
||||
"Rename Chat": "Перейменувати чат",
|
||||
"Renaming chat…": "Перейменування чату…",
|
||||
"Delete Chat": "Видалити чат",
|
||||
"Deleting chat…": "Видалення чату…",
|
||||
"Loading chat…": "Завантаження чату…",
|
||||
"Bulk Delete": "Масове видалення",
|
||||
"Deleting ${0} character(s)…": "Видалення ${0} персонажа(ів)…"
|
||||
}
|
||||
|
||||
@@ -1442,5 +1442,15 @@
|
||||
"Interleaved Thinking": "Lập luận xen kẽ",
|
||||
"Since Last User Message": "Kể từ tin nhắn người dùng gần nhất",
|
||||
"Active Tool Chain": "Chuỗi công cụ đang hoạt động",
|
||||
"openrouter_interleaved_thinking_hint": "Gửi lập luận từ các lượt trợ lý trước đó cùng với yêu cầu gọi công cụ để duy trì ngữ cảnh lập luận xen kẽ."
|
||||
"openrouter_interleaved_thinking_hint": "Gửi lập luận từ các lượt trợ lý trước đó cùng với yêu cầu gọi công cụ để duy trì ngữ cảnh lập luận xen kẽ.",
|
||||
"SillyTavern Logo": "Logo SillyTavern",
|
||||
"Initializing…": "Đang khởi tạo…",
|
||||
"Name not accepted, as it is the same as before (ignoring case and accents).": "Tên không được chấp nhận vì giống như trước đó (bỏ qua chữ hoa/thường và dấu).",
|
||||
"Rename Chat": "Đổi tên cuộc trò chuyện",
|
||||
"Renaming chat…": "Đang đổi tên cuộc trò chuyện…",
|
||||
"Delete Chat": "Xóa cuộc trò chuyện",
|
||||
"Deleting chat…": "Đang xóa cuộc trò chuyện…",
|
||||
"Loading chat…": "Đang tải cuộc trò chuyện…",
|
||||
"Bulk Delete": "Xóa hàng loạt",
|
||||
"Deleting ${0} character(s)…": "Đang xóa ${0} nhân vật…"
|
||||
}
|
||||
|
||||
@@ -3635,5 +3635,13 @@
|
||||
"sd_prompt_9": "你的人设 (多模态模式)",
|
||||
"Show only favorites": "仅显示收藏",
|
||||
"Show only groups": "仅显示群聊",
|
||||
"Show Tag List": "显示标签列表"
|
||||
"Show Tag List": "显示标签列表",
|
||||
"SillyTavern Logo": "SillyTavern 徽标",
|
||||
"Initializing…": "正在初始化…",
|
||||
"Renaming chat…": "正在重命名聊天…",
|
||||
"Delete Chat": "删除聊天",
|
||||
"Deleting chat…": "正在删除聊天…",
|
||||
"Loading chat…": "正在加载聊天…",
|
||||
"Bulk Delete": "批量删除",
|
||||
"Deleting ${0} character(s)…": "正在删除 ${0} 个角色…"
|
||||
}
|
||||
|
||||
@@ -2764,5 +2764,15 @@
|
||||
"Interleaved Thinking": "交錯思維",
|
||||
"Since Last User Message": "自上一則使用者訊息起",
|
||||
"Active Tool Chain": "使用中的工具鏈",
|
||||
"openrouter_interleaved_thinking_hint": "會在工具呼叫請求中附帶先前助理回合的推理,以維持交錯思維的上下文。"
|
||||
"openrouter_interleaved_thinking_hint": "會在工具呼叫請求中附帶先前助理回合的推理,以維持交錯思維的上下文。",
|
||||
"SillyTavern Logo": "SillyTavern 標誌",
|
||||
"Initializing…": "正在初始化…",
|
||||
"Name not accepted, as it is the same as before (ignoring case and accents).": "名稱不被接受,因為它與之前相同(忽略大小寫和重音)。",
|
||||
"Rename Chat": "重新命名聊天",
|
||||
"Renaming chat…": "正在重新命名聊天…",
|
||||
"Delete Chat": "刪除聊天",
|
||||
"Deleting chat…": "正在刪除聊天…",
|
||||
"Loading chat…": "正在載入聊天…",
|
||||
"Bulk Delete": "批次刪除",
|
||||
"Deleting ${0} character(s)…": "正在刪除 ${0} 個角色…"
|
||||
}
|
||||
|
||||
+54
-19
@@ -244,7 +244,7 @@ import {
|
||||
isPersonaPanelOpen,
|
||||
} from './scripts/personas.js';
|
||||
import { getBackgrounds, initBackgrounds, loadBackgroundSettings, background_settings } from './scripts/backgrounds.js';
|
||||
import { hideLoader, showLoader } from './scripts/loader.js';
|
||||
import { loader } from './scripts/action-loader.js';
|
||||
import { BulkEditOverlay } from './scripts/BulkEditOverlay.js';
|
||||
import { initTextGenModels } from './scripts/textgen-models.js';
|
||||
import { appendFileContent, hasPendingFileAttachment, populateFileAttachment, decodeStyleTags, encodeStyleTags, isExternalMediaAllowed, preserveNeutralChat, restoreNeutralChat, formatCreatorNotes, initChatUtilities, addDOMPurifyHooks } from './scripts/chats.js';
|
||||
@@ -698,7 +698,28 @@ async function firstLoadInit() {
|
||||
throw new Error('Initialization failed');
|
||||
}
|
||||
|
||||
showLoader();
|
||||
const initLoaderOverlay = loader.createOverlay();
|
||||
initLoaderOverlay.classList.add('splash-screen');
|
||||
|
||||
const splashLogo = document.createElement('img');
|
||||
splashLogo.src = '/img/logo.png';
|
||||
splashLogo.alt = 'SillyTavern';
|
||||
splashLogo.className = 'splash-logo';
|
||||
splashLogo.ariaLabel = t`SillyTavern Logo`;
|
||||
|
||||
const splashMessage = document.createElement('h2');
|
||||
splashMessage.className = 'splash-message';
|
||||
splashMessage.textContent = t`Initializing…`;
|
||||
splashMessage.dataset.i18n = 'Initializing…';
|
||||
|
||||
initLoaderOverlay.prepend(splashLogo);
|
||||
initLoaderOverlay.appendChild(splashMessage);
|
||||
|
||||
const initLoaderHandle = loader.show({
|
||||
toastMode: loader.ToastMode.NONE,
|
||||
overlayContent: initLoaderOverlay,
|
||||
});
|
||||
|
||||
registerPromptManagerMigration();
|
||||
initDomHandlers();
|
||||
initStandaloneMode();
|
||||
@@ -724,7 +745,7 @@ async function firstLoadInit() {
|
||||
ToolManager.initToolSlashCommands();
|
||||
await initPresetManager();
|
||||
await initSystemMessages();
|
||||
await getSettings();
|
||||
await getSettings(initLoaderHandle);
|
||||
initKeyboard();
|
||||
initDynamicStyles();
|
||||
initTags();
|
||||
@@ -758,7 +779,7 @@ async function firstLoadInit() {
|
||||
addDebugFunctions();
|
||||
doDailyExtensionUpdatesCheck();
|
||||
await eventSource.emit(event_types.APP_INITIALIZED);
|
||||
await hideLoader();
|
||||
await initLoaderHandle.hide();
|
||||
await fixViewport();
|
||||
await eventSource.emit(event_types.APP_READY);
|
||||
}
|
||||
@@ -7763,7 +7784,7 @@ function reloadLoop() {
|
||||
|
||||
//MARK: getSettings()
|
||||
///////////////////////////////////////////
|
||||
export async function getSettings() {
|
||||
export async function getSettings(initLoaderHandle = null) {
|
||||
const response = await fetch('/api/settings/get', {
|
||||
method: 'POST',
|
||||
headers: getRequestHeaders(),
|
||||
@@ -7881,7 +7902,7 @@ export async function getSettings() {
|
||||
firstRun = !!settings.firstRun;
|
||||
|
||||
if (firstRun) {
|
||||
hideLoader();
|
||||
await initLoaderHandle?.hide();
|
||||
await doOnboarding(user_avatar);
|
||||
firstRun = false;
|
||||
}
|
||||
@@ -10457,7 +10478,7 @@ export async function doNewChat({ deleteCurrentChat = false } = {}) {
|
||||
* @param {string} param.newFileName New name for the chat (no JSONL extension)
|
||||
* @param {boolean} [param.loader=true] Whether to show loader during the operation
|
||||
*/
|
||||
export async function renameGroupOrCharacterChat({ characterId, groupId, oldFileName, newFileName, loader }) {
|
||||
export async function renameGroupOrCharacterChat({ characterId, groupId, oldFileName, newFileName, loader: showLoader }) {
|
||||
const currentChatId = getCurrentChatId();
|
||||
const body = {
|
||||
is_group: !!groupId,
|
||||
@@ -10475,9 +10496,13 @@ export async function renameGroupOrCharacterChat({ characterId, groupId, oldFile
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
loader && showLoader();
|
||||
const loaderHandle = showLoader ? loader.show({
|
||||
title: t`Rename Chat`,
|
||||
message: t`Renaming chat…`,
|
||||
toastMode: loader.ToastMode.STATIC,
|
||||
}) : null;
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/chats/rename', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(body),
|
||||
@@ -10510,11 +10535,10 @@ export async function renameGroupOrCharacterChat({ characterId, groupId, oldFile
|
||||
await reloadCurrentChat();
|
||||
}
|
||||
} catch {
|
||||
loader && hideLoader();
|
||||
await delay(500);
|
||||
await callGenericPopup('An error has occurred. Chat was not renamed.', POPUP_TYPE.TEXT);
|
||||
} finally {
|
||||
loader && hideLoader();
|
||||
await loaderHandle?.hide();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11072,21 +11096,32 @@ jQuery(async function () {
|
||||
async function handleDeleteChat(chatFile, group, fromSlashCommand = false) {
|
||||
// Close past chat popup.
|
||||
$('#select_chat_cross').trigger('click');
|
||||
showLoader();
|
||||
if (group) {
|
||||
await deleteGroupChat(group, chatFile);
|
||||
} else {
|
||||
await delChat(`${chatFile}.jsonl`);
|
||||
|
||||
const loaderHandle = loader.show({
|
||||
title: t`Delete Chat`,
|
||||
message: t`Deleting chat…`,
|
||||
toastMode: loader.ToastMode.STATIC,
|
||||
});
|
||||
|
||||
try {
|
||||
if (group) {
|
||||
await deleteGroupChat(group, chatFile);
|
||||
} else {
|
||||
await delChat(`${chatFile}.jsonl`);
|
||||
}
|
||||
} catch (error) {
|
||||
loaderHandle.hide();
|
||||
throw error;
|
||||
}
|
||||
|
||||
if (fromSlashCommand) { // When called from `/delchat` command, don't re-open the history view.
|
||||
$('#options').hide(); // Hide option popup menu.
|
||||
hideLoader();
|
||||
await loaderHandle.hide();
|
||||
} else { // Open the history view again after 2 seconds (delay to avoid edge cases for deleting last chat).
|
||||
setTimeout(function () {
|
||||
setTimeout(async function () {
|
||||
$('#option_select_chat').trigger('click');
|
||||
$('#options').hide(); // Hide option popup menu.
|
||||
hideLoader();
|
||||
await loaderHandle.hide();
|
||||
}, 2000);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,10 +14,11 @@ import {
|
||||
} from '../script.js';
|
||||
|
||||
import { favsToHotswap } from './RossAscends-mods.js';
|
||||
import { hideLoader, showLoader } from './loader.js';
|
||||
import { loader } from './action-loader.js';
|
||||
import { convertCharacterToPersona } from './personas.js';
|
||||
import { callGenericPopup, POPUP_TYPE } from './popup.js';
|
||||
import { createTagInput, getTagKeyForEntity, getTagsList, printTagList, tag_map, compareTagsForSort, removeTagFromMap, importTags, tag_import_setting } from './tags.js';
|
||||
import { t } from './i18n.js';
|
||||
|
||||
/**
|
||||
* Static object representing the actions of the
|
||||
@@ -846,15 +847,15 @@ class BulkEditOverlay {
|
||||
|
||||
const deleteChats = checkbox.prop('checked') ?? false;
|
||||
|
||||
showLoader();
|
||||
const toast = toastr.info('We\'re deleting your characters, please wait...', 'Working on it');
|
||||
const loaderHandle = loader.show({
|
||||
title: t`Bulk Delete`,
|
||||
message: t`Deleting ${characterIds.length} character(s)…`,
|
||||
toastMode: loader.ToastMode.STATIC,
|
||||
});
|
||||
const avatarList = characterIds.map(id => characters[id]?.avatar).filter(a => a);
|
||||
return CharacterContextMenu.delete(avatarList, deleteChats)
|
||||
.then(() => this.browseState())
|
||||
.finally(() => {
|
||||
toastr.clear(toast);
|
||||
hideLoader();
|
||||
});
|
||||
.finally(() => loaderHandle.hide());
|
||||
});
|
||||
|
||||
// At this moment the popup is already changed in the dom, but not yet closed/resolved. We build the avatar list here
|
||||
|
||||
@@ -1,16 +1,19 @@
|
||||
/**
|
||||
* Action loader utility - shows loader overlay with stoppable toast notification.
|
||||
* Unified action loader system - shows loader overlay with optional toast notifications.
|
||||
* Designed to be flexible and reusable for various long-running operations.
|
||||
* Supports stacking multiple loaders - overlay stays single, but toasts can stack.
|
||||
*
|
||||
* With default arguments, will function as a generation loader / wrapper.
|
||||
* Features:
|
||||
* - Stacking multiple loaders - overlay stays single, but toasts can stack
|
||||
* - Blocking and non-blocking modes
|
||||
* - Stoppable or static toasts
|
||||
* - Class-based handle system for fine-grained control
|
||||
*
|
||||
* @module action-loader
|
||||
*/
|
||||
|
||||
import { t } from './i18n.js';
|
||||
import { stopGeneration } from '../script.js';
|
||||
import { showLoader, hideLoader, isLoaderDisplayed } from './loader.js';
|
||||
import { Popup, POPUP_RESULT, POPUP_TYPE } from './popup.js';
|
||||
|
||||
/**
|
||||
* Enum representing the toast display mode for the action loader.
|
||||
@@ -33,6 +36,7 @@ export const ActionLoaderToastMode = {
|
||||
* @property {string} [message='Generating...'] - The message to display in the toast
|
||||
* @property {string} [title] - Optional title for the toast notification
|
||||
* @property {string} [stopTooltip='Stop'] - Tooltip text for the stop button
|
||||
* @property {HTMLElement|string|null} [overlayContent=null] - Custom content for the overlay (replaces default spinner)
|
||||
* @property {(() => void)|null} [onStop=null] - Custom stop handler. If null, calls `stopGeneration()`
|
||||
* @property {(() => void)|null} [onHide=null] - Custom hide handler. Called when the loader is hidden (not stopped).
|
||||
*/
|
||||
@@ -95,6 +99,7 @@ export class ActionLoaderHandle {
|
||||
* @param {string} [options.message='Generating...'] - Message to display in the toast
|
||||
* @param {string} [options.title] - Title for the toast notification
|
||||
* @param {string} [options.stopTooltip='Stop'] - Tooltip for the stop button
|
||||
* @param {HTMLElement|string|null} [options.overlayContent] - Custom content for the overlay (replaces default spinner)
|
||||
* @param {(() => void)|null} [options.onStop] - Custom stop handler
|
||||
* @param {(() => void)|null} [options.onHide] - Custom hide handler
|
||||
*/
|
||||
@@ -104,6 +109,7 @@ export class ActionLoaderHandle {
|
||||
message = t`Generating...`,
|
||||
title = '',
|
||||
stopTooltip = t`Stop`,
|
||||
overlayContent = null,
|
||||
onStop = null,
|
||||
onHide = null,
|
||||
} = {}) {
|
||||
@@ -113,13 +119,13 @@ export class ActionLoaderHandle {
|
||||
this.#onHide = onHide;
|
||||
|
||||
// Warn if non-blocking loader has no toast - it won't be visible to the user
|
||||
if (!blocking && toastMode === ActionLoaderToastMode.NONE) {
|
||||
if (!blocking && toastMode === ActionLoaderToastMode.NONE && !overlayContent) {
|
||||
console.warn('[ActionLoader] Non-blocking loader created without a toast. This loader will not be visible to the user.');
|
||||
}
|
||||
|
||||
// Show the blocking loader overlay if this is the first blocking handle
|
||||
if (blocking && !hasBlockingLoaders() && !isLoaderDisplayed()) {
|
||||
showLoader();
|
||||
if (blocking && !hasBlockingLoaders() && !isOverlayDisplayed()) {
|
||||
showOverlay(overlayContent);
|
||||
}
|
||||
|
||||
// Register this handle
|
||||
@@ -191,7 +197,7 @@ export class ActionLoaderHandle {
|
||||
|
||||
// Hide the overlay if this was the last blocking handle
|
||||
if (this.#blocking && !hasBlockingLoaders()) {
|
||||
await hideLoader();
|
||||
await hideOverlay();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -300,6 +306,12 @@ export const loader = {
|
||||
*/
|
||||
get: getLoaderHandleById,
|
||||
|
||||
/**
|
||||
* Checks if any blocking loader overlay is currently displayed.
|
||||
* @returns {boolean} True if a blocking overlay is shown
|
||||
*/
|
||||
isBlocking: isOverlayDisplayed,
|
||||
|
||||
/**
|
||||
* Toast display mode constants.
|
||||
* @type {typeof ActionLoaderToastMode}
|
||||
@@ -311,6 +323,12 @@ export const loader = {
|
||||
* @type {typeof ActionLoaderHandle}
|
||||
*/
|
||||
Handle: ActionLoaderHandle,
|
||||
|
||||
/**
|
||||
* Creates a fresh default loader overlay element.
|
||||
* @type {typeof createDefaultLoaderOverlay}
|
||||
*/
|
||||
createOverlay: createDefaultLoaderOverlay,
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -404,3 +422,145 @@ export function getLoaderHandleById(id) {
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Internal overlay management
|
||||
// ============================================================================
|
||||
|
||||
/** @type {Popup|null} The current loader overlay popup */
|
||||
let loaderPopup = null;
|
||||
|
||||
/** Whether the initial HTML preloader has been removed */
|
||||
let preloaderYoinked = false;
|
||||
|
||||
/**
|
||||
* Creates the default loader overlay element.
|
||||
* Always returns a fresh element instance.
|
||||
*
|
||||
* @returns {HTMLDivElement} A new loader overlay element
|
||||
*/
|
||||
export function createDefaultLoaderOverlay() {
|
||||
const loaderElement = document.createElement('div');
|
||||
loaderElement.id = 'loader';
|
||||
|
||||
const spinnerElement = document.createElement('div');
|
||||
spinnerElement.id = 'load-spinner';
|
||||
spinnerElement.className = 'fa-solid fa-gear fa-spin fa-3x';
|
||||
|
||||
loaderElement.appendChild(spinnerElement);
|
||||
|
||||
return loaderElement;
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes custom overlay content into a value supported by Popup.
|
||||
* @param {string|HTMLElement|null} customContent - Custom overlay content
|
||||
* @returns {string|HTMLElement} Content for Popup
|
||||
*/
|
||||
function getOverlayContent(customContent) {
|
||||
if (typeof customContent === 'string') {
|
||||
return customContent;
|
||||
}
|
||||
|
||||
if (customContent instanceof HTMLElement) {
|
||||
return customContent;
|
||||
}
|
||||
|
||||
return createDefaultLoaderOverlay();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the loader overlay is currently displayed.
|
||||
* @returns {boolean} True if overlay is shown
|
||||
*/
|
||||
function isOverlayDisplayed() {
|
||||
return !!loaderPopup;
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows the blocking loader overlay.
|
||||
* Internal function - use showActionLoader() instead.
|
||||
* @param {HTMLElement|string|null} [customContent] - Custom content for the overlay
|
||||
*/
|
||||
function showOverlay(customContent = null) {
|
||||
// Two loaders don't make sense. Don't await, we can overlay the old loader while it closes
|
||||
if (loaderPopup) loaderPopup.complete(POPUP_RESULT.CANCELLED);
|
||||
|
||||
const content = getOverlayContent(customContent);
|
||||
|
||||
loaderPopup = new Popup(content, POPUP_TYPE.DISPLAY, null, { transparent: true, animation: 'none', wide: true, large: true });
|
||||
|
||||
// No close button, loaders are not closable
|
||||
loaderPopup.closeButton.style.display = 'none';
|
||||
|
||||
loaderPopup.show();
|
||||
}
|
||||
|
||||
/**
|
||||
* Hides the blocking loader overlay with animation.
|
||||
* Internal function - use hideActionLoader() instead.
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async function hideOverlay() {
|
||||
if (!loaderPopup) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
return new Promise((resolve) => {
|
||||
const loaderElement = $('#loader');
|
||||
const spinner = $('#load-spinner');
|
||||
|
||||
if (!loaderElement.length) {
|
||||
console.warn('Loader element not found, skipping animation');
|
||||
cleanup();
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if transitions are enabled on spinner (which has the transition property)
|
||||
const transitionDuration = spinner.length && spinner[0] ? getComputedStyle(spinner[0]).transitionDuration : '0s';
|
||||
const hasTransitions = parseFloat(transitionDuration) > 0;
|
||||
|
||||
if (hasTransitions) {
|
||||
Promise.race([
|
||||
new Promise((r) => setTimeout(r, 500)), // Fallback timeout
|
||||
new Promise((r) => loaderElement.one('transitionend webkitTransitionEnd oTransitionEnd MSTransitionEnd', r)),
|
||||
]).finally(cleanup);
|
||||
} else {
|
||||
cleanup();
|
||||
}
|
||||
|
||||
function cleanup() {
|
||||
loaderElement.remove();
|
||||
// Yoink preloader entirely; it only exists to cover up unstyled content while loading JS
|
||||
// If it's present, we remove it once and then it's gone.
|
||||
yoinkPreloader();
|
||||
|
||||
loaderPopup.complete(POPUP_RESULT.AFFIRMATIVE)
|
||||
.catch((err) => console.error('Error completing loaderPopup:', err))
|
||||
.finally(() => {
|
||||
loaderPopup = null;
|
||||
resolve();
|
||||
});
|
||||
}
|
||||
|
||||
// Apply the blur styles to the entire loader element
|
||||
loaderElement.css({
|
||||
'filter': 'blur(15px)',
|
||||
'opacity': '0',
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the initial HTML preloader element.
|
||||
* Called once after the first loader hide.
|
||||
*/
|
||||
function yoinkPreloader() {
|
||||
if (preloaderYoinked) return;
|
||||
document.getElementById('preloader')?.remove();
|
||||
preloaderYoinked = true;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// End internal overlay management
|
||||
// ============================================================================
|
||||
|
||||
@@ -25,7 +25,7 @@ import {
|
||||
saveGroupBookmarkChat,
|
||||
selected_group,
|
||||
} from './group-chats.js';
|
||||
import { hideLoader, showLoader } from './loader.js';
|
||||
import { loader } from './action-loader.js';
|
||||
import { getLastMessageId } from './macros.js';
|
||||
import { Popup } from './popup.js';
|
||||
import { SlashCommand } from './slash-commands/SlashCommand.js';
|
||||
@@ -661,15 +661,20 @@ export function initBookmarks() {
|
||||
return;
|
||||
}
|
||||
|
||||
const loaderHandle = loader.show({
|
||||
title: t`Chat History`,
|
||||
message: t`Loading chat…`,
|
||||
toastMode: loader.ToastMode.STATIC,
|
||||
});
|
||||
|
||||
try {
|
||||
showLoader();
|
||||
if (selected_group) {
|
||||
await openGroupChat(selected_group, fileName);
|
||||
} else {
|
||||
await openCharacterChat(fileName);
|
||||
}
|
||||
} finally {
|
||||
await hideLoader();
|
||||
await loaderHandle.hide();
|
||||
}
|
||||
|
||||
$('#shadow_select_chat_popup').css('display', 'none');
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { DOMPurify, Popper } from '../lib.js';
|
||||
|
||||
import { eventSource, event_types, saveSettings, saveSettingsDebounced, getRequestHeaders, animation_duration, CLIENT_VERSION } from '../script.js';
|
||||
import { showLoader } from './loader.js';
|
||||
import { POPUP_RESULT, POPUP_TYPE, Popup, callGenericPopup } from './popup.js';
|
||||
import { renderTemplate, renderTemplateAsync } from './templates.js';
|
||||
import { delay, equalsIgnoreCaseAndAccents, isSubsetOf, sanitizeSelector, setValueByPath, versionCompare } from './utils.js';
|
||||
@@ -1158,7 +1157,6 @@ async function showExtensionsDetails() {
|
||||
abortController.abort();
|
||||
}
|
||||
if (requiresReload) {
|
||||
showLoader();
|
||||
location.reload();
|
||||
}
|
||||
}
|
||||
|
||||
+47
-68
@@ -1,80 +1,59 @@
|
||||
import { POPUP_RESULT, POPUP_TYPE, Popup } from './popup.js';
|
||||
import { loader } from './action-loader.js';
|
||||
|
||||
/** @type {Popup} */
|
||||
let loaderPopup;
|
||||
|
||||
let preloaderYoinked = false;
|
||||
|
||||
export function isLoaderDisplayed() {
|
||||
return !!loaderPopup;
|
||||
}
|
||||
/**
|
||||
* Handle for the legacy loader created by showLoader().
|
||||
* @type {import('./action-loader.js').ActionLoaderHandle|null}
|
||||
*/
|
||||
let legacyLoaderHandle = null;
|
||||
|
||||
/**
|
||||
* Shows the loader overlay.
|
||||
*
|
||||
* @deprecated Use `showActionLoader()` from action-loader.js instead.
|
||||
* This function now creates a blocking action loader with no toast.
|
||||
* The new system supports stacking multiple loaders and provides better control.
|
||||
*
|
||||
* @example
|
||||
* // New recommended approach:
|
||||
* import { showActionLoader } from './action-loader.js';
|
||||
* const handle = showActionLoader({ message: 'Loading...' });
|
||||
* // ... do work ...
|
||||
* handle.hide();
|
||||
*/
|
||||
export function showLoader() {
|
||||
// Two loaders don't make sense. Don't await, we can overlay the old loader while it closes
|
||||
if (loaderPopup) loaderPopup.complete(POPUP_RESULT.CANCELLED);
|
||||
// Hide any existing legacy loader first to maintain old behavior
|
||||
if (legacyLoaderHandle && legacyLoaderHandle.isActive) {
|
||||
legacyLoaderHandle.hide();
|
||||
}
|
||||
|
||||
loaderPopup = new Popup(`
|
||||
<div id="loader">
|
||||
<div id="load-spinner" class="fa-solid fa-gear fa-spin fa-3x"></div>
|
||||
</div>`, POPUP_TYPE.DISPLAY, null, { transparent: true, animation: 'none', wide: true, large: true });
|
||||
|
||||
// No close button, loaders are not closable
|
||||
loaderPopup.closeButton.style.display = 'none';
|
||||
|
||||
loaderPopup.show();
|
||||
// Create a blocking loader with no toast (matches old behavior)
|
||||
legacyLoaderHandle = loader.show({
|
||||
blocking: true,
|
||||
toastMode: loader.ToastMode.NONE,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Hides the loader overlay.
|
||||
*
|
||||
* @deprecated Use `hideActionLoader()` or `handle.hide()` from action-loader.js instead.
|
||||
* This function now hides the legacy loader created by showLoader().
|
||||
*
|
||||
* @example
|
||||
* // New recommended approach:
|
||||
* import { showActionLoader } from './action-loader.js';
|
||||
* const handle = showActionLoader({ message: 'Loading...' });
|
||||
* // ... do work ...
|
||||
* await handle.hide();
|
||||
*
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
export async function hideLoader() {
|
||||
if (!loaderPopup) {
|
||||
if (!legacyLoaderHandle || !legacyLoaderHandle.isActive) {
|
||||
console.warn('There is no loader showing to hide');
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
return new Promise((resolve) => {
|
||||
const spinner = $('#load-spinner');
|
||||
if (!spinner.length) {
|
||||
console.warn('Spinner element not found, skipping animation');
|
||||
cleanup();
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if transitions are enabled
|
||||
const transitionDuration = spinner[0] ? getComputedStyle(spinner[0]).transitionDuration : '0s';
|
||||
const hasTransitions = parseFloat(transitionDuration) > 0;
|
||||
|
||||
if (hasTransitions) {
|
||||
Promise.race([
|
||||
new Promise((r) => setTimeout(r, 500)), // Fallback timeout
|
||||
new Promise((r) => spinner.one('transitionend webkitTransitionEnd oTransitionEnd MSTransitionEnd', r)),
|
||||
]).finally(cleanup);
|
||||
} else {
|
||||
cleanup();
|
||||
}
|
||||
|
||||
function cleanup() {
|
||||
$('#loader').remove();
|
||||
// Yoink preloader entirely; it only exists to cover up unstyled content while loading JS
|
||||
// If it's present, we remove it once and then it's gone.
|
||||
yoinkPreloader();
|
||||
|
||||
loaderPopup.complete(POPUP_RESULT.AFFIRMATIVE)
|
||||
.catch((err) => console.error('Error completing loaderPopup:', err))
|
||||
.finally(() => {
|
||||
loaderPopup = null;
|
||||
resolve();
|
||||
});
|
||||
}
|
||||
|
||||
// Apply the styles
|
||||
spinner.css({
|
||||
'filter': 'blur(15px)',
|
||||
'opacity': '0',
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function yoinkPreloader() {
|
||||
if (preloaderYoinked) return;
|
||||
document.getElementById('preloader').remove();
|
||||
preloaderYoinked = true;
|
||||
await legacyLoaderHandle.hide();
|
||||
legacyLoaderHandle = null;
|
||||
}
|
||||
|
||||
@@ -189,7 +189,9 @@ export function getContext() {
|
||||
/** @deprecated Use callGenericPopup or Popup instead. */
|
||||
callPopup,
|
||||
callGenericPopup,
|
||||
/** @deprecated Use loader.show instead. */
|
||||
showLoader,
|
||||
/** @deprecated Use loader.hide instead. */
|
||||
hideLoader,
|
||||
mainApi: main_api,
|
||||
extensionSettings: extension_settings,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<div class="welcomePanel">
|
||||
<div class="welcomeHeaderTitle">
|
||||
<img src="img/logo.png" alt="SillyTavern Logo" class="welcomeHeaderLogo">
|
||||
<img src="img/logo.png" alt="SillyTavern Logo" data-i18n="[alt]SillyTavern Logo" class="welcomeHeaderLogo">
|
||||
<span class="welcomeHeaderVersionDisplay">{{version}}</span>
|
||||
<div class="mes_button showRecentChats" title="Show recent chats" data-i18n="[title]Show recent chats">
|
||||
<i class="fa-solid fa-circle-chevron-down fa-fw fa-lg"></i>
|
||||
|
||||
Reference in New Issue
Block a user