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