@trtc/calls-uikit-react 4.4.3 → 4.4.4
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/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.scss +116 -0
- package/src/Components/components/base/CustomSelect/CustomSelect.tsx +96 -0
- package/src/Components/components/common/AIAssistant/AISubtitle.scss +109 -0
- package/src/Components/components/common/AIAssistant/AISubtitle.tsx +52 -66
- package/src/Components/components/common/AIAssistant/components/AITranscriberSwitchH5.tsx +39 -0
- package/src/Components/components/common/AIAssistant/components/AITranscriberSwitchPC.scss +46 -0
- package/src/Components/components/common/AIAssistant/components/AITranscriberSwitchPC.tsx +42 -0
- package/src/Components/components/common/AIAssistant/components/SubtitleContent.scss +67 -0
- package/src/Components/components/common/AIAssistant/components/SubtitleContent.tsx +134 -0
- package/src/Components/components/common/AIAssistant/components/SubtitleSettingsH5.scss +275 -0
- package/src/Components/components/common/AIAssistant/components/SubtitleSettingsH5.tsx +265 -0
- package/src/Components/components/common/AIAssistant/components/SubtitleSettingsPC.scss +98 -0
- package/src/Components/components/common/AIAssistant/components/SubtitleSettingsPC.tsx +198 -0
- package/src/Components/components/common/AIAssistant/components/index.ts +5 -0
- package/src/Components/components/common/AIAssistant/index.ts +3 -1
- package/src/Components/components/common/TopBar/h5/TopBarH5.tsx +11 -1
- package/src/Components/components/common/TopBar/pc/TopBarPC.module.scss +20 -0
- package/src/Components/components/common/TopBar/pc/TopBarPC.tsx +14 -9
- package/src/Components/hooks/index.ts +2 -0
- package/src/Components/hooks/useAIAssistant.ts +174 -0
- package/src/TUICallService/CallService/AIAssistant.ts +285 -39
- package/src/TUICallService/CallService/index.ts +19 -9
- package/src/TUICallService/TUIStore/callStore.ts +5 -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/index.ts +1 -1
- package/tuicall-uikit-react.es.js +4265 -3703
- package/tuicall-uikit-react.umd.js +8 -3
- package/types/Components/components/base/CustomSelect/CustomSelect.d.ts +15 -0
- package/types/Components/components/common/AIAssistant/AISubtitle.d.ts +1 -0
- package/types/Components/components/common/AIAssistant/components/AITranscriberSwitchH5.d.ts +3 -0
- package/types/Components/components/common/AIAssistant/components/AITranscriberSwitchPC.d.ts +4 -0
- package/types/Components/components/common/AIAssistant/components/SubtitleContent.d.ts +8 -0
- package/types/Components/components/common/AIAssistant/components/SubtitleSettingsH5.d.ts +13 -0
- package/types/Components/components/common/AIAssistant/components/SubtitleSettingsPC.d.ts +13 -0
- package/types/Components/components/common/AIAssistant/components/index.d.ts +5 -0
- package/types/Components/components/common/AIAssistant/index.d.ts +1 -0
- package/types/Components/hooks/index.d.ts +2 -1
- package/types/Components/hooks/useAIAssistant.d.ts +22 -0
- package/types/TUICallService/CallService/AIAssistant.d.ts +72 -15
- package/types/TUICallService/CallService/index.d.ts +2 -1
- 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
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
import React, { useState, useEffect, useRef } from 'react';
|
|
2
|
+
import { TUICallKitAPI } from '../../../../../TUICallService/index';
|
|
3
|
+
import { useTranslate } from '../../../../hooks';
|
|
4
|
+
import './SubtitleSettingsH5.scss';
|
|
5
|
+
|
|
6
|
+
interface SubtitleSettingsH5Props {
|
|
7
|
+
visible: boolean;
|
|
8
|
+
onVisibleChange: (visible: boolean) => void;
|
|
9
|
+
onConfirm: (settings: {
|
|
10
|
+
recognitionLanguage: string;
|
|
11
|
+
translationLanguage: string;
|
|
12
|
+
subtitleEnabled: boolean;
|
|
13
|
+
}) => void;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
interface LanguageOption {
|
|
17
|
+
value: string;
|
|
18
|
+
label: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const SubtitleSettingsH5: React.FC<SubtitleSettingsH5Props> = ({
|
|
22
|
+
visible,
|
|
23
|
+
onVisibleChange,
|
|
24
|
+
onConfirm
|
|
25
|
+
}) => {
|
|
26
|
+
// Use i18n translation
|
|
27
|
+
const { t } = useTranslate();
|
|
28
|
+
|
|
29
|
+
// Confirmed settings (saved on confirm button click)
|
|
30
|
+
const confirmedSettingsRef = useRef({
|
|
31
|
+
recognitionLanguage: 'zh',
|
|
32
|
+
translationLanguage: '',
|
|
33
|
+
subtitleEnabled: true
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
// Temporary editing state (current user selections)
|
|
37
|
+
const [recognitionLanguage, setRecognitionLanguage] = useState('zh');
|
|
38
|
+
const [translationLanguage, setTranslationLanguage] = useState('');
|
|
39
|
+
const [subtitleEnabled, setSubtitleEnabled] = useState(true);
|
|
40
|
+
|
|
41
|
+
// UI state for language selectors
|
|
42
|
+
const [showRecognitionLanguageSelector, setShowRecognitionLanguageSelector] = useState(false);
|
|
43
|
+
const [showTranslationLanguageSelector, setShowTranslationLanguageSelector] = useState(false);
|
|
44
|
+
|
|
45
|
+
// Reset to last confirmed settings when modal opens
|
|
46
|
+
useEffect(() => {
|
|
47
|
+
if (visible) {
|
|
48
|
+
setRecognitionLanguage(confirmedSettingsRef.current.recognitionLanguage);
|
|
49
|
+
setTranslationLanguage(confirmedSettingsRef.current.translationLanguage);
|
|
50
|
+
setSubtitleEnabled(confirmedSettingsRef.current.subtitleEnabled);
|
|
51
|
+
setShowRecognitionLanguageSelector(false);
|
|
52
|
+
setShowTranslationLanguageSelector(false);
|
|
53
|
+
}
|
|
54
|
+
}, [visible]);
|
|
55
|
+
|
|
56
|
+
// Language options - Supported recognition languages
|
|
57
|
+
const recognitionLanguageOptions: LanguageOption[] = [
|
|
58
|
+
{ value: 'zh', label: '中文' },
|
|
59
|
+
{ value: 'en', label: 'English' }
|
|
60
|
+
];
|
|
61
|
+
|
|
62
|
+
// Translation options - Target translation languages (base list)
|
|
63
|
+
// Use native language names for better UX, but keep "no translation" internationalized
|
|
64
|
+
const baseTranslationLanguageOptions: LanguageOption[] = [
|
|
65
|
+
{ value: '', label: t('no-translation') },
|
|
66
|
+
{ value: 'zh', label: 'Chinese (中文)' },
|
|
67
|
+
{ value: 'en', label: 'English (英语)' },
|
|
68
|
+
{ value: 'vi', label: 'Vietnamese (越南语)' },
|
|
69
|
+
{ value: 'ja', label: 'Japanese (日语)' },
|
|
70
|
+
{ value: 'ko', label: 'Korean (韩语)' },
|
|
71
|
+
{ value: 'id', label: 'Indonesian (印尼语)' },
|
|
72
|
+
{ value: 'th', label: 'Thai (泰语)' },
|
|
73
|
+
{ value: 'pt', label: 'Portuguese (葡萄牙语)' },
|
|
74
|
+
{ value: 'ar', label: 'Arabic (阿拉伯语)' },
|
|
75
|
+
{ value: 'es', label: 'Spanish (西班牙语)' },
|
|
76
|
+
{ value: 'fr', label: 'French (法语)' },
|
|
77
|
+
{ value: 'ms', label: 'Malay (马来语)' },
|
|
78
|
+
{ value: 'de', label: 'German (德语)' },
|
|
79
|
+
{ value: 'it', label: 'Italian (意大利语)' },
|
|
80
|
+
{ value: 'ru', label: 'Russian (俄语)' }
|
|
81
|
+
];
|
|
82
|
+
|
|
83
|
+
// Dynamically calculate translation options - exclude current recognition language
|
|
84
|
+
const translationLanguageOptions = baseTranslationLanguageOptions.filter(
|
|
85
|
+
option => option.value === '' || option.value !== recognitionLanguage
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
const getLanguageDisplayName = (value: string) => {
|
|
89
|
+
const allOptions = [...recognitionLanguageOptions, ...translationLanguageOptions];
|
|
90
|
+
const option = allOptions.find(opt => opt.value === value);
|
|
91
|
+
return option?.label || '';
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
const closeModal = () => {
|
|
95
|
+
onVisibleChange(false);
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
const selectRecognitionLanguage = (value: string) => {
|
|
99
|
+
setRecognitionLanguage(value);
|
|
100
|
+
setShowRecognitionLanguageSelector(false);
|
|
101
|
+
|
|
102
|
+
// 如果翻译语言与新的识别语言相同,重置翻译语言
|
|
103
|
+
if (translationLanguage === value) {
|
|
104
|
+
setTranslationLanguage('');
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
const selectTranslationLanguage = (value: string) => {
|
|
109
|
+
setTranslationLanguage(value);
|
|
110
|
+
setShowTranslationLanguageSelector(false);
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
const toggleSubtitle = () => {
|
|
114
|
+
setSubtitleEnabled(!subtitleEnabled);
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
const confirmSettings = async () => {
|
|
118
|
+
try {
|
|
119
|
+
// Save current settings as confirmed settings
|
|
120
|
+
confirmedSettingsRef.current = {
|
|
121
|
+
recognitionLanguage,
|
|
122
|
+
translationLanguage,
|
|
123
|
+
subtitleEnabled
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
// Call updateRealTimeTranscriber when settings are confirmed
|
|
127
|
+
const translationLanguages = translationLanguage ? [translationLanguage] : [];
|
|
128
|
+
|
|
129
|
+
await TUICallKitAPI._aiAssistant.updateRealTimeTranscriber({
|
|
130
|
+
sourceLanguage: recognitionLanguage,
|
|
131
|
+
translationLanguages: translationLanguages,
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
// Emit confirm event
|
|
135
|
+
onConfirm({
|
|
136
|
+
recognitionLanguage,
|
|
137
|
+
translationLanguage,
|
|
138
|
+
subtitleEnabled
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
closeModal();
|
|
142
|
+
} catch (error) {
|
|
143
|
+
console.error('Failed to update AI transcriber settings:', error);
|
|
144
|
+
}
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
// Don't render if not visible
|
|
148
|
+
if (!visible) {
|
|
149
|
+
return null;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return (
|
|
153
|
+
<div className="subtitle-settings-overlay">
|
|
154
|
+
<div className="subtitle-settings-modal" onClick={(e) => e.stopPropagation()}>
|
|
155
|
+
{/* Header */}
|
|
156
|
+
<div className="modal-header">
|
|
157
|
+
<h3 className="modal-title">{t('ai-subtitle-settings')}</h3>
|
|
158
|
+
</div>
|
|
159
|
+
|
|
160
|
+
{/* Content */}
|
|
161
|
+
<div className="modal-content">
|
|
162
|
+
{/* Recognition and translation settings group */}
|
|
163
|
+
<div className="setting-group">
|
|
164
|
+
<div className="group-title">{t('recognition-and-translation-settings')}</div>
|
|
165
|
+
<div className="group-content">
|
|
166
|
+
{/* Recognition language */}
|
|
167
|
+
<div className="setting-section">
|
|
168
|
+
<div className="section-header" onClick={() => setShowRecognitionLanguageSelector(true)}>
|
|
169
|
+
<span className="section-title">{t('recognition-language')}</span>
|
|
170
|
+
<span className="section-value">
|
|
171
|
+
{getLanguageDisplayName(recognitionLanguage)}
|
|
172
|
+
<svg className="arrow-icon" width="16" height="16" viewBox="0 0 16 16" fill="none">
|
|
173
|
+
<path d="M6 4L10 8L6 12" stroke="#999" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
|
|
174
|
+
</svg>
|
|
175
|
+
</span>
|
|
176
|
+
</div>
|
|
177
|
+
</div>
|
|
178
|
+
|
|
179
|
+
{/* Translation language */}
|
|
180
|
+
<div className="setting-section">
|
|
181
|
+
<div className="section-header" onClick={() => setShowTranslationLanguageSelector(true)}>
|
|
182
|
+
<span className="section-title">{t('translation-language')}</span>
|
|
183
|
+
<span className="section-value">
|
|
184
|
+
{getLanguageDisplayName(translationLanguage) || t('no-translation')}
|
|
185
|
+
<svg className="arrow-icon" width="16" height="16" viewBox="0 0 16 16" fill="none">
|
|
186
|
+
<path d="M6 4L10 8L6 12" stroke="#999" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
|
|
187
|
+
</svg>
|
|
188
|
+
</span>
|
|
189
|
+
</div>
|
|
190
|
+
</div>
|
|
191
|
+
|
|
192
|
+
{/* Subtitle display bilingual */}
|
|
193
|
+
<div className="setting-section">
|
|
194
|
+
<div className="section-header">
|
|
195
|
+
<span className="section-title">{t('subtitle-display-bilingual')}</span>
|
|
196
|
+
<div
|
|
197
|
+
className={`toggle-switch ${subtitleEnabled ? 'active' : ''}`}
|
|
198
|
+
onClick={toggleSubtitle}
|
|
199
|
+
>
|
|
200
|
+
<div className="toggle-slider"></div>
|
|
201
|
+
</div>
|
|
202
|
+
</div>
|
|
203
|
+
</div>
|
|
204
|
+
</div>
|
|
205
|
+
</div>
|
|
206
|
+
</div>
|
|
207
|
+
|
|
208
|
+
{/* Footer with buttons */}
|
|
209
|
+
<div className="modal-footer">
|
|
210
|
+
<button className="btn btn-cancel" onClick={closeModal}>{t('Cancel')}</button>
|
|
211
|
+
<button className="btn btn-confirm" onClick={confirmSettings}>{t('confirm')}</button>
|
|
212
|
+
</div>
|
|
213
|
+
</div>
|
|
214
|
+
|
|
215
|
+
{/* Language Selector Modals */}
|
|
216
|
+
{/* Recognition Language Selector */}
|
|
217
|
+
{showRecognitionLanguageSelector && (
|
|
218
|
+
<div className="language-selector-overlay" onClick={() => setShowRecognitionLanguageSelector(false)}>
|
|
219
|
+
<div className="language-selector" onClick={(e) => e.stopPropagation()}>
|
|
220
|
+
<div className="selector-header">
|
|
221
|
+
<h3 className="selector-title">{t('select-recognition-language')}</h3>
|
|
222
|
+
<div className="header-placeholder"></div>
|
|
223
|
+
</div>
|
|
224
|
+
<div className="language-list">
|
|
225
|
+
{recognitionLanguageOptions.map((lang) => (
|
|
226
|
+
<div
|
|
227
|
+
key={lang.value}
|
|
228
|
+
className={`language-item ${recognitionLanguage === lang.value ? 'selected' : ''}`}
|
|
229
|
+
onClick={() => selectRecognitionLanguage(lang.value)}
|
|
230
|
+
>
|
|
231
|
+
<span>{lang.label}</span>
|
|
232
|
+
</div>
|
|
233
|
+
))}
|
|
234
|
+
</div>
|
|
235
|
+
</div>
|
|
236
|
+
</div>
|
|
237
|
+
)}
|
|
238
|
+
|
|
239
|
+
{/* Translation Language Selector */}
|
|
240
|
+
{showTranslationLanguageSelector && (
|
|
241
|
+
<div className="language-selector-overlay" onClick={() => setShowTranslationLanguageSelector(false)}>
|
|
242
|
+
<div className="language-selector" onClick={(e) => e.stopPropagation()}>
|
|
243
|
+
<div className="selector-header">
|
|
244
|
+
<h3 className="selector-title">{t('select-translation-language')}</h3>
|
|
245
|
+
<div className="header-placeholder"></div>
|
|
246
|
+
</div>
|
|
247
|
+
<div className="language-list">
|
|
248
|
+
{translationLanguageOptions.map((lang) => (
|
|
249
|
+
<div
|
|
250
|
+
key={lang.value}
|
|
251
|
+
className={`language-item ${translationLanguage === lang.value ? 'selected' : ''}`}
|
|
252
|
+
onClick={() => selectTranslationLanguage(lang.value)}
|
|
253
|
+
>
|
|
254
|
+
<span>{lang.label}</span>
|
|
255
|
+
</div>
|
|
256
|
+
))}
|
|
257
|
+
</div>
|
|
258
|
+
</div>
|
|
259
|
+
</div>
|
|
260
|
+
)}
|
|
261
|
+
</div>
|
|
262
|
+
);
|
|
263
|
+
};
|
|
264
|
+
|
|
265
|
+
export default SubtitleSettingsH5;
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
.subtitle-settings-overlay-pc {
|
|
2
|
+
position: absolute;
|
|
3
|
+
top: 50%;
|
|
4
|
+
left: 50%;
|
|
5
|
+
width: 489px;
|
|
6
|
+
height: 430px;
|
|
7
|
+
transform: translate(-50%, -50%);
|
|
8
|
+
display: flex;
|
|
9
|
+
align-items: center;
|
|
10
|
+
justify-content: center;
|
|
11
|
+
z-index: 1000;
|
|
12
|
+
font-family: PingFang SC;
|
|
13
|
+
box-shadow: 0px 2px 6px 0px var(--Black-8);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
.subtitle-settings-modal-pc {
|
|
17
|
+
background: white;
|
|
18
|
+
border-radius: 12px;
|
|
19
|
+
width: 480px;
|
|
20
|
+
height: 430px;
|
|
21
|
+
overflow: hidden;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
.modal-header-pc {
|
|
25
|
+
display: flex;
|
|
26
|
+
align-items: center;
|
|
27
|
+
justify-content: space-between;
|
|
28
|
+
padding: 24px 24px 10px 24px;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
.modal-title-pc {
|
|
32
|
+
font-size: 16px;
|
|
33
|
+
font-weight: 600;
|
|
34
|
+
color: #000000E5;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.close-btn-pc {
|
|
38
|
+
background: none;
|
|
39
|
+
border: none;
|
|
40
|
+
cursor: pointer;
|
|
41
|
+
padding: 4px;
|
|
42
|
+
border-radius: 4px;
|
|
43
|
+
transition: background-color 0.2s;
|
|
44
|
+
|
|
45
|
+
&:hover {
|
|
46
|
+
background-color: #f5f5f5;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.modal-content-pc {
|
|
51
|
+
padding: 24px;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
.setting-item-pc {
|
|
55
|
+
margin-bottom: 24px;
|
|
56
|
+
|
|
57
|
+
&:last-child {
|
|
58
|
+
margin-bottom: 0;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
.setting-label-pc {
|
|
63
|
+
display: block;
|
|
64
|
+
font-size: 14px;
|
|
65
|
+
font-weight: 400;
|
|
66
|
+
color: #4F586B;
|
|
67
|
+
margin-bottom: 8px;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
.modal-footer-pc {
|
|
71
|
+
display: flex;
|
|
72
|
+
justify-content: flex-end;
|
|
73
|
+
gap: 20px;
|
|
74
|
+
padding: 16px 24px 24px;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
.btn-pc {
|
|
78
|
+
width: 72px;
|
|
79
|
+
height: 32px;
|
|
80
|
+
border-radius: 6px;
|
|
81
|
+
font-size: 14px;
|
|
82
|
+
font-weight: 500;
|
|
83
|
+
cursor: pointer;
|
|
84
|
+
border-radius: 26px;
|
|
85
|
+
transition: all 0.2s;
|
|
86
|
+
border: 1px solid transparent;
|
|
87
|
+
|
|
88
|
+
&.btn-cancel-pc {
|
|
89
|
+
color: #1C66E5;
|
|
90
|
+
border-color: #1C66E5;
|
|
91
|
+
background-color: white;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
&.btn-confirm-pc {
|
|
95
|
+
background: #1C66E5;
|
|
96
|
+
color: white;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import React, { useState, useEffect, useRef } from 'react';
|
|
2
|
+
import { TUICallKitAPI } from '../../../../../TUICallService/index';
|
|
3
|
+
import { useTranslate } from '../../../../hooks';
|
|
4
|
+
import CustomSelect, { SelectOption } from '../../../base/CustomSelect/CustomSelect';
|
|
5
|
+
import './SubtitleSettingsPC.scss';
|
|
6
|
+
|
|
7
|
+
interface SubtitleSettingsPCProps {
|
|
8
|
+
visible: boolean;
|
|
9
|
+
onVisibleChange: (visible: boolean) => void;
|
|
10
|
+
onConfirm: (settings: {
|
|
11
|
+
recognitionLanguage: string;
|
|
12
|
+
translationLanguage: string;
|
|
13
|
+
subtitleDisplay: string;
|
|
14
|
+
}) => void;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const SubtitleSettingsPC: React.FC<SubtitleSettingsPCProps> = ({
|
|
18
|
+
visible,
|
|
19
|
+
onVisibleChange,
|
|
20
|
+
onConfirm
|
|
21
|
+
}) => {
|
|
22
|
+
// Use i18n translation
|
|
23
|
+
const { t } = useTranslate();
|
|
24
|
+
|
|
25
|
+
// Confirmed settings (saved on confirm button click)
|
|
26
|
+
const confirmedSettingsRef = useRef({
|
|
27
|
+
recognitionLanguage: 'zh',
|
|
28
|
+
translationLanguage: '',
|
|
29
|
+
subtitleDisplay: 'both'
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
// Temporary editing state (current user selections)
|
|
33
|
+
const [recognitionLanguage, setRecognitionLanguage] = useState('zh');
|
|
34
|
+
const [translationLanguage, setTranslationLanguage] = useState('');
|
|
35
|
+
const [subtitleDisplay, setSubtitleDisplay] = useState('both');
|
|
36
|
+
|
|
37
|
+
// Reset to last confirmed settings when modal opens
|
|
38
|
+
useEffect(() => {
|
|
39
|
+
if (visible) {
|
|
40
|
+
setRecognitionLanguage(confirmedSettingsRef.current.recognitionLanguage);
|
|
41
|
+
setTranslationLanguage(confirmedSettingsRef.current.translationLanguage);
|
|
42
|
+
setSubtitleDisplay(confirmedSettingsRef.current.subtitleDisplay);
|
|
43
|
+
}
|
|
44
|
+
}, [visible]);
|
|
45
|
+
|
|
46
|
+
// Language options for CustomSelect - Supported recognition languages
|
|
47
|
+
const languageOptions: SelectOption[] = [
|
|
48
|
+
{ value: 'zh', label: '中文' },
|
|
49
|
+
{ value: 'en', label: 'English' }
|
|
50
|
+
];
|
|
51
|
+
|
|
52
|
+
// Translation options for CustomSelect - Target translation languages (base list)
|
|
53
|
+
// Use native language names for better UX, but keep "no translation" internationalized
|
|
54
|
+
const baseTranslationOptions: SelectOption[] = [
|
|
55
|
+
{ value: '', label: t('no-translation') },
|
|
56
|
+
{ value: 'zh', label: 'Chinese (中文)' },
|
|
57
|
+
{ value: 'en', label: 'English (英语)' },
|
|
58
|
+
{ value: 'vi', label: 'Vietnamese (越南语)' },
|
|
59
|
+
{ value: 'ja', label: 'Japanese (日语)' },
|
|
60
|
+
{ value: 'ko', label: 'Korean (韩语)' },
|
|
61
|
+
{ value: 'id', label: 'Indonesian (印尼语)' },
|
|
62
|
+
{ value: 'th', label: 'Thai (泰语)' },
|
|
63
|
+
{ value: 'pt', label: 'Portuguese (葡萄牙语)' },
|
|
64
|
+
{ value: 'ar', label: 'Arabic (阿拉伯语)' },
|
|
65
|
+
{ value: 'es', label: 'Spanish (西班牙语)' },
|
|
66
|
+
{ value: 'fr', label: 'French (法语)' },
|
|
67
|
+
{ value: 'ms', label: 'Malay (马来语)' },
|
|
68
|
+
{ value: 'de', label: 'German (德语)' },
|
|
69
|
+
{ value: 'it', label: 'Italian (意大利语)' },
|
|
70
|
+
{ value: 'ru', label: 'Russian (俄语)' }
|
|
71
|
+
];
|
|
72
|
+
|
|
73
|
+
// Dynamically calculate translation options - exclude current recognition language
|
|
74
|
+
const translationOptions = baseTranslationOptions.filter(
|
|
75
|
+
option => option.value === '' || option.value !== recognitionLanguage
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
// Subtitle display options
|
|
79
|
+
const subtitleDisplayOptions: SelectOption[] = [
|
|
80
|
+
{ value: 'both', label: t('show-bilingual') },
|
|
81
|
+
{ value: 'original', label: t('show-original-only') }
|
|
82
|
+
];
|
|
83
|
+
|
|
84
|
+
const handleOverlayClick = () => {
|
|
85
|
+
closeModal();
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
const closeModal = () => {
|
|
89
|
+
onVisibleChange(false);
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
const handleLanguageChange = (value: string | number) => {
|
|
93
|
+
const newLanguage = String(value);
|
|
94
|
+
setRecognitionLanguage(newLanguage);
|
|
95
|
+
|
|
96
|
+
// 如果翻译语言与新的识别语言相同,重置翻译语言
|
|
97
|
+
if (translationLanguage === newLanguage) {
|
|
98
|
+
setTranslationLanguage('');
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
const handleTranslationChange = (value: string | number) => {
|
|
103
|
+
setTranslationLanguage(String(value));
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
const handleSubtitleDisplayChange = (value: string | number) => {
|
|
107
|
+
setSubtitleDisplay(String(value));
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
const confirmSettings = async () => {
|
|
111
|
+
try {
|
|
112
|
+
// Save current settings as confirmed settings
|
|
113
|
+
confirmedSettingsRef.current = {
|
|
114
|
+
recognitionLanguage,
|
|
115
|
+
translationLanguage,
|
|
116
|
+
subtitleDisplay
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
// Call updateRealTimeTranscriber when settings are confirmed
|
|
120
|
+
const translationLanguages = translationLanguage ? [translationLanguage] : [];
|
|
121
|
+
|
|
122
|
+
await TUICallKitAPI._aiAssistant.updateRealTimeTranscriber({
|
|
123
|
+
sourceLanguage: recognitionLanguage,
|
|
124
|
+
translationLanguages: translationLanguages,
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
// Emit confirm event
|
|
128
|
+
onConfirm({
|
|
129
|
+
recognitionLanguage,
|
|
130
|
+
translationLanguage,
|
|
131
|
+
subtitleDisplay
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
closeModal();
|
|
135
|
+
} catch (error) {
|
|
136
|
+
console.error('Failed to update AI transcriber settings:', error);
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
// Don't render if not visible
|
|
141
|
+
if (!visible) {
|
|
142
|
+
return null;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return (
|
|
146
|
+
<div className="subtitle-settings-overlay-pc" onClick={handleOverlayClick}>
|
|
147
|
+
<div className="subtitle-settings-modal-pc" onClick={(e) => e.stopPropagation()}>
|
|
148
|
+
<div className="modal-header-pc">
|
|
149
|
+
<h3 className="modal-title-pc">{t('ai-subtitle-settings')}</h3>
|
|
150
|
+
<button className="close-btn-pc" onClick={closeModal}>
|
|
151
|
+
<svg width="16" height="16" viewBox="0 0 16 16" fill="none">
|
|
152
|
+
<path d="M12 4L4 12M4 4L12 12" stroke="#666" strokeWidth="2" strokeLinecap="round"/>
|
|
153
|
+
</svg>
|
|
154
|
+
</button>
|
|
155
|
+
</div>
|
|
156
|
+
|
|
157
|
+
<div className="modal-content-pc">
|
|
158
|
+
{/* Recognition language */}
|
|
159
|
+
<div className="setting-item-pc">
|
|
160
|
+
<label className="setting-label-pc">{t('recognition-language')}</label>
|
|
161
|
+
<CustomSelect
|
|
162
|
+
value={recognitionLanguage}
|
|
163
|
+
options={languageOptions}
|
|
164
|
+
onChange={handleLanguageChange}
|
|
165
|
+
/>
|
|
166
|
+
</div>
|
|
167
|
+
|
|
168
|
+
{/* Translation language */}
|
|
169
|
+
<div className="setting-item-pc">
|
|
170
|
+
<label className="setting-label-pc">{t('translation-language')}</label>
|
|
171
|
+
<CustomSelect
|
|
172
|
+
value={translationLanguage}
|
|
173
|
+
options={translationOptions}
|
|
174
|
+
onChange={handleTranslationChange}
|
|
175
|
+
/>
|
|
176
|
+
</div>
|
|
177
|
+
|
|
178
|
+
{/* Subtitle display settings */}
|
|
179
|
+
<div className="setting-item-pc">
|
|
180
|
+
<label className="setting-label-pc">{t('subtitle-display-settings')}</label>
|
|
181
|
+
<CustomSelect
|
|
182
|
+
value={subtitleDisplay}
|
|
183
|
+
options={subtitleDisplayOptions}
|
|
184
|
+
onChange={handleSubtitleDisplayChange}
|
|
185
|
+
/>
|
|
186
|
+
</div>
|
|
187
|
+
</div>
|
|
188
|
+
|
|
189
|
+
<div className="modal-footer-pc">
|
|
190
|
+
<button className="btn-pc btn-cancel-pc" onClick={closeModal}>{t('Cancel')}</button>
|
|
191
|
+
<button className="btn-pc btn-confirm-pc" onClick={confirmSettings}>{t('save')}</button>
|
|
192
|
+
</div>
|
|
193
|
+
</div>
|
|
194
|
+
</div>
|
|
195
|
+
);
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
export default SubtitleSettingsPC;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { default as SubtitleContent } from './SubtitleContent';
|
|
2
|
+
export { default as SubtitleSettingsPC } from './SubtitleSettingsPC';
|
|
3
|
+
export { default as SubtitleSettingsH5 } from './SubtitleSettingsH5';
|
|
4
|
+
export { default as AITranscriberSwitchPC } from './AITranscriberSwitchPC';
|
|
5
|
+
export { default as AITranscriberSwitchH5 } from './AITranscriberSwitchH5';
|
|
@@ -3,9 +3,11 @@ import Time from '../../../base/Time/Time';
|
|
|
3
3
|
import Col from '../../../base/Grid/Col';
|
|
4
4
|
import Row from '../../../base/Grid/Row';
|
|
5
5
|
import Minimize from '../../ButtonPanel/button/Minimize';
|
|
6
|
+
import { AITranscriberSwitchH5 } from '../../AIAssistant/components';
|
|
6
7
|
import { CallRole, CallStatus } from '../../../../../TUICallService';
|
|
7
8
|
import Tip from '../../Tip/Tip';
|
|
8
9
|
import { CallInfoContext } from '../../../../context';
|
|
10
|
+
import { useAIAssistant } from '../../../../hooks/useAIAssistant';
|
|
9
11
|
import topBarH5Style from './TopBarH5.module.scss';
|
|
10
12
|
|
|
11
13
|
interface ITopBarH5Props {
|
|
@@ -17,13 +19,21 @@ export default function TopBarH5(props: ITopBarH5Props) {
|
|
|
17
19
|
const {
|
|
18
20
|
isGroupCall, callStatus, callRole, allowedMinimized,
|
|
19
21
|
} = useContext(CallInfoContext);
|
|
22
|
+
|
|
23
|
+
const { isAITranscriberEnabled } = useAIAssistant();
|
|
24
|
+
|
|
20
25
|
const showTip = (isGroupCall
|
|
21
26
|
&& (callStatus === CallStatus.CONNECTED || callRole === CallRole.CALLER));
|
|
27
|
+
const showAITranscriberSwitch = callStatus === CallStatus.CONNECTED && isAITranscriberEnabled;
|
|
28
|
+
|
|
22
29
|
return (
|
|
23
30
|
<div className={topBarH5Style['top-bar-container']}>
|
|
24
31
|
<Row className={topBarH5Style['top-bar']}>
|
|
25
32
|
<Col justify='start' span={4}>
|
|
26
|
-
{
|
|
33
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: '12px' }}>
|
|
34
|
+
{allowedMinimized && <Minimize />}
|
|
35
|
+
{showAITranscriberSwitch && <AITranscriberSwitchH5 />}
|
|
36
|
+
</div>
|
|
27
37
|
</Col>
|
|
28
38
|
<Col align='center' span={4}>
|
|
29
39
|
{callStatus === CallStatus.CONNECTED
|
|
@@ -5,4 +5,24 @@
|
|
|
5
5
|
line-height: 20px;
|
|
6
6
|
margin-top: 20px;
|
|
7
7
|
z-index: 2;
|
|
8
|
+
display: flex;
|
|
9
|
+
align-items: center;
|
|
10
|
+
justify-content: flex-end;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
.topbar-center {
|
|
14
|
+
position: absolute;
|
|
15
|
+
left: 50%;
|
|
16
|
+
transform: translateX(-50%);
|
|
17
|
+
display: flex;
|
|
18
|
+
align-items: center;
|
|
19
|
+
justify-content: center;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.topbar-right-controls {
|
|
23
|
+
margin-right: 10px;
|
|
24
|
+
display: flex;
|
|
25
|
+
align-items: center;
|
|
26
|
+
gap: 16px;
|
|
27
|
+
z-index: 3;
|
|
8
28
|
}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import React, { useContext } from 'react';
|
|
2
2
|
import Time from '../../../base/Time/Time';
|
|
3
|
-
import Col from '../../../base/Grid/Col';
|
|
4
|
-
import Row from '../../../base/Grid/Row';
|
|
5
3
|
import Minimize from '../../ButtonPanel/button/Minimize/Minimize';
|
|
6
4
|
import FullScreen from '../../ButtonPanel/button/FullScreen';
|
|
5
|
+
import { AITranscriberSwitchPC } from '../../AIAssistant/components';
|
|
7
6
|
import { CallStatus } from '../../../../../TUICallService';
|
|
8
7
|
import { CallInfoContext } from '../../../../context';
|
|
8
|
+
import { useAIAssistant } from '../../../../hooks/useAIAssistant';
|
|
9
9
|
import topBarPCStyle from './TopBarPC.module.scss';
|
|
10
10
|
|
|
11
11
|
interface ITopBarPCProps {
|
|
@@ -20,17 +20,22 @@ export default function TopBarPC(props: ITopBarPCProps) {
|
|
|
20
20
|
allowedMinimized,
|
|
21
21
|
allowedFullScreen,
|
|
22
22
|
} = useContext(CallInfoContext);
|
|
23
|
+
|
|
24
|
+
const { isAITranscriberEnabled } = useAIAssistant();
|
|
25
|
+
|
|
23
26
|
const showTime = callStatus === CallStatus.CONNECTED && !isGroupCall;
|
|
27
|
+
const showAITranscriberSwitch = callStatus === CallStatus.CONNECTED && isAITranscriberEnabled;
|
|
28
|
+
|
|
24
29
|
return (
|
|
25
|
-
<
|
|
26
|
-
<
|
|
27
|
-
<Col span={4}>
|
|
30
|
+
<div className={topBarPCStyle['single-top-bar']}>
|
|
31
|
+
<div className={topBarPCStyle['topbar-center']}>
|
|
28
32
|
{showTime && <Time callDuration={callDuration} />}
|
|
29
|
-
</
|
|
30
|
-
<
|
|
33
|
+
</div>
|
|
34
|
+
<div className={topBarPCStyle['topbar-right-controls']}>
|
|
35
|
+
{showAITranscriberSwitch && <AITranscriberSwitchPC />}
|
|
31
36
|
{allowedMinimized && <Minimize />}
|
|
32
37
|
{allowedFullScreen && <FullScreen domID='tuicallkit-id' />}
|
|
33
|
-
</
|
|
34
|
-
</
|
|
38
|
+
</div>
|
|
39
|
+
</div>
|
|
35
40
|
);
|
|
36
41
|
}
|