seawolf2357 commited on
Commit
cedd312
·
verified ·
1 Parent(s): f324e82

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +296 -155
app.py CHANGED
@@ -88,22 +88,46 @@ HTML_CONTENT = """<!DOCTYPE html>
88
  overflow: hidden;
89
  }
90
  .container {
91
- max-width: 900px;
92
  margin: 0 auto;
93
  padding: 20px;
94
  flex-grow: 1;
95
  display: flex;
96
  flex-direction: column;
97
  width: 100%;
98
- height: calc(100vh - 40px);
99
  box-sizing: border-box;
 
100
  }
101
  .header {
102
  text-align: center;
103
- padding: 20px 0;
104
  border-bottom: 1px solid var(--border-color);
105
  margin-bottom: 20px;
106
  flex-shrink: 0;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
107
  }
108
  .logo {
109
  display: flex;
@@ -125,18 +149,19 @@ HTML_CONTENT = """<!DOCTYPE html>
125
  background-color: var(--card-bg);
126
  border-radius: 12px;
127
  padding: 20px;
128
- margin-bottom: 20px;
129
  border: 1px solid var(--border-color);
 
 
130
  }
131
  .settings-grid {
132
- display: grid;
133
- grid-template-columns: 1fr 1fr;
134
  gap: 15px;
135
  margin-bottom: 15px;
136
  }
137
  .interpretation-section {
138
  display: flex;
139
- align-items: center;
140
  gap: 15px;
141
  padding: 15px;
142
  background-color: var(--dark-bg);
@@ -151,12 +176,13 @@ HTML_CONTENT = """<!DOCTYPE html>
151
  .setting-item {
152
  display: flex;
153
  align-items: center;
 
154
  gap: 10px;
155
  }
156
  .setting-label {
157
  font-size: 14px;
158
  color: #aaa;
159
- min-width: 80px;
160
  }
161
  /* Toggle switch */
162
  .toggle-switch {
@@ -193,7 +219,8 @@ HTML_CONTENT = """<!DOCTYPE html>
193
  border-radius: 6px;
194
  font-size: 14px;
195
  cursor: pointer;
196
- min-width: 150px;
 
197
  }
198
  select:focus {
199
  outline: none;
@@ -233,14 +260,16 @@ HTML_CONTENT = """<!DOCTYPE html>
233
  border: 1px solid var(--border-color);
234
  overflow: hidden;
235
  min-height: 0;
 
236
  }
237
  .chat-messages {
238
  flex-grow: 1;
239
  overflow-y: auto;
240
- padding: 10px;
241
  scrollbar-width: thin;
242
  scrollbar-color: var(--primary-color) var(--card-bg);
243
  min-height: 0;
 
244
  }
245
  .chat-messages::-webkit-scrollbar {
246
  width: 6px;
@@ -250,14 +279,15 @@ HTML_CONTENT = """<!DOCTYPE html>
250
  border-radius: 6px;
251
  }
252
  .message {
253
- margin-bottom: 20px;
254
- padding: 14px;
255
  border-radius: 8px;
256
- font-size: 16px;
257
- line-height: 1.6;
258
  position: relative;
259
- max-width: 80%;
260
  animation: fade-in 0.3s ease-out;
 
261
  }
262
  @keyframes fade-in {
263
  from {
@@ -287,11 +317,30 @@ HTML_CONTENT = """<!DOCTYPE html>
287
  }
288
  .controls {
289
  text-align: center;
290
- margin-top: 20px;
291
  display: flex;
292
  justify-content: center;
293
  gap: 10px;
294
  flex-shrink: 0;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
295
  }
296
  button {
297
  background: linear-gradient(135deg, var(--primary-color), var(--secondary-color));
@@ -323,6 +372,7 @@ HTML_CONTENT = """<!DOCTYPE html>
323
  background: linear-gradient(135deg, #2ecc71, #27ae60);
324
  padding: 10px 20px;
325
  font-size: 14px;
 
326
  }
327
  #send-button:hover {
328
  background: linear-gradient(135deg, #27ae60, #229954);
@@ -477,112 +527,123 @@ HTML_CONTENT = """<!DOCTYPE html>
477
  </div>
478
  </div>
479
 
480
- <div class="settings-section">
481
- <div class="settings-grid">
482
- <div class="setting-item">
483
- <span class="setting-label">웹 검색</span>
484
- <div id="search-toggle" class="toggle-switch">
485
- <div class="toggle-slider"></div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
486
  </div>
487
  </div>
488
- <div class="setting-item">
489
- <span class="setting-label">자동 번역</span>
490
- <select id="language-select">
491
- <option value="">비활성화</option>
492
- <option value="ko">한국어 (Korean)</option>
493
- <option value="en">English</option>
494
- <option value="es">Español (Spanish)</option>
495
- <option value="fr">Français (French)</option>
496
- <option value="de">Deutsch (German)</option>
497
- <option value="it">Italiano (Italian)</option>
498
- <option value="pt">Português (Portuguese)</option>
499
- <option value="ru">Русский (Russian)</option>
500
- <option value="ja">日本語 (Japanese)</option>
501
- <option value="zh">中文 (Chinese)</option>
502
- <option value="ar">العربية (Arabic)</option>
503
- <option value="hi">हिन्दी (Hindi)</option>
504
- <option value="nl">Nederlands (Dutch)</option>
505
- <option value="pl">Polski (Polish)</option>
506
- <option value="tr">Türkçe (Turkish)</option>
507
- <option value="vi">Tiếng Việt (Vietnamese)</option>
508
- <option value="th">ไทย (Thai)</option>
509
- <option value="id">Bahasa Indonesia</option>
510
- <option value="sv">Svenska (Swedish)</option>
511
- <option value="da">Dansk (Danish)</option>
512
- <option value="no">Norsk (Norwegian)</option>
513
- <option value="fi">Suomi (Finnish)</option>
514
- <option value="he">עברית (Hebrew)</option>
515
- <option value="uk">Українська (Ukrainian)</option>
516
- <option value="cs">Čeština (Czech)</option>
517
- <option value="el">Ελληνικά (Greek)</option>
518
- <option value="ro">Română (Romanian)</option>
519
- <option value="hu">Magyar (Hungarian)</option>
520
- <option value="ms">Bahasa Melayu (Malay)</option>
521
- </select>
522
  </div>
523
  </div>
524
- <div class="interpretation-section">
525
- <div class="setting-item">
526
- <span class="setting-label">자동 통역</span>
527
- <div id="interpretation-toggle" class="toggle-switch">
528
- <div class="toggle-slider"></div>
 
 
 
 
 
529
  </div>
530
  </div>
531
- <div class="setting-item" id="interpretation-language-container" style="display: none;">
532
- <span class="setting-label">통역 언어</span>
533
- <select id="interpretation-language-select">
534
- <option value="">언어 선택</option>
535
- <option value="ko">한국어 (Korean)</option>
536
- <option value="en">English</option>
537
- <option value="es">Español (Spanish)</option>
538
- <option value="fr">Français (French)</option>
539
- <option value="de">Deutsch (German)</option>
540
- <option value="it">Italiano (Italian)</option>
541
- <option value="pt">Português (Portuguese)</option>
542
- <option value="ru">Русский (Russian)</option>
543
- <option value="ja">日本語 (Japanese)</option>
544
- <option value="zh">中文 (Chinese)</option>
545
- <option value="ar">العربية (Arabic)</option>
546
- <option value="hi">हिन्दी (Hindi)</option>
547
- <option value="nl">Nederlands (Dutch)</option>
548
- <option value="pl">Polski (Polish)</option>
549
- <option value="tr">Türkçe (Turkish)</option>
550
- <option value="vi">Tiếng Việt (Vietnamese)</option>
551
- <option value="th">ไทย (Thai)</option>
552
- <option value="id">Bahasa Indonesia</option>
553
- <option value="sv">Svenska (Swedish)</option>
554
- <option value="da">Dansk (Danish)</option>
555
- <option value="no">Norsk (Norwegian)</option>
556
- <option value="fi">Suomi (Finnish)</option>
557
- <option value="he">עברית (Hebrew)</option>
558
- <option value="uk">Українська (Ukrainian)</option>
559
- <option value="cs">Čeština (Czech)</option>
560
- <option value="el">Ελληνικά (Greek)</option>
561
- <option value="ro">Română (Romanian)</option>
562
- <option value="hu">Magyar (Hungarian)</option>
563
- <option value="ms">Bahasa Melayu (Malay)</option>
564
- </select>
565
- </div>
566
- </div>
567
- <div class="interpretation-info" id="interpretation-info" style="display: none;">
568
- 통역 모드: 입력한 음성이 선택한 언어로 자동 통역됩니다.
569
- </div>
570
- <div class="text-input-section">
571
- <label for="system-prompt" class="setting-label">시스템 프롬프트:</label>
572
- <textarea id="system-prompt" placeholder="AI 어시스턴트의 성격, 역할, 행동 방식을 정의하세요...">You are a helpful assistant. Respond in a friendly and professional manner.</textarea>
573
- </div>
574
- </div>
575
-
576
- <div class="chat-container">
577
- <div class="chat-messages" id="chat-messages"></div>
578
- <div class="text-input-section" style="margin-top: 10px;">
579
- <input type="text" id="text-input" placeholder="텍스트 메시지를 입력하세요..." />
580
  </div>
581
  </div>
582
- <div class="controls">
583
- <button id="start-button">대화 시작</button>
584
- <button id="send-button" style="display: none;">전송</button>
585
- </div>
586
  </div>
587
  <audio id="audio-output"></audio>
588
 
@@ -629,24 +690,35 @@ HTML_CONTENT = """<!DOCTYPE html>
629
 
630
  // Interpretation mode toggle
631
  interpretationToggle.addEventListener('click', () => {
632
- interpretationMode = !interpretationMode;
633
- interpretationToggle.classList.toggle('active', interpretationMode);
634
- interpretationLanguageContainer.style.display = interpretationMode ? 'flex' : 'none';
635
- interpretationInfo.style.display = interpretationMode ? 'block' : 'none';
636
-
637
- // Disable translation mode when interpretation is enabled
638
- if (interpretationMode) {
639
- languageSelect.value = '';
640
- selectedLanguage = '';
641
- languageSelect.disabled = true;
642
- searchToggle.classList.remove('active');
643
- webSearchEnabled = false;
644
- searchToggle.style.opacity = '0.5';
645
- searchToggle.style.pointerEvents = 'none';
646
  } else {
 
 
 
 
 
 
 
 
 
647
  languageSelect.disabled = false;
648
  searchToggle.style.opacity = '1';
649
  searchToggle.style.pointerEvents = 'auto';
 
 
 
 
 
650
  }
651
 
652
  console.log('Interpretation mode:', interpretationMode);
@@ -656,6 +728,26 @@ HTML_CONTENT = """<!DOCTYPE html>
656
  interpretationLanguageSelect.addEventListener('change', () => {
657
  interpretationLanguage = interpretationLanguageSelect.value;
658
  console.log('Interpretation language:', interpretationLanguage);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
659
  });
660
 
661
  // System prompt update
@@ -736,14 +828,18 @@ HTML_CONTENT = """<!DOCTYPE html>
736
  statusDot.className = 'status-dot ' + state;
737
  if (state === 'connected') {
738
  statusText.textContent = '연결됨';
739
- sendButton.style.display = 'block';
 
 
740
  isVoiceActive = true;
741
  } else if (state === 'connecting') {
742
  statusText.textContent = '연결 중...';
743
  sendButton.style.display = 'none';
744
  } else {
745
  statusText.textContent = '연결 대기 중';
746
- sendButton.style.display = 'block'; // Show send button even when disconnected for text chat
 
 
747
  isVoiceActive = false;
748
  }
749
  }
@@ -979,6 +1075,11 @@ HTML_CONTENT = """<!DOCTYPE html>
979
  stop();
980
  }
981
  });
 
 
 
 
 
982
  </script>
983
  </body>
984
 
@@ -1195,12 +1296,32 @@ class OpenAIHandler(AsyncStreamHandler):
1195
  return ""
1196
 
1197
  target_language_name = SUPPORTED_LANGUAGES.get(self.interpretation_language, self.interpretation_language)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1198
  return (
1199
- f"\n\nIMPORTANT: You are now in INTERPRETATION MODE. "
1200
- f"You must ONLY translate what the user says into {target_language_name} ({self.interpretation_language}). "
1201
- f"DO NOT generate any responses, opinions, or additional content. "
1202
- f"Your ONLY task is to translate the user's speech accurately into {target_language_name}. "
1203
- f"You are a professional interpreter - simply translate what is said, nothing more."
 
 
 
 
 
 
1204
  )
1205
 
1206
  def get_translation_instructions(self):
@@ -1234,9 +1355,11 @@ class OpenAIHandler(AsyncStreamHandler):
1234
  print(f"start_up: Updated settings from storage - webrtc_id={self.webrtc_id}, "
1235
  f"web_search_enabled={self.web_search_enabled}, target_language={self.target_language}, "
1236
  f"interpretation_mode={self.interpretation_mode}")
 
1237
 
1238
  print(f"Starting up handler with web_search_enabled={self.web_search_enabled}, "
1239
- f"target_language={self.target_language}, interpretation_mode={self.interpretation_mode}")
 
1240
  self.client = openai.AsyncOpenAI()
1241
 
1242
  # Define the web search function
@@ -1246,10 +1369,17 @@ class OpenAIHandler(AsyncStreamHandler):
1246
  # Check if in interpretation mode
1247
  if self.interpretation_mode:
1248
  # In interpretation mode, override all instructions
 
 
 
 
 
 
1249
  interpretation_instructions = self.get_interpretation_instructions()
1250
- instructions = interpretation_instructions
1251
  # No tools in interpretation mode
1252
  tools = []
 
1253
  else:
1254
  # Normal mode - add translation instructions if language is selected
1255
  translation_instructions = self.get_translation_instructions()
@@ -1296,7 +1426,12 @@ class OpenAIHandler(AsyncStreamHandler):
1296
  ) as conn:
1297
  # Update session with tools
1298
  session_update = {
1299
- "turn_detection": {"type": "server_vad"},
 
 
 
 
 
1300
  "instructions": instructions,
1301
  "tools": tools,
1302
  "tool_choice": "auto" if tools else "none"
@@ -1305,21 +1440,27 @@ class OpenAIHandler(AsyncStreamHandler):
1305
  # Add voice setting based on interpretation or translation language
1306
  voice_language = self.interpretation_language if self.interpretation_mode else self.target_language
1307
  if voice_language:
1308
- # Map languages to appropriate voices
1309
- voice_map = {
1310
- "en": "alloy",
1311
- "es": "nova",
1312
- "fr": "nova",
1313
- "de": "nova",
1314
- "ja": "nova",
1315
- "zh": "nova",
1316
- # Default to alloy for other languages
1317
- }
1318
- session_update["voice"] = voice_map.get(voice_language, "alloy")
 
 
 
 
 
1319
 
1320
  await conn.session.update(session=session_update)
1321
  self.connection = conn
1322
- print(f"Connected with tools: {len(tools)} functions, voice: {session_update.get('voice', 'default')}")
 
1323
 
1324
  async for event in self.connection:
1325
  # Debug logging for function calls
@@ -1346,18 +1487,18 @@ class OpenAIHandler(AsyncStreamHandler):
1346
  ),
1347
  )
1348
 
1349
- # Handle function calls
1350
- elif event.type == "response.function_call_arguments.start":
1351
  print(f"Function call started")
1352
  self.function_call_in_progress = True
1353
  self.current_function_args = ""
1354
  self.current_call_id = getattr(event, 'call_id', None)
1355
 
1356
- elif event.type == "response.function_call_arguments.delta":
1357
  if self.function_call_in_progress:
1358
  self.current_function_args += event.delta
1359
 
1360
- elif event.type == "response.function_call_arguments.done":
1361
  if self.function_call_in_progress:
1362
  print(f"Function call done, args: {self.current_function_args}")
1363
  try:
@@ -1421,7 +1562,7 @@ class OpenAIHandler(AsyncStreamHandler):
1421
 
1422
 
1423
  # Create initial handler instance
1424
- handler = OpenAIHandler(web_search_enabled=False)
1425
 
1426
  # Create components
1427
  chatbot = gr.Chatbot(type="messages")
 
88
  overflow: hidden;
89
  }
90
  .container {
91
+ max-width: 1400px;
92
  margin: 0 auto;
93
  padding: 20px;
94
  flex-grow: 1;
95
  display: flex;
96
  flex-direction: column;
97
  width: 100%;
98
+ height: 100vh;
99
  box-sizing: border-box;
100
+ overflow: hidden;
101
  }
102
  .header {
103
  text-align: center;
104
+ padding: 15px 0;
105
  border-bottom: 1px solid var(--border-color);
106
  margin-bottom: 20px;
107
  flex-shrink: 0;
108
+ background-color: var(--card-bg);
109
+ }
110
+ .main-content {
111
+ display: flex;
112
+ gap: 20px;
113
+ flex-grow: 1;
114
+ min-height: 0;
115
+ overflow: hidden;
116
+ }
117
+ .sidebar {
118
+ width: 350px;
119
+ flex-shrink: 0;
120
+ display: flex;
121
+ flex-direction: column;
122
+ gap: 20px;
123
+ overflow-y: auto;
124
+ max-height: calc(100vh - 120px);
125
+ }
126
+ .chat-section {
127
+ flex-grow: 1;
128
+ display: flex;
129
+ flex-direction: column;
130
+ min-width: 0;
131
  }
132
  .logo {
133
  display: flex;
 
149
  background-color: var(--card-bg);
150
  border-radius: 12px;
151
  padding: 20px;
 
152
  border: 1px solid var(--border-color);
153
+ overflow-y: auto;
154
+ flex-grow: 1;
155
  }
156
  .settings-grid {
157
+ display: flex;
158
+ flex-direction: column;
159
  gap: 15px;
160
  margin-bottom: 15px;
161
  }
162
  .interpretation-section {
163
  display: flex;
164
+ flex-direction: column;
165
  gap: 15px;
166
  padding: 15px;
167
  background-color: var(--dark-bg);
 
176
  .setting-item {
177
  display: flex;
178
  align-items: center;
179
+ justify-content: space-between;
180
  gap: 10px;
181
  }
182
  .setting-label {
183
  font-size: 14px;
184
  color: #aaa;
185
+ min-width: 60px;
186
  }
187
  /* Toggle switch */
188
  .toggle-switch {
 
219
  border-radius: 6px;
220
  font-size: 14px;
221
  cursor: pointer;
222
+ min-width: 120px;
223
+ max-width: 200px;
224
  }
225
  select:focus {
226
  outline: none;
 
260
  border: 1px solid var(--border-color);
261
  overflow: hidden;
262
  min-height: 0;
263
+ height: 100%;
264
  }
265
  .chat-messages {
266
  flex-grow: 1;
267
  overflow-y: auto;
268
+ padding: 15px;
269
  scrollbar-width: thin;
270
  scrollbar-color: var(--primary-color) var(--card-bg);
271
  min-height: 0;
272
+ max-height: calc(100vh - 250px);
273
  }
274
  .chat-messages::-webkit-scrollbar {
275
  width: 6px;
 
279
  border-radius: 6px;
280
  }
281
  .message {
282
+ margin-bottom: 15px;
283
+ padding: 12px 16px;
284
  border-radius: 8px;
285
+ font-size: 15px;
286
+ line-height: 1.5;
287
  position: relative;
288
+ max-width: 85%;
289
  animation: fade-in 0.3s ease-out;
290
+ word-wrap: break-word;
291
  }
292
  @keyframes fade-in {
293
  from {
 
317
  }
318
  .controls {
319
  text-align: center;
320
+ margin-top: auto;
321
  display: flex;
322
  justify-content: center;
323
  gap: 10px;
324
  flex-shrink: 0;
325
+ padding-top: 20px;
326
+ }
327
+ /* Responsive design */
328
+ @media (max-width: 1024px) {
329
+ .sidebar {
330
+ width: 300px;
331
+ }
332
+ }
333
+ @media (max-width: 768px) {
334
+ .main-content {
335
+ flex-direction: column;
336
+ }
337
+ .sidebar {
338
+ width: 100%;
339
+ margin-bottom: 20px;
340
+ }
341
+ .chat-section {
342
+ height: 400px;
343
+ }
344
  }
345
  button {
346
  background: linear-gradient(135deg, var(--primary-color), var(--secondary-color));
 
372
  background: linear-gradient(135deg, #2ecc71, #27ae60);
373
  padding: 10px 20px;
374
  font-size: 14px;
375
+ flex-shrink: 0;
376
  }
377
  #send-button:hover {
378
  background: linear-gradient(135deg, #27ae60, #229954);
 
527
  </div>
528
  </div>
529
 
530
+ <div class="main-content">
531
+ <div class="sidebar">
532
+ <div class="settings-section">
533
+ <h3 style="margin: 0 0 15px 0; color: var(--primary-color);">설정</h3>
534
+ <div class="settings-grid">
535
+ <div class="setting-item">
536
+ <span class="setting-label">웹 검색</span>
537
+ <div id="search-toggle" class="toggle-switch">
538
+ <div class="toggle-slider"></div>
539
+ </div>
540
+ </div>
541
+ <div class="setting-item">
542
+ <span class="setting-label">자동 번역</span>
543
+ <select id="language-select">
544
+ <option value="">비활성화</option>
545
+ <option value="ko">한국어 (Korean)</option>
546
+ <option value="en">English</option>
547
+ <option value="es">Español (Spanish)</option>
548
+ <option value="fr">Français (French)</option>
549
+ <option value="de">Deutsch (German)</option>
550
+ <option value="it">Italiano (Italian)</option>
551
+ <option value="pt">Português (Portuguese)</option>
552
+ <option value="ru">Русский (Russian)</option>
553
+ <option value="ja">日本語 (Japanese)</option>
554
+ <option value="zh">中文 (Chinese)</option>
555
+ <option value="ar">العربية (Arabic)</option>
556
+ <option value="hi">हिन्दी (Hindi)</option>
557
+ <option value="nl">Nederlands (Dutch)</option>
558
+ <option value="pl">Polski (Polish)</option>
559
+ <option value="tr">Türkçe (Turkish)</option>
560
+ <option value="vi">Tiếng Việt (Vietnamese)</option>
561
+ <option value="th">ไทย (Thai)</option>
562
+ <option value="id">Bahasa Indonesia</option>
563
+ <option value="sv">Svenska (Swedish)</option>
564
+ <option value="da">Dansk (Danish)</option>
565
+ <option value="no">Norsk (Norwegian)</option>
566
+ <option value="fi">Suomi (Finnish)</option>
567
+ <option value="he">עברית (Hebrew)</option>
568
+ <option value="uk">Українська (Ukrainian)</option>
569
+ <option value="cs">Čeština (Czech)</option>
570
+ <option value="el">Ελληνικά (Greek)</option>
571
+ <option value="ro">Română (Romanian)</option>
572
+ <option value="hu">Magyar (Hungarian)</option>
573
+ <option value="ms">Bahasa Melayu (Malay)</option>
574
+ </select>
575
+ </div>
576
+ </div>
577
+ <div class="interpretation-section">
578
+ <div class="setting-item">
579
+ <span class="setting-label">자동 통역</span>
580
+ <div id="interpretation-toggle" class="toggle-switch">
581
+ <div class="toggle-slider"></div>
582
+ </div>
583
+ </div>
584
+ <div class="setting-item" id="interpretation-language-container" style="display: none;">
585
+ <span class="setting-label">통역 언어</span>
586
+ <select id="interpretation-language-select">
587
+ <option value="">언어 선택</option>
588
+ <option value="ko">한국어 (Korean)</option>
589
+ <option value="en">English</option>
590
+ <option value="es">Español (Spanish)</option>
591
+ <option value="fr">Français (French)</option>
592
+ <option value="de">Deutsch (German)</option>
593
+ <option value="it">Italiano (Italian)</option>
594
+ <option value="pt">Português (Portuguese)</option>
595
+ <option value="ru">Русский (Russian)</option>
596
+ <option value="ja">日本語 (Japanese)</option>
597
+ <option value="zh">中文 (Chinese)</option>
598
+ <option value="ar">العربية (Arabic)</option>
599
+ <option value="hi">हिन्दी (Hindi)</option>
600
+ <option value="nl">Nederlands (Dutch)</option>
601
+ <option value="pl">Polski (Polish)</option>
602
+ <option value="tr">Türkçe (Turkish)</option>
603
+ <option value="vi">Tiếng Việt (Vietnamese)</option>
604
+ <option value="th">ไทย (Thai)</option>
605
+ <option value="id">Bahasa Indonesia</option>
606
+ <option value="sv">Svenska (Swedish)</option>
607
+ <option value="da">Dansk (Danish)</option>
608
+ <option value="no">Norsk (Norwegian)</option>
609
+ <option value="fi">Suomi (Finnish)</option>
610
+ <option value="he">עברית (Hebrew)</option>
611
+ <option value="uk">Українська (Ukrainian)</option>
612
+ <option value="cs">Čeština (Czech)</option>
613
+ <option value="el">Ελληνικά (Greek)</option>
614
+ <option value="ro">Română (Romanian)</option>
615
+ <option value="hu">Magyar (Hungarian)</option>
616
+ <option value="ms">Bahasa Melayu (Malay)</option>
617
+ </select>
618
+ </div>
619
+ </div>
620
+ <div class="interpretation-info" id="interpretation-info" style="display: none;">
621
+ 통역 모드: 입력한 음성이 선택한 언어로 자동 통역됩니다.
622
+ </div>
623
+ <div class="text-input-section">
624
+ <label for="system-prompt" class="setting-label">시스템 프롬프트:</label>
625
+ <textarea id="system-prompt" placeholder="AI 어시스턴트의 성격, 역할, 행동 방식을 정의하세요...">You are a helpful assistant. Respond in a friendly and professional manner.</textarea>
626
  </div>
627
  </div>
628
+
629
+ <div class="controls">
630
+ <button id="start-button">대화 시작</button>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
631
  </div>
632
  </div>
633
+
634
+ <div class="chat-section">
635
+ <div class="chat-container">
636
+ <h3 style="margin: 0 0 15px 0; color: var(--primary-color);">대화</h3>
637
+ <div class="chat-messages" id="chat-messages"></div>
638
+ <div class="text-input-section" style="margin-top: 10px;">
639
+ <div style="display: flex; gap: 10px;">
640
+ <input type="text" id="text-input" placeholder="텍스트 메시지를 입력하세요..." style="flex-grow: 1;" />
641
+ <button id="send-button" style="display: none;">전송</button>
642
+ </div>
643
  </div>
644
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
645
  </div>
646
  </div>
 
 
 
 
647
  </div>
648
  <audio id="audio-output"></audio>
649
 
 
690
 
691
  // Interpretation mode toggle
692
  interpretationToggle.addEventListener('click', () => {
693
+ if (!interpretationMode) {
694
+ // Turning ON interpretation mode
695
+ interpretationLanguageContainer.style.display = 'flex';
696
+ interpretationInfo.style.display = 'block';
697
+
698
+ // Show language selector first
699
+ showError('통역 언어를 선택해주세요.');
700
+ interpretationToggle.classList.remove('active');
701
+
702
+ // Don't actually enable interpretation mode until language is selected
703
+ return;
 
 
 
704
  } else {
705
+ // Turning OFF interpretation mode
706
+ interpretationMode = false;
707
+ interpretationToggle.classList.remove('active');
708
+ interpretationLanguageContainer.style.display = 'none';
709
+ interpretationInfo.style.display = 'none';
710
+ interpretationLanguage = '';
711
+ interpretationLanguageSelect.value = '';
712
+
713
+ // Re-enable other features
714
  languageSelect.disabled = false;
715
  searchToggle.style.opacity = '1';
716
  searchToggle.style.pointerEvents = 'auto';
717
+ textInput.disabled = false;
718
+ textInput.placeholder = '텍스트 메시지를 입력하세요...';
719
+ sendButton.style.display = 'block';
720
+
721
+ console.log('Interpretation mode disabled');
722
  }
723
 
724
  console.log('Interpretation mode:', interpretationMode);
 
728
  interpretationLanguageSelect.addEventListener('change', () => {
729
  interpretationLanguage = interpretationLanguageSelect.value;
730
  console.log('Interpretation language:', interpretationLanguage);
731
+
732
+ if (interpretationLanguage && !interpretationMode) {
733
+ // Now actually enable interpretation mode
734
+ interpretationMode = true;
735
+ interpretationToggle.classList.add('active');
736
+
737
+ // Disable other features
738
+ languageSelect.value = '';
739
+ selectedLanguage = '';
740
+ languageSelect.disabled = true;
741
+ searchToggle.classList.remove('active');
742
+ webSearchEnabled = false;
743
+ searchToggle.style.opacity = '0.5';
744
+ searchToggle.style.pointerEvents = 'none';
745
+ textInput.disabled = true;
746
+ textInput.placeholder = '통역 모드에서는 텍스트 입력이 지원되지 않습니다';
747
+ sendButton.style.display = 'none';
748
+
749
+ console.log('Interpretation mode enabled with language:', interpretationLanguage);
750
+ }
751
  });
752
 
753
  // System prompt update
 
828
  statusDot.className = 'status-dot ' + state;
829
  if (state === 'connected') {
830
  statusText.textContent = '연결됨';
831
+ if (!interpretationMode) {
832
+ sendButton.style.display = 'block';
833
+ }
834
  isVoiceActive = true;
835
  } else if (state === 'connecting') {
836
  statusText.textContent = '연결 중...';
837
  sendButton.style.display = 'none';
838
  } else {
839
  statusText.textContent = '연결 대기 중';
840
+ if (!interpretationMode) {
841
+ sendButton.style.display = 'block'; // Show send button even when disconnected for text chat
842
+ }
843
  isVoiceActive = false;
844
  }
845
  }
 
1075
  stop();
1076
  }
1077
  });
1078
+
1079
+ // Initialize send button visibility on page load
1080
+ window.addEventListener('DOMContentLoaded', () => {
1081
+ sendButton.style.display = 'block';
1082
+ });
1083
  </script>
1084
  </body>
1085
 
 
1296
  return ""
1297
 
1298
  target_language_name = SUPPORTED_LANGUAGES.get(self.interpretation_language, self.interpretation_language)
1299
+ target_code = self.interpretation_language
1300
+
1301
+ # Language-specific examples
1302
+ examples = {
1303
+ "en": "Hello, the weather is nice today",
1304
+ "es": "Hola, el clima está agradable hoy",
1305
+ "fr": "Bonjour, il fait beau aujourd'hui",
1306
+ "de": "Hallo, das Wetter ist heute schön",
1307
+ "ja": "こんにちは、今日はいい天気ですね",
1308
+ "zh": "你好,今天天气很好"
1309
+ }
1310
+
1311
+ example_translation = examples.get(target_code, "Hello, the weather is nice today")
1312
+
1313
  return (
1314
+ f"INTERPRETATION MODE - CRITICAL RULES:\n\n"
1315
+ f"1. You are ONLY a translator to {target_language_name} (language code: {target_code}).\n"
1316
+ f"2. NEVER respond in any other language.\n"
1317
+ f"3. NEVER generate conversation or additional content.\n"
1318
+ f"4. ONLY translate what the user says.\n"
1319
+ f"5. STOP immediately after translating.\n\n"
1320
+ f"Example:\n"
1321
+ f"If user says: '안녕하세요, 오늘 날씨가 좋네요'\n"
1322
+ f"You MUST respond ONLY: '{example_translation}'\n\n"
1323
+ f"DO NOT say anything else. DO NOT continue talking.\n"
1324
+ f"Your output language MUST be {target_language_name} ONLY."
1325
  )
1326
 
1327
  def get_translation_instructions(self):
 
1355
  print(f"start_up: Updated settings from storage - webrtc_id={self.webrtc_id}, "
1356
  f"web_search_enabled={self.web_search_enabled}, target_language={self.target_language}, "
1357
  f"interpretation_mode={self.interpretation_mode}")
1358
+ print(f"Handler interpretation settings: mode={self.interpretation_mode}, language={self.interpretation_language}")
1359
 
1360
  print(f"Starting up handler with web_search_enabled={self.web_search_enabled}, "
1361
+ f"target_language={self.target_language}, interpretation_mode={self.interpretation_mode}, "
1362
+ f"interpretation_language={self.interpretation_language}")
1363
  self.client = openai.AsyncOpenAI()
1364
 
1365
  # Define the web search function
 
1369
  # Check if in interpretation mode
1370
  if self.interpretation_mode:
1371
  # In interpretation mode, override all instructions
1372
+ base_instructions = (
1373
+ f"You are a professional interpreter. Your ONLY task is to translate what the user says "
1374
+ f"into {SUPPORTED_LANGUAGES.get(self.interpretation_language, self.interpretation_language)}. "
1375
+ f"Do not add any commentary, do not continue the conversation, do not generate new content. "
1376
+ f"Simply translate what was said and stop."
1377
+ )
1378
  interpretation_instructions = self.get_interpretation_instructions()
1379
+ instructions = base_instructions + "\n\n" + interpretation_instructions
1380
  # No tools in interpretation mode
1381
  tools = []
1382
+ print(f"Interpretation mode active - target language: {self.interpretation_language}")
1383
  else:
1384
  # Normal mode - add translation instructions if language is selected
1385
  translation_instructions = self.get_translation_instructions()
 
1426
  ) as conn:
1427
  # Update session with tools
1428
  session_update = {
1429
+ "turn_detection": {
1430
+ "type": "server_vad",
1431
+ "threshold": 0.5,
1432
+ "prefix_padding_ms": 300,
1433
+ "silence_duration_ms": 500 if self.interpretation_mode else 700
1434
+ },
1435
  "instructions": instructions,
1436
  "tools": tools,
1437
  "tool_choice": "auto" if tools else "none"
 
1440
  # Add voice setting based on interpretation or translation language
1441
  voice_language = self.interpretation_language if self.interpretation_mode else self.target_language
1442
  if voice_language:
1443
+ # Use only alloy voice to avoid language confusion
1444
+ # The model will handle the language based on instructions
1445
+ session_update["voice"] = "alloy"
1446
+
1447
+ # For interpretation mode, explicitly set the output language
1448
+ if self.interpretation_mode:
1449
+ session_update["output_audio_format"] = "pcm16"
1450
+
1451
+ print(f"Voice set to: alloy for language: {voice_language}")
1452
+
1453
+ # For interpretation mode, ensure proper language settings
1454
+ if self.interpretation_mode and self.interpretation_language:
1455
+ session_update["modalities"] = ["text", "audio"]
1456
+ session_update["temperature"] = 0.3 # Lower temperature for more accurate translation
1457
+ session_update["max_response_output_tokens"] = 500 # Limit output to prevent long generations
1458
+ print(f"Interpretation session config: voice={session_update.get('voice')}, lang={self.interpretation_language}")
1459
 
1460
  await conn.session.update(session=session_update)
1461
  self.connection = conn
1462
+ print(f"Connected with tools: {len(tools)} functions, voice: {session_update.get('voice', 'default')}, "
1463
+ f"interpretation_mode: {self.interpretation_mode}, language: {self.interpretation_language if self.interpretation_mode else self.target_language}")
1464
 
1465
  async for event in self.connection:
1466
  # Debug logging for function calls
 
1487
  ),
1488
  )
1489
 
1490
+ # Handle function calls (only in non-interpretation mode)
1491
+ elif event.type == "response.function_call_arguments.start" and not self.interpretation_mode:
1492
  print(f"Function call started")
1493
  self.function_call_in_progress = True
1494
  self.current_function_args = ""
1495
  self.current_call_id = getattr(event, 'call_id', None)
1496
 
1497
+ elif event.type == "response.function_call_arguments.delta" and not self.interpretation_mode:
1498
  if self.function_call_in_progress:
1499
  self.current_function_args += event.delta
1500
 
1501
+ elif event.type == "response.function_call_arguments.done" and not self.interpretation_mode:
1502
  if self.function_call_in_progress:
1503
  print(f"Function call done, args: {self.current_function_args}")
1504
  try:
 
1562
 
1563
 
1564
  # Create initial handler instance
1565
+ handler = OpenAIHandler(web_search_enabled=False, interpretation_mode=False)
1566
 
1567
  # Create components
1568
  chatbot = gr.Chatbot(type="messages")