Update ui/tabs.py
Browse files- ui/tabs.py +193 -76
ui/tabs.py
CHANGED
|
@@ -1018,7 +1018,7 @@ def create_streaming_voice_tab(streaming_service: StreamingVoiceService):
|
|
| 1018 |
with gr.Blocks() as streaming_tab:
|
| 1019 |
gr.Markdown("## 🎤 Trò chuyện giọng nói thời gian thực - Tối ưu hóa")
|
| 1020 |
|
| 1021 |
-
#
|
| 1022 |
vad_result_state = gr.State(value=None)
|
| 1023 |
|
| 1024 |
with gr.Row():
|
|
@@ -1027,6 +1027,7 @@ def create_streaming_voice_tab(streaming_service: StreamingVoiceService):
|
|
| 1027 |
with gr.Row():
|
| 1028 |
start_btn = gr.Button("🎙️ Bắt đầu VAD", variant="primary")
|
| 1029 |
stop_btn = gr.Button("🛑 Dừng VAD", variant="secondary")
|
|
|
|
| 1030 |
|
| 1031 |
gr.Markdown("### Chế độ tự động (VAD)")
|
| 1032 |
gr.Markdown("Hệ thống tự động nhận diện khi bạn bắt đầu nói")
|
|
@@ -1038,6 +1039,14 @@ def create_streaming_voice_tab(streaming_service: StreamingVoiceService):
|
|
| 1038 |
interactive=False
|
| 1039 |
)
|
| 1040 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1041 |
gr.Markdown("### Chế độ thủ công")
|
| 1042 |
microphone = gr.Microphone(
|
| 1043 |
label="🎤 Nhấn để nói thủ công",
|
|
@@ -1052,27 +1061,21 @@ def create_streaming_voice_tab(streaming_service: StreamingVoiceService):
|
|
| 1052 |
)
|
| 1053 |
refresh_latency_btn = gr.Button("🔄 Refresh Metrics", size="sm")
|
| 1054 |
|
| 1055 |
-
clear_btn = gr.Button("🗑️ Xóa hội thoại")
|
| 1056 |
-
|
| 1057 |
-
# State info
|
| 1058 |
-
state_info = gr.Textbox(
|
| 1059 |
-
label="Thông tin hệ thống",
|
| 1060 |
-
value="Khởi tạo...",
|
| 1061 |
-
lines=3,
|
| 1062 |
-
interactive=False
|
| 1063 |
-
)
|
| 1064 |
-
|
| 1065 |
with gr.Column(scale=2):
|
|
|
|
| 1066 |
transcription_box = gr.Textbox(
|
| 1067 |
-
label="📝 Bạn
|
| 1068 |
-
lines=
|
| 1069 |
-
interactive=False
|
|
|
|
| 1070 |
)
|
| 1071 |
|
|
|
|
| 1072 |
response_box = gr.Textbox(
|
| 1073 |
label="🤖 Phản hồi AI",
|
| 1074 |
-
lines=
|
| 1075 |
-
interactive=False
|
|
|
|
| 1076 |
)
|
| 1077 |
|
| 1078 |
audio_output = gr.Audio(
|
|
@@ -1083,98 +1086,207 @@ def create_streaming_voice_tab(streaming_service: StreamingVoiceService):
|
|
| 1083 |
|
| 1084 |
# State variables
|
| 1085 |
is_vad_active = gr.State(value=False)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1086 |
|
| 1087 |
def vad_callback(result):
|
| 1088 |
-
"""Callback khi VAD phát hiện speech
|
| 1089 |
-
print(f"🎯
|
| 1090 |
-
|
| 1091 |
|
| 1092 |
def start_vad():
|
| 1093 |
-
"""Bắt đầu VAD
|
| 1094 |
-
|
| 1095 |
-
|
| 1096 |
-
|
| 1097 |
-
|
| 1098 |
-
|
| 1099 |
-
|
| 1100 |
-
|
| 1101 |
-
|
| 1102 |
-
|
| 1103 |
-
|
| 1104 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1105 |
|
| 1106 |
def stop_vad():
|
| 1107 |
"""Dừng VAD"""
|
| 1108 |
-
|
| 1109 |
-
|
| 1110 |
-
|
| 1111 |
-
|
| 1112 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1113 |
|
| 1114 |
def process_microphone(audio_data):
|
| 1115 |
-
"""Xử lý microphone input"""
|
| 1116 |
if audio_data is None:
|
| 1117 |
-
return "Chưa có âm thanh", "Hãy nói gì đó...", None, "VAD:
|
| 1118 |
|
| 1119 |
try:
|
|
|
|
|
|
|
|
|
|
| 1120 |
result = streaming_service.process_streaming_audio(audio_data)
|
|
|
|
| 1121 |
state = streaming_service.get_conversation_state()
|
| 1122 |
state_text = f"Manual mode\nHistory: {state['history_length']} messages"
|
| 1123 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1124 |
except Exception as e:
|
| 1125 |
-
|
|
|
|
| 1126 |
|
| 1127 |
def check_vad_results():
|
| 1128 |
-
"""Kiểm tra
|
| 1129 |
-
|
| 1130 |
-
|
| 1131 |
-
|
| 1132 |
-
|
| 1133 |
-
state = streaming_service.get_conversation_state()
|
| 1134 |
-
state_text = f"VAD mode\nHistory: {state['history_length']} messages\nQueue: {state['queue_size']}"
|
| 1135 |
|
| 1136 |
-
|
| 1137 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1138 |
|
| 1139 |
def clear_chat():
|
| 1140 |
"""Xóa hội thoại"""
|
| 1141 |
streaming_service.clear_conversation()
|
| 1142 |
state = streaming_service.get_conversation_state()
|
| 1143 |
-
state_text = f"Đã xóa hội thoại\nHistory: {state['history_length']} messages"
|
| 1144 |
-
|
|
|
|
| 1145 |
|
| 1146 |
def refresh_latency():
|
| 1147 |
"""Làm mới latency metrics"""
|
| 1148 |
-
|
| 1149 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1150 |
|
| 1151 |
-
def
|
| 1152 |
"""Cập nhật thông tin trạng thái"""
|
| 1153 |
-
|
| 1154 |
-
|
| 1155 |
-
|
| 1156 |
-
|
| 1157 |
-
|
| 1158 |
-
|
| 1159 |
-
|
| 1160 |
-
|
| 1161 |
-
|
| 1162 |
-
|
| 1163 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1164 |
|
| 1165 |
# Event handlers
|
| 1166 |
-
start_btn.click(
|
| 1167 |
-
|
|
|
|
|
|
|
| 1168 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1169 |
microphone.stream(
|
| 1170 |
process_microphone,
|
| 1171 |
inputs=[microphone],
|
| 1172 |
-
outputs=[transcription_box, response_box, audio_output,
|
| 1173 |
)
|
| 1174 |
|
| 1175 |
clear_btn.click(
|
| 1176 |
clear_chat,
|
| 1177 |
-
outputs=[transcription_box, response_box, audio_output,
|
| 1178 |
)
|
| 1179 |
|
| 1180 |
refresh_latency_btn.click(
|
|
@@ -1182,16 +1294,21 @@ def create_streaming_voice_tab(streaming_service: StreamingVoiceService):
|
|
| 1182 |
outputs=[latency_display]
|
| 1183 |
)
|
| 1184 |
|
| 1185 |
-
#
|
| 1186 |
-
gr.Timer(
|
|
|
|
|
|
|
|
|
|
| 1187 |
fn=check_vad_results,
|
| 1188 |
-
outputs=[transcription_box, response_box, audio_output,
|
| 1189 |
)
|
| 1190 |
|
| 1191 |
-
# Timer để cập nhật system info
|
| 1192 |
-
gr.Timer(
|
| 1193 |
-
|
| 1194 |
-
|
|
|
|
|
|
|
| 1195 |
)
|
| 1196 |
|
| 1197 |
return streaming_tab
|
|
|
|
| 1018 |
with gr.Blocks() as streaming_tab:
|
| 1019 |
gr.Markdown("## 🎤 Trò chuyện giọng nói thời gian thực - Tối ưu hóa")
|
| 1020 |
|
| 1021 |
+
# Store VAD results
|
| 1022 |
vad_result_state = gr.State(value=None)
|
| 1023 |
|
| 1024 |
with gr.Row():
|
|
|
|
| 1027 |
with gr.Row():
|
| 1028 |
start_btn = gr.Button("🎙️ Bắt đầu VAD", variant="primary")
|
| 1029 |
stop_btn = gr.Button("🛑 Dừng VAD", variant="secondary")
|
| 1030 |
+
clear_btn = gr.Button("🗑️ Xóa hội thoại")
|
| 1031 |
|
| 1032 |
gr.Markdown("### Chế độ tự động (VAD)")
|
| 1033 |
gr.Markdown("Hệ thống tự động nhận diện khi bạn bắt đầu nói")
|
|
|
|
| 1039 |
interactive=False
|
| 1040 |
)
|
| 1041 |
|
| 1042 |
+
# Hiển thị trạng thái real-time
|
| 1043 |
+
status_display = gr.Textbox(
|
| 1044 |
+
label="🎯 Trạng thái hiện tại",
|
| 1045 |
+
value="Đang chờ...",
|
| 1046 |
+
interactive=False,
|
| 1047 |
+
lines=2
|
| 1048 |
+
)
|
| 1049 |
+
|
| 1050 |
gr.Markdown("### Chế độ thủ công")
|
| 1051 |
microphone = gr.Microphone(
|
| 1052 |
label="🎤 Nhấn để nói thủ công",
|
|
|
|
| 1061 |
)
|
| 1062 |
refresh_latency_btn = gr.Button("🔄 Refresh Metrics", size="sm")
|
| 1063 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1064 |
with gr.Column(scale=2):
|
| 1065 |
+
# Real-time transcription
|
| 1066 |
transcription_box = gr.Textbox(
|
| 1067 |
+
label="📝 Bạn đang nói (real-time)",
|
| 1068 |
+
lines=3,
|
| 1069 |
+
interactive=False,
|
| 1070 |
+
value="Nói gì đó để bắt đầu..."
|
| 1071 |
)
|
| 1072 |
|
| 1073 |
+
# AI Response
|
| 1074 |
response_box = gr.Textbox(
|
| 1075 |
label="🤖 Phản hồi AI",
|
| 1076 |
+
lines=5,
|
| 1077 |
+
interactive=False,
|
| 1078 |
+
value="Tôi sẽ trả lời bạn ở đây..."
|
| 1079 |
)
|
| 1080 |
|
| 1081 |
audio_output = gr.Audio(
|
|
|
|
| 1086 |
|
| 1087 |
# State variables
|
| 1088 |
is_vad_active = gr.State(value=False)
|
| 1089 |
+
last_vad_update = gr.State(value=0)
|
| 1090 |
+
|
| 1091 |
+
# Global variable for VAD callback
|
| 1092 |
+
vad_results_queue = queue.Queue()
|
| 1093 |
|
| 1094 |
def vad_callback(result):
|
| 1095 |
+
"""Callback khi VAD phát hiện speech"""
|
| 1096 |
+
print(f"🎯 VAD Callback: {result.get('transcription', 'No text')}")
|
| 1097 |
+
vad_results_queue.put(result)
|
| 1098 |
|
| 1099 |
def start_vad():
|
| 1100 |
+
"""Bắt đầu VAD"""
|
| 1101 |
+
try:
|
| 1102 |
+
# Set callback
|
| 1103 |
+
success = streaming_service.start_listening(vad_callback)
|
| 1104 |
+
|
| 1105 |
+
if success:
|
| 1106 |
+
is_vad_active.value = True
|
| 1107 |
+
status = "✅ VAD đang chạy - Hãy nói gì đó!"
|
| 1108 |
+
|
| 1109 |
+
# Hiển thị thông báo
|
| 1110 |
+
if streaming_service.speech_callback:
|
| 1111 |
+
streaming_service.speech_callback({
|
| 1112 |
+
'transcription': "VAD đã sẵn sàng! Hãy nói...",
|
| 1113 |
+
'response': "",
|
| 1114 |
+
'tts_audio': None,
|
| 1115 |
+
'status': 'listening'
|
| 1116 |
+
})
|
| 1117 |
+
|
| 1118 |
+
state = streaming_service.get_conversation_state()
|
| 1119 |
+
state_text = f"✅ VAD: Đang hoạt động\nQueue: {state['queue_size']}\nThreads: {state['worker_threads']}"
|
| 1120 |
+
status_msg = "🎤 Đang lắng nghe... nói đi!"
|
| 1121 |
+
|
| 1122 |
+
else:
|
| 1123 |
+
status = "❌ Không thể khởi động VAD"
|
| 1124 |
+
state_text = "Lỗi khởi động"
|
| 1125 |
+
status_msg = "Lỗi!"
|
| 1126 |
+
|
| 1127 |
+
return status, state_text, status_msg
|
| 1128 |
+
|
| 1129 |
+
except Exception as e:
|
| 1130 |
+
print(f"❌ Lỗi start_vad: {e}")
|
| 1131 |
+
return "❌ Lỗi khởi động", f"Lỗi: {e}", "Lỗi!"
|
| 1132 |
|
| 1133 |
def stop_vad():
|
| 1134 |
"""Dừng VAD"""
|
| 1135 |
+
try:
|
| 1136 |
+
streaming_service.stop_listening()
|
| 1137 |
+
is_vad_active.value = False
|
| 1138 |
+
|
| 1139 |
+
# Clear queue
|
| 1140 |
+
while not vad_results_queue.empty():
|
| 1141 |
+
try:
|
| 1142 |
+
vad_results_queue.get_nowait()
|
| 1143 |
+
except:
|
| 1144 |
+
pass
|
| 1145 |
+
|
| 1146 |
+
state = streaming_service.get_conversation_state()
|
| 1147 |
+
state_text = f"🛑 VAD: Đã dừng\nHistory: {state['history_length']} messages"
|
| 1148 |
+
status_msg = "Đã dừng lắng nghe"
|
| 1149 |
+
|
| 1150 |
+
return "🛑 VAD đã dừng", state_text, status_msg
|
| 1151 |
+
|
| 1152 |
+
except Exception as e:
|
| 1153 |
+
print(f"❌ Lỗi stop_vad: {e}")
|
| 1154 |
+
return "Lỗi!", f"Lỗi: {e}", "Lỗi!"
|
| 1155 |
|
| 1156 |
def process_microphone(audio_data):
|
| 1157 |
+
"""Xử lý microphone input manual mode"""
|
| 1158 |
if audio_data is None:
|
| 1159 |
+
return "Chưa có âm thanh", "Hãy nói gì đó...", None, "VAD: Tắt", "Manual mode"
|
| 1160 |
|
| 1161 |
try:
|
| 1162 |
+
print(f"🎤 Manual audio: {len(audio_data[1])} samples")
|
| 1163 |
+
|
| 1164 |
+
# Process with streaming service
|
| 1165 |
result = streaming_service.process_streaming_audio(audio_data)
|
| 1166 |
+
|
| 1167 |
state = streaming_service.get_conversation_state()
|
| 1168 |
state_text = f"Manual mode\nHistory: {state['history_length']} messages"
|
| 1169 |
+
|
| 1170 |
+
# Determine status
|
| 1171 |
+
if result['status'] == 'processing':
|
| 1172 |
+
status_msg = "⏳ Đang xử lý..."
|
| 1173 |
+
elif result['status'] == 'listening':
|
| 1174 |
+
status_msg = "🎤 Đang nghe..."
|
| 1175 |
+
else:
|
| 1176 |
+
status_msg = result['status']
|
| 1177 |
+
|
| 1178 |
+
return result['transcription'], result['response'], result['tts_audio'], state_text, status_msg
|
| 1179 |
+
|
| 1180 |
except Exception as e:
|
| 1181 |
+
print(f"❌ Lỗi process_microphone: {e}")
|
| 1182 |
+
return f"Lỗi: {e}", "Xin lỗi, có lỗi xảy ra", None, "Lỗi xử lý", "Lỗi!"
|
| 1183 |
|
| 1184 |
def check_vad_results():
|
| 1185 |
+
"""Kiểm tra và hiển thị kết quả VAD"""
|
| 1186 |
+
try:
|
| 1187 |
+
# Check if VAD is active
|
| 1188 |
+
if not is_vad_active.value:
|
| 1189 |
+
return gr.skip(), gr.skip(), gr.skip(), gr.skip(), gr.skip()
|
|
|
|
|
|
|
| 1190 |
|
| 1191 |
+
# Try to get result from queue
|
| 1192 |
+
try:
|
| 1193 |
+
result = vad_results_queue.get_nowait()
|
| 1194 |
+
|
| 1195 |
+
print(f"📥 Got VAD result: {result.get('transcription', 'No text')}")
|
| 1196 |
+
|
| 1197 |
+
state = streaming_service.get_conversation_state()
|
| 1198 |
+
state_text = f"VAD mode\nQueue: {state['queue_size']}\nThreads: {state['worker_threads']}"
|
| 1199 |
+
|
| 1200 |
+
# Determine status message
|
| 1201 |
+
if result.get('status') == 'processing':
|
| 1202 |
+
status_msg = "⏳ Đang xử lý VAD..."
|
| 1203 |
+
elif result.get('status') == 'partial':
|
| 1204 |
+
status_msg = "🎤 Đang nhận diện..."
|
| 1205 |
+
elif result.get('status') == 'completed':
|
| 1206 |
+
status_msg = "✅ Đã xử lý xong"
|
| 1207 |
+
else:
|
| 1208 |
+
status_msg = result.get('status', 'Đang lắng nghe')
|
| 1209 |
+
|
| 1210 |
+
return (
|
| 1211 |
+
result.get('transcription', ''),
|
| 1212 |
+
result.get('response', ''),
|
| 1213 |
+
result.get('tts_audio', None),
|
| 1214 |
+
state_text,
|
| 1215 |
+
status_msg
|
| 1216 |
+
)
|
| 1217 |
+
|
| 1218 |
+
except queue.Empty:
|
| 1219 |
+
# No new results
|
| 1220 |
+
return gr.skip(), gr.skip(), gr.skip(), gr.skip(), gr.skip()
|
| 1221 |
+
|
| 1222 |
+
except Exception as e:
|
| 1223 |
+
print(f"❌ Lỗi check_vad_results: {e}")
|
| 1224 |
+
return gr.skip(), gr.skip(), gr.skip(), gr.skip(), gr.skip()
|
| 1225 |
|
| 1226 |
def clear_chat():
|
| 1227 |
"""Xóa hội thoại"""
|
| 1228 |
streaming_service.clear_conversation()
|
| 1229 |
state = streaming_service.get_conversation_state()
|
| 1230 |
+
state_text = f"✅ Đã xóa hội thoại\nHistory: {state['history_length']} messages"
|
| 1231 |
+
status_msg = "Sẵn sàng"
|
| 1232 |
+
return "", "", None, state_text, status_msg
|
| 1233 |
|
| 1234 |
def refresh_latency():
|
| 1235 |
"""Làm mới latency metrics"""
|
| 1236 |
+
try:
|
| 1237 |
+
stats = streaming_service.get_latency_stats()
|
| 1238 |
+
return stats
|
| 1239 |
+
except Exception as e:
|
| 1240 |
+
print(f"❌ Lỗi refresh_latency: {e}")
|
| 1241 |
+
return {}
|
| 1242 |
|
| 1243 |
+
def update_status_info():
|
| 1244 |
"""Cập nhật thông tin trạng thái"""
|
| 1245 |
+
try:
|
| 1246 |
+
state = streaming_service.get_conversation_state()
|
| 1247 |
+
|
| 1248 |
+
formatted_state = f"🎯 VAD: {'✅ Đang chạy' if state['is_listening'] else '❌ Dừng'}\n"
|
| 1249 |
+
formatted_state += f"📊 Queue: {state['queue_size']}\n"
|
| 1250 |
+
formatted_state += f"📝 History: {state['history_length']} messages\n"
|
| 1251 |
+
formatted_state += f"🧵 Threads: {state['worker_threads']}\n"
|
| 1252 |
+
formatted_state += f"⏰ Last: {state['last_update']}"
|
| 1253 |
+
|
| 1254 |
+
# Get latency stats
|
| 1255 |
+
latency_info = streaming_service.get_latency_stats()
|
| 1256 |
+
|
| 1257 |
+
# Current status
|
| 1258 |
+
if state['is_listening']:
|
| 1259 |
+
current_status = "🎤 Đang lắng nghe... nói đi!"
|
| 1260 |
+
else:
|
| 1261 |
+
current_status = "🛑 Đã dừng"
|
| 1262 |
+
|
| 1263 |
+
return formatted_state, latency_info, current_status
|
| 1264 |
+
|
| 1265 |
+
except Exception as e:
|
| 1266 |
+
print(f"❌ Lỗi update_status_info: {e}")
|
| 1267 |
+
return f"Lỗi: {e}", {}, "Lỗi!"
|
| 1268 |
|
| 1269 |
# Event handlers
|
| 1270 |
+
start_btn.click(
|
| 1271 |
+
start_vad,
|
| 1272 |
+
outputs=[vad_status, status_display, transcription_box]
|
| 1273 |
+
)
|
| 1274 |
|
| 1275 |
+
stop_btn.click(
|
| 1276 |
+
stop_vad,
|
| 1277 |
+
outputs=[vad_status, status_display, transcription_box]
|
| 1278 |
+
)
|
| 1279 |
+
|
| 1280 |
+
# Microphone streaming
|
| 1281 |
microphone.stream(
|
| 1282 |
process_microphone,
|
| 1283 |
inputs=[microphone],
|
| 1284 |
+
outputs=[transcription_box, response_box, audio_output, status_display, vad_status]
|
| 1285 |
)
|
| 1286 |
|
| 1287 |
clear_btn.click(
|
| 1288 |
clear_chat,
|
| 1289 |
+
outputs=[transcription_box, response_box, audio_output, status_display, vad_status]
|
| 1290 |
)
|
| 1291 |
|
| 1292 |
refresh_latency_btn.click(
|
|
|
|
| 1294 |
outputs=[latency_display]
|
| 1295 |
)
|
| 1296 |
|
| 1297 |
+
# IMPORTANT: Timer để kiểm tra kết quả VAD - sử dụng interval ngắn hơn
|
| 1298 |
+
timer_component = gr.Timer(0.5) # 500ms interval
|
| 1299 |
+
|
| 1300 |
+
# Connect timer to check function
|
| 1301 |
+
timer_component.tick(
|
| 1302 |
fn=check_vad_results,
|
| 1303 |
+
outputs=[transcription_box, response_box, audio_output, status_display, vad_status]
|
| 1304 |
)
|
| 1305 |
|
| 1306 |
+
# Timer để cập nhật system info (mỗi 2 giây)
|
| 1307 |
+
info_timer = gr.Timer(2.0)
|
| 1308 |
+
|
| 1309 |
+
info_timer.tick(
|
| 1310 |
+
fn=update_status_info,
|
| 1311 |
+
outputs=[status_display, latency_display, vad_status]
|
| 1312 |
)
|
| 1313 |
|
| 1314 |
return streaming_tab
|