File size: 9,214 Bytes
350bd0d a52b880 350bd0d 4110c58 9dd339c a52b880 4110c58 a52b880 4110c58 a52b880 4110c58 a52b880 4110c58 350bd0d 4110c58 350bd0d 9dd339c 350bd0d 9dd339c a52b880 350bd0d a52b880 350bd0d 9dd339c 350bd0d 9dd339c 350bd0d a52b880 9dd339c 350bd0d a52b880 350bd0d 4110c58 a52b880 9dd339c a52b880 9dd339c a52b880 350bd0d a52b880 350bd0d a52b880 350bd0d a52b880 9dd339c a52b880 350bd0d 9dd339c 8fee980 9dd339c 8fee980 9dd339c 350bd0d a52b880 350bd0d a52b880 350bd0d 8fee980 a52b880 350bd0d 8fee980 9dd339c 350bd0d 8fee980 a52b880 350bd0d 9dd339c 350bd0d 8fee980 9dd339c 8fee980 9dd339c 350bd0d 8fee980 9dd339c 8fee980 a52b880 350bd0d a52b880 350bd0d a52b880 350bd0d 8fee980 a52b880 350bd0d 8fee980 9dd339c 350bd0d 9dd339c a52b880 4110c58 a52b880 350bd0d 9dd339c 350bd0d a52b880 350bd0d 4110c58 350bd0d a52b880 9dd339c 350bd0d 9dd339c a52b880 4110c58 a52b880 9dd339c a52b880 9dd339c a52b880 350bd0d 4110c58 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 |
import gradio as gr
import os
import openai
import time
from pathlib import Path
openai.api_key = os.getenv("OPENAI_API_KEY")
client = openai.OpenAI()
LLM_MODEL = "gpt-4o"
ASR_MODEL = "gpt-4o-transcribe"
TTS_MODEL = "gpt-4o-mini-tts"
TTS_VOICE = "nova"
# --- 預設辯論主題 (台灣時事) ---
CURRENT_TW_TOPICS = [
"台灣是否應提高核能發電比例以應對能源需求?",
"面對高房價,興建社會住宅是最佳解方嗎?",
"台灣是否應放寬代理孕母的限制? (涉及生育權、倫理爭議、法律規範)",
"少子化下,延後退休年齡或引進更多外籍移工哪個更急迫?",
"台灣是否應積極推動雙語教育(中英文)作為主要教學語言?",
"健保制度改革:提高保費、部分負擔,或有其他永續方案?",
"台灣是否應對網路言論進行更嚴格的管制?",
"台灣的死刑制度是否應廢除?",
]
# --- Helper 函數:呼叫 OpenAI API ---
def call_asr(audio_filepath):
if not audio_filepath: return ""
try:
with open(audio_filepath, "rb") as audio_file:
transcript = client.audio.transcriptions.create(model=ASR_MODEL, file=audio_file)
return transcript.text
except Exception as e:
print(f"ASR Error (OpenAI): {e}")
return f"[語音辨識失敗: {e}]"
def call_llm(topic, user_stance, messages):
ai_stance = "反方" if user_stance == "正方" else "正方"
system_prompt = f"你正在參與一場關於「{topic}」的辯論。你扮演的是堅定的「{ai_stance}」。請根據對話歷史,針對使用者的最新論點,提出具有批判性、質疑性或反駁性的回應。保持簡潔有力,專注於論證,字數控制在150字以內。"
openai_messages = [{"role": "system", "content": system_prompt}]
for msg in messages:
role = msg.get("role") if msg.get("role") in ["user", "assistant"] else "user"
content = msg.get("content", "")
if content: openai_messages.append({"role": role, "content": content})
try:
response = client.chat.completions.create(model=LLM_MODEL, messages=openai_messages, max_tokens=250, temperature=0.7)
ai_response = response.choices[0].message.content.strip()
return ai_response
except Exception as e:
print(f"LLM Error (OpenAI): {e}")
return f"[AI 回應生成失敗: {e}]"
def call_tts(text):
try:
if not text or not isinstance(text, str) or text.startswith("["):
print(f"Skipping TTS for invalid text: {text}")
return None
speech_file_path = Path(f"/tmp/speech_{int(time.time() * 1000)}.mp3")
response = client.audio.speech.create(model=TTS_MODEL, voice=TTS_VOICE, input=text)
response.stream_to_file(speech_file_path)
return str(speech_file_path)
except Exception as e:
print(f"TTS Error (OpenAI): {e}")
return None
# --- Gradio 主函數 ---
def debate_turn(topic_from_dropdown, custom_topic, user_stance, user_input_text, user_input_audio, history):
final_topic = ""
if custom_topic and custom_topic.strip():
final_topic = custom_topic.strip()
print(f"Using custom topic: {final_topic}")
elif topic_from_dropdown:
final_topic = topic_from_dropdown
print(f"Using dropdown topic: {final_topic}")
else:
history.append(("[錯誤:請選擇或輸入一個辯論主題]", None))
return history, None, "", ""
user_text = ""
processed_audio_path = None
if user_input_audio:
print(f"Processing audio input: {user_input_audio}")
processed_audio_path = user_input_audio
user_text = call_asr(processed_audio_path)
history.append(((processed_audio_path,), None))
if user_text.startswith("["):
history.append((user_text, None))
return history, None, "", final_topic
if not user_text or user_text.startswith("["):
if user_input_text:
user_text = user_input_text
if not processed_audio_path:
history.append((user_text, None))
elif user_input_text:
user_text = user_input_text
history.append((f"(改用文字輸入: {user_text})", None))
else:
if not processed_audio_path:
history.append(("[錯誤:請提供文字或語音論點]", None))
return history, None, "", final_topic
if not isinstance(user_text, str) or user_text.startswith("["):
print("Invalid user text, stopping turn.")
if history and isinstance(history[-1][0], tuple) and history[-1][1] is None:
history.append((f"[無法處理用戶輸入: {user_text}]", None))
return history, None, "", final_topic
llm_messages = []
for i, turn in enumerate(history):
user_msg, ai_msg = turn
user_content = None
if isinstance(user_msg, str):
if not user_msg.startswith("[") and not user_msg.startswith("(改用文字輸入:") and not user_msg.startswith("(語音辨識結果:"):
user_content = user_msg
elif isinstance(user_msg, tuple):
if i == len(history) - 1 and not user_text.startswith("["):
user_content = user_text
if user_content: llm_messages.append({"role": "user", "content": user_content})
ai_content = None
if isinstance(ai_msg, str):
if not ai_msg.startswith("["): ai_content = ai_msg
elif isinstance(ai_msg, tuple) and len(ai_msg) > 0:
if isinstance(ai_msg[0], str) and ai_msg[0].endswith(".mp3"): pass
elif isinstance(ai_msg[0], str) and not ai_msg[0].startswith("["): ai_content = ai_msg[0]
if ai_content: llm_messages.append({"role": "assistant", "content": ai_content})
if not llm_messages or llm_messages[-1]["role"] == "assistant":
if not user_text.startswith("["): llm_messages.append({"role": "user", "content": user_text})
else:
print("Skipping LLM call due to invalid final user text.")
return history, None, "", final_topic
ai_response_text = call_llm(final_topic, user_stance, llm_messages)
ai_response_audio_path = call_tts(ai_response_text)
last_user_turn_index = -1
for i in range(len(history) - 1, -1, -1):
if history[i][1] is None and not history[i][0] is None:
last_user_turn_index = i
break
if last_user_turn_index != -1:
history[last_user_turn_index] = (history[last_user_turn_index][0], ai_response_text)
if ai_response_audio_path and not ai_response_text.startswith("["):
history.append((None, (ai_response_audio_path,)))
else:
print("Warning: Could not find user's turn. Appending AI response.")
history.append(("[用戶回合丟失?]", ai_response_text))
if ai_response_audio_path and not ai_response_text.startswith("["):
history.append((None, (ai_response_audio_path,)))
return history, None, "", final_topic
# --- Gradio UI (與上一版相同) ---
with gr.Blocks(theme=gr.themes.Soft(), title="時事觀點對對碰 (OpenAI + 自訂主題)") as demo:
gr.Markdown("## 🗣️ 時事觀點對對碰")
gr.Markdown("選擇預設議題或輸入自訂議題,選擇立場,用文字或語音提出論點,AI 將扮演對手與你辯論!")
chat_history = gr.State([])
with gr.Row():
topic_dd = gr.Dropdown(CURRENT_TW_TOPICS, label="選擇預設辯論主題", value=CURRENT_TW_TOPICS[0])
custom_topic_txt = gr.Textbox(label="或輸入自訂辯論主題", placeholder="若此處輸入,將優先使用此主題...")
stance_radio = gr.Radio(["正方", "反方"], label="選擇你的立場", value="正方")
chatbot_ui = gr.Chatbot(label="辯論區", height=500, render_markdown=True, bubble_full_width=False)
with gr.Row():
with gr.Column(scale=7):
user_txt = gr.Textbox(label="輸入你的論點 (文字)", placeholder="在此輸入文字...")
with gr.Column(scale=3):
user_audio = gr.Audio(sources=["microphone"], type="filepath", label="或錄製你的論點 (語音)")
submit_btn = gr.Button("送出論點", variant="primary")
# --- 事件綁定 ---
submit_btn.click(
fn=debate_turn,
inputs=[topic_dd, custom_topic_txt, stance_radio, user_txt, user_audio, chatbot_ui],
outputs=[chatbot_ui, user_audio, user_txt, custom_topic_txt]
)
# 當選擇下拉選單時,清空自訂輸入框
def clear_custom_topic(dropdown_value):
if dropdown_value:
return ""
return gr.skip()
topic_dd.change(fn=clear_custom_topic, inputs=[topic_dd], outputs=[custom_topic_txt])
# 當在自訂輸入框打字時,清除下拉選單的選擇
def clear_dropdown(custom_text):
if custom_text and custom_text.strip():
return None
return gr.skip() # Corrected: Use lowercase 's'
custom_topic_txt.change(fn=clear_dropdown, inputs=[custom_topic_txt], outputs=[topic_dd])
if __name__ == "__main__":
demo.launch(debug=True) |