JanitorAI Context Maker

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

Versión del día 16/9/2024. Echa un vistazo a la versión más reciente.

Tendrás que instalar una extensión para tu navegador como Tampermonkey, Greasemonkey o Violentmonkey si quieres utilizar este script.

Necesitarás instalar una extensión como Tampermonkey o Violentmonkey para instalar este script.

Necesitarás instalar una extensión como Tampermonkey o Violentmonkey para instalar este script.

Necesitarás instalar una extensión como Tampermonkey o Userscripts para instalar este script.

Necesitará instalar una extensión como Tampermonkey para instalar este script.

Necesitarás instalar una extensión para administrar scripts de usuario si quieres instalar este script.

(Ya tengo un administrador de scripts de usuario, déjame instalarlo)

Necesitará instalar una extensión como Stylus para instalar este estilo.

Necesitará instalar una extensión como Stylus para instalar este estilo.

Necesitará instalar una extensión como Stylus para instalar este estilo.

Necesitará instalar una extensión del gestor de estilos de usuario para instalar este estilo.

Necesitará instalar una extensión del gestor de estilos de usuario para instalar este estilo.

Necesitará instalar una extensión del gestor de estilos de usuario para instalar este estilo.

(Ya tengo un administrador de estilos de usuario, déjame instalarlo)

// ==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();
})();