yiming-0120 commited on
Commit
a52b880
·
verified ·
1 Parent(s): 8fee980

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +106 -109
app.py CHANGED
@@ -1,50 +1,46 @@
1
  import gradio as gr
2
  import os
3
- import openai # Use the OpenAI library
4
  import time
5
  from pathlib import Path
6
- # from pydub import AudioSegment # Still potentially needed if input format varies
7
 
8
  # --- OpenAI API 設定 ---
9
- # **強烈建議** 將 API Key 設為環境變數 OPENAI_API_KEY
10
- # 或在使用 Hugging Face Spaces 部署時設為 Secret
11
- # 不要直接寫在程式碼中!
12
  openai.api_key = os.getenv("OPENAI_API_KEY")
13
  if not openai.api_key:
14
  raise ValueError("請設定 OpenAI API Key 環境變數 OPENAI_API_KEY")
15
 
16
- # Initialize OpenAI client (new SDK style)
17
  client = openai.OpenAI()
18
 
19
  # --- 模型選擇 (OpenAI Models) ---
20
- LLM_MODEL = "gpt-3.5-turbo" # Or "gpt-4", "gpt-4-turbo-preview", etc.
21
  ASR_MODEL = "whisper-1"
22
- TTS_MODEL = "tts-1" # Or "tts-1-hd"
23
- TTS_VOICE = "alloy" # Choose from: alloy, echo, fable, onyx, nova, shimmer
24
-
25
- # --- 預設辯論主題 ---
26
- TOPICS = [
27
- "AI 是否會大規模取代人類工作?",
28
- "社群媒體對社會利大於弊嗎?",
29
- "是否應該推行全民基本收入 (UBI)?",
30
- "全球化對本地文化是威脅還是機遇?",
31
- "基因編輯技術應受更嚴格的倫理限制嗎?"
 
 
32
  ]
33
 
34
- # --- Helper 函數:呼叫 OpenAI API ---
35
-
36
  def call_asr(audio_filepath):
37
  """將音訊檔案轉換為文字 (使用 OpenAI Whisper)"""
38
  if not audio_filepath:
39
  return ""
40
  try:
41
- # OpenAI Whisper API 需要文件句柄
42
  with open(audio_filepath, "rb") as audio_file:
43
  transcript = client.audio.transcriptions.create(
44
  model=ASR_MODEL,
45
  file=audio_file
46
  )
47
- return transcript.text # 返回辨識出的文字
48
  except Exception as e:
49
  print(f"ASR Error (OpenAI): {e}")
50
  return f"[語音辨識失敗: {e}]"
@@ -52,15 +48,14 @@ def call_asr(audio_filepath):
52
  def call_llm(topic, user_stance, messages):
53
  """呼叫 OpenAI LLM 進行辯論回應"""
54
  ai_stance = "反方" if user_stance == "正方" else "正方"
 
55
  system_prompt = f"你正在參與一場關於「{topic}」的辯論。你扮演的是堅定的「{ai_stance}」。請根據對話歷史,針對使用者的最新論點,提出具有批判性、質疑性或反駁性的回應。保持簡潔有力,專注於論證,字數控制在150字以內。"
56
 
57
- # 準備 OpenAI messages 格式
58
  openai_messages = [{"role": "system", "content": system_prompt}]
59
  for msg in messages:
60
- # 確保 role 'user' 'assistant'
61
- role = msg.get("role") if msg.get("role") in ["user", "assistant"] else "user" # 預設為 user
62
  content = msg.get("content", "")
63
- if content: # 確保內容不為空
64
  openai_messages.append({"role": role, "content": content})
65
 
66
  try:
@@ -79,95 +74,85 @@ def call_llm(topic, user_stance, messages):
79
  def call_tts(text):
80
  """將文字轉換為語音 (使用 OpenAI TTS)"""
81
  try:
82
- # 檢查文字是否有效
83
  if not text or not isinstance(text, str) or text.startswith("["):
