Jiangxz commited on
Commit
b106933
·
verified ·
1 Parent(s): f1891ea

Upload 2 files

Browse files
Files changed (2) hide show
  1. app.py +388 -179
  2. requirements.txt +7 -1
app.py CHANGED
@@ -3,150 +3,264 @@
3
 
4
  import gradio as gr
5
  import openai
6
- import time
7
- import re
 
 
 
8
  import os
 
 
 
 
 
9
 
10
- MODELS = [
11
- "Meta-Llama-3.1-405B-Instruct",
12
- "Meta-Llama-3.1-70B-Instruct",
13
- "Meta-Llama-3.1-8B-Instruct"
14
- ]
15
-
16
- API_BASE = "https://api.sambanova.ai/v1"
 
 
 
 
 
 
 
 
 
 
 
 
 
17
 
18
  def create_client(api_key=None):
19
- """Creates an OpenAI client instance."""
20
  if api_key:
21
  openai.api_key = api_key
22
  else:
23
  openai.api_key = os.getenv("YOUR_API_KEY")
24
- return openai.OpenAI(api_key=openai.api_key, base_url=API_BASE)
25
-
26
- def chat_with_ai(message, chat_history, system_prompt):
27
- """Formats the chat history for the API call."""
28
- # 初始化訊息列表,首先新增系統提示詞
29
- messages = [{"role": "system", "content": system_prompt}]
30
- # 遍歷聊天歷史,將使用者和AI機器人的對話新增到訊息列表中
31
- for tup in chat_history:
32
- # 獲取字典的第一個鍵(通常是使用者的訊息)
33
- first_key = list(tup.keys())[0]
34
- # 獲取字典的最後一個鍵(通常是AI機器人的回應)
35
- last_key = list(tup.keys())[-1]
36
- # 將使用者的訊息新增到messages列表中
37
- messages.append({"role": "user", "content": tup[first_key]})
38
- # 將AI機器人的回應新增到messages列表中
39
- messages.append({"role": "assistant", "content": tup[last_key]})
40
- # 新增當前使用者的訊息
41
- messages.append({"role": "user", "content": message})
42
- return messages
43
-
44
- def respond(message, chat_history, model, system_prompt, thinking_budget, api_key):
45
- """Sends the message to the API and gets the response."""
46
- # 建立OpenAI客戶端
47
- client = create_client(api_key)
48
- # 格式化聊天歷史
49
- messages = chat_with_ai(message, chat_history, system_prompt.format(budget=thinking_budget))
50
- # 記錄開始時間
51
  start_time = time.time()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
52
  try:
53
- # 呼叫API獲取回應
54
- completion = client.chat.completions.create(model=model, messages=messages)
55
- response = completion.choices[0].message.content
56
- # 計算思考時間
57
- thinking_time = time.time() - start_time
58
- return response, thinking_time
 
 
 
 
 
 
 
 
 
59
  except Exception as e:
