Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add custom translation API #606

Merged
merged 3 commits into from
Feb 22, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,33 +1,32 @@
<template>
<NvStack spacing="5">
<NvGroup justify="apart" no-wrap spacing="5">
<NvStack>
<NvText type="label">Enable speech-to-text-to-speech</NvText>
<NvText type="caption">This might prevent your device from going to sleep</NvText>
</NvStack>
<NvSwitch
:modelValue="settingsStore.enableSTTTS"
class="shrink-0"
@update:modelValue="(value) => settingsStore.$patch({ enableSTTTS: value })"
/>
</NvGroup>
<template v-if="settingsStore.enableSTTTS">
<NvGoogleCloudCredentialsFormPart>
Izabela uses Google Cloud Speech for speech recognition which requires Google Cloud
Credentials to be imported
</NvGoogleCloudCredentialsFormPart>
<template v-if="!!googleCloudSpeechCredentialsPath">
<NvStack spacing="5">
<NvDivider direction="horizontal" />

<NvGoogleCloudCredentialsFormPart>
Izabela uses Google Cloud Speech for speech recognition which requires Google Cloud
Credentials to be imported
</NvGoogleCloudCredentialsFormPart>
<template v-if="!!googleCloudSpeechCredentialsPath">
<NvGroup justify="apart" no-wrap spacing="5">
<NvStack>
<NvText type="label">Enable speech-to-text-to-speech</NvText>
<NvText type="caption">This might prevent your device from going to sleep</NvText>
</NvStack>
<NvSwitch
:modelValue="settingsStore.enableSTTTS"
class="shrink-0"
@update:modelValue="(value) => settingsStore.$patch({ enableSTTTS: value })"
/>
</NvGroup>
<template v-if="settingsStore.enableSTTTS">
<NvDivider direction="horizontal" />
<NvGroup justify="apart" no-wrap>
<NvText type="label"> Speech recognition language </NvText>
<NvText type="label"> Speech recognition language</NvText>
<NvSpeechInputLanguageSelect />
</NvGroup>
<NvDivider direction="horizontal" />
<NvGroup justify="apart" no-wrap>
<NvText type="label"> Speech recognition strategy </NvText>
<NvText type="label"> Speech recognition strategy</NvText>
<NvSpeechRecognitionStrategySelect />
</NvGroup>
<template
Expand Down
10 changes: 10 additions & 0 deletions apps/app/src/features/settings/store/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,11 @@ export const useSettingsStore = defineStore(
const speechInputLanguage = ref('en-US')
const enableTranslation = ref(false)
const speechRecognitionStrategy = ref<'continuous-native' | 'continuous-web' | 'ptr'>('ptr')
const textTranslationStrategy = ref<'cloud-translation' | 'custom'>('cloud-translation')
const customTextTranslationEndpoint = ref('')
const customTextTranslationApiKey = ref('')
const customTextTranslationFrom = ref('')
const customTextTranslationTo = ref('')
const keybindings = ref<Record<string, Key[]>>({
recordAudio: [
{
Expand Down Expand Up @@ -140,6 +145,11 @@ export const useSettingsStore = defineStore(
textOutputLanguage,
speechInputLanguage,
enableTranslation,
textTranslationStrategy,
customTextTranslationEndpoint,
customTextTranslationApiKey,
customTextTranslationFrom,
customTextTranslationTo,
}
},
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<template>
<NvSelect
ref="select"
v-loading="isFetching"
:autocompleteWidth="width"
:modelValue="settingsStore.customTextTranslationFrom"
:options="options"
placeholder="Select a language"
@update:modelValue="(value) => settingsStore.$patch({ customTextTranslationFrom: value })"
/>
</template>
<script lang="ts" setup>
import { NvSelect } from '@packages/ui'
import { useElementSize } from '@vueuse/core'
import { computed, ref } from 'vue'
import { useSettingsStore } from '@/features/settings/store'
import { useGetCustomLanguagesQuery } from '@/features/translation/hooks/useGetCustomLanguagesQuery'

const settingsStore = useSettingsStore()
const select = ref()
const { width } = useElementSize(select)
const { data, isFetching } = useGetCustomLanguagesQuery()
const options = computed(
() =>
data.value?.from.map((language) => ({
label: language.name,
value: language.id,
})) || [],
)
</script>
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<template>
<NvSelect
ref="select"
v-loading="isFetching"
:autocompleteWidth="width"
:modelValue="settingsStore.customTextTranslationTo"
:options="options"
placeholder="Select a language"
@update:modelValue="(value) => settingsStore.$patch({ customTextTranslationTo: value })"
/>
</template>
<script lang="ts" setup>
import { NvSelect } from '@packages/ui'
import { useElementSize } from '@vueuse/core'
import { computed, ref } from 'vue'
import { useSettingsStore } from '@/features/settings/store'
import { useGetCustomLanguagesQuery } from '@/features/translation/hooks/useGetCustomLanguagesQuery'

const settingsStore = useSettingsStore()
const select = ref()
const { width } = useElementSize(select)
const { data, isFetching } = useGetCustomLanguagesQuery()
const options = computed(
() =>
data.value?.to.map((language) => ({
label: language.name,
value: language.id,
})) || [],
)
</script>
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<template>
<NvSelect
ref="select"
:autocompleteWidth="width"
:modelValue="settingsStore.textTranslationStrategy"
:options="[
{
label: 'Cloud Translation',
value: 'cloud-translation',
},
{
label: 'Custom',
value: 'custom',
},
]"
class="shrink-0"
@update:modelValue="(value) => settingsStore.$patch({ textTranslationStrategy: value })"
/>
</template>
<script lang="ts" setup>
import { NvSelect } from '@packages/ui'
import { useElementSize } from '@vueuse/core'
import { ref } from 'vue'
import { useSettingsStore } from '@/features/settings/store'

const settingsStore = useSettingsStore()
const select = ref()
const { width } = useElementSize(select)
</script>
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { useQuery } from 'vue-query'
import axios from 'axios'
import { useSettingsStore } from '@/features/settings/store'
import { decrypt } from '@/utils/security'

export const useGetCustomLanguagesQueryKey = () => ['get-custom-languages']
export const useGetCustomLanguagesQuery = () =>
useQuery(useGetCustomLanguagesQueryKey(), async () => {
const settingsStore = useSettingsStore()
const endpoint = settingsStore.customTextTranslationEndpoint
const { data } = await axios.post<{
from: {
id: string
name: string
}[]
to: {
id: string
name: string
}[]
}>(`${endpoint.endsWith('/') ? endpoint.slice(0, -1) : endpoint}/languages`, {
credentials: {
apiKey: decrypt(settingsStore.customTextTranslationApiKey),
},
})
return data
})
34 changes: 31 additions & 3 deletions apps/app/src/modules/electron-translation/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import { useSettingsStore } from '@/features/settings/store'
import { v2 } from '@google-cloud/translate'
import axios from 'axios'
import { decrypt } from '@/utils/security'

export const ElectronTranslation = () => ({
async translate(
Expand All @@ -10,9 +13,34 @@ export const ElectronTranslation = () => ({
format?: string
},
): Promise<string> {
const translate = new v2.Translate()
const [translations] = await translate.translate(text, options)
return translations
try {
const settingsStore = useSettingsStore()
if (settingsStore.textTranslationStrategy === 'cloud-translation') {
const translate = new v2.Translate()
const [translations] = await translate.translate(text, options)
return translations
}
if (settingsStore.textTranslationStrategy === 'custom') {
const endpoint = settingsStore.customTextTranslationEndpoint
const { data } = await axios.post<string>(
`${endpoint.endsWith('/') ? endpoint.slice(0, -1) : endpoint}/translate`,
{
credentials: {
apiKey: decrypt(settingsStore.customTextTranslationApiKey),
},
payload: {
text,
from: settingsStore.customTextTranslationFrom,
to: settingsStore.customTextTranslationTo,
},
},
)
return data
}
return text
} catch (error) {
return text
}
},
})

Expand Down
16 changes: 8 additions & 8 deletions apps/app/src/teams/messenger/components/NvHistoryMessage.vue
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@
<NvStack class="!flex-1 min-h-0">
<NvStack>
<NvText>{{ message.originalMessage || id }}</NvText>
<NvGroup v-if="message.translatedMessage">
<NvIcon name="english-to-chinese" size="3"/>
<NvGroup v-if="message.translatedMessage" align="start" noWrap>
<NvIcon name="english-to-chinese" size="3" />
<NvText>{{ message.translatedMessage || id }}</NvText>
</NvGroup>
</NvStack>
Expand Down Expand Up @@ -53,7 +53,7 @@
},
]"
>
<NvButton class="shrink-0" icon-name="ellipsis-v" size="sm"/>
<NvButton class="shrink-0" icon-name="ellipsis-v" size="sm" />
</NvContextMenu>
</NvGroup>
<div v-if="isPlaying" class="h-2 relative bg-gray-10">
Expand Down Expand Up @@ -120,9 +120,9 @@ const downloadMessageLocally = async () => {
reader.onload = () => {
ElectronFilesystem.downloadMessagePrompt(
completeMessage,
`${ formatedCreatedAt.value } - ${ engine.value?.name } - ${ engine.value?.getVoiceName(
`${formatedCreatedAt.value} - ${engine.value?.name} - ${engine.value?.getVoiceName(
message.value?.voice,
) } - ${ message.value?.message }`.replace(/([^a-z0-9\s-]+)/gi, '_'),
)} - ${message.value?.message}`.replace(/([^a-z0-9\s-]+)/gi, '_'),
reader.result as string,
).finally(() => {
downloading.value = false
Expand All @@ -138,9 +138,9 @@ const downloadMessageLocally = async () => {
const playMessage = computed(() =>
message.value
? {
...message.value,
excludeFromHistory: true,
}
...message.value,
excludeFromHistory: true,
}
: undefined,
)
const { play, isPlaying, isLoading, progress } = usePlayMessage(playMessage)
Expand Down
39 changes: 32 additions & 7 deletions apps/app/src/teams/messenger/components/NvMessengerMessageBar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@
@update:modelValue="(value) => settingsStore.$patch({ enableTranslation: value })"
/>
</NvGroup>
<NvDivider direction="horizontal" />
<NvFormItem label="Translation strategy">
<NvTranslationStrategySelect />
</NvFormItem>
<NvAccessBlocker
:allowed="!!googleCloudSpeechCredentialsPath && settingsStore.enableTranslation"
:reason="
Expand All @@ -44,13 +48,31 @@
>
<NvStack spacing="4">
<NvDivider direction="horizontal" />
<NvFormItem label="From">
<NvTranslationFromSelect />
</NvFormItem>
<NvDivider direction="horizontal" />
<NvFormItem label="To">
<NvTranslationToSelect />
</NvFormItem>
<template v-if="settingsStore.textTranslationStrategy === 'cloud-translation'">
<NvFormItem label="From">
<NvTranslationFromSelect />
</NvFormItem>
<NvDivider direction="horizontal" />
<NvFormItem label="To">
<NvTranslationToSelect />
</NvFormItem>
</template>
<template v-if="settingsStore.textTranslationStrategy === 'custom'">
<NvAccessBlocker
:allowed="!!settingsStore.customTextTranslationEndpoint"
reason="Endpoint and/or credentials required"
>
<NvStack spacing="4">
<NvFormItem label="From">
<NvCustomTranslationFromSelect />
</NvFormItem>
<NvDivider direction="horizontal" />
<NvFormItem label="To">
<NvCustomTranslationToSelect />
</NvFormItem>
</NvStack>
</NvAccessBlocker>
</template>
</NvStack>
</NvAccessBlocker>
</NvStack>
Expand Down Expand Up @@ -118,7 +140,10 @@ import { inject } from 'vue'
import { useRoute } from 'vue-router'
import NvTranslationFromSelect from '@/features/translation/components/inputs/NvTranslationFromSelect.vue'
import NvTranslationToSelect from '@/features/translation/components/inputs/NvTranslationToSelect.vue'
import NvCustomTranslationFromSelect from '@/features/translation/components/inputs/NvCustomTranslationFromSelect.vue'
import NvCustomTranslationToSelect from '@/features/translation/components/inputs/NvCustomTranslationToSelect.vue'
import { useGetGoogleCloudSpeechCredentialsPath } from '@/features/settings/hooks'
import NvTranslationStrategySelect from '@/features/translation/components/inputs/NvTranslationStrategySelect.vue'

const settingsStore = useSettingsStore()
const messengerContext = inject('messenger')
Expand Down
Loading