84
  print(f"Skipping TTS for invalid text: {text}")
85
  return None
86
-
87
- # 產生唯一的暫存檔案路徑
88
- speech_file_path = Path(f"/tmp/speech_{int(time.time() * 1000)}.mp3") # OpenAI TTS 輸出 mp3
89
-
90
  response = client.audio.speech.create(
91
  model=TTS_MODEL,
92
  voice=TTS_VOICE,
93
  input=text
94
  )
95
-
96
- # 將音訊流式寫入檔案
97
  response.stream_to_file(speech_file_path)
98
-
99
- return str(speech_file_path) # 返回檔案路徑
100
  except Exception as e:
101
  print(f"TTS Error (OpenAI): {e}")
102
  return None
103
 
104
- # --- Gradio 主函數 (與之前版本基本相同,只是調用的 helper 不同) ---
105
- def debate_turn(topic, user_stance, user_input_text, user_input_audio, history):
106
- """處理一輪辯論"""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
107
  user_text = ""
108
- processed_audio_path = None # 用來記錄實際處理的音訊路徑
109
 
110
- # 優先處理語音輸入
111
  if user_input_audio:
112
  print(f"Processing audio input: {user_input_audio}")
113
- processed_audio_path = user_input_audio # 記錄路徑
114
  user_text = call_asr(processed_audio_path)
115
- # 將用戶原始語音(路徑)加入歷史
116
- history.append(((processed_audio_path,), None)) # 用戶音訊路徑作為輸入顯示
117
- if user_text and not user_text.startswith("["):
118
- # 成功辨識,在下面會統一處理
119
- pass
120
- elif user_text.startswith("["): # 辨識失敗
121
- history.append((user_text, None)) # 顯示錯誤訊息
122
- # 清空輸入並返回
123
- return history, None, ""
124
-
125
- # 如果沒有有效語音輸入 或 語音辨識失敗,則使用文字輸入
126
  if not user_text or user_text.startswith("["):
127
  if user_input_text:
128
  user_text = user_input_text
129
- # 將用戶文字輸入加入 history,這會是這一輪的開始
130
- # 如果前面已經因為 audio 加入了 history,這裡就不重複加
131
- if not processed_audio_path:
132
  history.append((user_text, None))
133
- # 如果 audio 辨識失敗,且有文字輸入,用文字覆蓋辨識結果 (或者提示用戶?)
134
- # 這裡選擇使用文字輸入(如果有的話)
135
- elif user_input_text:
136
  user_text = user_input_text
137
- # 替換掉 history audio 辨識失敗的訊息,或者追加? 這裡選擇追加文字輸入
138
- history.append((user_text, None))
139
-
140
-
141
  else:
142
- # 如果兩者都無效 (沒有輸入文字,且音訊無效/未提供/辨識失敗)
143
- if not processed_audio_path: # 僅當連音訊都沒嘗試時才報錯
144
  history.append(("[錯誤:請提供文字或語音論點]", None))
145
  # 如果是語音辨識失敗,前面已加入錯誤訊息
146
- return history, None, ""
147
 
148
- # 確保 user_text 是有效的字串才繼續
149
  if not isinstance(user_text, str) or user_text.startswith("["):
150
  print("Invalid user text, stopping turn.")
151
- # 如果 history 最後一筆是 audio tuple,且辨識失敗,避免錯誤繼續
152
  if history and isinstance(history[-1][0], tuple) and history[-1][1] is None:
153
  history.append((f"[無法處理用戶輸入: {user_text}]", None))
154
- return history, None, ""
155
 
156
  # --- 準備呼叫 LLM ---
157
  llm_messages = []
158
- # 從 history 整理出 LLM 需要的 messages 格式 (保持不變)
159
  for i, turn in enumerate(history):
160
  user_msg, ai_msg = turn
161
  user_content = None
