JanitorAI Context Maker

Adds a Location and Character System to JanitorAI, hopefully making the bots a little less dumb.

As of 16/09/2024. See the latest version.

// ==UserScript==
// @name         JanitorAI Context Maker
// @namespace    http://tampermonkey.net/
// @version      1.7
// @license MIT
// @description  Adds a Location and Character System to JanitorAI, hopefully making the bots a little less dumb.
// @match        https://janitorai.com/chats/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=https://janitorai.com/
// @grant        GM.setValue
// @grant        GM.getValue
// ==/UserScript==

(async function() {
    'use strict';

    const themes = {
        dark: {
            '--bg-color': 'rgba(34, 34, 34, var(--ui-transparency))',
            '--text-color': '#ffffff',
            '--border-color': 'rgba(68, 68, 68, var(--ui-transparency))',
            '--button-bg-color': 'rgba(0, 123, 255, var(--ui-transparency))',
            '--active-char-color': 'rgba(173, 216, 230, var(--ui-transparency))',
            '--success-bg-color': 'rgba(40, 167, 69, var(--ui-transparency))',
            '--info-bg-color': 'rgba(23, 162, 184, var(--ui-transparency))',
            '--warning-bg-color': 'rgba(255, 193, 7, var(--ui-transparency))',
            '--muted-bg-color': 'rgba(108, 117, 125, var(--ui-transparency))',
            '--danger-bg-color': 'rgba(220, 53, 69, var(--ui-transparency))',
            '--shadow-color': 'rgba(0,0,0,0.5)'
        },
        light: {
            '--bg-color': 'rgba(255, 255, 255, var(--ui-transparency))',
            '--text-color': '#000000',
            '--border-color': 'rgba(204, 204, 204, var(--ui-transparency))',
            '--button-bg-color': 'rgba(0, 123, 255, var(--ui-transparency))',
            '--active-char-color': 'rgba(173, 216, 230, var(--ui-transparency))',
            '--success-bg-color': 'rgba(40, 167, 69, var(--ui-transparency))',
            '--info-bg-color': 'rgba(23, 162, 184, var(--ui-transparency))',
            '--warning-bg-color': 'rgba(255, 193, 7, var(--ui-transparency))',
            '--muted-bg-color': 'rgba(108, 117, 125, var(--ui-transparency))',
            '--danger-bg-color': 'rgba(220, 53, 69, var(--ui-transparency))',
            '--shadow-color': 'rgba(0,0,0,0.1)'
        },
        sepia: {
            '--bg-color': 'rgba(244, 232, 208, var(--ui-transparency))',
            '--text-color': '#5b4636',
            '--border-color': 'rgba(193, 154, 107, var(--ui-transparency))',
            '--button-bg-color': 'rgba(160, 82, 45, var(--ui-transparency))',
            '--active-char-color': 'rgba(210, 180, 140, var(--ui-transparency))',
            '--success-bg-color': 'rgba(107, 68, 35, var(--ui-transparency))',
            '--info-bg-color': 'rgba(194, 148, 110, var(--ui-transparency))',
            '--warning-bg-color': 'rgba(215, 172, 116, var(--ui-transparency))',
            '--muted-bg-color': 'rgba(160, 130, 94, var(--ui-transparency))',
            '--danger-bg-color': 'rgba(168, 96, 50, var(--ui-transparency))',
            '--shadow-color': 'rgba(0,0,0,0.3)'
        },
        solarized: {
            '--bg-color': 'rgba(253, 246, 227, var(--ui-transparency))',
            '--text-color': '#657b83',
            '--border-color': 'rgba(238, 232, 213, var(--ui-transparency))',
            '--button-bg-color': 'rgba(38, 139, 210, var(--ui-transparency))',
            '--active-char-color': 'rgba(133, 153, 0, var(--ui-transparency))',
            '--success-bg-color': 'rgba(133, 153, 0, var(--ui-transparency))',
            '--info-bg-color': 'rgba(38, 139, 210, var(--ui-transparency))',
            '--warning-bg-color': 'rgba(181, 137, 0, var(--ui-transparency))',
            '--muted-bg-color': 'rgba(147, 161, 161, var(--ui-transparency))',
            '--danger-bg-color': 'rgba(220, 50, 47, var(--ui-transparency))',
            '--shadow-color': 'rgba(0,0,0,0.2)'
        },
        forest: {
            '--bg-color': 'rgba(34, 49, 34, var(--ui-transparency))',
            '--text-color': '#e0f7e9',
            '--border-color': 'rgba(46, 61, 46, var(--ui-transparency))',
            '--button-bg-color': 'rgba(60, 179, 113, var(--ui-transparency))',
            '--active-char-color': 'rgba(144, 238, 144, var(--ui-transparency))',
            '--success-bg-color': 'rgba(34, 139, 34, var(--ui-transparency))',
            '--info-bg-color': 'rgba(144, 238, 144, var(--ui-transparency))',
            '--warning-bg-color': 'rgba(189, 183, 107, var(--ui-transparency))',
            '--muted-bg-color': 'rgba(85, 107, 47, var(--ui-transparency))',
            '--danger-bg-color': 'rgba(139, 69, 19, var(--ui-transparency))',
            '--shadow-color': 'rgba(0,0,0,0.4)'
        },
        ocean: {
            '--bg-color': 'rgba(28, 107, 160, var(--ui-transparency))',
            '--text-color': '#ffffff',
            '--border-color': 'rgba(54, 144, 192, var(--ui-transparency))',
            '--button-bg-color': 'rgba(0, 123, 255, var(--ui-transparency))',
            '--active-char-color': 'rgba(173, 216, 230, var(--ui-transparency))',
            '--success-bg-color': 'rgba(60, 179, 113, var(--ui-transparency))',
            '--info-bg-color': 'rgba(23, 162, 184, var(--ui-transparency))',
            '--warning-bg-color': 'rgba(255, 193, 7, var(--ui-transparency))',
            '--muted-bg-color': 'rgba(108, 117, 125, var(--ui-transparency))',
            '--danger-bg-color': 'rgba(220, 53, 69, var(--ui-transparency))',
            '--shadow-color': 'rgba(0,0,0,0.3)'
        }
    };

    // Initialize CSS variables
    function setTheme(themeName) {
        const theme = themes[themeName];
        Object.keys(theme).forEach(key => {
            document.documentElement.style.setProperty(key, theme[key]);
        });
    }

    // Get saved transparency and theme, or use defaults
    const defaultTransparency = await GM.getValue('transparency', '0.9');
    const defaultTheme = await GM.getValue('theme', 'dark');

    // Set default theme and transparency
    document.documentElement.style.setProperty('--ui-transparency', defaultTransparency);
    setTheme(defaultTheme);

    // Add placeholder styles
    const style = document.createElement('style');
    style.innerHTML = `
    input::placeholder, textarea::placeholder {
        color: var(--text-color);
    }
    input:-ms-input-placeholder, textarea:-ms-input-placeholder {
        color: var(--text-color);
    }
    input::-ms-input-placeholder, textarea::-ms-input-placeholder {
        color: var(--text-color);
    }
    input::-webkit-input-placeholder, textarea::-webkit-input-placeholder {
        color: var(--text-color);
    }
    `;
    document.head.appendChild(style);

    // Character Sidebar
    const characterSidebar = document.createElement('div');
    characterSidebar.id = 'character-sheet-sidebar';
    characterSidebar.style.cssText = `
        position: fixed;
        top: 0;
        right: -300px;
        width: 300px;
        height: 100%;
        display: flex;
        flex-direction: column;
        background-color: var(--bg-color);
        border-left: 2px solid var(--border-color);
        box-shadow: -2px 0 5px var(--shadow-color);
        box-sizing: border-box;
        transition: right 0.3s;
        z-index: 9999;
    `;
    document.body.appendChild(characterSidebar);

    const characterToggleButton = document.createElement('button');
    characterToggleButton.textContent = 'Characters ☰';
    characterToggleButton.style.cssText = `
        position: fixed;
        top: 10px;
        right: 10px;
        padding: 5px 10px;
        border: none;
        background-color: var(--button-bg-color);
        color: var(--text-color);
        border-radius: 5px;
        cursor: pointer;
        transition: right 0.3s;
        z-index: 10000;
    `;
    characterToggleButton.addEventListener('click', () => {
        const isOpen = characterSidebar.style.right === '0px';
        characterSidebar.style.right = isOpen ? '-300px' : '0';
        characterToggleButton.style.right = isOpen ? '10px' : '310px';
    });
    document.body.appendChild(characterToggleButton);

    const characterSlotList = document.createElement('div');
    characterSlotList.id = 'character-slot-list';
    characterSlotList.style.cssText = `
        flex-grow: 1;
        overflow-y: auto;
        margin-top: 10px;
    `;
    characterSidebar.appendChild(characterSlotList);

    const characterButtonBox = document.createElement('div');
    characterButtonBox.style.cssText = `
        height: 40px;
        background-color: var(--bg-color);
        flex-shrink: 0;
        margin-bottom: 10px;
    `;
    characterSidebar.appendChild(characterButtonBox);

    const characterButtonContainer = document.createElement('div');
    characterButtonContainer.style.cssText = `
        display: flex;
        flex-wrap: wrap;
        justify-content: space-between;
    `;

    const newCharacterButton = document.createElement('button');
    newCharacterButton.textContent = 'New Character';
    newCharacterButton.style.cssText = `
        margin: 5px 0;
        padding: 5px 10px;
        border: none;
        background-color: var(--success-bg-color);
        color: var(--text-color);
        border-radius: 5px;
        cursor: pointer;
        width: calc(50% - 5px);
    `;
    newCharacterButton.addEventListener('click', createNewCharacter);

    const importCharacterButton = document.createElement('button');
    importCharacterButton.textContent = 'Import Character';
    importCharacterButton.style.cssText = `
        margin: 5px 0;
        padding: 5px 10px;
        border: none;
        background-color: var(--info-bg-color);
        color: var(--text-color);
        border-radius: 5px;
        cursor: pointer;
        width: calc(50% - 5px);
    `;
    importCharacterButton.addEventListener('click', importCharacter);

    const exportAllButton = document.createElement('button');
    exportAllButton.textContent = 'Export All';
    exportAllButton.style.cssText = `
        margin: 5px 0;
        padding: 5px 10px;
        border: none;
        background-color: var(--warning-bg-color);
        color: var(--text-color);
        border-radius: 5px;
        cursor: pointer;
        width: 100%;
    `;
    exportAllButton.addEventListener('click', exportAllCharacters);

    const importAllButton = document.createElement('button');
    importAllButton.textContent = 'Import All';
    importAllButton.style.cssText = `
        margin: 5px 0;
        padding: 5px 10px;
        border: none;
        background-color: var(--muted-bg-color);
        color: var(--text-color);
        border-radius: 5px;
        cursor: pointer;
        width: 100%;
    `;
    importAllButton.addEventListener('click', importAllCharacters);

    characterButtonContainer.appendChild(newCharacterButton);
    characterButtonContainer.appendChild(importCharacterButton);
    characterButtonContainer.appendChild(exportAllButton);
    characterButtonContainer.appendChild(importAllButton);
    characterSidebar.appendChild(characterButtonContainer);

    let characterData = [];

    function createNewCharacter() {
        const newCharacter = {
            active: true,
            emoji: '😀',
            name: 'New Character',
            sex: '',
            species: '',
            age: '',
            bodyType: '',
            personality: '',
            bio: '',
            userControlled: false
        };
        characterData.push(newCharacter);
        renderCharacterSlots();
    }

    function importCharacter() {
        const fileInput = document.createElement('input');
        fileInput.type = 'file';
        fileInput.accept = '.json';
        fileInput.addEventListener('change', (event) => {
            const file = event.target.files[0];
            const reader = new FileReader();
            reader.onload = (e) => {
                const importedCharacter = JSON.parse(e.target.result);
                characterData.push(importedCharacter);
                renderCharacterSlots();
            };
            reader.readAsText(file);
        });
        fileInput.click();
    }

    function exportAllCharacters() {
        const json = JSON.stringify(characterData, null, 2);
        const blob = new Blob([json], { type: 'application/json' });
        const url = URL.createObjectURL(blob);
        const link = document.createElement('a');
        link.href = url;
        link.download = `all_characters.json`;
        link.click();
    }

    function importAllCharacters() {
        const fileInput = document.createElement('input');
        fileInput.type = 'file';
        fileInput.accept = '.json';
        fileInput.addEventListener('change', (event) => {
            const file = event.target.files[0];
            const reader = new FileReader();
            reader.onload = (e) => {
                const importedCharacters = JSON.parse(e.target.result);
                characterData = importedCharacters;
                renderCharacterSlots();
            };
            reader.readAsText(file);
        });
        fileInput.click();
    }

    function renderCharacterSlots() {
        characterSlotList.innerHTML = '';

        characterData.sort((a, b) => a.name.localeCompare(b.name));

        characterData.forEach((character, index) => {
            const characterSlot = document.createElement('div');
            characterSlot.className = 'character-slot';
            characterSlot.style.cssText = `
                display: flex;
                align-items: center;
                justify-content: space-between;
                padding: 10px;
                margin-bottom: 10px;
                border: 1px solid var(--border-color);
                border-radius: 5px;
                background-color: ${character.userControlled ? 'var(--active-char-color)' : 'var(--bg-color)'};
            `;

            const activeButton = document.createElement('button');
            activeButton.textContent = character.active ? '🟢' : '🔴';
            activeButton.style.cssText = `
                padding: 3px;
                border: none;
                background: none;
                cursor: pointer;
                color: var(--text-color);
            `;
            activeButton.addEventListener('click', () => {
                character.active = !character.active;
                renderCharacterSlots();
            });

            const emojiSpan = document.createElement('span');
            emojiSpan.textContent = character.emoji;
            emojiSpan.style.marginRight = '10px';

            const nameSpan = document.createElement('span');
            nameSpan.textContent = character.name;
            nameSpan.style.cssText = `
                overflow: hidden;
                white-space: nowrap;
                text-overflow: ellipsis;
                max-width: 80px;
                color: var(--text-color);
            `;

            const editButton = document.createElement('button');
            editButton.textContent = '✏️';
            editButton.style.cssText = `
                padding: 3px;
                border: none;
                background: none;
                cursor: pointer;
                color: var(--text-color);
            `;
            editButton.addEventListener('click', () => {
                editCharacter(index);
            });

            const deleteButton = document.createElement('button');
            deleteButton.textContent = '🗑️';
            deleteButton.style.cssText = `
                padding: 3px;
                border: none;
                background: none;
                cursor: pointer;
                color: var(--text-color);
            `;
            deleteButton.addEventListener('click', () => {
                deleteCharacter(index);
            });

            const exportButton = document.createElement('button');
            exportButton.textContent = '⬇️';
            exportButton.style.cssText = `
                padding: 3px;
                border: none;
                background: none;
                cursor: pointer;
                color: var(--text-color);
            `;
            exportButton.addEventListener('click', () => {
                exportCharacter(character);
            });

            characterSlot.appendChild(activeButton);
            characterSlot.appendChild(emojiSpan);
            characterSlot.appendChild(nameSpan);
            characterSlot.appendChild(editButton);
            characterSlot.appendChild(deleteButton);
            characterSlot.appendChild(exportButton);
            characterSlotList.appendChild(characterSlot);
        });
    }

    function editCharacter(index) {
        const character = characterData[index];
        const editForm = document.createElement('form');
        editForm.innerHTML = `
            <label style="color: var(--text-color);">Emoji: <input type="text" name="emoji" value="${character.emoji}" style="background-color: var(--bg-color); color: var(--text-color); border: 1px solid var(--border-color); border-radius: 5px; padding: 5px; width: 100%; box-sizing: border-box;"></label><br>
            <label style="color: var(--text-color);">Name: <input type="text" name="name" value="${character.name}" style="background-color: var(--bg-color); color: var(--text-color); border: 1px solid var(--border-color); border-radius: 5px; padding: 5px; width: 100%; box-sizing: border-box;"></label><br>
            <label style="color: var(--text-color);">Sex: <input type="text" name="sex" value="${character.sex}" style="background-color: var(--bg-color); color: var(--text-color); border: 1px solid var(--border-color); border-radius: 5px; padding: 5px; width: 100%; box-sizing: border-box;"></label><br>
            <label style="color: var(--text-color);">Species: <input type="text" name="species" value="${character.species}" style="background-color: var(--bg-color); color: var(--text-color); border: 1px solid var(--border-color); border-radius: 5px; padding: 5px; width: 100%; box-sizing: border-box;"></label><br>
            <label style="color: var(--text-color);">Age: <input type="text" name="age" value="${character.age}" style="background-color: var(--bg-color); color: var(--text-color); border: 1px solid var(--border-color); border-radius: 5px; padding: 5px; width: 100%; box-sizing: border-box;"></label><br>
            <label style="color: var(--text-color);">Body Type: <input type="text" name="bodyType" value="${character.bodyType}" style="background-color: var(--bg-color); color: var(--text-color); border: 1px solid var(--border-color); border-radius: 5px; padding: 5px; width: 100%; box-sizing: border-box;"></label><br>
            <label style="color: var(--text-color);">Personality: <input type="text" name="personality" value="${character.personality}" style="background-color: var(--bg-color); color: var(--text-color); border: 1px solid var(--border-color); border-radius: 5px; padding: 5px; width: 100%; box-sizing: border-box;"></label><br>
            <label style="color: var(--text-color);">Bio: <textarea name="bio" style="background-color: var(--bg-color); color: var(--text-color); border: 1px solid var(--border-color); border-radius: 5px; padding: 5px; width: 100%; box-sizing: border-box;">${character.bio}</textarea></label><br>
            <label style="color: var(--text-color);">User Controlled: <input type="checkbox" name="userControlled" ${character.userControlled ? 'checked' : ''}></label><br>
            <button type="submit" style="background-color: var(--button-bg-color); color: var(--text-color); border: none; padding: 5px 10px; border-radius: 5px;">Save</button>
        `;
        editForm.addEventListener('submit', (event) => {
            event.preventDefault();
            character.emoji = editForm.emoji.value;
            character.name = editForm.name.value;
            character.sex = editForm.sex.value;
            character.species = editForm.species.value;
            character.age = editForm.age.value;
            character.bodyType = editForm.bodyType.value;
            character.personality = editForm.personality.value;
            character.bio = editForm.bio.value;
            character.userControlled = editForm.userControlled.checked;
            renderCharacterSlots();
        });
        characterSlotList.innerHTML = '';
        characterSlotList.appendChild(editForm);
    }

    function deleteCharacter(index) {
        characterData.splice(index, 1);
        renderCharacterSlots();
    }

    function exportCharacter(character) {
        const json = JSON.stringify(character, null, 2);
        const blob = new Blob([json], { type: 'application/json' });
        const url = URL.createObjectURL(blob);
        const link = document.createElement('a');
        link.href = url;
        link.download = `${character.name}.json`;
        link.click();
    }

    // Location Sidebar
    const locationSidebar = document.createElement('div');
    locationSidebar.id = 'location-sheet-sidebar';
    locationSidebar.style.cssText = `
        position: fixed;
        top: 0;
        left: -300px;
        width: 300px;
        height: 100%;
        display: flex;
        flex-direction: column;
        background-color: var(--bg-color);
        border-right: 2px solid var(--border-color);
        box-shadow: 2px 0 5px var(--shadow-color);
        box-sizing: border-box;
        transition: left 0.3s;
        z-index: 9999;
    `;
    document.body.appendChild(locationSidebar);

    const locationToggleButton = document.createElement('button');
    locationToggleButton.textContent = '☰ Locations';
    locationToggleButton.style.cssText = `
        position: fixed;
        top: 10px;
        left: 10px;
        padding: 5px 10px;
        border: none;
        background-color: var(--button-bg-color);
        color: var(--text-color);
        border-radius: 5px;
        cursor: pointer;
        transition: left 0.3s;
        z-index: 10000;
    `;
    locationToggleButton.addEventListener('click', () => {
        const isOpen = locationSidebar.style.left === '0px';
        locationSidebar.style.left = isOpen ? '-300px' : '0';
        locationToggleButton.style.left = isOpen ? '10px' : '310px';
    });
    document.body.appendChild(locationToggleButton);

    const locationSlotList = document.createElement('div');
    locationSlotList.id = 'location-slot-list';
    locationSlotList.style.cssText = `
        flex-grow: 1;
        overflow-y: auto;
        margin-top: 10px;
    `;
    locationSidebar.appendChild(locationSlotList);

    const globalInputs = document.createElement('div');
    globalInputs.style.cssText = `
        display: flex;
        justify-content: space-between;
        margin-bottom: 10px;
    `;
    const timeInput = document.createElement('input');
    timeInput.type = 'text';
    timeInput.placeholder = 'Time';
    timeInput.style.cssText = `
        width: calc(50% - 5px);
        padding: 5px;
        border: 1px solid var(--border-color);
        border-radius: 5px;
        background-color: var(--bg-color);
        color: var(--text-color);
    `;
    const weatherInput = document.createElement('input');
    weatherInput.type = 'text';
    weatherInput.placeholder = 'Weather';
    weatherInput.style.cssText = `
        width: calc(50% - 5px);
        padding: 5px;
        border: 1px solid var(--border-color);
        border-radius: 5px;
        background-color: var(--bg-color);
        color: var(--text-color);
    `;
    globalInputs.appendChild(timeInput);
    globalInputs.appendChild(weatherInput);
    locationSidebar.appendChild(globalInputs);

    const locationButtonContainer = document.createElement('div');
    locationButtonContainer.style.cssText = `
        display: flex;
        flex-wrap: wrap;
        justify-content: space-between;
    `;

    const newLocationButton = document.createElement('button');
    newLocationButton.textContent = 'New Location';
    newLocationButton.style.cssText = `
        margin: 5px 0;
        padding: 5px 10px;
        border: none;
        background-color: var(--success-bg-color);
        color: var(--text-color);
        border-radius: 5px;
        cursor: pointer;
        width: calc(50% - 5px);
    `;
    newLocationButton.addEventListener('click', createNewLocation);

    const importLocationButton = document.createElement('button');
    importLocationButton.textContent = 'Import Location';
    importLocationButton.style.cssText = `
        margin: 5px 0;
        padding: 5px 10px;
        border: none;
        background-color: var(--info-bg-color);
        color: var(--text-color);
        border-radius: 5px;
        cursor: pointer;
        width: calc(50% - 5px);
    `;
    importLocationButton.addEventListener('click', importLocation);

    const exportAllLocationsButton = document.createElement('button');
    exportAllLocationsButton.textContent = 'Export All';
    exportAllLocationsButton.style.cssText = `
        margin: 5px 0;
        padding: 5px 10px;
        border: none;
        background-color: var(--warning-bg-color);
        color: var(--text-color);
        border-radius: 5px;
        cursor: pointer;
        width: 100%;
    `;
    exportAllLocationsButton.addEventListener('click', exportAllLocations);

    const importAllLocationsButton = document.createElement('button');
    importAllLocationsButton.textContent = 'Import All';
    importAllLocationsButton.style.cssText = `
        margin: 5px 0;
        padding: 5px 10px;
        border: none;
        background-color: var(--muted-bg-color);
        color: var(--text-color);
        border-radius: 5px;
        cursor: pointer;
        width: 100%;
    `;
    importAllLocationsButton.addEventListener('click', importAllLocations);

    locationButtonContainer.appendChild(newLocationButton);
    locationButtonContainer.appendChild(importLocationButton);
    locationButtonContainer.appendChild(exportAllLocationsButton);
    locationButtonContainer.appendChild(importAllLocationsButton);
    locationSidebar.appendChild(locationButtonContainer);

    let locationData = [];

    function createNewLocation() {
        const newLocation = {
            active: locationData.length === 0,
            emoji: '📍',
            name: 'New Location',
            description: ''
        };
        locationData.push(newLocation);
        renderLocationSlots();
    }

    function importLocation() {
        const fileInput = document.createElement('input');
        fileInput.type = 'file';
        fileInput.accept = '.json';
        fileInput.addEventListener('change', (event) => {
            const file = event.target.files[0];
            const reader = new FileReader();
            reader.onload = (e) => {
                const importedLocation = JSON.parse(e.target.result);
                locationData.push(importedLocation);
                renderLocationSlots();
            };
            reader.readAsText(file);
        });
        fileInput.click();
    }

    function exportAllLocations() {
        const json = JSON.stringify(locationData, null, 2);
        const blob = new Blob([json], { type: 'application/json' });
        const url = URL.createObjectURL(blob);
        const link = document.createElement('a');
        link.href = url;
        link.download = `all_locations.json`;
        link.click();
    }

    function importAllLocations() {
        const fileInput = document.createElement('input');
        fileInput.type = 'file';
        fileInput.accept = '.json';
        fileInput.addEventListener('change', (event) => {
            const file = event.target.files[0];
            const reader = new FileReader();
            reader.onload = (e) => {
                const importedLocations = JSON.parse(e.target.result);
                locationData = importedLocations;
                renderLocationSlots();
            };
            reader.readAsText(file);
        });
        fileInput.click();
    }

    function renderLocationSlots() {
        locationSlotList.innerHTML = '';

        locationData.sort((a, b) => a.name.localeCompare(b.name));

        locationData.forEach((location, index) => {
            const locationSlot = document.createElement('div');
            locationSlot.className = 'location-slot';
            locationSlot.style.cssText = `
                display: flex;
                align-items: center;
                justify-content: space-between;
                padding: 10px;
                margin-bottom: 10px;
                border: 1px solid var(--border-color);
                border-radius: 5px;
                background-color: var(--bg-color);
            `;

            const activeButton = document.createElement('button');
            activeButton.textContent = location.active ? '🟢' : '🔴';
            activeButton.style.cssText = `
                padding: 3px;
                border: none;
                background: none;
                cursor: pointer;
                color: var(--text-color);
            `;
            activeButton.addEventListener('click', () => {
                locationData.forEach(loc => loc.active = false);
                location.active = true;
                renderLocationSlots();
            });

            const emojiSpan = document.createElement('span');
            emojiSpan.textContent = location.emoji;
            emojiSpan.style.marginRight = '10px';

            const nameSpan = document.createElement('span');
            nameSpan.textContent = location.name;
            nameSpan.style.cssText = `
                overflow: hidden;
                white-space: nowrap;
                text-overflow: ellipsis;
                max-width: 80px;
                color: var(--text-color);
            `;

            const editButton = document.createElement('button');
            editButton.textContent = '✏️';
            editButton.style.cssText = `
                padding: 3px;
                border: none;
                background: none;
                cursor: pointer;
                color: var(--text-color);
            `;
            editButton.addEventListener('click', () => {
                editLocation(index);
            });

            const deleteButton = document.createElement('button');
            deleteButton.textContent = '🗑️';
            deleteButton.style.cssText = `
                padding: 3px;
                border: none;
                background: none;
                cursor: pointer;
                color: var(--text-color);
            `;
            deleteButton.addEventListener('click', () => {
                deleteLocation(index);
            });

            const exportButton = document.createElement('button');
            exportButton.textContent = '⬇️';
            exportButton.style.cssText = `
                padding: 3px;
                border: none;
                background: none;
                cursor: pointer;
                color: var(--text-color);
            `;
            exportButton.addEventListener('click', () => {
                exportLocation(location);
            });

            locationSlot.appendChild(activeButton);
            locationSlot.appendChild(emojiSpan);
            locationSlot.appendChild(nameSpan);
            locationSlot.appendChild(editButton);
            locationSlot.appendChild(deleteButton);
            locationSlot.appendChild(exportButton);
            locationSlotList.appendChild(locationSlot);
        });
    }

    function editLocation(index) {
        const location = locationData[index];
        const editForm = document.createElement('form');
        editForm.innerHTML = `
            <label style="color: var(--text-color);">Emoji: <input type="text" name="emoji" value="${location.emoji}" style="background-color: var(--bg-color); color: var(--text-color); border: 1px solid var(--border-color); border-radius: 5px; padding: 5px; width: 100%; box-sizing: border-box;"></label><br>
            <label style="color: var(--text-color);">Name: <input type="text" name="name" value="${location.name}" style="background-color: var(--bg-color); color: var(--text-color); border: 1px solid var(--border-color); border-radius: 5px; padding: 5px; width: 100%; box-sizing: border-box;"></label><br>
            <label style="color: var(--text-color);">Description: <textarea name="description" style="background-color: var(--bg-color); color: var(--text-color); border: 1px solid var(--border-color); border-radius: 5px; padding: 5px; width: 100%; box-sizing: border-box;">${location.description}</textarea></label><br>
            <button type="submit" style="background-color: var(--button-bg-color); color: var(--text-color); border: none; padding: 5px 10px; border-radius: 5px;">Save</button>
        `;
        editForm.addEventListener('submit', (event) => {
            event.preventDefault();
            location.emoji = editForm.emoji.value;
            location.name = editForm.name.value;
            location.description = editForm.description.value;
            renderLocationSlots();
        });
        locationSlotList.innerHTML = '';
        locationSlotList.appendChild(editForm);
    }

    function deleteLocation(index) {
        if (locationData[index].active && locationData.length > 1) {
            locationData.splice(index, 1);
            locationData[0].active = true;
        } else if (locationData.length === 1) {
            alert("At least one location must be active.");
        } else {
            locationData.splice(index, 1);
        }
        renderLocationSlots();
    }

    function exportLocation(location) {
        const json = JSON.stringify(location, null, 2);
        const blob = new Blob([json], { type: 'application/json' });
        const url = URL.createObjectURL(blob);
        const link = document.createElement('a');
        link.href = url;
        link.download = `${location.name}.json`;
        link.click();
    }

    // Context Menu
    const contextMenuButton = document.createElement('button');
    contextMenuButton.textContent = 'Context Menu';
    contextMenuButton.style.cssText = `
        position: fixed;
        top: 10px;
        left: 50%;
        transform: translateX(-50%);
        padding: 5px 10px;
        border: none;
        background-color: var(--button-bg-color);
        color: var(--text-color);
        border-radius: 5px;
        cursor: pointer;
        z-index: 10000;
    `;
    document.body.appendChild(contextMenuButton);

    const contextPanel = document.createElement('div');
    contextPanel.id = 'context-panel';
    contextPanel.style.cssText = `
        position: fixed;
        top: 50%;
        left: 50%;
        transform: translate(-50%, -50%);
        width: 400px;
        padding: 20px;
        background-color: var(--bg-color);
        border: 1px solid var(--border-color);
        box-shadow: 0 0 10px var(--shadow-color);
        border-radius: 5px;
        display: none;
        z-index: 10001;
    `;
    document.body.appendChild(contextPanel);

    const transparencyLabel = document.createElement('label');
    transparencyLabel.textContent = 'UI Transparency';
    transparencyLabel.style.cssText = `
        color: var(--text-color);
        display: block;
        margin-bottom: 5px;
    `;
    contextPanel.appendChild(transparencyLabel);

    const transparencySlider = document.createElement('input');
    transparencySlider.type = 'range';
    transparencySlider.min = '0.1';
    transparencySlider.max = '1';
    transparencySlider.step = '0.1';
    transparencySlider.value = '0.9';
    transparencySlider.style.cssText = `
        width: 100%;
        margin-bottom: 10px;
    `;
    transparencySlider.addEventListener('input', () => {
        document.documentElement.style.setProperty('--ui-transparency', transparencySlider.value);
        // Re-apply current theme to update transparency
        setTheme(themeDropdown.value);
        GM.setValue('transparency', transparencySlider.value);
    });
    contextPanel.appendChild(transparencySlider);

    // After creating transparencySlider
    transparencySlider.value = defaultTransparency;

    const dropperLabel = document.createElement('label');
    dropperLabel.textContent = 'UI Theme';
    dropperLabel.style.cssText = `
        color: var(--text-color);
        display: block;
        margin-bottom: 5px;
    `;
    contextPanel.appendChild(dropperLabel);

    const themeDropdown = document.createElement('select');
    themeDropdown.style.cssText = `
        width: 100%;
        padding: 5px;
        margin-bottom: 10px;
        border: 1px solid var(--border-color);
        border-radius: 5px;
        background-color: var(--bg-color);
        color: var(--text-color);
    `;
    Object.keys(themes).forEach(theme => {
        const option = document.createElement('option');
        option.value = theme;
        option.textContent = theme.charAt(0).toUpperCase() + theme.slice(1);
        themeDropdown.appendChild(option);
    });

    themeDropdown.addEventListener('change', () => {
        setTheme(themeDropdown.value);
        GM.setValue('theme', themeDropdown.value);
    });
    contextPanel.appendChild(themeDropdown);

    // After creating themeDropdown and adding options
    themeDropdown.value = defaultTheme;

    // Add divider
    const divider = document.createElement('hr');
    divider.style.cssText = `
    border: none;
    border-top: 1px solid var(--border-color);
    margin: 10px 0;
`;
    contextPanel.appendChild(divider);

    const contextLabel = document.createElement('label');
    contextLabel.textContent = 'Primary Context';
    contextLabel.style.cssText = `
        color: var(--text-color);
        display: block;
        margin-bottom: 5px;
    `;
    contextPanel.appendChild(contextLabel);

    const primaryContextInput = document.createElement('textarea');
    primaryContextInput.placeholder = 'Primary context goes here...';
    primaryContextInput.style.cssText = `
        width: 100%;
        height: 100px;
        margin-bottom: 10px;
        padding: 10px;
        border: 1px solid var(--border-color);
        border-radius: 5px;
        box-sizing: border-box;
        background-color: var(--bg-color);
        color: var(--text-color);
    `;
    contextPanel.appendChild(primaryContextInput);

    const updateContextButton = document.createElement('button');
    updateContextButton.textContent = 'Update Context';
    updateContextButton.style.cssText = `
        width: 100%;
        padding: 10px;
        border: none;
        background-color: var(--success-bg-color);
        color: var(--text-color);
        border-radius: 5px;
        cursor: pointer;
    `;

    // Define the sleep function
    function sleep(ms) {
        return new Promise(resolve => setTimeout(resolve, ms));
    }

    updateContextButton.addEventListener('click', async () => {
        const primaryContext = primaryContextInput.value;
        const fullContext = `Context;\n"` + primaryContext + `"\n\n` + window.getContext();
        console.log("setting context; " + fullContext);

        window.manuClick('//*[@id="menu-button-:rb:"]');
        await sleep(250);

        window.manuClick('//*[@id="menu-list-:rb:-menuitem-:ru:"]');
        await sleep(250);

        window.manuWrite("/html/body/div[8]/div[3]/div/section/div/textarea", fullContext);
        await sleep(250);

        window.manuClick("/html/body/div[8]/div[3]/div/section/footer/button[2]");
    });

    window.manuWrite = function(xpath, text) {
        var result = document.evaluate(
            xpath,
            document,
            null,
            XPathResult.FIRST_ORDERED_NODE_TYPE,
            null
        );

        var node = result.singleNodeValue;

        if (node) {
            node.focus();

            // Since the node is an HTMLTextAreaElement, get the value setter from its prototype
            const nativeTextAreaValueSetter = Object.getOwnPropertyDescriptor(
                window.HTMLTextAreaElement.prototype,
                'value'
            ).set;

            nativeTextAreaValueSetter.call(node, text);

            // Dispatch the 'input' event to simulate user input
            var event = new Event('input', { bubbles: true });
            node.dispatchEvent(event);

        } else {
            console.error("Node not found for XPath:", xpath);
        }
    }

    window.manuClick = function(xpath) {
        var result = document.evaluate(
            xpath,
            document,
            null,
            XPathResult.FIRST_ORDERED_NODE_TYPE,
            null
        );

        var node = result.singleNodeValue;

        if (node) {
            node.click();
        }
    }

    contextPanel.appendChild(updateContextButton);

    contextMenuButton.addEventListener('click', () => {
        contextPanel.style.display = contextPanel.style.display === 'none' ? 'block' : 'none';
    });

    // Make getContext accessible from the console
    window.getContext = function() {
        const activeLocation = locationData.find(loc => loc.active);
        if (!activeLocation) return '';

        let context = `Setting;\n"${activeLocation.name}" (${timeInput.value}, ${weatherInput.value}):\n`;
        context += `\u0009"${activeLocation.description}"\n`;

        context += `\n{{user}}'s characters;\n`;

        characterData
            .filter(char => char.active && char.userControlled)
            .forEach(char => {
            context += `"${char.name}" (${char.sex}, ${char.species}, ${char.age}, ${char.bodyType}, ${char.personality}):\n`;
            context += `\u0009"${char.bio}"\n`;
        });

        context += `\n{{char}}'s characters;\n`;

        characterData
            .filter(char => char.active && !char.userControlled)
            .forEach(char => {
            context += `"${char.name}" (${char.sex}, ${char.species}, ${char.age}, ${char.bodyType}, ${char.personality}):\n`;
            context += `\u0009"${char.bio}"\n`;
        });

        return context;
    };

    // Initial render
    renderCharacterSlots();
    renderLocationSlots();
})();