Greasy Fork

Canvas ChatGPT

Adds ChatGPT into Instructure Canvas

目前为 2023-04-27 提交的版本。查看 最新版本

// ==UserScript==
// @name         Canvas ChatGPT
// @namespace    http://tampermonkey.net/
// @version      1.2
// @description  Adds ChatGPT into Instructure Canvas
// @author       Riley Campbell
// @match        *.instructure.com/*
// @license      https://creativecommons.org/licenses/by-nc/4.0/
// ==/UserScript==
(async function() {
    'use strict';
    if(!window.location.href.includes("conversations")) {
        setInterval(function(){
            window.defaultMessage = {
                "role": "system",
                "content": localStorage.getItem('AIprompt')
            }
        },1000)
        let prompt = localStorage.getItem('AIprompt')
        if (prompt==null) {
            prompt=''
            localStorage.setItem('AIprompt','')
        }
        window.defaultMessage = {
            "role": "system",
            "content": prompt
        }
        window.AImessages = [window.defaultMessage]
        window.renderMessages = function() {
            let container = document.getElementById('container')
            container.innerHTML = ''
            for(let message in window.AImessages) {
                message = window.AImessages[message]
                if(message.role == "system") {
                    continue
                }
                container.innerHTML += '<table><tr><th><p>' + message.role.charAt(0).toUpperCase() + message.role.slice(1) + ': </p></th><th style="white-space: break-spaces;">' + message.content + '</th></tr></table><hr class="solid">'
            }
            window.scrollTo({
                top: document.body.scrollHeight,
                behavior: 'smooth'
            });
        }
        window.sendMessage = async function() {
            document.getElementById('container').innerHTML += '<img style="display: block; width: 20px;margin-left: auto; margin-right: auto; margin-bottom:5px;" src="https://i.gifer.com/origin/34/34338d26023e5515f6cc8969aa027bca_w200.gif">'
            let message = {
                "role": "user",
                "content": document.getElementById('myTextArea').value
            }
            window.AImessages.push(message)
            document.getElementById('myTextArea').value = ''
            let messages = [window.defaultMessage, message]
            if(document.getElementById('memory').checked) {
                messages = window.AImessages
            }
            let response = await fetch('https://api.openai.com/v1/chat/completions', {
                method: "POST",
                headers: {
                    "Content-Type": "application/json",
                    "Authorization": `Bearer ` + localStorage.getItem('openaiAPIKey')
                },
                body: JSON.stringify({
                    "model": localStorage.getItem('AImodel'),
                    "messages": messages,
                    "temperature": 1
                })
            });

            if(response.ok) {
                let result = await response.json()
                window.AImessages.push(result.choices[0].message)
                window.renderMessages()
            } else {
                document.getElementById('container').innerHTML = `Error ${response.status}: ${response.statusText}`;
            };
        }
        window.populateModels = async function() {
            let supportedModels = ['gpt-4', 'gpt-4-0314', 'gpt-4-32k', 'gpt-4-32k-0314', 'gpt-3.5-turbo', 'gpt-3.5-turbo-0301']
            let response = await fetch('https://api.openai.com/v1/models', {
                method: "GET",
                headers: {
                    "Content-Type": "application/json",
                    "Authorization": `Bearer ` + localStorage.getItem('openaiAPIKey')
                },
            });
            let options = await response.json()
            let item = null
            let element = null
            let models = document.getElementById('models')
            models.innerHTML = ''
            for(let i = 0; i < options.data.length; i++) {
                item = options.data[i]
                if(supportedModels.includes(item.id)) {
                    element = document.createElement("option")
                    element.value = item.id
                    element.innerHTML = item.id
                    models.appendChild(element)
                }
            }
        }
        document.getElementById('wrapper').innerHTML += `
<style>
    .switch {
        position: relative;
        display: inline-block;
        width: 60px;
        height: 34px
    }

    .switch input {
        opacity: 0;
        width: 0;
        height: 0
    }

    .slider {
        position: absolute;
        cursor: pointer;
        top: 0;
        left: 0;
        right: 0;
        bottom: 0;
        background-color: #ccc;
        -webkit-transition: .4s;
        transition: .4s
    }

    .slider:before {
        position: absolute;
        content: "";
        height: 26px;
        width: 26px;
        left: 4px;
        bottom: 4px;
        background-color: #fff;
        -webkit-transition: .4s;
        transition: .4s
    }

    input:checked+.slider {
        background-color: #2196F3
    }

    input:focus+.slider {
        box-shadow: 0 0 1px #2196F3
    }

    input:checked+.slider:before {
        -webkit-transform: translateX(26px);
        -ms-transform: translateX(26px);
        transform: translateX(26px)
    }

    .slider.round {
        border-radius: 34px
    }

    .slider.round:before {
        border-radius: 50%
    }

    input[type="checkbox"] {
        display: none;
    }

    .wrap-collabsible {
        margin: 1.2rem 0;
    }

    .lbl-toggle {
        display: block;
        font-weight: bold;
        font-family: monospace;
        font-size: 1.2rem;
        text-transform: uppercase;
        text-align: center;
        padding: 1rem;
        color: #DDD;
        background: #0069ff;
        cursor: pointer;
        border-radius: 7px;
        transition: all 0.25s ease-out;
    }

    .lbl-toggle:hover {
        color: #FFF;
    }

    .lbl-toggle::before {
        content: " ";
        display: inline-block;
        border-top: 5px solid transparent;
        border-bottom: 5px solid transparent;
        border-left: 5px solid currentColor;
        vertical-align: middle;
        margin-right: .7rem;
        transform: translateY(-2px);
        transition: transform .2s ease-out;
    }

    .toggle:checked+.lbl-toggle::before {
        transform: rotate(90deg) translateX(-3px);
    }

    .collapsible-content {
        max-height: 0px;
        overflow: hidden;
        transition: max-height .25s ease-in-out;
    }

    .toggle:checked+.lbl-toggle+.collapsible-content {
        max-height: 350px;
    }

    .toggle:checked+.lbl-toggle {
        border-bottom-right-radius: 0;
        border-bottom-left-radius: 0;
    }

    .collapsible-content .content-inner {
        background: rgba(0, 105, 255, .2);
        border-bottom: 1px solid rgba(0, 105, 255, .45);
        border-bottom-left-radius: 7px;
        border-bottom-right-radius: 7px;
        padding: .5rem 1rem;
    }

    .collapsible-content p {
        margin-bottom: 0;
    }
</style>
<div class="ic-DashboardCard" style="padding:10px; color: ; margin-bottom:10px; width: 95%;margin-left:auto;margin-right:auto;">
    <div width=100% height=650px style="padding:10px;" id="container"></div>
    <textarea style="display: block; width: 95%; height: 100px; resize: none; margin-left: auto; margin-right: auto; margin-top:10px;" id="myTextArea"></textarea>
    <br>
    <button class="Button button-sidebar-wide" id=\'submitButton\' style="display: block; width: 95%;margin-left: auto; margin-right: auto; margin-top: 5px; margin-botton: 5px;">Clear History</button>
    <br>
    <div class="wrap-collabsible">
        <input id="collapsible" class="toggle" type="checkbox">
        <label for="collapsible" class="lbl-toggle">Settings</label>
        <div class="collapsible-content">
            <div class="content-inner">
                <table style="max-height:34px;">
                    <tr style="max-height:34px;">
                        <td>
                            <label style="display:block; max-height:34px;" class="switch">
                                <input id="memory" type="checkbox">
                                <span class="slider round"></span>
                            </label>
                        </td>
                        <td>
                            <label style="display:block;max-height:34px;text-align: center;margin-top:auto;margin-bottom:auto;">Allow AI to remember past messages. (may incur extra charges)</label>
                        </td>
                    </tr>
                </table>
                <br>
                <label for="models">Model:</label>
                <select name="models" id="models"></select>
                <br>
                <textarea style="display: block; width: 95%; height: 100px; resize: none; margin-left: auto; margin-right: auto; margin-top:10px;" placeholder="Default System Prompt (Requires history to be cleared in order to take effect)" id="systemPrompt">`+localStorage.getItem('AIprompt')+`</textarea>
                <br>
                <input id="key" placeholder="API Key" type="text" style="display:block;float:left;"></input>
                <button class="Button button-sidebar-wide" id="setAPIKey" style="display: block;">Set API Key</button>
            </div>
        </div>
    </div>
</div>
        `
        await window.populateModels()
        document.getElementById('models').value = localStorage.getItem('AImodel')
        let checked = (localStorage.getItem('AImemory') === 'true')
        document.getElementById('memory').checked = checked
        setInterval(function() {
            localStorage.setItem('AImemory', document.getElementById('memory').checked)
            localStorage.setItem('AImodel', document.getElementById('models').value)
            localStorage.setItem('AIprompt', document.getElementById('systemPrompt').value)
        }, 1000)
        document.getElementById('submitButton').onclick = async () => {
            window.AImessages = [window.defaultMessage]
            window.renderMessages()
        };
        document.getElementById('setAPIKey').onclick = async () => {
            localStorage.setItem('openaiAPIKey', document.getElementById('key').value)
            document.getElementById('key').value = ''
            await window.populateModels()
        };
        const myTextarea = document.getElementById("myTextArea");
        myTextarea.addEventListener("keydown", async function(event) {
            if(event.key === "Enter" && !event.shiftKey) {
                event.preventDefault();
                await window.sendMessage()
            }
        })
    };
})();