Add files using upload-large-folder tool
Browse files- r1-a/response_generation/minicpm/MiniCPM-o/web_demos/minicpm-o_2.6/web_server/src/components/ExtraInfo/index.vue +36 -0
- r1-a/response_generation/minicpm/MiniCPM-o/web_demos/minicpm-o_2.6/web_server/src/components/IdeasList/index.vue +67 -0
- r1-a/response_generation/minicpm/MiniCPM-o/web_demos/minicpm-o_2.6/web_server/src/components/LikeAndDislike/index.vue +110 -0
- r1-a/response_generation/minicpm/MiniCPM-o/web_demos/minicpm-o_2.6/web_server/src/components/ModelConfig/index copy.vue +404 -0
- r1-a/response_generation/minicpm/MiniCPM-o/web_demos/minicpm-o_2.6/web_server/src/components/ModelConfig/index.vue +456 -0
- r1-a/response_generation/minicpm/MiniCPM-o/web_demos/minicpm-o_2.6/web_server/src/components/ModelOutput/index.vue +91 -0
- r1-a/response_generation/minicpm/MiniCPM-o/web_demos/minicpm-o_2.6/web_server/src/components/SelectTimbre/index.vue +122 -0
- r1-a/response_generation/minicpm/MiniCPM-o/web_demos/minicpm-o_2.6/web_server/src/components/SkipBtn/index.vue +67 -0
- r1-a/response_generation/minicpm/MiniCPM-o/web_demos/minicpm-o_2.6/web_server/src/components/SvgIcon/index.vue +39 -0
- r1-a/response_generation/minicpm/MiniCPM-o/web_demos/minicpm-o_2.6/web_server/src/components/Voice/index.vue +138 -0
- r1-a/response_generation/minicpm/MiniCPM-o/web_demos/minicpm-o_2.6/web_server/src/directives/index.js +8 -0
- r1-a/response_generation/minicpm/MiniCPM-o/web_demos/minicpm-o_2.6/web_server/src/enums/index.js +18 -0
- r1-a/response_generation/minicpm/MiniCPM-o/web_demos/minicpm-o_2.6/web_server/src/hooks/useHttp.js +61 -0
- r1-a/response_generation/minicpm/MiniCPM-o/web_demos/minicpm-o_2.6/web_server/src/hooks/useQueue.js +95 -0
- r1-a/response_generation/minicpm/MiniCPM-o/web_demos/minicpm-o_2.6/web_server/src/hooks/useRandomId.js +9 -0
- r1-a/response_generation/minicpm/MiniCPM-o/web_demos/minicpm-o_2.6/web_server/src/hooks/useVoice.js +38 -0
- r1-a/response_generation/minicpm/MiniCPM-o/web_demos/minicpm-o_2.6/web_server/src/i18n/en.json +36 -0
- r1-a/response_generation/minicpm/MiniCPM-o/web_demos/minicpm-o_2.6/web_server/src/i18n/zh.json +36 -0
- r1-a/response_generation/minicpm/MiniCPM-o/web_demos/minicpm-o_2.6/web_server/src/router/guard/index.js +5 -0
- r1-a/response_generation/minicpm/MiniCPM-o/web_demos/minicpm-o_2.6/web_server/src/router/index.js +16 -0
r1-a/response_generation/minicpm/MiniCPM-o/web_demos/minicpm-o_2.6/web_server/src/components/ExtraInfo/index.vue
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<template>
|
| 2 |
+
<div class="extra-info">
|
| 3 |
+
<div class="model-version" v-if="modelVersion">模型版本: {{ modelVersion }}</div>
|
| 4 |
+
<div class="web-version">前端版本: {{ webVersion }}</div>
|
| 5 |
+
</div>
|
| 6 |
+
</template>
|
| 7 |
+
|
| 8 |
+
<script setup>
|
| 9 |
+
defineProps({
|
| 10 |
+
modelVersion: {
|
| 11 |
+
type: String,
|
| 12 |
+
default: ''
|
| 13 |
+
},
|
| 14 |
+
webVersion: {
|
| 15 |
+
type: String,
|
| 16 |
+
default: ''
|
| 17 |
+
}
|
| 18 |
+
});
|
| 19 |
+
</script>
|
| 20 |
+
|
| 21 |
+
<style lang="less" scoped>
|
| 22 |
+
.extra-info {
|
| 23 |
+
position: fixed;
|
| 24 |
+
top: 62px;
|
| 25 |
+
left: 4vw;
|
| 26 |
+
display: flex;
|
| 27 |
+
.model-version,
|
| 28 |
+
.web-version {
|
| 29 |
+
font-size: 12px;
|
| 30 |
+
color: red;
|
| 31 |
+
}
|
| 32 |
+
.model-version {
|
| 33 |
+
margin-right: 16px;
|
| 34 |
+
}
|
| 35 |
+
}
|
| 36 |
+
</style>
|
r1-a/response_generation/minicpm/MiniCPM-o/web_demos/minicpm-o_2.6/web_server/src/components/IdeasList/index.vue
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<template>
|
| 2 |
+
<div class="ideas">
|
| 3 |
+
<div class="ideas-title">
|
| 4 |
+
<img src="@/assets/images/ideas-icon.png " />
|
| 5 |
+
<span>Convsersation ideas</span>
|
| 6 |
+
</div>
|
| 7 |
+
<div class="ideas-content">
|
| 8 |
+
<div class="ideas-content-item" v-for="(item, index) in ideasList" :key="index">{{ item }}</div>
|
| 9 |
+
</div>
|
| 10 |
+
</div>
|
| 11 |
+
</template>
|
| 12 |
+
|
| 13 |
+
<script setup>
|
| 14 |
+
defineProps({
|
| 15 |
+
ideasList: {
|
| 16 |
+
type: Array,
|
| 17 |
+
default: () => []
|
| 18 |
+
}
|
| 19 |
+
});
|
| 20 |
+
</script>
|
| 21 |
+
|
| 22 |
+
<style lang="less" scoped>
|
| 23 |
+
.ideas {
|
| 24 |
+
margin-top: 16px;
|
| 25 |
+
box-shadow: 0 0 0 0.5px #e0e0e0;
|
| 26 |
+
border-radius: 12px;
|
| 27 |
+
padding: 18px 28px;
|
| 28 |
+
&-title {
|
| 29 |
+
font-size: 20px;
|
| 30 |
+
font-weight: 500;
|
| 31 |
+
margin-bottom: 20px;
|
| 32 |
+
display: flex;
|
| 33 |
+
align-items: center;
|
| 34 |
+
img {
|
| 35 |
+
width: 24px;
|
| 36 |
+
height: 24px;
|
| 37 |
+
margin-right: 10px;
|
| 38 |
+
}
|
| 39 |
+
span {
|
| 40 |
+
color: #171717;
|
| 41 |
+
font-family: PingFang SC;
|
| 42 |
+
font-size: 16px;
|
| 43 |
+
font-style: normal;
|
| 44 |
+
font-weight: 500;
|
| 45 |
+
line-height: normal;
|
| 46 |
+
}
|
| 47 |
+
}
|
| 48 |
+
&-content {
|
| 49 |
+
display: grid;
|
| 50 |
+
grid-template-columns: repeat(3, 1fr);
|
| 51 |
+
gap: 8px;
|
| 52 |
+
&-item {
|
| 53 |
+
display: flex;
|
| 54 |
+
align-items: center;
|
| 55 |
+
border-radius: 10px;
|
| 56 |
+
background: #eaefff;
|
| 57 |
+
padding: 10px 24px;
|
| 58 |
+
color: #7579eb;
|
| 59 |
+
font-family: PingFang SC;
|
| 60 |
+
font-size: 14px;
|
| 61 |
+
font-style: normal;
|
| 62 |
+
font-weight: 400;
|
| 63 |
+
line-height: normal;
|
| 64 |
+
}
|
| 65 |
+
}
|
| 66 |
+
}
|
| 67 |
+
</style>
|
r1-a/response_generation/minicpm/MiniCPM-o/web_demos/minicpm-o_2.6/web_server/src/components/LikeAndDislike/index.vue
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<template>
|
| 2 |
+
<div class="like-box">
|
| 3 |
+
<div class="like-btn" @click="selectFeedbackStatus('like')">
|
| 4 |
+
<img v-if="feedbackStatus === '' || feedbackStatus === 'dislike'" src="@/assets/images/zan.png" />
|
| 5 |
+
<img v-else src="@/assets/images/zan-active.png" />
|
| 6 |
+
</div>
|
| 7 |
+
<div class="dislike-btn" @click="selectFeedbackStatus('dislike')">
|
| 8 |
+
<img v-if="feedbackStatus === '' || feedbackStatus === 'like'" src="@/assets/images/cai.png" />
|
| 9 |
+
<img v-else src="@/assets/images/cai-active.png" />
|
| 10 |
+
</div>
|
| 11 |
+
</div>
|
| 12 |
+
<el-dialog
|
| 13 |
+
v-model="dialogVisible"
|
| 14 |
+
:title="t('feedbackDialogTitle')"
|
| 15 |
+
width="400"
|
| 16 |
+
:align-center="true"
|
| 17 |
+
@close="cancelFeedback"
|
| 18 |
+
>
|
| 19 |
+
<el-input type="textarea" :rows="4" v-model="comment" />
|
| 20 |
+
<div class="operate-btn">
|
| 21 |
+
<el-button type="primary" :loading="submitLoading" @click="submitFeedback">确定</el-button>
|
| 22 |
+
<el-button @click="cancelFeedback">取消</el-button>
|
| 23 |
+
</div>
|
| 24 |
+
</el-dialog>
|
| 25 |
+
</template>
|
| 26 |
+
<script setup>
|
| 27 |
+
import { feedback } from '@/apis';
|
| 28 |
+
import { useI18n } from 'vue-i18n';
|
| 29 |
+
|
| 30 |
+
const { t } = useI18n();
|
| 31 |
+
const feedbackStatus = defineModel('feedbackStatus');
|
| 32 |
+
const curResponseId = defineModel('curResponseId');
|
| 33 |
+
const dialogVisible = ref(false);
|
| 34 |
+
const comment = ref('');
|
| 35 |
+
const submitLoading = ref(false);
|
| 36 |
+
const selectFeedbackStatus = val => {
|
| 37 |
+
if (!curResponseId.value) {
|
| 38 |
+
return;
|
| 39 |
+
}
|
| 40 |
+
feedbackStatus.value = val;
|
| 41 |
+
dialogVisible.value = true;
|
| 42 |
+
};
|
| 43 |
+
// 提交反馈
|
| 44 |
+
const submitFeedback = async () => {
|
| 45 |
+
submitLoading.value = true;
|
| 46 |
+
const { code, message } = await feedback({
|
| 47 |
+
response_id: curResponseId.value,
|
| 48 |
+
rating: feedbackStatus.value,
|
| 49 |
+
comment: comment.value
|
| 50 |
+
});
|
| 51 |
+
submitLoading.value = false;
|
| 52 |
+
if (code !== 0) {
|
| 53 |
+
ElMessage({
|
| 54 |
+
type: 'error',
|
| 55 |
+
message: message,
|
| 56 |
+
duration: 3000,
|
| 57 |
+
customClass: 'system-error'
|
| 58 |
+
});
|
| 59 |
+
return;
|
| 60 |
+
}
|
| 61 |
+
ElMessage.success('反馈成功');
|
| 62 |
+
dialogVisible.value = false;
|
| 63 |
+
setTimeout(() => {
|
| 64 |
+
feedbackStatus.value = '';
|
| 65 |
+
}, 2000);
|
| 66 |
+
};
|
| 67 |
+
const cancelFeedback = () => {
|
| 68 |
+
dialogVisible.value = false;
|
| 69 |
+
feedbackStatus.value = '';
|
| 70 |
+
};
|
| 71 |
+
</script>
|
| 72 |
+
<style lang="less" scoped>
|
| 73 |
+
.like-box {
|
| 74 |
+
display: flex;
|
| 75 |
+
margin: 0 16px;
|
| 76 |
+
.like-btn,
|
| 77 |
+
.dislike-btn {
|
| 78 |
+
width: 26px;
|
| 79 |
+
height: 26px;
|
| 80 |
+
background: #f3f3f3;
|
| 81 |
+
display: flex;
|
| 82 |
+
align-items: center;
|
| 83 |
+
justify-content: center;
|
| 84 |
+
border-radius: 8px;
|
| 85 |
+
cursor: pointer;
|
| 86 |
+
&:hover {
|
| 87 |
+
background: #d1d1d1;
|
| 88 |
+
}
|
| 89 |
+
img {
|
| 90 |
+
width: 16px;
|
| 91 |
+
height: 16px;
|
| 92 |
+
}
|
| 93 |
+
}
|
| 94 |
+
.dislike-btn {
|
| 95 |
+
margin-left: 16px;
|
| 96 |
+
}
|
| 97 |
+
}
|
| 98 |
+
.operate-btn {
|
| 99 |
+
margin-top: 20px;
|
| 100 |
+
display: flex;
|
| 101 |
+
justify-content: flex-end;
|
| 102 |
+
.el-button--primary {
|
| 103 |
+
background: #647fff;
|
| 104 |
+
border-color: #647fff;
|
| 105 |
+
&:hover {
|
| 106 |
+
border-color: #647fff;
|
| 107 |
+
}
|
| 108 |
+
}
|
| 109 |
+
}
|
| 110 |
+
</style>
|
r1-a/response_generation/minicpm/MiniCPM-o/web_demos/minicpm-o_2.6/web_server/src/components/ModelConfig/index copy.vue
ADDED
|
@@ -0,0 +1,404 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<template>
|
| 2 |
+
<div class="user-config">
|
| 3 |
+
<div class="user-config-title">模型配置</div>
|
| 4 |
+
<div class="config-item">
|
| 5 |
+
<div class="config-item-label">语音打断:</div>
|
| 6 |
+
<div class="config-item-content">
|
| 7 |
+
<el-switch
|
| 8 |
+
v-model="configData.canStopByVoice"
|
| 9 |
+
inline-prompt
|
| 10 |
+
active-text="是"
|
| 11 |
+
inactive-text="否"
|
| 12 |
+
size="small"
|
| 13 |
+
:disabled="isCalling"
|
| 14 |
+
/>
|
| 15 |
+
</div>
|
| 16 |
+
</div>
|
| 17 |
+
<div class="config-item">
|
| 18 |
+
<div class="config-item-label">视频画质:</div>
|
| 19 |
+
<div class="config-item-content">
|
| 20 |
+
<el-radio-group v-model="configData.videoQuality" :disabled="isCalling">
|
| 21 |
+
<el-radio :value="true">高清</el-radio>
|
| 22 |
+
<el-radio :value="false">低清</el-radio>
|
| 23 |
+
</el-radio-group>
|
| 24 |
+
</div>
|
| 25 |
+
</div>
|
| 26 |
+
<div class="config-item">
|
| 27 |
+
<div class="config-item-label">VAD阈值:</div>
|
| 28 |
+
<div class="config-item-content vad-slider">
|
| 29 |
+
<el-slider
|
| 30 |
+
v-model="configData.vadThreshold"
|
| 31 |
+
:min="0.5"
|
| 32 |
+
:max="1"
|
| 33 |
+
:step="0.1"
|
| 34 |
+
size="small"
|
| 35 |
+
:disabled="isCalling"
|
| 36 |
+
/>
|
| 37 |
+
</div>
|
| 38 |
+
</div>
|
| 39 |
+
<!-- <div class="timbre-model">
|
| 40 |
+
<div class="timbre-model-label">音色人物:</div>
|
| 41 |
+
<div class="timbre-model-content">
|
| 42 |
+
<el-select
|
| 43 |
+
v-model="configData.timbreId"
|
| 44 |
+
style="width: 100%"
|
| 45 |
+
@change="handleChangePeople"
|
| 46 |
+
clearable
|
| 47 |
+
placeholder="请选择"
|
| 48 |
+
>
|
| 49 |
+
<el-option v-for="item in peopleList" :key="item.id" :value="item.id" :label="item.name">
|
| 50 |
+
{{ item.name }}
|
| 51 |
+
</el-option>
|
| 52 |
+
</el-select>
|
| 53 |
+
</div>
|
| 54 |
+
</div> -->
|
| 55 |
+
<div class="prompt-item">
|
| 56 |
+
<div class="prompt-item-label">Assistant_prompt:</div>
|
| 57 |
+
<div class="prompt-item-content">
|
| 58 |
+
<el-input
|
| 59 |
+
type="textarea"
|
| 60 |
+
:rows="3"
|
| 61 |
+
v-model="configData.assistantPrompt"
|
| 62 |
+
resize="none"
|
| 63 |
+
:disabled="isCalling"
|
| 64 |
+
/>
|
| 65 |
+
</div>
|
| 66 |
+
</div>
|
| 67 |
+
<div class="config-item">
|
| 68 |
+
<div class="config-item-label">使用语音prompt:</div>
|
| 69 |
+
<div class="config-item-content">
|
| 70 |
+
<el-switch
|
| 71 |
+
v-model="configData.useAudioPrompt"
|
| 72 |
+
inline-prompt
|
| 73 |
+
active-text="是"
|
| 74 |
+
inactive-text="否"
|
| 75 |
+
size="small"
|
| 76 |
+
:disabled="isCalling"
|
| 77 |
+
@change="handleSelectUseAudioPrompt"
|
| 78 |
+
/>
|
| 79 |
+
</div>
|
| 80 |
+
</div>
|
| 81 |
+
<div class="voice-prompt-box">
|
| 82 |
+
<div class="prompt-item" v-if="configData.useAudioPrompt">
|
| 83 |
+
<div class="prompt-item-label">Voice_clone_prompt:</div>
|
| 84 |
+
<div class="prompt-item-content">
|
| 85 |
+
<el-input
|
| 86 |
+
type="textarea"
|
| 87 |
+
:rows="8"
|
| 88 |
+
v-model="configData.voiceClonePrompt"
|
| 89 |
+
resize="none"
|
| 90 |
+
:disabled="isCalling"
|
| 91 |
+
/>
|
| 92 |
+
</div>
|
| 93 |
+
</div>
|
| 94 |
+
|
| 95 |
+
<div class="timbre-config" v-if="configData.useAudioPrompt">
|
| 96 |
+
<div class="timbre-config-label">音色选择:</div>
|
| 97 |
+
<div class="timbre-config-content">
|
| 98 |
+
<el-checkbox-group v-model="configData.timbre" @change="handleSelectTimbre" :disabled="isCalling">
|
| 99 |
+
<el-checkbox :value="1" label="Default Audio"></el-checkbox>
|
| 100 |
+
<el-upload
|
| 101 |
+
v-model:file-list="fileList"
|
| 102 |
+
action=""
|
| 103 |
+
:multiple="false"
|
| 104 |
+
:on-change="handleChangeFile"
|
| 105 |
+
:auto-upload="false"
|
| 106 |
+
:show-file-list="false"
|
| 107 |
+
:disabled="isCalling"
|
| 108 |
+
accept="audio/*"
|
| 109 |
+
>
|
| 110 |
+
<el-checkbox :value="2">
|
| 111 |
+
<!-- <span>Customization: Upload Audio</span> -->
|
| 112 |
+
<span>Customization</span>
|
| 113 |
+
<SvgIcon name="upload" className="checkbox-icon" />
|
| 114 |
+
</el-checkbox>
|
| 115 |
+
</el-upload>
|
| 116 |
+
</el-checkbox-group>
|
| 117 |
+
</div>
|
| 118 |
+
</div>
|
| 119 |
+
<div class="file-content" v-if="fileName">
|
| 120 |
+
<SvgIcon name="document" class="document-icon" />
|
| 121 |
+
<span class="file-name">{{ fileName }}</span>
|
| 122 |
+
</div>
|
| 123 |
+
</div>
|
| 124 |
+
</div>
|
| 125 |
+
</template>
|
| 126 |
+
|
| 127 |
+
<script setup>
|
| 128 |
+
const isCalling = defineModel('isCalling');
|
| 129 |
+
const type = defineModel('type');
|
| 130 |
+
|
| 131 |
+
let defaultVoiceClonePrompt =
|
| 132 |
+
'你是一个AI助手。你能接受视频,音频和文本输入并输出语音和文本。模仿输入音频中的声音特征。';
|
| 133 |
+
let defaultAssistantPrompt = '作为助手,你将使用这种声音风格说话。';
|
| 134 |
+
|
| 135 |
+
const fileList = ref([]);
|
| 136 |
+
const fileName = ref('');
|
| 137 |
+
|
| 138 |
+
const configData = ref({
|
| 139 |
+
canStopByVoice: false,
|
| 140 |
+
videoQuality: false,
|
| 141 |
+
useAudioPrompt: true,
|
| 142 |
+
vadThreshold: 0.8,
|
| 143 |
+
voiceClonePrompt: defaultVoiceClonePrompt,
|
| 144 |
+
assistantPrompt: defaultAssistantPrompt,
|
| 145 |
+
timbre: [1],
|
| 146 |
+
audioFormat: 'mp3',
|
| 147 |
+
base64Str: '',
|
| 148 |
+
timbreId: ''
|
| 149 |
+
});
|
| 150 |
+
|
| 151 |
+
const peopleList = [
|
| 152 |
+
{
|
| 153 |
+
id: 1,
|
| 154 |
+
name: 'Trump',
|
| 155 |
+
voiceClonePrompt: '',
|
| 156 |
+
assistantPrompt: ''
|
| 157 |
+
},
|
| 158 |
+
{
|
| 159 |
+
id: 2,
|
| 160 |
+
name: '说相声',
|
| 161 |
+
voiceClonePrompt: '克隆音频提示中的音色以生成语音',
|
| 162 |
+
assistantPrompt: '请角色扮演这段音频,请以相声演员的口吻说话'
|
| 163 |
+
},
|
| 164 |
+
{
|
| 165 |
+
id: 3,
|
| 166 |
+
name: '默认',
|
| 167 |
+
voiceClonePrompt: defaultVoiceClonePrompt,
|
| 168 |
+
assistantPrompt: defaultAssistantPrompt
|
| 169 |
+
}
|
| 170 |
+
];
|
| 171 |
+
watch(
|
| 172 |
+
() => type.value,
|
| 173 |
+
val => {
|
| 174 |
+
if (val === 'video') {
|
| 175 |
+
console.log('val: ', val);
|
| 176 |
+
defaultVoiceClonePrompt =
|
| 177 |
+
'你是一个AI助手。你能接受视频,音频和文本输入并输出语音和文本。模仿输入音频中的声音特征。';
|
| 178 |
+
defaultAssistantPrompt = '作为助手,你将使用这种声音风格说话。';
|
| 179 |
+
} else {
|
| 180 |
+
defaultVoiceClonePrompt = '克隆音频提示中的音色以生成语音。';
|
| 181 |
+
defaultAssistantPrompt = 'Your task is to be a helpful assistant using this voice pattern.';
|
| 182 |
+
}
|
| 183 |
+
configData.value.voiceClonePrompt = defaultVoiceClonePrompt;
|
| 184 |
+
configData.value.assistantPrompt = defaultAssistantPrompt;
|
| 185 |
+
},
|
| 186 |
+
{ immediate: true }
|
| 187 |
+
);
|
| 188 |
+
onMounted(() => {
|
| 189 |
+
handleSetStorage();
|
| 190 |
+
});
|
| 191 |
+
const handleSelectTimbre = e => {
|
| 192 |
+
if (e.length > 1) {
|
| 193 |
+
const val = e[e.length - 1];
|
| 194 |
+
configData.value.timbre = [val];
|
| 195 |
+
// 默认音色
|
| 196 |
+
if (val === 1) {
|
| 197 |
+
configData.value.audioFormat = 'mp3';
|
| 198 |
+
configData.value.base64Str = '';
|
| 199 |
+
fileList.value = [];
|
| 200 |
+
fileName.value = '';
|
| 201 |
+
}
|
| 202 |
+
}
|
| 203 |
+
};
|
| 204 |
+
const handleChangeFile = file => {
|
| 205 |
+
if (isAudio(file) && sizeNotExceed(file)) {
|
| 206 |
+
fileList.value = [file];
|
| 207 |
+
fileName.value = file.name;
|
| 208 |
+
configData.value.timbre = [2];
|
| 209 |
+
handleUpload();
|
| 210 |
+
} else {
|
| 211 |
+
ElMessage.error('Please upload audio file and size not exceed 10MB');
|
| 212 |
+
}
|
| 213 |
+
};
|
| 214 |
+
const isAudio = file => {
|
| 215 |
+
return file.raw.type.includes('audio');
|
| 216 |
+
};
|
| 217 |
+
const sizeNotExceed = file => {
|
| 218 |
+
return file.size / 1024 / 1024 <= 10;
|
| 219 |
+
};
|
| 220 |
+
const handleUpload = async () => {
|
| 221 |
+
const file = fileList.value[0].raw;
|
| 222 |
+
if (file) {
|
| 223 |
+
const reader = new FileReader();
|
| 224 |
+
reader.onload = e => {
|
| 225 |
+
const base64String = e.target.result.split(',')[1];
|
| 226 |
+
configData.value.audioFormat = file.name.split('.')[1];
|
| 227 |
+
configData.value.base64Str = base64String;
|
| 228 |
+
};
|
| 229 |
+
reader.readAsDataURL(file);
|
| 230 |
+
}
|
| 231 |
+
};
|
| 232 |
+
const handleSelectUseAudioPrompt = val => {
|
| 233 |
+
if (val) {
|
| 234 |
+
configData.value.voiceClonePrompt = defaultVoiceClonePrompt;
|
| 235 |
+
configData.value.assistantPrompt = defaultAssistantPrompt;
|
| 236 |
+
}
|
| 237 |
+
};
|
| 238 |
+
// 配置发生变化,更新到localstorage中
|
| 239 |
+
watch(configData.value, () => {
|
| 240 |
+
handleSetStorage();
|
| 241 |
+
});
|
| 242 |
+
const handleSetStorage = () => {
|
| 243 |
+
const { timbre, canStopByVoice, ...others } = configData.value;
|
| 244 |
+
const defaultConfigData = {
|
| 245 |
+
canStopByVoice,
|
| 246 |
+
...others
|
| 247 |
+
};
|
| 248 |
+
localStorage.setItem('configData', JSON.stringify(defaultConfigData));
|
| 249 |
+
localStorage.setItem('canStopByVoice', canStopByVoice);
|
| 250 |
+
};
|
| 251 |
+
const handleChangePeople = val => {
|
| 252 |
+
console.log('val: ', val);
|
| 253 |
+
if (!val) {
|
| 254 |
+
return;
|
| 255 |
+
}
|
| 256 |
+
const index = peopleList.findIndex(item => item.id === val);
|
| 257 |
+
configData.value.voiceClonePrompt = peopleList[index].voiceClonePrompt;
|
| 258 |
+
configData.value.assistantPrompt = peopleList[index].assistantPrompt;
|
| 259 |
+
configData.value.timbre = [1];
|
| 260 |
+
};
|
| 261 |
+
</script>
|
| 262 |
+
<style lang="less">
|
| 263 |
+
.user-config {
|
| 264 |
+
&-title {
|
| 265 |
+
height: 61px;
|
| 266 |
+
padding: 18px 18px 0;
|
| 267 |
+
color: rgba(23, 23, 23, 0.9);
|
| 268 |
+
font-family: PingFang SC;
|
| 269 |
+
font-size: 16px;
|
| 270 |
+
font-style: normal;
|
| 271 |
+
font-weight: 500;
|
| 272 |
+
line-height: normal;
|
| 273 |
+
}
|
| 274 |
+
.config-item {
|
| 275 |
+
display: flex;
|
| 276 |
+
align-items: center;
|
| 277 |
+
width: 100%;
|
| 278 |
+
padding: 0 0 0 18px;
|
| 279 |
+
margin-bottom: 12px;
|
| 280 |
+
&-label {
|
| 281 |
+
width: 120px;
|
| 282 |
+
flex-shrink: 0;
|
| 283 |
+
}
|
| 284 |
+
&-content {
|
| 285 |
+
flex: 1;
|
| 286 |
+
margin-left: 16px;
|
| 287 |
+
.el-radio-group {
|
| 288 |
+
.el-radio {
|
| 289 |
+
width: 50px;
|
| 290 |
+
}
|
| 291 |
+
}
|
| 292 |
+
}
|
| 293 |
+
&-content.vad-slider {
|
| 294 |
+
width: 80%;
|
| 295 |
+
padding-left: 7px;
|
| 296 |
+
margin-right: 20px;
|
| 297 |
+
.el-slider__button {
|
| 298 |
+
width: 14px;
|
| 299 |
+
height: 14px;
|
| 300 |
+
}
|
| 301 |
+
}
|
| 302 |
+
}
|
| 303 |
+
.timbre-config {
|
| 304 |
+
padding: 0 0 0 18px;
|
| 305 |
+
&-label {
|
| 306 |
+
margin-bottom: 12px;
|
| 307 |
+
}
|
| 308 |
+
&-content {
|
| 309 |
+
display: flex;
|
| 310 |
+
align-items: center;
|
| 311 |
+
.el-checkbox-group {
|
| 312 |
+
display: flex;
|
| 313 |
+
flex-wrap: wrap;
|
| 314 |
+
flex: 1;
|
| 315 |
+
> .el-checkbox {
|
| 316 |
+
margin-right: 12px;
|
| 317 |
+
}
|
| 318 |
+
}
|
| 319 |
+
.el-checkbox {
|
| 320 |
+
padding: 8px 16px;
|
| 321 |
+
border-radius: 10px;
|
| 322 |
+
background: #eaefff;
|
| 323 |
+
margin-bottom: 12px;
|
| 324 |
+
height: 40px;
|
| 325 |
+
.el-checkbox__input {
|
| 326 |
+
.el-checkbox__inner {
|
| 327 |
+
border: 1px solid #4dc100;
|
| 328 |
+
}
|
| 329 |
+
}
|
| 330 |
+
.el-checkbox__input.is-checked {
|
| 331 |
+
.el-checkbox__inner {
|
| 332 |
+
background: #4dc100;
|
| 333 |
+
}
|
| 334 |
+
}
|
| 335 |
+
.el-checkbox__input.is-checked.is-disabled {
|
| 336 |
+
.el-checkbox__inner::after {
|
| 337 |
+
border-color: #ffffff;
|
| 338 |
+
}
|
| 339 |
+
}
|
| 340 |
+
}
|
| 341 |
+
.el-checkbox__label {
|
| 342 |
+
color: #7579eb !important;
|
| 343 |
+
font-family: PingFang SC;
|
| 344 |
+
font-size: 16px;
|
| 345 |
+
font-style: normal;
|
| 346 |
+
font-weight: 400;
|
| 347 |
+
line-height: normal;
|
| 348 |
+
display: flex;
|
| 349 |
+
align-items: center;
|
| 350 |
+
.checkbox-icon {
|
| 351 |
+
margin-left: 4px;
|
| 352 |
+
}
|
| 353 |
+
}
|
| 354 |
+
.el-checkbox + .el-checkbox {
|
| 355 |
+
margin-left: 12px;
|
| 356 |
+
}
|
| 357 |
+
}
|
| 358 |
+
}
|
| 359 |
+
.prompt-item {
|
| 360 |
+
// padding: 0 0 0 18px;
|
| 361 |
+
margin-bottom: 12px;
|
| 362 |
+
&-label {
|
| 363 |
+
// margin-bottom: 16px;
|
| 364 |
+
}
|
| 365 |
+
}
|
| 366 |
+
.file-content {
|
| 367 |
+
padding: 0 0 0 18px;
|
| 368 |
+
font-size: 14px;
|
| 369 |
+
display: flex;
|
| 370 |
+
align-items: center;
|
| 371 |
+
.document-icon {
|
| 372 |
+
width: 16px;
|
| 373 |
+
height: 16px;
|
| 374 |
+
margin-right: 4px;
|
| 375 |
+
}
|
| 376 |
+
.file-name {
|
| 377 |
+
flex: 1;
|
| 378 |
+
overflow: hidden;
|
| 379 |
+
white-space: nowrap;
|
| 380 |
+
text-overflow: ellipsis;
|
| 381 |
+
}
|
| 382 |
+
}
|
| 383 |
+
.timbre-model {
|
| 384 |
+
padding: 0 0 0 18px;
|
| 385 |
+
margin-bottom: 12px;
|
| 386 |
+
display: flex;
|
| 387 |
+
align-items: center;
|
| 388 |
+
&-label {
|
| 389 |
+
width: 120px;
|
| 390 |
+
flex-shrink: 0;
|
| 391 |
+
}
|
| 392 |
+
&-content {
|
| 393 |
+
flex: 1;
|
| 394 |
+
margin-left: 16px;
|
| 395 |
+
}
|
| 396 |
+
}
|
| 397 |
+
.voice-prompt-box {
|
| 398 |
+
border: 1px solid #eaefff;
|
| 399 |
+
margin-left: 18px;
|
| 400 |
+
padding: 12px;
|
| 401 |
+
width: 50%;
|
| 402 |
+
}
|
| 403 |
+
}
|
| 404 |
+
</style>
|
r1-a/response_generation/minicpm/MiniCPM-o/web_demos/minicpm-o_2.6/web_server/src/components/ModelConfig/index.vue
ADDED
|
@@ -0,0 +1,456 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<template>
|
| 2 |
+
<div :class="`user-config ${t('modelConfigTitle') === '模型配置' ? '' : 'en-user-config'}`">
|
| 3 |
+
<div class="user-config-title">{{ t('modelConfigTitle') }}</div>
|
| 4 |
+
<div class="config-item">
|
| 5 |
+
<div class="config-item-label">
|
| 6 |
+
<span>{{ t('audioInterruptionBtn') }}</span>
|
| 7 |
+
<el-tooltip class="box-item" effect="dark" :content="t('audioInterruptionTips')" placement="top">
|
| 8 |
+
<SvgIcon name="question" class="question-icon" /> </el-tooltip
|
| 9 |
+
>:
|
| 10 |
+
</div>
|
| 11 |
+
<div class="config-item-content">
|
| 12 |
+
<el-switch
|
| 13 |
+
v-model="configData.canStopByVoice"
|
| 14 |
+
inline-prompt
|
| 15 |
+
:active-text="t('yes')"
|
| 16 |
+
:inactive-text="t('no')"
|
| 17 |
+
size="small"
|
| 18 |
+
:disabled="isCalling"
|
| 19 |
+
/>
|
| 20 |
+
</div>
|
| 21 |
+
</div>
|
| 22 |
+
<div class="config-item" v-if="type === 'video'">
|
| 23 |
+
<div class="config-item-label">
|
| 24 |
+
<span>{{ t('videoQualityBtn') }}</span>
|
| 25 |
+
<el-tooltip class="box-item" effect="dark" :content="t('videoQualityTips')" placement="top">
|
| 26 |
+
<SvgIcon name="question" class="question-icon" /> </el-tooltip
|
| 27 |
+
>:
|
| 28 |
+
</div>
|
| 29 |
+
<div class="config-item-content">
|
| 30 |
+
<el-switch
|
| 31 |
+
v-model="configData.videoQuality"
|
| 32 |
+
inline-prompt
|
| 33 |
+
:active-text="t('yes')"
|
| 34 |
+
:inactive-text="t('no')"
|
| 35 |
+
size="small"
|
| 36 |
+
:disabled="isCalling"
|
| 37 |
+
/>
|
| 38 |
+
</div>
|
| 39 |
+
</div>
|
| 40 |
+
<div class="config-item">
|
| 41 |
+
<div class="config-item-label">
|
| 42 |
+
<span>{{ t('vadThresholdBtn') }}</span>
|
| 43 |
+
<el-tooltip class="box-item" effect="dark" :content="t('vadThresholdTips')" placement="top">
|
| 44 |
+
<SvgIcon name="question" class="question-icon" /> </el-tooltip
|
| 45 |
+
>:
|
| 46 |
+
</div>
|
| 47 |
+
<div class="config-item-content vad-slider">
|
| 48 |
+
<el-slider
|
| 49 |
+
v-model="configData.vadThreshold"
|
| 50 |
+
:min="0.5"
|
| 51 |
+
:max="1"
|
| 52 |
+
:step="0.1"
|
| 53 |
+
size="small"
|
| 54 |
+
:disabled="isCalling"
|
| 55 |
+
/>
|
| 56 |
+
</div>
|
| 57 |
+
</div>
|
| 58 |
+
<div class="prompt-item" v-if="type === 'voice'">
|
| 59 |
+
<div class="prompt-item-label">
|
| 60 |
+
<span>{{ t('assistantPromptBtn') }}</span>
|
| 61 |
+
<el-tooltip class="box-item" effect="dark" :content="t('assistantPromptTips')" placement="top">
|
| 62 |
+
<SvgIcon name="question" class="question-icon" /> </el-tooltip
|
| 63 |
+
>:
|
| 64 |
+
</div>
|
| 65 |
+
<div class="prompt-item-content">
|
| 66 |
+
<el-input
|
| 67 |
+
type="textarea"
|
| 68 |
+
:rows="3"
|
| 69 |
+
v-model="configData.assistantPrompt"
|
| 70 |
+
resize="none"
|
| 71 |
+
:disabled="isCalling"
|
| 72 |
+
/>
|
| 73 |
+
</div>
|
| 74 |
+
</div>
|
| 75 |
+
<!-- <div class="config-item">
|
| 76 |
+
<div class="config-item-label">{{ t('useVoicePromptBtn') }}:</div>
|
| 77 |
+
<div class="config-item-content">
|
| 78 |
+
<el-switch
|
| 79 |
+
v-model="configData.useAudioPrompt"
|
| 80 |
+
inline-prompt
|
| 81 |
+
:active-text="t('yes')"
|
| 82 |
+
:inactive-text="t('no')"
|
| 83 |
+
size="small"
|
| 84 |
+
:disabled="isCalling"
|
| 85 |
+
@change="handleSelectUseAudioPrompt"
|
| 86 |
+
/>
|
| 87 |
+
</div>
|
| 88 |
+
</div> -->
|
| 89 |
+
<div class="timbre-model">
|
| 90 |
+
<div class="timbre-model-label">
|
| 91 |
+
<span>{{ t('toneColorOptions') }}</span>
|
| 92 |
+
<el-tooltip class="box-item" effect="dark" :content="t('toneColorOptionsTips')" placement="top">
|
| 93 |
+
<SvgIcon name="question" class="question-icon" /> </el-tooltip
|
| 94 |
+
>:
|
| 95 |
+
</div>
|
| 96 |
+
<div class="timbre-model-content">
|
| 97 |
+
<el-select
|
| 98 |
+
v-model="configData.useAudioPrompt"
|
| 99 |
+
style="width: 100%"
|
| 100 |
+
@change="handleChangePeople"
|
| 101 |
+
placeholder="请选择"
|
| 102 |
+
:disabled="isCalling"
|
| 103 |
+
>
|
| 104 |
+
<el-option :value="0" :label="t('nullOption')">{{ t('nullOption') }}</el-option>
|
| 105 |
+
<el-option :value="1" :label="t('defaultOption')">{{ t('defaultOption') }}</el-option>
|
| 106 |
+
<el-option :value="2" :label="t('femaleOption')">{{ t('femaleOption') }}</el-option>
|
| 107 |
+
<el-option :value="3" :label="t('maleOption')">{{ t('maleOption') }}</el-option>
|
| 108 |
+
</el-select>
|
| 109 |
+
</div>
|
| 110 |
+
</div>
|
| 111 |
+
<!-- <div class="prompt-item">
|
| 112 |
+
<div class="prompt-item-label">
|
| 113 |
+
<span>{{ t('voiceClonePromptInput') }}</span>
|
| 114 |
+
<el-tooltip class="box-item" effect="dark" :content="t('voiceClonePromptTips')" placement="top">
|
| 115 |
+
<SvgIcon name="question" class="question-icon" /> </el-tooltip
|
| 116 |
+
>:
|
| 117 |
+
</div>
|
| 118 |
+
<div class="prompt-item-content">
|
| 119 |
+
<el-input
|
| 120 |
+
type="textarea"
|
| 121 |
+
:rows="3"
|
| 122 |
+
v-model="configData.voiceClonePrompt"
|
| 123 |
+
resize="none"
|
| 124 |
+
:disabled="true"
|
| 125 |
+
/>
|
| 126 |
+
</div>
|
| 127 |
+
</div> -->
|
| 128 |
+
<!-- <div class="timbre-config" v-if="configData.useAudioPrompt">
|
| 129 |
+
<div class="timbre-config-label">{{ t('audioChoiceBtn') }}:</div>
|
| 130 |
+
<div class="timbre-config-content">
|
| 131 |
+
<el-checkbox-group v-model="configData.timbre" @change="handleSelectTimbre" :disabled="isCalling">
|
| 132 |
+
<el-checkbox :value="1" :label="t('defaultAudioBtn')"></el-checkbox>
|
| 133 |
+
<el-upload
|
| 134 |
+
v-model:file-list="fileList"
|
| 135 |
+
action=""
|
| 136 |
+
:multiple="false"
|
| 137 |
+
:on-change="handleChangeFile"
|
| 138 |
+
:auto-upload="false"
|
| 139 |
+
:show-file-list="false"
|
| 140 |
+
:disabled="isCalling"
|
| 141 |
+
accept="audio/*"
|
| 142 |
+
>
|
| 143 |
+
<el-checkbox :value="2">
|
| 144 |
+
<span>{{ t('customizationBtn') }}</span>
|
| 145 |
+
<SvgIcon name="upload" className="checkbox-icon" />
|
| 146 |
+
</el-checkbox>
|
| 147 |
+
</el-upload>
|
| 148 |
+
</el-checkbox-group>
|
| 149 |
+
</div>
|
| 150 |
+
</div>
|
| 151 |
+
<div class="file-content" v-if="fileName">
|
| 152 |
+
<SvgIcon name="document" class="document-icon" />
|
| 153 |
+
<span class="file-name">{{ fileName }}</span>
|
| 154 |
+
</div> -->
|
| 155 |
+
</div>
|
| 156 |
+
</template>
|
| 157 |
+
|
| 158 |
+
<script setup>
|
| 159 |
+
const isCalling = defineModel('isCalling');
|
| 160 |
+
const type = defineModel('type');
|
| 161 |
+
import { useI18n } from 'vue-i18n';
|
| 162 |
+
|
| 163 |
+
const { t, locale } = useI18n();
|
| 164 |
+
|
| 165 |
+
let defaultVoiceClonePrompt =
|
| 166 |
+
'你是一个AI助手。你能接受视频,音频和文本输入并输出语音和文本。模仿输入音频中的声音特征。';
|
| 167 |
+
let defaultAssistantPrompt = '';
|
| 168 |
+
|
| 169 |
+
const fileList = ref([]);
|
| 170 |
+
const fileName = ref('');
|
| 171 |
+
|
| 172 |
+
const configData = ref({
|
| 173 |
+
canStopByVoice: false,
|
| 174 |
+
videoQuality: false,
|
| 175 |
+
useAudioPrompt: 1,
|
| 176 |
+
vadThreshold: 0.8,
|
| 177 |
+
voiceClonePrompt: defaultVoiceClonePrompt,
|
| 178 |
+
assistantPrompt: defaultAssistantPrompt,
|
| 179 |
+
timbre: [1],
|
| 180 |
+
audioFormat: 'mp3',
|
| 181 |
+
base64Str: ''
|
| 182 |
+
});
|
| 183 |
+
|
| 184 |
+
// let peopleList = [];
|
| 185 |
+
// watch(
|
| 186 |
+
// () => type.value,
|
| 187 |
+
// val => {
|
| 188 |
+
// console.log('val: ', val);
|
| 189 |
+
// if (val === 'video') {
|
| 190 |
+
// defaultVoiceClonePrompt =
|
| 191 |
+
// '你是一个AI助手。你能接受视频,音频和文本输入并输出语音和文本。模仿输入音频中的声音特征。';
|
| 192 |
+
// defaultAssistantPrompt = '作为助手,你将使用这种声音风格说话。';
|
| 193 |
+
// } else {
|
| 194 |
+
// defaultVoiceClonePrompt = '克隆音频提示中的音色以生成语音。';
|
| 195 |
+
// defaultAssistantPrompt = 'Your task is to be a helpful assistant using this voice pattern.';
|
| 196 |
+
// }
|
| 197 |
+
// configData.value.voiceClonePrompt = defaultVoiceClonePrompt;
|
| 198 |
+
// configData.value.assistantPrompt = defaultAssistantPrompt;
|
| 199 |
+
// },
|
| 200 |
+
// { immediate: true }
|
| 201 |
+
// );
|
| 202 |
+
watch(
|
| 203 |
+
locale,
|
| 204 |
+
(newLocale, oldLocale) => {
|
| 205 |
+
console.log(`Language switched from ${oldLocale} to ${newLocale}`);
|
| 206 |
+
if (newLocale === 'zh' && type.value === 'video') {
|
| 207 |
+
defaultAssistantPrompt = '作为助手,你将使用这种声音风格说话。';
|
| 208 |
+
} else if (newLocale === 'zh' && type.value === 'voice') {
|
| 209 |
+
defaultAssistantPrompt = '作为助手,你将使用这种声音风格说话。';
|
| 210 |
+
} else if (newLocale === 'en' && type.value === 'video') {
|
| 211 |
+
defaultAssistantPrompt = 'As an assistant, you will speak using this voice style.';
|
| 212 |
+
} else {
|
| 213 |
+
defaultAssistantPrompt = 'As an assistant, you will speak using this voice style.';
|
| 214 |
+
}
|
| 215 |
+
configData.value.assistantPrompt = defaultAssistantPrompt;
|
| 216 |
+
},
|
| 217 |
+
{ immediate: true }
|
| 218 |
+
);
|
| 219 |
+
onMounted(() => {
|
| 220 |
+
handleSetStorage();
|
| 221 |
+
});
|
| 222 |
+
const handleSelectTimbre = e => {
|
| 223 |
+
if (e.length > 1) {
|
| 224 |
+
const val = e[e.length - 1];
|
| 225 |
+
configData.value.timbre = [val];
|
| 226 |
+
// 默认音色
|
| 227 |
+
if (val === 1) {
|
| 228 |
+
configData.value.audioFormat = 'mp3';
|
| 229 |
+
configData.value.base64Str = '';
|
| 230 |
+
fileList.value = [];
|
| 231 |
+
fileName.value = '';
|
| 232 |
+
}
|
| 233 |
+
}
|
| 234 |
+
};
|
| 235 |
+
const handleChangeFile = file => {
|
| 236 |
+
if (isAudio(file) && sizeNotExceed(file)) {
|
| 237 |
+
fileList.value = [file];
|
| 238 |
+
fileName.value = file.name;
|
| 239 |
+
configData.value.timbre = [2];
|
| 240 |
+
handleUpload();
|
| 241 |
+
} else {
|
| 242 |
+
ElMessage.error('Please upload audio file and size not exceed 10MB');
|
| 243 |
+
}
|
| 244 |
+
};
|
| 245 |
+
const isAudio = file => {
|
| 246 |
+
return file.raw.type.includes('audio');
|
| 247 |
+
};
|
| 248 |
+
const sizeNotExceed = file => {
|
| 249 |
+
return file.size / 1024 / 1024 <= 10;
|
| 250 |
+
};
|
| 251 |
+
const handleUpload = async () => {
|
| 252 |
+
const file = fileList.value[0].raw;
|
| 253 |
+
if (file) {
|
| 254 |
+
const reader = new FileReader();
|
| 255 |
+
reader.onload = e => {
|
| 256 |
+
const base64String = e.target.result.split(',')[1];
|
| 257 |
+
configData.value.audioFormat = file.name.split('.')[1];
|
| 258 |
+
configData.value.base64Str = base64String;
|
| 259 |
+
};
|
| 260 |
+
reader.readAsDataURL(file);
|
| 261 |
+
}
|
| 262 |
+
};
|
| 263 |
+
const handleSelectUseAudioPrompt = val => {
|
| 264 |
+
if (val) {
|
| 265 |
+
configData.value.voiceClonePrompt = defaultVoiceClonePrompt;
|
| 266 |
+
configData.value.assistantPrompt = defaultAssistantPrompt;
|
| 267 |
+
}
|
| 268 |
+
};
|
| 269 |
+
// 配置发生变化,更新到localstorage中
|
| 270 |
+
watch(configData.value, () => {
|
| 271 |
+
handleSetStorage();
|
| 272 |
+
});
|
| 273 |
+
const handleSetStorage = () => {
|
| 274 |
+
const { timbre, canStopByVoice, ...others } = configData.value;
|
| 275 |
+
const defaultConfigData = {
|
| 276 |
+
canStopByVoice,
|
| 277 |
+
...others
|
| 278 |
+
};
|
| 279 |
+
localStorage.setItem('configData', JSON.stringify(defaultConfigData));
|
| 280 |
+
localStorage.setItem('canStopByVoice', canStopByVoice);
|
| 281 |
+
};
|
| 282 |
+
const handleChangePeople = val => {
|
| 283 |
+
console.log('val: ', val);
|
| 284 |
+
// const index = peopleList.findIndex(item => item.id === val);
|
| 285 |
+
configData.value.voiceClonePrompt = defaultVoiceClonePrompt;
|
| 286 |
+
configData.value.assistantPrompt = defaultAssistantPrompt;
|
| 287 |
+
configData.value.timbre = [1];
|
| 288 |
+
};
|
| 289 |
+
</script>
|
| 290 |
+
<style lang="less" scoped>
|
| 291 |
+
.user-config {
|
| 292 |
+
&-title {
|
| 293 |
+
height: 61px;
|
| 294 |
+
padding: 18px 18px 0;
|
| 295 |
+
color: rgba(23, 23, 23, 0.9);
|
| 296 |
+
font-family: PingFang SC;
|
| 297 |
+
font-size: 16px;
|
| 298 |
+
font-style: normal;
|
| 299 |
+
font-weight: 500;
|
| 300 |
+
line-height: normal;
|
| 301 |
+
}
|
| 302 |
+
.config-item {
|
| 303 |
+
display: flex;
|
| 304 |
+
align-items: center;
|
| 305 |
+
width: 100%;
|
| 306 |
+
padding: 0 0 0 18px;
|
| 307 |
+
margin-bottom: 20px;
|
| 308 |
+
&-label {
|
| 309 |
+
width: 120px;
|
| 310 |
+
flex-shrink: 0;
|
| 311 |
+
display: flex;
|
| 312 |
+
align-items: center;
|
| 313 |
+
}
|
| 314 |
+
&-content {
|
| 315 |
+
flex: 1;
|
| 316 |
+
margin-left: 16px;
|
| 317 |
+
.el-radio-group {
|
| 318 |
+
.el-radio {
|
| 319 |
+
width: 50px;
|
| 320 |
+
}
|
| 321 |
+
}
|
| 322 |
+
}
|
| 323 |
+
&-content.vad-slider {
|
| 324 |
+
width: 80%;
|
| 325 |
+
padding-left: 7px;
|
| 326 |
+
margin-right: 20px;
|
| 327 |
+
.el-slider__button {
|
| 328 |
+
width: 14px;
|
| 329 |
+
height: 14px;
|
| 330 |
+
}
|
| 331 |
+
}
|
| 332 |
+
}
|
| 333 |
+
.timbre-config {
|
| 334 |
+
padding: 0 0 0 18px;
|
| 335 |
+
&-label {
|
| 336 |
+
margin-bottom: 20px;
|
| 337 |
+
display: flex;
|
| 338 |
+
align-items: center;
|
| 339 |
+
}
|
| 340 |
+
&-content {
|
| 341 |
+
display: flex;
|
| 342 |
+
align-items: center;
|
| 343 |
+
.el-checkbox-group {
|
| 344 |
+
display: flex;
|
| 345 |
+
flex-wrap: wrap;
|
| 346 |
+
flex: 1;
|
| 347 |
+
> .el-checkbox {
|
| 348 |
+
margin-right: 12px;
|
| 349 |
+
}
|
| 350 |
+
}
|
| 351 |
+
.el-checkbox {
|
| 352 |
+
padding: 8px 16px;
|
| 353 |
+
border-radius: 10px;
|
| 354 |
+
background: #eaefff;
|
| 355 |
+
margin-bottom: 12px;
|
| 356 |
+
height: 40px;
|
| 357 |
+
.el-checkbox__input {
|
| 358 |
+
.el-checkbox__inner {
|
| 359 |
+
border: 1px solid #4dc100;
|
| 360 |
+
}
|
| 361 |
+
}
|
| 362 |
+
.el-checkbox__input.is-checked {
|
| 363 |
+
.el-checkbox__inner {
|
| 364 |
+
background: #4dc100;
|
| 365 |
+
}
|
| 366 |
+
}
|
| 367 |
+
.el-checkbox__input.is-checked.is-disabled {
|
| 368 |
+
.el-checkbox__inner::after {
|
| 369 |
+
border-color: #ffffff;
|
| 370 |
+
}
|
| 371 |
+
}
|
| 372 |
+
}
|
| 373 |
+
.el-checkbox__label {
|
| 374 |
+
color: #7579eb !important;
|
| 375 |
+
font-family: PingFang SC;
|
| 376 |
+
font-size: 16px;
|
| 377 |
+
font-style: normal;
|
| 378 |
+
font-weight: 400;
|
| 379 |
+
line-height: normal;
|
| 380 |
+
display: flex;
|
| 381 |
+
align-items: center;
|
| 382 |
+
.checkbox-icon {
|
| 383 |
+
margin-left: 4px;
|
| 384 |
+
}
|
| 385 |
+
}
|
| 386 |
+
.el-checkbox + .el-checkbox {
|
| 387 |
+
margin-left: 12px;
|
| 388 |
+
}
|
| 389 |
+
}
|
| 390 |
+
}
|
| 391 |
+
.prompt-item {
|
| 392 |
+
padding: 0 0 0 18px;
|
| 393 |
+
margin-bottom: 20px;
|
| 394 |
+
&-label {
|
| 395 |
+
// margin-bottom: 16px;
|
| 396 |
+
display: flex;
|
| 397 |
+
align-items: center;
|
| 398 |
+
}
|
| 399 |
+
}
|
| 400 |
+
.file-content {
|
| 401 |
+
padding: 0 0 0 18px;
|
| 402 |
+
font-size: 14px;
|
| 403 |
+
display: flex;
|
| 404 |
+
align-items: center;
|
| 405 |
+
.document-icon {
|
| 406 |
+
width: 16px;
|
| 407 |
+
height: 16px;
|
| 408 |
+
margin-right: 4px;
|
| 409 |
+
}
|
| 410 |
+
.file-name {
|
| 411 |
+
flex: 1;
|
| 412 |
+
overflow: hidden;
|
| 413 |
+
white-space: nowrap;
|
| 414 |
+
text-overflow: ellipsis;
|
| 415 |
+
}
|
| 416 |
+
}
|
| 417 |
+
.timbre-model {
|
| 418 |
+
padding: 0 0 0 18px;
|
| 419 |
+
margin-bottom: 20px;
|
| 420 |
+
display: flex;
|
| 421 |
+
align-items: center;
|
| 422 |
+
&-label {
|
| 423 |
+
width: 120px;
|
| 424 |
+
flex-shrink: 0;
|
| 425 |
+
display: flex;
|
| 426 |
+
align-items: center;
|
| 427 |
+
}
|
| 428 |
+
&-content {
|
| 429 |
+
flex: 1;
|
| 430 |
+
margin-left: 16px;
|
| 431 |
+
}
|
| 432 |
+
}
|
| 433 |
+
}
|
| 434 |
+
.en-user-config {
|
| 435 |
+
.config-item-label {
|
| 436 |
+
width: 160px;
|
| 437 |
+
}
|
| 438 |
+
.timbre-model-label {
|
| 439 |
+
width: 160px;
|
| 440 |
+
}
|
| 441 |
+
}
|
| 442 |
+
.question-icon {
|
| 443 |
+
width: 14px;
|
| 444 |
+
height: 14px;
|
| 445 |
+
cursor: pointer;
|
| 446 |
+
margin-left: 6px;
|
| 447 |
+
}
|
| 448 |
+
</style>
|
| 449 |
+
<style lang="less">
|
| 450 |
+
.el-switch--small .el-switch__core {
|
| 451 |
+
min-width: 50px;
|
| 452 |
+
}
|
| 453 |
+
.el-popper.is-dark {
|
| 454 |
+
max-width: 300px;
|
| 455 |
+
}
|
| 456 |
+
</style>
|
r1-a/response_generation/minicpm/MiniCPM-o/web_demos/minicpm-o_2.6/web_server/src/components/ModelOutput/index.vue
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<template>
|
| 2 |
+
<div class="output-area">
|
| 3 |
+
<div
|
| 4 |
+
:class="`output-area-item ${item.type === 'USER' ? 'user-item' : 'bot-item'}`"
|
| 5 |
+
:key="index"
|
| 6 |
+
v-for="(item, index) in outputData"
|
| 7 |
+
>
|
| 8 |
+
<div v-if="item.type === 'USER'" class="user-input">
|
| 9 |
+
<audio v-if="item.audio" :src="item.audio" controls></audio>
|
| 10 |
+
</div>
|
| 11 |
+
<div v-else class="bot-output">
|
| 12 |
+
<div class="output-item">{{ item.text }}</div>
|
| 13 |
+
<audio v-if="item.audio" :src="item.audio" controls></audio>
|
| 14 |
+
</div>
|
| 15 |
+
</div>
|
| 16 |
+
</div>
|
| 17 |
+
</template>
|
| 18 |
+
|
| 19 |
+
<script setup>
|
| 20 |
+
const props = defineProps({
|
| 21 |
+
outputData: {
|
| 22 |
+
type: Array,
|
| 23 |
+
default: () => []
|
| 24 |
+
},
|
| 25 |
+
containerClass: {
|
| 26 |
+
type: String,
|
| 27 |
+
default: ''
|
| 28 |
+
}
|
| 29 |
+
});
|
| 30 |
+
watch(
|
| 31 |
+
() => props.outputData,
|
| 32 |
+
newVal => {
|
| 33 |
+
nextTick(() => {
|
| 34 |
+
if (newVal && props.containerClass) {
|
| 35 |
+
let dom = document.querySelector(`.${props.containerClass}`);
|
| 36 |
+
if (dom) {
|
| 37 |
+
dom.scrollTop = dom.scrollHeight;
|
| 38 |
+
}
|
| 39 |
+
}
|
| 40 |
+
});
|
| 41 |
+
},
|
| 42 |
+
{ deep: true }
|
| 43 |
+
);
|
| 44 |
+
</script>
|
| 45 |
+
|
| 46 |
+
<style lang="less" scoped>
|
| 47 |
+
.output-area {
|
| 48 |
+
display: flex;
|
| 49 |
+
flex-direction: column;
|
| 50 |
+
&-item {
|
| 51 |
+
width: fit-content;
|
| 52 |
+
}
|
| 53 |
+
&-item + &-item {
|
| 54 |
+
margin-top: 16px;
|
| 55 |
+
}
|
| 56 |
+
&-item.user-item {
|
| 57 |
+
align-self: flex-end;
|
| 58 |
+
.user-input {
|
| 59 |
+
}
|
| 60 |
+
}
|
| 61 |
+
&-item.bot-item {
|
| 62 |
+
align-self: flex-start;
|
| 63 |
+
width: 100%;
|
| 64 |
+
.bot-output {
|
| 65 |
+
width: 100%;
|
| 66 |
+
display: flex;
|
| 67 |
+
flex-direction: column;
|
| 68 |
+
.output-item {
|
| 69 |
+
padding: 8px 24px;
|
| 70 |
+
border-radius: 10px;
|
| 71 |
+
color: #202224;
|
| 72 |
+
background: #f3f3f3;
|
| 73 |
+
max-width: 90%;
|
| 74 |
+
width: fit-content;
|
| 75 |
+
font-family: PingFang SC;
|
| 76 |
+
font-size: 16px;
|
| 77 |
+
font-style: normal;
|
| 78 |
+
font-weight: 400;
|
| 79 |
+
line-height: normal;
|
| 80 |
+
word-break: break-all;
|
| 81 |
+
word-wrap: break-word;
|
| 82 |
+
white-space: pre-wrap;
|
| 83 |
+
display: inline-block;
|
| 84 |
+
}
|
| 85 |
+
.output-item + audio {
|
| 86 |
+
margin-top: 16px;
|
| 87 |
+
}
|
| 88 |
+
}
|
| 89 |
+
}
|
| 90 |
+
}
|
| 91 |
+
</style>
|
r1-a/response_generation/minicpm/MiniCPM-o/web_demos/minicpm-o_2.6/web_server/src/components/SelectTimbre/index.vue
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<template>
|
| 2 |
+
<div class="select-timbre">
|
| 3 |
+
<el-checkbox-group v-model="timbre" @change="handleSelectTimbre" :disabled="disabled">
|
| 4 |
+
<el-checkbox :value="1" label="Default Audio"></el-checkbox>
|
| 5 |
+
<!-- <el-upload
|
| 6 |
+
v-model:file-list="fileList"
|
| 7 |
+
action=""
|
| 8 |
+
:multiple="false"
|
| 9 |
+
:on-change="handleChangeFile"
|
| 10 |
+
:auto-upload="false"
|
| 11 |
+
:show-file-list="false"
|
| 12 |
+
:disabled="disabled"
|
| 13 |
+
accept="audio/*"
|
| 14 |
+
>
|
| 15 |
+
<el-checkbox :value="2">
|
| 16 |
+
<span>Customization: Upload Audio</span>
|
| 17 |
+
<SvgIcon name="upload" className="checkbox-icon" />
|
| 18 |
+
</el-checkbox>
|
| 19 |
+
</el-upload> -->
|
| 20 |
+
</el-checkbox-group>
|
| 21 |
+
</div>
|
| 22 |
+
</template>
|
| 23 |
+
|
| 24 |
+
<script setup>
|
| 25 |
+
const timbre = defineModel('timbre');
|
| 26 |
+
const audioData = defineModel('audioData');
|
| 27 |
+
const disabled = defineModel('disabled');
|
| 28 |
+
const fileList = ref([]);
|
| 29 |
+
|
| 30 |
+
const handleSelectTimbre = e => {
|
| 31 |
+
if (e.length > 1) {
|
| 32 |
+
const val = e[e.length - 1];
|
| 33 |
+
timbre.value = [val];
|
| 34 |
+
// 默认音色
|
| 35 |
+
if (val === 1) {
|
| 36 |
+
audioData.value = {
|
| 37 |
+
base64Str: '',
|
| 38 |
+
type: 'mp3'
|
| 39 |
+
};
|
| 40 |
+
}
|
| 41 |
+
}
|
| 42 |
+
};
|
| 43 |
+
const handleChangeFile = file => {
|
| 44 |
+
if (isAudio(file) && sizeNotExceed(file)) {
|
| 45 |
+
fileList.value = [file];
|
| 46 |
+
timbre.value = [2];
|
| 47 |
+
handleUpload();
|
| 48 |
+
} else {
|
| 49 |
+
ElMessage.error('Please upload audio file and size not exceed 1MB');
|
| 50 |
+
}
|
| 51 |
+
};
|
| 52 |
+
const isAudio = file => {
|
| 53 |
+
return file.name.endsWith('.mp3') || file.name.endsWith('.wav');
|
| 54 |
+
};
|
| 55 |
+
const sizeNotExceed = file => {
|
| 56 |
+
return file.size / 1024 / 1024 <= 1;
|
| 57 |
+
};
|
| 58 |
+
const handleUpload = async () => {
|
| 59 |
+
const file = fileList.value[0].raw;
|
| 60 |
+
if (file) {
|
| 61 |
+
const reader = new FileReader();
|
| 62 |
+
reader.onload = e => {
|
| 63 |
+
const base64String = e.target.result.split(',')[1];
|
| 64 |
+
audioData.value = {
|
| 65 |
+
base64Str: base64String,
|
| 66 |
+
type: file.name.split('.')[1]
|
| 67 |
+
};
|
| 68 |
+
};
|
| 69 |
+
reader.readAsDataURL(file);
|
| 70 |
+
}
|
| 71 |
+
};
|
| 72 |
+
</script>
|
| 73 |
+
<style lang="less">
|
| 74 |
+
.select-timbre {
|
| 75 |
+
display: flex;
|
| 76 |
+
align-items: center;
|
| 77 |
+
.el-checkbox-group {
|
| 78 |
+
display: flex;
|
| 79 |
+
> .el-checkbox {
|
| 80 |
+
margin-right: 12px;
|
| 81 |
+
}
|
| 82 |
+
}
|
| 83 |
+
.el-checkbox {
|
| 84 |
+
padding: 8px 16px;
|
| 85 |
+
border-radius: 10px;
|
| 86 |
+
background: #eaefff;
|
| 87 |
+
margin-right: 0;
|
| 88 |
+
height: 40px;
|
| 89 |
+
.el-checkbox__input {
|
| 90 |
+
.el-checkbox__inner {
|
| 91 |
+
border: 1px solid #4dc100;
|
| 92 |
+
}
|
| 93 |
+
}
|
| 94 |
+
.el-checkbox__input.is-checked {
|
| 95 |
+
.el-checkbox__inner {
|
| 96 |
+
background: #4dc100;
|
| 97 |
+
}
|
| 98 |
+
}
|
| 99 |
+
.el-checkbox__input.is-checked.is-disabled {
|
| 100 |
+
.el-checkbox__inner::after {
|
| 101 |
+
border-color: #ffffff;
|
| 102 |
+
}
|
| 103 |
+
}
|
| 104 |
+
}
|
| 105 |
+
.el-checkbox__label {
|
| 106 |
+
color: #7579eb !important;
|
| 107 |
+
font-family: PingFang SC;
|
| 108 |
+
font-size: 16px;
|
| 109 |
+
font-style: normal;
|
| 110 |
+
font-weight: 400;
|
| 111 |
+
line-height: normal;
|
| 112 |
+
display: flex;
|
| 113 |
+
align-items: center;
|
| 114 |
+
.checkbox-icon {
|
| 115 |
+
margin-left: 4px;
|
| 116 |
+
}
|
| 117 |
+
}
|
| 118 |
+
.el-checkbox + .el-checkbox {
|
| 119 |
+
margin-left: 12px;
|
| 120 |
+
}
|
| 121 |
+
}
|
| 122 |
+
</style>
|
r1-a/response_generation/minicpm/MiniCPM-o/web_demos/minicpm-o_2.6/web_server/src/components/SkipBtn/index.vue
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<template>
|
| 2 |
+
<div :class="`skip-btn ${disabled ? 'disabled-btn' : ''}`">
|
| 3 |
+
<div class="pause-icon">
|
| 4 |
+
<SvgIcon name="pause" className="pause-svg" />
|
| 5 |
+
</div>
|
| 6 |
+
<span class="btn-text">{{ t('skipMessageBtn') }}</span>
|
| 7 |
+
</div>
|
| 8 |
+
</template>
|
| 9 |
+
<script setup>
|
| 10 |
+
import { useI18n } from 'vue-i18n';
|
| 11 |
+
|
| 12 |
+
const { t } = useI18n();
|
| 13 |
+
defineProps({
|
| 14 |
+
disabled: {
|
| 15 |
+
type: Boolean,
|
| 16 |
+
default: false
|
| 17 |
+
}
|
| 18 |
+
});
|
| 19 |
+
</script>
|
| 20 |
+
<style lang="less">
|
| 21 |
+
.skip-btn {
|
| 22 |
+
flex-shrink: 0;
|
| 23 |
+
display: flex;
|
| 24 |
+
align-items: center;
|
| 25 |
+
padding: 8px 14px 8px 10px;
|
| 26 |
+
border-radius: 90px;
|
| 27 |
+
background: #5865f2;
|
| 28 |
+
cursor: pointer;
|
| 29 |
+
user-select: none;
|
| 30 |
+
.pause-icon {
|
| 31 |
+
display: flex;
|
| 32 |
+
justify-content: center;
|
| 33 |
+
align-items: center;
|
| 34 |
+
width: 32px;
|
| 35 |
+
height: 32px;
|
| 36 |
+
background: #ffffff;
|
| 37 |
+
border-radius: 50%;
|
| 38 |
+
margin-right: 8px;
|
| 39 |
+
.pause-svg {
|
| 40 |
+
width: 18px;
|
| 41 |
+
height: 18px;
|
| 42 |
+
color: #5865f2;
|
| 43 |
+
}
|
| 44 |
+
}
|
| 45 |
+
.btn-text {
|
| 46 |
+
color: #fff;
|
| 47 |
+
font-family: PingFang SC;
|
| 48 |
+
font-size: 16px;
|
| 49 |
+
font-style: normal;
|
| 50 |
+
font-weight: 400;
|
| 51 |
+
line-height: normal;
|
| 52 |
+
}
|
| 53 |
+
}
|
| 54 |
+
.disabled-btn {
|
| 55 |
+
cursor: not-allowed;
|
| 56 |
+
background: #f3f3f3;
|
| 57 |
+
.pause-icon {
|
| 58 |
+
background: #d1d1d1;
|
| 59 |
+
.pause-svg {
|
| 60 |
+
color: #ffffff;
|
| 61 |
+
}
|
| 62 |
+
}
|
| 63 |
+
.btn-text {
|
| 64 |
+
color: #d1d1d1;
|
| 65 |
+
}
|
| 66 |
+
}
|
| 67 |
+
</style>
|
r1-a/response_generation/minicpm/MiniCPM-o/web_demos/minicpm-o_2.6/web_server/src/components/SvgIcon/index.vue
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<template>
|
| 2 |
+
<svg :class="iconClass" v-html="content"></svg>
|
| 3 |
+
</template>
|
| 4 |
+
|
| 5 |
+
<script setup>
|
| 6 |
+
const props = defineProps({
|
| 7 |
+
name: {
|
| 8 |
+
type: String,
|
| 9 |
+
required: true
|
| 10 |
+
},
|
| 11 |
+
className: {
|
| 12 |
+
type: String,
|
| 13 |
+
default: ''
|
| 14 |
+
}
|
| 15 |
+
});
|
| 16 |
+
|
| 17 |
+
const content = ref('');
|
| 18 |
+
|
| 19 |
+
const iconClass = computed(() => ['svg-icon', props.className]);
|
| 20 |
+
onMounted(() => {
|
| 21 |
+
import(`@/assets/svg/${props.name}.svg`)
|
| 22 |
+
.then(module => {
|
| 23 |
+
fetch(module.default)
|
| 24 |
+
.then(response => response.text())
|
| 25 |
+
.then(svg => {
|
| 26 |
+
content.value = svg;
|
| 27 |
+
});
|
| 28 |
+
})
|
| 29 |
+
.catch(error => {
|
| 30 |
+
console.error(`Error loading SVG icon: ${props.name}`, error);
|
| 31 |
+
});
|
| 32 |
+
});
|
| 33 |
+
</script>
|
| 34 |
+
<style lang="less" scoped>
|
| 35 |
+
.svg-icon {
|
| 36 |
+
width: 24px;
|
| 37 |
+
height: 24px;
|
| 38 |
+
}
|
| 39 |
+
</style>
|
r1-a/response_generation/minicpm/MiniCPM-o/web_demos/minicpm-o_2.6/web_server/src/components/Voice/index.vue
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<template>
|
| 2 |
+
<div class="bars" id="bars" :style="boxStyle">
|
| 3 |
+
<!-- 柱形条 -->
|
| 4 |
+
<div class="bar" v-for="(item, index) in defaultList" :key="index" :style="itemAttr(item)"></div>
|
| 5 |
+
</div>
|
| 6 |
+
</template>
|
| 7 |
+
|
| 8 |
+
<script setup>
|
| 9 |
+
const props = defineProps({
|
| 10 |
+
analyser: {
|
| 11 |
+
type: Object
|
| 12 |
+
},
|
| 13 |
+
dataArray: {
|
| 14 |
+
type: [Array, Uint8Array]
|
| 15 |
+
},
|
| 16 |
+
isCalling: {
|
| 17 |
+
type: Boolean,
|
| 18 |
+
default: false
|
| 19 |
+
},
|
| 20 |
+
isPlaying: {
|
| 21 |
+
type: Boolean,
|
| 22 |
+
default: false
|
| 23 |
+
},
|
| 24 |
+
// 容器高度
|
| 25 |
+
boxStyle: {
|
| 26 |
+
type: Object,
|
| 27 |
+
default: () => {
|
| 28 |
+
return {
|
| 29 |
+
height: '80px'
|
| 30 |
+
};
|
| 31 |
+
}
|
| 32 |
+
},
|
| 33 |
+
// 柱形条宽度
|
| 34 |
+
itemStyle: {
|
| 35 |
+
type: Object,
|
| 36 |
+
default: () => {
|
| 37 |
+
return {
|
| 38 |
+
width: '6px',
|
| 39 |
+
margin: '0 2px',
|
| 40 |
+
borderRadius: '5px'
|
| 41 |
+
};
|
| 42 |
+
}
|
| 43 |
+
},
|
| 44 |
+
configList: {
|
| 45 |
+
type: Array,
|
| 46 |
+
default: () => []
|
| 47 |
+
}
|
| 48 |
+
});
|
| 49 |
+
const animationFrameId = ref();
|
| 50 |
+
const defaultList = ref([]);
|
| 51 |
+
const bgColor = ref('#4c5cf8');
|
| 52 |
+
const itemAttr = computed(() => item => {
|
| 53 |
+
return {
|
| 54 |
+
height: item + 'px',
|
| 55 |
+
...props.itemStyle
|
| 56 |
+
};
|
| 57 |
+
});
|
| 58 |
+
watch(
|
| 59 |
+
() => props.dataArray,
|
| 60 |
+
newVal => {
|
| 61 |
+
if (newVal && props.isCalling) {
|
| 62 |
+
console.log('draw');
|
| 63 |
+
drawBars();
|
| 64 |
+
} else {
|
| 65 |
+
console.log('stop');
|
| 66 |
+
stopDraw();
|
| 67 |
+
}
|
| 68 |
+
}
|
| 69 |
+
);
|
| 70 |
+
watch(
|
| 71 |
+
() => props.configList,
|
| 72 |
+
newVal => {
|
| 73 |
+
if (newVal.length > 0) {
|
| 74 |
+
defaultList.value = newVal;
|
| 75 |
+
}
|
| 76 |
+
},
|
| 77 |
+
{ immediate: true }
|
| 78 |
+
);
|
| 79 |
+
watch(
|
| 80 |
+
() => props.isPlaying,
|
| 81 |
+
newVal => {
|
| 82 |
+
if (newVal) {
|
| 83 |
+
// 绿色
|
| 84 |
+
bgColor.value = '#4dc100';
|
| 85 |
+
} else {
|
| 86 |
+
// 蓝色
|
| 87 |
+
bgColor.value = '#4c5cf8';
|
| 88 |
+
}
|
| 89 |
+
}
|
| 90 |
+
);
|
| 91 |
+
function drawBars() {
|
| 92 |
+
const bars = document.querySelectorAll('.bar');
|
| 93 |
+
if (bars.length === 0) {
|
| 94 |
+
cancelAnimationFrame(animationFrameId.value);
|
| 95 |
+
return;
|
| 96 |
+
}
|
| 97 |
+
|
| 98 |
+
const maxHeight = document.querySelector('.bars').clientHeight; // 最大高度为容器的高度
|
| 99 |
+
|
| 100 |
+
const averageVolume = props.dataArray.reduce((sum, value) => sum + value, 0) / props.dataArray.length;
|
| 101 |
+
const normalizedVolume = props.isPlaying ? Math.random() : averageVolume / 128; // 将音量数据归一化为0到1之间
|
| 102 |
+
|
| 103 |
+
bars.forEach((bar, index) => {
|
| 104 |
+
const minHeight = defaultList.value[index];
|
| 105 |
+
const randomFactor = Math.random() * 1.5 + 0.5; // 随机因子
|
| 106 |
+
const newHeight = Math.min(
|
| 107 |
+
maxHeight,
|
| 108 |
+
minHeight + (maxHeight - minHeight) * normalizedVolume * randomFactor
|
| 109 |
+
); // 根据音量设置高度
|
| 110 |
+
bar.style.height = `${newHeight}px`; // 设置新的高度
|
| 111 |
+
bar.style.backgroundColor = bgColor.value;
|
| 112 |
+
});
|
| 113 |
+
|
| 114 |
+
animationFrameId.value = requestAnimationFrame(drawBars);
|
| 115 |
+
}
|
| 116 |
+
const stopDraw = () => {
|
| 117 |
+
if (animationFrameId.value) {
|
| 118 |
+
cancelAnimationFrame(animationFrameId.value);
|
| 119 |
+
}
|
| 120 |
+
};
|
| 121 |
+
</script>
|
| 122 |
+
|
| 123 |
+
<style lang="less" scoped>
|
| 124 |
+
.bars {
|
| 125 |
+
display: flex;
|
| 126 |
+
justify-content: center;
|
| 127 |
+
align-items: center;
|
| 128 |
+
}
|
| 129 |
+
.bar {
|
| 130 |
+
// width: 6px;
|
| 131 |
+
// margin: 0 2px;
|
| 132 |
+
background-color: #4c5cf8;
|
| 133 |
+
transition:
|
| 134 |
+
height 0.1s,
|
| 135 |
+
background-color 0.1s;
|
| 136 |
+
border-radius: 5px; /* 圆角 */
|
| 137 |
+
}
|
| 138 |
+
</style>
|
r1-a/response_generation/minicpm/MiniCPM-o/web_demos/minicpm-o_2.6/web_server/src/directives/index.js
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/**
|
| 2 |
+
* Configure and register global directives
|
| 3 |
+
*/
|
| 4 |
+
import ElTableInfiniteScroll from 'el-table-infinite-scroll';
|
| 5 |
+
|
| 6 |
+
export function setupGlobDirectives(app) {
|
| 7 |
+
app.use(ElTableInfiniteScroll);
|
| 8 |
+
}
|
r1-a/response_generation/minicpm/MiniCPM-o/web_demos/minicpm-o_2.6/web_server/src/enums/index.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
export const voiceIdeasList = ['TBD', 'TBD', 'TBD'];
|
| 2 |
+
export const videoIdeasList = ['TBD', 'TBD', 'TBD'];
|
| 3 |
+
export const limitTime = 10 * 60; // 限制单次使用时常不超过10分钟
|
| 4 |
+
export const tipsRemainingTime = 30; // 剩余30s时提醒用户
|
| 5 |
+
// 初始音频波形
|
| 6 |
+
export const voiceConfigList = [
|
| 7 |
+
16, 16, 16, 16, 36, 58, 50, 70, 50, 58, 36, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 46, 28,
|
| 8 |
+
60, 28, 68, 60, 28, 46, 16, 16, 16, 16, 16, 16, 16, 16, 36, 58, 50, 70, 50, 58, 36, 16, 16, 16, 16, 16, 16, 16, 16,
|
| 9 |
+
16, 16, 16, 16, 16, 16, 16, 16, 46, 28, 60, 28, 68, 60, 28, 46, 16, 16, 16, 16
|
| 10 |
+
];
|
| 11 |
+
// 初始视频中的音频波形
|
| 12 |
+
export const videoConfigList = [
|
| 13 |
+
8, 8, 8, 8, 18, 28, 26, 36, 26, 28, 18, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 24, 14, 30, 14, 34, 30, 14,
|
| 14 |
+
24, 8, 8, 8, 8, 8, 8, 8, 8, 18, 28, 26, 36, 26, 28, 18, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 24, 14, 30,
|
| 15 |
+
14, 34, 30, 14, 24, 8, 8, 8, 8, 8, 8, 8, 8, 18, 28, 26, 36, 26, 28, 18, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
|
| 16 |
+
8, 24, 14, 30, 14, 34, 30, 14, 24, 8, 8, 8, 8
|
| 17 |
+
];
|
| 18 |
+
export const showIdeasList = false;
|
r1-a/response_generation/minicpm/MiniCPM-o/web_demos/minicpm-o_2.6/web_server/src/hooks/useHttp.js
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import axios from 'axios';
|
| 2 |
+
import { setNewUserId, getNewUserId } from './useRandomId';
|
| 3 |
+
|
| 4 |
+
// 创建实例时配置默认值
|
| 5 |
+
const service = axios.create({
|
| 6 |
+
baseURL: '/',
|
| 7 |
+
timeout: 30000,
|
| 8 |
+
responseType: 'json'
|
| 9 |
+
});
|
| 10 |
+
|
| 11 |
+
// 请求拦截器
|
| 12 |
+
service.interceptors.request.use(config => {
|
| 13 |
+
if (config.url.includes('stream')) {
|
| 14 |
+
config.timeout = 3000;
|
| 15 |
+
}
|
| 16 |
+
if (window.location.search) {
|
| 17 |
+
config.url += window.location.search;
|
| 18 |
+
}
|
| 19 |
+
Object.assign(config.headers, ajaxHeader());
|
| 20 |
+
return config;
|
| 21 |
+
});
|
| 22 |
+
|
| 23 |
+
// 响应拦截器
|
| 24 |
+
service.interceptors.response.use(
|
| 25 |
+
response => {
|
| 26 |
+
let res = response.data;
|
| 27 |
+
if (response?.status === 200) {
|
| 28 |
+
return Promise.resolve({
|
| 29 |
+
code: 0,
|
| 30 |
+
message: '',
|
| 31 |
+
data: res
|
| 32 |
+
});
|
| 33 |
+
}
|
| 34 |
+
return Promise.resolve({ code: -1, message: '网络异常,请稍后再试', data: null });
|
| 35 |
+
},
|
| 36 |
+
error => {
|
| 37 |
+
const res = { code: -1, message: error?.response?.data?.detail || '网络异常,请稍后再试', data: null };
|
| 38 |
+
return Promise.resolve(res);
|
| 39 |
+
}
|
| 40 |
+
);
|
| 41 |
+
|
| 42 |
+
export const ajaxHeader = () => {
|
| 43 |
+
if (!localStorage.getItem('uid')) {
|
| 44 |
+
setNewUserId();
|
| 45 |
+
}
|
| 46 |
+
return {
|
| 47 |
+
'Content-Type': 'application/json;charset=UTF-8',
|
| 48 |
+
Accept: 'application/json',
|
| 49 |
+
service: 'minicpmo-server',
|
| 50 |
+
uid: getNewUserId()
|
| 51 |
+
};
|
| 52 |
+
};
|
| 53 |
+
|
| 54 |
+
export default {
|
| 55 |
+
get(url, params, config = {}) {
|
| 56 |
+
return service.get(url, { params, ...config });
|
| 57 |
+
},
|
| 58 |
+
post(url, data, config = {}) {
|
| 59 |
+
return service.post(url, data, { ...config });
|
| 60 |
+
}
|
| 61 |
+
};
|
r1-a/response_generation/minicpm/MiniCPM-o/web_demos/minicpm-o_2.6/web_server/src/hooks/useQueue.js
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
export class TaskQueue {
|
| 2 |
+
constructor() {
|
| 3 |
+
this.tasks = [];
|
| 4 |
+
this.isRunning = false;
|
| 5 |
+
this.isPaused = false;
|
| 6 |
+
this.currentTask = null;
|
| 7 |
+
}
|
| 8 |
+
|
| 9 |
+
// 添加任务到队列
|
| 10 |
+
addTask(task) {
|
| 11 |
+
this.tasks.push(task);
|
| 12 |
+
if (!this.isRunning) {
|
| 13 |
+
this.start();
|
| 14 |
+
}
|
| 15 |
+
}
|
| 16 |
+
|
| 17 |
+
// 删除任务
|
| 18 |
+
removeTask(taskToRemove) {
|
| 19 |
+
this.tasks = this.tasks.filter(task => task !== taskToRemove);
|
| 20 |
+
}
|
| 21 |
+
|
| 22 |
+
// 清空任务队列
|
| 23 |
+
clearQueue() {
|
| 24 |
+
this.tasks = [];
|
| 25 |
+
}
|
| 26 |
+
|
| 27 |
+
// 暂停任务执行
|
| 28 |
+
pause() {
|
| 29 |
+
this.isPaused = true;
|
| 30 |
+
}
|
| 31 |
+
|
| 32 |
+
// 恢复任务执行
|
| 33 |
+
resume() {
|
| 34 |
+
if (this.isPaused) {
|
| 35 |
+
this.isPaused = false;
|
| 36 |
+
if (!this.isRunning) {
|
| 37 |
+
this.start();
|
| 38 |
+
}
|
| 39 |
+
}
|
| 40 |
+
}
|
| 41 |
+
|
| 42 |
+
// 内部启动方法
|
| 43 |
+
async start() {
|
| 44 |
+
this.isRunning = true;
|
| 45 |
+
while (this.tasks.length > 0 && !this.isPaused) {
|
| 46 |
+
this.currentTask = this.tasks.shift();
|
| 47 |
+
await this.currentTask();
|
| 48 |
+
|
| 49 |
+
// 检查是否暂停或任务队列已清空
|
| 50 |
+
if (this.isPaused || this.tasks.length === 0) {
|
| 51 |
+
this.isRunning = false;
|
| 52 |
+
break;
|
| 53 |
+
}
|
| 54 |
+
}
|
| 55 |
+
this.isRunning = false;
|
| 56 |
+
}
|
| 57 |
+
}
|
| 58 |
+
|
| 59 |
+
// 示例任务函数
|
| 60 |
+
function exampleTask(id) {
|
| 61 |
+
return () =>
|
| 62 |
+
new Promise(resolve => {
|
| 63 |
+
console.log(`Executing task ${id}`);
|
| 64 |
+
setTimeout(() => {
|
| 65 |
+
console.log(`Task ${id} completed`);
|
| 66 |
+
resolve();
|
| 67 |
+
}, 1000); // 每个任务耗时1秒
|
| 68 |
+
});
|
| 69 |
+
}
|
| 70 |
+
|
| 71 |
+
// 测试示例
|
| 72 |
+
const queue = new TaskQueue();
|
| 73 |
+
|
| 74 |
+
// 添加任务到队列
|
| 75 |
+
for (let i = 1; i <= 5; i++) {
|
| 76 |
+
queue.addTask(exampleTask(i));
|
| 77 |
+
}
|
| 78 |
+
|
| 79 |
+
// 暂停队列,在2.5秒后执行
|
| 80 |
+
setTimeout(() => {
|
| 81 |
+
console.log('Pausing queue...');
|
| 82 |
+
queue.pause();
|
| 83 |
+
}, 2500);
|
| 84 |
+
|
| 85 |
+
// 恢复队列,在4.5秒后执行
|
| 86 |
+
setTimeout(() => {
|
| 87 |
+
console.log('Resuming queue...');
|
| 88 |
+
queue.resume();
|
| 89 |
+
}, 4500);
|
| 90 |
+
|
| 91 |
+
// 清空队列,在3秒后执行
|
| 92 |
+
setTimeout(() => {
|
| 93 |
+
console.log('Clearing queue...');
|
| 94 |
+
queue.clearQueue();
|
| 95 |
+
}, 3000);
|
r1-a/response_generation/minicpm/MiniCPM-o/web_demos/minicpm-o_2.6/web_server/src/hooks/useRandomId.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
const uid = 'uid';
|
| 2 |
+
export const setNewUserId = () => {
|
| 3 |
+
const randomId = Math.random().toString(36).slice(2).toUpperCase();
|
| 4 |
+
localStorage.setItem(uid, randomId);
|
| 5 |
+
return randomId;
|
| 6 |
+
};
|
| 7 |
+
export const getNewUserId = () => {
|
| 8 |
+
return localStorage.getItem('uid');
|
| 9 |
+
};
|
r1-a/response_generation/minicpm/MiniCPM-o/web_demos/minicpm-o_2.6/web_server/src/hooks/useVoice.js
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
const writeString = (view, offset, string) => {
|
| 2 |
+
for (let i = 0; i < string.length; i++) {
|
| 3 |
+
view.setUint8(offset + i, string.charCodeAt(i));
|
| 4 |
+
}
|
| 5 |
+
};
|
| 6 |
+
const floatTo16BitPCM = (output, offset, input) => {
|
| 7 |
+
for (let i = 0; i < input.length; i++, offset += 2) {
|
| 8 |
+
const s = Math.max(-1, Math.min(1, input[i]));
|
| 9 |
+
output.setInt16(offset, s < 0 ? s * 0x8000 : s * 0x7fff, true);
|
| 10 |
+
}
|
| 11 |
+
};
|
| 12 |
+
// audio buffer to wav file, need add 44 length header
|
| 13 |
+
export const encodeWAV = (samples, sampleRate) => {
|
| 14 |
+
const buffer = new ArrayBuffer(44 + samples.length * 2);
|
| 15 |
+
const view = new DataView(buffer);
|
| 16 |
+
const numChannels = 1;
|
| 17 |
+
const bitsPerSample = 16;
|
| 18 |
+
|
| 19 |
+
/* WAV 标头 */
|
| 20 |
+
writeString(view, 0, 'RIFF');
|
| 21 |
+
view.setUint32(4, 36 + samples.length * 2, true);
|
| 22 |
+
writeString(view, 8, 'WAVE');
|
| 23 |
+
writeString(view, 12, 'fmt ');
|
| 24 |
+
view.setUint32(16, 16, true);
|
| 25 |
+
view.setUint16(20, 1, true);
|
| 26 |
+
view.setUint16(22, numChannels, true);
|
| 27 |
+
view.setUint32(24, sampleRate, true);
|
| 28 |
+
view.setUint32(28, (sampleRate * numChannels * bitsPerSample) / 8, true);
|
| 29 |
+
view.setUint16(32, (numChannels * bitsPerSample) / 8, true);
|
| 30 |
+
view.setUint16(34, bitsPerSample, true);
|
| 31 |
+
writeString(view, 36, 'data');
|
| 32 |
+
view.setUint32(40, samples.length * 2, true);
|
| 33 |
+
|
| 34 |
+
/* PCM 数据 */
|
| 35 |
+
floatTo16BitPCM(view, 44, samples);
|
| 36 |
+
|
| 37 |
+
return new Blob([view], { type: 'audio/wav' });
|
| 38 |
+
};
|
r1-a/response_generation/minicpm/MiniCPM-o/web_demos/minicpm-o_2.6/web_server/src/i18n/en.json
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"menuTabVideo": "Realtime Video Call",
|
| 3 |
+
"menuTabAudio": "Realtime Voice Call",
|
| 4 |
+
"menuTabChatbot": "Chatbot",
|
| 5 |
+
"videoCallBtn": "Call MiniCPM-omni",
|
| 6 |
+
"audioCallBtn": "Call MiniCPM-omni",
|
| 7 |
+
"hangUpBtn": "Hang Up",
|
| 8 |
+
"notReadyBtn": "Not ready yet, please wait",
|
| 9 |
+
"skipMessageBtn": "Skip this message",
|
| 10 |
+
"feedbackDialogTitle": "Feedback issue",
|
| 11 |
+
"modelConfigTitle": "Model Config",
|
| 12 |
+
"audioInterruptionBtn": "Speech Interruption",
|
| 13 |
+
"audioInterruptionTips": "When the \"voice interruption\" mode is enabled, it allows users to interrupt the model while it is speaking. The model will immediately terminate the previous round of generation and respond to the user's latest question.",
|
| 14 |
+
"yes": "Yes",
|
| 15 |
+
"no": "No",
|
| 16 |
+
"videoQualityBtn": "HD Mode",
|
| 17 |
+
"videoQualityTips": "When the \"high resulation\" mode is enabled, the model will perform high resolution encoding on the last frame, allowing the model to see more detailed parts.",
|
| 18 |
+
"high": "High",
|
| 19 |
+
"low": "Low",
|
| 20 |
+
"vadThresholdBtn": "VAD Threshold",
|
| 21 |
+
"vadThresholdTips": "The VAD threshold indicates how long the sound needs to be silent before triggering inference. If the VAD threshold is too low, it may trigger accidentally during speech pauses, while if it's too high, it will result in slower initial response.",
|
| 22 |
+
"assistantPromptBtn": "Task Prompt",
|
| 23 |
+
"assistantPromptTips": "Model task instructions are used to support different task objectives.",
|
| 24 |
+
"useVoicePromptBtn": "Tone Color Prompt",
|
| 25 |
+
"voiceClonePromptInput": "Tone Color Prompt",
|
| 26 |
+
"voiceClonePromptTips": "Tone Color Prompt tips",
|
| 27 |
+
"audioChoiceBtn": "Audio Choice",
|
| 28 |
+
"defaultAudioBtn": "Default Audio",
|
| 29 |
+
"customizationBtn": "Customization: Upload Audio",
|
| 30 |
+
"toneColorOptions": "Voice Options",
|
| 31 |
+
"toneColorOptionsTips": "We have provided a selection of sample tone colors, and you also have the option to choose \"none\" and instruct the model to create a new tone color.",
|
| 32 |
+
"nullOption": "Null",
|
| 33 |
+
"defaultOption": "Female 1(Default)",
|
| 34 |
+
"femaleOption": "Female 2",
|
| 35 |
+
"maleOption": "Male 1"
|
| 36 |
+
}
|
r1-a/response_generation/minicpm/MiniCPM-o/web_demos/minicpm-o_2.6/web_server/src/i18n/zh.json
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"menuTabVideo": "实时视频通话",
|
| 3 |
+
"menuTabAudio": "实时语音通话",
|
| 4 |
+
"menuTabChatbot": "聊天机器人",
|
| 5 |
+
"videoCallBtn": "视频通话",
|
| 6 |
+
"audioCallBtn": "语音通话",
|
| 7 |
+
"hangUpBtn": "挂断",
|
| 8 |
+
"notReadyBtn": "服务繁忙,请稍后",
|
| 9 |
+
"skipMessageBtn": "跳过当前对话",
|
| 10 |
+
"feedbackDialogTitle": "请输入反馈意见",
|
| 11 |
+
"modelConfigTitle": "模型配置",
|
| 12 |
+
"audioInterruptionBtn": "语音打断",
|
| 13 |
+
"audioInterruptionTips": "开启\"语音打断\"功能,支持在模型说话时打断模型,模型会立刻结束上一轮的生成,并支持用户最新的问题。",
|
| 14 |
+
"yes": "是",
|
| 15 |
+
"no": "否",
|
| 16 |
+
"videoQualityBtn": "高清模式",
|
| 17 |
+
"videoQualityTips": "开启高清模式,模型会在最后一帧对图片进行高清编码,可以使得模型看得清更细节的部分。",
|
| 18 |
+
"high": "高清",
|
| 19 |
+
"low": "低清",
|
| 20 |
+
"vadThresholdBtn": "VAD阈值",
|
| 21 |
+
"vadThresholdTips": "vad阈值表示声音静音多久才开始触发推理,vad阈值过低会导致说话气口误触,过高会导致首响更慢。",
|
| 22 |
+
"assistantPromptBtn": "任务指令",
|
| 23 |
+
"assistantPromptTips": "模型的任务指令,用于支持不同的任务目标",
|
| 24 |
+
"useVoicePromptBtn": "音色指令",
|
| 25 |
+
"voiceClonePromptInput": "音色指令",
|
| 26 |
+
"voiceClonePromptTips": "我们的模型具有端到端的音色克隆能力,提供一段 5-7 秒的音频,模型在一定程度上可以用这种音色来说话。但基于法律考虑,我们的demo并不开启这个能力的试用。社区可以参照我们的开源代码自行适配。",
|
| 27 |
+
"audioChoiceBtn": "音色选择",
|
| 28 |
+
"defaultAudioBtn": "默认音色",
|
| 29 |
+
"customizationBtn": "自定义:上传音频",
|
| 30 |
+
"toneColorOptions": "语音选项",
|
| 31 |
+
"toneColorOptionsTips": "我们提供了一些示例音色,也可以选择“无”并通过指令让模型创建音色。",
|
| 32 |
+
"nullOption": "无",
|
| 33 |
+
"defaultOption": "女一号(默认)",
|
| 34 |
+
"femaleOption": "女二号",
|
| 35 |
+
"maleOption": "男一号"
|
| 36 |
+
}
|
r1-a/response_generation/minicpm/MiniCPM-o/web_demos/minicpm-o_2.6/web_server/src/router/guard/index.js
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { createStateGuard } from './stateGuard';
|
| 2 |
+
|
| 3 |
+
export function setupRouterGuard(router) {
|
| 4 |
+
createStateGuard(router);
|
| 5 |
+
}
|
r1-a/response_generation/minicpm/MiniCPM-o/web_demos/minicpm-o_2.6/web_server/src/router/index.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { createRouter, createWebHistory } from 'vue-router';
|
| 2 |
+
import { basicRoutes } from './menu';
|
| 3 |
+
|
| 4 |
+
// 创建一个可以被 Vue 应用程序使用的路由实例
|
| 5 |
+
export const router = createRouter({
|
| 6 |
+
// 创建一个 hash 历史记录。
|
| 7 |
+
history: createWebHistory(import.meta.env.BASE_URL),
|
| 8 |
+
// 路由列表。
|
| 9 |
+
routes: basicRoutes
|
| 10 |
+
});
|
| 11 |
+
|
| 12 |
+
// config router
|
| 13 |
+
// 配置路由器
|
| 14 |
+
export function setupRouter(app) {
|
| 15 |
+
app.use(router);
|
| 16 |
+
}
|