162
  if isinstance(user_msg, str):
163
- if not user_msg.startswith("[") and not user_msg.startswith("(語音辨識結果:"):
164
  user_content = user_msg
165
  elif isinstance(user_msg, tuple):
166
- # 如果是最新一輪的音訊,用 call_asr 的結果
167
- # 這裡需要更可靠的���式找到辨識結果,但為了簡化,先假設 user_text 是對的
168
- if i == len(history) - 1 and not user_text.startswith("["): # 假設 user_text 是剛辨識的
169
  user_content = user_text
170
- # 注意:如果歷史中有多次語音,這裡的邏輯需要加強才能正確配對
171
 
172
  if user_content:
173
  llm_messages.append({"role": "user", "content": user_content})
@@ -177,12 +162,10 @@ def debate_turn(topic, user_stance, user_input_text, user_input_audio, history):
177
  if not ai_msg.startswith("["):
178
  ai_content = ai_msg
179
  elif isinstance(ai_msg, tuple) and len(ai_msg) > 0:
180
- # 之前的結構是 (文字, (音訊,)),現在已修改,但先保留兼容舊結構的解析
181
- if isinstance(ai_msg[0], str) and not ai_msg[0].startswith("["):
182
- ai_content = ai_msg[0]
183
- # 新結構是 (音訊路徑,)
184
- elif isinstance(ai_msg[0], str) and ai_msg[0].endswith(".mp3"):
185
- pass # 這是音訊,不是文本內容
186
 
187
  if ai_content:
188
  llm_messages.append({"role": "assistant", "content": ai_content})
@@ -193,75 +176,89 @@ def debate_turn(topic, user_stance, user_input_text, user_input_audio, history):
193
  llm_messages.append({"role": "user", "content": user_text})
194
  else:
195
  print("Skipping LLM call due to invalid final user text.")
196
- return history, None, ""
197
 
198
- # --- 呼叫 LLM ---
199
- ai_response_text = call_llm(topic, user_stance, llm_messages)
200
 
201
  # --- 呼叫 TTS ---
202
  ai_response_audio_path = call_tts(ai_response_text)
203
 
204
- # --- 格式化 AI 回應並更新歷史 ---
205
- # 找到用戶最新輸入的那一條記錄(它還沒有 AI 回應)
206
  last_user_turn_index = -1
207
  for i in range(len(history) - 1, -1, -1):
208
- if history[i][1] is None and not history[i][0] is None: # 找到最後一個用戶有輸入但AI無回應的地方
209
  last_user_turn_index = i
210
  break
211
 
212
  if last_user_turn_index != -1:
213
- # ****** 修改核心 ******
214
- # 1. 更新找到的那一回合,填入 AI 的文字回應
215
  history[last_user_turn_index] = (history[last_user_turn_index][0], ai_response_text)
216
-
217
- # 2. 如果 TTS 成功,*追加*一個新的回合,只包含 AI 的音訊
218
  if ai_response_audio_path and not ai_response_text.startswith("["):
219
- # AI 的音訊回應格式應該是 (filepath,)
220
- history.append((None, (ai_response_audio_path,))) # User 設為 None, AI 設為音訊元組
221
- # ****** 修改結束 ******
222
  else:
223
- # 如果找不到用戶回合(理論上不該發生),直接追加
224
  print("Warning: Could not find user's turn. Appending AI response.")
225
  history.append(("[用戶回合丟失?]", ai_response_text))
226
  if ai_response_audio_path and not ai_response_text.startswith("["):
227
  history.append((None, (ai_response_audio_path,)))
228
 
 
 
229
 
230
- # 清空輸入框
231
- return history, None, ""
232
 
233
-
234
- # --- Gradio UI (與之前版本相同) ---
235
- with gr.Blocks(theme=gr.themes.Soft(), title="時事觀點對對碰 (OpenAI)") as demo:
236
- gr.Markdown("## 🗣️ 時事觀點對對碰 (OpenAI 版本)")
237
- gr.Markdown("選擇一個議題和你的立場,用文字或語音提出論點,AI 將扮演對手與你辯論!")
238
 
