接入语音
This commit is contained in:
@@ -1,6 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<meta-human/>
|
<chat class="fill" :config="setting" :showSettings="showSettings"
|
||||||
<chat class="fill mar-l8" :config="setting" :showSettings="showSettings"
|
|
||||||
@setting="showSettings=!showSettings"/>
|
@setting="showSettings=!showSettings"/>
|
||||||
<settings v-show="showSettings" v-model="setting"/>
|
<settings v-show="showSettings" v-model="setting"/>
|
||||||
</template>
|
</template>
|
||||||
@@ -8,12 +7,11 @@
|
|||||||
<script>
|
<script>
|
||||||
import Chat from "./components/chat";
|
import Chat from "./components/chat";
|
||||||
import Settings from "./components/settings";
|
import Settings from "./components/settings";
|
||||||
import {Alpaca, ChatGLM, ChatGPT} from "./utils/models";
|
import {ChatGLM} from "./utils/models";
|
||||||
import MetaHuman from "./components/metaHuman.vue";
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'App',
|
name: 'App',
|
||||||
components: {MetaHuman, Settings, Chat},
|
components: {Settings, Chat},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
showSettings: false,
|
showSettings: false,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<section class="chat">
|
<section class="chat">
|
||||||
<el-row align="middle mar-b16">
|
<el-row align="middle" class="mar-b16">
|
||||||
<ai-model :model="config.model"/>
|
<ai-model :model="config.model"/>
|
||||||
<el-row justify="end" class="fill mar-l8 gap-8">
|
<el-row justify="end" class="fill mar-l8 gap-8">
|
||||||
<!--聊天操作栏-->
|
<!--聊天操作栏-->
|
||||||
@@ -9,7 +9,10 @@
|
|||||||
</el-row>
|
</el-row>
|
||||||
</el-row>
|
</el-row>
|
||||||
<thinking-bar v-show="loading"/>
|
<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">
|
<el-row class="mar-t8" align="middle">
|
||||||
<chat-input class="fill mar-r8" v-model="inputText" @enter="handleSend"/>
|
<chat-input class="fill mar-r8" v-model="inputText" @enter="handleSend"/>
|
||||||
<el-button type="primary" @click="handleSend">发送</el-button>
|
<el-button type="primary" @click="handleSend">发送</el-button>
|
||||||
@@ -24,10 +27,11 @@ import ChatInput from "../components/chatInput";
|
|||||||
import ThinkingBar from "../components/thinkingBar";
|
import ThinkingBar from "../components/thinkingBar";
|
||||||
import AiModel from "../ui/AiModel";
|
import AiModel from "../ui/AiModel";
|
||||||
import {USER_AVATAR} from "../utils/env";
|
import {USER_AVATAR} from "../utils/env";
|
||||||
|
import MetaHuman from "./metaHuman.vue";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "chat",
|
name: "chat",
|
||||||
components: {AiModel, ChatContent, ChatInput, ThinkingBar},
|
components: {MetaHuman, AiModel, ChatContent, ChatInput, ThinkingBar},
|
||||||
props: {
|
props: {
|
||||||
config: {default: () => ({})}
|
config: {default: () => ({})}
|
||||||
},
|
},
|
||||||
@@ -35,7 +39,8 @@ export default {
|
|||||||
return {
|
return {
|
||||||
loading: false,
|
loading: false,
|
||||||
inputText: "",
|
inputText: "",
|
||||||
chatHistory: []
|
chatHistory: [],
|
||||||
|
speech: ""
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
@@ -65,9 +70,10 @@ export default {
|
|||||||
}
|
}
|
||||||
this.chatHistory.push(aiMsg)
|
this.chatHistory.push(aiMsg)
|
||||||
if (this.config.stream) {
|
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 {
|
} else {
|
||||||
ai.chat(this.chatHistory).then(reply => {
|
ai.chat(this.chatHistory).then(reply => {
|
||||||
|
this.speech = reply
|
||||||
const decodeArr = reply.split("")
|
const decodeArr = reply.split("")
|
||||||
decodeArr.forEach(e => this.chatHistory.at(-1).msg += e)
|
decodeArr.forEach(e => this.chatHistory.at(-1).msg += e)
|
||||||
}).finally(this.handleAfterSend)
|
}).finally(this.handleAfterSend)
|
||||||
|
|||||||
@@ -1,28 +1,36 @@
|
|||||||
<script>
|
<script>
|
||||||
import * as PIXI from "pixi.js";
|
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";
|
import {Live2DModel} from "pixi-live2d-display";
|
||||||
|
|
||||||
window.PIXI = PIXI;
|
window.PIXI = PIXI;
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "metaHuman",
|
name: "metaHuman",
|
||||||
|
props: {
|
||||||
|
voice: String
|
||||||
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
ins: null
|
ins: null,
|
||||||
|
speech: null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
voice(v) {
|
||||||
|
v && this.speak(v)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
initLive2d() {
|
initLive2d() {
|
||||||
load(window.Live2D && window.Live2DCubismCore)
|
return load(window.Live2D && window.Live2DCubismCore)
|
||||||
.then(() => Live2DModel.from("/live2d/ots14_1203/ots14_1203.model.json"))
|
.then(() => Live2DModel.from("/live2d/ots14_1203/ots14_1203.model.json"))
|
||||||
.then(model => {
|
.then(model => {
|
||||||
this.ins.stage.addChild(model)
|
this.ins.stage.addChild(model)
|
||||||
|
|
||||||
model.rotation = Math.PI;
|
model.rotation = Math.PI;
|
||||||
model.skew.x = Math.PI;
|
model.skew.x = Math.PI;
|
||||||
model.x = 400
|
model.x = 380
|
||||||
model.y = 200
|
model.y = 100
|
||||||
model.scale.set(0.16, 0.16);
|
model.scale.set(0.16, 0.16);
|
||||||
// interaction
|
// interaction
|
||||||
model.on('hit', (hitAreas) => {
|
model.on('hit', (hitAreas) => {
|
||||||
@@ -48,6 +56,17 @@ export default {
|
|||||||
});
|
});
|
||||||
model.on("pointerupoutside", () => (model.dragging = false));
|
model.on("pointerupoutside", () => (model.dragging = false));
|
||||||
model.on("pointerup", () => (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() {
|
mounted() {
|
||||||
@@ -58,15 +77,14 @@ export default {
|
|||||||
backgroundAlpha: 0,
|
backgroundAlpha: 0,
|
||||||
})
|
})
|
||||||
this.$el.appendChild(this.ins.view)
|
this.$el.appendChild(this.ins.view)
|
||||||
this.initLive2d()
|
this.initLive2d().then(() => this.speak("大家好,我是AI小助手"))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<section class="metaHuman">
|
<section class="metaHuman"/>
|
||||||
</section>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
|
|||||||
@@ -198,7 +198,7 @@ export class ChatGLM extends BaseModel {
|
|||||||
} else return Promise.reject("没有权限或者网络异常,请重新尝试!")
|
} else return Promise.reject("没有权限或者网络异常,请重新尝试!")
|
||||||
}
|
}
|
||||||
|
|
||||||
streamOutput(reader, chat) {
|
streamOutput(reader, chat, cb) {
|
||||||
return reader.read().then(({done, value}) => {
|
return reader.read().then(({done, value}) => {
|
||||||
if (done) {
|
if (done) {
|
||||||
return;
|
return;
|
||||||
@@ -206,8 +206,13 @@ export class ChatGLM extends BaseModel {
|
|||||||
const decode = new TextDecoder().decode(value)
|
const decode = new TextDecoder().decode(value)
|
||||||
const dialogue = decode.split("event:").at(-1)
|
const dialogue = decode.split("event:").at(-1)
|
||||||
const msg = dialogue.split("\n").filter(e => e.startsWith("data:"))?.map(e => e.replace("data:", '')).join("\n")
|
const msg = dialogue.split("\n").filter(e => e.startsWith("data:"))?.map(e => e.replace("data:", '')).join("\n")
|
||||||
if (msg?.length > 0) chat.msg = msg
|
if (msg?.length > 0) {
|
||||||
return this.streamOutput(reader, chat)
|
chat.msg = msg
|
||||||
|
if (typeof cb == "function") {
|
||||||
|
cb(msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return this.streamOutput(reader, chat, cb)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,3 +43,9 @@ export const addJs = url => {
|
|||||||
script.src = url
|
script.src = url
|
||||||
document.body.appendChild(script)
|
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)
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user