JanitorAI Context Maker

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

ของเมื่อวันที่ 16-09-2024 ดู เวอร์ชันล่าสุด

คุณจะต้องติดตั้งส่วนขยาย เช่น Tampermonkey, Greasemonkey หรือ Violentmonkey เพื่อติดตั้งสคริปต์นี้

คุณจะต้องติดตั้งส่วนขยาย เช่น Tampermonkey หรือ Violentmonkey เพื่อติดตั้งสคริปต์นี้

คุณจะต้องติดตั้งส่วนขยาย เช่น Tampermonkey หรือ Violentmonkey เพื่อติดตั้งสคริปต์นี้

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

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