<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8"/>
    <meta name="viewport" contents="width=device-width, initial-scale=1.0" />
    <title>StarCoder Editor</title>
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/js-base64@3.7.2/base64.min.js"></script>

    <script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.4.14/ace.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.4.14/mode-plain_text.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.4.14/mode-c_cpp.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.4.14/mode-csharp.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.4.14/mode-clojure.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.4.14/mode-coffee.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.4.14/mode-golang.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.4.14/mode-haskell.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.4.14/mode-python.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.4.14/mode-java.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.4.14/mode-javascript.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.4.14/mode-lua.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.4.14/mode-objectivec.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.4.14/mode-perl.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.4.14/mode-php.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.4.14/mode-python.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.4.14/mode-ruby.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.4.14/mode-rust.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.4.14/mode-scala.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.4.14/mode-sh.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.4.14/mode-swift.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.4.14/mode-typescript.min.js"></script>
    <link rel="stylesheet" href="static/style.css">
</head>
<style type="text/css">
/* body {
    font-family: sans-serif;
} */
/* .leftside {
} */
main {
  max-width: 80rem;
}
.rightside {
    width: 30em;
}
.submit-holder {
    margin-top: 2em;
}
.submit input {
    font-size: 16pt;
}
.slider {
    width: 20em;
}
#faq {
    max-width: 60em;
}
#result {
    font-family: monospace;
    white-space: pre-wrap;
    word-wrap: break-word;
    font-size: 12pt;
    clear: both;
    margin-top: 1em;
    border: 1px solid black;
    padding: 1em;
    width: 60em;
    min-height: 12em;
}
#prompt {
    font-weight: bold;
}
.loader {
    border: 4px solid #f3f3f3;
    border-radius: 50%;
    border-top: 4px solid #3498db;
    width: 30px;
    height: 30px;
    animation: spin 2s linear infinite;
    margin-right: 1em;
}
@keyframes spin {
    0% { transform: rotate(0deg); }
    100% { transform: rotate(360deg); }
}
#loader_holder {
    visibility: hidden;
    display: flex;
    align-items: center;
}

