mirror of
https://github.com/simon987/keysmash.git
synced 2025-04-03 08:23:06 +00:00
Initial commit (UI)
This commit is contained in:
parent
2f963dfced
commit
f2a4a0e8c8
19
keysmash/README.md
Normal file
19
keysmash/README.md
Normal file
@ -0,0 +1,19 @@
|
||||
# keysmash
|
||||
|
||||
## Project setup
|
||||
```
|
||||
yarn install
|
||||
```
|
||||
|
||||
### Compiles and hot-reloads for development
|
||||
```
|
||||
yarn serve
|
||||
```
|
||||
|
||||
### Compiles and minifies for production
|
||||
```
|
||||
yarn build
|
||||
```
|
||||
|
||||
### Customize configuration
|
||||
See [Configuration Reference](https://cli.vuejs.org/config/).
|
5
keysmash/babel.config.js
Normal file
5
keysmash/babel.config.js
Normal file
@ -0,0 +1,5 @@
|
||||
module.exports = {
|
||||
presets: [
|
||||
'@vue/cli-plugin-babel/preset'
|
||||
]
|
||||
}
|
26298
keysmash/package-lock.json
generated
Normal file
26298
keysmash/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
33
keysmash/package.json
Normal file
33
keysmash/package.json
Normal file
@ -0,0 +1,33 @@
|
||||
{
|
||||
"name": "keysmash",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"serve": "vue-cli-service serve",
|
||||
"build": "vue-cli-service build"
|
||||
},
|
||||
"dependencies": {
|
||||
"@tensorflow/tfjs": "^3.15.0",
|
||||
"axios": "^0.26.1",
|
||||
"core-js": "^3.6.5",
|
||||
"seedrandom": "^2.4.4",
|
||||
"vue": "^2.6.11",
|
||||
"vuetify": "^2.6.0",
|
||||
"vuex": "^3.4.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vue/cli-plugin-babel": "~4.5.13",
|
||||
"@vue/cli-plugin-vuex": "~4.5.13",
|
||||
"@vue/cli-service": "~4.5.13",
|
||||
"sass": "~1.32.0",
|
||||
"sass-loader": "^10.0.0",
|
||||
"vue-cli-plugin-vuetify": "~2.4.8",
|
||||
"vue-template-compiler": "^2.6.11",
|
||||
"vuetify-loader": "^1.7.0"
|
||||
},
|
||||
"browserslist": [
|
||||
"> 1%",
|
||||
"last 2 versions",
|
||||
"not dead"
|
||||
]
|
||||
}
|
17
keysmash/public/index.html
Normal file
17
keysmash/public/index.html
Normal file
@ -0,0 +1,17 @@
|
||||
<!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>Keyboard smash detector</title>
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@mdi/font@latest/css/materialdesignicons.min.css">
|
||||
</head>
|
||||
<body>
|
||||
<noscript>
|
||||
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
|
||||
</noscript>
|
||||
<div id="app"></div>
|
||||
</body>
|
||||
</html>
|
117
keysmash/src/App.vue
Normal file
117
keysmash/src/App.vue
Normal file
@ -0,0 +1,117 @@
|
||||
<template>
|
||||
<v-app>
|
||||
<v-main>
|
||||
|
||||
<v-container>
|
||||
<v-card>
|
||||
<v-card-title>Bottom keyboard smash detector</v-card-title>
|
||||
<v-card-text>
|
||||
<v-textarea
|
||||
v-model="message"
|
||||
rows="2" outlined
|
||||
label="Type message here"
|
||||
clearable
|
||||
no-resize
|
||||
counter
|
||||
:loading="!initialized || computing"
|
||||
:disabled="!initialized"
|
||||
:counter-value="x => `${x ? x.length : 0}/96`"
|
||||
:rules="[v => v.length <= 96 || 'Max 96 characters']"
|
||||
>
|
||||
</v-textarea>
|
||||
|
||||
<v-btn @click="doPrediction()">Predict</v-btn>
|
||||
|
||||
<div class="mb-5"></div>
|
||||
|
||||
<v-alert
|
||||
v-if="prediction > 0.5"
|
||||
prominent
|
||||
outlined
|
||||
type="warning"
|
||||
border="left"
|
||||
>
|
||||
<template #prepend>
|
||||
<PleadingEmoji></PleadingEmoji>
|
||||
</template>
|
||||
|
||||
You're a bottom
|
||||
</v-alert>
|
||||
|
||||
<v-alert
|
||||
v-else-if="prediction !== 0"
|
||||
prominent
|
||||
outlined
|
||||
type="info"
|
||||
border="left"
|
||||
>
|
||||
<template #prepend>
|
||||
<RobotEmoji></RobotEmoji>
|
||||
</template>
|
||||
|
||||
You're a robot
|
||||
</v-alert>
|
||||
|
||||
</v-card-text>
|
||||
|
||||
</v-card>
|
||||
</v-container>
|
||||
</v-main>
|
||||
</v-app>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import BottomKeySmashClassifier from "@/model";
|
||||
import PleadingEmoji from "@/components/PleadingEmoji";
|
||||
import RobotEmoji from "@/components/RobotEmoji";
|
||||
|
||||
export default {
|
||||
name: 'App',
|
||||
|
||||
components: {
|
||||
RobotEmoji,
|
||||
PleadingEmoji
|
||||
},
|
||||
|
||||
data: () => ({
|
||||
message: "",
|
||||
initialized: false,
|
||||
computing: false,
|
||||
model: null,
|
||||
prediction: 0,
|
||||
}),
|
||||
|
||||
created() {
|
||||
this.model = new BottomKeySmashClassifier();
|
||||
|
||||
this.model.init().then(() => {
|
||||
this.initialized = true;
|
||||
});
|
||||
},
|
||||
|
||||
methods: {
|
||||
doPrediction() {
|
||||
this.predict().then(p => {
|
||||
this.prediction = p;
|
||||
})
|
||||
},
|
||||
async predict() {
|
||||
this.computing = true;
|
||||
const result = await this.model.predict(this.message);
|
||||
this.computing = false;
|
||||
|
||||
console.log(result)
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
@media (min-width: 1904px) {
|
||||
.container {
|
||||
max-width: 1185px;
|
||||
}
|
||||
}
|
||||
</style>
|
35
keysmash/src/components/PleadingEmoji.vue
Normal file
35
keysmash/src/components/PleadingEmoji.vue
Normal file
@ -0,0 +1,35 @@
|
||||
<template>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36">
|
||||
<circle fill="#FFCC4D" cx="18" cy="18" r="18"/>
|
||||
<path fill="#65471B"
|
||||
d="M20.996 27c-.103 0-.206-.016-.309-.049-1.76-.571-3.615-.571-5.375 0-.524.169-1.089-.117-1.26-.642-.171-.525.117-1.089.643-1.26 2.162-.702 4.447-.702 6.609 0 .525.171.813.735.643 1.26-.137.421-.529.691-.951.691z"/>
|
||||
<path fill="#FFF"
|
||||
d="M30.335 12.068c-.903 2.745-3.485 4.715-6.494 4.715-.144 0-.289-.005-.435-.014-1.477-.093-2.842-.655-3.95-1.584.036.495.076.997.136 1.54.152 1.388.884 2.482 2.116 3.163.82.454 1.8.688 2.813.752 1.734.109 3.57-.28 4.873-.909 1.377-.665 2.272-1.862 2.456-3.285.183-1.415-.354-2.924-1.515-4.378z"/>
|
||||
<path fill="#65471B"
|
||||
d="M21.351 7.583c-1.297.55-1.947 2.301-1.977 5.289l.039.068c.897 1.319 2.373 2.224 4.088 2.332.114.007.228.011.341.011 2.634 0 4.849-1.937 5.253-4.524-.115-.105-.221-.212-.343-.316-3.715-3.17-6.467-3.257-7.401-2.86z"/>
|
||||
<path fill="#F4900C"
|
||||
d="M23.841 16.783c3.009 0 5.591-1.97 6.494-4.715-.354-.443-.771-.88-1.241-1.309-.404 2.587-2.619 4.524-5.253 4.524-.113 0-.227-.004-.341-.011-1.715-.108-3.191-1.013-4.088-2.332l-.039-.068c-.007.701.021 1.473.083 2.313 1.108.929 2.473 1.491 3.95 1.584.146.01.291.014.435.014z"/>
|
||||
<circle fill="#FFF" cx="21.413" cy="10.705" r="1.107"/>
|
||||
<path fill="#FFF"
|
||||
d="M12.159 16.783c-3.009 0-5.591-1.97-6.494-4.715-1.161 1.454-1.697 2.963-1.515 4.377.185 1.423 1.079 2.621 2.456 3.285 1.303.629 3.138 1.018 4.873.909 1.013-.064 1.993-.297 2.813-.752 1.231-.681 1.963-1.775 2.116-3.163.06-.542.1-1.042.136-1.536-1.103.923-2.47 1.487-3.95 1.58-.146.011-.291.015-.435.015z"/>
|
||||
<path fill="#65471B"
|
||||
d="M12.159 15.283c.113 0 .227-.004.341-.011 1.715-.108 3.191-1.013 4.088-2.332l.039-.068c-.031-2.988-.68-4.739-1.977-5.289-.934-.397-3.687-.31-7.401 2.859-.122.104-.227.211-.343.316.404 2.588 2.619 4.525 5.253 4.525z"/>
|
||||
<path fill="#F4900C"
|
||||
d="M16.626 12.872l-.039.068c-.897 1.319-2.373 2.224-4.088 2.332-.114.007-.228.011-.341.011-2.634 0-4.849-1.937-5.253-4.524-.47.429-.887.866-1.241 1.309.903 2.745 3.485 4.715 6.494 4.715.144 0 .289-.005.435-.014 1.48-.093 2.847-.657 3.95-1.58.062-.841.091-1.614.083-2.317z"/>
|
||||
<path fill="#FFF"
|
||||
d="M9.781 11.81c.61-.038 1.074-.564 1.035-1.174-.038-.61-.564-1.074-1.174-1.036-.61.038-1.074.564-1.036 1.174.039.61.565 1.074 1.175 1.036z"/>
|
||||
</svg>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "PleadingEmoji"
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
svg {
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
}
|
||||
</style>
|
37
keysmash/src/components/RobotEmoji.vue
Normal file
37
keysmash/src/components/RobotEmoji.vue
Normal file
@ -0,0 +1,37 @@
|
||||
<template>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36">
|
||||
<ellipse fill="#F4900C" cx="33.5" cy="14.5" rx="2.5" ry="3.5"/>
|
||||
<ellipse fill="#F4900C" cx="2.5" cy="14.5" rx="2.5" ry="3.5"/>
|
||||
<path fill="#FFAC33"
|
||||
d="M34 19c0 .553-.447 1-1 1h-3c-.553 0-1-.447-1-1v-9c0-.552.447-1 1-1h3c.553 0 1 .448 1 1v9zM7 19c0 .553-.448 1-1 1H3c-.552 0-1-.447-1-1v-9c0-.552.448-1 1-1h3c.552 0 1 .448 1 1v9z"/>
|
||||
<path fill="#FFCC4D" d="M28 5c0 2.761-4.478 4-10 4C12.477 9 8 7.761 8 5s4.477-5 10-5c5.522 0 10 2.239 10 5z"/>
|
||||
<path fill="#F4900C"
|
||||
d="M25 4.083C25 5.694 21.865 7 18 7c-3.866 0-7-1.306-7-2.917 0-1.611 3.134-2.917 7-2.917 3.865 0 7 1.306 7 2.917z"/>
|
||||
<path fill="#269"
|
||||
d="M30 5.5C30 6.881 28.881 7 27.5 7h-19C7.119 7 6 6.881 6 5.5S7.119 3 8.5 3h19C28.881 3 30 4.119 30 5.5z"/>
|
||||
<path fill="#55ACEE" d="M30 6H6c-1.104 0-2 .896-2 2v26h28V8c0-1.104-.896-2-2-2z"/>
|
||||
<path fill="#3B88C3"
|
||||
d="M35 33v-1c0-1.104-.896-2-2-2H22.071l-3.364 3.364c-.391.391-1.023.391-1.414 0L13.929 30H3c-1.104 0-2 .896-2 2v1c0 1.104-.104 2 1 2h32c1.104 0 1-.896 1-2z"/>
|
||||
<circle fill="#FFF" cx="24.5" cy="14.5" r="4.5"/>
|
||||
<circle fill="#DD2E44" cx="24.5" cy="14.5" r="2.721"/>
|
||||
<circle fill="#FFF" cx="11.5" cy="14.5" r="4.5"/>
|
||||
<path fill="#F5F8FA"
|
||||
d="M29 25.5c0 1.381-1.119 2.5-2.5 2.5h-17C8.119 28 7 26.881 7 25.5S8.119 23 9.5 23h17c1.381 0 2.5 1.119 2.5 2.5z"/>
|
||||
<path fill="#CCD6DD"
|
||||
d="M17 23h2v5h-2zm-5 0h2v5h-2zm10 0h2v5h-2zM7 25.5c0 1.21.859 2.218 2 2.45v-4.9c-1.141.232-2 1.24-2 2.45zm20-2.45v4.899c1.141-.232 2-1.24 2-2.45s-.859-2.217-2-2.449z"/>
|
||||
<circle fill="#DD2E44" cx="11.5" cy="14.5" r="2.721"/>
|
||||
</svg>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "RobotEmoji"
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
svg {
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
}
|
||||
</style>
|
12
keysmash/src/main.js
Normal file
12
keysmash/src/main.js
Normal file
@ -0,0 +1,12 @@
|
||||
import Vue from 'vue'
|
||||
import App from './App.vue'
|
||||
import store from './store'
|
||||
import vuetify from './plugins/vuetify'
|
||||
|
||||
Vue.config.productionTip = false
|
||||
|
||||
new Vue({
|
||||
store,
|
||||
vuetify,
|
||||
render: h => h(App)
|
||||
}).$mount('#app')
|
32
keysmash/src/model.js
Normal file
32
keysmash/src/model.js
Normal file
@ -0,0 +1,32 @@
|
||||
import * as tf from '@tensorflow/tfjs';
|
||||
import axios from "axios";
|
||||
|
||||
|
||||
export default class BottomKeySmashClassifier {
|
||||
|
||||
constructor() {
|
||||
self._maxLen = 96;
|
||||
}
|
||||
|
||||
async init() {
|
||||
self._model = await tf.loadLayersModel('/tfjs_model/model.json');
|
||||
const resp = await axios.get("/tokens.json");
|
||||
self._tokens = resp.data;
|
||||
}
|
||||
|
||||
async predict(message) {
|
||||
message = message.toLowerCase();
|
||||
const inputIds = Array.from(message).map(c => self._tokens[c]);
|
||||
|
||||
// padding
|
||||
while (inputIds.length !== self._maxLen) {
|
||||
inputIds.push(0);
|
||||
}
|
||||
|
||||
const tensor = tf.tensor(inputIds, [1, 96], "int32");
|
||||
|
||||
let prediction = self._model.predict(tensor)
|
||||
|
||||
return (await prediction.array())[0][1]
|
||||
}
|
||||
}
|
7
keysmash/src/plugins/vuetify.js
Normal file
7
keysmash/src/plugins/vuetify.js
Normal file
@ -0,0 +1,7 @@
|
||||
import Vue from 'vue';
|
||||
import Vuetify from 'vuetify/lib/framework';
|
||||
|
||||
Vue.use(Vuetify);
|
||||
|
||||
export default new Vuetify({
|
||||
});
|
15
keysmash/src/store/index.js
Normal file
15
keysmash/src/store/index.js
Normal file
@ -0,0 +1,15 @@
|
||||
import Vue from 'vue'
|
||||
import Vuex from 'vuex'
|
||||
|
||||
Vue.use(Vuex)
|
||||
|
||||
export default new Vuex.Store({
|
||||
state: {
|
||||
},
|
||||
mutations: {
|
||||
},
|
||||
actions: {
|
||||
},
|
||||
modules: {
|
||||
}
|
||||
})
|
BIN
keysmash/static/tfjs_model/group1-shard1of1.bin
Normal file
BIN
keysmash/static/tfjs_model/group1-shard1of1.bin
Normal file
Binary file not shown.
BIN
keysmash/static/tfjs_model/group1-shard1of4.bin
Normal file
BIN
keysmash/static/tfjs_model/group1-shard1of4.bin
Normal file
Binary file not shown.
BIN
keysmash/static/tfjs_model/group1-shard2of4.bin
Normal file
BIN
keysmash/static/tfjs_model/group1-shard2of4.bin
Normal file
Binary file not shown.
BIN
keysmash/static/tfjs_model/group1-shard3of4.bin
Normal file
BIN
keysmash/static/tfjs_model/group1-shard3of4.bin
Normal file
Binary file not shown.
BIN
keysmash/static/tfjs_model/group1-shard4of4.bin
Normal file
BIN
keysmash/static/tfjs_model/group1-shard4of4.bin
Normal file
Binary file not shown.
1
keysmash/static/tfjs_model/model.json
Normal file
1
keysmash/static/tfjs_model/model.json
Normal file
File diff suppressed because one or more lines are too long
1
keysmash/static/tokens.json
Normal file
1
keysmash/static/tokens.json
Normal file
@ -0,0 +1 @@
|
||||
{"UNK": 1, "d": 2, "f": 3, "j": 4, "s": 5, "h": 6, "k": 7, "g": 8, "l": 9, "a": 10, "i": 11, "n": 12, "o": 13, "u": 14, "b": 15, "e": 16, ";": 17, "r": 18, "w": 19, "c": 20, "v": 21, "p": 22, " ": 23, "t": 24, "y": 25, "m": 26, ",": 27, "z": 28, "'": 29, "q": 30, "0": 31, "9": 32, "x": 33, ".": 34, "]": 35, "[": 36, "/": 37, ":": 38, "-": 39, "#": 40, ">": 41, "7": 42, "\\": 43, "2": 44, "4": 45, "\u00e1": 46, "\u2018": 47, "<": 48, "?": 49, "!": 50, "8": 51, "=": 52, "5": 53, "6": 54, "3": 55, ")": 56}
|
5
keysmash/vue.config.js
Normal file
5
keysmash/vue.config.js
Normal file
@ -0,0 +1,5 @@
|
||||
module.exports = {
|
||||
transpileDependencies: [
|
||||
'vuetify'
|
||||
]
|
||||
}
|
8439
keysmash/yarn.lock
Normal file
8439
keysmash/yarn.lock
Normal file
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user