239
  chat_history = gr.State([]) # 儲存對話歷史
240
 
241
  with gr.Row():
242
- topic_dd = gr.Dropdown(TOPICS, label="選擇辯論主題", value=TOPICS[0])
 
 
 
 
243
  stance_radio = gr.Radio(["正方", "反方"], label="選擇你的立場", value="正方")
244
 
245
- # Chatbot 現在可以顯示 Audio 元件
246
  chatbot_ui = gr.Chatbot(label="辯論區", height=500, render_markdown=True, bubble_full_width=False)
247
 
248
  with gr.Row():
249
  with gr.Column(scale=7):
250
  user_txt = gr.Textbox(label="輸入你的論點 (文字)", placeholder="在此輸入文字...")
251
  with gr.Column(scale=3):
252
- # 讓 Audio 元件接收麥克風輸入,輸出為檔案路徑
253
  user_audio = gr.Audio(sources=["microphone"], type="filepath", label="或錄製你的論點 (語音)")
254
 
255
  submit_btn = gr.Button("送出論點", variant="primary")
256
 
257
- # --- 事件綁定 ---
258
  submit_btn.click(
259
  fn=debate_turn,
260
- inputs=[topic_dd, stance_radio, user_txt, user_audio, chatbot_ui], # chatbot_ui 作為 history 輸入
261
- outputs=[chatbot_ui, user_audio, user_txt] # chatbot_ui 作為 history 輸出,並清空 audio 和 text 輸入
 
 
262
  )
263
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
264
  if __name__ == "__main__":
265
- # 注意:在 Hugging Face Spaces 上部署時不需要 share=True
266
- # 在 Colab 或本地 Jupyter Notebook 可能需要
267
- demo.launch(debug=True) # 開啟 debug 可以在控制台看到更詳細的錯誤
 
1
  import gradio as gr
2
  import os
3
+ import openai
4
  import time
5
  from pathlib import Path
6
+ # from pydub import AudioSegment
7
 
8
  # --- OpenAI API 設定 ---
 
 
 
9
  openai.api_key = os.getenv("OPENAI_API_KEY")
10
  if not openai.api_key:
11
  raise ValueError("請設定 OpenAI API Key 環境變數 OPENAI_API_KEY")
12
 
 
13
  client = openai.OpenAI()
14
 
15
  # --- 模型選擇 (OpenAI Models) ---
16
+ LLM_MODEL = "gpt-3.5-turbo"
17
  ASR_MODEL = "whisper-1"
18
+ TTS_MODEL = "tts-1"
19
+ TTS_VOICE = "nova" # 改用聽起來可能較清晰的聲音
20
+
21
+ # --- 預設辯論主題 (更新為台灣時事) ---
22
+ CURRENT_TW_TOPICS = [
23
+ "台灣是否應提高核能發電比例以應對能源需求?",
24
+ "面對高房價,興建社會住宅是最佳解方嗎?",
25
+ "如何平衡經濟發展與環境保護(例如:能源轉型、生態保育)?",
26
+ "少子化下,延後退休年齡或引進更多外籍移工哪個更急迫?",
27
+ "改善台灣交通亂象,應優先加強執法還是改善道路設計?",
28
+ "健保制度改革:提高保費、部分負擔,或有其他永續方案?",
29
+ # 可以再加入其他近期熱門議題
30
  ]
31
 
32
+ # --- Helper 函數:呼叫 OpenAI API (call_asr, call_llm, call_tts 保持不變) ---
 
33
  def call_asr(audio_filepath):
34
  """將音訊檔案轉換為文字 (使用 OpenAI Whisper)"""
35
  if not audio_filepath:
36
  return ""
37
  try:
 
