Advertisement
Guest User

Untitled

a guest
May 22nd, 2025
35
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
HTML 5 54.82 KB | Software | 0 0
  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4.     <meta charset="UTF-8">
  5.     <meta name="viewport" content="width=device-width, initial-scale=1.0">
  6.     <title>AI Chat Interface - Enhanced</title>
  7.     <style>
  8.         body {
  9.             font-family: sans-serif;
  10.             background-color: #2c2f33;
  11.             color: #ccc;
  12.             margin: 0;
  13.             padding: 20px;
  14.             font-size: 14px;
  15.         }
  16.         .container {
  17.             max-width: 900px;
  18.             margin: auto;
  19.             background-color: #36393f;
  20.             padding: 20px;
  21.             border-radius: 8px;
  22.             box-shadow: 0 0 15px rgba(0,0,0,0.5);
  23.         }
  24.         h2, h3 {
  25.             color: #eee;
  26.             border-bottom: 1px solid #4f545c;
  27.             padding-bottom: 10px;
  28.             margin-top: 20px;
  29.             margin-bottom: 15px;
  30.             text-align: center;
  31.         }
  32.         .section {
  33.             margin-bottom: 25px;
  34.             padding: 15px;
  35.             background-color: #40444b;
  36.             border-radius: 5px;
  37.         }
  38.         label {
  39.             display: block;
  40.             margin-bottom: 5px;
  41.             color: #b9bbbe;
  42.         }
  43.         input[type="text"], textarea {
  44.             width: calc(100% - 22px);
  45.             padding: 10px;
  46.             margin-bottom: 10px;
  47.             border-radius: 3px;
  48.             border: 1px solid #2e3338;
  49.             background-color: #2e3338;
  50.             color: #ccc;
  51.             font-size: 1em;
  52.             resize: vertical;
  53.         }
  54.         textarea {
  55.             min-height: 80px;
  56.         }
  57.         .button, button {
  58.             padding: 10px 15px;
  59.             border: none;
  60.             border-radius: 3px;
  61.             background-color: #5865f2; /* Discord-like blue */
  62.             color: white;
  63.             cursor: pointer;
  64.             font-size: 1em;
  65.             transition: background-color 0.2s ease;
  66.         }
  67.         .button:hover, button:hover {
  68.             background-color: #4752c4;
  69.         }
  70.         .button-secondary {
  71.             background-color: #72767d;
  72.         }
  73.         .button-secondary:hover {
  74.             background-color: #5c5f66;
  75.         }
  76.         .button-danger {
  77.             background-color: #ed4245;
  78.         }
  79.         .button-danger:hover {
  80.             background-color: #c9383b;
  81.         }
  82.         .icon-button {
  83.             background: none;
  84.             border: 1px solid #555;
  85.             color: #ccc;
  86.             padding: 5px 8px;
  87.             margin-right: 5px;
  88.             font-size: 0.9em;
  89.         }
  90.         .icon-button:hover {
  91.             background-color: #555;
  92.         }
  93.         .inline-controls {
  94.             display: flex;
  95.             align-items: center;
  96.             margin-top: -5px; /* Align with bottom of textarea */
  97.             margin-bottom: 10px;
  98.         }
  99.         .inline-controls .button {
  100.             margin-left: auto; /* Pushes generate button to the right */
  101.         }
  102.         #chatLog {
  103.             height: 350px;
  104.             background-color: #2e3338;
  105.             border: 1px solid #202225;
  106.             border-radius: 3px;
  107.             padding: 10px;
  108.             overflow-y: auto;
  109.             margin-bottom: 10px;
  110.             /* `white-space: pre-wrap;` removed as Markdown parsing handles structure */
  111.             word-wrap: break-word;
  112.             font-size: 0.95em;
  113.         }
  114.         /* Markdown specific styling */
  115.         #chatLog .chat-message { /* Container for each message */
  116.             margin-bottom: 15px; /* Spacing between messages */
  117.             line-height: 1.6; /* Improve readability */
  118.         }
  119.  
  120.         /* NEW FEATURE: Make chat message text bold by default */
  121.         #chatLog .chat-message p {
  122.             font-weight: bold;
  123.         }
  124.  
  125.         #chatLog strong { /* Speaker name in chat, also covers markdown bold */
  126.             color: #7289da;
  127.         }
  128.         #chatLog em {
  129.             font-style: italic;
  130.             font-weight: normal; /* Ensure italic text is not bold unless explicitly strong */
  131.         }
  132.         #chatLog .chat-message:last-child {
  133.             margin-bottom: 0; /* No margin after the very last message */
  134.         }
  135.         #chatLog .chat-message p:first-child { /* Remove top margin for the first paragraph in a message */
  136.             margin-top: 0;
  137.         }
  138.         #chatLog .chat-message p:last-child { /* Remove bottom margin for the last paragraph in a message */
  139.             margin-bottom: 0;
  140.         }
  141.         #chatLog pre { /* Style for code blocks */
  142.             background-color: #202225;
  143.             border: 1px solid #4f545c;
  144.             padding: 10px;
  145.             border-radius: 5px;
  146.             overflow-x: auto;
  147.             font-family: 'Consolas', 'Monaco', 'Courier New', monospace;
  148.             font-size: 0.9em;
  149.             font-weight: normal; /* Ensure preformatted text is not bold */
  150.         }
  151.         #chatLog code { /* Inline code */
  152.             background-color: #4f545c;
  153.             padding: 2px 4px;
  154.             border-radius: 3px;
  155.             font-family: 'Consolas', 'Monaco', 'Courier New', monospace;
  156.             font-weight: normal; /* Ensure code text is not bold */
  157.         }
  158.         #chatLog a { /* Link styling */
  159.             color: #7289da;
  160.             text-decoration: none;
  161.         }
  162.         #chatLog a:hover {
  163.             text-decoration: underline;
  164.         }
  165.         #chatLog blockquote { /* Blockquote styling */
  166.             border-left: 4px solid #5865f2;
  167.             margin: 0;
  168.             padding: 0 15px;
  169.             color: #b9bbbe;
  170.             font-weight: normal; /* Ensure blockquote text is not bold */
  171.         }
  172.         #chatLog ul, #chatLog ol { /* List styling */
  173.             margin-left: 20px;
  174.             padding-left: 0;
  175.             font-weight: normal; /* Ensure list items are not bold by default */
  176.         }
  177.         #chatLog li {
  178.             margin-bottom: 5px;
  179.         }
  180.         #chatLog hr { /* Horizontal rule */
  181.             border: 0;
  182.             border-top: 1px solid #4f545c;
  183.             margin: 20px 0;
  184.             font-weight: normal; /* Ensure horizontal rule itself doesn't cause issues */
  185.         }
  186.         #chatLog h1, #chatLog h2, #chatLog h3, #chatLog h4, #chatLog h5, #chatLog h6 {
  187.             color: #eee;
  188.             margin-top: 1em;
  189.             margin-bottom: 0.5em;
  190.             border-bottom: none; /* Override general h2,h3 border-bottom */
  191.             padding-bottom: 0;
  192.             text-align: left;
  193.             font-weight: bold; /* Headings are typically bold */
  194.         }
  195.  
  196.         .chat-input-area {
  197.             display: flex;
  198.             flex-direction: column;
  199.         }
  200.         .chat-input-area textarea {
  201.             margin-bottom: 5px;
  202.         }
  203.         .chat-controls {
  204.             display: flex;
  205.             flex-wrap: wrap;
  206.             gap: 10px;
  207.             justify-content: space-between;
  208.             align-items: center;
  209.             margin-bottom: 10px;
  210.         }
  211.         .chat-controls .speaker-buttons button {
  212.             margin-right: 5px;
  213.         }
  214.         .speaker-buttons button.active {
  215.             background-color: #4752c4;
  216.             font-weight: bold;
  217.         }
  218.         .auto-improve-label {
  219.             display: flex;
  220.             align-items: center;
  221.             color: #b9bbbe;
  222.         }
  223.         .auto-improve-label input {
  224.             margin-right: 5px;
  225.             margin-bottom: 0; /* Override general input margin */
  226.         }
  227.         .header-button {
  228.             display: block;
  229.             margin: 0 auto 20px auto;
  230.             width: fit-content;
  231.         }
  232.         .file-ops button { margin-top: 5px; }
  233.         .additional-instructions textarea { min-height: 40px; font-size: 0.9em;}
  234.     </style>
  235. </head>
  236. <body>
  237.     <div class="container">
  238.         <button id="generateCharacterOverall" class="button header-button">&#x2728; generate a character</button>
  239.  
  240.         <div class="section names-section">
  241.             <h3>— Names —</h3>
  242.             <label for="botNickname">The bot's nickname</label>
  243.             <input type="text" id="botNickname" placeholder="Bot Name">
  244.             <label for="yourNickname">Your nickname</label>
  245.             <input type="text" id="yourNickname" placeholder="Your Name">
  246.         </div>
  247.  
  248.         <div class="section">
  249.             <h3>— Bot Character Description —</h3>
  250.             <textarea id="botDescription" placeholder="(Optional) Describe who the AI/bot is and what personality you want them to have."></textarea>
  251.             <div class="inline-controls">
  252.                 <button class="icon-button" title="Save Bot Desc" onclick="saveToLocalStorage('botDescription')">&#x1F4BE;</button>
  253.                 <button class="icon-button" title="Load Bot Desc" onclick="loadFromLocalStorage('botDescription')">&#x1F4C4;</button>
  254.                 <button class="button" onclick="generateForTextarea('botDescription')">&#x2728; generate</button>
  255.             </div>
  256.         </div>
  257.  
  258.         <div class="section">
  259.             <h3>— Your Character Description —</h3>
  260.             <textarea id="yourDescription" placeholder="(Optional) Describe the character that *you* will be in this chat."></textarea>
  261.             <div class="inline-controls">
  262.                 <button class="icon-button" title="Save Your Desc" onclick="saveToLocalStorage('yourDescription')">&#x1F4BE;</button>
  263.                 <button class="icon-button" title="Load Your Desc" onclick="loadFromLocalStorage('yourDescription')">&#x1F4C4;</button>
  264.                 <button class="button" onclick="generateForTextarea('yourDescription')">&#x2728; generate</button>
  265.             </div>
  266.         </div>
  267.  
  268.         <div class="section">
  269.             <h3>— Scenario & Lore —</h3>
  270.            <textarea id="scenarioLore" placeholder="(Optional) Describe the world, scenario overview, lore, side characters, and any other relevant information."></textarea>
  271.            <div class="inline-controls">
  272.                <button class="icon-button" title="Save Scenario" onclick="saveToLocalStorage('scenarioLore')">&#x1F4BE;</button>
  273.                 <button class="icon-button" title="Load Scenario" onclick="loadFromLocalStorage('scenarioLore')">&#x1F4C4;</button>
  274.                 <button class="button" onclick="generateForTextarea('scenarioLore')">&#x2728; generate</button>
  275.             </div>
  276.         </div>
  277.  
  278.         <div class="section additional-instructions">
  279.             <h3>— Additional Prompting —</h3>
  280.             <label for="whatHappensNext">What happens next? (Brief idea for the AI)</label>
  281.             <textarea id="whatHappensNext" placeholder="(Optional) e.g., Anon tries to open the mysterious box."></textarea>
  282.             <label for="writingInstructions">General writing instructions (Style, tone, things to avoid, etc.)</label>
  283.             <textarea id="writingInstructions" placeholder="(Optional) e.g., Keep messages short. Avoid cliches."></textarea>
  284.         </div>
  285.  
  286.         <div class="section">
  287.             <h3>— Chat Logs —</h3>
  288.             <div id="chatLog" contenteditable="true">The chat logs will show up here, and you can edit them.
  289.  
  290. The text here is completely freeform - for example, if you wanted to add a narrator that comes in every now and then, you could just write something like:
  291.  
  292. Narrator: Later that day...
  293.  
  294. And you can use *asterisks* for actions, and stuff like that.</div>
  295.         </div>
  296.  
  297.         <div class="chat-input-area section">
  298.             <textarea id="chatInput" placeholder="Write here and tap send, or just tap send to get the AI to write the next message for you. You can also directly edit the chat log text above. Markdown is supported!"></textarea>
  299.             <div class="chat-controls">
  300.                 <div class="speaker-buttons" id="speakerButtonContainer">
  301.                     <button id="speakAsUser" class="button-secondary active">User</button>
  302.                     <button id="speakAsBot" class="button-secondary">Bot</button>
  303.                     <button id="speakAsNarrator" class="button-secondary">Narrator</button>
  304.                     <button id="addCustomSpeaker" class="button-secondary icon-button" title="Add Custom Speaker">&#x2795;</button>
  305.                 </div>
  306.                 <label class="auto-improve-label">
  307.                     <input type="checkbox" id="autoImprove"> auto-improve
  308.                 </label>
  309.                  <button id="continueChat" class="button-secondary icon-button" title="Continue last message or generate new">&#x21AA;</button>
  310.                 <button id="clearChat" class="button-danger icon-button" title="Clear Chat Log">&#x1F5D1;</button>
  311.             </div>
  312.             <button id="sendMessage" class="button">send message</button>
  313.         </div>
  314.  
  315.         <div class="section file-ops">
  316.             <h3>— Data Management —</h3>
  317.             <button id="saveChatToFile" class="button-secondary">Save Chat to File</button>
  318.             <input type="file" id="loadChatFromFileInput" accept=".json" style="display: none;">
  319.             <button id="loadChatFromFile" class="button-secondary">Load Chat from File</button>
  320.         </div>
  321.     </div>
  322.  
  323.     <!-- Markdown and DOMPurify CDNs -->
  324.     <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
  325.     <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/purify.min.js"></script>
  326.  
  327.     <script>
  328.         const PROXY_URL = 'http://localhost:5001/v1/chat/completions'; // IMPORTANT: Replace with your proxy URL
  329.  
  330.         // DOM Elements
  331.         const botNicknameInput = document.getElementById('botNickname');
  332.         const yourNicknameInput = document.getElementById('yourNickname');
  333.         const botDescriptionTextarea = document.getElementById('botDescription');
  334.         const yourDescriptionTextarea = document.getElementById('yourDescription');
  335.         const scenarioLoreTextarea = document.getElementById('scenarioLore');
  336.         const whatHappensNextTextarea = document.getElementById('whatHappensNext');
  337.         const writingInstructionsTextarea = document.getElementById('writingInstructions');
  338.         const chatLogDiv = document.getElementById('chatLog');
  339.         const chatInputTextarea = document.getElementById('chatInput');
  340.         const sendMessageButton = document.getElementById('sendMessage');
  341.         const autoImproveCheckbox = document.getElementById('autoImprove');
  342.         const speakerButtonContainer = document.getElementById('speakerButtonContainer');
  343.         const speakAsUserButton = document.getElementById('speakAsUser');
  344.         const speakAsBotButton = document.getElementById('speakAsBot');
  345.         const speakAsNarratorButton = document.getElementById('speakAsNarrator');
  346.         const addCustomSpeakerButton = document.getElementById('addCustomSpeaker');
  347.         const clearChatButton = document.getElementById('clearChat');
  348.         const continueChatButton = document.getElementById('continueChat');
  349.         const generateCharacterOverallButton = document.getElementById('generateCharacterOverall');
  350.         const saveChatToFileButton = document.getElementById('saveChatToFile');
  351.         const loadChatFromFileButton = document.getElementById('loadChatFromFile');
  352.         const loadChatFromFileInput = document.getElementById('loadChatFromFileInput');
  353.  
  354.  
  355.         let currentSpeaker = "User"; // Default speaker
  356.         let customSpeakers = [];
  357.  
  358.         // Global variable to track if initial placeholder is present in chatLogDiv
  359.         let hasInitialChatLogPlaceholder = chatLogDiv.innerHTML.includes("The chat logs will show up here");
  360.  
  361.         // Initialize Marked.js for Markdown parsing
  362.         marked.setOptions({
  363.             gfm: true,    // Enable GitHub Flavored Markdown
  364.             breaks: true, // Convert newlines to <br>
  365.         });
  366.  
  367.         // --- Utility Functions ---
  368.         function saveToLocalStorage(elementId) {
  369.             const element = document.getElementById(elementId);
  370.             if (element) {
  371.                 // For chatLog, we might not want to save HTML to local storage if it gets too big
  372.                 // This is handled by file operations for robust saving/loading of full chat state.
  373.                 localStorage.setItem(elementId, element.value || element.innerHTML);
  374.                 alert(`${elementId} saved!`);
  375.             }
  376.         }
  377.  
  378.         function loadFromLocalStorage(elementId) {
  379.             const element = document.getElementById(elementId);
  380.             const savedValue = localStorage.getItem(elementId);
  381.             if (element && savedValue !== null) {
  382.                if (element.tagName === 'TEXTAREA' || element.tagName === 'INPUT') {
  383.                    element.value = savedValue;
  384.                 } else {
  385.                     // Not loading chatLog from local storage by default.
  386.                     // If elementId is chatLog, do nothing here.
  387.                 }
  388.                 // Special handling for nickname updates
  389.                 if (elementId === 'yourNickname') updateSpeakerButtonText('speakAsUser', savedValue || 'User');
  390.                 if (elementId === 'botNickname') updateSpeakerButtonText('speakAsBot', savedValue || 'Bot');
  391.             }
  392.         }
  393.        
  394.         function updateSpeakerButtonText(buttonId, newText) {
  395.             const button = document.getElementById(buttonId);
  396.             if (button) button.textContent = newText;
  397.         }
  398.  
  399.         yourNicknameInput.addEventListener('input', () => {
  400.             updateSpeakerButtonText('speakAsUser', yourNicknameInput.value || 'User');
  401.             localStorage.setItem('yourNickname', yourNicknameInput.value);
  402.         });
  403.  
  404.         botNicknameInput.addEventListener('input', () => {
  405.             updateSpeakerButtonText('speakAsBot', botNicknameInput.value || 'Bot');
  406.             localStorage.setItem('botNickname', botNicknameInput.value);
  407.         });
  408.  
  409.         function addMessageToChatLog(speaker, message) {
  410.             const speakerName = speaker === 'User' ? (yourNicknameInput.value.trim() || 'User') :
  411.                                 speaker === 'Bot' ? (botNicknameInput.value.trim() || 'Bot') : speaker;
  412.            
  413.             // If it's the first message and the placeholder is still there, clear the log
  414.             if (hasInitialChatLogPlaceholder) {
  415.                 chatLogDiv.innerHTML = '';
  416.                 hasInitialChatLogPlaceholder = false;
  417.             }
  418.  
  419.             // Create a div for the new message
  420.             const messageContainer = document.createElement('div');
  421.             messageContainer.className = 'chat-message';
  422.  
  423.             // Construct the raw Markdown string for the message, including the speaker name
  424.             // Speaker name will be bolded by Markdown (e.g., "**User:**")
  425.             const rawMarkdownContent = `**${speakerName}:** ${message}`;
  426.            
  427.             // Parse Markdown to HTML
  428.             let parsedHtml = marked.parse(rawMarkdownContent);
  429.            
  430.             // Sanitize the HTML using DOMPurify before injecting
  431.             // USE_PROFILES: {html: true} allows common HTML tags (like those generated by Markdown)
  432.             const cleanHtml = DOMPurify.sanitize(parsedHtml, {USE_PROFILES: {html: true}});
  433.  
  434.             messageContainer.innerHTML = cleanHtml;
  435.            
  436.             chatLogDiv.appendChild(messageContainer);
  437.             chatLogDiv.scrollTop = chatLogDiv.scrollHeight;
  438.         }
  439.  
  440.         function getChatLogForAI() {
  441.             let textContent = "";
  442.             // Iterate over each child element within chatLogDiv
  443.             Array.from(chatLogDiv.children).forEach(childElement => {
  444.                 // Only process elements that are actual chat messages (divs with class 'chat-message')
  445.                 if (childElement.classList.contains('chat-message')) {
  446.                     const tempDiv = document.createElement('div');
  447.                     // Get the inner HTML of the message div (which contains the markdown-rendered content like <p><strong>Speaker:</strong> text</p>)
  448.                     tempDiv.innerHTML = childElement.innerHTML;
  449.                    
  450.                     // Extract plain text from the rendered HTML
  451.                     let plainLine = tempDiv.textContent || tempDiv.innerText || "";
  452.                     plainLine = plainLine.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, ''); // Trim leading/trailing whitespace
  453.  
  454.                     if(plainLine) {
  455.                         // Add double newline separation between messages for AI context, consistent with prompt structure
  456.                         if (textContent !== "") {
  457.                             textContent += "\n\n";
  458.                         }
  459.                         textContent += plainLine;
  460.                     }
  461.                 }
  462.             });
  463.             return textContent.trim();
  464.         }
  465.  
  466.         function setActiveSpeakerButton(selectedButton) {
  467.             document.querySelectorAll('#speakerButtonContainer button').forEach(btn => {
  468.                 btn.classList.remove('active');
  469.                 btn.classList.add('button-secondary');
  470.             });
  471.             selectedButton.classList.add('active');
  472.             selectedButton.classList.remove('button-secondary');
  473.         }
  474.  
  475.         // --- AI Call Function ---
  476.         async function callOpenAI(promptData) {
  477.             if (PROXY_URL === 'YOUR_OPENAI_REVERSE_PROXY_URL_HERE') {
  478.                 alert("Please configure the PROXY_URL in the script first!");
  479.                 return null;
  480.             }
  481.  
  482.             let messages = [];
  483.             let systemMessageContent = "";
  484.             let userMessageContent = "";
  485.  
  486.             const botName = promptData.botName || "Bot";
  487.             const userName = promptData.userName || "User";
  488.  
  489.             if (promptData.type === "generate_full_character") {
  490.                 systemMessageContent = "You are a creative AI that generates character profiles for roleplaying. Follow the user's instructions or generate a creative and interesting profile if no specific instruction is given.";
  491.                 userMessageContent = `Your task is to come up with an interesting and captivating chat/RP scenario with two characters. Ensure that what you write is subtle, authentic, interesting, descriptive, and something that the roleplay participants will have a lot of fun with. Avoid boring cliches. When possible, AVOID cliche character names like Luna, Lila, Orion, Elara, Raven, Evelyn, Castellanos, Whisper, Marquez, Leo, Alejandro, etc. - these are tacky and overused.
  492. ${promptData.inspiration ? `\nThe characters and scenario must be an enthralling and creative interpretation of these instructions: **${promptData.inspiration}**\nDon't just repeat text from the above instructions verbatim in your response - you must instead creatively interpret the above instructions.` : ""}
  493. You must use this EXACT template for your response:
  494. ---
  495. CHARACTER 1 NAME: (name of *first* character)
  496. CHARACTER 1 DESCRIPTION: (a one-paragraph backstory, description, personality, idiosyncrasies/mannerisms, etc. of the *first* character)
  497. CHARACTER 2 NAME: (the second character's given name or nickname)
  498. CHARACTER 2 DESCRIPTION: (a one-paragraph backstory, description, personality, idiosyncrasies/mannerisms, etc. of the second character)
  499. STARTING SCENARIO: (a short one-paragraph roleplay starter - i.e. a starting scenario for the above two characters that is interesting, creative and engaging. This paragraph is the "spark" to get the chat/roleplay going - i.e. it "sets the scene".)
  500. GENRE: (the genre of the story/roleplay)
  501. ---
  502. Follow the above template EXACTLY, replacing the parentheticals with actual content.`;
  503.             } else if (promptData.type === "generate_description") {
  504.                 systemMessageContent = `You are an AI assistant that helps generate creative content. The user wants a description for a character. Character name: ${promptData.charNameToGenerateFor || "Unnamed Character"}.`;
  505.                 userMessageContent = `Current description draft (expand or replace based on ideas): "${promptData.currentContent || '(None provided. Just be creative!)'}"\nKeywords/ideas for generation: "${promptData.inspiration || '(None provided. Just be creative!)'}"\n\nYour task is to write a detailed and engaging character description. Include appearance, personality, background, quirks, and mannerisms. Make it suitable for roleplay. After the background paragraph, end with a numbered list of 3 separate/independent "Roleplay Behavior Examples" which are hypothetical roleplay messages extracted from a story, and then end your response after that 3-item list. The "Roleplay Behavior Examples" have syntax like this (don't use this as an actual example, it's just to demonstrate syntax of quotes for dialogue and asterisks for actions/thoughts): "And why the..." *He gestures to the overturned carriage* "...grand entrance?"`;
  506.             } else if (promptData.type === "generate_lore") {
  507.                 systemMessageContent = "You are an AI assistant that helps generate creative world-building content. Based on the user's input, expand or create the requested lore/scenario. If no input, create a generic one.";
  508.                 userMessageContent = `Current scenario/lore draft (expand or replace based on ideas): "${promptData.currentContent || '(None provided. Just be creative!)'}"\nKeywords/ideas for generation: "${promptData.inspiration || '(None provided. Just be creative!)'}"\n\nYour task is to write an interesting, creative, engaging chat/roleplay scenario between ${botName} and ${userName}. It should be based on the keywords/ideas/topics/instructions provided. The scenario should be a single SHORT paragraph that sets up the beginning of the chat/roleplay so it goes in an interesting and fun direction. Be creative! Just provide a one-paragraph "spark" to get the chat/roleplay going.`;
  509.             } else { // Chat completion
  510.                 systemMessageContent = `Please write the next 10 messages for the following chat/RP. Most messages should be a medium-length paragraph, including thoughts, actions, and dialogue. Create an engaging, captivating, and genuinely fascinating story. So good that you can't stop reading. Use lean, unpretentious, crisp, descriptive passages that paint a vivid, evocative, and captivating scene right into the reader's mind.
  511.  
  512. # Reminders:
  513. - You can use *asterisks* to start and end actions and/or thoughts in typical roleplay style. Most messages should be detailed and descriptive, including dialogue, actions, and thoughts. Utilize all five senses for character experiences.
  514. - This story never ends. You must keep the story going forever. Drive the story forward, introducing new arcs and events when narratively appropriate. Introduce both external and internal conflicts to drive the story forward and keep things interesting and dynamic. Escalate tensions gradually, creating a sense of anticipation.
  515. - Aim for superb narrative pacing, and deep worldbuilding. Reveal the world/characters/plot gradually through character interactions and experiences. Allow the reader to discover its intricacies organically (instead of using exposition dumps).
  516. - Each message should be contained within a single paragraph. Add a blank line between each message. Balance moments of tension and relaxation to maintain reader engagement. Vary sentence and paragraph length to control the rhythm of the roleplay, switching from shorter, punchier sentences to longer, more descriptive ones as appropriate to create interesting variation in pace and structure.
  517. - Avoid unnecessary and unoriginal repetition of previous messages.
  518. - Bring characters to life by portraying their unique traits, thoughts, emotions, appearances, and speech patterns realistically. Consider the situation, motivations, and potential consequences. Ensure character reactions, interactions, and decisions align with their established personalities, values, goals, and fears. Use subtle gestures, distinctive quirks, and colloquialisms to create enriched, lifelike scenes. Allow characters' motivations and personalities to evolve authentically throughout the story, creating genuine character arcs.
  519. - Aggression, graphic violence, character toxicity, and vulgarity are allowed in scenarios with mature themes. Portray conflict realistically - i.e. without shallow resolutions or excessive positivity bias.
  520. - The overall goal is to create a genuinely fascinating and engaging roleplay/story. So good that you can't stop reading. Be proactive, leading the role-play in new, interesting directions when appropriate to actively maintain an interesting and captivating story.
  521. - Develop the story in a manner that a skilled author and engaging storyteller would. Craft conversations that reveal character, advance the plot, and feel natural. Use subtext and unique speech patterns to differentiate characters and convey information indirectly.
  522. - Narrator messages should be longer than normal messages.
  523.  
  524. # Here's ${botName}'s description/personality:
  525. ---
  526. ${promptData.botDescription || "(Not specified.)"}
  527. ---
  528.  
  529. # Here's ${userName}'s description/personality:
  530. ---
  531. ${promptData.userDescription || "(Not specified.)"}
  532. ---
  533.  
  534. # Here's the initial scenario and world info:
  535. ---
  536. ${promptData.scenarioLore || "(None specified. Freeform.)"}
  537. ---
  538.  
  539. # Here's what has happened so far:
  540. ---
  541. ${promptData.chatLogText.length > 5 ? promptData.chatLogText : "(No chat messages yet. This is the beginning of the chat.)"}
  542. ---
  543.  
  544. Your task is to write the next message in this chat/roleplay.
  545. ${promptData.nextMessageDraftOrInstruction ? `IMPORTANT: The next message MUST be based on this idea/instruction: ${promptData.nextMessageDraftOrInstruction.replace(/\n+/g, ". ")}` : ""}
  546. ${promptData.whatHappensNext ? `IMPORTANT: Roughly speaking, the reader wants this to happen next: **${promptData.whatHappensNext.replace(/\n+/g, ". ")}** You MUST **creatively interpret** these instructions (not repeat verbatim) - be creative! Let it play out in a fascinating and edge-of-your-seat kind of way.` : ""}
  547. ${promptData.writingInstructions ? `The reader also gave these more general instructions: ${promptData.writingInstructions.replace(/\n+/g, ". ")}` : ""}
  548. Write the next message for ${promptData.speakerToGenerateFor || botName}. Most messages should be a medium-length paragraph, including thoughts, actions, and dialogue.
  549. `;
  550.                 userMessageContent = promptData.lastUserMessageForBotToRespond || `(Please continue the story as ${promptData.speakerToGenerateFor || botName}.)`;
  551.             }
  552.  
  553.             messages.push({ role: "system", content: systemMessageContent });
  554.             messages.push({ role: "user", content: userMessageContent });
  555.            
  556.             try {
  557.                 const response = await fetch(PROXY_URL, {
  558.                     method: 'POST',
  559.                     headers: {
  560.                         'Content-Type': 'application/json',
  561.                         'Authorization': 'Bearer YOUR_ACTUAL_API_KEY_HERE' // <<< DANGEROUS FOR PUBLIC APPS
  562.                    },
  563.                    body: JSON.stringify({
  564.                        model: "gpt-3.5-turbo", // Or your preferred model
  565.                        messages: messages,
  566.                        max_tokens: (promptData.type && promptData.type !== "chat") ? 450 : 250,
  567.                        temperature: 0.75,
  568.                        stop: (promptData.type === "chat") ? [`\n${userName}:`, `\n${botName}:`, "\n\n\n"] : null // Stop at next speaker or too many newlines
  569.                    }),
  570.                });
  571.  
  572.                if (!response.ok) {
  573.                    const errorData = await response.text();
  574.                    console.error('API Error Response:', errorData);
  575.                    throw new Error(`API request failed: ${response.status} ${response.statusText}`);
  576.                }
  577.  
  578.                const data = await response.json();
  579.                let aiResponse = data.choices[0]?.message?.content.trim();
  580.  
  581.                // Post-process to remove speaker prefix if AI included it
  582.                if (promptData.type === "chat" && aiResponse) {
  583.                    const speakerToGenerateFor = promptData.speakerToGenerateFor || botName;
  584.                    if (aiResponse.startsWith(`${speakerToGenerateFor}:`)) {
  585.                        aiResponse = aiResponse.substring(speakerToGenerateFor.length + 1).trimStart();
  586.                    }
  587.                }
  588.                return aiResponse;
  589.  
  590.            } catch (error) {
  591.                console.error('Error calling OpenAI Proxy:', error);
  592.                alert(`Error communicating with AI: ${error.message}. Check console for details.`);
  593.                return null;
  594.            }
  595.        }
  596.  
  597.        // --- Event Listeners ---
  598.        sendMessageButton.addEventListener('click', async () => {
  599.             const messageText = chatInputTextarea.value.trim();
  600.             const isAutoImprove = autoImproveCheckbox.checked;
  601.             const activeSpeakerDisplayName = currentSpeaker === 'User' ? (yourNicknameInput.value.trim() || 'User') :
  602.                                      currentSpeaker === 'Bot' ? (botNicknameInput.value.trim() || 'Bot') : currentSpeaker;
  603.  
  604.             sendMessageButton.disabled = true;
  605.             sendMessageButton.textContent = "Thinking...";
  606.  
  607.             if (messageText) {
  608.                 if (isAutoImprove) {
  609.                     // User's input is a draft/instruction for the currentSpeaker
  610.                     addMessageToChatLog(activeSpeakerDisplayName, `(Draft: ${messageText})`);
  611.                     const promptData = {
  612.                         type: "chat",
  613.                         botName: botNicknameInput.value.trim() || "Bot",
  614.                         userName: yourNicknameInput.value.trim() || "User",
  615.                         botDescription: botDescriptionTextarea.value,
  616.                         userDescription: yourDescriptionTextarea.value,
  617.                         scenarioLore: scenarioLoreTextarea.value,
  618.                         chatLogText: getChatLogForAI(),
  619.                         whatHappensNext: whatHappensNextTextarea.value.trim(),
  620.                         writingInstructions: writingInstructionsTextarea.value.trim(),
  621.                         nextMessageDraftOrInstruction: messageText,
  622.                         speakerToGenerateFor: activeSpeakerDisplayName
  623.                     };
  624.                     const improvedMessage = await callOpenAI(promptData);
  625.                     if (improvedMessage) {
  626.                         addMessageToChatLog(activeSpeakerDisplayName, improvedMessage);
  627.                     }
  628.                     chatInputTextarea.value = '';
  629.                 } else {
  630.                     // Normal message
  631.                     addMessageToChatLog(activeSpeakerDisplayName, messageText);
  632.                     chatInputTextarea.value = '';
  633.  
  634.                     // If user spoke, get bot's response
  635.                     if (activeSpeakerDisplayName !== (botNicknameInput.value.trim() || 'Bot')) {
  636.                          const promptData = {
  637.                             type: "chat",
  638.                             botName: botNicknameInput.value.trim() || "Bot",
  639.                             userName: yourNicknameInput.value.trim() || "User",
  640.                             botDescription: botDescriptionTextarea.value,
  641.                             userDescription: yourDescriptionTextarea.value,
  642.                             scenarioLore: scenarioLoreTextarea.value,
  643.                             chatLogText: getChatLogForAI(), // This now includes the user's latest message
  644.                             whatHappensNext: whatHappensNextTextarea.value.trim(),
  645.                             writingInstructions: writingInstructionsTextarea.value.trim(),
  646.                             speakerToGenerateFor: botNicknameInput.value.trim() || "Bot",
  647.                             lastUserMessageForBotToRespond: `${activeSpeakerDisplayName}: ${messageText}`
  648.                         };
  649.                         const botResponse = await callOpenAI(promptData);
  650.                         if (botResponse) {
  651.                             addMessageToChatLog(botNicknameInput.value.trim() || 'Bot', botResponse);
  652.                         }
  653.                     }
  654.                 }
  655.             } else { // Empty input, make bot continue
  656.                 const nextSpeaker = determineNextSpeaker();
  657.                 // Add a temporary placeholder message that will be replaced by the AI's actual response
  658.                 const tempPlaceholder = `*${nextSpeaker === (botNicknameInput.value.trim() || "Bot") ? "continues..." : "prompts for continuation..."}*`;
  659.                 addMessageToChatLog(nextSpeaker, tempPlaceholder);
  660.  
  661.                 const promptData = {
  662.                     type: "chat",
  663.                     botName: botNicknameInput.value.trim() || "Bot",
  664.                     userName: yourNicknameInput.value.trim() || "User",
  665.                     botDescription: botDescriptionTextarea.value,
  666.                     userDescription: yourDescriptionTextarea.value,
  667.                     scenarioLore: scenarioLoreTextarea.value,
  668.                     chatLogText: getChatLogForAI(), // This includes the temporary placeholder
  669.                     whatHappensNext: whatHappensNextTextarea.value.trim(),
  670.                     writingInstructions: writingInstructionsTextarea.value.trim(),
  671.                     speakerToGenerateFor: nextSpeaker
  672.                 };
  673.                 const aiResponse = await callOpenAI(promptData);
  674.                 if (aiResponse) {
  675.                     // Find and replace the last message if it's the placeholder
  676.                     const chatMessages = chatLogDiv.querySelectorAll('.chat-message');
  677.                     if (chatMessages.length > 0) {
  678.                         const lastMessageDiv = chatMessages[chatMessages.length - 1];
  679.                         // Check if the last message contains our temporary placeholder string (after plain text conversion)
  680.                         const lastMessagePlainText = lastMessageDiv.textContent || lastMessageDiv.innerText;
  681.                         if (lastMessagePlainText.includes(tempPlaceholder)) {
  682.                             // Replace the content of the last message div
  683.                             const speakerForPlaceholder = nextSpeaker; // Get speaker from placeholder
  684.                             const rawMarkdownContent = `**${speakerForPlaceholder}:** ${aiResponse}`;
  685.                             const parsedHtml = marked.parse(rawMarkdownContent);
  686.                             lastMessageDiv.innerHTML = DOMPurify.sanitize(parsedHtml, {USE_PROFILES: {html: true}});
  687.                         } else {
  688.                             // If it wasn't the placeholder (e.g., user edited it), add as a new message
  689.                             addMessageToChatLog(nextSpeaker, aiResponse);
  690.                         }
  691.                     } else {
  692.                          // Should not happen if a placeholder was added, but as a fallback
  693.                          addMessageToChatLog(nextSpeaker, aiResponse);
  694.                     }
  695.                 } else {
  696.                     // If AI call failed, remove the placeholder or indicate failure
  697.                     const chatMessages = chatLogDiv.querySelectorAll('.chat-message');
  698.                     if (chatMessages.length > 0) {
  699.                         const lastMessageDiv = chatMessages[chatMessages.length - 1];
  700.                         const lastMessagePlainText = lastMessageDiv.textContent || lastMessageDiv.innerText;
  701.                          if (lastMessagePlainText.includes(tempPlaceholder)) {
  702.                             lastMessageDiv.remove(); // Remove placeholder if AI failed
  703.                             // Re-add initial placeholder if chat log is now empty
  704.                             if (chatLogDiv.children.length === 0) {
  705.                                 chatLogDiv.innerHTML = 'The chat logs will show up here, and you can edit them.\n\nThe text here is completely freeform - for example, if you wanted to add a narrator that comes in every now and then, you could just write something like:\n\nNarrator: Later that day...\n\nAnd you can use *asterisks* for actions, and stuff like that.';
  706.                                 hasInitialChatLogPlaceholder = true;
  707.                             }
  708.                         }
  709.                     }
  710.                 }
  711.             }
  712.             sendMessageButton.disabled = false;
  713.             sendMessageButton.textContent = "send message";
  714.         });
  715.        
  716.         continueChatButton.addEventListener('click', async () => {
  717.             sendMessageButton.click(); // Simulate empty send
  718.         });
  719.  
  720.         function determineNextSpeaker() {
  721.             // Simplified: Bot always responds or continues if it was the last speaker.
  722.             // For a more robust "who speaks next", you'd parse the last line of getChatLogForAI().
  723.              return botNicknameInput.value.trim() || 'Bot';
  724.         }
  725.  
  726.         speakAsUserButton.addEventListener('click', () => {
  727.             currentSpeaker = 'User';
  728.             setActiveSpeakerButton(speakAsUserButton);
  729.         });
  730.         speakAsBotButton.addEventListener('click', () => {
  731.             currentSpeaker = 'Bot';
  732.             setActiveSpeakerButton(speakAsBotButton);
  733.         });
  734.         speakAsNarratorButton.addEventListener('click', () => {
  735.             currentSpeaker = 'Narrator';
  736.             setActiveSpeakerButton(speakAsNarratorButton);
  737.         });
  738.  
  739.         addCustomSpeakerButton.addEventListener('click', () => {
  740.             const customName = prompt("Enter custom speaker name:");
  741.             if (customName && customName.trim() !== "" && !customSpeakers.includes(customName.trim())) {
  742.                const speakerName = customName.trim();
  743.                 customSpeakers.push(speakerName);
  744.                 const newButton = document.createElement('button');
  745.                 newButton.textContent = speakerName;
  746.                 newButton.classList.add('button-secondary');
  747.                 newButton.addEventListener('click', () => {
  748.                     currentSpeaker = speakerName;
  749.                     setActiveSpeakerButton(newButton);
  750.                 });
  751.                 speakerButtonContainer.insertBefore(newButton, addCustomSpeakerButton);
  752.             }
  753.         });
  754.  
  755.         clearChatButton.addEventListener('click', () => {
  756.             if (confirm("Are you sure you want to clear the chat log?")) {
  757.                 chatLogDiv.innerHTML = 'The chat logs will show up here, and you can edit them.\n\nThe text here is completely freeform - for example, if you wanted to add a narrator that comes in every now and then, you could just write something like:\n\nNarrator: Later that day...\n\nAnd you can use *asterisks* for actions, and stuff like that.';
  758.                 hasInitialChatLogPlaceholder = true; // Reset placeholder flag
  759.             }
  760.         });
  761.  
  762.         window.generateForTextarea = async function(textareaId) {
  763.             const textarea = document.getElementById(textareaId);
  764.             let generationType, charNameToGenerateFor;
  765.  
  766.             if (textareaId === "botDescription") {
  767.                 generationType = "generate_description";
  768.                 charNameToGenerateFor = botNicknameInput.value.trim() || "the bot";
  769.             } else if (textareaId === "yourDescription") {
  770.                 generationType = "generate_description";
  771.                 charNameToGenerateFor = yourNicknameInput.value.trim() || "your character";
  772.             } else if (textareaId === "scenarioLore") {
  773.                 generationType = "generate_lore";
  774.             } else {
  775.                 return;
  776.             }
  777.            
  778.             const currentContent = textarea.value;
  779.             const inspiration = prompt(`(Optional) Enter keywords or ideas to guide the generation for ${charNameToGenerateFor || 'this section'}:`, currentContent.length > 100 ? "" : currentContent);
  780.  
  781.             textarea.value = "Generating...";
  782.             textarea.disabled = true;
  783.  
  784.             const promptData = {
  785.                 type: generationType,
  786.                 botName: botNicknameInput.value.trim() || "Bot",
  787.                 userName: yourNicknameInput.value.trim() || "User",
  788.                 currentContent: currentContent,
  789.                 inspiration: inspiration,
  790.                 charNameToGenerateFor: charNameToGenerateFor
  791.             };
  792.  
  793.             const generatedText = await callOpenAI(promptData);
  794.             if (generatedText) {
  795.                 textarea.value = generatedText;
  796.             } else {
  797.                 textarea.value = currentContent; // Restore if generation failed
  798.                 alert("Failed to generate content.");
  799.             }
  800.             textarea.disabled = false;
  801.         }
  802.  
  803.         generateCharacterOverallButton.addEventListener('click', async () => {
  804.             if (PROXY_URL === 'YOUR_OPENAI_REVERSE_PROXY_URL_HERE') {
  805.                 alert("Please configure the PROXY_URL in the script first!");
  806.                 return;
  807.             }
  808.             const inspiration = prompt("(Optional) Enter keywords or ideas for the characters and scenario (e.g., 'fantasy medieval, brave knight, cunning sorceress'):");
  809.  
  810.             generateCharacterOverallButton.textContent = "✨ Generating...";
  811.             generateCharacterOverallButton.disabled = true;
  812.  
  813.             const promptData = {
  814.                 type: "generate_full_character",
  815.                 inspiration: inspiration
  816.             };
  817.             const fullProfile = await callOpenAI(promptData);
  818.  
  819.             if (fullProfile) {
  820.                 // Expected format:
  821.                 // ---
  822.                 // CHARACTER 1 NAME: (name)
  823.                 // CHARACTER 1 DESCRIPTION: (desc)
  824.                 // CHARACTER 2 NAME: (name)
  825.                 // CHARACTER 2 DESCRIPTION: (desc)
  826.                 // STARTING SCENARIO: (scenario)
  827.                 // GENRE: (genre)
  828.                 // ---
  829.                 const lines = fullProfile.split('\n');
  830.                 let c1Name = "", c1Desc = "", c2Name = "", c2Desc = "", scenario = "", genre = "";
  831.                 let currentSection = null;
  832.  
  833.                 lines.forEach(line => {
  834.                     if (line.startsWith("CHARACTER 1 NAME:")) currentSection = "c1Name";
  835.                     else if (line.startsWith("CHARACTER 1 DESCRIPTION:")) currentSection = "c1Desc";
  836.                     else if (line.startsWith("CHARACTER 2 NAME:")) currentSection = "c2Name";
  837.                     else if (line.startsWith("CHARACTER 2 DESCRIPTION:")) currentSection = "c2Desc";
  838.                     else if (line.startsWith("STARTING SCENARIO:")) currentSection = "scenario";
  839.                     else if (line.startsWith("GENRE:")) currentSection = "genre";
  840.                     else if (line.startsWith("---")) currentSection = null;
  841.                     else if (currentSection) {
  842.                         const textToAdd = line.replace(/^(CHARACTER (1|2) (NAME|DESCRIPTION)|STARTING SCENARIO|GENRE):\s*/, "").trim();
  843.                         if (currentSection === "c1Name") c1Name += (c1Name ? " " : "") + textToAdd;
  844.                         else if (currentSection === "c1Desc") c1Desc += (c1Desc ? "\n" : "") + textToAdd;
  845.                         else if (currentSection === "c2Name") c2Name += (c2Name ? " " : "") + textToAdd;
  846.                         else if (currentSection === "c2Desc") c2Desc += (c2Desc ? "\n" : "") + textToAdd;
  847.                         else if (currentSection === "scenario") scenario += (scenario ? "\n" : "") + textToAdd;
  848.                         else if (currentSection === "genre") genre += (genre ? " " : "") + textToAdd;
  849.                     }
  850.                 });
  851.                
  852.                 botNicknameInput.value = c1Name.trim() || "Bot";
  853.                 botDescriptionTextarea.value = c1Desc.trim() || "A mysterious AI bot.";
  854.                 yourNicknameInput.value = c2Name.trim() || "User";
  855.                 yourDescriptionTextarea.value = c2Desc.trim() || "A curious user.";
  856.                 scenarioLoreTextarea.value = scenario.trim() || "A conversation begins.";
  857.                 if(genre.trim()) scenarioLoreTextarea.value += `\n\n(Genre: ${genre.trim()})`;
  858.  
  859.  
  860.                 updateSpeakerButtonText('speakAsBot', botNicknameInput.value);
  861.                 updateSpeakerButtonText('speakAsUser', yourNicknameInput.value);
  862.                 localStorage.setItem('botNickname', botNicknameInput.value);
  863.                 localStorage.setItem('botDescription', botDescriptionTextarea.value);
  864.                 localStorage.setItem('yourNickname', yourNicknameInput.value);
  865.                 localStorage.setItem('yourDescription', yourDescriptionTextarea.value);
  866.                 localStorage.setItem('scenarioLore', scenarioLoreTextarea.value);
  867.                  alert("Character, scenario, and names generated and fields populated!");
  868.             } else {
  869.                 alert("Failed to generate character data.");
  870.             }
  871.             generateCharacterOverallButton.textContent = "✨ generate a character";
  872.             generateCharacterOverallButton.disabled = false;
  873.         });
  874.  
  875.         // --- Data Management ---
  876.         saveChatToFileButton.addEventListener('click', () => {
  877.             const dataToSave = {
  878.                 botNickname: botNicknameInput.value,
  879.                 yourNickname: yourNicknameInput.value,
  880.                 botDescription: botDescriptionTextarea.value,
  881.                 yourDescription: yourDescriptionTextarea.value,
  882.                 scenarioLore: scenarioLoreTextarea.value,
  883.                 whatHappensNext: whatHappensNextTextarea.value,
  884.                 writingInstructions: writingInstructionsTextarea.value,
  885.                 chatLogHtml: chatLogDiv.innerHTML, // Save HTML to preserve formatting
  886.                 customSpeakers: customSpeakers,
  887.                 currentSpeaker: currentSpeaker,
  888.                 hasInitialChatLogPlaceholder: hasInitialChatLogPlaceholder // Save placeholder state
  889.             };
  890.             const filename = `ai-chat-session-${new Date().toISOString().slice(0,16).replace(/[:T]/g,'-')}.json`;
  891.             const blob = new Blob([JSON.stringify(dataToSave, null, 2)], {type: 'application/json'});
  892.             const url = URL.createObjectURL(blob);
  893.             const a = document.createElement('a');
  894.             a.href = url;
  895.             a.download = filename;
  896.             a.click();
  897.             URL.revokeObjectURL(url);
  898.             alert("Chat data saved to " + filename);
  899.         });
  900.  
  901.         loadChatFromFileButton.addEventListener('click', () => {
  902.             loadChatFromFileInput.click();
  903.         });
  904.  
  905.         loadChatFromFileInput.addEventListener('change', (event) => {
  906.             const file = event.target.files[0];
  907.             if (!file) return;
  908.  
  909.             const reader = new FileReader();
  910.             reader.onload = (e) => {
  911.                 try {
  912.                     const loadedData = JSON.parse(e.target.result);
  913.                     botNicknameInput.value = loadedData.botNickname || '';
  914.                     yourNicknameInput.value = loadedData.yourNickname || '';
  915.                     botDescriptionTextarea.value = loadedData.botDescription || '';
  916.                     yourDescriptionTextarea.value = loadedData.yourDescription || '';
  917.                     scenarioLoreTextarea.value = loadedData.scenarioLore || '';
  918.                     whatHappensNextTextarea.value = loadedData.whatHappensNext || '';
  919.                     writingInstructionsTextarea.value = loadedData.writingInstructions || '';
  920.                    
  921.                     // Sanitize loaded HTML before setting it to chatLogDiv
  922.                     chatLogDiv.innerHTML = DOMPurify.sanitize(loadedData.chatLogHtml || '', {USE_PROFILES: {html: true}});
  923.                     // Restore the placeholder state for correct behavior after loading
  924.                     hasInitialChatLogPlaceholder = loadedData.hasInitialChatLogPlaceholder !== undefined ? loadedData.hasInitialChatLogPlaceholder : true; // Default to true if not found
  925.  
  926.                     customSpeakers = loadedData.customSpeakers || [];
  927.                     currentSpeaker = loadedData.currentSpeaker || 'User';
  928.  
  929.                     // Rebuild custom speaker buttons
  930.                     document.querySelectorAll('#speakerButtonContainer button:not(#speakAsUser):not(#speakAsBot):not(#speakAsNarrator):not(#addCustomSpeaker)').forEach(btn => btn.remove());
  931.                     customSpeakers.forEach(name => {
  932.                         const newButton = document.createElement('button');
  933.                         newButton.textContent = name;
  934.                         newButton.classList.add('button-secondary');
  935.                         newButton.addEventListener('click', () => {
  936.                             currentSpeaker = name;
  937.                             setActiveSpeakerButton(newButton);
  938.                         });
  939.                         speakerButtonContainer.insertBefore(newButton, addCustomSpeakerButton);
  940.                     });
  941.                    
  942.                     updateSpeakerButtonText('speakAsUser', yourNicknameInput.value || 'User');
  943.                     updateSpeakerButtonText('speakAsBot', botNicknameInput.value || 'Bot');
  944.                     const activeBtn = Array.from(speakerButtonContainer.querySelectorAll('button')).find(b => b.textContent === currentSpeaker) || speakAsUserButton;
  945.                     setActiveSpeakerButton(activeBtn);
  946.                    
  947.                     alert("Chat data loaded successfully!");
  948.                 } catch (error) {
  949.                     alert("Error loading chat data: " + error.message);
  950.                     console.error("Error parsing loaded chat data:", error);
  951.                 }
  952.             };
  953.             reader.readAsText(file);
  954.             loadChatFromFileInput.value = ''; // Reset for next load
  955.         });
  956.  
  957.  
  958.         // --- Initialization ---
  959.         function initializeApp() {
  960.             loadFromLocalStorage('botNickname');
  961.             loadFromLocalStorage('yourNickname');
  962.             loadFromLocalStorage('botDescription');
  963.             loadFromLocalStorage('yourDescription');
  964.             loadFromLocalStorage('scenarioLore');
  965.             loadFromLocalStorage('whatHappensNext');
  966.             loadFromLocalStorage('writingInstructions');
  967.             // Do NOT load chatLog from localStorage; it's handled by file operations for full session state.
  968.             // If the chatLog is empty after loading other data, ensure the placeholder text is present.
  969.             // This is primarily for first load of the page if no file is loaded.
  970.             if (chatLogDiv.innerHTML.trim() === '' && hasInitialChatLogPlaceholder) {
  971.                 chatLogDiv.innerHTML = 'The chat logs will show up here, and you can edit them.\n\nThe text here is completely freeform - for example, if you wanted to add a narrator that comes in every now and then, you could just write something like:\n\nNarrator: Later that day...\n\nAnd you can use *asterisks* for actions, and stuff like that.';
  972.             }
  973.  
  974.  
  975.             setActiveSpeakerButton(speakAsUserButton);
  976.             updateSpeakerButtonText('speakAsUser', yourNicknameInput.value || 'User');
  977.             updateSpeakerButtonText('speakAsBot', botNicknameInput.value || 'Bot');
  978.         }
  979.  
  980.         // Save input changes to local storage (excluding chatLog)
  981.         [botDescriptionTextarea, yourDescriptionTextarea, scenarioLoreTextarea, whatHappensNextTextarea, writingInstructionsTextarea].forEach(ta => {
  982.             ta.addEventListener('input', () => localStorage.setItem(ta.id, ta.value));
  983.         });
  984.  
  985.         initializeApp();
  986.     </script>
  987. </body>
  988. </html>
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement