Update app.py
Browse files
app.py
CHANGED
|
@@ -16,7 +16,7 @@ client = openai.OpenAI()
|
|
| 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 = [
|
|
@@ -26,45 +26,29 @@ CURRENT_TW_TOPICS = [
|
|
| 26 |
"少子化下,延後退休年齡或引進更多外籍移工哪個更急迫?",
|
| 27 |
"改善台灣交通亂象,應優先加強執法還是改善道路設計?",
|
| 28 |
"健保制度改革:提高保費、部分負擔,或有其他永續方案?",
|
| 29 |
-
# 可以再加入其他近期熱門議題
|
| 30 |
]
|
| 31 |
|
| 32 |
# --- Helper 函數:呼叫 OpenAI API (call_asr, call_llm, call_tts 保持不變) ---
|
| 33 |
def call_asr(audio_filepath):
|
| 34 |
-
|
| 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}]"
|
| 47 |
|
| 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:
|
| 62 |
-
response = client.chat.completions.create(
|
| 63 |
-
model=LLM_MODEL,
|
| 64 |
-
messages=openai_messages,
|
| 65 |
-
max_tokens=250,
|
| 66 |
-
temperature=0.7,
|
| 67 |
-
)
|
| 68 |
ai_response = response.choices[0].message.content.strip()
|
| 69 |
return ai_response
|
| 70 |
except Exception as e:
|
|
@@ -72,45 +56,33 @@ def call_llm(topic, user_stance, messages):
|
|
| 72 |
return f"[AI 回應生成失敗: {e}]"
|
| 73 |
|
| 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 主函數 (
|
| 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, "", ""
|
| 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
|
|
@@ -118,20 +90,19 @@ def debate_turn(topic_from_dropdown, custom_topic, user_stance, user_input_text,
|
|
| 118 |
history.append(((processed_audio_path,), None))
|
| 119 |
if user_text.startswith("["):
|
| 120 |
history.append((user_text, None))
|
| 121 |
-
return history, None, "", final_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("["):
|
|
@@ -140,51 +111,35 @@ def debate_turn(topic_from_dropdown, custom_topic, user_stance, user_input_text,
|
|
| 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})
|
| 159 |
|
| 160 |
ai_content = None
|
| 161 |
if isinstance(ai_msg, str):
|
| 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 |
-
|
| 167 |
-
|
| 168 |
-
ai_content = ai_msg[0] # 假設是 (文字, (音訊路徑,)) 結構中的文字
|
| 169 |
-
|
| 170 |
-
if ai_content:
|
| 171 |
-
llm_messages.append({"role": "assistant", "content": ai_content})
|
| 172 |
|
| 173 |
-
# 確保最後是 user message
|
| 174 |
if not llm_messages or llm_messages[-1]["role"] == "assistant":
|
| 175 |
-
if not user_text.startswith("["):
|
| 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:
|
|
@@ -192,9 +147,7 @@ def debate_turn(topic_from_dropdown, custom_topic, user_stance, user_input_text,
|
|
| 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:
|
|
@@ -203,23 +156,18 @@ def debate_turn(topic_from_dropdown, custom_topic, user_stance, user_input_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)
|
|
@@ -232,33 +180,29 @@ with gr.Blocks(theme=gr.themes.Soft(), title="時事觀點對對碰 (OpenAI +
|
|
| 232 |
|
| 233 |
submit_btn = gr.Button("送出論點", variant="primary")
|
| 234 |
|
| 235 |
-
# --- 事件綁定 (
|
| 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 |
-
|
| 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.
|
| 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
|
| 258 |
-
return gr.
|
| 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)
|
|
|
|
| 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 = [
|
|
|
|
| 26 |
"少子化下,延後退休年齡或引進更多外籍移工哪個更急迫?",
|
| 27 |
"改善台灣交通亂象,應優先加強執法還是改善道路設計?",
|
| 28 |
"健保制度改革:提高保費、部分負擔,或有其他永續方案?",
|
|
|
|
| 29 |
]
|
| 30 |
|
| 31 |
# --- Helper 函數:呼叫 OpenAI API (call_asr, call_llm, call_tts 保持不變) ---
|
| 32 |
def call_asr(audio_filepath):
|
| 33 |
+
if not audio_filepath: return ""
|
|
|
|
|
|
|
| 34 |
try:
|
| 35 |
with open(audio_filepath, "rb") as audio_file:
|
| 36 |
+
transcript = client.audio.transcriptions.create(model=ASR_MODEL, file=audio_file)
|
|
|
|
|
|
|
|
|
|
| 37 |
return transcript.text
|
| 38 |
except Exception as e:
|
| 39 |
print(f"ASR Error (OpenAI): {e}")
|
| 40 |
return f"[語音辨識失敗: {e}]"
|
| 41 |
|
| 42 |
def call_llm(topic, user_stance, messages):
|
|
|
|
| 43 |
ai_stance = "反方" if user_stance == "正方" else "正方"
|
|
|
|
| 44 |
system_prompt = f"你正在參與一場關於「{topic}」的辯論。你扮演的是堅定的「{ai_stance}」。請根據對話歷史,針對使用者的最新論點,提出具有批判性、質疑性或反駁性的回應。保持簡潔有力,專注於論證,字數控制在150字以內。"
|
|
|
|
| 45 |
openai_messages = [{"role": "system", "content": system_prompt}]
|
| 46 |
for msg in messages:
|
| 47 |
role = msg.get("role") if msg.get("role") in ["user", "assistant"] else "user"
|
| 48 |
content = msg.get("content", "")
|
| 49 |
+
if content: openai_messages.append({"role": role, "content": content})
|
|
|
|
|
|
|
| 50 |
try:
|
| 51 |
+
response = client.chat.completions.create(model=LLM_MODEL, messages=openai_messages, max_tokens=250, temperature=0.7)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 52 |
ai_response = response.choices[0].message.content.strip()
|
| 53 |
return ai_response
|
| 54 |
except Exception as e:
|
|
|
|
| 56 |
return f"[AI 回應生成失敗: {e}]"
|
| 57 |
|
| 58 |
def call_tts(text):
|
|
|
|
| 59 |
try:
|
| 60 |
if not text or not isinstance(text, str) or text.startswith("["):
|
| 61 |
print(f"Skipping TTS for invalid text: {text}")
|
| 62 |
return None
|
| 63 |
speech_file_path = Path(f"/tmp/speech_{int(time.time() * 1000)}.mp3")
|
| 64 |
+
response = client.audio.speech.create(model=TTS_MODEL, voice=TTS_VOICE, input=text)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 65 |
response.stream_to_file(speech_file_path)
|
| 66 |
return str(speech_file_path)
|
| 67 |
except Exception as e:
|
| 68 |
print(f"TTS Error (OpenAI): {e}")
|
| 69 |
return None
|
| 70 |
|
| 71 |
+
# --- Gradio 主函數 (與上一版相同) ---
|
| 72 |
def debate_turn(topic_from_dropdown, custom_topic, user_stance, user_input_text, user_input_audio, history):
|
|
|
|
|
|
|
|
|
|
| 73 |
final_topic = ""
|
| 74 |
+
if custom_topic and custom_topic.strip():
|
| 75 |
final_topic = custom_topic.strip()
|
| 76 |
print(f"Using custom topic: {final_topic}")
|
| 77 |
elif topic_from_dropdown:
|
| 78 |
final_topic = topic_from_dropdown
|
| 79 |
print(f"Using dropdown topic: {final_topic}")
|
| 80 |
else:
|
|
|
|
| 81 |
history.append(("[錯誤:請選擇或輸入一個辯論主題]", None))
|
| 82 |
+
return history, None, "", ""
|
| 83 |
|
|
|
|
| 84 |
user_text = ""
|
| 85 |
processed_audio_path = None
|
|
|
|
| 86 |
if user_input_audio:
|
| 87 |
print(f"Processing audio input: {user_input_audio}")
|
| 88 |
processed_audio_path = user_input_audio
|
|
|
|
| 90 |
history.append(((processed_audio_path,), None))
|
| 91 |
if user_text.startswith("["):
|
| 92 |
history.append((user_text, None))
|
| 93 |
+
return history, None, "", final_topic
|
| 94 |
|
| 95 |
if not user_text or user_text.startswith("["):
|
| 96 |
if user_input_text:
|
| 97 |
user_text = user_input_text
|
| 98 |
+
if not processed_audio_path:
|
| 99 |
history.append((user_text, None))
|
| 100 |
+
elif user_input_text:
|
| 101 |
user_text = user_input_text
|
| 102 |
+
history.append((f"(改用文字輸入: {user_text})", None))
|
| 103 |
else:
|
| 104 |
if not processed_audio_path:
|
| 105 |
history.append(("[錯誤:請提供文字或語音論點]", None))
|
|
|
|
| 106 |
return history, None, "", final_topic
|
| 107 |
|
| 108 |
if not isinstance(user_text, str) or user_text.startswith("["):
|
|
|
|
| 111 |
history.append((f"[無法處理用戶輸入: {user_text}]", None))
|
| 112 |
return history, None, "", final_topic
|
| 113 |
|
|
|
|
| 114 |
llm_messages = []
|
|
|
|
| 115 |
for i, turn in enumerate(history):
|
| 116 |
user_msg, ai_msg = turn
|
| 117 |
user_content = None
|
| 118 |
if isinstance(user_msg, str):
|
| 119 |
+
if not user_msg.startswith("[") and not user_msg.startswith("(改用文字輸入:") and not user_msg.startswith("(語音辨識結果:"):
|
| 120 |
user_content = user_msg
|
| 121 |
elif isinstance(user_msg, tuple):
|
|
|
|
| 122 |
if i == len(history) - 1 and not user_text.startswith("["):
|
| 123 |
user_content = user_text
|
| 124 |
+
if user_content: llm_messages.append({"role": "user", "content": user_content})
|
|
|
|
|
|
|
| 125 |
|
| 126 |
ai_content = None
|
| 127 |
if isinstance(ai_msg, str):
|
| 128 |
+
if not ai_msg.startswith("["): ai_content = ai_msg
|
|
|
|
| 129 |
elif isinstance(ai_msg, tuple) and len(ai_msg) > 0:
|
| 130 |
+
if isinstance(ai_msg[0], str) and ai_msg[0].endswith(".mp3"): pass
|
| 131 |
+
elif isinstance(ai_msg[0], str) and not ai_msg[0].startswith("["): ai_content = ai_msg[0]
|
| 132 |
+
if ai_content: llm_messages.append({"role": "assistant", "content": ai_content})
|
|
|
|
|
|
|
|
|
|
|
|
|
| 133 |
|
|
|
|
| 134 |
if not llm_messages or llm_messages[-1]["role"] == "assistant":
|
| 135 |
+
if not user_text.startswith("["): llm_messages.append({"role": "user", "content": user_text})
|
|
|
|
| 136 |
else:
|
| 137 |
print("Skipping LLM call due to invalid final user text.")
|
| 138 |
return history, None, "", final_topic
|
| 139 |
|
|
|
|
| 140 |
ai_response_text = call_llm(final_topic, user_stance, llm_messages)
|
|
|
|
|
|
|
| 141 |
ai_response_audio_path = call_tts(ai_response_text)
|
| 142 |
|
|
|
|
| 143 |
last_user_turn_index = -1
|
| 144 |
for i in range(len(history) - 1, -1, -1):
|
| 145 |
if history[i][1] is None and not history[i][0] is None:
|
|
|
|
| 147 |
break
|
| 148 |
|
| 149 |
if last_user_turn_index != -1:
|
|
|
|
| 150 |
history[last_user_turn_index] = (history[last_user_turn_index][0], ai_response_text)
|
|
|
|
| 151 |
if ai_response_audio_path and not ai_response_text.startswith("["):
|
| 152 |
history.append((None, (ai_response_audio_path,)))
|
| 153 |
else:
|
|
|
|
| 156 |
if ai_response_audio_path and not ai_response_text.startswith("["):
|
| 157 |
history.append((None, (ai_response_audio_path,)))
|
| 158 |
|
| 159 |
+
return history, None, "", final_topic
|
|
|
|
|
|
|
| 160 |
|
| 161 |
+
# --- Gradio UI (與上一版相同) ---
|
| 162 |
with gr.Blocks(theme=gr.themes.Soft(), title="時事觀點對對碰 (OpenAI + 自訂主題)") as demo:
|
| 163 |
gr.Markdown("## 🗣️ 時事觀點對對碰 (OpenAI + 自訂主題)")
|
| 164 |
gr.Markdown("選擇預設議題或輸入自訂議題,選擇立場,用文字或語音提出論點,AI 將扮演對手與你辯論!")
|
| 165 |
|
| 166 |
+
chat_history = gr.State([])
|
| 167 |
|
| 168 |
with gr.Row():
|
|
|
|
| 169 |
topic_dd = gr.Dropdown(CURRENT_TW_TOPICS, label="選擇預設辯論主題", value=CURRENT_TW_TOPICS[0])
|
|
|
|
| 170 |
custom_topic_txt = gr.Textbox(label="或輸入自訂辯論主題", placeholder="若此處輸入,將優先使用此主題...")
|
|
|
|
| 171 |
stance_radio = gr.Radio(["正方", "反方"], label="選擇你的立場", value="正方")
|
| 172 |
|
| 173 |
chatbot_ui = gr.Chatbot(label="辯論區", height=500, render_markdown=True, bubble_full_width=False)
|
|
|
|
| 180 |
|
| 181 |
submit_btn = gr.Button("送出論點", variant="primary")
|
| 182 |
|
| 183 |
+
# --- 事件綁定 (與上一版相同,但函數內部已修正 gr.skip) ---
|
| 184 |
submit_btn.click(
|
| 185 |
fn=debate_turn,
|
|
|
|
| 186 |
inputs=[topic_dd, custom_topic_txt, stance_radio, user_txt, user_audio, chatbot_ui],
|
| 187 |
+
outputs=[chatbot_ui, user_audio, user_txt, custom_topic_txt]
|
|
|
|
| 188 |
)
|
| 189 |
|
| 190 |
+
# 當選擇下拉選單時,清空自訂輸入框
|
| 191 |
def clear_custom_topic(dropdown_value):
|
|
|
|
| 192 |
if dropdown_value:
|
| 193 |
return ""
|
| 194 |
+
return gr.skip() # Corrected: Use lowercase 's'
|
| 195 |
|
| 196 |
topic_dd.change(fn=clear_custom_topic, inputs=[topic_dd], outputs=[custom_topic_txt])
|
| 197 |
|
| 198 |
+
# 當在自訂輸入框打字時,清除下拉選單的選擇
|
| 199 |
def clear_dropdown(custom_text):
|
|
|
|
| 200 |
if custom_text and custom_text.strip():
|
| 201 |
+
return None
|
| 202 |
+
return gr.skip() # Corrected: Use lowercase 's'
|
| 203 |
|
| 204 |
custom_topic_txt.change(fn=clear_dropdown, inputs=[custom_topic_txt], outputs=[topic_dd])
|
| 205 |
|
| 206 |
|
| 207 |
if __name__ == "__main__":
|
| 208 |
+
demo.launch(debug=True) # debug=True is helpful
|