diff --git a/autoexpert_userscript.js b/autoexpert_userscript.js index 04203ba..f318b00 100644 --- a/autoexpert_userscript.js +++ b/autoexpert_userscript.js @@ -1,59 +1,57 @@ // ==UserScript== // @name AutoExpert v6 Loader // @namespace https://spdustin.substack.com -// @version 1.0 +// @version 1.0.1 // @description Loads AutoExpert custom instructions and provides simple buttons to activate them // @match https://chat.openai.com/* // @grant GM_xmlhttpRequest // ==/UserScript== -(function() { - const autoExpertPaths = [ - { - "title": "Standard v5", - "id": "std_5", - "user_url": "https://raw.githubusercontent.com/spdustin/ChatGPT-AutoExpert/dev__pre-eval/standard-edition/chatgpt_GPT4__about_me.md", - "model_url": "https://raw.githubusercontent.com/spdustin/ChatGPT-AutoExpert/dev__pre-eval/standard-edition/chatgpt_GPT4__custom_instructions.md", - "emoji": "🧠" - }, - { - "title": "Dev v5", - "id": "dev_5", - "user_url": "https://raw.githubusercontent.com/spdustin/ChatGPT-AutoExpert/dev__pre-eval/developer-edition/chatgpt__about_me.md", - "model_url": "https://raw.githubusercontent.com/spdustin/ChatGPT-AutoExpert/dev__pre-eval/developer-edition/chatgpt__custom_instructions.md", - "emoji": "💻" - }, - { - "title": "Voice V5", - "id": "voice_5", - "user_url": "https://raw.githubusercontent.com/spdustin/ChatGPT-AutoExpert/dev__voice_edition/voice-edition/chatgpt_GPT4_voice__about_me.md", - "model_url": "https://raw.githubusercontent.com/spdustin/ChatGPT-AutoExpert/dev__voice_edition/voice-edition/chatgpt_GPT4_voice__custom_instructions.md", - "emoji": "💬" - } +(function () { + const autoExpertPaths = [{ + "title": "Standard v5", + "id": "std_5", + "user_url": "https://raw.githubusercontent.com/spdustin/ChatGPT-AutoExpert/dev__pre-eval/standard-edition/chatgpt_GPT4__about_me.md", + "model_url": "https://raw.githubusercontent.com/spdustin/ChatGPT-AutoExpert/dev__pre-eval/standard-edition/chatgpt_GPT4__custom_instructions.md", + "emoji": "🧠" + }, + { + "title": "Dev v5", + "id": "dev_5", + "user_url": "https://raw.githubusercontent.com/spdustin/ChatGPT-AutoExpert/dev__pre-eval/developer-edition/chatgpt__about_me.md", + "model_url": "https://raw.githubusercontent.com/spdustin/ChatGPT-AutoExpert/dev__pre-eval/developer-edition/chatgpt__custom_instructions.md", + "emoji": "💻" + }, + { + "title": "Voice V5", + "id": "voice_5", + "user_url": "https://raw.githubusercontent.com/spdustin/ChatGPT-AutoExpert/dev__voice_edition/voice-edition/chatgpt_GPT4_voice__about_me.md", + "model_url": "https://raw.githubusercontent.com/spdustin/ChatGPT-AutoExpert/dev__voice_edition/voice-edition/chatgpt_GPT4_voice__custom_instructions.md", + "emoji": "💬" + } ]; const BASE_URL = 'https://chat.openai.com'; const dataCache = {}; const customInstructionButtons = createFloatingButtonContainer(); - + let cachedToken = null; let tokenExpiration = Date.now(); - + function handleError(context) { return error => console.error(`An error occurred in ${context}:`, JSON.stringify(error, Object.getOwnPropertyNames(error))); } - + async function fetchToken() { if (Date.now() < tokenExpiration) return cachedToken; try { const tokenData = await fetch(`${BASE_URL}/api/auth/session`) .then(res => res.json()); cachedToken = tokenData.accessToken; - // Assuming token expires in 1 hour for example tokenExpiration = Date.now() + 3600000; } catch (err) { handleError('fetchToken')(err); } } - + async function fetchData(url, options = {}, needsAuth = false, isCrossOrigin = false) { if (needsAuth) { await fetchToken(); @@ -62,7 +60,7 @@ ...options.headers }; } - + if (isCrossOrigin) { // Use GM_xmlhttpRequest for cross-origin requests return new Promise((resolve, reject) => { @@ -83,8 +81,7 @@ return response.json(); } } - - + async function updateCustomInstructions(data) { try { const response = await fetchData(`${BASE_URL}/backend-api/user_system_messages`, { @@ -94,7 +91,7 @@ }, body: JSON.stringify(data) }, true); - + if (response.status !== 200) { throw new Error(`Failed to update custom instructions: ${response.statusText}`); } @@ -102,7 +99,20 @@ handleError('updateCustomInstructions')(err); } } - + + function createFloatingButtonContainer() { + const container = document.createElement('div'); + container.style.position = 'fixed'; + container.style.padding = '.25em'; + container.style.top = '3em'; + container.style.right = '1em'; + container.style.zIndex = '9999'; + container.style.backgroundColor = "var(--surface-tertiary)"; + container.style.display = "flex"; + container.style.flexDirection = "column"; + return container; + } + function createButton(buttonData) { const btn = document.createElement('button'); btn.title = buttonData.title; @@ -110,7 +120,7 @@ btn.innerHTML = buttonData.emoji; btn.dataset.userUrl = buttonData.user_url; btn.dataset.modelUrl = buttonData.model_url; - btn.style.backgroundColor = "#DFDFDF"; + btn.style.backgroundColor = "var(--surface-primary)"; btn.style.border = "none"; btn.style.color = "white"; btn.style.padding = ".25em"; @@ -120,8 +130,7 @@ btn.style.cursor = "pointer"; return btn; } - - + function updateButtonUI(btn, { disabled, backgroundColor, @@ -131,24 +140,28 @@ btn.style.backgroundColor = backgroundColor; btn.innerHTML = text || btn.innerHTML; } - + function generateButtons(autoExpertPaths, targetContainer) { autoExpertPaths.forEach(buttonData => { const btn = createButton(buttonData); btn.onclick = () => handleButtonClick(btn); targetContainer.appendChild(btn); }); - + const disableBtn = createButton({ id: "ae_disable", emoji: "🚫" }); disableBtn.onclick = () => handleDisableClick(disableBtn); targetContainer.appendChild(disableBtn); - + + const downloadBtn = createDownloadDataButton(); + downloadBtn.onclick = () => handleDownloadDataClick(downloadBtn); + targetContainer.appendChild(downloadBtn); + document.body.appendChild(targetContainer); } - + function handleButtonClick(btn) { updateButtonUI(btn, { disabled: true, @@ -156,31 +169,34 @@ }); performButtonClickAction(btn) .then(() => { - updateButtonUI(btn, { backgroundColor: 'green' }); + updateButtonUI(btn, { + backgroundColor: 'green' + }); location.reload(true); }) .catch((err) => { - updateButtonUI(btn, { backgroundColor: 'red' }); + updateButtonUI(btn, { + backgroundColor: 'red' + }); handleError('handleButtonClick')(err); }); } - + async function performButtonClickAction(btn) { // The contents from autoExpertPaths are cross-origin const userContentPromise = fetchData(btn.dataset.userUrl, {}, false, true); const modelContentPromise = fetchData(btn.dataset.modelUrl, {}, false, true); - + const [aboutUserContent, aboutModelContent] = await Promise.all([userContentPromise, modelContentPromise]); const data = { "about_user_message": aboutUserContent, "about_model_message": aboutModelContent, "enabled": true, }; - + await updateCustomInstructions(data); } - - + function handleDisableClick(btn) { updateButtonUI(btn, { backgroundColor: 'yellow', @@ -188,34 +204,68 @@ }); performDisableClickAction(btn) .then(() => { - updateButtonUI(btn, { backgroundColor: 'green' }); + updateButtonUI(btn, { + backgroundColor: 'green' + }); location.reload(true); }) .catch((err) => { - updateButtonUI(btn, { backgroundColor: 'red' }); + updateButtonUI(btn, { + backgroundColor: 'red' + }); handleError('handleDisableClick')(err); }); } - + async function performDisableClickAction(btn) { const fetchedData = await fetchData(`${BASE_URL}/backend-api/user_system_messages`, {}, true); fetchedData.enabled = false; - + await updateCustomInstructions(fetchedData); } - - - function createFloatingButtonContainer() { - const container = document.createElement('div'); - container.style.position = 'fixed'; - container.style.top = '3em'; - container.style.right = '1em'; - container.style.zIndex = '9999'; - container.style.width = '2em'; - container.style.backgroundColor = "#FFFFFF"; - return container; + + function createDownloadDataButton() { + const downloadBtn = createButton({ + title: "Download Data", + id: "download_data", + emoji: "📥" + }); + downloadBtn.onclick = handleDownloadDataClick.bind(downloadBtn); + return downloadBtn; } - + + async function handleDownloadDataClick(btn) { + try { + updateButtonUI(btn, { + backgroundColor: 'yellow', + disabled: true + }); + const data = await fetchData(`${BASE_URL}/backend-api/user_system_messages`, {}, true); + if (data) { + const blob = new Blob([JSON.stringify(data)], { type: 'application/json' }); + const url = URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = 'user_system_messages.json'; + document.body.appendChild(a); + a.click(); + document.body.removeChild(a); + URL.revokeObjectURL(url); + } else { + throw new Error('Received undefined data'); + } + updateButtonUI(btn, { + backgroundColor: '#DFDFDF', + disabled: false + }); + } catch (err) { + updateButtonUI(btn, { + backgroundColor: 'red' + }); + handleError('handleDownloadDataClick')(err); + } + } + generateButtons(autoExpertPaths, customInstructionButtons); -})(); +})(); \ No newline at end of file