- // ==UserScript==
- // @name Persona Impersonator
- // @namespace http://tampermonkey.net/
- // @version 1.3
- // @description Impersonation. (RYW-Style)
- // @author Grok 3 (xAI)
- // @match https://character.ai/*
- // @grant GM_xmlhttpRequest
- // @grant GM_getValue
- // @grant GM_setValue
- // @setting openrouter_key {type: 'text', default: '', description: 'Your OpenRouter API Key'}
- // @setting openrouter_model {type: 'text', default: 'openai/gpt-3.5-turbo', description: 'OpenRouter Model (e.g., openai/gpt-3.5-turbo)'}
- // ==/UserScript==
-
- (function() {
- 'use strict';
-
- // ### Helper Functions for Fetching Conversation History
-
- /** Wraps GM_xmlhttpRequest in a Promise for easier async handling */
- function GM_xmlhttpRequestPromise(details) {
- return new Promise((resolve, reject) => {
- details.onload = function(response) {
- resolve(response);
- };
- details.onerror = function() {
- reject(new Error('GM_xmlhttpRequest failed'));
- };
- GM_xmlhttpRequest(details);
- });
- }
-
- /** Retrieves the access token from a meta tag */
- function getAccessToken() {
- const meta = document.querySelector('meta[cai_token]');
- return meta ? meta.getAttribute('cai_token') : null;
- }
-
- /** Extracts the character ID from the URL */
- function getCharId() {
- const path = window.location.pathname.split('/');
- if (path[1] === 'chat' && path[2]) {
- return path[2];
- }
- return null;
- }
-
- /** Fetches the current conversation ID for the character */
- async function getCurrentConverId() {
- const AccessToken = getAccessToken();
- const charId = getCharId();
- if (!AccessToken || !charId) return null;
-
- try {
- const res = await GM_xmlhttpRequestPromise({
- method: "GET",
- url: `https://neo.character.ai/chats/recent/${charId}`,
- headers: {
- 'Accept': 'application/json',
- 'Content-Type': 'application/json',
- "authorization": AccessToken
- }
- });
-
- if (res.status === 200) {
- const data = JSON.parse(res.responseText);
- if (data.chats && data.chats.length > 0) {
- return data.chats[0].chat_id;
- }
- }
- return null;
- } catch (error) {
- console.error('Error fetching conversation ID:', error);
- return null;
- }
- }
-
- /** Fetches all messages in the conversation, handling pagination with custom tagging */
- async function fetchMessagesChat2({ AccessToken, converExtId, nextToken = null, turns = [] }) {
- let url = `https://neo.character.ai/turns/${converExtId}/`;
- if (nextToken) url += `?next_token=${nextToken}`;
-
- try {
- const res = await GM_xmlhttpRequestPromise({
- method: "GET",
- url: url,
- headers: {
- 'Accept': 'application/json',
- 'Content-Type': 'application/json',
- "authorization": AccessToken
- }
- });
-
- if (res.status === 200) {
- const data = JSON.parse(res.responseText);
- turns = [...turns, ...data.turns];
- if (data.meta.next_token == null) {
- const simplifiedChat = turns.map(msg => {
- const primary = msg.candidates.find(c => c.candidate_id === msg.primary_candidate_id);
- const [alternative] = msg.candidates.slice(-1);
- const chosen = primary ?? alternative;
- const senderName = msg.author.name || (msg.author.is_human ? 'Human' : 'Axel');
- const tag = msg.author.is_human ? `user:${senderName}` : 'assistant';
- return {
- tag: tag,
- message: chosen?.raw_content || "[Message broken]"
- };
- });
- simplifiedChat.reverse(); // Oldest first
- return simplifiedChat;
- } else {
- return fetchMessagesChat2({ AccessToken, converExtId, nextToken: data.meta.next_token, turns });
- }
- } else {
- throw new Error(`Fetch failed: ${res.status}`);
- }
- } catch (error) {
- console.error('Error fetching messages:', error);
- throw error;
- }
- }
-
- // ### Core Functions
-
- /** Waits for DOM to be ready */
- function waitForDOM(callback) {
- if (document.readyState === 'complete' || document.readyState === 'interactive') {
- callback();
- } else {
- document.addEventListener('DOMContentLoaded', callback);
- const observer = new MutationObserver(() => {
- if (document.getElementId('__next')) {
- observer.disconnect();
- callback();
- }
- });
- observer.observe(document.body, { childList: true, subtree: true });
- }
- }
-
- /** Fetches OpenRouter models and pricing */
- function fetchOpenRouterModels(apiKey, callback) {
- if (!apiKey) {
- callback(null, 'Please enter your OpenRouter API key to fetch models.');
- return;
- }
-
- console.log('Fetching OpenRouter models...');
- GM_xmlhttpRequest({
- method: 'GET',
- url: 'https://openrouter.ai/api/v1/models',
- headers: {
- 'Authorization': `Bearer ${apiKey}`
- },
- onload: function(response) {
- if (response.status === 200) {
- const data = JSON.parse(response.responseText);
- callback(data.data, null);
- } else {
- callback(null, `Error: ${response.status} - ${response.responseText}`);
- }
- },
- onerror: function() {
- callback(null, 'Failed to connect to OpenRouter API.');
- }
- });
- }
-
- /** Fetches Google Gemini models using the Generative Language API */
- function fetchGeminiModels(apiKey, callback) {
- if (!apiKey) {
- callback(null, 'Please enter your Google Gemini API key to fetch models.');
- return;
- }
-
- console.log('Fetching Google Gemini models...');
- GM_xmlhttpRequest({
- method: 'GET',
- url: 'https://generativelanguage.googleapis.com/v1beta/models?key=' + apiKey,
- headers: {
- 'Content-Type': 'application/json'
- },
- onload: function(response) {
- if (response.status === 200) {
- const data = JSON.parse(response.responseText);
- const models = data.models.filter(model =>
- model.name.startsWith('models/gemini-1.5-') ||
- model.name.startsWith('models/gemini-2.0-') ||
- model.name.startsWith('models/aqa') ||
- model.name.startsWith('models/gemini-2.5-') ||
- model.name.startsWith('models/gemini-embedding-') ||
- model.name.startsWith('models/gemini-exp-') ||
- model.name.startsWith('models/gemini-ultra') ||
- model.name.startsWith('models/gemma-')
- ).map(model => ({
- id: model.name.split('/').pop(),
- name: model.displayName || model.name.split('/').pop(),
- pricing: { prompt: 'N/A', completion: 'N/A' }
- }));
- callback(models, null);
- } else {
- console.error('Failed to fetch Gemini models:', response.status, response.responseText);
- // Fallback to hardcoded list if API call fails
- const fallbackModels = [
- { id: 'gemini-1.0-pro', name: 'Gemini 1.0 Pro', pricing: { prompt: 'N/A', completion: 'N/A' } },
- { id: 'gemini-1.0-pro-001', name: 'Gemini 1.0 Pro 001', pricing: { prompt: 'N/A', completion: 'N/A' } },
- { id: 'gemini-1.5-pro-002', name: 'Gemini 1.5 Pro 002', pricing: { prompt: 'N/A', completion: 'N/A' } },
- { id: 'gemini-1.5-pro-001', name: 'Gemini 1.5 Pro 001', pricing: { prompt: 'N/A', completion: 'N/A' } },
- { id: 'gemini-1.5-flash-002', name: 'Gemini 1.5 Flash 002', pricing: { prompt: 'N/A', completion: 'N/A' } },
- { id: 'gemini-1.5-flash-001', name: 'Gemini 1.5 Flash 001', pricing: { prompt: 'N/A', completion: 'N/A' } },
- { id: 'gemini-2.0-flash', name: 'Gemini 2.0 Flash', pricing: { prompt: 'N/A', completion: 'N/A' } },
- { id: 'gemini-2.0-pro', name: 'Gemini 2.0 Pro', pricing: { prompt: 'N/A', completion: 'N/A' } },
- { id: 'gemini-2.0-pro-vision', name: 'Gemini 2.0 Pro Vision', pricing: { prompt: 'N/A', completion: 'N/A' } },
- { id: 'gemini-2.0-nano', name: 'Gemini 2.0 Nano', pricing: { prompt: 'N/A', completion: 'N/A' } },
- { id: 'gemini-exp-0827', name: 'Gemini Exp 0827', pricing: { prompt: 'N/A', completion: 'N/A' } },
- { id: 'gemini-exp-0924', name: 'Gemini Exp 0924', pricing: { prompt: 'N/A', completion: 'N/A' } },
- { id: 'gemma-3-27b-it', name: 'Gemma 3 27B IT', pricing: { prompt: 'N/A', completion: 'N/A' } }
- ];
- callback(fallbackModels, `Error: ${response.status} - ${response.responseText}`);
- }
- },
- onerror: function() {
- console.error('Network error fetching Gemini models');
- const fallbackModels = [
- { id: 'gemini-1.0-pro', name: 'Gemini 1.0 Pro', pricing: { prompt: 'N/A', completion: 'N/A' } },
- { id: 'gemini-1.0-pro-001', name: 'Gemini 1.0 Pro 001', pricing: { prompt: 'N/A', completion: 'N/A' } },
- { id: 'gemini-1.5-pro-002', name: 'Gemini 1.5 Pro 002', pricing: { prompt: 'N/A', completion: 'N/A' } },
- { id: 'gemini-1.5-pro-001', name: 'Gemini 1.5 Pro 001', pricing: { prompt: 'N/A', completion: 'N/A' } },
- { id: 'gemini-1.5-flash-002', name: 'Gemini 1.5 Flash 002', pricing: { prompt: 'N/A', completion: 'N/A' } },
- { id: 'gemini-1.5-flash-001', name: 'Gemini 1.5 Flash 001', pricing: { prompt: 'N/A', completion: 'N/A' } },
- { id: 'gemini-2.0-flash', name: 'Gemini 2.0 Flash', pricing: { prompt: 'N/A', completion: 'N/A' } },
- { id: 'gemini-2.0-pro', name: 'Gemini 2.0 Pro', pricing: { prompt: 'N/A', completion: 'N/A' } },
- { id: 'gemini-2.0-pro-vision', name: 'Gemini 2.0 Pro Vision', pricing: { prompt: 'N/A', completion: 'N/A' } },
- { id: 'gemini-2.0-nano', name: 'Gemini 2.0 Nano', pricing: { prompt: 'N/A', completion: 'N/A' } },
- { id: 'gemini-exp-0827', name: 'Gemini Exp 0827', pricing: { prompt: 'N/A', completion: 'N/A' } },
- { id: 'gemini-exp-0924', name: 'Gemini Exp 0924', pricing: { prompt: 'N/A', completion: 'N/A' } },
- { id: 'gemma-3-27b-it', name: 'Gemma 3 27B IT', pricing: { prompt: 'N/A', completion: 'N/A' } }
- ];
- callback(fallbackModels, 'Failed to connect to Google Gemini API.');
- }
- });
- }
-
- /** Generates text using OpenRouter API */
- function generateOpenRouterText(apiKey, model, messages, output, copyBtn) {
- const payload = {
- model: model,
- messages: messages.map(msg => ({
- role: msg.tag.startsWith('user:') ? 'user' : msg.tag,
- content: msg.message
- }))
- };
-
- console.log('Sending request to OpenRouter:', payload);
- GM_xmlhttpRequest({
- method: 'POST',
- url: 'https://openrouter.ai/api/v1/chat/completions',
- headers: {
- 'Content-Type': 'application/json',
- 'Authorization': `Bearer ${apiKey}`
- },
- data: JSON.stringify(payload),
- onload: function(response) {
- if (response.status === 200) {
- const data = JSON.parse(response.responseText);
- const generatedText = data.choices[0].message.content.trim();
- output.value = generatedText;
- copyBtn.style.display = 'block';
- console.log('Generated:', generatedText);
- } else {
- output.value = `Error: ${response.status} - ${response.responseText}`;
- copyBtn.style.display = 'none';
- }
- },
- onerror: function() {
- output.value = 'Failed to connect to OpenRouter API.';
- copyBtn.style.display = 'none';
- }
- });
- }
-
- /** Generates text using Google Gemini API with temperature */
- function generateGeminiText(apiKey, model, messages, temperature, output, copyBtn) {
- const payload = {
- contents: [{
- parts: messages.map(msg => ({
- text: `${msg.tag}: ${msg.message}`
- }))
- }],
- generationConfig: {
- temperature: parseFloat(temperature) || 1.0,
- maxOutputTokens: 2048
- }
- };
-
- console.log('Sending request to Google Gemini:', payload);
- console.log(model, payload);
- GM_xmlhttpRequest({
- method: 'POST',
- url: `https://generativelanguage.googleapis.com/v1beta/models/${model}:generateContent?key=${apiKey}`,
- headers: {
- 'Content-Type': 'application/json'
- },
- data: JSON.stringify(payload),
- onload: function(response) {
- if (response.status === 200) {
- const data = JSON.parse(response.responseText);
- const generatedText = data.candidates[0].content.parts[0].text.trim();
- output.value = generatedText;
- copyBtn.style.display = 'block';
- console.log('Generated:', generatedText);
- } else {
- output.value = `Error: ${response.status} - ${response.responseText}`;
- copyBtn.style.display = 'none';
- }
- },
- onerror: function() {
- output.value = 'Failed to connect to Google Gemini API.';
- copyBtn.style.display = 'none';
- }
- });
- }
-
- // ### UI Creation
-
- function createUI() {
- const storedPersona = localStorage.getItem('cai_persona') || '';
- const storedApiSelection = localStorage.getItem('cai_api_selection') || 'openrouter';
- const storedOpenRouterKey = GM_getValue('openrouter_key', '');
- const storedGeminiKey = localStorage.getItem('cai_gemini_key') || '';
- const storedModel = GM_getValue('openrouter_model', 'openai/gpt-3.5-turbo');
- const storedInput = localStorage.getItem('cai_input') || '';
- const storedTemperature = localStorage.getItem('cai_gemini_temperature') || '1.0';
-
- // Inject CSS
- const style = document.createElement('style');
- style.innerHTML = `
- @import url('https://fonts.googleapis.com/css2?family=Noto+Sans:ital,wght@0,100..900;1,100..900&display=swap');
- .ptrk_main {
- position: fixed !important;
- display: flex;
- flex-direction: column;
- margin: 0;
- z-index: 10000 !important;
- min-width: 300px;
- background-color: rgba(33, 37, 41, 0.95);
- right: 0;
- top: 0;
- height: 100vh;
- padding: 18px;
- color: white;
- font-family: "Noto Sans", sans-serif;
- font-size: 13px;
- transition: transform 0.3s ease;
- width: 470px;
- box-sizing: border-box;
- box-shadow: -2px 0 5px rgba(0, 0, 0, 0.5);
- overflow-y: auto;
- }
- .ptrk_main.ptrk_hidden {
- transform: translateX(100%);
- }
- .ptrk_toggle_btn {
- position: fixed !important;
- top: 105px;
- right: 10px;
- z-index: 10001;
- background-color: rgba(33, 37, 41, 0.95);
- color: white;
- padding: 8px 16px;
- border-radius: 5px;
- cursor: pointer;
- font-family: "Noto Sans", sans-serif;
- font-size: 14px;
- transition: background-color 0.3s;
- box-shadow: 0 2px 5px rgba(0, 0, 0, 0.5);
- }
- .ptrk_toggle_btn:hover {
- background-color: rgba(50, 55, 60, 0.95);
- }
- .ptrk_main fieldset {
- border: 1px solid rgb(59, 59, 63);
- border-radius: 3px;
- padding: 10px;
- margin-bottom: 10px;
- }
- .ptrk_main legend {
- font-size: 12px;
- padding: 0 5px;
- }
- .ptrk_main input, .ptrk_main textarea, .ptrk_main select {
- width: 100%;
- color: #d1d5db;
- padding: 10px;
- margin: 5px 0;
- box-sizing: border-box;
- font-size: 12px;
- background: rgba(0, 0, 0, 0.2);
- border: 1px solid #8e8e8e;
- border-radius: 3px;
- -webkit-appearance: none;
- -moz-appearance: none;
- appearance: none;
- }
- .ptrk_main select {
- cursor: pointer;
- background: rgba(0, 0, 0, 0.2) url('data:image/svg+xml;utf8,<svg fill="%23d1d5db" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"><path d="M7 10l5 5 5-5z"/></svg>') no-repeat right 10px center;
- }
- .ptrk_main select::-ms-expand {
- display: none;
- }
- .ptrk_main select option {
- color: #d1d5db;
- background: rgba(33, 37, 41, 0.95);
- }
- .ptrk_main textarea {
- resize: vertical;
- }
- .ptrk_main textarea[readonly] {
- background: rgba(255, 255, 255, 0.05);
- border-color: rgba(255, 255, 255, 0.1);
- }
- .ptrk_main .abtn {
- cursor: pointer;
- padding: 6px 12px;
- border-radius: 3px;
- font-weight: bold;
- margin: 2px;
- background: rgb(95, 99, 101);
- text-align: center;
- transition: background 0.2s;
- }
- .ptrk_main .abtn:hover {
- background: rgb(118, 123, 125);
- }
- .ptrk_main .midbtns {
- display: flex;
- justify-content: center;
- margin-top: 5px;
- }
- .ptrk_models_table {
- max-height: 200px;
- overflow-y: auto;
- margin-top: 5px;
- }
- .ptrk_models_table table {
- width: 100%;
- border-collapse: collapse;
- font-size: 11px;
- color: #d1d5db;
- background: rgba(0, 0, 0, 0.5);
- }
- .ptrk_models_table th, .ptrk_models_table td {
- border: 1px solid rgb(45, 45, 48);
- padding: 5px;
- text-align: left;
- }
- .ptrk_models_table th {
- background: rgba(0, 0, 0, 0.6);
- }
- `;
- document.head.appendChild(style);
-
- // Toggle button
- const toggleBtn = document.createElement('div');
- toggleBtn.classList.add('ptrk_toggle_btn');
- toggleBtn.textContent = 'Hide UI';
- document.body.appendChild(toggleBtn);
-
- // Main UI container
- const mainDom = document.createElement('div');
- mainDom.classList.add('ptrk_main');
- mainDom.innerHTML = `
- <fieldset>
- <legend>API Selection</legend>
- <select id="api-selection">
- <option value="openrouter" ${storedApiSelection === 'openrouter' ? 'selected' : ''}>OpenRouter</option>
- <option value="gemini" ${storedApiSelection === 'gemini' ? 'selected' : ''}>Google Gemini</option>
- </select>
- </fieldset>
- <fieldset id="openrouter-key-field" style="${storedApiSelection === 'openrouter' ? '' : 'display: none;'}">
- <legend>OpenRouter API Key</legend>
- <input id="api-key" type="text" placeholder="Enter your OpenRouter API key..." value="${storedOpenRouterKey}">
- </fieldset>
- <fieldset id="gemini-key-field" style="${storedApiSelection === 'gemini' ? '' : 'display: none;'}">
- <legend>Google Gemini API Key</legend>
- <input id="gemini-key" type="text" placeholder="Enter your Gemini API key..." value="${storedGeminiKey}">
- </fieldset>
- <fieldset id="gemini-temp-field" style="${storedApiSelection === 'gemini' ? '' : 'display: none;'}">
- <legend>Temperature (0.0 - 2.0)</legend>
- <input id="gemini-temperature" type="number" step="0.1" min="0" max="2" placeholder="Enter temperature (default 1.0)" value="${storedTemperature}">
- </fieldset>
- <fieldset>
- <legend>Model</legend>
- <select id="model-select">
- <option value="${storedModel}">${storedModel} (default)</option>
- </select>
- </fieldset>
- <fieldset>
- <legend>Available Models</legend>
- <div class="ptrk_models_table">
- <table>
- <thead>
- <tr>
- <th>Model Name</th>
- <th>Input ($/1k)</th>
- <th>Output ($/1k)</th>
- </tr>
- </thead>
- <tbody id="models-table-body">
- <tr><td colspan="3">Loading models...</td></tr>
- </tbody>
- </table>
- </div>
- </fieldset>
- <fieldset>
- <legend>Persona</legend>
- <textarea id="persona-input" placeholder="Enter your persona here...">${storedPersona}</textarea>
- </fieldset>
- <fieldset>
- <legend>Input</legend>
- <textarea id="user-input" placeholder="Enter message to impersonate...">${storedInput}</textarea>
- <div class="midbtns">
- <div class="abtn" data-tag="generate">Generate Text</div>
- <div class="abtn" data-tag="generate-next">Generate Next Response</div>
- </div>
- </fieldset>
- <fieldset>
- <legend>Output</legend>
- <textarea id="output-text" readonly></textarea>
- <div class="midbtns">
- <div class="abtn" data-tag="copy" style="display: none;">Copy to Clipboard</div>
- </div>
- </fieldset>
- `;
- document.body.appendChild(mainDom);
-
- // UI elements
- const apiSelection = mainDom.querySelector('#api-selection');
- const openRouterKeyField = mainDom.querySelector('#openrouter-key-field');
- const geminiKeyField = mainDom.querySelector('#gemini-key-field');
- const geminiTempField = mainDom.querySelector('#gemini-temp-field');
- const apiKeyInput = mainDom.querySelector('#api-key');
- const geminiKeyInput = mainDom.querySelector('#gemini-key');
- const geminiTempInput = mainDom.querySelector('#gemini-temperature');
- const modelSelect = mainDom.querySelector('#model-select');
- const modelsTableBody = mainDom.querySelector('#models-table-body');
- const personaInput = mainDom.querySelector('#persona-input');
- const input = mainDom.querySelector('#user-input');
- const generateBtn = mainDom.querySelector('[data-tag="generate"]');
- const generateNextBtn = mainDom.querySelector('[data-tag="generate-next"]');
- const output = mainDom.querySelector('#output-text');
- const copyBtn = mainDom.querySelector('[data-tag="copy"]');
-
- // Toggle UI visibility
- let isHidden = false;
- toggleBtn.addEventListener('click', () => {
- isHidden = !isHidden;
- mainDom.classList.toggle('ptrk_hidden', isHidden);
- toggleBtn.textContent = isHidden ? 'Show UI' : 'Hide UI';
- });
-
- // API selection change
- apiSelection.addEventListener('change', () => {
- const selection = apiSelection.value;
- localStorage.setItem('cai_api_selection', selection);
- if (selection === 'openrouter') {
- openRouterKeyField.style.display = '';
- geminiKeyField.style.display = 'none';
- geminiTempField.style.display = 'none';
- updateModels('openrouter');
- } else if (selection === 'gemini') {
- openRouterKeyField.style.display = 'none';
- geminiKeyField.style.display = '';
- geminiTempField.style.display = '';
- updateModels('gemini');
- }
- });
-
- // Save OpenRouter API key
- apiKeyInput.addEventListener('change', () => {
- const apiKey = apiKeyInput.value.trim();
- GM_setValue('openrouter_key', apiKey);
- if (apiSelection.value === 'openrouter') {
- updateModels('openrouter');
- }
- });
-
- // Save Gemini API key
- geminiKeyInput.addEventListener('change', () => {
- const apiKey = geminiKeyInput.value.trim();
- localStorage.setItem('cai_gemini_key', apiKey);
- if (apiSelection.value === 'gemini') {
- updateModels('gemini');
- }
- });
-
- // Save Gemini temperature
- geminiTempInput.addEventListener('change', () => {
- const temperature = geminiTempInput.value.trim();
- localStorage.setItem('cai_gemini_temperature', temperature);
- });
-
- // Save selected model
- modelSelect.addEventListener('change', () => {
- const model = modelSelect.value;
- GM_setValue('openrouter_model', model);
- });
-
- // Save persona
- personaInput.addEventListener('change', () => {
- const persona = personaInput.value.trim();
- localStorage.setItem('cai_persona', persona);
- });
-
- // Save input
- input.addEventListener('change', () => {
- const userInput = input.value.trim();
- localStorage.setItem('cai_input', userInput);
- });
-
- // Generate text from manual input
- generateBtn.addEventListener('click', () => {
- const selection = apiSelection.value;
- const openRouterKey = apiKeyInput.value.trim();
- const geminiKey = geminiKeyInput.value.trim();
- const model = modelSelect.value;
- const persona = personaInput.value.trim();
- const userInput = input.value.trim();
- const temperature = geminiTempInput.value.trim();
-
- if (selection === 'openrouter' && !openRouterKey) {
- output.value = 'Please enter your OpenRouter API key.';
- copyBtn.style.display = 'none';
- return;
- } else if (selection === 'gemini' && !geminiKey) {
- output.value = 'Please enter your Google Gemini API key.';
- copyBtn.style.display = 'none';
- return;
- }
- if (!persona) {
- output.value = 'Please enter a persona.';
- copyBtn.style.display = 'none';
- return;
- }
- if (!userInput) {
- output.value = 'Please enter a message.';
- copyBtn.style.display = 'none';
- return;
- }
-
- const messages = [
- { tag: 'system', message: persona },
- { tag: 'user', message: userInput }
- ];
-
- if (selection === 'openrouter') {
- generateOpenRouterText(openRouterKey, model, messages, output, copyBtn);
- } else if (selection === 'gemini') {
- generateGeminiText(geminiKey, model, messages, temperature, output, copyBtn);
- }
- });
-
- // Generate next response from conversation history with new input
- generateNextBtn.addEventListener('click', async () => {
- const selection = apiSelection.value;
- const openRouterKey = apiKeyInput.value.trim();
- const geminiKey = geminiKeyInput.value.trim();
- const model = modelSelect.value;
- const persona = personaInput.value.trim();
- const userInput = input.value.trim();
- const temperature = geminiTempInput.value.trim();
-
- if (selection === 'openrouter' && !openRouterKey) {
- output.value = 'Please enter your OpenRouter API key.';
- copyBtn.style.display = 'none';
- return;
- } else if (selection === 'gemini' && !geminiKey) {
- output.value = 'Please enter your Google Gemini API key.';
- copyBtn.style.display = 'none';
- return;
- }
- if (!persona) {
- output.value = 'Please enter a persona.';
- copyBtn.style.display = 'none';
- return;
- }
- if (!userInput) {
- output.value = 'Please enter a message.';
- copyBtn.style.display = 'none';
- return;
- }
-
- const AccessToken = getAccessToken();
- if (!AccessToken) {
- output.value = 'Could not retrieve access token. Are you logged in?';
- copyBtn.style.display = 'none';
- return;
- }
-
- const charId = getCharId();
- if (!charId) {
- output.value = 'Could not find character ID. Are you on a chat page?';
- copyBtn.style.display = 'none';
- return;
- }
-
- const converId = await getCurrentConverId();
- if (!converId) {
- output.value = 'Could not find current conversation ID.';
- copyBtn.style.display = 'none';
- return;
- }
-
- output.value = 'Fetching conversation history...';
- try {
- const chatData = await fetchMessagesChat2({ AccessToken, converExtId: converId });
- const messages = [
- { tag: 'system', message: persona },
- ...(chatData || []).map(msg => ({
- tag: msg.tag,
- message: msg.message
- })),
- { tag: 'user', message: userInput }
- ];
-
- if (selection === 'openrouter') {
- generateOpenRouterText(openRouterKey, model, messages, output, copyBtn);
- } else if (selection === 'gemini') {
- generateGeminiText(geminiKey, model, messages, temperature, output, copyBtn);
- }
- } catch (error) {
- output.value = `Error: ${error.message}`;
- copyBtn.style.display = 'none';
- }
- });
-
- // Copy output to clipboard
- copyBtn.addEventListener('click', () => {
- navigator.clipboard.writeText(output.value).then(() => {
- alert('Text copied to clipboard!');
- });
- });
-
- // Update models list and dropdown based on API selection
- function updateModels(api) {
- if (api === 'openrouter') {
- const apiKey = apiKeyInput.value.trim();
- fetchOpenRouterModels(apiKey, (models, error) => {
- if (error) {
- modelsTableBody.innerHTML = `<tr><td colspan="3">${error}</td></tr>`;
- modelSelect.innerHTML = `<option value="${storedModel}">${storedModel} (default)</option>`;
- return;
- }
-
- modelSelect.innerHTML = models.map(model => {
- const selected = model.id === storedModel ? 'selected' : '';
- return `<option value="${model.id}" ${selected}>${model.name}</option>`;
- }).join('');
-
- modelsTableBody.innerHTML = models.map(model => {
- const inputCost = model.pricing?.prompt ? (parseFloat(model.pricing.prompt) * 1000).toFixed(4) : 'N/A';
- const outputCost = model.pricing?.completion ? (parseFloat(model.pricing.completion) * 1000).toFixed(4) : 'N/A';
- return `
- <tr>
- <td>${model.name}</td>
- <td>${inputCost}</td>
- <td>${outputCost}</td>
- </tr>
- `;
- }).join('');
- });
- } else if (api === 'gemini') {
- const apiKey = geminiKeyInput.value.trim();
- fetchGeminiModels(apiKey, (models, error) => {
- if (error) {
- modelsTableBody.innerHTML = `<tr><td colspan="3">${error}</td></tr>`;
- modelSelect.innerHTML = models.map(model => {
- const selected = model.id === storedModel ? 'selected' : '';
- return `<option value="${model.id}" ${selected}>${model.name}</option>`;
- }).join('');
- } else {
- modelSelect.innerHTML = models.map(model => {
- const selected = model.id === storedModel ? 'selected' : '';
- return `<option value="${model.id}" ${selected}>${model.name}</option>`;
- }).join('');
-
- modelsTableBody.innerHTML = models.map(model => {
- return `
- <tr>
- <td>${model.name}</td>
- <td>${model.pricing.prompt}</td>
- <td>${model.pricing.completion}</td>
- </tr>
- `;
- }).join('');
- }
- });
- }
- }
-
- // Initial models fetch based on stored selection
- updateModels(storedApiSelection);
- }
-
- // ### Run Script
- waitForDOM(() => {
- console.log('DOM ready, initializing UI');
- createUI();
- });
- })();