1f commited on
Commit
09e811a
·
verified ·
1 Parent(s): 6ded3c0

Add files using upload-large-folder tool

Browse files
Files changed (20) hide show
  1. r1-a/response_generation/minicpm/MiniCPM-o/web_demos/minicpm-o_2.6/web_server/src/components/ExtraInfo/index.vue +36 -0
  2. r1-a/response_generation/minicpm/MiniCPM-o/web_demos/minicpm-o_2.6/web_server/src/components/IdeasList/index.vue +67 -0
  3. r1-a/response_generation/minicpm/MiniCPM-o/web_demos/minicpm-o_2.6/web_server/src/components/LikeAndDislike/index.vue +110 -0
  4. r1-a/response_generation/minicpm/MiniCPM-o/web_demos/minicpm-o_2.6/web_server/src/components/ModelConfig/index copy.vue +404 -0
  5. r1-a/response_generation/minicpm/MiniCPM-o/web_demos/minicpm-o_2.6/web_server/src/components/ModelConfig/index.vue +456 -0
  6. r1-a/response_generation/minicpm/MiniCPM-o/web_demos/minicpm-o_2.6/web_server/src/components/ModelOutput/index.vue +91 -0
  7. r1-a/response_generation/minicpm/MiniCPM-o/web_demos/minicpm-o_2.6/web_server/src/components/SelectTimbre/index.vue +122 -0
  8. r1-a/response_generation/minicpm/MiniCPM-o/web_demos/minicpm-o_2.6/web_server/src/components/SkipBtn/index.vue +67 -0
  9. r1-a/response_generation/minicpm/MiniCPM-o/web_demos/minicpm-o_2.6/web_server/src/components/SvgIcon/index.vue +39 -0
  10. r1-a/response_generation/minicpm/MiniCPM-o/web_demos/minicpm-o_2.6/web_server/src/components/Voice/index.vue +138 -0
  11. r1-a/response_generation/minicpm/MiniCPM-o/web_demos/minicpm-o_2.6/web_server/src/directives/index.js +8 -0
  12. r1-a/response_generation/minicpm/MiniCPM-o/web_demos/minicpm-o_2.6/web_server/src/enums/index.js +18 -0
  13. r1-a/response_generation/minicpm/MiniCPM-o/web_demos/minicpm-o_2.6/web_server/src/hooks/useHttp.js +61 -0
  14. r1-a/response_generation/minicpm/MiniCPM-o/web_demos/minicpm-o_2.6/web_server/src/hooks/useQueue.js +95 -0
  15. r1-a/response_generation/minicpm/MiniCPM-o/web_demos/minicpm-o_2.6/web_server/src/hooks/useRandomId.js +9 -0
  16. r1-a/response_generation/minicpm/MiniCPM-o/web_demos/minicpm-o_2.6/web_server/src/hooks/useVoice.js +38 -0
  17. r1-a/response_generation/minicpm/MiniCPM-o/web_demos/minicpm-o_2.6/web_server/src/i18n/en.json +36 -0
  18. r1-a/response_generation/minicpm/MiniCPM-o/web_demos/minicpm-o_2.6/web_server/src/i18n/zh.json +36 -0
  19. r1-a/response_generation/minicpm/MiniCPM-o/web_demos/minicpm-o_2.6/web_server/src/router/guard/index.js +5 -0
  20. 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
+ }