Skip to content

Commit 31c2e9b

Browse files
committed
feat: custom system prompt (#21)
1 parent 6d6cf10 commit 31c2e9b

File tree

7 files changed

+80
-77
lines changed

7 files changed

+80
-77
lines changed

components.d.ts

+1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ declare module '@vue/runtime-core' {
1111
export interface GlobalComponents {
1212
Avatar: typeof import('./src/components/Avatar.vue')['default']
1313
Button: typeof import('./src/components/Button.vue')['default']
14+
ElCascader: typeof import('element-plus/es')['ElCascader']
1415
ElSwitch: typeof import('element-plus/es')['ElSwitch']
1516
ElTooltip: typeof import('element-plus/es')['ElTooltip']
1617
InputKit: typeof import('./src/components/InputKit.vue')['default']

src/hooks/useSpeechService.ts

+2-5
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,6 @@ export const useSpeechService = ({ langs = <const>['fr-FR', 'ja-JP', 'en-US', 'z
8383
mediaRecorder = new MediaRecorder(stream)
8484

8585
mediaRecorder.ondataavailable = (e) => {
86-
console.log(chunks, 'c')
8786
chunks.push(e.data)
8887
}
8988

@@ -103,12 +102,10 @@ export const useSpeechService = ({ langs = <const>['fr-FR', 'ja-JP', 'en-US', 'z
103102

104103
isRecognizReadying.value = true
105104

106-
recognizer.value.canceled = () => {
107-
console.log('Recognize canceled')
108-
}
109105
recognizer.value.recognized = (s, e) => {
110106
console.log('Recognize result: ', e.result.text)
111107
cb && cb(e.result.text)
108+
// isRecognizing.value = false
112109
}
113110
recognizer.value.recognizing = (s, event) => {
114111
console.log('Recognize recognizing', event.result.text)
@@ -128,9 +125,9 @@ export const useSpeechService = ({ langs = <const>['fr-FR', 'ja-JP', 'en-US', 'z
128125
isRecognizing.value = false
129126
}
130127
recognizer.value.startContinuousRecognitionAsync(async () => {
131-
await audioRecorder()
132128
isRecognizing.value = true
133129
isRecognizReadying.value = false
130+
await audioRecorder()
134131
console.log('Recognize...')
135132
},
136133
(error) => {

src/pages/Home/components/Content.vue

+2-2
Original file line numberDiff line numberDiff line change
@@ -114,9 +114,9 @@ async function onSubmit(fromRecognize = false) {
114114
...currentChatMessages.value,
115115
{ content: message.value, role: 'user', audioBlob: fromRecognize ? await blobToBase64(audioBlob.value) : '' },
116116
])
117-
const tempCurrentChatMessages = currentChatMessages.value.map(x => ({ content: x.content, role: x.role })) // 发送的请求中需去除audioBlob
117+
const tempCurrentChatMessages = chatMessages.value.map(x => ({ content: x.content, role: x.role })) // 发送的请求中需去除audioBlob
118118
const systemMessage = currentChatMessages.value[0]
119-
const relativeMessage = [...tempCurrentChatMessages, { content: message.value, role: 'user' }].slice(-(Number(chatRememberCount.value))) // 保留最近的几条消息
119+
const relativeMessage = tempCurrentChatMessages.slice(-(Number(chatRememberCount.value))) // 保留最近的几条消息
120120
const prompts = [systemMessage, ...relativeMessage] as ChatMessage[]
121121
122122
message.value = ''

src/pages/Home/components/NewChat.vue

+67-65
Original file line numberDiff line numberDiff line change
@@ -15,33 +15,46 @@ const currentAvatarIndex = ref(Math.random() * avatarList.value.length | 0)
1515
const store = useConversationStore()
1616
const { ssmlToSpeak, isSynthesizing, isPlaying } = useSpeechService({ isFetchAllVoice: false })
1717
const allLanguages = computed(() => [...new Set(allVoices.map(v => v.locale))].filter(l => Object.keys(supportLanguageMap).includes(l)))
18-
const selectLanguage = ref('')
19-
const filterVoices = ref<VoiceInfo[]>([])
20-
const selectVoiceName = ref('')
2118
const desc = ref('')
2219
const name = ref('')
2320
const rate = ref('1.0')
2421
const previewText = ref('polyglot is awesome!')
25-
const filterStyles = ref<string[]>([])
26-
const selectStyle = ref('Neural')
22+
const presets = ref('Act as if you are meeting someone for the first time. How would you introduce yourself and start a conversation?')
2723
28-
const canAdd = computed(() => !!(selectLanguage.value && selectVoiceName.value && desc.value && name.value))
29-
30-
onBeforeMount(() => {
31-
selectLanguage.value = allLanguages.value[0]
32-
changeSelectLanguage(selectLanguage.value)
33-
})
24+
const voiceValue = ref<string[]>(['en-US', 'en-US-JennyNeural', 'chat'])
3425
35-
watch(selectLanguage, changeSelectLanguage)
26+
const selectLanguage = computed(() => voiceValue.value[0])
27+
const selectVoiceName = computed(() => voiceValue.value[1])
28+
const selectStyle = computed(() => voiceValue.value[2])
29+
const canAdd = computed(() => !!(selectLanguage.value && selectVoiceName.value && desc.value && name.value))
3630
37-
function changeSelectLanguage(newSelectLanguage: string) {
38-
filterVoices.value = allVoices.filter(v => v.locale === newSelectLanguage)
39-
selectVoiceName.value = filterVoices.value[0]?.shortName
31+
interface Option {
32+
label: string
33+
value: string
34+
children?: Option[]
4035
}
4136
42-
watch(selectVoiceName, (n) => {
43-
filterStyles.value = filterVoices.value.filter(v => v.shortName === n)[0]?.styleList || []
44-
selectStyle.value = filterStyles.value[0] || 'Neural'
37+
const options = ref<Option[] >([])
38+
39+
onMounted(() => {
40+
allLanguages.value.forEach((item) => {
41+
const children: Option[] = []
42+
allVoices.forEach((v) => {
43+
if (v.locale === item) {
44+
children.push({
45+
value: v.shortName,
46+
label: `${v.gender === 1 ? '🧒🏻' : '👦🏻'} ${v.localName}`,
47+
children: v.styleList?.map(x => ({ label: voiceStyleMap[x], value: x })) || [],
48+
})
49+
}
50+
})
51+
52+
options.value.push({
53+
value: item,
54+
label: supportLanguageMap[item],
55+
children,
56+
})
57+
})
4558
})
4659
4760
const randomAvatar = getAvatarUrl(avatarList.value[Math.random() * avatarList.value.length | 0]) // 随机默认选择一个头像
@@ -60,7 +73,7 @@ const addChat = (event: any) => {
6073
rate: +rate.value,
6174
isDefault: false,
6275
voiceStyle: selectStyle.value,
63-
})
76+
}, presets.value)
6477
store.changeCurrentKey(uid)
6578
emits('close')
6679
}
@@ -74,16 +87,8 @@ const previewSpeech = () => {
7487
}
7588
</script>
7689

77-
<script>
78-
79-
</script>
80-
8190
<template>
82-
<div flex="~ col gap-3" items-center>
83-
<!-- <div text-lg font-bold>
84-
自定义对话
85-
</div> -->
86-
91+
<div class="wrapper" flex="~ col gap-3" items-center>
8792
<div flex>
8893
<Avatar v-model:image-url="imageUrl" />
8994
</div>
@@ -96,32 +101,17 @@ const previewSpeech = () => {
96101
<input v-model="desc" type="text">
97102
</div>
98103
<div flex>
99-
<label for="">语言</label>
100-
<select v-model="selectLanguage">
101-
<option v-for="item in allLanguages" :key="item" :value="item">
102-
{{ supportLanguageMap[item] }}
103-
</option>
104-
</select>
105-
</div>
106-
<div flex>
107-
<label for="">音色</label>
108-
<select v-model="selectVoiceName">
109-
<option v-for="item in filterVoices" :key="item.shortName" :value="item.shortName">
110-
{{ `${item.locale} / ${item.gender === 1 ? 'Female' : 'Male'} / ${item.localName}` }}
111-
</option>
112-
</select>
104+
<label for="">语音</label>
105+
<div w-55 flex>
106+
<ElCascader v-model="voiceValue" style="width: 220px;" :options="options" />
107+
</div>
113108
</div>
114109
<div flex>
115-
<label for="">风格</label>
116-
<select v-model="selectStyle">
117-
<option value="Neural">
118-
正常
119-
</option>
120-
121-
<option v-for="item in filterStyles" :key="item" :value="item">
122-
{{ voiceStyleMap[item] }}
123-
</option>
124-
</select>
110+
<label for="">语速</label>
111+
<div w-55 flex>
112+
<input v-model="rate" flex-1 type="range" step="0.1" min="0.1" max="2.0">
113+
<span w-4 ml-1>{{ Number(rate).toFixed(1) }}</span>
114+
</div>
125115
</div>
126116
<div relative center-y>
127117
<div flex>
@@ -137,18 +127,12 @@ const previewSpeech = () => {
137127
</button>
138128
</div>
139129
</div>
130+
131+
<!-- todo -->
140132
<div flex>
141-
<label for="">语速</label>
142-
<div w-55 flex>
143-
<input v-model="rate" flex-1 type="range" step="0.1" min="0.1" max="2.0">
144-
<span w-4 ml-1>{{ Number(rate).toFixed(1) }}</span>
145-
</div>
133+
<label for="">场景预设</label>
134+
<textarea v-model="presets" :rows="3" placeholder="system prompt" />
146135
</div>
147-
<!-- todo -->
148-
<!-- <div flex>
149-
<label center-y justify-end mr-2 for="">预设</label>
150-
<textarea id="message" resize-none w-50 block p-2 text-sm placeholder="Write your thoughts here..." />
151-
</div> -->
152136
<div center-y text-sm text-gray-500>
153137
<i inline-block w-4 h-4 m-1 cursor-auto i-ic:baseline-lightbulb />
154138
点击头像可更换头像
@@ -162,12 +146,30 @@ const previewSpeech = () => {
162146

163147
<style scoped>
164148
label{
165-
@apply center-y justify-end mr-2 w-20
149+
@apply center-y justify-center w-20
166150
}
167151
input{
168-
@apply w-50 p-2
152+
@apply w-50 p-2 text-[#222]
169153
}
170154
select{
171-
@apply w-55 select-settings
155+
@apply w-55 select-settings text-[#222]
156+
}
157+
textarea{
158+
@apply resize-none w-50 font-sans block p-2 text-[#222]
159+
}
160+
textArea::-webkit-scrollbar{
161+
width: 0;
162+
height: 0;
163+
}
164+
.wrapper :deep(.el-input__wrapper){
165+
box-shadow: 0 0 0 1px #777 inset;
166+
padding-top: 3px;
167+
padding-bottom: 3px;
168+
}
169+
.wrapper :deep(.el-input__inner){
170+
color: #222;
171+
}
172+
.wrapper :deep(.el-input__wrapper):hover {
173+
box-shadow: 0 0 0 1px #777 inset;
172174
}
173175
</style>

src/pages/Home/components/Tool.vue

+1-1
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ watch([azureKey, azureRegion, ttsPassword], () => {
5555
<span>Setting</span>
5656
</div>
5757
</div>
58-
<Modal v-model:visible="addVisible" :z-index="2" class="dark:bg-[#111111] bg-white" center max-w-120 p6 @close="closeNewChat()">
58+
<Modal v-model:visible="addVisible" h-80vh :z-index="2" class="dark:bg-[#111111] bg-white" center max-w-120 p6 @close="closeNewChat()">
5959
<NewChat :all-voices="tempAllVoices as any" @close="addVisible = false" />
6060
</Modal>
6161

src/stores/index.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { v4 as uuid } from 'uuid'
2-
import { generatePrompt, getAvatarUrl } from '@/utils'
2+
import { basePrompt, generatePrompt, getAvatarUrl } from '@/utils'
33

44
const defaultConversations = [{
55
key: uuid(),
@@ -79,12 +79,12 @@ export const useConversationStore = defineStore('conversation', {
7979
cleanCurrentConversations() {
8080
this.chatMessages(this.currentKey)!.chatMessages.length = 1
8181
},
82-
addConversation(conversation: Omit<Conversation, 'chatMessages'>) {
82+
addConversation(conversation: Omit<Conversation, 'chatMessages'>, systemPrompt?: string) {
8383
this.conversations.push({
8484
...conversation,
8585
chatMessages: [{
8686
role: 'system',
87-
content: generatePrompt(conversation.language),
87+
content: systemPrompt ? basePrompt(conversation.language, systemPrompt) : generatePrompt(conversation.language),
8888
}],
8989
})
9090
},

src/utils/openAi.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,9 @@ export const parseOpenAIStream = (rawResponse: Response) => {
126126
export const verifyOpenKey = (key?: string | null) => key && ([41, 51].includes(key.length))
127127

128128
export const generatePrompt = (language: string) => {
129-
return `I want you to act as an ${language} speaking partner and improver. No matter what language I speak to you, you need to reply me in ${language}. I hope you keep your responses clean and limit your responses to 100 characters. I hope you are humorous and interesting. I hope you will ask me a question from time to time in your reply. Now let\'s start practicing. Remember, I want you reply me in ${language} and you are humorous and interesting`
129+
return `I want you to act as an ${language} speaking partner and improver. No matter what language I speak to you, you need to reply me in ${language}. I hope you keep your responses clean and limit your responses to 80 characters. I hope you are humorous and interesting. I hope you will ask me a question from time to time in your reply. Now let\'s start practicing. Remember, I want you reply me in ${language} and you are humorous and interesting`
130130
}
131131

132+
export const basePrompt = (language: string, prompt?: string) => {
133+
return `${prompt}.Remember, I want you reply me in ${language}, I hope limit your responses to 80 characters. Now let\'s start practicing. `
134+
}

0 commit comments

Comments
 (0)