mirror of https://github.com/dessant/buster
You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
611 lines
15 KiB
Vue
611 lines
15 KiB
Vue
<template>
|
|
<v-app id="app" v-if="dataLoaded">
|
|
<div class="section">
|
|
<div class="section-title" v-once>
|
|
{{ getText('optionSectionTitle_services') }}
|
|
</div>
|
|
<div class="option-wrap">
|
|
<div class="option select">
|
|
<vn-select
|
|
:label="getText('optionTitle_speechService')"
|
|
:items="listItems.speechService"
|
|
v-model="options.speechService"
|
|
>
|
|
</vn-select>
|
|
</div>
|
|
|
|
<div
|
|
class="option text-field"
|
|
v-if="options.speechService === 'googleSpeechApi'"
|
|
>
|
|
<vn-text-field
|
|
:label="getText('inputLabel_apiKey')"
|
|
v-model.trim="options.googleSpeechApiKey"
|
|
>
|
|
</vn-text-field>
|
|
</div>
|
|
|
|
<a
|
|
class="service-guide"
|
|
v-if="options.speechService === 'googleSpeechApi'"
|
|
target="_blank"
|
|
rel="noreferrer"
|
|
href="https://github.com/dessant/buster/wiki/Configuring-Google-Cloud-Speech-to-Text"
|
|
>
|
|
{{ getText('linkText_apiGuide') }}
|
|
</a>
|
|
|
|
<div
|
|
class="option select"
|
|
v-if="options.speechService === 'ibmSpeechApi'"
|
|
>
|
|
<vn-select
|
|
:label="getText('optionTitle_ibmSpeechApiLoc')"
|
|
:items="listItems.ibmSpeechApiLoc"
|
|
v-model="options.ibmSpeechApiLoc"
|
|
>
|
|
</vn-select>
|
|
</div>
|
|
<div
|
|
class="option text-field"
|
|
v-if="options.speechService === 'ibmSpeechApi'"
|
|
>
|
|
<vn-text-field
|
|
v-model.trim="options.ibmSpeechApiKey"
|
|
:label="getText('inputLabel_apiKey')"
|
|
>
|
|
</vn-text-field>
|
|
</div>
|
|
|
|
<a
|
|
class="service-guide"
|
|
v-if="options.speechService === 'ibmSpeechApi'"
|
|
target="_blank"
|
|
rel="noreferrer"
|
|
href="https://github.com/dessant/buster/wiki/Configuring-IBM-Watson-Speech-to-Text"
|
|
>
|
|
{{ getText('linkText_apiGuide') }}
|
|
</a>
|
|
|
|
<div
|
|
class="option select"
|
|
v-if="options.speechService === 'microsoftSpeechApi'"
|
|
>
|
|
<vn-select
|
|
:label="getText('optionTitle_microsoftSpeechApiLoc')"
|
|
:items="listItems.microsoftSpeechApiLoc"
|
|
v-model="options.microsoftSpeechApiLoc"
|
|
>
|
|
</vn-select>
|
|
</div>
|
|
<div
|
|
class="option text-field"
|
|
v-if="options.speechService === 'microsoftSpeechApi'"
|
|
>
|
|
<vn-text-field
|
|
v-model.trim="options.microsoftSpeechApiKey"
|
|
:label="getText('inputLabel_apiKey')"
|
|
>
|
|
</vn-text-field>
|
|
</div>
|
|
|
|
<a
|
|
class="service-guide"
|
|
v-if="options.speechService === 'microsoftSpeechApi'"
|
|
target="_blank"
|
|
rel="noreferrer"
|
|
href="https://github.com/dessant/buster/wiki/Configuring-Microsoft-Azure-Speech-to-Text"
|
|
>
|
|
{{ getText('linkText_apiGuide') }}
|
|
</a>
|
|
|
|
<vn-text-field
|
|
class="text-field"
|
|
v-if="options.speechService === 'witSpeechApi'"
|
|
v-for="item in witSpeechApis"
|
|
:key="item.id"
|
|
:model-value="options.witSpeechApiKeys[item] || ''"
|
|
:label="
|
|
getText('inputLabel_apiKeyType', [
|
|
getText(`optionValue_witSpeechApiLang_${item}`)
|
|
])
|
|
"
|
|
@update:modelValue="saveWitSpeechApiKey($event.trim(), item)"
|
|
>
|
|
</vn-text-field>
|
|
<div
|
|
class="wit-add-api"
|
|
v-if="options.speechService === 'witSpeechApi'"
|
|
>
|
|
<vn-select
|
|
:label="getText('optionTitle_witSpeechApiLang')"
|
|
:items="listItems.witSpeechApiLang"
|
|
v-model="witSpeechApiLang"
|
|
>
|
|
</vn-select>
|
|
|
|
<vn-button :disabled="!witSpeechApiLang" @click="addWitSpeechApi">
|
|
{{ getText('buttonLabel_addApi') }}
|
|
</vn-button>
|
|
</div>
|
|
|
|
<a
|
|
class="service-guide"
|
|
v-if="options.speechService === 'witSpeechApi'"
|
|
target="_blank"
|
|
rel="noreferrer"
|
|
href="https://github.com/dessant/buster/wiki/Configuring-Wit.ai"
|
|
>
|
|
{{ getText('linkText_apiGuide') }}
|
|
</a>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="section section-client">
|
|
<div class="section-title" v-once>
|
|
{{ getText('optionSectionTitle_client') }}
|
|
</div>
|
|
<div class="section-desc" v-once>
|
|
{{ getText('optionSectionDescription_client') }}
|
|
</div>
|
|
<div class="option-wrap">
|
|
<div
|
|
class="option"
|
|
v-if="
|
|
clientAppInstalled ||
|
|
(clientAppVerified && options.simulateUserInput)
|
|
"
|
|
>
|
|
<vn-switch
|
|
:label="getText('optionTitle_simulateUserInput')"
|
|
v-model="options.simulateUserInput"
|
|
></vn-switch>
|
|
</div>
|
|
|
|
<div
|
|
class="option"
|
|
v-if="clientAppVerified && options.simulateUserInput"
|
|
>
|
|
<vn-switch
|
|
:label="getText('optionTitle_navigateWithKeyboard')"
|
|
v-model="options.navigateWithKeyboard"
|
|
></vn-switch>
|
|
</div>
|
|
|
|
<div class="option" v-if="clientAppInstalled">
|
|
<vn-switch
|
|
:label="getText('optionTitle_autoUpdateClientApp')"
|
|
v-model="options.autoUpdateClientApp"
|
|
></vn-switch>
|
|
</div>
|
|
|
|
<div
|
|
class="client-download"
|
|
v-if="clientAppVerified && !clientAppInstalled"
|
|
>
|
|
<div
|
|
class="download-desc"
|
|
v-html="
|
|
getText('pageContent_optionClientAppDownloadDesc', [
|
|
`<a target='_blank' rel='noreferrer' href='${installGuideUrl}'>${getText(
|
|
'linkText_installGuide'
|
|
)}</a>`
|
|
])
|
|
"
|
|
></div>
|
|
<div class="download-error" v-if="!clientAppDownloadUrl">
|
|
{{ getText('pageContent_optionClientAppOSError') }}
|
|
</div>
|
|
|
|
<vn-button
|
|
class="download-button"
|
|
:disabled="!clientAppDownloadUrl"
|
|
@click="$refs.dlLink.click()"
|
|
variant="elevated"
|
|
>
|
|
{{ getText('buttonLabel_downloadApp') }}
|
|
</vn-button>
|
|
<a
|
|
ref="dlLink"
|
|
class="download-link"
|
|
target="_blank"
|
|
rel="noreferrer"
|
|
:href="clientAppDownloadUrl"
|
|
></a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="section">
|
|
<div class="section-title" v-once>
|
|
{{ getText('optionSectionTitle_misc') }}
|
|
</div>
|
|
<div class="option-wrap">
|
|
<div class="option select">
|
|
<vn-select
|
|
:label="getText('optionTitle_appTheme')"
|
|
:items="listItems.appTheme"
|
|
v-model="options.appTheme"
|
|
>
|
|
</vn-select>
|
|
</div>
|
|
<div class="option">
|
|
<vn-switch
|
|
:label="getText('optionTitle_loadEnglishChallenge')"
|
|
v-model="options.loadEnglishChallenge"
|
|
></vn-switch>
|
|
</div>
|
|
<div class="option" v-if="!options.loadEnglishChallenge">
|
|
<vn-switch
|
|
:label="getText('optionTitle_tryEnglishSpeechModel')"
|
|
v-model="options.tryEnglishSpeechModel"
|
|
></vn-switch>
|
|
</div>
|
|
<div class="option" v-if="enableContributions">
|
|
<vn-switch
|
|
:label="getText('optionTitle_showContribPage')"
|
|
v-model="options.showContribPage"
|
|
></vn-switch>
|
|
</div>
|
|
<div class="option button" v-if="enableContributions">
|
|
<vn-button
|
|
class="contribute-button vn-icon--start"
|
|
@click="showContribute"
|
|
><vn-icon src="/src/contribute/assets/heart.svg"></vn-icon>
|
|
{{ getText('buttonLabel_contribute') }}
|
|
</vn-button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</v-app>
|
|
</template>
|
|
|
|
<script>
|
|
import {toRaw} from 'vue';
|
|
import {Button, Icon, Select, Switch, TextField} from 'vueton';
|
|
|
|
import storage from 'storage/storage';
|
|
import {getListItems, showContributePage, pingClientApp} from 'utils/app';
|
|
import {getText} from 'utils/common';
|
|
import {enableContributions, clientAppVersion} from 'utils/config';
|
|
import {
|
|
optionKeys,
|
|
clientAppPlatforms,
|
|
captchaWitSpeechApiLangCodes
|
|
} from 'utils/data';
|
|
|
|
export default {
|
|
components: {
|
|
[Button.name]: Button,
|
|
[Icon.name]: Icon,
|
|
[Select.name]: Select,
|
|
[Switch.name]: Switch,
|
|
[TextField.name]: TextField
|
|
},
|
|
|
|
data: function () {
|
|
return {
|
|
dataLoaded: false,
|
|
|
|
listItems: {
|
|
...getListItems(
|
|
{
|
|
speechService: [
|
|
'witSpeechApiDemo',
|
|
'googleSpeechApi',
|
|
'witSpeechApi',
|
|
'ibmSpeechApi',
|
|
'microsoftSpeechApi'
|
|
]
|
|
},
|
|
{scope: 'optionValue_speechService'}
|
|
),
|
|
...getListItems(
|
|
{
|
|
ibmSpeechApiLoc: [
|
|
'seoul',
|
|
'london',
|
|
'frankfurt',
|
|
'dallas',
|
|
'washington',
|
|
'sydney',
|
|
'tokyo'
|
|
]
|
|
},
|
|
{scope: 'optionValue_ibmSpeechApiLoc'}
|
|
),
|
|
...getListItems(
|
|
{
|
|
microsoftSpeechApiLoc: [
|
|
'eastAu',
|
|
'centralCa',
|
|
'centralUs',
|
|
'centralFr',
|
|
'centralIn',
|
|
'eastJp',
|
|
'westJp',
|
|
'southBr',
|
|
'centralKr',
|
|
'northCh',
|
|
'northCentralUs',
|
|
'southCentralUs',
|
|
'westCentralUs',
|
|
'southUk',
|
|
'eastUs',
|
|
'eastUs2',
|
|
'westUs',
|
|
'westUs2',
|
|
'eastAsia',
|
|
'southeastAsia',
|
|
'westEu',
|
|
'northEu'
|
|
]
|
|
},
|
|
{scope: 'optionValue_microsoftSpeechApiLoc'}
|
|
),
|
|
...getListItems(
|
|
{
|
|
witSpeechApiLang: [
|
|
...new Set(
|
|
Object.values(captchaWitSpeechApiLangCodes).filter(Boolean)
|
|
)
|
|
].sort()
|
|
},
|
|
{scope: 'optionValue_witSpeechApiLang'}
|
|
),
|
|
...getListItems(
|
|
{appTheme: ['auto', 'light', 'dark']},
|
|
{scope: 'optionValue_appTheme'}
|
|
)
|
|
},
|
|
|
|
enableContributions,
|
|
|
|
witSpeechApiLang: null,
|
|
witSpeechApis: [],
|
|
|
|
clientAppVerified: false,
|
|
clientAppInstalled: false,
|
|
clientAppDownloadUrl: '',
|
|
installGuideUrl: '',
|
|
|
|
options: {
|
|
speechService: '',
|
|
googleSpeechApiKey: '',
|
|
ibmSpeechApiLoc: '',
|
|
ibmSpeechApiKey: '',
|
|
microsoftSpeechApiLoc: '',
|
|
microsoftSpeechApiKey: '',
|
|
witSpeechApiKeys: {},
|
|
loadEnglishChallenge: false,
|
|
tryEnglishSpeechModel: false,
|
|
simulateUserInput: false,
|
|
autoUpdateClientApp: false,
|
|
navigateWithKeyboard: false,
|
|
appTheme: '',
|
|
showContribPage: false
|
|
}
|
|
};
|
|
},
|
|
|
|
methods: {
|
|
getText,
|
|
|
|
verifyClientApp: async function () {
|
|
try {
|
|
await pingClientApp();
|
|
this.clientAppInstalled = true;
|
|
} catch (err) {
|
|
if (!this.installGuideUrl) {
|
|
this.installGuideUrl =
|
|
'https://github.com/dessant/buster/wiki/Installing-the-client-app';
|
|
const {os, arch} = this.$env;
|
|
if (clientAppPlatforms.includes(`${os}/${arch}`)) {
|
|
this.installGuideUrl += `#${os}`;
|
|
this.clientAppDownloadUrl = `https://github.com/dessant/buster-client/releases/download/v${clientAppVersion}/buster-client-setup-v${clientAppVersion}-${os}-${arch}`;
|
|
if (os === 'windows') {
|
|
this.clientAppDownloadUrl += '.exe';
|
|
}
|
|
}
|
|
}
|
|
|
|
this.clientAppInstalled = false;
|
|
}
|
|
|
|
this.clientAppVerified = true;
|
|
},
|
|
|
|
setWitSpeechApiLangOptions: function () {
|
|
this.listItems.witSpeechApiLang = this.listItems.witSpeechApiLang.filter(
|
|
item => !this.witSpeechApis.includes(item.value)
|
|
);
|
|
},
|
|
|
|
addWitSpeechApi: function () {
|
|
this.witSpeechApis.push(this.witSpeechApiLang);
|
|
this.witSpeechApiLang = null;
|
|
this.setWitSpeechApiLangOptions();
|
|
},
|
|
|
|
saveWitSpeechApiKey: function (value, lang) {
|
|
const apiKeys = this.options.witSpeechApiKeys;
|
|
if (value) {
|
|
this.options.witSpeechApiKeys = Object.assign({}, apiKeys, {
|
|
[lang]: value
|
|
});
|
|
} else if (apiKeys[lang]) {
|
|
delete apiKeys[lang];
|
|
this.options.witSpeechApiKeys = Object.assign({}, apiKeys);
|
|
}
|
|
},
|
|
|
|
showContribute: async function () {
|
|
await showContributePage();
|
|
}
|
|
},
|
|
|
|
created: async function () {
|
|
const options = await storage.get(optionKeys);
|
|
|
|
for (const option of Object.keys(this.options)) {
|
|
this.options[option] = options[option];
|
|
|
|
this.$watch(
|
|
`options.${option}`,
|
|
async function (value) {
|
|
await storage.set({[option]: toRaw(value)});
|
|
await browser.runtime.sendMessage({id: 'optionChange'});
|
|
},
|
|
{deep: true}
|
|
);
|
|
}
|
|
|
|
this.witSpeechApis = Object.keys(options.witSpeechApiKeys);
|
|
this.setWitSpeechApiLangOptions();
|
|
|
|
document.title = getText('pageTitle', [
|
|
getText('pageTitle_options'),
|
|
getText('extensionName')
|
|
]);
|
|
|
|
this.verifyClientApp();
|
|
|
|
this.dataLoaded = true;
|
|
}
|
|
};
|
|
</script>
|
|
|
|
<style lang="scss">
|
|
@use 'vueton/styles' as vueton;
|
|
|
|
@include vueton.theme-base;
|
|
|
|
.v-application__wrap {
|
|
display: grid;
|
|
grid-row-gap: 32px;
|
|
grid-column-gap: 48px;
|
|
padding: 24px;
|
|
grid-auto-rows: min-content;
|
|
grid-auto-columns: min-content;
|
|
}
|
|
|
|
.section-title {
|
|
font-size: 20px;
|
|
font-weight: 500;
|
|
letter-spacing: 0.25px;
|
|
line-height: 32px;
|
|
}
|
|
|
|
.section-desc {
|
|
font-size: 14px;
|
|
font-weight: 400;
|
|
letter-spacing: 0.25px;
|
|
line-height: 20px;
|
|
|
|
padding-top: 8px;
|
|
max-width: 380px;
|
|
}
|
|
|
|
.option-wrap {
|
|
display: grid;
|
|
grid-row-gap: 24px;
|
|
padding-top: 24px;
|
|
}
|
|
|
|
.option {
|
|
display: flex;
|
|
align-items: center;
|
|
height: 20px;
|
|
|
|
&.button {
|
|
height: 40px;
|
|
}
|
|
|
|
&.select,
|
|
&.text-field {
|
|
height: 56px;
|
|
}
|
|
|
|
& .contribute-button {
|
|
@include vueton.theme-prop(color, primary);
|
|
|
|
& .vn-icon {
|
|
@include vueton.theme-prop(background-color, cta);
|
|
}
|
|
}
|
|
}
|
|
|
|
.text-field .v-input__control {
|
|
width: 326px;
|
|
}
|
|
|
|
.section-client .section-desc {
|
|
width: 272px;
|
|
}
|
|
|
|
.wit-add-api {
|
|
display: flex;
|
|
align-items: center;
|
|
|
|
& .vn-select {
|
|
& .v-input__control,
|
|
& .v-input__details {
|
|
max-width: calc(100vw - 48px - 124px) !important;
|
|
}
|
|
}
|
|
|
|
& .vn-button {
|
|
margin-left: 24px;
|
|
@include vueton.theme-prop(color, primary);
|
|
}
|
|
}
|
|
|
|
.service-guide {
|
|
font-size: 16px;
|
|
font-weight: 400;
|
|
letter-spacing: 0.5px;
|
|
line-height: 24px;
|
|
}
|
|
|
|
.client-download {
|
|
width: 272px;
|
|
}
|
|
|
|
.download-desc,
|
|
.download-error {
|
|
font-size: 14px;
|
|
font-weight: 400;
|
|
letter-spacing: 0.25px;
|
|
line-height: 20px;
|
|
}
|
|
|
|
.download-desc a {
|
|
font-size: 14px;
|
|
font-weight: 400;
|
|
letter-spacing: 0.25px;
|
|
line-height: 20px;
|
|
}
|
|
|
|
.download-desc {
|
|
max-width: 240px;
|
|
}
|
|
|
|
.download-error {
|
|
margin-top: 12px;
|
|
@include vueton.theme-prop(color, error);
|
|
}
|
|
|
|
.download-link {
|
|
visibility: hidden;
|
|
}
|
|
|
|
.download-button {
|
|
margin-top: 24px;
|
|
@include vueton.theme-prop(background-color, primary);
|
|
|
|
& .v-btn__content {
|
|
@include vueton.theme-prop(color, on-primary);
|
|
}
|
|
}
|
|
</style>
|