@trtc/calls-uikit-vue 4.4.2 → 4.4.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +2 -2
- package/src/Components/TUICallKit.vue +1 -1
- package/src/Components/assets/aiAssistant/desktop/subtitleSettings.svg +4 -0
- package/src/Components/assets/aiAssistant/mobile/close-aiAssistant.svg +7 -0
- package/src/Components/assets/aiAssistant/mobile/open-aiAssistant.svg +7 -0
- package/src/Components/assets/aiAssistant/mobile/subtitleSettings.svg +3 -0
- package/src/Components/components/base/CustomSelect/CustomSelect.ts +45 -0
- package/src/Components/components/base/CustomSelect/CustomSelect.vue +199 -0
- package/src/Components/components/common/AIAssistant/AISubtitle.vue +155 -37
- package/src/Components/components/common/AIAssistant/components/AITranscriberSwitchH5.vue +35 -0
- package/src/Components/components/common/AIAssistant/components/AITranscriberSwitchPC.vue +89 -0
- package/src/Components/components/common/AIAssistant/components/SubtitleContent.vue +234 -0
- package/src/Components/components/common/AIAssistant/components/SubtitleSettingsH5.vue +534 -0
- package/src/Components/components/common/AIAssistant/components/SubtitleSettingsPC.vue +294 -0
- package/src/Components/components/common/TopBar/TopBar.vue +41 -15
- package/src/Components/hooks/index.ts +1 -0
- package/src/Components/hooks/useAIAssistant.ts +142 -0
- package/src/Components/hooks/useGetVolumeMap.ts +2 -2
- package/src/TUICallService/CallService/AIAssistant.ts +285 -39
- package/src/TUICallService/CallService/UIKitModal.ts +14 -6
- package/src/TUICallService/CallService/bellContext.ts +25 -2
- package/src/TUICallService/CallService/engineEventHandler.ts +6 -1
- package/src/TUICallService/CallService/index.ts +72 -39
- package/src/TUICallService/CallService/miniProgram.ts +0 -12
- package/src/TUICallService/TUIStore/callStore.ts +6 -1
- package/src/TUICallService/UIKitModal/UIKitModal.ts +117 -0
- package/src/TUICallService/UIKitModal/index.ts +2 -0
- package/src/TUICallService/UIKitModal/type.ts +15 -0
- package/src/TUICallService/const/index.ts +4 -0
- package/src/TUICallService/interface/ICallStore.ts +5 -0
- package/src/TUICallService/locales/en.ts +15 -0
- package/src/TUICallService/locales/ja_JP.ts +15 -0
- package/src/TUICallService/locales/zh-cn.ts +15 -0
- package/src/TUICallService/utils/common-utils.ts +1 -1
- package/src/index.ts +1 -1
- package/stats.html +1 -1
- package/tuicall-uikit-vue.es.js +4199 -3474
- package/tuicall-uikit-vue.umd.js +2 -2
- package/types/Components/components/base/CustomSelect/CustomSelect.d.ts +41 -0
- package/types/Components/hooks/index.d.ts +1 -0
- package/types/Components/hooks/useAIAssistant.d.ts +19 -0
- package/types/TUICallService/CallService/AIAssistant.d.ts +72 -15
- package/types/TUICallService/CallService/bellContext.d.ts +3 -0
- package/types/TUICallService/CallService/index.d.ts +4 -3
- package/types/TUICallService/CallService/miniProgram.d.ts +0 -1
- package/types/TUICallService/UIKitModal/UIKitModal.d.ts +4 -0
- package/types/TUICallService/UIKitModal/index.d.ts +2 -0
- package/types/TUICallService/UIKitModal/type.d.ts +13 -0
- package/types/TUICallService/interface/ICallStore.d.ts +4 -0
- package/types/TUICallService/locales/en.d.ts +14 -0
- package/types/TUICallService/locales/ja_JP.d.ts +14 -0
- package/types/TUICallService/locales/zh-cn.d.ts +14 -0
- package/types/tsconfig.tsbuildinfo +1 -1
- package/src/Components/components/common/AIAssistant/AIAssistant.ts +0 -130
- package/src/Components/components/common/AIAssistant/index.ts +0 -11
- package/types/Components/components/common/AIAssistant/AIAssistant.d.ts +0 -40
- package/types/Components/components/common/AIAssistant/index.d.ts +0 -4
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div v-if="visible" class="subtitle-settings-overlay" @click="handleOverlayClick">
|
|
3
|
+
<div class="subtitle-settings-modal" @click.stop>
|
|
4
|
+
<div class="modal-header">
|
|
5
|
+
<h3 class="modal-title">{{ t('ai-subtitle-settings') }}</h3>
|
|
6
|
+
<button class="close-btn" @click="closeModal">
|
|
7
|
+
<svg width="16" height="16" viewBox="0 0 16 16" fill="none">
|
|
8
|
+
<path d="M12 4L4 12M4 4L12 12" stroke="#666" stroke-width="2" stroke-linecap="round"/>
|
|
9
|
+
</svg>
|
|
10
|
+
</button>
|
|
11
|
+
</div>
|
|
12
|
+
|
|
13
|
+
<div class="modal-content">
|
|
14
|
+
<!-- Recognition Language -->
|
|
15
|
+
<div class="setting-item">
|
|
16
|
+
<label class="setting-label">{{ t('recognition-language') }}</label>
|
|
17
|
+
<CustomSelect
|
|
18
|
+
:value="recognitionLanguage"
|
|
19
|
+
@input="handleLanguageChange"
|
|
20
|
+
:options="languageOptions"
|
|
21
|
+
@change="handleLanguageChange"
|
|
22
|
+
/>
|
|
23
|
+
</div>
|
|
24
|
+
|
|
25
|
+
<!-- Translation Language -->
|
|
26
|
+
<div class="setting-item">
|
|
27
|
+
<label class="setting-label">{{ t('translation-language') }}</label>
|
|
28
|
+
<CustomSelect
|
|
29
|
+
:value="translationLanguage"
|
|
30
|
+
@input="handleTranslationChange"
|
|
31
|
+
:options="translationOptions"
|
|
32
|
+
@change="handleTranslationChange"
|
|
33
|
+
/>
|
|
34
|
+
</div>
|
|
35
|
+
|
|
36
|
+
<!-- Subtitle Display Settings -->
|
|
37
|
+
<div class="setting-item">
|
|
38
|
+
<label class="setting-label">{{ t('subtitle-display-settings') }}</label>
|
|
39
|
+
<CustomSelect
|
|
40
|
+
:value="subtitleDisplay"
|
|
41
|
+
@input="handleSubtitleDisplayChange"
|
|
42
|
+
:options="subtitleDisplayOptions"
|
|
43
|
+
@change="handleSubtitleDisplayChange"
|
|
44
|
+
/>
|
|
45
|
+
</div>
|
|
46
|
+
</div>
|
|
47
|
+
|
|
48
|
+
<div class="modal-footer">
|
|
49
|
+
<button class="btn btn-cancel" @click="closeModal">{{ t('Cancel') }}</button>
|
|
50
|
+
<button class="btn btn-confirm" @click="confirmSettings">{{ t('save') }}</button>
|
|
51
|
+
</div>
|
|
52
|
+
</div>
|
|
53
|
+
</div>
|
|
54
|
+
</template>
|
|
55
|
+
|
|
56
|
+
<script lang="ts" setup>
|
|
57
|
+
import { ref, computed, watch } from '../../../../../adapter-vue';
|
|
58
|
+
import { TUICallKitAPI } from '../../../../../TUICallService/index';
|
|
59
|
+
import CustomSelect from '../../../base/CustomSelect/CustomSelect.vue';
|
|
60
|
+
import { useTranslate } from '../../../../hooks/useTranslate';
|
|
61
|
+
|
|
62
|
+
interface Props {
|
|
63
|
+
visible: boolean;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
interface Emits {
|
|
67
|
+
(e: 'update:visible', value: boolean): void;
|
|
68
|
+
(e: 'confirm', settings: {
|
|
69
|
+
recognitionLanguage: string;
|
|
70
|
+
translationLanguage: string;
|
|
71
|
+
subtitleDisplay: string;
|
|
72
|
+
}): void;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const props = withDefaults(defineProps<Props>(), {
|
|
76
|
+
visible: false
|
|
77
|
+
});
|
|
78
|
+
const emit = defineEmits<Emits>();
|
|
79
|
+
|
|
80
|
+
// Use i18n translation
|
|
81
|
+
const t = useTranslate();
|
|
82
|
+
|
|
83
|
+
// Settings state
|
|
84
|
+
const recognitionLanguage = ref('zh');
|
|
85
|
+
const translationLanguage = ref('en');
|
|
86
|
+
const subtitleDisplay = ref('both');
|
|
87
|
+
|
|
88
|
+
// Saved settings to restore on cancel
|
|
89
|
+
const savedSettings = ref({
|
|
90
|
+
recognitionLanguage: 'zh',
|
|
91
|
+
translationLanguage: 'en',
|
|
92
|
+
subtitleDisplay: 'both'
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
// Language options for CustomSelect - supported recognition languages
|
|
96
|
+
// Use native language names for better UX (users can always recognize their own language)
|
|
97
|
+
const languageOptions = computed(() => [
|
|
98
|
+
{ value: 'zh', label: '中文' },
|
|
99
|
+
{ value: 'en', label: 'English' }
|
|
100
|
+
]);
|
|
101
|
+
|
|
102
|
+
// Translation options for CustomSelect - translation target languages (base list)
|
|
103
|
+
// Use native language names for better UX (users can always recognize their own language)
|
|
104
|
+
const baseTranslationOptions = computed(() => [
|
|
105
|
+
{ value: '', label: t.value('no-translation') },
|
|
106
|
+
{ value: 'zh', label: 'Chinese (中文)' },
|
|
107
|
+
{ value: 'en', label: 'English (英语)' },
|
|
108
|
+
{ value: 'vi', label: 'Vietnamese (越南语)' },
|
|
109
|
+
{ value: 'ja', label: 'Japanese (日语)' },
|
|
110
|
+
{ value: 'ko', label: 'Korean (韩语)' },
|
|
111
|
+
{ value: 'id', label: 'Indonesian (印尼语)' },
|
|
112
|
+
{ value: 'th', label: 'Thai (泰语)' },
|
|
113
|
+
{ value: 'pt', label: 'Portuguese (葡萄牙语)' },
|
|
114
|
+
{ value: 'ar', label: 'Arabic (阿拉伯语)' },
|
|
115
|
+
{ value: 'es', label: 'Spanish (西班牙语)' },
|
|
116
|
+
{ value: 'fr', label: 'French (法语)' },
|
|
117
|
+
{ value: 'ms', label: 'Malay (马来语)' },
|
|
118
|
+
{ value: 'de', label: 'German (德语)' },
|
|
119
|
+
{ value: 'it', label: 'Italian (意大利语)' },
|
|
120
|
+
{ value: 'ru', label: 'Russian (俄语)' }
|
|
121
|
+
]);
|
|
122
|
+
|
|
123
|
+
// Computed property to filter translation options based on selected recognition language
|
|
124
|
+
const translationOptions = computed(() => {
|
|
125
|
+
return baseTranslationOptions.value.filter(option =>
|
|
126
|
+
option.value === '' || option.value !== recognitionLanguage.value
|
|
127
|
+
);
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
// Subtitle display options for CustomSelect
|
|
131
|
+
const subtitleDisplayOptions = computed(() => [
|
|
132
|
+
{ value: 'both', label: t.value('show-bilingual') },
|
|
133
|
+
{ value: 'original', label: t.value('show-original-only') },
|
|
134
|
+
]);
|
|
135
|
+
|
|
136
|
+
const handleLanguageChange = (value: string | number) => {
|
|
137
|
+
recognitionLanguage.value = value as string;
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
const handleTranslationChange = (value: string | number) => {
|
|
141
|
+
translationLanguage.value = value as string;
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
const handleSubtitleDisplayChange = (value: string | number) => {
|
|
145
|
+
subtitleDisplay.value = value as string;
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
const closeModal = () => {
|
|
149
|
+
// Restore saved settings when closing without confirming
|
|
150
|
+
recognitionLanguage.value = savedSettings.value.recognitionLanguage;
|
|
151
|
+
translationLanguage.value = savedSettings.value.translationLanguage;
|
|
152
|
+
subtitleDisplay.value = savedSettings.value.subtitleDisplay;
|
|
153
|
+
|
|
154
|
+
emit('update:visible', false);
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
const handleOverlayClick = () => {
|
|
158
|
+
closeModal();
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
const confirmSettings = async () => {
|
|
162
|
+
try {
|
|
163
|
+
// Call updateRealTimeTranscriber when settings are confirmed
|
|
164
|
+
const translationLanguages = translationLanguage.value ? [translationLanguage.value] : [];
|
|
165
|
+
|
|
166
|
+
await TUICallKitAPI._aiAssistant.updateRealTimeTranscriber({
|
|
167
|
+
sourceLanguage: recognitionLanguage.value,
|
|
168
|
+
translationLanguages: translationLanguages,
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
// Save the confirmed settings
|
|
172
|
+
savedSettings.value = {
|
|
173
|
+
recognitionLanguage: recognitionLanguage.value,
|
|
174
|
+
translationLanguage: translationLanguage.value,
|
|
175
|
+
subtitleDisplay: subtitleDisplay.value
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
emit('confirm', {
|
|
179
|
+
recognitionLanguage: recognitionLanguage.value,
|
|
180
|
+
translationLanguage: translationLanguage.value,
|
|
181
|
+
subtitleDisplay: subtitleDisplay.value,
|
|
182
|
+
});
|
|
183
|
+
} catch (error) {
|
|
184
|
+
console.error('Failed to update AI transcriber settings:', error);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
closeModal();
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
// Watch for recognition language changes and reset translation language if it conflicts
|
|
191
|
+
watch(recognitionLanguage, (newRecognitionLang) => {
|
|
192
|
+
// If the current translation language is the same as the new recognition language, reset it
|
|
193
|
+
if (translationLanguage.value === newRecognitionLang) {
|
|
194
|
+
translationLanguage.value = '';
|
|
195
|
+
}
|
|
196
|
+
});
|
|
197
|
+
</script>
|
|
198
|
+
|
|
199
|
+
<style lang="scss" scoped>
|
|
200
|
+
.subtitle-settings-overlay {
|
|
201
|
+
position: absolute;
|
|
202
|
+
top: 50%;
|
|
203
|
+
left: 50%;
|
|
204
|
+
display: flex;
|
|
205
|
+
align-items: center;
|
|
206
|
+
justify-content: center;
|
|
207
|
+
z-index: 1000;
|
|
208
|
+
font-family: PingFang SC;
|
|
209
|
+
box-shadow: 0px 2px 6px 0px var(--Black-8);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
.subtitle-settings-modal {
|
|
213
|
+
background: white;
|
|
214
|
+
border-radius: 12px;
|
|
215
|
+
width: 480px;
|
|
216
|
+
overflow: hidden;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
.modal-header {
|
|
220
|
+
display: flex;
|
|
221
|
+
align-items: center;
|
|
222
|
+
justify-content: space-between;
|
|
223
|
+
padding: 24px 24px 10px 24px;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
.modal-title {
|
|
227
|
+
font-size: 16px;
|
|
228
|
+
font-weight: 600;
|
|
229
|
+
color: #000000E5;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
.close-btn {
|
|
233
|
+
background: none;
|
|
234
|
+
border: none;
|
|
235
|
+
cursor: pointer;
|
|
236
|
+
padding: 4px;
|
|
237
|
+
border-radius: 4px;
|
|
238
|
+
transition: background-color 0.2s;
|
|
239
|
+
|
|
240
|
+
&:hover {
|
|
241
|
+
background-color: #f5f5f5;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
.modal-content {
|
|
246
|
+
padding: 24px;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
.setting-item {
|
|
250
|
+
margin-bottom: 24px;
|
|
251
|
+
|
|
252
|
+
&:last-child {
|
|
253
|
+
margin-bottom: 0;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
.setting-label {
|
|
258
|
+
display: block;
|
|
259
|
+
font-size: 14px;
|
|
260
|
+
font-weight: 400;
|
|
261
|
+
color: #4F586B;
|
|
262
|
+
margin-bottom: 8px;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
.modal-footer {
|
|
266
|
+
display: flex;
|
|
267
|
+
justify-content: flex-end;
|
|
268
|
+
gap: 20px;
|
|
269
|
+
padding: 16px 24px 24px;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
.btn {
|
|
273
|
+
width: 72px;
|
|
274
|
+
height: 32px;
|
|
275
|
+
border-radius: 6px;
|
|
276
|
+
font-size: 14px;
|
|
277
|
+
font-weight: 500;
|
|
278
|
+
cursor: pointer;
|
|
279
|
+
border-radius: 26px;
|
|
280
|
+
transition: all 0.2s;
|
|
281
|
+
border: 1px solid transparent;
|
|
282
|
+
|
|
283
|
+
&.btn-cancel {
|
|
284
|
+
color: #1C66E5;
|
|
285
|
+
border-color: #1C66E5;
|
|
286
|
+
background-color: white;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
&.btn-confirm {
|
|
290
|
+
background: #1C66E5;
|
|
291
|
+
color: white;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
</style>
|
|
@@ -6,25 +6,23 @@
|
|
|
6
6
|
<Timer v-if="showTimer" :call-duration="callDuration" />
|
|
7
7
|
</Col>
|
|
8
8
|
<Col :span="8" justify="end" align="center">
|
|
9
|
-
<
|
|
10
|
-
<
|
|
11
|
-
<
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
<Col :span="3" justify="center">
|
|
15
|
-
<FullScreen v-if="allowedFullScreen" />
|
|
16
|
-
</Col>
|
|
17
|
-
</Row>
|
|
9
|
+
<div class="topbar-right-controls">
|
|
10
|
+
<AITranscriberSwitch v-if="showAITranscriberSwitch" />
|
|
11
|
+
<Minimize v-if="showMinimize" />
|
|
12
|
+
<FullScreen v-if="allowedFullScreen" />
|
|
13
|
+
</div>
|
|
18
14
|
</Col>
|
|
19
15
|
</Row>
|
|
20
16
|
<Row v-if="!isPC">
|
|
21
17
|
<Col :span="8" align="center">
|
|
22
|
-
<
|
|
23
|
-
<
|
|
18
|
+
<div class="top-bar-left-controls">
|
|
19
|
+
<div class="control-item">
|
|
24
20
|
<Minimize v-if="!isPC && showMinimize" />
|
|
25
|
-
</
|
|
26
|
-
<
|
|
27
|
-
|
|
21
|
+
</div>
|
|
22
|
+
<div class="control-item">
|
|
23
|
+
<AITranscriberSwitchH5 v-if="showAITranscriberSwitch" />
|
|
24
|
+
</div>
|
|
25
|
+
</div>
|
|
28
26
|
</Col>
|
|
29
27
|
<Col :span="8" justify="center" align="center">
|
|
30
28
|
<Timer v-if="showTimer" :call-duration="callDuration" />
|
|
@@ -51,7 +49,7 @@ export default {
|
|
|
51
49
|
|
|
52
50
|
<script setup lang="ts">
|
|
53
51
|
import { toRefs, computed, ref, onMounted, onUnmounted } from '../../../../adapter-vue';
|
|
54
|
-
import { useCallInfoContext, useCallDuration, useCustomUI } from '../../../hooks';
|
|
52
|
+
import { useCallInfoContext, useCallDuration, useCustomUI, useAIAssistant } from '../../../hooks';
|
|
55
53
|
import { CallRole, CallStatus, FeatureButton, NAME, StoreName, TUIGlobal, TUIStore } from '../../../../TUICallService';
|
|
56
54
|
import { TopBarProps } from './TopBar';
|
|
57
55
|
import Row from '../../base/Layout/Row/Row.vue';
|
|
@@ -60,14 +58,21 @@ import Minimize from '../Button/Minimize.vue';
|
|
|
60
58
|
import FullScreen from '../Button/FullScreen.vue';
|
|
61
59
|
import InviteUser from '../Button/InviteUser.vue';
|
|
62
60
|
import Timer from '../Timer/Timer.vue';
|
|
61
|
+
import AITranscriberSwitch from '../AIAssistant/components/AITranscriberSwitchPC.vue';
|
|
62
|
+
import AITranscriberSwitchH5 from '../AIAssistant/components/AITranscriberSwitchH5.vue';
|
|
63
63
|
|
|
64
64
|
defineProps(TopBarProps);
|
|
65
65
|
const isPC = TUIGlobal.isPC;
|
|
66
66
|
const { callStatus, isGroupCall, callRole, allowedFullScreen } = toRefs(useCallInfoContext());
|
|
67
67
|
const { callDuration } = useCallDuration();
|
|
68
|
+
const { isAITranscriberEnabled } = useAIAssistant();
|
|
68
69
|
const customUIConfig = useCustomUI();
|
|
69
70
|
const showTimer = computed(() => (callStatus.value === CallStatus.CONNECTED));
|
|
70
71
|
const showMinimize = ref(TUIStore.getData(StoreName.CALL, NAME.ENABLE_FLOAT_WINDOW));
|
|
72
|
+
const showAITranscriberSwitch = computed(() => {
|
|
73
|
+
// Show AI transcriber switch when call is connected AND AI transcriber UI is enabled
|
|
74
|
+
return callStatus.value === CallStatus.CONNECTED && isAITranscriberEnabled.value;
|
|
75
|
+
});
|
|
71
76
|
const showInviteUser = computed(() => {
|
|
72
77
|
if (
|
|
73
78
|
!isGroupCall.value
|
|
@@ -113,4 +118,25 @@ onUnmounted(() => {
|
|
|
113
118
|
display: flex;
|
|
114
119
|
align-items: center;
|
|
115
120
|
}
|
|
121
|
+
|
|
122
|
+
.topbar-right-controls {
|
|
123
|
+
margin-right: 10px;
|
|
124
|
+
display: flex;
|
|
125
|
+
align-items: center;
|
|
126
|
+
gap: 16px; // Adjust this value to match design spacing
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
.top-bar-left-controls {
|
|
130
|
+
display: flex;
|
|
131
|
+
align-items: center;
|
|
132
|
+
justify-content: flex-start;
|
|
133
|
+
gap: 16px;
|
|
134
|
+
margin-left: 10px;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
.control-item {
|
|
138
|
+
display: flex;
|
|
139
|
+
align-items: center;
|
|
140
|
+
justify-content: center;
|
|
141
|
+
}
|
|
116
142
|
</style>
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AI Assistant Hook
|
|
3
|
+
* @description Vue 3 composable for AI Transcriber functionality
|
|
4
|
+
*/
|
|
5
|
+
import { ref, onMounted, onUnmounted } from '../../adapter-vue';
|
|
6
|
+
import { TUIStore, StoreName, NAME } from '../../TUICallService/index';
|
|
7
|
+
import { TranscriberMessage } from '../../TUICallService/CallService/AIAssistant';
|
|
8
|
+
|
|
9
|
+
// Translation content interface
|
|
10
|
+
interface ITranslationContent {
|
|
11
|
+
language: string;
|
|
12
|
+
text: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// Converted message interface
|
|
16
|
+
interface ITranslationInfo {
|
|
17
|
+
roundId: string;
|
|
18
|
+
sender: string;
|
|
19
|
+
nick?: string;
|
|
20
|
+
text: string;
|
|
21
|
+
end: boolean;
|
|
22
|
+
translation: ITranslationContent[];
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* AI Assistant composable hook
|
|
27
|
+
* Provides reactive state management for AI transcription functionality
|
|
28
|
+
*/
|
|
29
|
+
export function useAIAssistant() {
|
|
30
|
+
// AI Transcriber Status State
|
|
31
|
+
const isAITranscriberEnabled = ref(TUIStore.getData(StoreName.CALL, NAME.IS_AI_TRANSCRIBER_ENABLED));
|
|
32
|
+
const isAITranscriberRunning = ref(TUIStore.getData(StoreName.CALL, NAME.IS_AI_TRANSCRIBER_RUNNING));
|
|
33
|
+
|
|
34
|
+
// Converted message list
|
|
35
|
+
const subtitleInfoList = ref<ITranslationInfo[]>([]);
|
|
36
|
+
|
|
37
|
+
// Helper functions for message conversion
|
|
38
|
+
const parseTranslationTexts = (translationTexts: any): ITranslationContent[] => {
|
|
39
|
+
if (!translationTexts) {
|
|
40
|
+
return [];
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Handle Map type
|
|
44
|
+
if (translationTexts instanceof Map) {
|
|
45
|
+
return Array.from(translationTexts.entries()).map(([language, content]) => ({
|
|
46
|
+
language: String(language),
|
|
47
|
+
text: ensureStringContent(content)
|
|
48
|
+
}));
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Handle plain object type
|
|
52
|
+
if (typeof translationTexts === 'object') {
|
|
53
|
+
return Object.entries(translationTexts).map(([language, content]) => ({
|
|
54
|
+
language: String(language),
|
|
55
|
+
text: ensureStringContent(content)
|
|
56
|
+
}));
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return [];
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const ensureStringContent = (content: any): string => {
|
|
63
|
+
if (content === null || content === undefined) {
|
|
64
|
+
return '';
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (typeof content === 'string') {
|
|
68
|
+
return content;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (typeof content === 'object') {
|
|
72
|
+
try {
|
|
73
|
+
return JSON.stringify(content);
|
|
74
|
+
} catch (error) {
|
|
75
|
+
console.warn('Failed to stringify translation content:', error);
|
|
76
|
+
return '[Invalid Object]';
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return String(content);
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
const convertToLegacyFormat = (messages: TranscriberMessage[]): ITranslationInfo[] => {
|
|
84
|
+
return messages.map(msg => ({
|
|
85
|
+
roundId: msg.segmentId,
|
|
86
|
+
sender: msg.speakerUserId,
|
|
87
|
+
nick: '',
|
|
88
|
+
text: msg.sourceText,
|
|
89
|
+
end: msg.isCompleted,
|
|
90
|
+
translation: parseTranslationTexts(msg.translationTexts)
|
|
91
|
+
}));
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
// Store watcher callbacks
|
|
95
|
+
const handleAITranscriberEnabledChange = (value: boolean) => {
|
|
96
|
+
isAITranscriberEnabled.value = value || false;
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
const handleAITranscriberRunningChange = (value: boolean) => {
|
|
100
|
+
isAITranscriberRunning.value = value || false;
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
const handleRealtimeMessageListChange = (messageList: TranscriberMessage[]) => {
|
|
104
|
+
if (messageList && messageList.length > 0) {
|
|
105
|
+
const convertedMessages = convertToLegacyFormat(messageList);
|
|
106
|
+
subtitleInfoList.value = convertedMessages;
|
|
107
|
+
} else {
|
|
108
|
+
subtitleInfoList.value = [];
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
// Lifecycle management
|
|
113
|
+
onMounted(() => {
|
|
114
|
+
// Subscribe to TUIStore changes
|
|
115
|
+
TUIStore.watch(StoreName.CALL, {
|
|
116
|
+
[NAME.IS_AI_TRANSCRIBER_ENABLED]: handleAITranscriberEnabledChange,
|
|
117
|
+
[NAME.IS_AI_TRANSCRIBER_RUNNING]: handleAITranscriberRunningChange,
|
|
118
|
+
[NAME.REALTIME_MESSAGE_LIST]: handleRealtimeMessageListChange,
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
// Get initial message data
|
|
122
|
+
const currentMessages = TUIStore.getData(StoreName.CALL, NAME.REALTIME_MESSAGE_LIST);
|
|
123
|
+
if (currentMessages) {
|
|
124
|
+
handleRealtimeMessageListChange(currentMessages);
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
onUnmounted(() => {
|
|
129
|
+
// Unsubscribe from TUIStore changes
|
|
130
|
+
TUIStore.unwatch(StoreName.CALL, {
|
|
131
|
+
[NAME.IS_AI_TRANSCRIBER_ENABLED]: handleAITranscriberEnabledChange,
|
|
132
|
+
[NAME.IS_AI_TRANSCRIBER_RUNNING]: handleAITranscriberRunningChange,
|
|
133
|
+
[NAME.REALTIME_MESSAGE_LIST]: handleRealtimeMessageListChange,
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
return {
|
|
138
|
+
isAITranscriberEnabled,
|
|
139
|
+
isAITranscriberRunning,
|
|
140
|
+
subtitleInfoList,
|
|
141
|
+
};
|
|
142
|
+
}
|
|
@@ -8,9 +8,9 @@ export function useGetVolumeMap() {
|
|
|
8
8
|
const remoteUserInfoList = ref(TUIStore.getData(StoreName.CALL, NAME.REMOTE_USER_INFO_LIST));
|
|
9
9
|
|
|
10
10
|
const handleLocalUserInfoChange = (value) => {
|
|
11
|
-
const isAudioAvailable = TUIGlobal.isWeChat ? value
|
|
11
|
+
const isAudioAvailable = TUIGlobal.isWeChat ? value?.enableMic : value?.isAudioAvailable;
|
|
12
12
|
if (isAudioAvailable) {
|
|
13
|
-
volumeMap.value = { ...volumeMap.value, localVideo: value
|
|
13
|
+
volumeMap.value = { ...volumeMap.value, localVideo: value?.volume };
|
|
14
14
|
}
|
|
15
15
|
};
|
|
16
16
|
const handleRemoteUserInfoListChange = (value) => {
|