Skip to content

Commit

Permalink
feat: open proxy
Browse files Browse the repository at this point in the history
  • Loading branch information
liou666 committed Apr 1, 2023
1 parent 0177d31 commit b815b8e
Show file tree
Hide file tree
Showing 13 changed files with 153 additions and 69 deletions.
1 change: 1 addition & 0 deletions src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ console.log('[App.vue]', `Electron ${process.versions.electron}!`)
<Header />
<Aside />
<Content class="chat-wrap hide-scrollbar" />
<!-- <Aside /> -->
</div>
</template>

Expand Down
49 changes: 29 additions & 20 deletions src/components/Content.vue
Original file line number Diff line number Diff line change
@@ -1,30 +1,38 @@
<script setup lang="ts">
import Button from '@/components/widgets/Button.vue'
import { generateText } from '@/server/api'
import { useScroll } from '@/hooks'
import { SpeechService, getKey, verifyKey } from '@/utils'
import { getOpenAzureKey, getOpenAzureRegion, getOpenKey, getOpenProxy, verifyOpenKey } from '@/utils'
import { useConversationStore } from '@/stores'
const { VITE_REGION, VITE_SCRIPTION_KEY } = import.meta.env
const systemMessage: SystemMessage = {
role: 'system',
content: 'I want you to act as a spoken English teacher and improver. I will speak to you in English and you will reply to me in English to practice my spoken English. I want you to keep your reply neat, limiting the reply to 100 words. I want you to strictly correct my grammar mistakes, typos, and factual errors. I want you to ask me a question in your reply. Now let\'s start practicing, you could ask me a question first. Remember, I want you to strictly correct my grammar mistakes, typos, and factual errors.',
}
// const systemMessage: SystemMessage = {
// role: 'system',
// content: 'I want you to act as a spoken English teacher and improver. I will speak to you in English and you will reply to me in English to practice my spoken English. I want you to keep your reply neat, limiting the reply to 100 words. I want you to strictly correct my grammar mistakes, typos, and factual errors. I want you to ask me a question in your reply. Now let\'s start practicing, you could ask me a question first. Remember, I want you to strictly correct my grammar mistakes, typos, and factual errors.',
// }
// hooks
const store = useConversationStore()
const { el, scrollToBottom } = useScroll()
const {
language,
voiceName,
isRecognizing,
startRecognizeSpeech,
stopRecognizeSpeech,
textToSpeak,
} = useSpeechService(getOpenAzureKey(), getOpenAzureRegion())
// states
const message = ref('') // input message
const text = ref('') // current select message
const loading = ref(false)
const speechService = ref(new SpeechService(VITE_SCRIPTION_KEY, VITE_REGION))
const isRecognizing = computed(() => speechService.value.isRecognizing)
const messageLength = computed(() => store.currentChatMessages.length)
// hooks
const { el, scrollToBottom } = useScroll()
const currentKey = computed(() => store.currentKey)
// effects
watch(messageLength, () => nextTick(() => scrollToBottom()))
watch(currentKey, () => {
language.value = store.currentLanguage as any
voiceName.value = store.currentVoice as any
})
// methods
const roleClass = (role: string) => {
Expand All @@ -38,8 +46,8 @@ const roleClass = (role: string) => {
}
}
const onSubmit = async () => {
const key = getKey()
if (!verifyKey(key)) return alert('请输入正确的API-KEY')
const key = getOpenKey()
if (!verifyOpenKey(key)) return alert('请输入正确的API-KEY')
if (!message.value) return
store.changeConversations([
Expand All @@ -49,7 +57,7 @@ const onSubmit = async () => {
message.value = ''
loading.value = true
try {
const res = await generateText(store.currentChatMessages, key!)
const res = await generateText(store.currentChatMessages, key!, getOpenProxy())
if (res.error) {
alert(res.error?.message)
return loading.value = false
Expand All @@ -69,17 +77,17 @@ const onSubmit = async () => {
function speak(content: string) {
text.value = content
speechService.value.textToSpeak(content)
textToSpeak(content)
}
const recognize = async () => {
if (isRecognizing.value) {
const result = await speechService.value.stopRecognizeSpeech()
const result = await stopRecognizeSpeech()
message.value = result
onSubmit()
}
else {
speechService.value.startRecognizeSpeech()
startRecognizeSpeech()
}
}
</script>
Expand All @@ -102,11 +110,11 @@ const recognize = async () => {
<span class="bg-gray-100/20 rounded-lg w-4 py-1 px-3 center" @click="speak(item.content)">
<i icon-btn rotate-90 i-ic:sharp-wifi />
</span>
<!-- <span
<span
class="bg-gray-100/20 ml-1 cursor-pointer rounded-lg w-4 py-1 px-3 center"
>
<i icon-btn i-carbon:ibm-watson-language-translator />
</span> -->
</span>
</p>
</div>
</div>
Expand Down Expand Up @@ -143,6 +151,7 @@ const recognize = async () => {
</Button>
<Button
:disabled="loading"
@click="store.changeConversations([])"
>
<i i-carbon:trash-can />
</Button>
Expand Down
12 changes: 3 additions & 9 deletions src/components/Header.vue
Original file line number Diff line number Diff line change
@@ -1,20 +1,14 @@
<script setup lang="ts">
import InputKit from '@/components/widgets/InputKit.vue'
const openKey = useLocalStorage('openKey', '')
</script>

<template>
<header class="bg-#ecf7fd dark:bg-#1f262a">
<h3 text-2xl>
🤖️ <span class="text-gradient ">Polyglot</span>
</h3>
<div class="center-y">
<InputKit v-model="openKey">
<template #mainText>
Self Key
</template>
</InputKit>
</div>
<a class="center-y">
<i w-6 h-6 icon-btn i-carbon:logo-github />
</a>
</header>
</template>

Expand Down
22 changes: 20 additions & 2 deletions src/components/Nav.vue
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
<script setup lang="ts">
import Card from './widgets/Card.vue'
import { useConversationStore } from '@/stores'
import InputKit from '@/components/widgets/InputKit.vue'
import { OPEN_KEY, OPEN_PROXY } from '@/constant'
const openKey = useLocalStorage(OPEN_KEY, '')
const proxy = useLocalStorage(OPEN_PROXY, '')
const isDark = useDark()
const toggleDark = useToggle(isDark)
const store = useConversationStore()
</script>

Expand Down Expand Up @@ -33,8 +38,21 @@ const store = useConversationStore()
<span v-else>Dark Mode</span>
</div>
<div nav-item>
<i icon-btn i-carbon:settings />
<span>Setting</span>
<InputKit v-model="proxy" input-type="text">
<template #mainIcon>
<i i-carbon:server-proxy />
</template>
<template #mainText>
Proxy
</template>
</InputKit>
</div>
<div nav-item>
<InputKit v-model="openKey">
<template #mainText>
OpenAi Key
</template>
</InputKit>
</div>
</div>
</div>
Expand Down
11 changes: 9 additions & 2 deletions src/components/widgets/InputKit.vue
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ const [isShowInput, toggle] = useToggle(false)

<template>
<slot name="mainIcon">
<i mr-2 rotate-115 i-ic:baseline-key />
<i icon-btn rotate-115 i-ic:baseline-key />
</slot>
<div v-if="isShowInput">
<div v-if="isShowInput" class="wrapper">
<input
v-model="tempValue"
:type="props.inputType || 'password'"
Expand All @@ -33,3 +33,10 @@ const [isShowInput, toggle] = useToggle(false)
</slot>
</div>
</template>

<style scoped>
.wrapper{
display: grid;
grid-template-columns: 100px 1fr 1fr;
}
</style>
4 changes: 4 additions & 0 deletions src/constant.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export const OPEN_KEY = 'openKey'
export const OPEN_PROXY = 'openProxy'
export const AZURE_REGION = 'azureRegion'
export const AZURE_KEY = 'azureKey'
56 changes: 39 additions & 17 deletions src/hooks/useSpeechService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,35 +6,41 @@ import {
SpeechSynthesizer,
} from 'microsoft-cognitiveservices-speech-sdk'

export const useSpeechService = (subscriptionKey: string, region: string) => {
const language = ref('en-US')
const voiceName = ref('en-US-GuyNeural')
const speechConfig = SpeechConfig.fromSubscription(subscriptionKey, region)
export const useSpeechService = (subscriptionKey: string, region: string, langs = <const>['fr-FR', 'ja-JP', 'en-US', 'zh-CN', 'zh-HK', 'ko-KR', 'de-DE']) => {
const languages = ref(langs)
const language = ref<typeof langs[number]>(langs[0])
const languageMap = ref<Partial<Record<typeof langs[number], VoiceInfo[]>>>({})
const voiceName = ref('en-US-JennyMultilingualNeural')

const speechConfig = ref(SpeechConfig.fromSubscription(subscriptionKey, region))
const isRecognizing = ref(false)
const allVoices = ref<VoiceInfo[]>([])

const audioConfig = AudioConfig.fromDefaultMicrophoneInput()
const recognizer = new SpeechRecognizer(speechConfig, audioConfig)
const synthesizer = new SpeechSynthesizer(speechConfig)
const recognizer = ref(new SpeechRecognizer(speechConfig.value, audioConfig))
const synthesizer = ref(new SpeechSynthesizer(speechConfig.value))

watch([language, voiceName], ([lang, voice]) => {
speechConfig.speechRecognitionLanguage = lang
speechConfig.speechSynthesisLanguage = lang
speechConfig.speechSynthesisVoiceName = voice
speechConfig.value.speechRecognitionLanguage = lang
speechConfig.value.speechSynthesisLanguage = lang
speechConfig.value.speechSynthesisVoiceName = voice
recognizer.value = new SpeechRecognizer(speechConfig.value, audioConfig)
synthesizer.value = new SpeechSynthesizer(speechConfig.value)
}, {
immediate: true,
})

// 语音识别
const startRecognizeSpeech = () => {
isRecognizing.value = true
recognizer.startContinuousRecognitionAsync()
recognizer.value.startContinuousRecognitionAsync()
}

// 停止语音识别
const stopRecognizeSpeech = (): Promise<string> => {
return new Promise((resolve, reject) => {
recognizer.recognized = (s, e) => {
recognizer.stopContinuousRecognitionAsync()
recognizer.value.recognized = (s, e) => {
recognizer.value.stopContinuousRecognitionAsync()
isRecognizing.value = false
resolve(e.result.text)
}
Expand All @@ -45,7 +51,7 @@ export const useSpeechService = (subscriptionKey: string, region: string) => {
const recognizeSpeech = (): Promise<string> => {
isRecognizing.value = true
return new Promise((resolve, reject) => {
recognizer.recognizeOnceAsync((result) => {
recognizer.value.recognizeOnceAsync((result) => {
if (result.text) {
isRecognizing.value = false
resolve(result.text)
Expand All @@ -61,22 +67,37 @@ export const useSpeechService = (subscriptionKey: string, region: string) => {

// 语音合成
const textToSpeak = async (text: string, voice?: string) => {
speechConfig.speechSynthesisVoiceName = voice || speechConfig.speechSynthesisVoiceName
synthesizer.speakTextAsync(text)
speechConfig.value.speechSynthesisVoiceName = voice || speechConfig.value.speechSynthesisVoiceName
synthesizer.value.speakTextAsync(text)
}

// 停止语音合成
const stopTextToSpeak = () => {
synthesizer.close()
synthesizer.value.close()
}

// 获取语音列表
const getVoices = async (): Promise<VoiceInfo[]> => {
const res = await synthesizer.getVoicesAsync()
const res = await synthesizer.value.getVoicesAsync()
return res.voices
}

onMounted(async () => {
try {
allVoices.value = await getVoices()
// fr-FR 法语 ja-JP 日语 en-US 英语 zh-CN 中文 zh-HK 粤语 ko-KR 韩语 de-DE 德语
for (const lang of languages.value)
languageMap.value[lang] = allVoices.value.filter(x => lang === x.locale)
console.log(languageMap)
}
catch (error) {
allVoices.value = []
}
})

return {
languageMap,
languages,
language,
voiceName,
isRecognizing,
Expand All @@ -86,5 +107,6 @@ export const useSpeechService = (subscriptionKey: string, region: string) => {
textToSpeak,
stopTextToSpeak,
getVoices,
allVoices,
}
}
8 changes: 2 additions & 6 deletions src/server/api.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
import { OpenAi } from '@/utils'
const apiKey = import.meta.env.VITE_OPENAI_API_KEY
const proxy = import.meta.env.VITE_SERVE_PROXY

// const openai = new OpenAi(apiKey, proxy)

export const generateText = async (messages: ChatMessage[], apiKey: string) => {
export const generateText = async (messages: ChatMessage[], apiKey: string, proxy?: string) => {
const openai = new OpenAi(apiKey, proxy)

const { url, initOptions } = openai.generateTurboPayload({ messages })
Expand All @@ -18,7 +14,7 @@ export const generateText = async (messages: ChatMessage[], apiKey: string) => {
}
}

export const generateDashboardInfo = async (apiKey: string) => {
export const generateDashboardInfo = async (apiKey: string, proxy?: string) => {
const openai = new OpenAi(apiKey, proxy)

const { url, initOptions } = openai.generateDashboardPayload()
Expand Down
26 changes: 20 additions & 6 deletions src/stores/index.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,20 @@
const defaultConversations = [{
key: 'Tom',
desc: 'as a teacher',
key: 'Jenny',
desc: '美国',
language: 'en-US',
voice: 'en-US-JennyMultilingualNeural',
chatMessages: [],
}, {
key: 'Jarry',
desc: 'as a classrooms',
key: '碧衣',
desc: '日本',
language: 'ja-JP',
voice: 'ja-JP-AoiNeural',
chatMessages: [],
}, {
key: 'Lilei',
desc: 'as a basketball paly',
key: '선히',
desc: '韩国',
language: 'ko-KR',
voice: 'ko-KR-SunHiNeural',
chatMessages: [],
}] as const

Expand All @@ -18,6 +24,8 @@ export interface Conversation {
key: Key
desc: string
chatMessages: ChatMessage[]
language: string
voice: string
}

export interface State{
Expand All @@ -42,6 +50,12 @@ export const useConversationStore = defineStore('conversation', {
currentChatMessages(state) {
return state.conversations.find(x => x.key === state.currentKey)!.chatMessages
},
currentVoice(state) {
return state.conversations.find(x => x.key === state.currentKey)!.voice
},
currentLanguage(state) {
return state.conversations.find(x => x.key === state.currentKey)!.language
},
},
actions: {
changeConversations(chatMessages: ChatMessage[]) {
Expand Down
Loading

0 comments on commit b815b8e

Please sign in to comment.