diff --git a/CHANGELOG.md b/CHANGELOG.md index 00084ab..9e23c17 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,8 @@ Follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) · versioned wi ### Changed - **API Key Handoff Cache Policy.** Settings and explicit external-key regeneration responses now send `no-store` headers so browser and intermediary caches cannot retain the client-visible access-key handoff. +- **Navigation Race Protection.** Rehearser phase requests now survive late feature-module initialization, and + overlapping character-library refreshes cannot overwrite a newer table/card view. - **API Boundary Hardening.** External API authentication is enabled by default, request and mutation limits are enforced, and settings responses redact provider credentials for non-browser callers. - **Server-side Provider Credentials.** Provider keys are now masked even for same-origin Settings responses, are diff --git a/static/dist/main.min.js b/static/dist/main.min.js index f90b85f..52a1ff7 100644 --- a/static/dist/main.min.js +++ b/static/dist/main.min.js @@ -1838,4 +1838,4 @@ Respond with STRICT JSON only: `)[0].slice(0,60)||"",backstory:data.description||data.char_persona||"",mannerisms:data.personality||"",arc_note:data.scenario||"",voice_pattern:data.mes_example||data.example_dialogue||data.first_mes||data.char_greeting||"",gender:ext.gender||stGuessGender((data.description||"")+" "+(data.personality||"")),note:data.creator_notes||"",tags,voice:ext.tts_voice||null,tier:"supporting",st_card:data}}function stFromRecord(rec){const sh=rec.sheet||{},prev=sh.st_card||{},descParts=[sh.physical,sh.clothing,sh.backstory,sh.alignment].map(x=>String(x||"").trim()).filter(Boolean),persona=[sh.archetype,sh.mannerisms,sh.motivation,sh.fears].map(x=>String(x||"").trim()).filter(Boolean).join(` `);return{spec:"chara_card_v2",spec_version:"2.0",data:{name:rec.name,description:prev.description||descParts.join(` -`),personality:persona||prev.personality||"",scenario:rec.book||prev.scenario||"",first_mes:prev.first_mes||"",mes_example:sh.voice_pattern||prev.mes_example||"",creator_notes:"Exported from TTS Voice Creator"+(rec.book?" \xB7 "+rec.book:""),system_prompt:prev.system_prompt||"",post_history_instructions:prev.post_history_instructions||"",tags:String(rec.tags||rec.book||"").split(",").map(t=>t.trim()).filter(Boolean),creator:prev.creator||"",character_version:prev.character_version||"1.0",extensions:Object.assign({},prev.extensions,{tts_voice:rec.voice||""})}}}function stDownloadJson(card,filename){const blob=new Blob([JSON.stringify(card,null,2)],{type:"application/json"}),a=document.createElement("a");a.href=URL.createObjectURL(blob),a.download=filename,document.body.appendChild(a),a.click(),a.remove(),setTimeout(()=>URL.revokeObjectURL(a.href),1e3)}async function stImportCards(book,fileList){let n=0;for(const file of fileList)try{const data=await stParseFile(file),sheet=stToSheet(data);typeof clUpsert=="function"&&(await clUpsert(book||sheet.name,Object.assign({},sheet,{tags:book||""})),n++)}catch(e){typeof toast=="function"&&toast("\u201C"+(file.name||"card")+"\u201D: "+(e.message||e),"error")}return n}function stImportDialog(book,onDone){const inp=document.createElement("input");inp.type="file",inp.accept=".json,.png",inp.multiple=!0,inp.onchange=async()=>{if(!inp.files.length)return;const n=await stImportCards(book,inp.files);typeof toast=="function"&&toast(n?"Imported "+n+" character"+(n>1?"s":""):"Nothing imported",n?"success":"error"),typeof onDone=="function"&&onDone()},inp.click()}function stExportRecord(rec){const card=stFromRecord(rec),safe=String(rec.name||"character").replace(/[^\w\- ]+/g,"").trim().replace(/\s+/g,"_")||"character";stDownloadJson(card,safe+".card.json")}window.stParseFile=stParseFile,window.stToSheet=stToSheet,window.stFromRecord=stFromRecord,window.stImportCards=stImportCards,window.stImportDialog=stImportDialog,window.stExportRecord=stExportRecord;const LIB_READER_API="/api/reader/docs";function prodKey(title){return String(title||"").trim().toLowerCase()}window._libraryView=function(){try{return localStorage.getItem("ttsvc_library_view")||"books"}catch{return"books"}}(),window.navLibraryView=function(view){typeof navTo=="function"&&navTo("s-library"),window._libraryView=view;try{localStorage.setItem("ttsvc_library_view",view)}catch{}document.querySelectorAll("[data-library-view]").forEach(function(el){el.classList.toggle("is-active",el.dataset.libraryView===view)}),document.querySelectorAll("[data-library-panel]").forEach(function(el){el.classList.toggle("is-active",el.dataset.libraryPanel===view)}),view==="characters"&&typeof refreshWorkflowCrumbs=="function"&&refreshWorkflowCrumbs("castlib"),libraryRender(view)},window.libraryRender=function(view){view=view||window._libraryView||"books",document.querySelectorAll("[data-library-view]").forEach(function(el){el.classList.toggle("is-active",el.dataset.libraryView===view)}),document.querySelectorAll("[data-library-panel]").forEach(function(el){el.classList.toggle("is-active",el.dataset.libraryPanel===view)}),view==="books"?libraryRenderBooks():view==="plays"?libraryRenderPlays():view==="characters"&&typeof window.libraryRenderCharacters=="function"&&window.libraryRenderCharacters()};function _libSkeleton(n){return Array.from({length:n},()=>'
No books yet.
Open Read Aloud, import a PDF or text, and save it to your library.
No theater plays yet.
Open Script Rehearsal \u2192 Import / Export to add a script, or cast a book as an audiobook.
No characters yet.
Open a book in Read Aloud, cast it as an audiobook, then click Cast Characters to generate character sheets \u2014 or import an existing cast from SillyTavern.
| '+th("alpha","Name")+th("gender","Geschlecht")+th("age","Alter","Estimated age")+th("lines","Zeilen","Anzahl Zeilen")+th("language","Sprache")+th("align","Gut/B\xF6se","Moralische Gesinnung")+th("profile","Profil (gef\xFCllt)","Sort by filled profile fields; the second number is always 32")+th("voice","Stimme")+th("rating","Rating","Rate the assigned voice")+' | Voice Design Prompt | Notes | Tags | Book / Script |
|---|
Used in every voice design (and image) prompt for this book, so a fantasy story doesn't end up with 1920s-general portraits or English voices in a German book just because one character's own sheet was too sparse to tell.
"'+escHtml(reuse.voiceId)+'" is already used for '+escHtml(rec.name)+' in "'+escHtml(reuse.book)+'". Reuse it for series consistency, or design a brand-new voice just for this book?
Edit the description, then generate a new voice from it. This replaces '+(rec.voice?"the current voice":"this character\u2019s voice")+'.
Optional \u2014 let the AI fill out full character profiles (appearance, backstory, voice notes) for reference. Skip this if you just want to cast voices quickly.
No books yet.
Open Read Aloud, import a PDF or text, and save it to your library.
No theater plays yet.
Open Script Rehearsal \u2192 Import / Export to add a script, or cast a book as an audiobook.
No characters yet.
Open a book in Read Aloud, cast it as an audiobook, then click Cast Characters to generate character sheets \u2014 or import an existing cast from SillyTavern.
| '+th("alpha","Name")+th("gender","Geschlecht")+th("age","Alter","Estimated age")+th("lines","Zeilen","Anzahl Zeilen")+th("language","Sprache")+th("align","Gut/B\xF6se","Moralische Gesinnung")+th("profile","Profil (gef\xFCllt)","Sort by filled profile fields; the second number is always 32")+th("voice","Stimme")+th("rating","Rating","Rate the assigned voice")+' | Voice Design Prompt | Notes | Tags | Book / Script |
|---|
Used in every voice design (and image) prompt for this book, so a fantasy story doesn't end up with 1920s-general portraits or English voices in a German book just because one character's own sheet was too sparse to tell.
"'+escHtml(reuse.voiceId)+'" is already used for '+escHtml(rec.name)+' in "'+escHtml(reuse.book)+'". Reuse it for series consistency, or design a brand-new voice just for this book?
Edit the description, then generate a new voice from it. This replaces '+(rec.voice?"the current voice":"this character\u2019s voice")+'.
Optional \u2014 let the AI fill out full character profiles (appearance, backstory, voice notes) for reference. Skip this if you just want to cast voices quickly.