Commit d32e1cdc authored by ali's avatar ali

feat: 国际化,数字人打开多窗口限制

parent fead0580
......@@ -22,6 +22,7 @@
"flvjs",
"suhe",
"superres",
"vconsole",
"Vosk"
],
"editor.inlineSuggest.showToolbar": "always"
......
......@@ -48,7 +48,7 @@ export default class IPCs {
async (
event,
url: string,
options: BrowserWindowConstructorOptions & { isCloseOther: boolean }
options: BrowserWindowConstructorOptions
) => {
const ops = Object.assign(
{},
......@@ -61,9 +61,11 @@ export default class IPCs {
options
)
if (IPCs.browserWindows.has(url) && ops.isCloseOther) {
const wins = IPCs.browserWindows.get(url)
wins?.forEach((w) => !w.isDestroyed() && w.close())
IPCs.browserWindows.forEach((wins) => {
wins.forEach((w) => !w.isDestroyed() && w.close())
})
if (IPCs.browserWindows.has(url)) {
IPCs.browserWindows.set(url, [])
}
......
<script setup lang="tsx">
import { computed, ref } from 'vue'
import { computed, onMounted, ref } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import useStore from '@/renderer/store'
import { storeToRefs } from 'pinia'
import { audioAiTTS } from '@/renderer/plugins/tts'
import VConsole from 'vconsole'
import { useI18n } from 'vue-i18n'
const { locale, availableLocales } = useI18n()
const router = useRouter()
const route: any = useRoute()
const { settings } = useStore()
const setting = storeToRefs(settings)
let vconsole: VConsole | null = null
const languages = ref([setting.locale.value])
settings.tts === 'xf_tts' && settings.getSource()
......@@ -34,7 +37,16 @@ function init() {
vconsole = new VConsole()
}
}
init()
onMounted((): void => {
languages.value = availableLocales
locale.value = setting.locale.value
init()
})
const handleChangeLanguage = (val): void => {
locale.value = val
}
const handleRoute = (path: string): void => {
router.push(path)
......@@ -119,7 +131,7 @@ function clear() {
:class="{ active: isCurrentRoute('/') }"
@click="handleRoute('/')"
>
照片数字人
{{ $t('menu.photo') }}
</v-btn>
<v-btn
prepend-icon="mdi-fit-to-screen-outline"
......@@ -127,26 +139,37 @@ function clear() {
:class="{ active: isCurrentRoute('/video') }"
@click="handleRoute('/video')"
>
视频数字人
{{ $t('menu.video') }}
</v-btn>
<v-dialog width="600">
<template #activator="{ props }">
<v-btn v-bind="props" color="#fff" class="settings">
<v-icon start icon="mdi-wrench"></v-icon>
配置
{{ $t('menu.configure') }}
</v-btn>
</template>
<template #default="{ isActive }">
<v-card title="配置">
<v-card :title="$t('menu.configure')">
<v-sheet width="500" class="mx-auto mt-6">
<v-form ref="form">
<v-select
data-testid="select-language"
:model-value="locale"
density="compact"
:label="$t('menu.change-language')"
:items="languages"
@update:model-value="handleChangeLanguage"
>
{{ $t('menu.change-language') }}
</v-select>
<v-select
v-model="setting.asr.value"
:items="asrItems"
:rules="[(v) => !!v || '请选择 Asr']"
label="语音识别(ASR)"
label="ASR"
required
></v-select>
......@@ -154,7 +177,7 @@ function clear() {
v-model="setting.tts.value"
:items="['xf_tts', 'local_tts']"
:rules="[(v) => !!v || '请选择 TTS']"
label="文字转语音(TTS)"
label="TTS"
required
></v-select>
......@@ -162,7 +185,7 @@ function clear() {
<v-select
v-model="setting.voskSelectModel.value"
:items="setting.voskModels.value"
label="vosk_asr 模型"
label="vosk_asr"
required
:loading="voskModelLoading"
:disabled="voskModelLoading"
......@@ -183,7 +206,7 @@ function clear() {
<template v-if="setting.asr.value === 'vosk_ws'">
<v-text-field
label="ASR 地址"
label="ASR-HOST"
:rules="[(value) => !!value || 'ASR 地址必填']"
hide-details="auto"
:model-value="setting.voskWsLUrl"
......@@ -192,7 +215,7 @@ function clear() {
<v-text-field
style="margin-top: 22px"
label="TTS 地址"
label="TTS-HOST"
:rules="[(value) => !!value || 'TTS 地址必填']"
hide-details="auto"
:model-value="setting.ttsHost"
......@@ -200,7 +223,7 @@ function clear() {
<v-text-field
style="margin-top: 22px"
label="LLM 地址"
label="LLM-HOST"
:rules="[(value) => !!value || 'LLM 地址必填']"
hide-details="auto"
:model-value="setting.llmUrl"
......@@ -211,13 +234,13 @@ function clear() {
style="margin-top: 22px"
:items="liveHosts"
:rules="[(v) => !!v || '请选择音色']"
label="直播地址"
label="Live-HOST"
required
></v-select>
<v-slider
v-model="setting.llmToTTSSliceLength.value"
label="TTS 分句长度"
label="TTS Clause length"
class="align-center"
:max="100"
:min="0"
......@@ -242,14 +265,14 @@ function clear() {
true-value="yes"
false-value="no"
color="primary"
:label="`是否打开全屏: ${setting.isFullscreen.value}`"
:label="`full screen: ${setting.isFullscreen.value}`"
></v-switch>
<v-switch
v-model="setting.isOpenDevTools.value"
hide-details
color="primary"
:label="`是否打开 devTool: ${setting.isOpenDevTools.value}`"
:label="`is open devTool: ${setting.isOpenDevTools.value}`"
@update:model-value="changeOpenDevTools"
></v-switch>
......@@ -257,7 +280,7 @@ function clear() {
v-model="setting.vConsole.value"
hide-details
color="primary"
:label="`是否打开 vConsole: ${setting.vConsole.value}`"
:label="`is open vConsole: ${setting.vConsole.value}`"
@update:model-value="changeOpenVConsole"
></v-switch>
</v-form>
......
{
"desc": {
"welcome-title": "Hello Vutron! Everything is ready.",
"welcome-desc": "You can now write cross-platform web applications. See changes in real time and build to multiple platforms with one command. If you're not already familiar with it, you can get help from the documentation page below, or run some pre-written example code.",
"second-desc": "You have moved to the second screen! There is nothing here!"
},
"title": {
"main": "Main Screen",
"second": "Second Screen",
"error": "Unknown Error"
},
"menu": {
"change-theme": "Change Theme",
"photo": "photo-digital person",
"video": "video digital person",
"change-language": "Change Language",
"increase-count": "Count 1 increment",
"documentation": "Documentation",
"github": "Source Code"
"configure": "Configure",
"refresh": "refreshing"
},
"from": {
"diy-photo": "Customized Photos url(https://xxx.png)"
}
}
{
"desc": {
"welcome-title": "你好武特龍! 一切都準備好了。",
"welcome-desc": "您現在可以編寫跨平台的 Web 應用程序。 實時查看更改並使用一個命令構建到多個平台。 如果您還不熟悉它,可以從下面的文檔頁面獲得幫助,或者運行一些預先編寫的示例代碼。",
"second-desc": "您已移至第二個屏幕! 這裡什麼都沒有!"
},
"title": {
"main": "主屏幕",
"second": "第二屏",
"error": "未知錯誤"
},
"menu": {
"change-theme": "改變主題",
"photo": "照片數字人",
"video": "視頻數字人",
"change-language": "改變語言",
"increase-count": "計數 1 個增量",
"documentation": "文檔",
"github": "源代碼"
"configure": "設置",
"refresh": "refreshing"
},
"from": {
"diy-photo": "自定義照片 url(https://xxx.png)"
}
}
{
"desc": {
"welcome-title": "你好武特龙! 一切都准备好了。",
"welcome-desc": "您现在可以编写跨平台的 Web 应用程序。 实时查看更改并使用一个命令构建到多个平台。 如果您还不熟悉它,可以从下面的文档页面获得帮助,或者运行一些预先编写的示例代码。",
"second-desc": "您已移至第二个屏幕! 这里什么都没有!"
},
"title": {
"main": "主屏幕",
"second": "第二屏",
"error": "未知错误"
},
"menu": {
"change-theme": "改变主题",
"photo": "照片数字人",
"video": "视频数字人",
"change-language": "改变语言",
"increase-count": "计数 1 个增量",
"documentation": "文档",
"github": "源代码"
"configure": "配置",
"refresh": "清除缓存并刷新"
},
"from": {
"diy-photo": "自定义照片 url(https://xxx.png)"
}
}
import { createI18n } from 'vue-i18n'
import en from '@/renderer/locales/en.json'
import ko from '@/renderer/locales/ko.json'
// import ko from '@/renderer/locales/ko.json'
import zh from '@/renderer/locales/zh.json'
import zhHant from '@/renderer/locales/zh-hant.json'
import de from '@/renderer/locales/de.json'
import es from '@/renderer/locales/es.json'
import ja from '@/renderer/locales/ja.json'
import fr from '@/renderer/locales/fr.json'
import ru from '@/renderer/locales/ru.json'
import pt from '@/renderer/locales/pt.json'
// import de from '@/renderer/locales/de.json'
// import es from '@/renderer/locales/es.json'
// import ja from '@/renderer/locales/ja.json'
// import fr from '@/renderer/locales/fr.json'
// import ru from '@/renderer/locales/ru.json'
// import pt from '@/renderer/locales/pt.json'
import { getCurrentLocale } from '@/renderer/utils'
export default createI18n({
......@@ -18,14 +18,7 @@ export default createI18n({
globalInjection: true,
messages: {
en,
ko,
zh,
zhHant,
de,
es,
ja,
fr,
ru,
pt
zhHant
}
})
......@@ -67,7 +67,7 @@ function removePhoto(index: number) {
<template>
<v-container class="d-flex mt-6 pb-0">
<v-text-field
label="自定义照片 url(https://xxx.png)"
:label="$t('from.diy-photo')"
:model-value="urlValue"
:loading="imgLoading"
:rules="[(v) => appendPhoto(v)]"
......
......@@ -30,6 +30,7 @@ let flvPlayer: flvjs.Player | null = null
onMounted(() => {
init()
document.body.style.overflow = 'hidden'
})
function loadImg(): Promise<HTMLImageElement> {
......@@ -376,8 +377,9 @@ async function onAsr(question: string) {
let sliceAnswer = ''
let answer = ''
answerArray.length = 0
let isTime = true
let sliceAnswerLength = 10
answerArray.length = 0
photoRole!.answerArgs = new PhotoAnswer()
ws.onmessage = (message) => {
......@@ -407,8 +409,9 @@ async function onAsr(question: string) {
for (let i = 0; i < textArr.length; i++) {
const t = textArr[i]
sliceAnswer += t
if (/[。,?!;,.?!;]/.test(t) && sliceAnswer.length >= settings.llmToTTSSliceLength) {
if (/[。,?!;,.?!;]/.test(t) && sliceAnswer.length >= sliceAnswerLength) {
console.timeEnd('sliceAnswer')
sliceAnswerLength = settings.llmToTTSSliceLength
answerArray.push({ text: sliceAnswer, isLast: true })
runTTSTask(answerArray)
sliceAnswer = ''
......@@ -551,7 +554,6 @@ async function runAudioPlay() {
</v-btn>
</div>
</template>
<style scoped>
.voice {
display: flex;
......
......@@ -302,6 +302,7 @@ async function onAsr(question: string) {
let answer = ''
const answerArray: string[] = []
let isTime = true
let sliceAnswerLength = 10
inputContext.ws = ws
ws.onmessage = (message) => {
......@@ -332,8 +333,9 @@ async function onAsr(question: string) {
for (let i = 0; i < textArr.length; i++) {
const t = textArr[i]
sliceAnswer += t
if (/[。,?!;,.?!;]/.test(t) && sliceAnswer.length >= settings.llmToTTSSliceLength) {
if (/[。,?!;,.?!;]/.test(t) && sliceAnswer.length >= sliceAnswerLength) {
console.timeEnd('sliceAnswer')
sliceAnswerLength = settings.llmToTTSSliceLength
answerArray.push(sliceAnswer)
runTTSTask(answerArray)
sliceAnswer = ''
......@@ -485,6 +487,7 @@ async function xfTTS(text: string) {
class="mb-2 chip"
color="white"
variant="outlined"
:disabled="microphoneState !== 'waitInput' && microphoneState !== 'input'"
@click="onAsr(item.q)"
>
<v-icon start icon="mdi-help-circle-outline"></v-icon>
......
......@@ -9,6 +9,11 @@ const usePhotoStore = defineStore('photo', {
state: () =>
({
list: [
{
url: new URL('/images/photo/5.png', import.meta.url).href,
liveUrl:
'https://resources.laihua.com/2023-12-20/3a7bb5f0-9efb-11ee-a16e-d362e6bb4f85.png'
},
{
url: new URL('/images/photo/1.png', import.meta.url).href,
liveUrl:
......
......@@ -5,6 +5,7 @@ import type { Model } from '@/renderer/plugins/asr/index'
const voskModelMap: Map<string, Model | null> = new Map()
export type ISettings = {
locale: string
filePath: string
userData: string
appData: string
......@@ -35,6 +36,7 @@ const useSettingsStore = defineStore('settings', {
persist: true,
state: () =>
({
locale: navigator?.language?.split('-')[0] || 'en',
filePath: '',
userData: '',
appData: '',
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment