(:Summary: Preferences for [[PmWiki/access keys]] and edit form:)
Preferences for [[PmWiki/access keys]] and edit form

This page can be used as template for (personal) preferences, or set as default site preference (see below).

->[@
  # Access keys - hold Alt (Windows IE), Shift + Alt (Firefox)
  # or Control (Mac) and tap the indicated key on your keyboard
  # to trigger the corresponding action.
#  'ak_view'          => '' ,         # view page
  'ak_edit'          => 'e',         # edit page
  'ak_history'       => 'h',         # page history
#  'ak_print'         => '',          # print view of page
  'ak_recentchanges' => 'c',         # Recent Changes
  'ak_save'          => 's',         # save page
  'ak_saveedit'      => 'u',         # save and keep editing
  'ak_savedraft'     => 'd',         # save as draft
  'ak_preview'       => 'p',         # preview page
  'ak_textedit'      => ','          # focus to edit textarea (at the end)
  'ak_em'            => 'i',         # emphasized text (italic)
  'ak_strong'        => 'b',         # strong text (bold)

#  'ak_attach'        => '',          # attach a file
#  'ak_backlinks'     => '',          # show backlinks
#  'ak_logout'        => '',          # log out

  # Editing components
  'e_rows' => '20',                  # rows in edit textarea
  'e_cols' => '70',                  # columns in edit textarea
  'Site.EditForm' => 'Site.EditForm' # location of EditForm
  @]

To create personal user (browser) preferences,
* make a copy of [[{$FullName}?action=source | this page]] somewhere, preferably as @@Profiles.''%green%insert_your_name_here%%''-Preferences@@
* edit that page with your new preferred settings,
* select [[{$FullName}?setprefs={$FullName} | Set Preferences of this Page]] on the page containing your newly created settings. 
-> This sets a preference cookie on your browser which tells PmWiki where to find your personal preference settings.

To revert to the PmWiki default preferences
* select [[{$Name}?setprefs=| Revert to PmWiki Default Preferences]] to unset the preference cookie

See also [[Cookbook:UserConfigurations]] about how to customise the edit form for personal use.


Note that by default, parsing of {$FullName} is disabled. To make it the default site preference, add
 XLPage('prefs', "Site.Preferences");
to a config file (e.g. local/config.php).

To disable user preferences entirely set $EnablePrefs = 0;
<!-- Global site tag (gtag.js) - Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-14BXFXE5GM"></script>
<script>
  window.dataLayer = window.dataLayer || [];
  function gtag(){dataLayer.push(arguments);}
  gtag('js', new Date());
  gtag('config', 'G-14BXFXE5GM');
</script><!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Streaming Chatbot</title>
  <style>
    /* Collapsed chat container */
    #chatbotContainer {
      position: fixed;
      bottom: 20px;
      right: 20px;
      width: 60px;
      height: 60px;
      border-radius: 50%;
      background-color: #007aff;
      box-shadow: 0 0 10px rgba(0, 0, 0, 0.2);
      cursor: pointer;
      z-index: 1000;
      animation: pulse 2s infinite;
      display: flex;
      align-items: center;
      justify-content: center;
      transition: all 0.3s ease;
    }

    /* Expanded chat container with responsive width and vertical scrolling */
    #chatbotContainer.expanded {
      width: auto;
      height: auto;
      min-width: 250px;
      max-width: 90vw; /* For mobile browsers */
      max-height: 90vh; /* Limit height to 90% of the viewport */
      padding: 10px;
      border-radius: 20px;
      animation: none;
      background-color: rgba(255, 255, 255, 0.8); /* semi-transparent */
      box-shadow: 0 0 20px rgba(0, 0, 0, 0.5);
      overflow-y: auto;  /* Vertical scrollbar appears if content exceeds max-height */
      overflow-x: auto;
      right: 20px;
      left: auto;
    }

    /* Double chat window width for desktop browsers */
    @media (min-width: 768px) {
      #chatbotContainer.expanded {
        max-width: 800px;
      }
    }

    #chatbotIcon {
      font-size: 30px;
      color: white;
    }

    #chatbotContainer.expanded #chatbotIcon {
      display: none;
    }

    #chatContent {
      display: none;
      flex-direction: column;
      height: auto;
      min-width: 250px;
      max-width: 88vw; /* For mobile browsers */
      max-height: 88vh; /* Limit height to 90% of the viewport */
      padding: 5px;
      border-radius: 5px;
      animation: none;
      background-color: rgba(255, 255, 255, 0.8); /* semi-transparent */
      box-shadow: 0 0 20px rgba(0, 0, 0, 0.5);
      overflow-y: auto;  /* Vertical scrollbar appears if content exceeds max-height */
      overflow-x: hidden;
      right: 20px;
      left: auto;
    }

    #chatbotContainer.expanded #chatContent {
      display: flex;
    }

    /* Chat box for messages */
    #chatBox {
      flex-grow: 1;
      overflow-y: auto;
      border: none;
      padding: 10px;
      background-color: #f0f0f0;
      border-radius: 10px;
      margin-bottom: 10px;
    }

    /* Code block styling: force horizontal scroll if content exceeds width */
    .message pre {
      display: block;
      max-width: 100%;
      overflow-x: auto;
      white-space: pre;
      -webkit-overflow-scrolling: touch;
      padding: 10px;
      background-color: #d3d3d3;
      color: #030303;
      border-radius: 5px;
      margin: 5px 0;
    }

    /* Inline code styling */
    .message code {
      background-color: #d3d3d3;
      color: #030303;
      padding: 2px 4px;
      border-radius: 3px;
    }

    #messageInput, #sendMessageButton {
      padding: 10px;
      border: 1px solid #ccc;
      border-radius: 20px;
      font-size: 12px;
    }

    #messageInput {
      flex-grow: 1;
      margin-right: 10px;
      border: 1px solid #dcdcdc;
    }

    #sendMessageButton {
      background-color: #007aff;
      color: white;
      border: none;
      cursor: pointer;
      border-radius: 20px;
    }

    #sendMessageButton:hover {
      background-color: #005bb5;
    }

    /* Updated minimize button styling to match send button */
    #minimizeButton {
      background-color: #007aff;
      color: white;
      border: none;
      cursor: pointer;
      border-radius: 20px;
      padding: 10px;
      margin-left: 10px;
    }

    #minimizeButton:hover {
      background-color: #005bb5;
    }

    /* Chat message bubble styles */
    .message {
      margin: 5px 0;
      padding: 10px;
      border-radius: 20px;
      max-width: 100%;
      font-size: 12px;
      line-height: 1.4;
      word-wrap: break-word;
    }

    .sent {
      align-self: flex-end;
      background-color: #007aff;
      color: white;
      border-bottom-right-radius: 0;
    }

    .received {
      align-self: flex-start;
      background-color: #e5e5ea;
      color: black;
      border-bottom-left-radius: 0;
    }

    .typing {
      align-self: flex-start;
      font-style: italic;
      color: #888;
    }

    @keyframes pulse {
      0% { transform: scale(1); }
      50% { transform: scale(1.1); }
      100% { transform: scale(1); }
    }
  </style>
