├── .babelrc ├── .editorconfig ├── .electron-vue ├── build.js ├── dev-client.js ├── dev-runner.js ├── webpack.main.config.js └── webpack.renderer.config.js ├── .eslintignore ├── .eslintrc.yml ├── .gitignore ├── .stylelintrc.yml ├── .travis.yml ├── README.md ├── appveyor.yml ├── build └── icons │ ├── 256x256.png │ ├── icon.icns │ └── icon.ico ├── dist ├── electron │ └── .gitkeep └── web │ └── .gitkeep ├── package-lock.json ├── package.json ├── resources └── suggestions.txt ├── screenshots ├── screenshot-1.jpg ├── screenshot-2.jpg ├── screenshot-3.JPG └── screenshot-merged.jpg ├── src ├── assistant.ejs ├── main │ └── index.js ├── renderer │ ├── assets │ │ └── .gitkeep │ ├── config.js │ ├── containers │ │ ├── assistant.vue │ │ └── response.vue │ ├── index.js │ └── providers │ │ ├── assistant │ │ ├── assistant.js │ │ ├── index.js │ │ ├── microphone.js │ │ └── player.js │ │ ├── authentication │ │ ├── authentication.js │ │ ├── google-electron-auth.js │ │ └── index.js │ │ ├── commands │ │ ├── assistant.js │ │ ├── audio-control.js │ │ ├── index.js │ │ ├── spotify.js │ │ └── youtube.js │ │ ├── router │ │ └── index.js │ │ ├── spotify │ │ ├── index.js │ │ └── spotify.js │ │ └── store │ │ ├── assistant.js │ │ ├── index.js │ │ └── store.js └── response.ejs ├── static ├── .gitkeep ├── bubbles.png ├── hotword_enrollment_confirm.png ├── ic_mic.png ├── icon.ico ├── icon.png ├── loading.gif ├── molecule.svg ├── opa_logo_big.png ├── ping.mp3 ├── powered_by_google_dark.png ├── powered_by_google_light.png ├── quantum_ic_devices_other_grey600_24.png ├── recording.gif ├── splash.html ├── v.png └── waiting.svg └── test ├── .eslintrc ├── e2e ├── index.js ├── specs │ └── Launch.spec.js └── utils.js └── unit ├── index.js └── karma.conf.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "@babel/preset-env", 5 | { 6 | "targets": { 7 | "chrome": "66", 8 | "node": 8 9 | } 10 | } 11 | ] 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = tab 6 | end_of_line = lf 7 | insert_final_newline = true 8 | trim_trailing_whitespace = true 9 | 10 | [*.md] 11 | trim_trailing_whitespace = false 12 | 13 | [*.yml] 14 | indent_size = 2 15 | indent_style = space 16 | -------------------------------------------------------------------------------- /.electron-vue/build.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | process.env.NODE_ENV = 'production' 4 | 5 | const { say } = require('cfonts') 6 | const chalk = require('chalk') 7 | const del = require('del') 8 | const { spawn } = require('child_process') 9 | const webpack = require('webpack') 10 | const Multispinner = require('multispinner') 11 | 12 | 13 | const mainConfig = require('./webpack.main.config') 14 | const rendererConfig = require('./webpack.renderer.config') 15 | 16 | const doneLog = chalk.bgGreen.white(' DONE ') + ' ' 17 | const errorLog = chalk.bgRed.white(' ERROR ') + ' ' 18 | const okayLog = chalk.bgBlue.white(' OKAY ') + ' ' 19 | const isCI = process.env.CI || false 20 | 21 | if (process.env.BUILD_TARGET === 'clean') clean() 22 | else build() 23 | 24 | function clean () { 25 | del.sync(['build/*', '!build/icons', '!build/icons/icon.*']) 26 | console.log(`\n${doneLog}\n`) 27 | process.exit() 28 | } 29 | 30 | function build () { 31 | greeting() 32 | 33 | del.sync(['dist/electron/*', '!.gitkeep']) 34 | 35 | const tasks = ['main', 'renderer'] 36 | const m = new Multispinner(tasks, { 37 | preText: 'building', 38 | postText: 'process' 39 | }) 40 | 41 | let results = '' 42 | 43 | m.on('success', () => { 44 | process.stdout.write('\x1B[2J\x1B[0f') 45 | console.log(`\n\n${results}`) 46 | console.log(`${okayLog}take it away ${chalk.yellow('`electron-builder`')}\n`) 47 | process.exit() 48 | }) 49 | 50 | pack(mainConfig).then(result => { 51 | results += result + '\n\n' 52 | m.success('main') 53 | }).catch(err => { 54 | m.error('main') 55 | console.log(`\n ${errorLog}failed to build main process`) 56 | console.error(`\n${err}\n`) 57 | process.exit(1) 58 | }) 59 | 60 | pack(rendererConfig).then(result => { 61 | results += result + '\n\n' 62 | m.success('renderer') 63 | }).catch(err => { 64 | m.error('renderer') 65 | console.log(`\n ${errorLog}failed to build renderer process`) 66 | console.error(`\n${err}\n`) 67 | process.exit(1) 68 | }) 69 | } 70 | 71 | function pack (config) { 72 | return new Promise((resolve, reject) => { 73 | webpack(config, (err, stats) => { 74 | if (err) reject(err.stack || err) 75 | else if (stats.hasErrors()) { 76 | let err = '' 77 | 78 | stats.toString({ 79 | chunks: false, 80 | colors: true 81 | }) 82 | .split(/\r?\n/) 83 | .forEach(line => { 84 | err += ` ${line}\n` 85 | }) 86 | 87 | reject(err) 88 | } else { 89 | resolve(stats.toString({ 90 | chunks: false, 91 | colors: true 92 | })) 93 | } 94 | }) 95 | }) 96 | } 97 | 98 | function greeting () { 99 | const cols = process.stdout.columns 100 | let text = '' 101 | 102 | if (cols > 85) text = 'lets-build' 103 | else if (cols > 60) text = 'lets-|build' 104 | else text = false 105 | 106 | if (text && !isCI) { 107 | say(text, { 108 | colors: ['yellow'], 109 | font: 'simple3d', 110 | space: false 111 | }) 112 | } else console.log(chalk.yellow.bold('\n lets-build')) 113 | console.log() 114 | } 115 | -------------------------------------------------------------------------------- /.electron-vue/dev-client.js: -------------------------------------------------------------------------------- 1 | 2 | const hotClient = require('webpack-hot-middleware/client?noInfo=true&reload=true') 3 | 4 | hotClient.subscribe((/* event */) => { 5 | /** 6 | * Reload browser when HTMLWebpackPlugin emits a new index.html 7 | * 8 | * Currently disabled until jantimon/html-webpack-plugin#680 is resolved. 9 | * https://github.com/SimulatedGREG/electron-vue/issues/437 10 | * https://github.com/jantimon/html-webpack-plugin/issues/680 11 | */ 12 | // if (event.action === 'reload') { 13 | // window.location.reload() 14 | // } 15 | }) 16 | -------------------------------------------------------------------------------- /.electron-vue/dev-runner.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const chalk = require('chalk') 4 | const electron = require('electron') 5 | const path = require('path') 6 | const { say } = require('cfonts') 7 | const { spawn } = require('child_process') 8 | const webpack = require('webpack') 9 | const WebpackDevServer = require('webpack-dev-server') 10 | const webpackHotMiddleware = require('webpack-hot-middleware') 11 | 12 | const mainConfig = require('./webpack.main.config') 13 | const rendererConfig = require('./webpack.renderer.config') 14 | 15 | let electronProcess = null 16 | let manualRestart = false 17 | let hotMiddleware 18 | 19 | function logStats(proc, data) { 20 | let log = '' 21 | 22 | log += chalk.yellow.bold(`┏ ${proc} Process ${new Array((19 - proc.length) + 1).join('-')}`) 23 | log += '\n\n' 24 | 25 | if (typeof data === 'object') { 26 | data.toString({ 27 | colors: true, 28 | chunks: false 29 | }).split(/\r?\n/).forEach(line => { 30 | log += ' ' + line + '\n' 31 | }) 32 | } else { 33 | log += ` ${data}\n` 34 | } 35 | 36 | log += '\n' + chalk.yellow.bold(`┗ ${new Array(28 + 1).join('-')}`) + '\n' 37 | 38 | console.log(log) 39 | } 40 | 41 | function startRenderer() { 42 | return new Promise((resolve, reject) => { 43 | rendererConfig.entry.renderer = [path.join(__dirname, 'dev-client')].concat(rendererConfig.entry.renderer) 44 | 45 | const compiler = webpack(rendererConfig) 46 | hotMiddleware = webpackHotMiddleware(compiler, { 47 | log: false, 48 | heartbeat: 2500 49 | }) 50 | 51 | compiler.plugin('compilation', compilation => { 52 | compilation.plugin('html-webpack-plugin-after-emit', (data, cb) => { 53 | hotMiddleware.publish({ action: 'reload' }) 54 | cb() 55 | }) 56 | }) 57 | 58 | compiler.plugin('done', stats => { 59 | logStats('Renderer', stats) 60 | }) 61 | 62 | const server = new WebpackDevServer( 63 | compiler, 64 | { 65 | contentBase: path.join(__dirname, '../'), 66 | quiet: true, 67 | before(app, ctx) { 68 | app.use(hotMiddleware) 69 | ctx.middleware.waitUntilValid(() => { 70 | resolve() 71 | }) 72 | } 73 | } 74 | ) 75 | 76 | server.listen(9080) 77 | }) 78 | } 79 | 80 | function startMain() { 81 | return new Promise((resolve, reject) => { 82 | // mainConfig.entry.main = [path.join(__dirname, '../src/main/index.js')].concat(mainConfig.entry.main) 83 | 84 | const compiler = webpack(mainConfig) 85 | 86 | compiler.plugin('watch-run', (compilation, done) => { 87 | logStats('Main', chalk.white.bold('compiling...')) 88 | hotMiddleware.publish({ action: 'compiling' }) 89 | done() 90 | }) 91 | 92 | compiler.watch({}, (err, stats) => { 93 | if (err) { 94 | console.log(err) 95 | return 96 | } 97 | 98 | logStats('Main', stats) 99 | 100 | if (electronProcess && electronProcess.kill) { 101 | manualRestart = true 102 | process.kill(electronProcess.pid) 103 | electronProcess = null 104 | startElectron() 105 | 106 | setTimeout(() => { 107 | manualRestart = false 108 | }, 5000) 109 | } 110 | 111 | resolve() 112 | }) 113 | }) 114 | } 115 | 116 | function startElectron() { 117 | electronProcess = spawn(electron, ['--inspect=5858', path.join(__dirname, '../dist/electron/main.js')]) 118 | 119 | electronProcess.stdout.on('data', data => { 120 | electronLog(data, 'blue') 121 | }) 122 | electronProcess.stderr.on('data', data => { 123 | electronLog(data, 'red') 124 | }) 125 | 126 | electronProcess.on('close', () => { 127 | if (!manualRestart) process.exit() 128 | }) 129 | } 130 | 131 | function electronLog(data, color) { 132 | let log = '' 133 | data = data.toString().split(/\r?\n/) 134 | data.forEach(line => { 135 | log += ` ${line}\n` 136 | }) 137 | if (/[0-9A-z]+/.test(log)) { 138 | console.log( 139 | chalk[color].bold('┏ Electron -------------------') + 140 | '\n\n' + 141 | log + 142 | chalk[color].bold('┗ ----------------------------') + 143 | '\n' 144 | ) 145 | } 146 | } 147 | 148 | function greeting() { 149 | const cols = process.stdout.columns 150 | let text = '' 151 | 152 | if (cols > 104) text = 'Assistant|Desktop'; 153 | else if (cols > 76) text = 'Assistant-|Desktop' 154 | else text = false 155 | 156 | if (text) { 157 | say(text, { 158 | colors: ['yellow'], 159 | font: 'simple', 160 | space: false 161 | }) 162 | } else console.log(chalk.yellow.bold('\n electron-vue')) 163 | console.log(chalk.blue(' getting ready...') + '\n') 164 | } 165 | 166 | function init() { 167 | greeting() 168 | 169 | Promise.all([startRenderer(), startMain()]) 170 | .then(() => { 171 | startElectron() 172 | }) 173 | .catch(err => { 174 | console.error(err) 175 | }) 176 | } 177 | 178 | init() 179 | -------------------------------------------------------------------------------- /.electron-vue/webpack.main.config.js: -------------------------------------------------------------------------------- 1 | process.env.BABEL_ENV = 'main' 2 | 3 | const path = require('path') 4 | const { dependencies } = require('../package.json') 5 | 6 | /* eslint-disable*/ 7 | const webpack = require('webpack') 8 | /* eslint-enable */ 9 | 10 | const mainConfig = { 11 | mode: process.env.NODE_ENV, 12 | entry: { 13 | main: path.join(__dirname, '../src/main/index.js'), 14 | }, 15 | externals: [...Object.keys(dependencies || {})], 16 | module: { 17 | rules: [ 18 | { 19 | test: /\.js$/, 20 | use: 'babel-loader', 21 | exclude: /node_modules/, 22 | }, 23 | { 24 | test: /\.node$/, 25 | use: 'node-loader', 26 | }, 27 | ], 28 | }, 29 | node: { 30 | __dirname: process.env.NODE_ENV !== 'production', 31 | __filename: process.env.NODE_ENV !== 'production', 32 | }, 33 | output: { 34 | filename: '[name].js', 35 | libraryTarget: 'commonjs2', 36 | path: path.join(__dirname, '../dist/electron'), 37 | }, 38 | // plugins: [new webpack.NoEmitOnErrorsPlugin()], 39 | resolve: { 40 | extensions: ['.js', '.json', '.node'], 41 | }, 42 | target: 'electron-main', 43 | } 44 | 45 | module.exports = mainConfig 46 | -------------------------------------------------------------------------------- /.electron-vue/webpack.renderer.config.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | process.env.BABEL_ENV = 'renderer' 4 | 5 | const path = require('path') 6 | const { dependencies } = require('../package.json') 7 | const webpack = require('webpack') 8 | 9 | const CopyWebpackPlugin = require('copy-webpack-plugin') 10 | const VueLoaderPlugin = require('vue-loader/lib/plugin') 11 | // const MiniCssExtractPlugin = require('mini-css-extract-plugin') 12 | const HtmlWebpackPlugin = require('html-webpack-plugin') 13 | 14 | /** 15 | * List of node_modules to include in webpack bundle 16 | * 17 | * Required for specific packages like Vue UI libraries 18 | * that provide pure *.vue files that need compiling 19 | * https://simulatedgreg.gitbooks.io/electron-vue/content/en/webpack-configurations.html#white-listing-externals 20 | */ 21 | let whiteListedModules = ['vue', 'vuetify'] 22 | 23 | let rendererConfig = { 24 | mode: 'none', 25 | entry: { 26 | renderer: path.join(__dirname, '../src/renderer/index.js') 27 | }, 28 | externals: [ 29 | ...Object.keys(dependencies || {}).filter(d => !whiteListedModules.includes(d)) 30 | ], 31 | module: { 32 | rules: [ 33 | { 34 | test: /\.scss$/, 35 | use: ['vue-style-loader', 'css-loader', 'sass-loader'], 36 | }, 37 | { 38 | test: /\.sass$/, 39 | use: ['vue-style-loader', 'css-loader', 'sass-loader?indentedSyntax'], 40 | }, 41 | { 42 | test: /\.less$/, 43 | use: ['vue-style-loader', 'css-loader', 'less-loader'], 44 | }, 45 | { 46 | test: /\.css$/, 47 | use: ['vue-style-loader', 'css-loader'], 48 | }, 49 | { 50 | test: /\.html$/, 51 | use: 'vue-html-loader', 52 | }, 53 | { 54 | test: /\.js$/, 55 | use: 'babel-loader', 56 | exclude: /node_modules/, 57 | }, 58 | { 59 | test: /\.node$/, 60 | use: 'node-loader', 61 | }, 62 | { 63 | test: /\.vue$/, 64 | use: { 65 | loader: 'vue-loader', 66 | options: { 67 | extractCSS: process.env.NODE_ENV === 'production', 68 | loaders: { 69 | sass: 'vue-style-loader!css-loader!sass-loader?indentedSyntax=1', 70 | scss: 'vue-style-loader!css-loader!sass-loader', 71 | less: 'vue-style-loader!css-loader!less-loader', 72 | }, 73 | }, 74 | }, 75 | }, 76 | { 77 | test: /\.(png|jpe?g|gif|svg)(\?.*)?$/, 78 | use: { 79 | loader: 'url-loader', 80 | query: { 81 | limit: 10000, 82 | name: 'imgs/[name]--[folder].[ext]', 83 | }, 84 | }, 85 | }, 86 | { 87 | test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/, 88 | loader: 'url-loader', 89 | options: { 90 | limit: 10000, 91 | name: 'media/[name]--[folder].[ext]', 92 | }, 93 | }, 94 | { 95 | test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/, 96 | use: { 97 | loader: 'url-loader', 98 | query: { 99 | limit: 10000, 100 | name: 'fonts/[name]--[folder].[ext]', 101 | }, 102 | }, 103 | }, 104 | ], 105 | }, 106 | node: { 107 | __dirname: process.env.NODE_ENV !== 'production', 108 | __filename: process.env.NODE_ENV !== 'production' 109 | }, 110 | plugins: [ 111 | new HtmlWebpackPlugin({ 112 | filename: 'assistant.html', 113 | template: path.resolve(__dirname, '../src/assistant.ejs'), 114 | nodeModules: process.env.NODE_ENV !== 'production' 115 | ? path.resolve(__dirname, '../node_modules') 116 | : false 117 | }), 118 | new HtmlWebpackPlugin({ 119 | filename: 'response.html', 120 | template: path.resolve(__dirname, '../src/response.ejs'), 121 | nodeModules: process.env.NODE_ENV !== 'production' 122 | ? path.resolve(__dirname, '../node_modules') 123 | : false 124 | }), 125 | new VueLoaderPlugin(), 126 | new webpack.HotModuleReplacementPlugin(), 127 | // new webpack.NoEmitOnErrorsPlugin(), 128 | ], 129 | output: { 130 | filename: '[name].js', 131 | libraryTarget: 'commonjs2', 132 | path: path.join(__dirname, '../dist/electron') 133 | }, 134 | resolve: { 135 | alias: { 136 | '@': path.join(__dirname, '../src/renderer'), 137 | vue$: path.resolve(__dirname, '../node_modules/vue/dist/vue.esm.js'), 138 | }, 139 | extensions: ['.js', '.vue', '.json', '.css', '.node'], 140 | modules: [ 141 | path.resolve(__dirname, '../node_modules'), 142 | path.resolve(__dirname, '../src/renderer'), 143 | ], 144 | }, 145 | target: 'electron-renderer' 146 | } 147 | 148 | /** 149 | * Adjust rendererConfig for production settings 150 | */ 151 | if (process.env.NODE_ENV === 'production') { 152 | rendererConfig.devtool = '' 153 | 154 | rendererConfig.plugins.push( 155 | new CopyWebpackPlugin([ 156 | { 157 | from: path.join(__dirname, '../static'), 158 | to: path.join(__dirname, '../dist/electron/static'), 159 | } 160 | ]), 161 | ) 162 | } 163 | 164 | module.exports = rendererConfig 165 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | test/unit/coverage/** 2 | test/unit/*.js 3 | test/e2e/*.js 4 | -------------------------------------------------------------------------------- /.eslintrc.yml: -------------------------------------------------------------------------------- 1 | --- 2 | parserOptions: 3 | parser: babel-eslint 4 | 5 | extends: 6 | - airbnb-base 7 | - plugin:vue/recommended 8 | 9 | settings: 10 | import/resolver: 11 | webpack: 12 | config: ./.electron-vue/webpack.renderer.config.js 13 | import/core-modules: [ electron ] 14 | 15 | rules: 16 | import/extensions: 17 | - error 18 | - on 19 | - js: off 20 | vue: off 21 | 22 | indent: off 23 | indent-legacy: 24 | - error 25 | - tab 26 | 27 | no-tabs: off 28 | max-len: 29 | - warn 30 | - 120 31 | 32 | no-console: off 33 | 34 | no-undef: error 35 | no-unused-vars: error 36 | 37 | no-underscore-dangle: 38 | - error 39 | - allow: [ __static ] 40 | 41 | # Possible Errors 42 | vue/no-dupe-keys: error 43 | vue/no-reserved-keys: error 44 | vue/no-shared-component-data: error 45 | vue/no-unused-vars: error 46 | 47 | vue/require-valid-default-prop: error 48 | vue/return-in-computed-property: error 49 | 50 | 51 | # Best Practices 52 | vue/no-duplicate-attributes: error 53 | vue/no-side-effects-in-computed-properties: error 54 | vue/order-in-components: error 55 | 56 | # Stylistic Issues 57 | vue/attribute-hyphenation: error 58 | vue/html-indent: 59 | - error 60 | - tab 61 | vue/html-quotes: error 62 | vue/html-self-closing: 63 | - error 64 | - html: 65 | normal: always 66 | void: always 67 | component: always 68 | vue/max-attributes-per-line: error 69 | vue/mustache-interpolation-spacing: error 70 | vue/name-property-casing: 71 | - error 72 | - PascalCase 73 | vue/no-multi-spaces: error 74 | vue/v-bind-style: error 75 | vue/v-on-style: error 76 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | dist/electron/* 3 | dist/web/* 4 | build/* 5 | !build/icons 6 | coverage 7 | node_modules/ 8 | npm-debug.log 9 | npm-debug.log.* 10 | thumbs.db 11 | !.gitkeep 12 | .env 13 | client_secret*.json 14 | -------------------------------------------------------------------------------- /.stylelintrc.yml: -------------------------------------------------------------------------------- 1 | --- 2 | extends: 3 | - stylelint-config-standard 4 | - stylelint-config-concentric 5 | 6 | plugins: 7 | - stylelint-scss 8 | 9 | processors: 10 | - '@mapbox/stylelint-processor-arbitrary-tags' 11 | 12 | rules: 13 | at-rule-no-unknown: null 14 | declaration-empty-line-before: null 15 | indentation: tab 16 | no-empty-source: null 17 | no-invalid-double-slash-comments: null 18 | number-leading-zero: never 19 | property-no-unknown: 20 | - true 21 | - ignoreProperties: 22 | - '//' 23 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # Commented sections below can be used to run tests on the CI server 2 | # https://simulatedgreg.gitbooks.io/electron-vue/content/en/testing.html#on-the-subject-of-ci-testing 3 | osx_image: xcode8.3 4 | sudo: required 5 | dist: trusty 6 | language: c 7 | matrix: 8 | include: 9 | - os: osx 10 | - os: linux 11 | env: CC=clang CXX=clang++ npm_config_clang=1 12 | compiler: clang 13 | cache: 14 | directories: 15 | - node_modules 16 | - "$HOME/.electron" 17 | - "$HOME/.cache" 18 | addons: 19 | apt: 20 | packages: 21 | - libgnome-keyring-dev 22 | - icnsutils 23 | #- xvfb 24 | before_install: 25 | - mkdir -p /tmp/git-lfs && curl -L https://github.com/github/git-lfs/releases/download/v1.2.1/git-lfs-$([ 26 | "$TRAVIS_OS_NAME" == "linux" ] && echo "linux" || echo "darwin")-amd64-1.2.1.tar.gz 27 | | tar -xz -C /tmp/git-lfs --strip-components 1 && /tmp/git-lfs/git-lfs pull 28 | - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then sudo apt-get install --no-install-recommends -y icnsutils graphicsmagick xz-utils; fi 29 | install: 30 | #- export DISPLAY=':99.0' 31 | #- Xvfb :99 -screen 0 1024x768x24 > /dev/null 2>&1 & 32 | - nvm install 7 33 | - curl -o- -L https://yarnpkg.com/install.sh | bash 34 | - source ~/.bashrc 35 | - npm install -g xvfb-maybe 36 | - yarn 37 | script: 38 | #- xvfb-maybe node_modules/.bin/karma start test/unit/karma.conf.js 39 | #- yarn run pack && xvfb-maybe node_modules/.bin/mocha test/e2e 40 | - yarn run build 41 | branches: 42 | only: 43 | - master 44 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Google Assistant for Desktop 2 | 3 | Google Assistant for Desktop is an cross-platform assistant powered by the Google Assistant SDK, it has support for the official HTML5 screen output. 4 | 5 | ![Screenshot of Google Assistant Desktop Client](/screenshots/screenshot-3.JPG?raw=true "Full html5 window preview") 6 | 7 | ## Features 8 | 9 | * Google Assistant SDK v1alpha2 10 | * 'Transparent' HTML5 screen output overlay 11 | * Develop your own commands, trigger ```ask(question) // Promise``` or ```say(query)``` functions to let the assistant do whatever you want. 12 | * Build in basic Spotify Web API support as a demo. 13 | 14 | ### Shortcut 15 | 16 | Since hotword integration is not included yet, I've included a shortcut that triggers the assistant: 17 | 18 | * Windows: Ctrl+Alt+A 19 | * Mac OS: Control+Option+A 20 | 21 | You'll hear a sound when you can start speaking almost instantly. 22 | 23 | #### NOTE 24 | 25 | This assistant desktop app is made for developers and hackers alike who like to play around with the SDK's and software. It will probably never be released as a stand-alone app that you can download since Google will probably never give us production API keys for a desktop client. 26 | 27 | ## TODO 28 | 29 | * Update README configuration steps for key generation and creating basic commands yourself. 30 | 31 | ## Configuration 32 | 33 | There's a couple of things that need to be done in order to run this application properly. 34 | 35 | First of all you need your own Google Cloud account, since the Google Assistant SDK is still in alpha, google allows only 500 api calls everyday, the trial version will suffice since the Google Assistant SDK is still in alpha. 36 | 37 | Follow the steps on this page; 38 | https://developers.google.com/assistant/sdk/guides/library/python/embed/config-dev-project-and-account 39 | 40 | After you've got your `client_secret_.json` file rename it to `client_secret.json` and put it in the `src/renderer` folder. 41 | 42 | ## Build Setup 43 | 44 | ``` bash 45 | # install dependencies 46 | npm install # Install dev mode 47 | npm start # 48 | ``` 49 | 50 | ## Used Libraries / Boilerplates 51 | 52 | * [Google Assistant (A node.js implementation of the Google Assistant SDK)](https://github.com/endoplasmic/google-assistant) 53 | ** Connection to the Google Assistant SDK Service 54 | * [A Node.js wrapper for Spotify's Web API](https://github.com/thelinmichael/spotify-web-api-node) 55 | ** Used for integrating custom commands for Spotify 56 | * [A voice control - voice commands - speech recognition and speech synthesis javascript library.](https://github.com/sdkcarlos/artyom.js) 57 | ** Used for text processing custom voice commands (The Google Assistant SDK does the actual transcription) 58 | 59 | --- 60 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | # Commented sections below can be used to run tests on the CI server 2 | # https://simulatedgreg.gitbooks.io/electron-vue/content/en/testing.html#on-the-subject-of-ci-testing 3 | version: 0.1.{build} 4 | 5 | branches: 6 | only: 7 | - master 8 | 9 | image: Visual Studio 2017 10 | platform: 11 | - x64 12 | 13 | cache: 14 | - node_modules 15 | - '%APPDATA%\npm-cache' 16 | - '%USERPROFILE%\.electron' 17 | - '%USERPROFILE%\AppData\Local\Yarn\cache' 18 | 19 | init: 20 | - git config --global core.autocrlf input 21 | 22 | install: 23 | - ps: Install-Product node 8 x64 24 | - choco install yarn --ignore-dependencies 25 | - git reset --hard HEAD 26 | - yarn 27 | - node --version 28 | 29 | build_script: 30 | #- yarn test 31 | - yarn build 32 | 33 | test: off 34 | -------------------------------------------------------------------------------- /build/icons/256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ItsWendell/google-assistant-desktop-client/9b775bc3e4d8da8c99fb33cb9452ec3019f413a5/build/icons/256x256.png -------------------------------------------------------------------------------- /build/icons/icon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ItsWendell/google-assistant-desktop-client/9b775bc3e4d8da8c99fb33cb9452ec3019f413a5/build/icons/icon.icns -------------------------------------------------------------------------------- /build/icons/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ItsWendell/google-assistant-desktop-client/9b775bc3e4d8da8c99fb33cb9452ec3019f413a5/build/icons/icon.ico -------------------------------------------------------------------------------- /dist/electron/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ItsWendell/google-assistant-desktop-client/9b775bc3e4d8da8c99fb33cb9452ec3019f413a5/dist/electron/.gitkeep -------------------------------------------------------------------------------- /dist/web/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ItsWendell/google-assistant-desktop-client/9b775bc3e4d8da8c99fb33cb9452ec3019f413a5/dist/web/.gitkeep -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ga-desktop", 3 | "version": "0.4.2", 4 | "author": "Wendell Misiedjan", 5 | "description": "A Google Assistant desktop Client", 6 | "license": "n/a", 7 | "main": "./dist/electron/main.js", 8 | "scripts": { 9 | "build": "node .electron-vue/build.js && electron-builder", 10 | "build:dir": "node .electron-vue/build.js && electron-builder --dir", 11 | "build:noasar": "node .electron-vue/build.js && electron-builder --dir --asar=false", 12 | "build:clean": "cross-env BUILD_TARGET=clean node .electron-vue/build.js", 13 | "dev": "cross-env NODE_ENV=development node .electron-vue/dev-runner.js", 14 | "e2e": "npm run pack && mocha test/e2e", 15 | "lint": "eslint ./src/renderer/ --ext js,vue --max-warnings 0", 16 | "lint:fix": "eslint ./src/renderer/ --ext js,vue --fix", 17 | "pack": "npm run pack:main && npm run pack:renderer", 18 | "pack:main": "cross-env NODE_ENV=production webpack --progress --colors --config .electron-vue/webpack.main.config.js", 19 | "pack:renderer": "cross-env NODE_ENV=production webpack --progress --colors --config .electron-vue/webpack.renderer.config.js", 20 | "test": "npm run unit && npm run e2e", 21 | "unit": "karma start test/unit/karma.conf.js", 22 | "postinstall": "npm rebuild --target=4.0.3 --runtime=electron", 23 | "start": "npm run dev" 24 | }, 25 | "build": { 26 | "productName": "Google Assistant for Desktop", 27 | "appId": "org.digispark.ga-desktop", 28 | "directories": { 29 | "output": "build" 30 | }, 31 | "files": [ 32 | "dist/electron/**/*" 33 | ], 34 | "dmg": { 35 | "contents": [ 36 | { 37 | "x": 410, 38 | "y": 150, 39 | "type": "link", 40 | "path": "/Applications" 41 | }, 42 | { 43 | "x": 130, 44 | "y": 150, 45 | "type": "file" 46 | } 47 | ] 48 | }, 49 | "mac": { 50 | "icon": "build/icons/icon.icns" 51 | }, 52 | "win": { 53 | "icon": "build/icons/icon.ico" 54 | }, 55 | "linux": { 56 | "icon": "build/icons/" 57 | } 58 | }, 59 | "dependencies": { 60 | "@babel/runtime-corejs2": "^7.0.0", 61 | "artyom.js": "^1.0.6", 62 | "axios": "^0.18.0", 63 | "co": "^4.6.0", 64 | "dotenv": "^6.0.0", 65 | "electron-rebuild": "^1.8.2", 66 | "eslint-plugin-pug": "^1.1.1", 67 | "fs": "0.0.1-security", 68 | "google-assistant": "github:WMisiedjan/google-assistant#master", 69 | "googleapis": "^34.0.0", 70 | "grpc": "^1.18.0", 71 | "jquery": "^3.3.1", 72 | "metaget": "^1.0.6", 73 | "mkdirp": "^0.5.1", 74 | "msr": "^1.3.4", 75 | "node-fetch": "^2.2.0", 76 | "opn": "^5.4.0", 77 | "path": "^0.12.7", 78 | "pcm-convert": "^1.6.5", 79 | "perfect-scrollbar": "^1.4.0", 80 | "pump": "^3.0.0", 81 | "pumpify": "^1.5.1", 82 | "spotify-web-api-node": "^4.0.0", 83 | "spotify-web-helper": "^1.14.0", 84 | "v-bar": "^3.0.0", 85 | "vue": "^2.5.17", 86 | "vue-electron": "^1.0.6", 87 | "vue-perfect-scrollbar": "^0.1.0", 88 | "vue-router": "^3.0.1", 89 | "vuetify": "^1.2.5", 90 | "vuex": "^3.0.1" 91 | }, 92 | "devDependencies": { 93 | "@babel/core": "^7.2.2", 94 | "@babel/polyfill": "^7.2.5", 95 | "@babel/preset-env": "^7.3.1", 96 | "@babel/register": "^7.0.0", 97 | "@mapbox/stylelint-processor-arbitrary-tags": "^0.2.0", 98 | "babel-eslint": "^10.0.0", 99 | "babel-loader": "^8.0.5", 100 | "babel-minify-webpack-plugin": "^0.3.1", 101 | "babel-plugin-istanbul": "^5.0.1", 102 | "bootstrap-sass": "^3.4.0", 103 | "cfonts": "^2.4.2", 104 | "chai": "^4.2.0", 105 | "chalk": "^2.4.2", 106 | "copy-webpack-plugin": "^4.6.0", 107 | "cross-env": "^5.2.0", 108 | "css-loader": "^1.0.0", 109 | "del": "^3.0.0", 110 | "devtron": "^1.4.0", 111 | "electron": "4.0.3", 112 | "electron-builder": "^20.38.5", 113 | "electron-debug": "^2.1.0", 114 | "electron-devtools-installer": "^2.2.4", 115 | "electron-rebuild": "^1.8.4", 116 | "eslint": "^5.13.0", 117 | "eslint-config-airbnb-base": "^13.1.0", 118 | "eslint-import-resolver-webpack": "^0.10.1", 119 | "eslint-loader": "^2.1.2", 120 | "eslint-plugin-import": "^2.16.0", 121 | "eslint-plugin-vue": "^4.7.1", 122 | "file-loader": "^2.0.0", 123 | "html-webpack-plugin": "^3.2.0", 124 | "inject-loader": "^4.0.1", 125 | "karma": "^3.1.4", 126 | "karma-chai": "^0.1.0", 127 | "karma-coverage": "^1.1.2", 128 | "karma-electron": "^6.0.0", 129 | "karma-mocha": "^1.3.0", 130 | "karma-sourcemap-loader": "^0.3.7", 131 | "karma-spec-reporter": "^0.0.32", 132 | "karma-webpack": "^3.0.5", 133 | "mini-css-extract-plugin": "^0.4.5", 134 | "mocha": "^5.2.0", 135 | "multispinner": "^0.2.1", 136 | "node-loader": "^0.6.0", 137 | "require-dir": "^1.2.0", 138 | "spectron": "^5.0.0", 139 | "style-loader": "^0.23.0", 140 | "stylelint": "^9.10.1", 141 | "stylelint-config-concentric": "^2.0.2", 142 | "stylelint-config-standard": "^18.2.0", 143 | "stylelint-scss": "^3.5.3", 144 | "stylelint-webpack-plugin": "^0.10.5", 145 | "stylus": "^0.54.5", 146 | "stylus-loader": "^3.0.2", 147 | "url-loader": "^1.1.1", 148 | "vue-html-loader": "^1.2.4", 149 | "vue-loader": "^15.6.2", 150 | "vue-style-loader": "^4.1.2", 151 | "vue-template-compiler": "^2.6.5", 152 | "webpack": "^4.29.3", 153 | "webpack-dev-server": "^3.1.14", 154 | "webpack-hot-middleware": "^2.24.2", 155 | "webpack-merge": "^4.2.1" 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /resources/suggestions.txt: -------------------------------------------------------------------------------- 1 | 2 | Who is Roger Federer? 3 | How old is Adam Sandler? 4 | Where was Ashton Kutcher born? 5 | Who is Tiger Woods married to? 6 | Who is Uma Thurman's sister? 7 | Who wrote Gone with the Wind? 8 | Who invented the mobile phone? 9 | When is the sunrise in South Loy? 10 | What is the time zone in Rosaside? 11 | What time is it in Brandtview? 12 | What is the time at home? 13 | What's the weather like? 14 | Do I need a jacket for tomorrow? 15 | What's the weather going to be like in West Cedrick this afternoon? 16 | What's the temperature outside? 17 | Is there a chance of rain today? 18 | What's the Motorola stock price? 19 | What's Volkswagen trending at? 20 | What's 500 months in numbers? 21 | Convert 507 miles to yards? 22 | What's 569 rupees to dollars? 23 | What's the tip for 198 dollars? 24 | Square root of 7579? 25 | What's 9271 divided by 6518? 26 | What's 40 percent of 3293? 27 | What's 40 percent of 5617 divided by 3976? 28 | Open youtube.com 29 | Open Slack 30 | Increase brightness 31 | Take a picture 32 | Take a selfie 33 | Turn on WiFi 34 | Turn on flashlight 35 | Record a video 36 | Increase volume 37 | Set the volume to 6 38 | Set the volume to full 39 | Mute the volume 40 | Define nemo 41 | What's the definition of open-source? 42 | What's the meaning of pixel? 43 | Set an alarm in 29 minutes 44 | Set an alarm for 09:24 AM 45 | Set a repeating alarm for 03:06 AM 46 | Set an alarm for 09:21 PM labeled Pick kids from school 47 | Set a repeating alarm at 12:59 PM for Saturday 48 | Show me my alarms 49 | When is my next alarm? 50 | Wake me up at 03:22 AM everyday 51 | New meeting 52 | Schedule an event grandmas birthday tonight at 02:36 PM 53 | What's my next appointment? 54 | Show me the appointments for this afternoon 55 | What does my day look like on Sunday? 56 | Show me my bills 57 | Where is my package? 58 | Where's my hotel? 59 | Show me restaurants near my hotel 60 | Add eggs and oranges to my shopping list. 61 | Make a note: feed the dog 62 | Note to self: feed the dog 63 | Find Monte Anderson's number 64 | When is Calista Kunde's birthday? 65 | Call Concepcion Abernathy 66 | Call Bobbie Ondricka on speakerphone 67 | Call the nearest gas station 68 | Call McDonalds 69 | Show me my messages 70 | Text Delta Emard What are you doing 71 | Send an email to my brother Hey, you didn't give me feedback on the document 72 | Send a Whatsapp message to my father 73 | Post to Facebook 74 | Say I want to eat something in French? 75 | Translate Can I have water to Chinese? 76 | Add a reminder 77 | Remind me to download the new Foo Fighters album at 11:42 AM 78 | Remind me to drink more water next time i'm at the gym 79 | Remind me to buy eggs at Wegmans 80 | Remind me to buy wet wipes at pharmacy 81 | Remind me to organize cables every Thursday 82 | Map of Greenland 83 | Show me nearest restaurants on map 84 | Navigate to Hansbury by car 85 | Navigate to my sister's place 86 | How far is North Emilia from Mariettaport? 87 | Walking directions to Tower Bridge 88 | What are some attractions around here? 89 | Show me popular museums in Africa 90 | Where is Eifell Tower? 91 | Is Big Ben open now? 92 | When does Van Gogh Museum close? 93 | Is Eifell Tower open on Thursday afternoon? 94 | Distance from here to Tower Bridge 95 | How far away is Cameroon? 96 | How is Manchester United doing? 97 | Results from Manchester City last game 98 | When is the next Manchester City game 99 | Did Tottenham Hotspur win the last game? 100 | Flight AA 120 101 | Flight status of AA 120 102 | Has LH 210 landed? 103 | When will AA 125 land? 104 | Go to ABCNews 105 | Browse androidauthority.com 106 | When was Jupiter Ascending released? 107 | Runtime of Inside Out? 108 | Listen to TV 109 | Who is the producer of Minions? 110 | Who acted in Jurassic World? 111 | Best movies of 2005 112 | Best drama movies 113 | Drama movies of 2002 114 | Oscar winners of 2010 115 | What are the best Anna Raadsveld movies 116 | What movies are playing on Thursday? 117 | Where is In the Heart of the Sea playing? 118 | Do a barrel roll 119 | What's the loneliest number? 120 | Make me a sandwich 121 | sudo Make me a sandwich 122 | When am i? 123 | Okay Jarvis 124 | Who are you? 125 | How much would could a woodchuck chuck if a woodchuck could chuck wood? 126 | Beam me up Scotty 127 | How can entropy be reversed? 128 | What's Anna Raadsveld's bacon number 129 | Tell me a joke 130 | Up, up, down, down, left, right, left, right 131 | Who's on first? 132 | Go go gadget Dialer 133 | Askew 134 | Roll a dice 135 | Flip a coin 136 | Play some music 137 | What's this song? 138 | What song am i listening to? 139 | Listen to my gym playlist 140 | Next song 141 | Pause song 142 | Listen to pop 143 | Play Lady Gaga 144 | Play Love In Vain by Rolling Stones 145 | Listen to the By the way album 146 | Listen to AC/DC 147 | Set timer for 20 minutes 148 | Start countdown 149 | -------------------------------------------------------------------------------- /screenshots/screenshot-1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ItsWendell/google-assistant-desktop-client/9b775bc3e4d8da8c99fb33cb9452ec3019f413a5/screenshots/screenshot-1.jpg -------------------------------------------------------------------------------- /screenshots/screenshot-2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ItsWendell/google-assistant-desktop-client/9b775bc3e4d8da8c99fb33cb9452ec3019f413a5/screenshots/screenshot-2.jpg -------------------------------------------------------------------------------- /screenshots/screenshot-3.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ItsWendell/google-assistant-desktop-client/9b775bc3e4d8da8c99fb33cb9452ec3019f413a5/screenshots/screenshot-3.JPG -------------------------------------------------------------------------------- /screenshots/screenshot-merged.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ItsWendell/google-assistant-desktop-client/9b775bc3e4d8da8c99fb33cb9452ec3019f413a5/screenshots/screenshot-merged.jpg -------------------------------------------------------------------------------- /src/assistant.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ga-desktop 6 | <% if (htmlWebpackPlugin.options.nodeModules) { %> 7 | 8 | 20 | <% } %> 21 | 22 | 23 |
24 |
25 | 26 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /src/main/index.js: -------------------------------------------------------------------------------- 1 | import { 2 | app, 3 | BrowserWindow, 4 | ipcMain, 5 | globalShortcut, 6 | screen, 7 | Tray, 8 | } from 'electron'; 9 | import path from 'path'; 10 | import os from 'os'; 11 | 12 | if (process.env.NODE_ENV === 'development') { 13 | try { 14 | // eslint-disable-next-line 15 | require('electron-debug')({ 16 | showDevTools: 'undocked', 17 | }); 18 | } catch (err) { 19 | console.log('Failed to install `electron-debug`'); 20 | } 21 | } else { 22 | global.__static = path.join(__dirname, '/static').replace(/\\/g, '\\\\'); 23 | } 24 | 25 | /** @type {BrowserWindow} */ 26 | let assistantWindow; 27 | /** @type {BrowserWindow} */ 28 | let responseWindow; 29 | /** @type {Tray} */ 30 | let mainTray; 31 | 32 | const miniToolbarSize = { 33 | width: 104, 34 | height: 52, 35 | }; 36 | 37 | const textToolbarSize = { 38 | width: 326, 39 | height: 52, 40 | }; 41 | 42 | /** 43 | * Set the size of the toolbar / assistant window. 44 | * 45 | * @param {Object} size Information about the user. 46 | * @param {Number} size.width The name of the user. 47 | * @param {Number} size.height {Number} The email of the user. 48 | * @returns {undefined} 49 | */ 50 | function setToolbarSize({ width, height }) { 51 | if (!assistantWindow) return; 52 | 53 | assistantWindow.setSize(width, height, true); 54 | assistantWindow.setPosition( 55 | screen.getPrimaryDisplay().workAreaSize.width - width, 56 | screen.getPrimaryDisplay().workAreaSize.height - height, 57 | ); 58 | } 59 | 60 | const assistantURL = process.env.NODE_ENV === 'development' 61 | ? 'http://localhost:9080/assistant.html' 62 | : `file://${__dirname}/assistant.html`; 63 | 64 | const responseURL = process.env.NODE_ENV === 'development' 65 | ? 'http://localhost:9080/response.html' 66 | : `file://${__dirname}/response.html`; 67 | 68 | /** 69 | * Registers the application shortcodes. 70 | */ 71 | function registerShortcuts() { 72 | globalShortcut.register('CommandOrControl+Option+A', () => { 73 | assistantWindow.show(); 74 | assistantWindow.webContents.send('start-assistant'); 75 | // [TODO]: Play activation sound 76 | }); 77 | } 78 | 79 | /** 80 | * Registers the application tray icon. 81 | * 82 | * @returns {Undefined} 83 | */ 84 | function registerTrayIcon() { 85 | const platform = os.platform(); 86 | const imageFolder = global.__static ? global.__static : path.join('static'); 87 | let trayImage; 88 | 89 | // Determine appropriate icon for platforms 90 | if (platform === 'darwin' || platform === 'linux') { 91 | trayImage = path.join(imageFolder, '/icon.png'); 92 | } else if (platform === 'win32') { 93 | trayImage = path.join(imageFolder, '/icon.ico'); 94 | } 95 | 96 | mainTray = new Tray(trayImage); 97 | mainTray.setToolTip('Google Assistant For Desktop'); 98 | mainTray.on('click', () => { 99 | assistantWindow.show(); 100 | }); 101 | } 102 | 103 | function registerEvents() { 104 | ipcMain.on('message', (event, message) => { 105 | console.log('Incoming Message: ', message); 106 | 107 | if (!assistantWindow) return; 108 | 109 | assistantWindow.webContents.send('message', message); 110 | if (message.event && message.event.cardHide && responseWindow) { 111 | responseWindow.hide(); 112 | assistantWindow.setAlwaysOnTop(false); 113 | } 114 | }); 115 | 116 | ipcMain.on('response', (event, html) => { 117 | if (!responseWindow) return; 118 | responseWindow.webContents.send('response', html); 119 | responseWindow.show(); 120 | assistantWindow.setAlwaysOnTop(true); 121 | }); 122 | 123 | ipcMain.on('miniToolbar', () => { 124 | setToolbarSize(miniToolbarSize); 125 | }); 126 | 127 | ipcMain.on('textToolbar', () => { 128 | setToolbarSize(textToolbarSize); 129 | }); 130 | } 131 | 132 | /** 133 | * Creates the main application window. 134 | */ 135 | function createWindows() { 136 | /** Assistant core and toolbar */ 137 | assistantWindow = new BrowserWindow({ 138 | height: 52, 139 | width: 104, 140 | show: true, 141 | frame: false, 142 | y: screen.getPrimaryDisplay().workAreaSize.height - 52, // - (mainHeight + 32) 143 | x: screen.getPrimaryDisplay().workAreaSize.width - 104, 144 | transparent: true, 145 | resizable: true, 146 | useContentSize: true, 147 | fullscreenable: true, 148 | }); 149 | 150 | /** Response window will be hidden until needed */ 151 | responseWindow = new BrowserWindow({ 152 | height: screen.getPrimaryDisplay().workAreaSize.height, 153 | width: screen.getPrimaryDisplay().workAreaSize.width, 154 | show: false, 155 | frame: false, 156 | resizable: false, 157 | y: 0, 158 | x: 0, 159 | transparent: true, 160 | useContentSize: true, 161 | }); 162 | 163 | assistantWindow.on('closed', () => { 164 | assistantWindow = null; 165 | }); 166 | 167 | responseWindow.on('closed', () => { 168 | responseWindow = null; 169 | }); 170 | 171 | assistantWindow.on('ready-to-show', () => { 172 | assistantWindow.show(); 173 | }); 174 | 175 | assistantWindow.loadURL(assistantURL); 176 | responseWindow.loadURL(responseURL); 177 | assistantWindow.webContents.openDevTools(); 178 | } 179 | 180 | 181 | function initializeApp() { 182 | createWindows(); 183 | registerEvents(); 184 | registerTrayIcon(); 185 | registerShortcuts(); 186 | } 187 | 188 | // NOTE: Setting a timeout here fixes the issues for linux transparency! 189 | app.on('ready', () => setTimeout(initializeApp, 1000)); 190 | 191 | app.on('window-all-closed', () => { 192 | if (process.platform !== 'darwin') { 193 | app.quit(); 194 | } 195 | }); 196 | 197 | app.on('activate', () => { 198 | if (assistantWindow === null || responseWindow === null) { 199 | assistantWindow = null; 200 | responseWindow = null; 201 | initializeApp(); 202 | } 203 | }); 204 | 205 | /** 206 | * Auto Updater 207 | * 208 | * Uncomment the following code below and install `electron-updater` to 209 | * support auto updating. Code Signing with a valid certificate is required. 210 | * https://simulatedgreg.gitbooks.io/electron-vue/content/en/using-electron-builder.html#auto-updating 211 | */ 212 | 213 | /* 214 | import { autoUpdater } from 'electron-updater' 215 | 216 | autoUpdater.on('update-downloaded', () => { 217 | autoUpdater.quitAndInstall() 218 | }) 219 | 220 | app.on('ready', () => { 221 | if (process.env.NODE_ENV === 'production') autoUpdater.checkForUpdates() 222 | }) 223 | */ 224 | -------------------------------------------------------------------------------- /src/renderer/assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ItsWendell/google-assistant-desktop-client/9b775bc3e4d8da8c99fb33cb9452ec3019f413a5/src/renderer/assets/.gitkeep -------------------------------------------------------------------------------- /src/renderer/config.js: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line 2 | import { remote } from 'electron'; 3 | import path from 'path'; 4 | import { installed as clientSecret } from './client_secret.json'; 5 | 6 | export default { 7 | auth: { 8 | /** [TODO]: Save tokens in local storage? */ 9 | /** [TODO]: Better secure client secret in main process maybe? */ 10 | key: clientSecret, 11 | keyFilePath: path.resolve(__dirname, 'client_secret.json'), 12 | savedTokensPath: path.join(remote.app.getPath('userData'), '/google.json'), 13 | }, 14 | assistant: { 15 | audio: { 16 | sampleRateOut: 24000, 17 | sampleRateIn: 16000, 18 | encodingIn: 'LINEAR16', 19 | encodingOut: 'MP3', 20 | }, 21 | lang: 'en-US', 22 | deviceId: 'ga-desktop', 23 | deviceModelId: 'ga-desktop-electron', 24 | screen: { 25 | isOn: true, 26 | }, 27 | }, 28 | spotify: { 29 | clientId: '84286ed53eaf4370b812263004b40c10', 30 | clientSecret: 'a92ed06c4b0e413bb6ba39f0e21c1da6', 31 | redirectUri: 'https://ga-desktop.local/callback', 32 | scopes: [ 33 | 'user-read-private', 34 | 'user-read-email', 35 | 'user-top-read', 36 | 'user-library-read', 37 | 'user-read-currently-playing', 38 | 'user-modify-playback-state', 39 | 'user-read-playback-state', 40 | 'streaming', 41 | ], 42 | savedTokensPath: path.join(remote.app.getPath('userData'), '/spotify.json'), 43 | }, 44 | }; 45 | -------------------------------------------------------------------------------- /src/renderer/containers/assistant.vue: -------------------------------------------------------------------------------- 1 | 41 | 42 | 134 | 135 | 173 | -------------------------------------------------------------------------------- /src/renderer/containers/response.vue: -------------------------------------------------------------------------------- 1 |