Skip to content

Commit

Permalink
feat: add ai avatar
Browse files Browse the repository at this point in the history
  • Loading branch information
sukbearai committed Nov 5, 2024
1 parent 89420bd commit 4677882
Show file tree
Hide file tree
Showing 164 changed files with 146,003 additions and 11 deletions.
106 changes: 106 additions & 0 deletions app/components/Live2d.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
<script setup lang="js">
// ライブラリの読み込み
import * as PIXI from 'pixi.js'
import { Live2DModel } from 'pixi-live2d-display'
import { DataType, ReturnCode } from '~/types/qt'
// mock数据
const { playTalkAct, initializeData, startDynamicUpdate } = useDynamicMockData()
initializeData()
startDynamicUpdate()
window.PIXI = PIXI
const canvasRef = ref(null)
let animationFrame = null
const model = ref()
const { connect, disconnect } = useWebChannel()
const cbId = connect((data) => {
// QT控制模型说话
if (data.type === DataType.AI_ASSISTANT) {
switch (data.ret) {
case ReturnCode.RECORDING_STATUS:
playTalkAct.value = false
break
case ReturnCode.AI_TALK:
playTalkAct.value = true
break
default:
console.warn(`Unknown return code: ${data.ret}`)
}
}
})
onBeforeUnmount(() => {
disconnect(cbId)
})
watch(playTalkAct, (val) => {
if (val) {
run()
}
else {
stop()
}
})
onMounted(async () => {
// PIXI.Applicationのインスタンスを生成
const app = new PIXI.Application({
width: 350,
height: 400,
transparent: true,
view: document.getElementById('2d'), // canvas要素を指定
})
// モデルの読み込み
model.value = await Live2DModel.from('/Resources/Natori/Natori.model3.json')
// pixijsのviewにモデルを描画する
app.stage.addChild(model.value)
// モデルの表示位置や表示場所などを設定(この辺はモデルに合わせて調整してください)
model.value.x = -200
model.value.y = -86
model.value.scale.set(0.18, 0.18) // モデルの縮尺
model.value.anchor.set(0, 0) // 表示の基準の位置
run()
})
function setMouthOpenY(v) {
v = Math.max(0, Math.min(1, v))
model.value.internalModel.coreModel.setParameterValueById('ParamMouthOpenY', v)
}
function run() {
if (!playTalkAct.value) {
return
}
// 模拟音频唇形同步
const randomValue = Math.random()
setMouthOpenY(randomValue)
// 使用 setTimeout 来控制更新频率,这里设置为每秒30次(1000/30 ≈ 33.33ms)
animationFrame = setTimeout(run, 1000 / 30)
}
function stop() {
if (!model.value) {
return
}
model.value.internalModel.coreModel.setParameterValueById('ParamMouthOpenY', 0)
}
onUnmounted(() => {
if (animationFrame) {
clearTimeout(animationFrame)
}
})
</script>

