JustPaste
HomeCategoriesAboutDonateContactTerms of UsePrivacy Policy
JustPaste

Free online notepad — write and share instantly

Navigate

  • Home
  • Timeline
  • Categories

Info

  • About
  • Donate
  • Contact

Legal

  • Terms of Use
  • Privacy Policy

© 2026 JustPaste.app. All rights reserved.

Made with ♥ by JustPaste

SCRIPT : Kingston Vang | Supabase | Vibecoded | Designed with Photoshop | Uses open router | JustPaste.app
about 1 month ago5 views
👨‍💻Programming

SCRIPT : Kingston Vang | Supabase | Vibecoded | Designed with Photoshop | Uses open router

// ==UserScript==
// @name         STUDY//OS
// @namespace    study-os
// @version      1.3.0
// @description  STUDY//OS educational AI multitool
// @author       STUDY//OS
// @match        *://*/*
// @grant        GM_xmlhttpRequest
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_deleteValue
// @connect      dzpmqkxijesvszrdnlxy.supabase.co
// @run-at       document-idle
// ==/UserScript==

(function () {

    "use strict";

    console.log("[STUDY//OS] Starting...");

    /* =========================================================
       CONFIG
       ========================================================= */

    const CONFIG = {

        SUPABASE_URL:
            "https://dzpmqkxijesvszrdnlxy.supabase.co",

        // PUT YOUR SUPABASE PUBLISHABLE / ANON KEY HERE
        SUPABASE_KEY:
            "sb_publishable_emyadr4a280vmeKErM5nOQ_MAN-JKUN",

        FUNCTION:
            "/functions/v1/quick-task",

        MODEL:
            "openrouter/free",

        VERSION:
            "1.3.0"
    };


    /* =========================================================
       STATE
       ========================================================= */

    const state = {

        host: null,

        shadow: null,

        panel: null,

        launcher: null,

        open: false,

        loading: false,

        feature: "explain",

        accessKey:
            GM_getValue(
                "study_os_access_key",
                ""
            )

    };


    /* =========================================================
       FEATURES
       ========================================================= */

    const FEATURES = [

        {
            id: "explain",
            icon: "✦",
            title: "EXPLAIN",
            description: "Understand a concept"
        },

        {
            id: "writing",
            icon: "✎",
            title: "WRITING",
            description: "Improve your writing"
        },

        {
            id: "math",
            icon: "∑",
            title: "MATH",
            description: "Step-by-step help"
        },

        {
            id: "quiz",
            icon: "?",
            title: "QUIZ",
            description: "Create practice questions"
        },

        {
            id: "summary",
            icon: "▤",
            title: "SUMMARY",
            description: "Make study notes"
        },

        {
            id: "custom",
            icon: "⌘",
            title: "ASK AI",
            description: "Ask anything"
        }

    ];


    /* =========================================================
       DOM HELPERS
       ========================================================= */

    function create(
        tag,
        options = {}
    ) {

        const element =
            document.createElement(tag);

        if (options.className) {

            element.className =
                options.className;

        }

        if (options.id) {

            element.id =
                options.id;

        }

        if (options.text !== undefined) {

            element.textContent =
                options.text;

        }

        if (options.type) {

            element.type =
                options.type;

        }

        if (options.placeholder) {

            element.placeholder =
                options.placeholder;

        }

        if (options.title) {

            element.title =
                options.title;

        }

        return element;

    }


    function append(
        parent,
        ...children
    ) {

        children.forEach(
            child => {

                if (child) {

                    parent.appendChild(
                        child
                    );

                }

            }
        );

        return parent;

    }


    function $(selector) {

        if (!state.shadow) {

            return null;

        }

        return state.shadow.querySelector(
            selector
        );

    }


    /* =========================================================
       STYLES
       ========================================================= */

    function installStyles() {

        const style =
            create("style");

        style.textContent = `

            * {
                box-sizing: border-box;
            }

            :host {
                all: initial;
            }

            .sos-launcher {

                position: fixed;

                right: 24px;
                bottom: 24px;

                width: 64px;
                height: 64px;

                display: flex;

                align-items: center;
                justify-content: center;

                z-index: 2147483647;

                border: 4px solid #000;

                background: #ff2bd6;

                color: #000;

                font-family:
                    Arial,
                    Helvetica,
                    sans-serif;

                font-size: 29px;

                font-weight: 900;

                cursor: pointer;

                box-shadow:
                    8px 8px 0 #000;

                user-select: none;

                transition:
                    transform .1s ease,
                    box-shadow .1s ease;
            }

            .sos-launcher:hover {

                transform:
                    translate(-3px, -3px);

                box-shadow:
                    11px 11px 0 #000;
            }

            .sos-launcher:active {

                transform:
                    translate(5px, 5px);

                box-shadow:
                    3px 3px 0 #000;
            }

            .sos-panel {

                position: fixed;

                right: 24px;
                bottom: 24px;

                width: 450px;

                height: 730px;

                max-width:
                    calc(100vw - 20px);

                max-height:
                    calc(100vh - 20px);

                display: flex;

                flex-direction: column;

                overflow: hidden;

                z-index: 2147483647;

                border: 4px solid #000;

                background: #ededed;

                color: #000;

                box-shadow:
                    12px 12px 0 #000;

                font-family:
                    Arial,
                    Helvetica,
                    sans-serif;
            }

            .sos-header {

                min-height: 68px;

                display: flex;

                align-items: center;

                justify-content: space-between;

                padding: 10px 12px;

                background: #000;

                color: #fff;
            }

            .sos-logo-main {

                font-size: 22px;

                font-weight: 1000;

                letter-spacing: -1.5px;
            }

            .sos-logo-sub {

                margin-top: 3px;

                color: #ff2bd6;

                font-family: monospace;

                font-size: 8px;

                font-weight: 700;
            }

            .sos-header-right {

                display: flex;

                align-items: center;

                gap: 10px;
            }

            .sos-status {

                display: flex;

                align-items: center;

                gap: 5px;

                font-family: monospace;

                font-size: 8px;
            }

            .sos-status-dot {

                width: 8px;
                height: 8px;

                border-radius: 50%;

                background: #00ff66;

                border: 1px solid #fff;
            }

            .sos-close {

                width: 32px;
                height: 32px;

                padding: 0;

                border: 2px solid #fff;

                background: #ff2bd6;

                color: #000;

                font-size: 22px;

                font-weight: 900;

                cursor: pointer;
            }

            .sos-content {

                flex: 1;

                overflow-y: auto;

                padding: 12px;
            }

            .sos-box {

                margin-bottom: 12px;

                border: 2px solid #000;

                background: #fff;
            }

            .sos-box-title {

                padding: 7px 9px;

                background: #000;

                color: #fff;

                font-family: monospace;

                font-size: 9px;

                font-weight: 900;

                letter-spacing: .8px;
            }

            .sos-input-wrap {

                padding: 9px;
            }

            .sos-material {

                width: 100%;

                height: 145px;

                resize: vertical;

                padding: 10px;

                border: 2px solid #000;

                outline: none;

                background: #fff;

                color: #000;

                font-family: monospace;

                font-size: 11px;

                line-height: 1.45;
            }

            .sos-material:focus {

                outline: 3px solid #ff2bd6;

                outline-offset: -3px;
            }

            .sos-char-count {

                margin-top: 5px;

                text-align: right;

                color: #666;

                font-family: monospace;

                font-size: 8px;
            }

            .sos-tools {

                display: grid;

                grid-template-columns:
                    repeat(2, 1fr);

                gap: 7px;

                padding: 8px;
            }

            .sos-tool {

                min-height: 60px;

                padding: 8px;

                border: 2px solid #000;

                background: #fff;

                color: #000;

                text-align: left;

                cursor: pointer;

                font-family:
                    Arial,
                    sans-serif;

                font-size: 10px;

                font-weight: 900;
            }

            .sos-tool:hover {

                background: #ff2bd6;
            }

            .sos-tool.active {

                background: #ff2bd6;

                box-shadow:
                    3px 3px 0 #000;
            }

            .sos-tool-icon {

                margin-right: 5px;

                font-size: 17px;
            }

            .sos-tool-description {

                display: block;

                margin-top: 5px;

                color: #555;

                font-family: monospace;

                font-size: 8px;

                font-weight: 400;
            }

            .sos-custom {

                display: none;

                padding: 9px;
            }

            .sos-custom textarea {

                width: 100%;

                height: 75px;

                resize: vertical;

                padding: 9px;

                border: 2px solid #000;

                outline: none;

                font-family: monospace;

                font-size: 10px;
            }

            .sos-run {

                width: 100%;

                margin-bottom: 12px;

                padding: 14px;

                border: 3px solid #000;

                background: #ff2bd6;

                color: #000;

                cursor: pointer;

                font-size: 13px;

                font-weight: 1000;

                box-shadow:
                    5px 5px 0 #000;
            }

            .sos-run:hover {

                transform:
                    translate(-2px, -2px);

                box-shadow:
                    7px 7px 0 #000;
            }

            .sos-run:disabled {

                opacity: .6;

                cursor: wait;
            }

            .sos-output {

                min-height: 150px;

                max-height: 330px;

                overflow-y: auto;

                padding: 11px;

                white-space: pre-wrap;

                word-break: break-word;

                font-size: 12px;

                line-height: 1.55;
            }

            .sos-access {

                padding: 9px;
            }

            .sos-access-status {

                margin-bottom: 7px;

                font-family: monospace;

                font-size: 9px;
            }

            .sos-access-input {

                width: 100%;

                padding: 10px;

                border: 2px solid #000;

                outline: none;

                font-family: monospace;

                font-size: 10px;
            }

            .sos-save {

                width: 100%;

                margin-top: 7px;

                padding: 9px;

                border: 2px solid #000;

                background: #fff;

                color: #000;

                cursor: pointer;

                font-size: 10px;

                font-weight: 900;
            }

            .sos-save:hover {

                background: #ff2bd6;
            }

            .sos-footer {

                min-height: 35px;

                display: flex;

                align-items: center;

                justify-content: space-between;

                padding: 6px 9px;

                border-top: 3px solid #000;

                background: #fff;

                font-family: monospace;

                font-size: 8px;
            }

            .sos-footer button {

                padding: 0;

                border: 0;

                background: transparent;

                color: #000;

                cursor: pointer;

                font-family: monospace;

                font-size: 8px;

                text-decoration: underline;
            }

            .sos-toast {

                position: fixed;

                right: 24px;

                bottom: 100px;

                max-width: 330px;

                padding: 11px 14px;

                z-index: 2147483647;

                border: 2px solid #ff2bd6;

                background: #000;

                color: #fff;

                box-shadow:
                    5px 5px 0 #ff2bd6;

                font-family: monospace;

                font-size: 10px;
            }

            .sos-toast.error {

                border-color: #ff3333;

                box-shadow:
                    5px 5px 0 #ff3333;
            }

            .sos-toast.success {

                border-color: #00ff66;

                box-shadow:
                    5px 5px 0 #00ff66;
            }

            .sos-spinner {

                display: inline-block;

                width: 14px;
                height: 14px;

                border: 3px solid #ddd;

                border-top-color: #000;

                border-radius: 50%;

                animation:
                    sos-spin .7s linear infinite;
            }

            @keyframes sos-spin {

                to {
                    transform:
                        rotate(360deg);
                }
            }

            @media(max-width: 500px) {

                .sos-panel {

                    right: 8px;
                    bottom: 8px;

                    width:
                        calc(100vw - 16px);

                    height:
                        calc(100vh - 16px);
                }

                .sos-launcher {

                    right: 14px;
                    bottom: 14px;
                }

            }

        `;

        state.shadow.appendChild(
            style
        );

    }


    /* =========================================================
       ROOT
       ========================================================= */

    function createRoot() {

        if (
            document.getElementById(
                "study-os-host"
            )
        ) {

            return;

        }

        const host =
            document.createElement(
                "div"
            );

        host.id =
            "study-os-host";

        const shadow =
            host.attachShadow({
                mode: "open"
            });

        state.host =
            host;

        state.shadow =
            shadow;

        document.body.appendChild(
            host
        );

        installStyles();

        createLauncher();

        console.log(
            "[STUDY//OS] Launcher rendered."
        );

    }


    /* =========================================================
       LAUNCHER
       ========================================================= */

    function createLauncher() {

        const button =
            create(
                "button",
                {
                    className:
                        "sos-launcher",

                    text:
                        "✦",

                    title:
                        "Open STUDY//OS",

                    type:
                        "button"
                }
            );

        button.addEventListener(
            "pointerdown",
            function (event) {

                event.preventDefault();

                event.stopPropagation();

                console.log(
                    "[STUDY//OS] Launcher clicked."
                );

                openPanel();

            },
            true
        );

        state.launcher =
            button;

        state.shadow.appendChild(
            button
        );

    }


    /* =========================================================
       BOX CREATOR
       ========================================================= */

    function createBox(title) {

        const container =
            create(
                "div",
                {
                    className:
                        "sos-box"
                }
            );

        const titleElement =
            create(
                "div",
                {
                    className:
                        "sos-box-title",

                    text:
                        title
                }
            );

        const body =
            create("div");

        append(
            container,
            titleElement,
            body
        );

        return {

            container:
                container,

            body:
                body

        };

    }


    /* =========================================================
       OPEN PANEL
       ========================================================= */

    function openPanel() {

        if (state.open) {

            return;

        }

        state.open =
            true;

        if (state.launcher) {

            state.launcher.remove();

        }

        const panel =
            create(
                "div",
                {
                    className:
                        "sos-panel"
                }
            );

        state.panel =
            panel;


        /* HEADER */

        const header =
            create(
                "div",
                {
                    className:
                        "sos-header"
                }
            );

        const logo =
            create("div");

        const logoMain =
            create(
                "div",
                {
                    className:
                        "sos-logo-main",

                    text:
                        "STUDY//OS"
                }
            );

        const logoSub =
            create(
                "div",
                {
                    className:
                        "sos-logo-sub",

                    text:
                        "STUDENT MULTITOOL // v" +
                        CONFIG.VERSION
                }
            );

        append(
            logo,
            logoMain,
            logoSub
        );


        const headerRight =
            create(
                "div",
                {
                    className:
                        "sos-header-right"
                }
            );

        const status =
            create(
                "div",
                {
                    className:
                        "sos-status"
                }
            );

        const dot =
            create(
                "span",
                {
                    className:
                        "sos-status-dot"
                }
            );

        const online =
            create(
                "span",
                {
                    text:
                        "ONLINE"
                }
            );

        append(
            status,
            dot,
            online
        );


        const close =
            create(
                "button",
                {
                    className:
                        "sos-close",

                    text:
                        "×",

                    type:
                        "button"
                }
            );

        close.addEventListener(
            "click",
            closePanel
        );

        append(
            headerRight,
            status,
            close
        );

        append(
            header,
            logo,
            headerRight
        );


        /* CONTENT */

        const content =
            create(
                "div",
                {
                    className:
                        "sos-content"
                }
            );


        /* MATERIAL INPUT */

        const materialBox =
            createBox(
                "YOUR MATERIAL"
            );

        const inputWrap =
            create(
                "div",
                {
                    className:
                        "sos-input-wrap"
                }
            );

        const material =
            create(
                "textarea",
                {
                    className:
                        "sos-material",

                    id:
                        "sos-material",

                    placeholder:
                        "Paste your essay, math problem, notes, question, assignment, or anything you want STUDY//OS to work with..."
                }
            );

        const charCount =
            create(
                "div",
                {
                    className:
                        "sos-char-count",

                    id:
                        "sos-char-count",

                    text:
                        "0 characters"
                }
            );

        material.addEventListener(
            "input",
            function () {

                updateCharacterCount();

            }
        );

        append(
            inputWrap,
            material,
            charCount
        );

        materialBox.body.appendChild(
            inputWrap
        );


        /* TOOLS */

        const toolsBox =
            createBox(
                "TOOLS"
            );

        const tools =
            create(
                "div",
                {
                    className:
                        "sos-tools"
                }
            );

        buildTools(
            tools
        );

        toolsBox.body.appendChild(
            tools
        );


        /* CUSTOM REQUEST */

        const customBox =
            create(
                "div",
                {
                    className:
                        "sos-box sos-custom",

                    id:
                        "sos-custom-box"
                }
            );

        const customTitle =
            create(
                "div",
                {
                    className:
                        "sos-box-title",

                    text:
                        "CUSTOM REQUEST"
                }
            );

        const customWrap =
            create("div");

        const customInput =
            create(
                "textarea",
                {
                    placeholder:
                        "What do you want STUDY//OS to do with the material?"
                }
            );

        append(
            customWrap,
            customInput
        );

        append(
            customBox,
            customTitle,
            customWrap
        );


        /* RUN */

        const run =
            create(
                "button",
                {
                    className:
                        "sos-run",

                    id:
                        "sos-run",

                    text:
                        "RUN STUDY//OS",

                    type:
                        "button"
                }
            );

        run.addEventListener(
            "click",
            runAI
        );


        /* OUTPUT */

        const outputBox =
            createBox(
                "OUTPUT"
            );

        const output =
            create(
                "div",
                {
                    className:
                        "sos-output",

                    id:
                        "sos-output",

                    text:
                        "Your AI response will appear here."
                }
            );

        outputBox.body.appendChild(
            output
        );


        /* ACCESS */

        const accessBox =
            createBox(
                "ACCESS"
            );

        const access =
            create(
                "div",
                {
                    className:
                        "sos-access"
                }
            );

        const accessStatus =
            create(
                "div",
                {
                    className:
                        "sos-access-status",

                    id:
                        "sos-access-status"
                }
            );

        const accessInput =
            create(
                "input",
                {
                    className:
                        "sos-access-input",

                    id:
                        "sos-access-input",

                    type:
                        "password",

                    placeholder:
                        "STUDY-OS-XXXX-XXXX..."
                }
            );

        accessInput.autocomplete =
            "off";

        const save =
            create(
                "button",
                {
                    className:
                        "sos-save",

                    text:
                        "SAVE ACCESS KEY",

                    type:
                        "button"
                }
            );

        save.addEventListener(
            "click",
            saveAccessKey
        );

        append(
            access,
            accessStatus,
            accessInput,
            save
        );

        accessBox.body.appendChild(
            access
        );


        /* BUILD */

        append(
            content,

            materialBox.container,

            toolsBox.container,

            customBox,

            run,

            outputBox.container,

            accessBox.container
        );


        /* FOOTER */

        const footer =
            create(
                "div",
                {
                    className:
                        "sos-footer"
                }
            );

        const version =
            create(
                "span",
                {
                    text:
                        "STUDY//OS // " +
                        CONFIG.VERSION
                }
            );

        const clear =
            create(
                "button",
                {
                    text:
                        "CLEAR KEY",

                    type:
                        "button"
                }
            );

        clear.addEventListener(
            "click",
            function () {

                GM_deleteValue(
                    "study_os_access_key"
                );

                state.accessKey =
                    "";

                updateAccessStatus();

                toast(
                    "Access key cleared.",
                    "success"
                );

            }
        );

        append(
            footer,
            version,
            clear
        );


        append(
            panel,
            header,
            content,
            footer
        );

        state.shadow.appendChild(
            panel
        );

        updateAccessStatus();

        updateCharacterCount();

        console.log(
            "[STUDY//OS] Panel opened."
        );

    }


    /* =========================================================
       TOOLS
       ========================================================= */

    function buildTools(
        container
    ) {

        FEATURES.forEach(
            feature => {

                const button =
                    create(
                        "button",
                        {
                            className:
                                "sos-tool",

                            type:
                                "button"
                        }
                    );

                const icon =
                    create(
                        "span",
                        {
                            className:
                                "sos-tool-icon",

                            text:
                                feature.icon
                        }
                    );

                const title =
                    create(
                        "span",
                        {
                            text:
                                feature.title
                        }
                    );

                const description =
                    create(
                        "span",
                        {
                            className:
                                "sos-tool-description",

                            text:
                                feature.description
                        }
                    );

                append(
                    button,
                    icon,
                    title,
                    description
                );


                if (
                    feature.id ===
                    state.feature
                ) {

                    button.classList.add(
                        "active"
                    );

                }


                button.addEventListener(
                    "click",
                    function () {

                        state.feature =
                            feature.id;

                        container
                            .querySelectorAll(
                                ".sos-tool"
                            )
                            .forEach(
                                item => {

                                    item.classList
                                        .remove(
                                            "active"
                                        );

                                }
                            );

                        button.classList.add(
                            "active"
                        );


                        const custom =
                            $("#sos-custom-box");


                        if (custom) {

                            custom.style.display =
                                feature.id ===
                                "custom"
                                    ? "block"
                                    : "none";

                        }

                    }
                );


                container.appendChild(
                    button
                );

            }
        );

    }


    /* =========================================================
       CLOSE
       ========================================================= */

    function closePanel() {

        if (state.panel) {

            state.panel.remove();

        }

        state.panel =
            null;

        state.open =
            false;

        createLauncher();

        console.log(
            "[STUDY//OS] Panel closed."
        );

    }


    /* =========================================================
       CHARACTER COUNT
       ========================================================= */

    function updateCharacterCount() {

        const input =
            $("#sos-material");

        const counter =
            $("#sos-char-count");

        if (!input || !counter) {

            return;

        }

        const count =
            input.value.length;

        counter.textContent =
            count.toLocaleString() +
            " characters";

    }


    /* =========================================================
       ACCESS KEY
       ========================================================= */

    function saveAccessKey() {

        const input =
            $("#sos-access-input");

        if (!input) {

            return;

        }

        const key =
            input.value.trim();

        if (!key) {

            toast(
                "Enter your STUDY//OS access key.",
                "error"
            );

            return;

        }

        state.accessKey =
            key;

        GM_setValue(
            "study_os_access_key",
            key
        );

        updateAccessStatus();

        toast(
            "Access key saved.",
            "success"
        );

    }


    function updateAccessStatus() {

        const status =
            $("#sos-access-status");

        const input =
            $("#sos-access-input");

        if (!status) {

            return;

        }

        if (state.accessKey) {

            status.textContent =
                "● ACCESS KEY SAVED";

            status.style.color =
                "#008833";

            if (input) {

                input.value =
                    state.accessKey;

            }

        } else {

            status.textContent =
                "○ NO ACCESS KEY";

            status.style.color =
                "#cc0000";

        }

    }


    /* =========================================================
       PROMPTS
       ========================================================= */

    function buildPrompt(
        feature,
        material,
        custom
    ) {

        const base = `

You are STUDY//OS, an educational AI
assistant for high-school students.

Your job is to help students understand
their school material.

Be clear, accurate, concise, and educational.

Do not pretend to be the student.

Here is the student's material:

--- BEGIN MATERIAL ---

${material}

--- END MATERIAL ---

`;


        switch (feature) {

            case "explain":

                return base + `

Explain this material in a way a
high-school student can understand.

Structure the response as:

MAIN IDEA

KEY CONCEPTS

SIMPLE EXPLANATION

EXAMPLE

WHAT TO REMEMBER

`;

            case "writing":

                return base + `

Act as a writing coach.

Analyze the writing for:

- grammar
- spelling
- clarity
- sentence structure
- organization
- repetition
- awkward wording

Explain the important problems.

Then provide an improved version while
preserving the student's general voice.

`;

            case "math":

                return base + `

Act as a math tutor.

Determine what the problem is asking.

Explain the relevant concept.

Work through the solution step-by-step.

Show calculations clearly.

Give the final answer separately.

`;

            case "quiz":

                return base + `

Create a five-question practice quiz.

Use a mixture of:

- multiple choice
- short answer
- application questions

Put the answer key at the bottom.

`;

            case "summary":

                return base + `

Turn this material into useful study notes.

Use:

MAIN IDEA

KEY TERMS

IMPORTANT FACTS

IMPORTANT DETAILS

QUICK REVIEW

`;

            case "custom":

                return base + `

Student request:

${custom}

Complete the request using the
provided material.

`;

            default:

                return base;

        }

    }


    /* =========================================================
       RUN
       ========================================================= */

    async function runAI() {

        if (state.loading) {

            return;

        }


        const input =
            $("#sos-material");


        const material =
            input
                ? input.value.trim()
                : "";


        if (!material) {

            toast(
                "Paste your material into the box first.",
                "error"
            );

            if (input) {

                input.focus();

            }

            return;

        }


        if (!state.accessKey) {

            toast(
                "Enter your STUDY//OS access key first.",
                "error"
            );

            const accessInput =
                $("#sos-access-input");

            if (accessInput) {

                accessInput.focus();

            }

            return;

        }


        let custom =
            "";


        if (
            state.feature ===
            "custom"
        ) {

            const customBox =
                $("#sos-custom-box");

            const textarea =
                customBox
                    ? customBox.querySelector(
                        "textarea"
                    )
                    : null;

            custom =
                textarea
                    ? textarea.value.trim()
                    : "";


            if (!custom) {

                toast(
                    "Tell ASK AI what you want it to do.",
                    "error"
                );

                return;

            }

        }


        const prompt =
            buildPrompt(
                state.feature,
                material,
                custom
            );


        await requestAI(
            prompt,
            material
        );

    }


    /* =========================================================
       API REQUEST
       ========================================================= */

    function requestAI(
        prompt,
        material
    ) {

        return new Promise(
            function (
                resolve,
                reject
            ) {

                setLoading(
                    true
                );


                const endpoint =
                    CONFIG.SUPABASE_URL +
                    CONFIG.FUNCTION;


                GM_xmlhttpRequest({

                    method:
                        "POST",

                    url:
                        endpoint,

                    timeout:
                        60000,

                    headers: {

                        "Content-Type":
                            "application/json",

                        "apikey":
                            CONFIG.SUPABASE_KEY,

                        "Authorization":
                            "Bearer " +
                            CONFIG.SUPABASE_KEY,

                        "X-STUDY-OS-KEY":
                            state.accessKey

                    },

                    data:
                        JSON.stringify({

                            model:
                                CONFIG.MODEL,

                            feature:
                                state.feature,

                            prompt:
                                prompt,

                            material:
                                material

                        }),


                    onload:
                        function (response) {

                            setLoading(
                                false
                            );


                            let data;


                            try {

                                data =
                                    JSON.parse(
                                        response.responseText
                                    );

                            } catch {

                                data = {

                                    error:
                                        response.responseText

                                };

                            }


                            if (
                                response.status <
                                    200 ||
                                response.status >=
                                    300
                            ) {

                                const message =
                                    data.error ||
                                    data.message ||
                                    "Request failed: " +
                                    response.status;


                                showOutput(
                                    "ERROR\n\n" +
                                    message
                                );


                                toast(
                                    message,
                                    "error"
                                );


                                reject(
                                    new Error(
                                        message
                                    )
                                );


                                return;

                            }


                            const answer =
                                data.answer ||
                                data.response ||
                                data.content ||
                                data.message ||
                                (
                                    data.data &&
                                    (
                                        data.data.answer ||
                                        data.data.response ||
                                        data.data.content
                                    )
                                );


                            if (!answer) {

                                showOutput(
                                    "The AI returned an empty response."
                                );

                                toast(
                                    "Empty AI response.",
                                    "error"
                                );

                                reject(
                                    new Error(
                                        "Empty response"
                                    )
                                );

                                return;

                            }


                            showOutput(
                                String(answer)
                            );


                            toast(
                                "Response received.",
                                "success"
                            );


                            resolve(
                                answer
                            );

                        },


                    onerror:
                        function () {

                            setLoading(
                                false
                            );

                            showOutput(
                                "Unable to connect to STUDY//OS."
                            );

                            toast(
                                "Connection failed.",
                                "error"
                            );

                            reject(
                                new Error(
                                    "Network error"
                                )
                            );

                        },


                    ontimeout:
                        function () {

                            setLoading(
                                false
                            );

                            showOutput(
                                "The request timed out."
                            );

                            toast(
                                "Request timed out.",
                                "error"
                            );

                            reject(
                                new Error(
                                    "Timeout"
                                )
                            );

                        }

                });

            }
        );

    }


    /* =========================================================
       LOADING
       ========================================================= */

    function setLoading(
        loading
    ) {

        state.loading =
            loading;


        const button =
            $("#sos-run");

        const output =
            $("#sos-output");


        if (!button) {

            return;

        }


        if (loading) {

            button.disabled =
                true;

            button.textContent =
                "THINKING...";


            if (output) {

                output.textContent =
                    "STUDY//OS is thinking...";

            }

        } else {

            button.disabled =
                false;

            button.textContent =
                "RUN STUDY//OS";

        }

    }


    /* =========================================================
       OUTPUT
       ========================================================= */

    function showOutput(
        text
    ) {

        const output =
            $("#sos-output");


        if (!output) {

            return;

        }


        /*
         * IMPORTANT:
         *
         * textContent is intentional.
         *
         * Never inject AI output as HTML.
         */

        output.textContent =
            String(
                text || ""
            );

    }


    /* =========================================================
       TOAST
       ========================================================= */

    function toast(
        message,
        type = ""
    ) {

        if (!state.shadow) {

            return;

        }


        const old =
            state.shadow.querySelector(
                ".sos-toast"
            );


        if (old) {

            old.remove();

        }


        const element =
            create(
                "div",
                {
                    className:
                        "sos-toast " +
                        type,

                    text:
                        String(message)
                }
            );


        state.shadow.appendChild(
            element
        );


        setTimeout(
            function () {

                if (
                    element &&
                    element.isConnected
                ) {

                    element.remove();

                }

            },
            3000
        );

    }


    /* =========================================================
       INIT
       ========================================================= */

    function init() {

        if (!document.body) {

            setTimeout(
                init,
                300
            );

            return;

        }


        createRoot();


        console.log(
            "[STUDY//OS] v" +
            CONFIG.VERSION +
            " loaded."
        );

    }


    init();

})();
← Back to timeline