label {
    margin-top: 1em;
    display: inline-elock;
    width: 10em;
    text-align: right;
    font-size: 80%;
}
#loader_holder_super {
}
#error {
    color: red;
    width: 100%;
}
#warning {
    color: darkorange;
    width: 100%;
}
#examples span {
    margin-right: 1em;
}
#editor {
    position: relative;
    width: 100%;
    height: 400px;
}
#editor-holder {
    position: relative;
    width: 100%;
    height: 400px;
}
.ace_infill {
    color: red;
}
</style>
<body>
  <main>
    <div class="card" id="about">
        <div class="header"> <h1>StarCoder Editor</h1> </div>
        <p>This is a demo to interactively generate code with <a href=https://huggingface.co/bigcode/starcoderbase target="_blank" rel="noopener noreferrer">StarCoderBase</a>, a 15B parameter model for code generation in 86 programming languages.</p>
        <p>Select one of the examples below, or input your own code into the editor. You can type &lt;infill&gt; to mark a location you want the model to insert code at.</p>
        <p>Click "Extend" to append text at the end of the editor. Click "Infill" to replace all &lt;infill&gt; masks. (Click "Add &lt;infill&gt; mask" to add a mask at the cursor or replace the current selection.) </p>
    </div>
    <div class="card" id="examples">
        <div id="examples-extend">
            <span class="softspan">Extend Examples:</span>
            <br>
            <span class="softspan"><a href='javascript:select_example("logistic-regression");'>Logistic Regression (Python)</a></span>
            <span class="softspan"><a href='javascript:select_example("array");'>Arrays (JS)</a></span>
            <span class="softspan"><a href='javascript:select_example("metadata");'>Metadata-Conditioning (JS)</a></span>
        </div>
        <div id="examples-infill">
            <span class="softspan">Infill Examples:</span>
            <br>
            <span class="softspan"><a href='javascript:select_example("type-pred");'>Type Prediction</a></span>
            <span class="softspan"><a href='javascript:select_example("docstring");'>Docstring Generation</a></span>
        </div>
    </div>
    <div class="card" id="controls">
        <div>
            <label>Num Tokens:</label>
            <input type="range" value="64" min="16" max="256" step="16" class="slider"
                oninput="this.nextElementSibling.value = this.value" name="length" id='length_slider'>
            <output class='a' id="length_slider_output">64</output>
        </div>
        <div>
            <label>Temperature:</label>
            <input type="range" value="0.6" min="0.1" max="1.0" step="0.10" class="slider"
                oninput="this.nextElementSibling.value = this.value" name="temp" id='temp_slider'>
            <output class='a' id="temp_slider_output">0.6</output>
        </div>
        <div id="buttons">
          <br>
          <input type="button" value="Extend" id="extend-form-button" />
          <input type="button" value="Infill" id="infill-form-button" />
          <br>
          <br>
          <input type="button" value="Add <infill> mask" id="insert-mask-button" title="add the infill marker at cursor or selection" />
        </div>
    </div>
    <div id="edit-container" class="card">
        <div id="syntax">
            <span class="softspan">Syntax:</span>
            <select name="mode" id="mode">
                <option value="text">Text</option>
                <option value="c_cpp">C/C++</option>
                <option value="csharp">C#</option>
                <option value="clojure">Clojure</option>
                <option value="coffee">CoffeeScript</option>
                <option value="golang">Go</option>
                <option value="haskell">Haskell</option>
                <option value="java">Java</option>
                <option value="javascript">JavaScript</option>
                <option value="lua">Lua</option>
                <option value="objectivec">Objective C</option>
                <option value="perl">Perl</option>
                <option value="php">PHP</option>
                <option value="python">Python</option>
                <option value="ruby">Ruby</option>
                <option value="rust">Rust</option>
                <option value="scala">Scala</option>
                <option value="sh">Shell</option>
                <option value="swift">Swift</option>
                <option value="typescript">Typescript</option>
            </select>
        </div>
        <div id="editor"></div>
    </div>
    <div id="loader_holder_super" class="card">
        <h1>Messages</h1>
        <div id="error"></div>
        <div id="warning"></div>
        <div id="loader_holder">
            <div class="loader"></div>
            <div>
                Generation queued, please wait...
            </div>
        </div>
    </div>
    <div id="info" class="card">
        <h1 id="debug-info">More Info</h1>
        <p>See our <a href="https://huggingface.co/bigcode" target="_blank" rel="noopener noreferrer">organization card</a> for links to the technical report, models, VSCode extension, training dataset, and more.</p>
        <p>Credits: BigCode team. This demo is based upon the <a href="https://huggingface.co/spaces/facebook/incoder-demo" target="_blank" rel="noopener noreferrer">InCoder demo<a> by Facebook AI Research.</p>
    </div>
  </main>
<script type="text/javascript">
// these constants are only used for providing user expectations.
var OVERHEAD = 3;
var PER_TOKEN = 0.12;
var SPLIT_TOKEN = "<infill>"

var Range = require("ace/range").Range;

// examples for the user
var EXAMPLES = {
    "type-pred": {
        "prompt": `def count_words(filename: str) -> <infill>
    """Count the number of occurrences of each word in the file."""
    with open(filename, 'r') as f:
        word_counts = {}
        for line in f:
            for word in line.split():
                if word in word_counts:
                    word_counts[word]  = 1
                else:
                    word_counts[word] = 1
    return word_counts
`,
        "length": 4,
        "temperature": 0.2,
        "mode": "python"
    },
    "docstring": {
        "prompt": `def _minimize_in_graph(build_loss_fn, num_steps=200, optimizer=None):
  """
  <infill>
  """
  optimizer = tf.compat.v1.train.AdamOptimizer(
      0.1) if optimizer is None else optimizer

  def train_loop_body(step):
    train_op = optimizer.minimize(
        build_loss_fn if tf.executing_eagerly() else build_loss_fn())
    return tf.tuple(tensors=[tf.add(step, 1)], control_inputs=[train_op])

  minimize_op = tf.compat.v1.while_loop(
      cond=lambda step: step < num_steps,
      body=train_loop_body,
      loop_vars=[tf.constant(0)],
      return_same_structure=True)[0] 
  return minimize_op`,
  "length": 128,
  "temperature": 0.2,
  "mode": "python",
    },
  // extend examples
    "logistic-regression": {
        "prompt": `X_train, y_train, X_test, y_test = train_test_split(X, y, test_size=0.1)

# Train a logistic regression model, predict the labels on the test set and compute the accuracy score`,
        "length": 64,
        "temperature": 0.2,
        "mode": "python"
    },
    "array": {
      "prompt": 
`// Returns every other value in the array as a new array.
function everyOther(arr) {`,
        "temperature": 0.2,
        "length": 64,
        "mode": "javascript"
    },
  "metadata": {
    "prompt": `<filename>start_server.js<gh_stars>100-1000
`,
        "temperature": 0.6,
        "length": 128,
        "mode": "javascript"
  },
};