60
- # 捕獲並返回錯誤資訊
61
- error_message = f"Error: {str(e)}"
62
- return error_message, time.time() - start_time
63
-
64
- def parse_response(response):
65
- """Parses the response from the API."""
66
- # 使用正規表示式提取回答部分
67
- answer_match = re.search(r'<answer>(.*?)</answer>', response, re.DOTALL)
68
- # 使用正規表示式提取反思部分
69
- reflection_match = re.search(r'<reflection>(.*?)</reflection>', response, re.DOTALL)
70
- # 提取答案和反思內容,如果沒有匹配則設爲空字串
71
- answer = answer_match.group(1).strip() if answer_match else ""
72
- reflection = reflection_match.group(1).strip() if reflection_match else ""
73
- # 提取所有步驟
74
- steps = re.findall(r'<step>(.*?)</step>', response, re.DOTALL)
75
- # 如果沒有提取到答案,則返回原始回應
76
- if answer == "":
77
- return response, "", ""
78
- return answer, reflection, steps
79
-
80
- def generate(message, history, model, system_prompt, thinking_budget, api_key):
81
- """Generates the chatbot response."""
82
- # 獲取AI回應和思考時間
83
- response, thinking_time = respond(message, history, model, system_prompt, thinking_budget, api_key)
84
- # 如果回應是錯誤資訊,直接返回
85
- if response.startswith("Error:"):
86
- return history + [({"role": "system", "content": response},)], ""
87
- # 解析AI的回應
88
- answer, reflection, steps = parse_response(response)
89
- # 初始化訊息列表
90
- messages = []
91
- # 新增使用者的輸入
92
- messages.append({"role": "user", "content": f'<div style="text-align: left;">{message}</div>'})
93
- # 格式化AI回應的步驟和反思
94
- formatted_steps = [f"Step {i}:{step}" for i, step in enumerate(steps, 1)]
95
- all_steps = "<br>".join(formatted_steps) + f"<br><br>Reflection:{reflection}"
96
- # 新增AI的推理過程和思考時間
97
- messages.append({
98
- "role": "assistant",
99
- "content": f'<div style="text-align: left;">{all_steps}</div>',
100
- "metadata": {"title": f"推理過程時間: {thinking_time:.2f} 秒"}
101
- })
102
- # 新增AI的最終答案
103
- messages.append({"role": "assistant", "content": f"<b>{answer}</b>"})
104
- # 返回更新後的歷史記錄和空字串(用於清空輸入框)
105
- return history + messages, ""
106
-
107
- DEFAULT_SYSTEM_PROMPT = """You are a helpful assistant in normal conversation.
108
- When given a problem to solve, you are an expert problem-solving assistant.
109
- Your task is to provide a detailed, step-by-step solution to a given question.
110
- Follow these instructions carefully:
111
- 1. Read the given question carefully and reset counter between <count> and </count> to {budget}
112
- 2. Generate a detailed, logical step-by-step solution.
113
- 3. Enclose each step of your solution within <step> and </step> tags.
114
- 4. You are allowed to use at most {budget} steps (starting budget),
115
- keep track of it by counting down within tags <count> </count>,
116
- STOP GENERATING MORE STEPS when hitting 0, you don't have to use all of them.
117
- 5. Do a self-reflection when you are unsure about how to proceed,
118
- based on the self-reflection and reward, decides whether you need to return
119
- to the previous steps.
120
- 6. After completing the solution steps, reorganize and synthesize the steps
121
- into the final answer within <answer> and </answer> tags.
122
- 7. Provide a critical, honest and subjective self-evaluation of your reasoning
123
- process within <reflection> and </reflection> tags.
124
- 8. Assign a quality score to your solution as a float between 0.0 (lowest
125
- quality) and 1.0 (highest quality), enclosed in <reward> and </reward> tags.
126
- Example format:
127
- <count> [starting budget] </count>
128
- <step> [Content of step 1] </step>
129
- <count> [remaining budget] </count>
130
- <step> [Content of step 2] </step>
131
- <reflection> [Evaluation of the steps so far] </reflection>
132
- <reward> [Float between 0.0 and 1.0] </reward>
133
- <count> [remaining budget] </count>
134
- <step> [Content of step 3 or Content of some previous step] </step>
135
- <count> [remaining budget] </count>
136
- ...
137
- <step> [Content of final step] </step>
138
- <count> [remaining budget] </count>
139
- <answer> [Final Answer] </answer> (must give final answer in this format)
140
- <reflection> [Evaluation of the solution] </reflection>
141
- <reward> [Float between 0.0 and 1.0] </reward>
142
- """
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
143
 
144
  custom_css = """
145
  .center-aligned {
146
  text-align: center !important;
147
  color: #ff4081;
148
  text-shadow: 2px 2px 4px rgba(0,0,0,0.1);
149
- margin-bottom: -5px !important;
 
 
 
 
 
 
 
 
 
 
 
 
150
  }
151
  .gr-input, .gr-box, .gr-dropdown {
152
  border-radius: 10px !important;
@@ -158,7 +272,7 @@ custom_css = """
158
  box-shadow: 0 0 0 2px rgba(245,0,87,0.2) !important;
159
  }
