tbdavid2019 commited on
Commit
1c3d8d3
·
1 Parent(s): d3aaa95

feat: 更新對話生成邏輯,增加輸入文本檢查及模板化系統,移除舊版提示詞欄位

Browse files
Files changed (3) hide show
  1. __pycache__/prompts.cpython-313.pyc +0 -0
  2. app.py +98 -96
  3. prompts.py +1 -38
__pycache__/prompts.cpython-313.pyc ADDED
Binary file (7.89 kB). View file
 
app.py CHANGED
@@ -16,7 +16,7 @@ import ebooklib
16
  from ebooklib import epub
17
 
18
  # 導入自定義模組
19
- from prompts import INSTRUCTION_TEMPLATES, get_template, get_all_template_names
20
  from quality_control import DialogueQualityChecker, validate_dialogue_structure, suggest_improvements
21
  from content_planner import ContentPlanner, SmartContentSplitter, create_adaptive_prompts
22
 
@@ -60,15 +60,9 @@ def fetch_models(api_key, api_base=None):
60
 
61
  def generate_dialogue_via_requests(
62
  pdf_text: str,
63
- intro_instructions: str,
64
- text_instructions: str,
65
- scratch_pad_instructions: str,
66
- prelude_dialog: str,
67
- podcast_dialog_instructions: str,
68
  model: str,
69
  llm_api_key: str,
70
  api_base: str,
71
- edited_transcript: str = None,
72
  user_feedback: str = None,
73
  num_parts: int = 3,
74
  max_input_length: int = 1000000,
@@ -82,6 +76,14 @@ def generate_dialogue_via_requests(
82
  """
83
  logger.info(f"準備生成對話,使用模型: {model}")
84
 
 
 
 
 
 
 
 
 
85
  # 限制輸入文本長度
86
  original_length = len(pdf_text)
87
  if len(pdf_text) > max_input_length:
@@ -90,15 +92,17 @@ def generate_dialogue_via_requests(
90
 
91
  logger.info(f"輸入文本長度: {len(pdf_text)} 字符")
92
 
93
- # 使用簡化的模板化提示詞
94
- base_prompt = podcast_dialog_instructions.format(content=pdf_text)
 
 
 
 
 
95
 
96
- # 如果有自定義提示詞或編輯過的文稿,添加到提示中
97
  if user_feedback:
98
  base_prompt += f"\n\n【額外要求】\n{user_feedback}"
99
-
100
- if edited_transcript:
101
- base_prompt += f"\n\n【參考文稿】\n{edited_transcript}"
102
 
103
  headers = {
104
  "Authorization": f"Bearer {llm_api_key}",
@@ -174,6 +178,11 @@ def generate_dialogue_via_requests(
174
  generated_content.strip().endswith(('在', '的', '了', '是', '會', '但', '因為', '所以', '這', '那'))
175
  )
176
 
 
 
 
 
 
177
  if is_truncated:
178
  logger.warning("檢測到內容可能被截斷,嘗試分批生成...")
179
  if progress_callback:
@@ -242,8 +251,7 @@ def generate_summary(
242
 
243
  # 從 prompts 模組獲取摘要模板
244
  try:
245
- summary_template = get_template(summary_type)["dialog"]
246
- prompt = summary_template.format(content=script_content)
247
  except KeyError:
248
  return f"錯誤:未找到摘要模板 '{summary_type}'"
249
 
@@ -295,6 +303,11 @@ def _generate_in_batches(pdf_text, base_prompt, headers, url, model, num_parts,
295
  分批生成的備用機制,只在單次生成被截斷時使用
296
  """
297
  try:
 
 
 
 
 
298
  logger.info(f"開始分批生成,共 {num_parts} 個部分")
299
 
300
  # 生成內容大綱(簡化版)
@@ -342,6 +355,7 @@ def _generate_in_batches(pdf_text, base_prompt, headers, url, model, num_parts,
342
  - 按照正常格式開場:speaker-1: 歡迎收聽 David888 Podcast,我是 David...
343
  - speaker-2 首次發言時自我介紹為 Cordelia
344
  - 討論前面的主題
 
345
  - **不要結束對話**,在一個開放的討論點停止
346
  - 必須使用繁體中文,格式為 speaker-1: 和 speaker-2:
347
  """
@@ -361,7 +375,8 @@ def _generate_in_batches(pdf_text, base_prompt, headers, url, model, num_parts,
361
  請:
362
  1. **不要重複開場**,直接繼續前面的對話
363
  2. 完成剩餘主題的討論
364
- 3. **自然地結束對話**,包含總結和告別
 
365
 
366
  **必須使用繁體中文,格式為 speaker-1: 和 speaker-2:**
367
  """
@@ -381,17 +396,18 @@ def _generate_in_batches(pdf_text, base_prompt, headers, url, model, num_parts,
381
  請:
382
  1. **不要重複開場**,直接繼續前面的對話
383
  2. ��論相應的主題
384
- 3. **不要結束對話**,在一個開放的討論點停止
 
385
 
386
  **必須使用繁體中文,格式為 speaker-1: 和 speaker-2:**
387
  """
388
 
389
- # 生成當前部分
390
  part_payload = {
391
  "model": model,
392
  "messages": [{"role": "user", "content": part_prompt}],
393
  "temperature": 0.7,
394
- "max_tokens": 8192
395
  }
396
 
397
  for attempt in range(max_retries):
@@ -436,12 +452,7 @@ def validate_and_generate_script(
436
  openai_api_key,
437
  text_model,
438
  api_base_value,
439
- intro_instructions,
440
- text_instructions,
441
- scratch_pad_instructions,
442
- prelude_dialog,
443
- podcast_dialog_instructions,
444
- edited_transcript,
445
  user_feedback,
446
  num_parts=3,
447
  max_input_length=1000000,
@@ -514,21 +525,44 @@ def validate_and_generate_script(
514
  progress_callback(f"處理 EPUB 文件: {os.path.basename(filename)}")
515
 
516
  book = epub.read_epub(file.name)
517
- item_count = len(list(book.get_items()))
 
518
  processed_count = 0
519
 
520
- for item in book.get_items():
521
- if item.get_type() == ebooklib.ITEM_DOCUMENT:
 
 
 
522
  try:
523
- processed_count += 1
524
- soup = BeautifulSoup(item.get_body_content(), 'html.parser')
525
- item_text = soup.get_text()
526
- combined_text += item_text + "\n\n"
527
- logger.debug(f"已處理 EPUB 項目 {processed_count}/{item_count}")
528
- if processed_count % 5 == 0 and progress_callback:
529
- progress_callback(f"處理 EPUB: {os.path.basename(filename)} - {processed_count}/{item_count} 項目")
530
- except Exception as e:
531
- logger.error(f"EPUB 項目處理錯誤: {str(e)}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
532
 
533
  logger.info(f"EPUB 處理完成: {filename}, 共處理 {processed_count} 個項目")
534
  if progress_callback:
@@ -538,6 +572,12 @@ def validate_and_generate_script(
538
  logger.error(error_msg)
539
  if progress_callback:
540
  progress_callback(error_msg)
 
 
 
 
 
 
541
  else:
542
  logger.warning(f"跳過不支持的文件格式: {filename}")
543
  if progress_callback:
@@ -548,6 +588,14 @@ def validate_and_generate_script(
548
  if progress_callback:
549
  progress_callback(f"所有文件處理完成,合併文本長度: {text_length} 字符")
550
 
 
 
 
 
 
 
 
 
551
  # 生成對話腳本
552
  logger.info("開始生成腳本...")
553
  if progress_callback:
@@ -555,21 +603,15 @@ def validate_and_generate_script(
555
 
556
  script = generate_dialogue_via_requests(
557
  pdf_text=combined_text,
558
- intro_instructions=intro_instructions,
559
- text_instructions=text_instructions,
560
- scratch_pad_instructions=scratch_pad_instructions,
561
- prelude_dialog=prelude_dialog,
562
- podcast_dialog_instructions=podcast_dialog_instructions,
563
  model=text_model,
564
  llm_api_key=openai_api_key,
565
  api_base=api_base_value,
566
- edited_transcript=edited_transcript,
567
  user_feedback=user_feedback,
568
  num_parts=num_parts,
569
  max_input_length=max_input_length,
570
  max_output_tokens=max_output_tokens,
571
  progress_callback=progress_callback,
572
- template_type="podcast"
573
  )
574
 
575
  logger.info("腳本生成完成")
@@ -632,44 +674,14 @@ with gr.Blocks(title="Script Generator", css="""
632
  interactive=True
633
  )
634
 
635
- intro_text = gr.Textbox(
636
- label="介紹提示詞 | Intro Instructions",
637
- lines=5,
638
- value=INSTRUCTION_TEMPLATES["podcast"]["intro"],
639
- interactive=True,
640
- visible=False # 隱藏此欄位
641
- )
642
-
643
- text_instructions = gr.Textbox(
644
- label="文本分析提示詞 | Text Instructions",
645
- lines=5,
646
- value=INSTRUCTION_TEMPLATES["podcast"]["text_instructions"],
647
- interactive=True,
648
- visible=False # 隱藏此欄位
649
- )
650
-
651
- scratch_pad = gr.Textbox(
652
- label="腦力激盪提示詞 | Scratch Pad",
653
- lines=5,
654
- value=INSTRUCTION_TEMPLATES["podcast"]["scratch_pad"],
655
- interactive=True,
656
- visible=False # 隱藏此欄位
657
- )
658
-
659
- prelude = gr.Textbox(
660
- label="前導提示詞 | Prelude",
661
- lines=5,
662
- value=INSTRUCTION_TEMPLATES["podcast"]["prelude"],
663
- interactive=True,
664
- visible=False # 隱藏此欄位
665
- )
666
-
667
  dialog = gr.Textbox(
668
- label="主要提示詞 | Main Prompt (預覽用,由模板自動設定)",
669
  lines=8,
670
- value=INSTRUCTION_TEMPLATES["podcast"]["dialog"],
671
- interactive=True,
672
- info="這是當前選擇模板的提示詞內容,通常不需要手動修改"
673
  )
674
 
675
  custom_prompt = gr.Textbox(
@@ -764,17 +776,12 @@ with gr.Blocks(title="Script Generator", css="""
764
  def update_template(template):
765
  logger.info(f"切換模板至: {template}")
766
  try:
767
- template_data = get_template(template)
768
- return [
769
- template_data["intro"],
770
- template_data["text_instructions"],
771
- template_data["scratch_pad"],
772
- template_data["prelude"],
773
- template_data["dialog"]
774
- ]
775
  except KeyError:
776
  logger.error(f"模板 {template} 不存在")
777
- return ["", "", "", "", ""]
778
 
779
  fetch_button.click(
780
  fn=handle_model_fetch,
@@ -785,7 +792,7 @@ with gr.Blocks(title="Script Generator", css="""
785
  template_dropdown.change(
786
  fn=update_template,
787
  inputs=[template_dropdown],
788
- outputs=[intro_text, text_instructions, scratch_pad, prelude, dialog]
789
  )
790
 
791
  def handle_script_generation(*args):
@@ -828,12 +835,7 @@ with gr.Blocks(title="Script Generator", css="""
828
  api_key,
829
  model_dropdown,
830
  api_base,
831
- intro_text,
832
- text_instructions,
833
- scratch_pad,
834
- prelude,
835
- dialog,
836
- gr.Textbox(value=""), # edited_transcript
837
  custom_prompt, # user_feedback
838
  num_parts_slider, # 添加滑動條參數
839
  max_input_length_slider, # 添加最大輸入文本長度參數
 
16
  from ebooklib import epub
17
 
18
  # 導入自定義模組
19
+ from prompts import get_prompt, get_all_template_names
20
  from quality_control import DialogueQualityChecker, validate_dialogue_structure, suggest_improvements
21
  from content_planner import ContentPlanner, SmartContentSplitter, create_adaptive_prompts
22
 
 
60
 
61
  def generate_dialogue_via_requests(
62
  pdf_text: str,
 
 
 
 
 
63
  model: str,
64
  llm_api_key: str,
65
  api_base: str,
 
66
  user_feedback: str = None,
67
  num_parts: int = 3,
68
  max_input_length: int = 1000000,
 
76
  """
77
  logger.info(f"準備生成對話,使用模型: {model}")
78
 
79
+ # 檢查輸入文本是否為空或過短
80
+ if not pdf_text or len(pdf_text.strip()) < 50:
81
+ error_msg = f"錯誤:輸入文本過短或為空(當前長度: {len(pdf_text)} 字符)。請確認上傳的文件包含有效內容。"
82
+ logger.error(error_msg)
83
+ if progress_callback:
84
+ progress_callback(error_msg)
85
+ return error_msg
86
+
87
  # 限制輸入文本長度
88
  original_length = len(pdf_text)
89
  if len(pdf_text) > max_input_length:
 
92
 
93
  logger.info(f"輸入文本長度: {len(pdf_text)} 字符")
94
 
95
+ # 使用直接從 prompts 模組獲取的模板
96
+ try:
97
+ base_prompt = get_prompt(template_type, pdf_text)
98
+ except KeyError:
99
+ # 如果模板不存在,使用默認的 podcast 模板
100
+ logger.warning(f"模板 '{template_type}' 不存在,使用默認 podcast 模板")
101
+ base_prompt = get_prompt("podcast", pdf_text)
102
 
103
+ # 如果有自定義提示詞,添加到提示中
104
  if user_feedback:
105
  base_prompt += f"\n\n【額外要求】\n{user_feedback}"
 
 
 
106
 
107
  headers = {
108
  "Authorization": f"Bearer {llm_api_key}",
 
178
  generated_content.strip().endswith(('在', '的', '了', '是', '會', '但', '因為', '所以', '這', '那'))
179
  )
180
 
181
+ # 如果原始輸入文本為空或太短,不進行分批生成
182
+ if len(pdf_text.strip()) < 100:
183
+ logger.warning("輸入文本太短或為空,跳過分批生成")
184
+ is_truncated = False
185
+
186
  if is_truncated:
187
  logger.warning("檢測到內容可能被截斷,嘗試分批生成...")
188
  if progress_callback:
 
251
 
252
  # 從 prompts 模組獲取摘要模板
253
  try:
254
+ prompt = get_prompt(summary_type, script_content)
 
255
  except KeyError:
256
  return f"錯誤:未找到摘要模板 '{summary_type}'"
257
 
 
303
  分批生成的備用機制,只在單次生成被截斷時使用
304
  """
305
  try:
306
+ # 檢查輸入文本是否足夠
307
+ if not pdf_text or len(pdf_text.strip()) < 100:
308
+ logger.error("輸入文本為空或太短,無法進行分批生成")
309
+ return None
310
+
311
  logger.info(f"開始分批生成,共 {num_parts} 個部分")
312
 
313
  # 生成內容大綱(簡化版)
 
355
  - 按照正常格式開場:speaker-1: 歡迎收聽 David888 Podcast,我是 David...
356
  - speaker-2 首次發言時自我介紹為 Cordelia
357
  - 討論前面的主題
358
+ - **生成至少 20-30 輪對話**
359
  - **不要結束對話**,在一個開放的討論點停止
360
  - 必須使用繁體中文,格式為 speaker-1: 和 speaker-2:
361
  """
 
375
  請:
376
  1. **不要重複開場**,直接繼續前面的對話
377
  2. 完成剩餘主題的討論
378
+ 3. **生成至少 20-30 輪對話**
379
+ 4. **自然地結束對話**,包含總結和告別
380
 
381
  **必須使用繁體中文,格式為 speaker-1: 和 speaker-2:**
382
  """
 
396
  請:
397
  1. **不要重複開場**,直接繼續前面的對話
398
  2. ��論相應的主題
399
+ 3. **生成至少 20-30 輪對話**
400
+ 4. **不要結束對話**,在一個開放的討論點停止
401
 
402
  **必須使用繁體中文,格式為 speaker-1: 和 speaker-2:**
403
  """
404
 
405
+ # 生成當前部分,使用更高的 token 限制
406
  part_payload = {
407
  "model": model,
408
  "messages": [{"role": "user", "content": part_prompt}],
409
  "temperature": 0.7,
410
+ "max_tokens": 16384 # 提高每部分的 token 限制
411
  }
412
 
413
  for attempt in range(max_retries):
 
452
  openai_api_key,
453
  text_model,
454
  api_base_value,
455
+ template_type,
 
 
 
 
 
456
  user_feedback,
457
  num_parts=3,
458
  max_input_length=1000000,
 
525
  progress_callback(f"處理 EPUB 文件: {os.path.basename(filename)}")
526
 
527
  book = epub.read_epub(file.name)
528
+ items = list(book.get_items())
529
+ item_count = len(items)
530
  processed_count = 0
531
 
532
+ # 如果沒有找到任何項目,嘗試替代方法
533
+ if item_count == 0:
534
+ logger.warning(f"EPUB 文件沒有找到項目,嘗試替代處理方法: {filename}")
535
+ # 嘗試直接讀取 spine 中的文檔
536
+ for spine_item in book.spine:
537
  try:
538
+ item_id = spine_item[0]
539
+ item = book.get_item_by_id(item_id)
540
+ if item:
541
+ soup = BeautifulSoup(item.get_body_content(), 'html.parser')
542
+ item_text = soup.get_text()
543
+ if item_text.strip(): # 只添加非空內容
544
+ combined_text += item_text + "\n\n"
545
+ processed_count += 1
546
+ except Exception as spine_error:
547
+ logger.debug(f"處理 spine 項目失敗: {spine_error}")
548
+ continue
549
+ else:
550
+ # 正常處理流程
551
+ for item in items:
552
+ if item.get_type() == ebooklib.ITEM_DOCUMENT:
553
+ try:
554
+ processed_count += 1
555
+ soup = BeautifulSoup(item.get_body_content(), 'html.parser')
556
+ item_text = soup.get_text()
557
+ if item_text.strip(): # 只添加非空內容
558
+ combined_text += item_text + "\n\n"
559
+
560
+ logger.debug(f"已處理 EPUB 項目 {processed_count}/{item_count}")
561
+ if processed_count % 5 == 0 and progress_callback:
562
+ progress_callback(f"處理 EPUB: {os.path.basename(filename)} - {processed_count}/{item_count} 項目")
563
+ except Exception as item_error:
564
+ logger.error(f"EPUB 項目處理錯誤: {item_error}")
565
+ continue
566
 
567
  logger.info(f"EPUB 處理完成: {filename}, 共處理 {processed_count} 個項目")
568
  if progress_callback:
 
572
  logger.error(error_msg)
573
  if progress_callback:
574
  progress_callback(error_msg)
575
+
576
+ # 提供 EPUB 處理失敗的具體建議
577
+ suggestion = "建議:1) 檢查 EPUB 文件是否完整;2) 嘗試用其他工具轉換為 PDF 或 TXT 格式後重新上傳"
578
+ logger.info(suggestion)
579
+ if progress_callback:
580
+ progress_callback(suggestion)
581
  else:
582
  logger.warning(f"跳過不支持的文件格式: {filename}")
583
  if progress_callback:
 
588
  if progress_callback:
589
  progress_callback(f"所有文件處理完成,合併文本長度: {text_length} 字符")
590
 
591
+ # 檢查是否成功提取到文本內容
592
+ if text_length < 50:
593
+ error_msg = f"錯誤:未能從上傳的文件中提取到足夠的文本內容(提取到 {text_length} 字符)。請檢查文件格式和內容是否正確。"
594
+ logger.error(error_msg)
595
+ if progress_callback:
596
+ progress_callback(error_msg)
597
+ return None, error_msg
598
+
599
  # 生成對話腳本
600
  logger.info("開始生成腳本...")
601
  if progress_callback:
 
603
 
604
  script = generate_dialogue_via_requests(
605
  pdf_text=combined_text,
 
 
 
 
 
606
  model=text_model,
607
  llm_api_key=openai_api_key,
608
  api_base=api_base_value,
 
609
  user_feedback=user_feedback,
610
  num_parts=num_parts,
611
  max_input_length=max_input_length,
612
  max_output_tokens=max_output_tokens,
613
  progress_callback=progress_callback,
614
+ template_type=template_type
615
  )
616
 
617
  logger.info("腳本生成完成")
 
674
  interactive=True
675
  )
676
 
677
+ # 移除舊版本的多個提示詞欄位,現在使用模板化系統
678
+ # 只保留一個模板預覽欄位
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
679
  dialog = gr.Textbox(
680
+ label="模板預覽 | Template Preview",
681
  lines=8,
682
+ value="選擇模板後將顯示提示詞內容",
683
+ interactive=False,
684
+ info="這是當前選擇模板的提示詞內容預覽"
685
  )
686
 
687
  custom_prompt = gr.Textbox(
 
776
  def update_template(template):
777
  logger.info(f"切換模板至: {template}")
778
  try:
779
+ # 使用新的模板系統
780
+ template_content = get_prompt(template, "[內容將在此處顯示]")
781
+ return template_content
 
 
 
 
 
782
  except KeyError:
783
  logger.error(f"模板 {template} 不存在")
784
+ return "錯誤:模板不存在"
785
 
786
  fetch_button.click(
787
  fn=handle_model_fetch,
 
792
  template_dropdown.change(
793
  fn=update_template,
794
  inputs=[template_dropdown],
795
+ outputs=[dialog]
796
  )
797
 
798
  def handle_script_generation(*args):
 
835
  api_key,
836
  model_dropdown,
837
  api_base,
838
+ template_dropdown, # 使用模板選擇
 
 
 
 
 
839
  custom_prompt, # user_feedback
840
  num_parts_slider, # 添加滑動條參數
841
  max_input_length_slider, # 添加最大輸入文本長度參數
prompts.py CHANGED
@@ -133,7 +133,6 @@ PROMPTS = {
133
 
134
  {content}""",
135
 
136
- # 新增的摘要模板
137
  "blog-summary": """你是 David888 Podcast 中文博客的編輯,將播客內容改寫成適合搜索引擎收錄的博客文章。
138
 
139
  【工作目標】
@@ -236,40 +235,4 @@ def validate_template(template: str) -> bool:
236
  template.format(content="test")
237
  return True
238
  except (KeyError, ValueError):
239
- return False
240
-
241
-
242
- # 為了向後兼容,提供舊版本接口
243
- def get_template(template_name: str) -> dict:
244
- """
245
- 向後兼容函數:模擬舊版本的模板格式
246
-
247
- Args:
248
- template_name: 模板名稱
249
-
250
- Returns:
251
- dict: 模擬舊版本格式的模板數據
252
- """
253
- if template_name not in PROMPTS:
254
- available_templates = list(PROMPTS.keys())
255
- raise KeyError(f"模板 '{template_name}' 不存在。可用模板: {available_templates}")
256
-
257
- # 為向後兼容,將新格式轉換為舊格式
258
- prompt = PROMPTS[template_name]
259
- return {
260
- "intro": f"使用模板: {template_name}",
261
- "text_instructions": "處理輸入文本",
262
- "scratch_pad": "分析和規劃內容",
263
- "prelude": "準備生成內容",
264
- "dialog": prompt
265
- }
266
-
267
-
268
- # 舊版本兼容
269
- def get_template_names():
270
- """向後兼容的函數名稱"""
271
- return get_all_template_names()
272
-
273
-
274
- # 舊版本兼容
275
- INSTRUCTION_TEMPLATES = {name: get_template(name) for name in PROMPTS.keys()}
 
133
 
134
  {content}""",
135
 
 
136
  "blog-summary": """你是 David888 Podcast 中文博客的編輯,將播客內容改寫成適合搜索引擎收錄的博客文章。
137
 
138
  【工作目標】
 
235
  template.format(content="test")
236
  return True
237
  except (KeyError, ValueError):
238
+ return False