var editor = ace.edit("editor");
editor.setOption("wrap", true);
//var editor = null;

function set_editor_mode(mode) {
    session = editor.session
    session.setMode("ace/mode/" + mode, function() {
        var rules = session.$mode.$highlightRules.getRules();
        for (var stateName in rules) {
            if (Object.prototype.hasOwnProperty.call(rules, stateName)) {
                rules[stateName].unshift({
                    token: 'infill',
                    regex: SPLIT_TOKEN 
                });
            }
        }
        // force recreation of tokenizer
        session.$mode.$tokenizer = null;
        session.bgTokenizer.setTokenizer(session.$mode.getTokenizer());
        // force re-highlight whole document
        session.bgTokenizer.start(0);
    });
}

/*
var textarea = $('textarea[name="prompt"]').hide();
var prefix_textarea = $('textarea[name="prefix"]').hide();
var suffix_textarea = $('textarea[name="suffix"]').hide();
editor.getSession().on('change', function () {
    textarea.val(editor.getSession().getValue());
});
*/

function set_text(text) {
    editor.getSession().setValue(text);
    // textarea.val(text);
}

function set_selection(data) {
    var lines = editor.getSession().doc.$lines;
    var lines_flat = join_lines(lines);
    if (data['type'] == 'generate') {
        doc_length = lines_flat.length;
        var start = convert_string_index_to_location(data['prompt'].length, lines);
        var end = convert_string_index_to_location(doc_length, lines);
        // reverse this so that we can shift select to shorten and delete extra stuff
        editor.selection.setRange(new Range(end.row, end.column, start.row, start.column));
    } else if (data['type'] == 'infill') {
        var length_so_far = 0;
        for (var i = 0; i < data['infills'].length; i++) {
            var prefix = data['parts'][i];
            var suffix = data['parts'][i+1];
            var infilled = data['infills'][i];
            var start = convert_string_index_to_location(length_so_far + prefix.length, lines);
            var end = convert_string_index_to_location(length_so_far + (prefix + infilled).length, lines);
            var range = null;
            if (data['infills'].length == 1) {
                range = new Range(end.row, end.column, start.row, start.column)
            } else {
                range = new Range(start.row, start.column, end.row, end.column)
            }
            if (i == 0) {
                editor.selection.setRange(range);
            } else {
                editor.selection.addRange(range);
            }
            length_so_far += (prefix + infilled).length;
        }
    }
    editor.focus();
}

function select_example(name) {
    $("#length_slider").val(EXAMPLES[name]["length"]);
    $("#length_slider_output").text(EXAMPLES[name]["length"]);
    $("#temp_slider").val(EXAMPLES[name]["temperature"]);
    $("#temp_slider_output").text(EXAMPLES[name]["temperature"]);
    set_text(EXAMPLES[name]["prompt"])
    var mode = EXAMPLES[name]["mode"];

    set_editor_mode(mode);
    $("#mode").val(mode).change();
}

function newline_character() {
    return editor.getSession().doc.getNewLineCharacter();
}

function join_lines(lines) {
    return lines.join(newline_character());
}

function get_prefix(location, lines) {
    if (!(location.hasOwnProperty('row') && location.hasOwnProperty('column'))) { 
        console.error("invalid location " + location);
    }
    if (location.row == 0) {
        return lines[location.row].substring(0, location.column);
    } else {
        return join_lines(lines.slice(0, location.row)) + newline_character() + lines[location.row].substring(0, location.column);
    }
}

function convert_location_to_string_index(location, lines) {
    return get_prefix(location, lines).length;
}