160
  .input-background {
161
- background-color: #ffe0b2 !important;
162
  padding: 15px !important;
163
  border-radius: 10px !important;
164
  margin: 0 !important;
@@ -169,79 +283,174 @@ custom_css = """
169
  border: 1px solid #f0f8ff;
170
  border-radius: 8px;
171
  }
172
- .api-background {
173
- background-color: #FFCFB3 !important;
 
 
 
 
 
 
 
174
  padding: 10px !important;
175
  border-radius: 10px !important;
176
  margin: 0 !important;
177
  }
178
- .custom-button {
 
 
 
 
 
 
 
179
  border-radius: 10px !important;
180
- background-color: #333333 !important;
181
- color: white !important;
182
- font-weight: bold !important;
183
- transition: all 0.3s ease !important;
184
  }
185
- .custom-button:hover {
186
- background-color: #000000 !important;
187
- transform: scale(1.05);
 
188
  }
189
- .pink-bg {
190
- background-color: #ff4081 !important;
 
191
  border-radius: 10px !important;
 
192
  }
193
- .pink-bg label, .pink-bg .label-wrap {
194
- color: white !important;
 
 
195
  }
196
- .pink-bg textarea {
197
  color: black !important;
 
 
 
 
 
 
 
 
198
  }
199
- .user-message .message.user {
 
 
 
 
 
 
200
  background-color: #FFF4B5 !important;
 
201
  border-radius: 10px !important;
202
- padding: 10px !important;
203
- margin: -8px 0px -8px -20px !important;
204
  }
205
- .assistant-message .message.bot {
206
- background-color: #B7E0FF !important;
 
207
  border-radius: 10px !important;
208
- padding: 10px !important;
209
- margin: -8px 0px -8px -20px !important;
 
 
 
 
 
210
  }
211
  """
212
 
213
- with gr.Blocks(theme=gr.themes.Monochrome(), css=custom_css) as demo:
214
- gr.Markdown("# 🏹 Multi-Chain Reasoning using Llama-3.1-405B-Instruct. Deployed by 江信宗 🏹", elem_classes="center-aligned")
215
- chatbot = gr.Chatbot(
216
- label="Chat",
217
- show_label=False,
218
- show_share_button=False,
219
- show_copy_button=True,
220
- likeable=True,
221
- layout="panel",
222
- type="messages",
223
- height="auto-max",
224
- elem_classes=["user-message", "assistant-message"],
225
- container=True
226
- )
227
- msg = gr.Textbox(label="請輸入您的問題:", placeholder="輸入完成後直接按 Enter 開始執行......", autofocus=True, max_lines=10, elem_classes="pink-bg")
228
  with gr.Row():
229
- model = gr.Dropdown(choices=MODELS, label="選擇模型", value=MODELS[0], elem_classes="input-background")
230
- thinking_budget = gr.Slider(minimum=1, maximum=100, value=20, step=1, label="思維規劃", info="模型所能進行的最大思考次數", elem_classes="input-background")
231
- api_key = gr.Textbox(label="API Key", type="password", placeholder="API authentication key for large language models", elem_classes="api-background")
232
- clear_button = gr.Button("清除聊天記錄", elem_classes="custom-button")
233
- clear_button.click(
234
- lambda: ([], "", gr.Info("已成功清除聊天記錄,請繼續提問......")),
235
- inputs=None,
236
- outputs=[chatbot, msg]
237
- )
238
- system_prompt = gr.Textbox(label="System Prompt", value=DEFAULT_SYSTEM_PROMPT, visible=False)
239
- msg.submit(generate, inputs=[msg, chatbot, model, system_prompt, thinking_budget, api_key], outputs=[chatbot, msg])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
240
 
241
- demo.load(js=None)
242
 
243
  if __name__ == "__main__":
244
  if "SPACE_ID" in os.environ:
245
- demo.launch()
246
  else:
247
- demo.launch(share=True, show_api=False)
 
3
 
4
  import gradio as gr
5
  import openai
6
+ from pydub import AudioSegment
7
+ from zhconv_rs import zhconv
8
+ import uuid
9
+ import edge_tts
10
+ import json
11
  import os
12
+ import re
13
+ import time
14
+ import aiofiles
15
+ import pypdf
16
+ import io
17
 
18
+ class TextExtractor:
19
+ @staticmethod
20
+ async def extract_from_pdf(file_path: str) -> str:
21
+ async with aiofiles.open(file_path, 'rb') as file:
22
+ content = await file.read()
23
+ pdf_reader = pypdf.PdfReader(io.BytesIO(content))
24
+ return "\n\n".join(page.extract_text() for page in pdf_reader.pages if page.extract_text())
25
+ @staticmethod
26
+ async def extract_from_txt(file_path: str) -> str:
27
+ async with aiofiles.open(file_path, 'r') as file:
28
+ return await file.read()
29
+ @classmethod
30
+ async def extract_text(cls, file_path: str) -> str:
31
+ _, file_extension = os.path.splitext(file_path)
32
+ if file_extension.lower() == '.pdf':
33
+ return await cls.extract_from_pdf(file_path)
34
+ elif file_extension.lower() == '.txt':
35
+ return await cls.extract_from_txt(file_path)
36
+ else:
37
+ raise gr.Error(f"Unsupported file type: {file_extension}")
38
 
39
  def create_client(api_key=None):
 
40
  if api_key:
41
  openai.api_key = api_key
42
  else:
43
  openai.api_key = os.getenv("YOUR_API_KEY")
44
+ return openai.OpenAI(api_key=openai.api_key, base_url="https://api.sambanova.ai/v1")
45
+
46
+ def generate_response(input_text, language, speaker1, speaker2, api_key):
47
+ speaker1_name = speaker1.split(' - ')[0]
48
+ speaker2_name = speaker2.split(' - ')[0]
49
+ gr.Info("正在生成 Podcast 劇本中......")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
50
  start_time = time.time()
51
+ if language == "Auto Detect":
52
+ language_instruction = "- The podcast MUST be in the same language as the user input."
53
+ else:
54
+ language_instruction = f"- The podcast Must reply to me in {language} language."
55
+ example = """
56
+ {
57
+ "topic": "AIF",
58
+ "podcast": [
59
+ {
60
+ "speaker": 1,
61
+ "line": "Welcome to the 財資歐北共 Podcast. I am the host {speaker1_name}. Today we have invited an expert {speaker2_name} to join our program despite his busy schedule."
62
+ },
63
+ {
64
+ "speaker": 2,
65
+ "line": "Hello everyone, I am {speaker2_name}, I am honored to come and chat with you."
66
+ },
67
+ {
68
+ "speaker": 1,
69
+ "line": "Today we will discuss a very interesting topic..."
70
+ },
71
+ {
72
+ "speaker": 2,
73
+ "line": "Yes, this topic is indeed fascinating. Let's start with..."
74
+ },
75
+ …………,
76
+ {
77
+ "speaker": 1,
78
+ "line": "Thank you {speaker2_name} for your professional sharing. Welcome to subscribe to the Wishing Podcast. Thank you and goodbye."
79
+ }
80
+ ]
81
+ }
82
+ """
83
+
84
+ system_prompt = f"""你的任務是將提供的輸入文字轉換為一個訊息豐富、吸引人且專業的播客對話。輸入文字可能會比較混亂或結構不完整,因為它可能來自不同來源,如PDF檔案或網頁文字等。不要擔心格式問題或任何不相關的訊息;你的目標是超越表面訊息提取可以在播客中討論的關鍵點和知��精華,並突顯有趣的事實。
85
+
86
+ 以下是你將要處理的輸入文字:
87
+ <input_text>
88
+ {{input_text}}
89
+ </input_text>
90
+
91
+ 首先,仔細閱讀輸入文字,並Chain-of-Thought積極找出主要話題、關鍵點、令人印象深刻的細節,以及任何有趣的事實或軼事。思考如何將這些訊息以有趣且吸引人的方式呈現出來,以適合高品質的播客劇本。
92
+
93
+ <scratchpad>
94
+ 頭腦風暴創造性的方法來深度探討你在輸入文字中識別出的主要話題、"key insights"及"golden nuggets of knowledge",儘可能思考使用真實生活的範例、講故事技巧或假設情境來讓內容更能吸引聽眾並讓他們感覺學習到新的知識。
95
+ 請記住,你的播客應當結構清晰和引人入勝並易於普通聽眾理解,避免使用過多的專業術語或假設聽眾對該話題已有瞭解。發揮你的想像力填補輸入文字中的任何空白,或頭腦風暴提出一些值得深入探討與發人深省的問題,以供播客討論。目標是創造一個訊息豐富且娛樂性強的對話,因此可以在你的方法上大膽盡情自由發揮創意。
96
+ 將你的頭腦風暴想法和播客對話的大綱寫在這裡,務必讓它有趣且吸引人。確保記錄下你希望在結尾重申的主要見解和觀點。
97
+ </scratchpad>
98
+
99
+ 現在你已經進行頭腦風暴並建立大綱,該開始撰寫實際的播客對話了。目標是主持人與專家之間自然、對話式的交流,融入你在頭腦風暴中得出的最佳想法,並花費精力確保將任何複雜話題以易於理解的方式解釋清楚,現實生活中的例子和相關的軼事對於讓訊息深入人心至關重要。
100
+ {language_instruction}
101
+ - The podcast should be most long.
102
+ - The podcast should be interesting, lively, and engaging, and hook the listener from the start.
103
+ - The script must be in JSON format.
104
+ Follow this JSON example structure, MUST be in {language} language:
105
+ {example}
106
+
107
+ <podcast_dialogue>
108
+ 根據你在頭腦風暴階段提出的關鍵點和創造性想法,撰寫一段引人入勝且訊息豐富的播客對話(至少1000個字)。定義Host({speaker1_name})和Expert({speaker2_name})的角色,Using signposts to guide listeners and avoiding a monotone, robotic tone,Host以熱情的方式突出有趣且吸引人的觀點,而Expert則提供深入分析、背景訊息和更宏觀的見解。內容必須以清晰的概述開始,並包括任何必要的上下文或解釋,使內容對一般聽眾容易理解。使用Host名字 {speaker1_name} 和Expert名字 {speaker2_name},為聽眾營造更吸引人和身臨其境的聆聽體驗。不要包括像[Host]或[Expert]這樣的括號預留位置。設計你的輸出內容必須生動活潑、促進聽眾參與,並避免單調語氣與機器人般的語調,因為它將直接朗讀為音訊。
109
+ 確保對話儘可能詳細且完整,同時保持在主題之內並維持吸引人的流暢性,避免每句開頭使用"好的"、"是的"。目標是使用你的全部輸出容量,建立儘可能最長的播客節目,同時以娛樂性的方式傳達輸入文字中的關鍵訊息,並追求引人入勝的學習體驗。
110
+ 在對話結束時,讓主持人和專家自然總結他們討論中的主要見解和要點,這應當是對話的隨機部分,以自然隨意而非明顯刻意的總結 - 目的是在結束前最後一次以自然流暢的方式強化核心思想。
111
+ </podcast_dialogue>
112
+ """
113
+ client = create_client(api_key)
114
+ response = client.chat.completions.create(
115
+ model="Meta-Llama-3.1-405B-Instruct",
116
+ messages=[
117
+ {"role": "system", "content": system_prompt},
118
+ {"role": "user", "content": input_text}
119
+ ],
120
+ temperature=1
121
+ )
122
  try:
123
+ podcast_match = re.search(r'{.*}', response.choices[0].message.content, re.DOTALL)
124
+ if podcast_match:
125
+ podcast_json = podcast_match.group(0)
126
+ if language == "繁體中文":
127
+ podcast_json = zhconv(podcast_json, "zh-tw")
128
+ try:
129
+ json.loads(podcast_json)
130
+ except json.JSONDecodeError:
131
+ podcast_json = re.sub(r',\s*}', '}', podcast_json)
132
+ podcast_json = re.sub(r',\s*]', ']', podcast_json)
133
+ end_time = time.time()
134
+ gr.Info(f"已成功生成 Podcast 劇本,執行時間: {(end_time - start_time):.2f} 秒。")
135
+ return podcast_json
136
+ else:
137
+ raise gr.Error("生成 Podcast 劇本失敗!請稍後重試或減少話題內容!")
138
  except Exception as e:
139
+ if "API key not valid" in str(e):
140
+ raise gr.Error("無效的 API 金鑰!!請提供有效的 API 金鑰。")
141
+ elif "rate limit" in str(e).lower():
142
+ raise gr.Error("API 金鑰使用額度已超過限制!!請稍後再試或使用其他 API 金鑰。")
143
+ else:
144
+ raise gr.Error(f"生成 Podcast 劇本失敗!!請稍後再試。")
145
+
146
+ async def tts_generate(input_text, speaker1, speaker2):
147
+ voice_names = {
148
+ "家豪 - 中文 (Male)": "zh-TW-YunJheNeural",
149
+ "淑芬 - 中文 (Female)": "zh-TW-HsiaoChenNeural",
150
+ "子晴 - 中文 (Female)": "zh-TW-HsiaoYuNeural",
151
+ "景睿 - 中文 (Male)": "zh-CN-YunxiNeural",
152
+ "品妍 - 中文 (Female)": "zh-CN-XiaoxiaoNeural",
153
+ "志明 - 中文 (Male)": "zh-CN-YunyangNeural",
154
+ "美玲 - 中文 (Female)": "zh-CN-XiaoyiNeural",
155
+ "建宏 - 中文 (Male)": "zh-CN-YunjianNeural",
156
+ "宥廷 - 中文 (Male)": "zh-CN-YunxiaNeural",
157
+ "雨霏 - 中文 (Female)": "zh-CN-liaoning-XiaobeiNeural",
158
+ "Andrew - English (Male)": "en-US-AndrewMultilingualNeural",
159
+ "Ava - English (Female)": "en-US-AvaMultilingualNeural",
160
+ "Brian - English (Male)": "en-US-BrianMultilingualNeural",
161
+ "Emma - English (Female)": "en-US-EmmaMultilingualNeural",
162
+ "Florian - German (Male)": "de-DE-FlorianMultilingualNeural",
163
+ "Seraphina - German (Female)": "de-DE-SeraphinaMultilingualNeural",
164
+ "Remy - French (Male)": "fr-FR-RemyMultilingualNeural",
165
+ "Vivienne - French (Female)": "fr-FR-VivienneMultilingualNeural"
166
+ }
167
+
168
+ speaker1_voice = voice_names[speaker1]
169
+ speaker2_voice = voice_names[speaker2]
170
+ gr.Info("正在生成 Podcast 音檔中......")
171
+ start_time = time.time()
172
+
173
+ try:
174
+ podcast_dict = json.loads(input_text)
175
+ except json.JSONDecodeError:
176
+ cleaned_input = re.sub(r',\s*}', '}', input_text)
177
+ cleaned_input = re.sub(r',\s*]', ']', cleaned_input)
178
+ podcast_dict = json.loads(cleaned_input)
179
+
180
+ podcast_json = {
181
+ "topic": podcast_dict.get("topic", "Unknown Topic"),
182
+ "podcast": []
183
+ }
184
+ speaker_map = {
185
+ 1: "speaker1",
186
+ 2: "speaker2"
187
+ }
188
+
189
+ combined = AudioSegment.empty()
190
+ for line in podcast_dict.get("podcast", []):
191
+ speaker = line.get("speaker")
192
+ text = line.get("line", "")
193
+ voice = speaker1_voice if speaker == 1 else speaker2_voice
194
+ voice_name = speaker1.split(' - ')[0] if speaker == 1 else speaker2.split(' - ')[0]
195
+
196
+ communicate = edge_tts.Communicate(text, voice)
197
+ audio_file = f"{voice_name}_{uuid.uuid4()}.mp3"
198
+ await communicate.save(audio_file)
199
+
200
+ audio = AudioSegment.from_mp3(audio_file)
201
+ combined += audio
202
+ os.remove(audio_file)
203
+
204
+ podcast_json["podcast"].append({
205
+ "speaker": speaker_map.get(speaker, speaker),
206
+ "line": text
207
+ })
208
+
209
+ output_file = f"Jiangxz_{uuid.uuid4()}.mp3"
210
+ combined.export(output_file, format="mp3")
211
+ end_time = time.time()
212
+ gr.Info(f"已成功生成 Podcast 音檔,執行時間: {(end_time - start_time):.2f} 秒。")
213
+ return output_file
214
+
215
+ async def process_podcast(input_text, input_file, language, speaker1, speaker2, api_key):
216
+ gr.Info("開始生成 Podcast 節目及音檔......")
217
+ start_time = time.time()
218
+ input_text = input_text.strip()
219
+ if input_file:
220
+ input_text = await TextExtractor.extract_text(input_file.name)
221
+ if not input_text.strip():
222
+ gr.Warning("PDF檔案不得為掃描圖片檔,請您確認正確輸入文字或上傳PDF文字檔。")
223
+ return None, None
224
+ podcast_script = generate_response(input_text, language, speaker1, speaker2, api_key)
225
+ speaker1_name = speaker1.split(' - ')[0]
226
+ speaker2_name = speaker2.split(' - ')[0]
227
+
228
+ try:
229
+ podcast_data = json.loads(podcast_script)
230
+ podcast_text = ""
231
+ for line in podcast_data.get("podcast", []):
232
+ if isinstance(line['speaker'], int):
233
+ speaker = speaker1_name if line['speaker'] == 1 else speaker2_name
234
+ else:
235
+ speaker = line['speaker']
236
+ podcast_text += f"{speaker}:{line['line']}\n"
237
+ except json.JSONDecodeError:
238
+ podcast_text = "Error: Unable to parse the podcast script."
239
+
240
+ audio_file = await tts_generate(podcast_script, speaker1, speaker2)
241
+ end_time = time.time()
242
+ gr.Info(f"已成功完成 Podcast 節目及音檔,總執行時間: {(end_time - start_time):.2f} 秒。")
243
+ gr.Info("請等待本訊息自動消失後即可播放或下載 Podcast 音檔!!")
244
+ return podcast_text, audio_file
245
 
246
  custom_css = """
247
  .center-aligned {
248
  text-align: center !important;
249
  color: #ff4081;
250
  text-shadow: 2px 2px 4px rgba(0,0,0,0.1);
251
+ margin-bottom: 0 !important;
252
+ }
253
+ .gen-button {
254
+ border-radius: 10px !important;
255
+ background-color: #ff4081 !important;
256
+ color: white !important;
257
+ font-weight: bold !important;
258
+ transition: all 0.3s ease !important;
259
+ margin: 0 !important;
260
+ }
261
+ .gen-button:hover {
262
+ background-color: #f50057 !important;
263
+ transform: scale(1.05);
264
  }
265
  .gr-input, .gr-box, .gr-dropdown {
266
  border-radius: 10px !important;
 
272
  box-shadow: 0 0 0 2px rgba(245,0,87,0.2) !important;
273
  }
274
  .input-background {
275
+ background-color: #B7E0FF !important;
276
  padding: 15px !important;
277
  border-radius: 10px !important;
278
  margin: 0 !important;
 
283
  border: 1px solid #f0f8ff;
284
  border-radius: 8px;
285
  }
286
+ .file-background {
287
+ background-color: #B7E0FF !important;
288
+ padding: 15px !important;
289
+ border-radius: 10px !important;
290
+ margin: 0 !important;
291
+ height: auto;
292
+ }
293
+ .lng-background {
294
+ background-color: #FFF5CD !important;
295
  padding: 10px !important;
296
  border-radius: 10px !important;
297
  margin: 0 !important;
298
  }
299
+ .lng-background select {
300
+ background-color: #ffffff;
301
+ border: 1px solid #f0f8ff;
302
+ border-radius: 8px;
303
+ }
304
+ .sk1-background {
305
+ background-color: #FFF5CD !important;
306
+ padding: 10px !important;
307
  border-radius: 10px !important;
308
+ margin: 0 !important;
 
 
 
309
  }
310
+ .sk1-background select {
311
+ background-color: #ffffff;
312
+ border: 1px solid #f0f8ff;
313
+ border-radius: 8px;
314
  }
315
+ .sk2-background {
316
+ background-color: #FFF5CD !important;
317
+ padding: 10px !important;
318
  border-radius: 10px !important;
319
+ margin: 0 !important;
320
  }
321
+ .sk2-background select {
322
+ background-color: #ffffff;
323
+ border: 1px solid #f0f8ff;
324
+ border-radius: 8px;
325
  }
326
+ .clear-button {
327
  color: black !important;
328
+ background-color: #FFCFB3 !important;
329
+ padding: 10px !important;
330
+ border-radius: 10px !important;
331
+ margin: 0 !important;
332
+ }
333
+ .clear-button:hover {
334
+ background-color: #FFA07A !important;
335
+ transform: scale(1.05);
336
  }
337
+ .api-background {
338
+ background-color: #FFCFB3 !important;
339
+ padding: 15px !important;
340
+ border-radius: 10px !important;
341
+ margin: 0 !important;
342
+ }
343
+ .audio-background {
344
  background-color: #FFF4B5 !important;
345
+ padding: 5px !important;
346
  border-radius: 10px !important;
347
+ margin: 0 !important;
 
348
  }
349
+ .script-background {
350
+ background-color: #FEF9D9 !important;
351
+ padding: 15px !important;
352
  border-radius: 10px !important;
353
+ margin: 0 !important;
354
+ }
355
+ .script-background textarea {
356
+ font-size: 18px !important;
357
+ background-color: #ffffff;
358
+ border: 1px solid #f0f8ff;
359
+ border-radius: 8px;
360
  }
361
  """
362
 
363
+ with gr.Blocks(theme=gr.themes.Monochrome(), css=custom_css) as iface:
364
+ gr.Markdown("""
365
+ # 🎙️ 聲音經濟 - 財資歐北共 Podcast 🎙️
366
+ > ### **※ 玩轉聲音魅力,開拓更多可能性,自動生成 Podcast 節目及音檔,系統布署:江信宗,LLM:Llama-3.1-405B-Instruct。**
367
+ """, elem_classes="center-aligned")
368
+
 
 
 
 
 
 
 
 
 
369
  with gr.Row():
370
+ input_text = gr.Textbox(
371
+ label="請輸入 Podcast 話題(建議50至1000字)",
372
+ placeholder="受限 LLM Context Length,建議2000字以內......",
373
+ autofocus=True,
374
+ max_lines=20,
375
+ scale=4,
376
+ elem_classes="input-background"
377
+ )
378
+ fileName = gr.File(
379
+ file_types=[".pdf", ".txt"],
380
+ label="或上傳 PDF 檔",
381
+ scale=1,
382
+ elem_classes="file-background"
383
+ )
384
+
385
+ def check_input_length(text):
386
+ if 0 < len(text) < 4:
387
+ return gr.Warning("輸入內容過短,請提供明確的話題內容。")
388
+ elif len(text) > 4096:
389
+ return gr.Warning("輸入內容已超過 max tokens,請縮短話題內容。")
390
+ input_text.change(fn=check_input_length, inputs=[input_text])
391
+
392
+ with gr.Row():
393
+ Language = gr.Dropdown(
394
+ choices=["繁體中文", "Auto Detect", "English", "日本語", "한국어", "Deutsch", "Français"],
395
+ value="繁體中文",
396
+ label="節目語言",
397
+ interactive=True,
398
+ scale=1,
399
+ elem_classes="lng-background"
400
+ )
401
+
402
+ speaker_choices = [
403
+ "家豪 - 中文 (Male)",
404
+ "淑芬 - 中文 (Female)",
405
+ "子晴 - 中文 (Female)",
406
+ "景睿 - 中文 (Male)",
407
+ "品妍 - 中文 (Female)",
408
+ "志明 - 中文 (Male)",
409
+ "美玲 - 中文 (Female)",
410
+ "建宏 - 中文 (Male)",
411
+ "宥廷 - 中文 (Male)",
412
+ "雨霏 - 中文 (Female)",
413
+ "Andrew - English (Male)",
414
+ "Ava - English (Female)",
415
+ "Brian - English (Male)",
416
+ "Emma - English (Female)",
417
+ "Florian - German (Male)",
418
+ "Seraphina - German (Female)",
419
+ "Remy - French (Male)",
420
+ "Vivienne - French (Female)"
421
+ ]
422
+
423
+ Speaker_1 = gr.Dropdown(
424
+ choices=speaker_choices,
425
+ value="景睿 - 中文 (Male)",
426
+ label="播客#1語音",
427
+ interactive=True,
428
+ scale=2,
429
+ elem_classes="sk1-background"
430
+ )
431
+ Speaker_2 = gr.Dropdown(
432
+ choices=speaker_choices,
433
+ value="品妍 - 中文 (Female)",
434
+ label="播客#2語音",
435
+ interactive=True,
436
+ scale=2,
437
+ elem_classes="sk2-background"
438
+ )
439
+
440
+ clear_input_text_button = gr.Button("清除Podcast話題", scale=1, elem_classes="clear-button")
441
+ clear_input_text_button.click(fn=lambda: (None, None), inputs=None, outputs=[input_text, fileName])
442
+
443
+ with gr.Row():
444
+ generate_button = gr.Button("生成 Podcast 節目及音檔", scale=2, elem_classes="gen-button")
445
+ api_key = gr.Textbox(label="請輸入您的 API Key", type="password", placeholder="API authentication key for large language models", scale=1, elem_classes="api-background")
446
+
447
+ audio_output = gr.Audio(label="Generated Podcast Audio", elem_classes="audio-background")
448
+ podcast_script = gr.Textbox(label="Generated Podcast 劇本", elem_classes="script-background")
449
+ generate_button.click(fn=process_podcast, inputs=[input_text, fileName, Language, Speaker_1, Speaker_2, api_key], outputs=[podcast_script, audio_output])
450
 
 
451
 
452
  if __name__ == "__main__":
453
  if "SPACE_ID" in os.environ:
454
+ iface.launch()
455
  else:
456
+ iface.launch(share=True, show_api=False)
requirements.txt CHANGED
@@ -1 +1,7 @@
1
- openai
 
 
 
 
 
 
 
1
+ gradio
2
+ openai
3
+ pydub
4
+ edge-tts
5
+ aiofiles
6
+ zhconv-rs
7
+ pypdf