<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>paper-hero</title> <link rel="stylesheet" href="https://unpkg.com/boltcss/bolt.min.css"> <script type="importmap"> { "imports": { "vue": "https://unpkg.com/vue@3/dist/vue.esm-browser.js" } } </script> <style> body { max-width: 800px; margin: 40px auto; padding: 0 20px; } .form-group { display: flex; flex-direction: row; justify-content: flex-start; align-items: center; } label { margin-right: 1rem; } button { margin: 0.2rem 0.2rem; } button:hover { background-color: #dbdbdb; } footer { text-align: center; margin-top: 2rem; } .button-group { margin-top: 3rem; } .search-button { background-color: #ffc83d; color: #d67d00; font-weight: bold; } .search-button:hover { background-color: #ffc83dc0; } .download-button { background-color: #98ca56; color: white; font-weight: bold; } .download-button:hover { background-color: #98ca56d1; } .output-title { margin-top: 2rem; margin-bottom: 0; display: block; background-color: #98ca56; color: white; font-weight: bold; font-size: large; padding: 6px 15px; border-top-left-radius: 6px; border-top-right-radius: 6px; } .output-box { margin-top: 0; padding: 6px 15px; background-color: white; border: 2px solid #98ca56; border-bottom-left-radius: 6px; border-bottom-right-radius: 6px; } </style> </head> <body> <header> <h1>💪 Paper Hero</h1> <p> Paper Hero is a toolkit to help search for papers from aclanthology, arXiv and dblp. </p> <p>GitHub Address: <a href="https://github.com/Spico197/paper-hero" target="_blank">Spico197/paper-hero</a></p> </header> <main> <div id="app"> <div class="form-group"> <label for="method"><strong>Source</strong></label> <select id="method" v-model="method"> <option value="" disabled>Please select a source</option> <option value="aclanthology">ACL Anthology</option> <option value="arxiv">ArXiv</option> <option value="dblp">DBLP</option> </select> </div> <div> <label for="max-res"><strong>Max Results</strong></label> <input id="max-res" type="number" v-model="maxResults"> </div> <div class="form-group"> <label for="add-field"><strong>New Field</strong></label> <select id="add-field" v-model="addField"> <option value="" disabled>Please select a field</option> <option :value="field" v-for="field in restFields">{{ field }}</option> </select> <button @click.prevent="addNewField">Add</button> </div> <hr> <div> <p><strong>Fields</strong></p> <p> Add <code>&&</code> to represent <code>AND</code> logic, e.g. <code>span-based && event extraction</code> means <em>span-based</em> and <em>event extraction</em> both appear in a field. </p> <p> For <code>year</code> and <code>month</code> fields, the query should follow the <code>start && end</code> format, e.g. year <code>2006 && 2013</code> means searching for papers published between <code>2006</code> and <code>2013</code>. </p> <div v-for="(groups, field) in query"> <label :for="field"><strong>{{ field }}</strong></label> <div v-for="(group, index) in groups"> <input class="field-input" type="text" v-model="query[field][index]" placeholder="text1 && text2 && text3" size="50"> <button @click.prevent="rmAnd(field, index)">X</button> </div> <button @click.prevent="addOr(field)">OR</button> </div> </div> <div v-if="timerHandler"> <p>⏱️ {{ searchSecondsTwoDecimal }}</p> </div> <div v-if="output"> <p class="output-title">Output Info</p> <p class="output-box"> {{ output }} <br> You are ready to download the results by clicking the download button below. <br> Like this tool? ⭐ me on <a href="https://github.com/Spico197/paper-hero" target="_blank">GitHub</a> ! </p> </div> <div class="button-group"> <button @click.prevent="resetQuery">Reset</button> <button class="search-button" @click.prevent="search">Search</button> <a :href="downloadHref" :download="`${method}.json`" v-if="downloadHref"> <button class="search-button download-button">Download</button> </a> </div> </div> </main> <footer> <hr> Made by <a href="https://spico197.github.io" target="_blank">Tong Zhu</a> w/ 💖 </footer> <script type="module"> import { createApp, ref, computed, toRaw, watch } from 'vue' createApp( { setup() { const method = ref("aclanthology") const query = ref({ title: [[]] }) const maxResults = ref(2000) const addField = ref("") const allFields = ["title", "author", "abstract", "venue", "year", "month"] const downloadUrl = ref('') const downloadToken = ref('') const downloadHref = ref('') const output = ref('') const timerHandler = ref(0) const searchSeconds = ref(0.0) const searchSecondsTwoDecimal = computed(() => { return `${searchSeconds.value.toFixed(1)}s` }) const restFields = computed(() => { let rest = [] for (const field of allFields) { if (!(field in query.value)) { rest.push(field) } } return rest }) function addNewField() { if (addField.value) { query.value[addField.value] = [[]] addField.value = "" } } function rmAnd(field, index) { if (query.value[field].length == 1) { delete query.value[field] } else { query.value[field].splice(index, 1) } } function addOr(field) { query.value[field].push([]) } function resetOutput() { output.value = "" downloadUrl.value = "" downloadToken.value = "" URL.revokeObjectURL(downloadHref.value) downloadHref.value = "" searchSeconds.value = 0.0 timerHandler.value = 0 searchSecondsTwoDecimal.value = "" } function resetQuery() { query.value = { title: [[]] } resetOutput() } function startTimer() { console.log("start") timerHandler.value = setInterval(() => { searchSeconds.value += 0.1 }, 100) } function endTimer() { console.log("end") if (timerHandler.value > 0) { console.log("endi") clearInterval(timerHandler.value) } } function search() { resetOutput() startTimer() let q = {} for (const prop in query.value) { q[prop] = [] for (let i = 0; i < query.value[prop].length; i++) { if (query.value[prop][i].length > 0) { let andString = toRaw(query.value[prop][i]) let andStrings = andString.split('&&') for (let j = 0; j < andStrings.length; j++) { andStrings[j] = andStrings[j].trim() } q[prop].push(andStrings) } } if (q[prop].length < 1) { delete q[prop] } } const postData = JSON.stringify({ "method": method.value, "query": q, "max_results": maxResults.value, "return_content": false, }) fetch( "/api/", { method: "POST", headers: { 'Content-Type': 'application/json', }, body: postData, } ) .then((response) => response.json()) .then((json) => { if (json["ok"] === false) { alert(json["msg"]) } else { downloadUrl.value = json["url"] downloadToken.value = json["token"] output.value = `${json["msg"]}, #matched paper: ${json["paper"]}` } }) .catch((err) => { alert(err) }) .finally(() => endTimer()) } watch(downloadUrl, (newUrl, oldUrl) => { if (downloadToken.value) { fetch( `/download/?u=${downloadUrl.value}&t=${downloadToken.value}`, { method: "GET", } ) .then((response) => response.blob()) .then((data) => { downloadHref.value = URL.createObjectURL(data) }) } }) return { method, query, restFields, addField, addNewField, search, rmAnd, addOr, resetQuery, maxResults, output, downloadUrl, downloadHref, searchSeconds, timerHandler, searchSecondsTwoDecimal, } } } ).mount("#app") </script> </body> </html>