function convert_string_index_to_location(string_index, lines) {
    var column = 0;
    var row = 0;
    var char_count = 0;
    var line_sep_length = editor.getSession().doc.getNewLineCharacter().length;
    for (var i = 0; i < lines.length; i++) {
        var line = lines[i];
        var new_char_count = char_count + line.length + line_sep_length;
        if (string_index < new_char_count) {
            return {
                'row': i,
                'column': string_index - char_count,
            }
        }
        char_count = new_char_count;
    }
    console.error("did not find index " + string_index + " in lines " + lines);
    return null;
}

function get_infill_parts(warn_on_single) {
    var lines = editor.getSession().doc.$lines;
    var lines_flat = join_lines(lines);
    parts = lines_flat.split(SPLIT_TOKEN)
    if (warn_on_single && parts.length == 1) {
        window.alert('There are no infill masks, add some <infill> masks before requesting an infill')
    }
    return parts
}

function insert_mask() {
    if (editor.selection.ranges.length > 1) {
        for (var i = 0; i < editor.selection.ranges.length; i++) {
            console.log('range is', editor.selection.ranges[i])
            editor.session.replace(editor.selection.ranges[i], SPLIT_TOKEN)
        }
    } else {
        editor.session.replace(editor.selection.getRange(), SPLIT_TOKEN)
    }
}


function make_generate_listener(url) {
    return async function(event) {
        var length = $("#length_slider").val();
        var eta = PER_TOKEN * length  + OVERHEAD;
        // $("#eta").text(eta);
        // $("#infill-form-button").click(function (event) { console.log(editor.selection.getCursor()); });

        // get temperature and response length parameters
        var send_data = {
            length: $("#length_slider").val(),
            temperature: $("#temp_slider").val(),
            extra_sentinel: $('#extra_sentinel_checkbox').is(":checked"),
            max_retries: $('#max_retries_slider').val(),
            parts: get_infill_parts(url == "infill"),
            prompt: editor.getSession().getValue(),
        }
        console.log("send_data:");
        console.log(send_data);

        $("#loader_holder").css("visibility", "visible");
        $("#extend-form-button").prop("disabled", true);
        $("#infill-form-button").prop("disabled", true);
        $("#error").text("");

        function complete() {
            $("#loader_holder").css("visibility", "hidden");
            $("#extend-form-button").prop("disabled", false);
            $("#infill-form-button").prop("disabled", false);
        }

        function success(receive_data) {
            console.log("Response:");
            console.log(receive_data);
            if (receive_data["result"] == "success") {
                console.log("success");
                // $("#prompt").text(data["prompt"]);
                // $("#response").text(data["text"]);
                set_text(receive_data["text"]);
                set_selection(receive_data);
                $("#error").text("");
                if (receive_data["message"] != "") {
                    $("#warning").text(receive_data["message"]);
                } else {
                    $("#warning").text("");
                }
            } else {
                console.log("error");
                console.log("data:");
                console.log(receive_data);
                set_text(receive_data["text"])
                $("#error").text(receive_data["message"]);
            }
        }

        function error(err) {
            console.log(err);
            $("#error").text(err);
        }

        try {
            var stringified = JSON.stringify(send_data);
            // var encoded_data = encodeURIComponent(btoa(stringified));
            var encoded_data = Base64.encodeURI(stringified);

            const response = await fetch(`${url}?info=${encoded_data}`);
            // const response = await fetch(`${url}` {
            //     method: 'GET',
            //     body: encoded_data,
            // });
            if (response.status >= 400) {
                error(response.statusText);
                console.log("here");
                console.log(response.status);
            } else {
                response.json().then(success).catch(error).finally(complete);
            }
        } catch (e) {
            error(e);
        } finally {
            complete();
        }
    }
}

// actual logic
$(document).ready(function() {
    $("#insert-mask-button").click(insert_mask);
    $("#extend-form-button").click(make_generate_listener("generate"));
    $("#infill-form-button").click(make_generate_listener("infill"));
    $("#mode").change(function (e) {
        var mode = $("#mode").val();
        set_editor_mode(mode);
    });
    select_example("python")
    // set_editor_mode("python");
});
</script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/iframe-resizer/4.3.2/iframeResizer.contentWindow.min.js"></script>
</body>
</html>