User:Anpang/Wikipe-tan Chatbox.js

From Wikipedia, the free encyclopedia
Note: After saving, you have to bypass your browser's cache to see the changes. Google Chrome, Firefox, Microsoft Edge and Safari: Hold down the ⇧ Shift key and click the Reload toolbar button. For details and instructions about other browsers, see Wikipedia:Bypass your cache.
// get css
importStylesheet("User:Anpang01/Wikipe-tan_Chatbox.css");

// add box
const wptcGreeting = "👋 Hello! What would you like me to help with?";

$("body").append(`
    <div class="wptc-box" id="wptc-box">
        <div>
            <img src="https://upload.wikimedia.org/wikipedia/commons/a/ac/Wikipe-tan_head.png" class="wptc-image" />
            <span class="wptc-name">Wikipe-tan</span>
            <a href="#" class="wptc-close" id="wptc-close">&times;</a>
        </div>
        <div class="wptc-inner-box">
            <div>
                <div id="wptc-bubbles">
            		<div class="wptc-bubble-left">
                    	<span>${wptcGreeting}</span>
                	</div>
                </div>
                <input type="text" class="wptc-input" id="wptc-input" />
            </div>
        </div>
        <span class="wptc-bottom-links">
            &ensp;
            <a href="https://en.wikipedia.org/wiki/User:Anpang01/Wikipe-tan_Chatbox">JS script</a>
            &ensp;
            <a href="https://en.wikipedia.org/wiki/Wikipedia:Wikipe-tan">Based on Wikipe-tan</a>
            &ensp;
        </span>
    </div>
`);

// close box handler
$("#wptc-close").on("click", function() {
	$("#wptc-box").hide();
});

// conversation data
const wptcConvers = [
    [
        "Hello!",
        "Hello {username}! What would you like me to help with?",
        "Nothing, actually.",
        "Okay, I'll just stay here in case you need my help."
    ],
    [
    	"What skin does Wikipedia use?",
    	"Wikipedia uses the Vector skin, and this page uses the {skin} skin."
    ],
    [
    	"What skin am I using?",
    	"You are using the {skin} skin."
    ],
    [
    	"What is the name of this website?",
    	"This is the English {sitename}."
    ]
];
const wptcDontKnow = [
    "Sorry, I couldn't understand you.",
    "Sorry, what do you mean by that?",
    "Sorry {username}, I couldn't understand that."
];
const wptcVariables = {
    "username": mw.config.get("wgUserName"),
    "skin": `${mw.config.get("skin")[0].toUpperCase()}${mw.config.get("skin").slice(1)}`.replace("-", " "),
    "sitename": mw.config.get("wgSiteName")
}

// helper functions
function wptcRandomElement(array) {
    return array[Math.floor(Math.random() * array.length)];
}

// from https://github.com/saniales/gestalt-pattern-matcher/blob/master/index.ts
function wptcGestaltSimilarity(first, second) {
    let stack = [first, second];
    let score = 0;
    
    while(stack.length != 0) {
        const first_sub_string = stack.pop();
        const second_sub_string = stack.pop();
        
        let longest_sequence_length = 0;
        let longest_sequence_index_1 = -1;
        let longest_sequence_index_2 = -1;

        for(let i = 0; i < first_sub_string.length; i++) {
            for(let j = 0; j < second_sub_string.length; j++) {
                let k = 0;
                while(i+k < first_sub_string.length && j+k < second_sub_string.length && first_sub_string.charAt(i+k) === second_sub_string.charAt(j+k)) {
                    k++;
                }
                if(k > longest_sequence_length) {
                    longest_sequence_length = k;
                    longest_sequence_index_1 = i;
                    longest_sequence_index_2 = j;
                }
            }
        }
    
        if(longest_sequence_length > 0) {
            score += longest_sequence_length * 2;
            if(longest_sequence_index_1 !== 0 && longest_sequence_index_2 !== 0) {
                stack.push(first_sub_string.substring(0, longest_sequence_index_1));
                stack.push(second_sub_string.substring(0, longest_sequence_index_2));
            }
            if(longest_sequence_index_1 + longest_sequence_length !== first_sub_string.length && 
                longest_sequence_index_2 + longest_sequence_length !== second_sub_string.length) {
                stack.push(first_sub_string.substring(longest_sequence_index_1 + longest_sequence_length, first_sub_string.length));
                stack.push(second_sub_string.substring(longest_sequence_index_2 + longest_sequence_length, second_sub_string.length));
            }
        }
    }
    
    return score / (first.length + second.length);
}

// get response
function wptcGetResponse(input) {
    contenders = [];
    highestScore = 0;

    for(const conver of wptcConvers) {
        for(const [index, message] of conver.entries()) {
            score = wptcGestaltSimilarity(message, input);

            if(conver.length == (index + 1)) {
                continue;
            }
            nextMessage = conver[index + 1];
            
            if(score > highestScore) {
                contenders = [nextMessage];
                highestScore = score;
            } else if(score == highestScore) {
                contenders.push(nextMessage);
            }
        }
    }

    let response = wptcRandomElement(highestScore < 0.3? wptcDontKnow: contenders);
    for(const [name, value] of Object.entries(wptcVariables)) {
        response = response.replace(`{${name}}`, value);
    }
    return response;
}

// submit handler
$("#wptc-input").keypress(function(key) {
    if(key.which === 13) { // is enter key
        // get and clear input
        let input = $("#wptc-input").val();
        $("#wptc-input").val("");
        
        // do stuff
        $("#wptc-bubbles").append(`
            <div class="wptc-bubble-right">
                <span>${input}</span>
            </div>
            <div class="wptc-bubble-left">
                <span>${wptcGetResponse(input)}</span>
            </div>
        `);
    }
});