38
  with open(audio_filepath, "rb") as audio_file:
39
  transcript = client.audio.transcriptions.create(
40
  model=ASR_MODEL,
41
  file=audio_file
42
  )
43
+ return transcript.text
44
  except Exception as e:
45
  print(f"ASR Error (OpenAI): {e}")
46
  return f"[語音辨識失敗: {e}]"
 
48
  def call_llm(topic, user_stance, messages):
49
  """呼叫 OpenAI LLM 進行辯論回應"""
50
  ai_stance = "反方" if user_stance == "正方" else "正方"
51
+ # System prompt 保持不變,因為 topic 會動態傳入
52
  system_prompt = f"你正在參與一場關於「{topic}」的辯論。你扮演的是堅定的「{ai_stance}」。請根據對話歷史,針對使用者的最新論點,提出具有批判性、質疑性或反駁性的回應。保持簡潔有力,專注於論證,字數控制在150字以內。"
53
 
 
54
  openai_messages = [{"role": "system", "content": system_prompt}]
55
  for msg in messages:
56
+ role = msg.get("role") if msg.get("role") in ["user", "assistant"] else "user"
 
57
  content = msg.get("content", "")
58
+ if content:
59
  openai_messages.append({"role": role, "content": content})
60
 
61
  try:
 
74
  def call_tts(text):
75
  """將文字轉換為語音 (使用 OpenAI TTS)"""
76
  try:
 
77
  if not text or not isinstance(text, str) or text.startswith("["):
78
  print(f"Skipping TTS for invalid text: {text}")
79
  return None
80
+ speech_file_path = Path(f"/tmp/speech_{int(time.time() * 1000)}.mp3")
 
 
 
81
  response = client.audio.speech.create(
82
  model=TTS_MODEL,
83
  voice=TTS_VOICE,
84
  input=text
85
  )
86
+ # 使用 stream_to_file (即使有 DeprecationWarning,目前仍可用)
 
87
  response.stream_to_file(speech_file_path)
88
+ return str(speech_file_path)
 
89
  except Exception as e:
90
  print(f"TTS Error (OpenAI): {e}")
91
  return None
92
 
93
+ # --- Gradio 主函數 (修改 inputs 和 topic 決定邏輯) ---
94
+ def debate_turn(topic_from_dropdown, custom_topic, user_stance, user_input_text, user_input_audio, history):
95
+ """處理一輪辯論,增加自訂主題處理"""
96
+
97
+ # --- 決定最終辯論主題 ---
98
+ final_topic = ""
99
+ if custom_topic and custom_topic.strip(): # 檢查自訂主題是否非空
100
+ final_topic = custom_topic.strip()
101
+ print(f"Using custom topic: {final_topic}")
102
+ elif topic_from_dropdown:
103
+ final_topic = topic_from_dropdown
104
+ print(f"Using dropdown topic: {final_topic}")
105
+ else:
106
+ # 如果兩者都無效,給一個預設或錯誤提示
107
+ history.append(("[錯誤:請選擇或輸入一個辯論主題]", None))
108
+ return history, None, "", "" # 返回時也要對應 outputs 數量
109
+
110
+ # --- 處理用戶輸入 (文字或語音) ---
111
  user_text = ""
112
+ processed_audio_path = None
113
 
 
114
  if user_input_audio:
115
  print(f"Processing audio input: {user_input_audio}")
116
+ processed_audio_path = user_input_audio
117
  user_text = call_asr(processed_audio_path)
118
+ history.append(((processed_audio_path,), None))
119
+ if user_text.startswith("["):
120
+ history.append((user_text, None))
121
+ return history, None, "", final_topic # 返回 history, 清空 audio, 清空 text, 保持 custom_topic 不變
122
+
 
 
 
 
 
 
123
  if not user_text or user_text.startswith("["):
124
  if user_input_text:
125
  user_text = user_input_text
