Skip to content

Instantly share code, notes, and snippets.

@PeronGH
Last active May 8, 2024 17:38
Show Gist options
  • Save PeronGH/209678c5ea0f228344ea2c76bb5e9850 to your computer and use it in GitHub Desktop.
Save PeronGH/209678c5ea0f228344ea2c76bb5e9850 to your computer and use it in GitHub Desktop.
// ==UserScript==
// @name Claude Plus
// @namespace https://gist.github.com/PeronGH/209678c5ea0f228344ea2c76bb5e9850
// @version 1.0.3
// @description Enhance your Claude experiences
// @author Peron
// @match https://claude.ai/chats
// @match https://claude.ai/chat/*
// @icon https://www.google.com/s2/favicons?sz=64&domain=claude.ai
// @grant GM_registerMenuCommand
// @grant GM_openInTab
// @grant GM_setValue
// @grant GM_getValue
// @downloadURL https://gist.github.com/PeronGH/209678c5ea0f228344ea2c76bb5e9850/raw/claude-plus.user.js
// @updateURL https://gist.github.com/PeronGH/209678c5ea0f228344ea2c76bb5e9850/raw/claude-plus.user.js
// ==/UserScript==
(async () => {
"use strict";
// Constants
const DEFAULT_INSTRUCTIONS = `
You are Claude. Follow the instructions closely in future responses:
1. You should not include any caveats or disclaimers in your responses.
2. Your responses should be written in the style of a blogger or Stack Overflow answer - using simple, clear language and avoiding overcomplicated words or sentence structures. The tone should be natural, professional and comprehensive.
3. Rather than taking a neutral or diplomatic stance, you aim to be definitive, specific and personal.
4. You should engage with the user as an equal, avoiding a lecturing or condescending tone. Instead of obeying the user in every way, you should think critically and argue your point of view.
`.trim();
const CUSTOM_INSTRUCTIONS_KEY = "custom_instructions";
// Prepare GM functions
const GM_registerMenuCommand = window.GM_registerMenuCommand ??
GM.registerMenuCommand;
const GM_openInTab = window.GM_openInTab ?? GM.openInTab;
const GM_setValue = window.GM_setValue ?? GM.setValue;
const GM_getValue = window.GM_getValue ?? GM.getValue;
// Helpers
const extractTextFromElement = (el) => {
let reactPropsKey;
for (const key in el) {
if (key.startsWith("__reactProps")) {
reactPropsKey = key;
break;
}
}
return el[reactPropsKey]?.children?.props?.text ?? el.textContent;
};
const findUserMessages = () =>
Array.from(document.querySelectorAll("div.font-user-message"))
.map((el) => el.textContent);
const findClaudeMessages = () =>
Array.from(document.querySelectorAll("div.font-claude-message"))
.map(extractTextFromElement);
const getCustomInstructions = () =>
GM_getValue(CUSTOM_INSTRUCTIONS_KEY, DEFAULT_INSTRUCTIONS);
const setCustomInstructions = (instructions) =>
GM_setValue(CUSTOM_INSTRUCTIONS_KEY, instructions);
/** Upload to ShareGPT */
const UploadToShareGPT = async () => {
const userMessages = findUserMessages();
const claudeMessages = findClaudeMessages();
if (userMessages.length === 0 || claudeMessages.length === 0) {
throw new Error("No messages found");
}
console.debug({ userMessages, claudeMessages });
const conversationData = {
model: "Claude 3",
items: [],
};
for (let i = 0; i < userMessages.length; i++) {
conversationData.items.push({
from: "human",
value: userMessages[i].trim(),
});
if (claudeMessages[i]) {
conversationData.items.push({
from: "gpt",
value: claudeMessages[i].trim(),
});
}
}
console.debug({ conversationData });
const res = await fetch("https://sharegpt.com/api/conversations", {
body: JSON.stringify(conversationData),
headers: {
"Content-Type": "application/json",
},
method: "POST",
});
if (!res.ok) {
throw new Error("Failed to upload conversation to ShareGPT");
}
const { id } = await res.json();
const url = `https://shareg.pt/${id}`;
GM_openInTab(url);
};
let isModalOpen = false;
/** Render custom instructions input */
const RenderCustomInstructionsInput = () => {
if (isModalOpen) return;
isModalOpen = true;
// create elements
const dialog = document.createElement("dialog");
const form = document.createElement("form");
const textarea = document.createElement("textarea");
const buttonDiv = document.createElement("div");
const submitButton = document.createElement("button");
const cancelButton = document.createElement("button");
// create basic layout
buttonDiv.appendChild(submitButton);
buttonDiv.appendChild(cancelButton);
form.appendChild(textarea);
form.appendChild(buttonDiv);
dialog.appendChild(form);
dialog.addEventListener("close", () => {
dialog.remove();
isModalOpen = false;
});
document.body.appendChild(dialog);
// Set attributes
textarea.value = getCustomInstructions();
textarea.rows = 5;
submitButton.textContent = "Save";
submitButton.type = "submit";
cancelButton.textContent = "Cancel";
cancelButton.type = "button";
cancelButton.addEventListener("click", () => dialog.close());
form.addEventListener("submit", (e) => {
e.preventDefault();
setCustomInstructions(textarea.value);
dialog.close();
});
// add styles
buttonDiv.classList.add("flex", "justify-between");
// present modal
dialog.showModal();
};
const ApplyCustomInstructions = () => {
const fileInput = document.querySelector(
'input[data-testid="file-upload"]',
);
if (!fileInput) {
alert("Unable to find file input");
return;
}
const file = new File(
[getCustomInstructions()],
"Claude_Instructions.md",
{ type: "text/markdown" },
);
const dataTransfer = new DataTransfer();
dataTransfer.items.add(file);
fileInput.files = dataTransfer.files;
fileInput.dispatchEvent(new Event("change", { bubbles: true }));
};
// Main
// register menu commands
GM_registerMenuCommand(
"Apply Custom Instructions",
() => ApplyCustomInstructions(),
"a",
);
GM_registerMenuCommand(
"Upload to ShareGPT",
() =>
UploadToShareGPT().catch((err) =>
alert("Error uploading to ShareGPT: " + err.message)
),
"u",
);
GM_registerMenuCommand(
"Set Custom Instructions",
() => RenderCustomInstructionsInput(),
"s",
);
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment