收到了用户反馈,需要加上键盘快捷键,不能点 enter 就发送。
和大模型通信的后续版本,有语音输入的需求,问了下某代码生成很厉害的模型,这里做一个记录。
键盘快捷键
原来使用@keyup.enter="handleSubmit"
,这样用户只要输入 enter 就发送请求,很有可能只输入了半句话,或者需要换行,导致用户体验不好。
新增了一个函数handleMultiLineNewline
作为换行处理。测试了@keydown.meta.enter
,可以在 mac 系统下使用 cmd + enter/windows 系统下使用 win + enter 触发换行。@keydown.ctrl.enter
和@keydown.shift.enter
可以触发换行。
但是以上连用不知道为啥消息又自动发出去了。。。
索性自己写了一个函数handleEnter
来处理换行。
另一个需求是中文输入法下,未输入完时,使用 enter 键,默认不发送消息,整体如下:
| <el-input id="userInput" v-model="userInput" placeholder="请输入您的问题" @compositionstart="onCompositionStart" @compositionend="onCompositionEnd" @keydown.enter="handleEnter" type="textarea" rows="5" maxlength="2048" resize="none" />
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| const handleEnter = async (event: KeyboardEvent) => { if ( (event.shiftKey && event.key === 'Enter') || (event.ctrlKey && event.key === 'Enter') || (event.metaKey && event.key === 'Enter') ) { const start = (event.target as HTMLTextAreaElement).selectionStart const end = (event.target as HTMLTextAreaElement).selectionEnd const value = userInput.value userInput.value = value.slice(0, start) + '\n' + value.slice(end) ;(event.target as HTMLTextAreaElement).selectionStart = ( event.target as HTMLTextAreaElement ).selectionEnd = start + 1 event.preventDefault() } else if (!isComposing.value && event.key === 'Enter') { handleSubmit(event) event.preventDefault() } }
const isComposing = ref(false) const onCompositionStart = () => { isComposing.value = true } const onCompositionEnd = () => { isComposing.value = false }
|
当用户输入为空时,不做处理;当大模型在输出时,禁止用户再次提交信息。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| const handleSubmit = async (event: any) => { console.log('handleSubmit', event) if (!userInput.value || userInput.value.trim().length === 0) { return } else if (loadingSend.value == true) { event.preventDefault() return } else { if (abortController.signal.aborted) { abortController = new AbortController() } loadingSend.value = true let text = userInput.value if (text.endsWith('\n')) { text = text.trimEnd() } chatList.value.push({ role: 'user', message: text }) userInput.value = '' scrollToBottom() handleChat(text) } }
|
结果某用户提了个 bug,说按了 Enter 键,消息还是发出去了,最终排查出来是 Safari 浏览器的问题,需要前端做一个兼容。
尝试了推荐的以下事件垫片代码,实测无效。
| import { useEventListener } from '@vueuse/core' const inputRef = ref(null) onMounted(() => { useEventListener(inputRef.value, 'compositionstart', onCompositionStart) useEventListener(inputRef.value, 'compositionend', onCompositionEnd) })
|
最后改了好几版,测试下来以下代码可以兼容 Safari 浏览器:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| const isComposing = ref(false) const ignoreEnter = ref(false) const onCompositionStart = () => { isComposing.value = true } const onCompositionEnd = () => { isComposing.value = false ignoreEnter.value = true setTimeout(() => { ignoreEnter.value = false }, 200) }
onMounted(() => { const inputElement = document.getElementById('userInput') if (inputElement) { if (!('oncompositionstart' in inputElement)) { let isComposing = false const keydownHandler = (event) => { if (event.keyCode === 229) { isComposing = true onCompositionStart() } } const keyupHandler = (event) => { if (isComposing) { isComposing = false onCompositionEnd() } } inputElement.addEventListener('keydown', keydownHandler) inputElement.addEventListener('keyup', keyupHandler)
onUnmounted(() => { inputElement.removeEventListener('keydown', keydownHandler) inputElement.removeEventListener('keyup', keyupHandler) }) } } })
|
语音输入
在 vue3 项目中,使用useSpeechRecognition
这个库,实现语音输入转文字功能。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| <template> <CustomIcon v-if="!isRecording" name="icon-voice" size="24" @click="isRecording = true" class="cursor-pointer" /> <CustomIcon v-else name="icon-voice-active" size="24" @click="isRecording = false" class="cursor-pointer" />
</template>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| import { useSpeechRecognition } from '@vueuse/core'
const isRecording = ref(false) const { isListening, result, start, stop, isSupported } = useSpeechRecognition({ lang: 'zh-CN', continuous: true }) if (isSupported.value) { watch(result, (value) => { if (isRecording.value) { userInput.value = value } }) }
watch(isListening, (value) => { if (!value && isRecording.value) { start() } })
watch(isRecording, (value) => { if (value) { start() } else { stop() } })
|