126
+ if not processed_audio_path: # 僅當沒有語音輸入時才新增文字輸入歷史
 
 
127
  history.append((user_text, None))
128
+ elif user_input_text: # 如果語音辨識失敗但有文字輸入
 
 
129
  user_text = user_input_text
130
+ history.append((f"(改用文字輸入: {user_text})", None)) # 標註一下
 
 
 
131
  else:
132
+ if not processed_audio_path:
 
133
  history.append(("[錯誤:請提供文字或語音論點]", None))
134
  # 如果是語音辨識失敗,前面已加入錯誤訊息
135
+ return history, None, "", final_topic
136
 
 
137
  if not isinstance(user_text, str) or user_text.startswith("["):
138
  print("Invalid user text, stopping turn.")
 
139
  if history and isinstance(history[-1][0], tuple) and history[-1][1] is None:
140
  history.append((f"[無法處理用戶輸入: {user_text}]", None))
141
+ return history, None, "", final_topic
142
 
143
  # --- 準備呼叫 LLM ---
144
  llm_messages = []
145
+ # (從 history 整理出 LLM messages 的邏輯保持不變)
146
  for i, turn in enumerate(history):
147
  user_msg, ai_msg = turn
148
  user_content = None
149
  if isinstance(user_msg, str):
150
+ if not user_msg.startswith("[") and not user_msg.startswith("(改用文字輸入:") and not user_msg.startswith("(語音辨識結果:"): # 避免將提示文字加入對話
151
  user_content = user_msg
152
  elif isinstance(user_msg, tuple):
153
+ # 假設 user_text 是剛辨識的結果
154
+ if i == len(history) - 1 and not user_text.startswith("["):
 
155
  user_content = user_text
 
156
 
157
  if user_content:
158
  llm_messages.append({"role": "user", "content": user_content})
 
162
  if not ai_msg.startswith("["):
163
  ai_content = ai_msg
164
  elif isinstance(ai_msg, tuple) and len(ai_msg) > 0:
165
+ if isinstance(ai_msg[0], str) and ai_msg[0].endswith(".mp3"):
166
+ pass # 音訊檔,略過
167
+ elif isinstance(ai_msg[0], str) and not ai_msg[0].startswith("["):
168
+ ai_content = ai_msg[0] # 假設是 (文字, (音訊路徑,)) 結構中的文字
 
 
169
 
170
  if ai_content:
171
  llm_messages.append({"role": "assistant", "content": ai_content})
 
176
  llm_messages.append({"role": "user", "content": user_text})
177
  else:
178
  print("Skipping LLM call due to invalid final user text.")
179
+ return history, None, "", final_topic
180
 
181
+ # --- 呼叫 LLM (使用 final_topic) ---
182
+ ai_response_text = call_llm(final_topic, user_stance, llm_messages)
183
 
184
  # --- 呼叫 TTS ---
185
  ai_response_audio_path = call_tts(ai_response_text)
186
 
187
+ # --- 格式化 AI 回應並更新歷史 (使用之前修正的邏輯) ---
 
188
  last_user_turn_index = -1
189
  for i in range(len(history) - 1, -1, -1):
190
+ if history[i][1] is None and not history[i][0] is None:
191
  last_user_turn_index = i
192
  break
193
 
194
  if last_user_turn_index != -1:
195
+ # 1. 更新找到的回合,填入 AI 文字回應
 
196
  history[last_user_turn_index] = (history[last_user_turn_index][0], ai_response_text)
197
+ # 2. 如果 TTS 成功,追加一個只包含 AI 音訊的回合
 
198
  if ai_response_audio_path and not ai_response_text.startswith("["):
199
+ history.append((None, (ai_response_audio_path,)))
 
 
200
  else:
 
201
  print("Warning: Could not find user's turn. Appending AI response.")
202
  history.append(("[用戶回合丟失?]", ai_response_text))
203
  if ai_response_audio_path and not ai_response_text.startswith("["):