</head>
<body>
  <div id="chatbotContainer">
    <div id="chatbotIcon">💬</div>
    <div id="chatContent">
      <div id="chatBox"></div>
      <!-- Container: input, send button, and minimize button on one row -->
      <div style="display: flex; align-items: center;">
        <input type="text" id="messageInput" placeholder="Ask a question...">
        <button id="sendMessageButton" onclick="sendMessage()">Send</button>
        <button id="minimizeButton" onclick="minimizeChat()">Minimize</button>
      </div>
    </div>
  </div>

  <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
  <script>
    const chatbotContainer = document.getElementById('chatbotContainer');
    const chatContent = document.getElementById('chatContent');

    // Toggle the expanded state when clicking the container or icon
    chatbotContainer.addEventListener('click', function(event) {
      if (event.target === chatbotContainer || event.target === document.getElementById('chatbotIcon')) {
        chatbotContainer.classList.toggle('expanded');
      }
    });

    function minimizeChat() {
      chatbotContainer.classList.remove('expanded');
      // Restore default max-width if it was changed due to code blocks
      chatbotContainer.style.maxWidth = "";
    }

    function sendMessage() {
      const input = document.getElementById('messageInput');
      const message = input.value.trim();

      if (message) {
        // Display the sent message
        displayMessage(message, 'sent');

        // Show typing indicator
        const typingIndicator = displayMessage('AI is typing...', 'typing', true);

        // Capture page context if needed
        const pageContent = document.body.innerText.trim();

        // Prepare payload
        const payload = `Context: ${pageContent} \n\n Question: ${message}`;

        // Open a WebSocket connection
        const websocket = new WebSocket('wss://hedengren.net/apmchat');
        input.value = '';

        let streamingText = '';
        let receivedMessageElement = null;

        websocket.onopen = function() {
          websocket.send(payload);
        };

        websocket.onmessage = function(event) {
          // On first chunk: remove typing indicator and create a new message container
          if (!receivedMessageElement) {
            removeMessage(typingIndicator);
            receivedMessageElement = document.createElement('div');
            receivedMessageElement.className = 'message received';
            document.getElementById('chatBox').appendChild(receivedMessageElement);
          }
          // Append incoming chunk
          streamingText += event.data;
          receivedMessageElement.innerHTML = marked.parse(streamingText);

          // If a code block (<pre> element) is detected in the message,
          // ensure the container is set to the desired max-width.
          if (receivedMessageElement.querySelector('pre')) {
            chatbotContainer.style.maxWidth = "90vw";
          }

          // Ensure the chat scrolls to the bottom
          document.getElementById('chatBox').scrollTop = document.getElementById('chatBox').scrollHeight;
        };

        websocket.onerror = function() {
          removeMessage(typingIndicator);
          displayMessage('Error in connection', 'received');
          websocket.close();
        };

        websocket.onclose = function() {
          // Any final cleanup if needed
        };
      }
    }

    // Utility: display a message and (optionally) return its element
    function displayMessage(text, className, returnElement = false) {
      const messageDiv = document.createElement('div');
      messageDiv.className = 'message ' + className;
      messageDiv.innerHTML = marked.parse(text);
      document.getElementById('chatBox').appendChild(messageDiv);
      document.getElementById('chatBox').scrollTop = document.getElementById('chatBox').scrollHeight;
      
      if (returnElement) {
        return messageDiv;
      }
    }

    function removeMessage(element) {
      if (element && element.parentNode) {
        element.parentNode.removeChild(element);
      }
    }

    // Send message on Enter key press
    document.getElementById('messageInput').addEventListener('keypress', function(e) {
      if (e.key === 'Enter') {
        sendMessage();
      }
    });
  </script>
</body>
</html>