<template>
<canvas id="2d" ref="canvasRef" />
</template>
15 changes: 15 additions & 0 deletions app/composables/mockData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -345,10 +345,21 @@ export function useDynamicMockData() {
let taskIndex = 0
const env1 = ref(50)
const env2 = ref(75)
const playTalkAct = ref(false)

// 结束口播动画
const stopDynamicTalkAnimation = (ms: number) => {
setTimeout(() => {
playTalkAct.value = false
}, Math.max(3000, ms))
}

// 生成新的聊天内容
const generateNewChat = () => {
const template = chatTemplates[Math.floor(Math.random() * chatTemplates.length)]!

stopDynamicTalkAnimation(template.valueContent.length * 100)

return {
...template,
id: `mock${Date.now()}${chatCounter++}`,
Expand Down Expand Up @@ -386,6 +397,9 @@ export function useDynamicMockData() {
if (timer)
clearInterval(timer)
timer = setInterval(() => {
// 口播动画
playTalkAct.value = true

// 动态更新任务列表状态
const currentTask = taskList.value[taskIndex]
if (currentTask && currentTask.stepList[0]) {
Expand Down Expand Up @@ -424,6 +438,7 @@ export function useDynamicMockData() {
return {
env1,
env2,
playTalkAct,
chatList,
evaluateList,
taskList,
Expand Down
5 changes: 3 additions & 2 deletions app/pages/v-screen/data/components/AIAvatar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,9 @@ watch(() => props.list, (newValue) => {

<template>
<div class="fit-content flex items-end justify-between pr-[20px]">
<div class="ai-digit-woman h-[400px] w-[300px]" />
<div class="message-list flex-1 py-[20px] pl-[300px] pr-[10px]">
<!-- <div class="ai-digit-woman h-[400px] w-[300px]" /> -->
<Live2d />
<div class="message-list flex-1 py-[20px] pr-[10px]">
<div
v-for="(item) in props.list"
:key="item.index"
Expand Down
8 changes: 4 additions & 4 deletions app/pages/v-screen/data/components/Progress.vue
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,11 @@ const strokeColor = computed(() => {
<template>
<svg :width="props.width" :height="props.height" viewBox="0 0 200 200">
<!-- 背景圆环 -->
<circle cx="100" cy="100" r="94" stroke="#032978" stroke-width="6" fill="none" />
<circle cx="100" cy="100" r="79" stroke="#e6e6e6" stroke-width="0" fill="#021C57" />
<circle cx="100" cy="100" r="46" stroke="#e6e6e6" stroke-width="0" fill="#032978" />
<circle cx="100" cy="100" r="100" stroke="#032978" stroke-width="8" fill="none" />
<circle cx="100" cy="100" r="90" stroke="#e6e6e6" stroke-width="0" fill="#021C57" />
<circle cx="100" cy="100" r="80" stroke="#e6e6e6" stroke-width="0" fill="#032978" />

<text x="56" y="110" font-size="14" fill="#fff">{{ props.text }}</text>
<text x="48" y="120" font-size="11" fill="#fff">{{ props.text }}</text>

<!-- 前景圆环,显示进度 -->
<circle
Expand Down
8 changes: 4 additions & 4 deletions app/pages/v-screen/data/components/VEnvironment.vue
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,12 @@ const size = ref(80)
<template>
<div class="flex justify-around">
<div class="flex flex-col items-center text-[#fff]">
<Progress :width="size" :height="size" :text="`${env1}c`" :percent="env1" />
<Progress :width="size" :height="size" :text="`${env1}°C`" :percent="env1" />
<div>温度</div>
</div>
<div class="flex flex-col items-center text-[#fff]">
<Progress :width="size" :height="size" :text="`${env2}c`" :percent="env2" />
<div>温度</div>
<div class="flex flex-col items-center text-center text-[#fff]">
<Progress :width="size" :height="size" :text="`${env2} %`" :percent="env2" />
<div>湿度</div>
</div>
</div>
</template>
1 change: 1 addition & 0 deletions app/types/qt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export enum Step_State {
NOT_STARTED = '0',
DOING = '1',
COMPLETED = '2',
AI_TALK = '3',
}

export enum PAGE_SYNC {
Expand Down
9 changes: 8 additions & 1 deletion nuxt.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,14 @@ export default defineNuxtConfig({
{ name: 'theme-color', media: '(prefers-color-scheme: light)', content: 'white' },
{ name: 'theme-color', media: '(prefers-color-scheme: dark)', content: '#222222' },
],
script: [],
script: [
{
src: '/live2d.min.js',
},
{
src: '/live2dcubismcore.min.js',
},
],
},
},

Expand Down
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@
"echarts": "^5.5.1",
"lodash-es": "^4.17.21",
"nuxt-vite-legacy": "^1.2.0",
"pixi-live2d-display": "^0.4.0",
"pixi.js": "6.5.0",
"scroll-into-view-if-needed": "^3.1.0",
"vue-echarts": "^7.0.3",
"vue3-lottie": "^3.3.1",
Expand Down
Loading

0 comments on commit 4677882

Please sign in to comment.