204
  history.append((None, (ai_response_audio_path,)))
205
 
206
+ # 清空本輪的文字和語音輸入,不清空自訂主題輸入框
207
+ return history, None, "", final_topic # 返回 history, 清空 audio, 清空 text, custom_topic 保持不變
208
 
 
 
209
 
210
+ # --- Gradio UI (增加自訂主題輸入框) ---
211
+ with gr.Blocks(theme=gr.themes.Soft(), title="時事觀點對對碰 (OpenAI + 自訂主題)") as demo:
212
+ gr.Markdown("## 🗣️ 時事觀點對對碰 (OpenAI + 自訂主題)")
213
+ gr.Markdown("選擇預設議題或輸入自訂議題,選擇立場,用文字或語音提出論點,AI 將扮演對手與你辯論!")
 
214
 
215
  chat_history = gr.State([]) # 儲存對話歷史
216
 
217
  with gr.Row():
218
+ # 選項一:下拉選單
219
+ topic_dd = gr.Dropdown(CURRENT_TW_TOPICS, label="選擇預設辯論主題", value=CURRENT_TW_TOPICS[0])
220
+ # 選項二:文字輸入框
221
+ custom_topic_txt = gr.Textbox(label="或輸入自訂辯論主題", placeholder="若此處輸入,將優先使用此主題...")
222
+ # 立場選擇
223
  stance_radio = gr.Radio(["正方", "反方"], label="選擇你的立場", value="正方")
224
 
 
225
  chatbot_ui = gr.Chatbot(label="辯論區", height=500, render_markdown=True, bubble_full_width=False)
226
 
227
  with gr.Row():
228
  with gr.Column(scale=7):
229
  user_txt = gr.Textbox(label="輸入你的論點 (文字)", placeholder="在此輸入文字...")
230
  with gr.Column(scale=3):
 
231
  user_audio = gr.Audio(sources=["microphone"], type="filepath", label="或錄製你的論點 (語音)")
232
 
233
  submit_btn = gr.Button("送出論點", variant="primary")
234
 
235
+ # --- 事件綁定 (更新 inputs 和 outputs) ---
236
  submit_btn.click(
237
  fn=debate_turn,
238
+ # inputs 順序需要與 debate_turn 函數參數對應
239
+ inputs=[topic_dd, custom_topic_txt, stance_radio, user_txt, user_audio, chatbot_ui],
240
+ # outputs: 更新 chatbot, 清空 audio 輸入, 清空 text 輸入, custom_topic 不變所以不用輸出更新
241
+ outputs=[chatbot_ui, user_audio, user_txt, custom_topic_txt] # custom_topic_txt 也作為輸出,使其保持不變
242
  )
243
 
244
+ # 當選擇下拉選單時,清空自訂輸入框 (可選,提升體驗)
245
+ def clear_custom_topic(dropdown_value):
246
+ # 只有當下拉選單有值被選擇時才觸發清空
247
+ if dropdown_value:
248
+ return ""
249
+ return gr.Skip() # 如果 dropdown 被清空,則不改變 custom_topic_txt
250
+
251
+ topic_dd.change(fn=clear_custom_topic, inputs=[topic_dd], outputs=[custom_topic_txt])
252
+
253
+ # 當在自訂輸入框打字時,清除下拉選單的選擇 (可選,提升體驗)
254
+ def clear_dropdown(custom_text):
255
+ # 只有當 custom_text 有內容時才觸發清空
256
+ if custom_text and custom_text.strip():
257
+ return None # 返回 None 會清除 Dropdown 的選擇
258
+ return gr.Skip() # 如果 custom_text 被清空,不改變 dropdown
259
+
260
+ custom_topic_txt.change(fn=clear_dropdown, inputs=[custom_topic_txt], outputs=[topic_dd])
261
+
262
+
263
  if __name__ == "__main__":
264
+ demo.launch(debug=True)