接入语音

This commit is contained in:
aixianling
2023-11-10 17:13:55 +08:00
parent ad77470063
commit 24a775f94a
5 changed files with 55 additions and 22 deletions

View File

@@ -1,6 +1,5 @@
<template>
<meta-human/>
<chat class="fill mar-l8" :config="setting" :showSettings="showSettings"
<chat class="fill" :config="setting" :showSettings="showSettings"
@setting="showSettings=!showSettings"/>
<settings v-show="showSettings" v-model="setting"/>
</template>
@@ -8,12 +7,11 @@
<script>
import Chat from "./components/chat";
import Settings from "./components/settings";
import {Alpaca, ChatGLM, ChatGPT} from "./utils/models";
import MetaHuman from "./components/metaHuman.vue";
import {ChatGLM} from "./utils/models";
export default {
name: 'App',
components: {MetaHuman, Settings, Chat},
components: {Settings, Chat},
data() {
return {
showSettings: false,

View File

@@ -1,6 +1,6 @@
<template>
<section class="chat">
<el-row align="middle mar-b16">
<el-row align="middle" class="mar-b16">
<ai-model :model="config.model"/>
<el-row justify="end" class="fill mar-l8 gap-8">
<!--聊天操作栏-->
@@ -9,7 +9,10 @@
</el-row>
</el-row>
<thinking-bar v-show="loading"/>
<chat-content class="fill" :list="chatHistory" ref="chatContent"/>
<el-row class="fill">
<meta-human :voice="speech"/>
<chat-content class="fill" :list="chatHistory" ref="chatContent"/>
</el-row>
<el-row class="mar-t8" align="middle">
<chat-input class="fill mar-r8" v-model="inputText" @enter="handleSend"/>
<el-button type="primary" @click="handleSend">发送</el-button>
@@ -24,10 +27,11 @@ import ChatInput from "../components/chatInput";
import ThinkingBar from "../components/thinkingBar";
import AiModel from "../ui/AiModel";
import {USER_AVATAR} from "../utils/env";
import MetaHuman from "./metaHuman.vue";
export default {
name: "chat",
components: {AiModel, ChatContent, ChatInput, ThinkingBar},
components: {MetaHuman, AiModel, ChatContent, ChatInput, ThinkingBar},
props: {
config: {default: () => ({})}
},
@@ -35,7 +39,8 @@ export default {
return {
loading: false,
inputText: "",
chatHistory: []
chatHistory: [],
speech: ""
}
},
methods: {
@@ -65,9 +70,10 @@ export default {
}
this.chatHistory.push(aiMsg)
if (this.config.stream) {
ai.chatStream(this.chatHistory).then(reader => ai.streamOutput(reader, this.chatHistory.at(-1))).finally(this.handleAfterSend)
ai.chatStream(this.chatHistory).then(reader => ai.streamOutput(reader, this.chatHistory.at(-1), v => this.speech = v)).finally(this.handleAfterSend)
} else {
ai.chat(this.chatHistory).then(reply => {
this.speech = reply
const decodeArr = reply.split("")
decodeArr.forEach(e => this.chatHistory.at(-1).msg += e)
}).finally(this.handleAfterSend)

View File

@@ -1,28 +1,36 @@
<script>
import * as PIXI from "pixi.js";
import {load} from "../utils/tools.js";
import {load, throttle} from "../utils/tools.js";
import {Live2DModel} from "pixi-live2d-display";
window.PIXI = PIXI;
export default {
name: "metaHuman",
props: {
voice: String
},
data() {
return {
ins: null
ins: null,
speech: null
}
},
watch: {
voice(v) {
v && this.speak(v)
}
},
methods: {
initLive2d() {
load(window.Live2D && window.Live2DCubismCore)
return load(window.Live2D && window.Live2DCubismCore)
.then(() => Live2DModel.from("/live2d/ots14_1203/ots14_1203.model.json"))
.then(model => {
this.ins.stage.addChild(model)
model.rotation = Math.PI;
model.skew.x = Math.PI;
model.x = 400
model.y = 200
model.x = 380
model.y = 100
model.scale.set(0.16, 0.16);
// interaction
model.on('hit', (hitAreas) => {
@@ -48,6 +56,17 @@ export default {
});
model.on("pointerupoutside", () => (model.dragging = false));
model.on("pointerup", () => (model.dragging = false));
},
speak(text) {
if (!this.speech) {
this.speech = new SpeechSynthesisUtterance()
}
const synth = speechSynthesis
throttle(() => {
this.speech.text = text
// this.speech.voice = speechSynthesis.getVoices()[13]
load(!synth.speaking).then(() => synth.speak(this.speech))
})
}
},
mounted() {
@@ -58,15 +77,14 @@ export default {
backgroundAlpha: 0,
})
this.$el.appendChild(this.ins.view)
this.initLive2d()
this.initLive2d().then(() => this.speak("大家好,我是AI小助手"))
})
}
}
</script>
<template>
<section class="metaHuman">
</section>
<section class="metaHuman"/>
</template>
<style scoped lang="scss">

View File

@@ -198,7 +198,7 @@ export class ChatGLM extends BaseModel {
} else return Promise.reject("没有权限或者网络异常,请重新尝试!")
}
streamOutput(reader, chat) {
streamOutput(reader, chat, cb) {
return reader.read().then(({done, value}) => {
if (done) {
return;
@@ -206,8 +206,13 @@ export class ChatGLM extends BaseModel {
const decode = new TextDecoder().decode(value)
const dialogue = decode.split("event:").at(-1)
const msg = dialogue.split("\n").filter(e => e.startsWith("data:"))?.map(e => e.replace("data:", '')).join("\n")
if (msg?.length > 0) chat.msg = msg
return this.streamOutput(reader, chat)
if (msg?.length > 0) {
chat.msg = msg
if (typeof cb == "function") {
cb(msg)
}
}
return this.streamOutput(reader, chat, cb)
})
}
}

View File

@@ -43,3 +43,9 @@ export const addJs = url => {
script.src = url
document.body.appendChild(script)
}
export const throttle = (fn, delay = 1000) => {
if (typeof fn != "function") return;
if (window.throttleTimer) clearTimeout(window.throttleTimer)
window.throttleTimer = setTimeout(() => fn.call(), delay)
}