openfree commited on
Commit
2c7be3c
ยท
verified ยท
1 Parent(s): d72cd5f

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +143 -65
app.py CHANGED
@@ -894,10 +894,12 @@ def export_to_docx(episodes: List[Dict], genre: str, title: str = "") -> bytes:
894
  doc.save(bytes_io)
895
  bytes_io.seek(0)
896
  return bytes_io.getvalue()
 
 
897
  def generate_random_webnovel_theme(genre: str, language: str) -> str:
898
  """Generate random web novel theme using novel_themes.json and LLM"""
899
  try:
900
- # Load novel_themes.json with error handling
901
  json_path = Path("novel_themes.json")
902
  if not json_path.exists():
903
  logger.warning("novel_themes.json not found, using fallback")
@@ -906,93 +908,165 @@ def generate_random_webnovel_theme(genre: str, language: str) -> str:
906
  try:
907
  with open(json_path, 'r', encoding='utf-8') as f:
908
  content = f.read()
909
- # Try to fix common JSON errors
910
- content = content.replace("'", '"') # Replace single quotes with double quotes
911
- content = re.sub(r',\s*}', '}', content) # Remove trailing commas before }
912
- content = re.sub(r',\s*]', ']', content) # Remove trailing commas before ]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
913
  themes_data = json.loads(content)
 
 
914
  except json.JSONDecodeError as e:
915
- logger.error(f"JSON parsing error: {e}")
916
- logger.error(f"Error at position: {e.pos if hasattr(e, 'pos') else 'unknown'}")
917
- # If JSON parsing fails, use LLM with genre-specific prompt
 
 
 
 
918
  return generate_theme_with_llm_only(genre, language)
919
 
920
- # Map genres to theme data
921
  genre_mapping = {
922
- "๋กœ๋งจ์Šค": ["romance_fantasy_villainess", "villainess_wants_to_be_lazy", "chaebol_family_intrigue"],
923
- "๋กœํŒ": ["romance_fantasy_villainess", "BL_novel_transmigration", "regression_childcare"],
924
- "ํŒํƒ€์ง€": ["system_constellation_hunter", "tower_ascension_challenger", "necromancer_solo_leveling"],
925
- "ํ˜„ํŒ": ["system_constellation_hunter", "chaebol_family_intrigue", "post_apocalypse_survival"],
926
- "๋ฌดํ˜‘": ["regression_revenge_pro", "necromancer_solo_leveling"],
927
- "๋ฏธ์Šคํ„ฐ๋ฆฌ": ["post_apocalypse_survival", "tower_ascension_challenger"],
928
- "๋ผ์ดํŠธ๋…ธ๋ฒจ": ["BL_novel_transmigration", "villainess_wants_to_be_lazy"]
929
  }
930
 
931
  # Get relevant core genres for selected genre
932
  relevant_genres = genre_mapping.get(genre, ["regression_revenge_pro"])
933
- selected_genre_key = random.choice(relevant_genres)
934
 
935
- # Get genre data
936
- core_genre = themes_data["core_genres"].get(selected_genre_key, {})
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
937
  compatible_elements = core_genre.get("compatible_elements", {})
938
 
939
- # Select random elements
940
  character_keys = compatible_elements.get("characters", [])
 
 
 
 
 
941
  selected_character_key = random.choice(character_keys) if character_keys else "betrayed_protagonist"
942
 
943
- # Get character variations
944
- character_data = themes_data["characters"].get(selected_character_key, {})
 
945
  character_variations = character_data.get("variations", [])
946
- character_desc = random.choice(character_variations) if character_variations else ""
947
- character_traits = character_data.get("traits", [])
948
 
949
- # Get settings
 
 
 
 
 
 
950
  settings = compatible_elements.get("settings", [])
951
- selected_setting = random.choice(settings) if settings else ""
 
 
 
 
 
 
952
 
953
- # Get all available settings for more variety
954
- all_settings = themes_data.get("settings", {})
955
- setting_details = []
956
- for setting_list in all_settings.values():
957
- setting_details.extend(setting_list)
958
- specific_setting = random.choice(setting_details) if setting_details else selected_setting
959
 
960
- # Get mechanics
961
- mechanics_keys = list(themes_data.get("core_mechanics", {}).keys())
962
- selected_mechanic = random.choice(mechanics_keys) if mechanics_keys else ""
963
- mechanic_data = themes_data["core_mechanics"].get(selected_mechanic, {})
964
- plot_points = mechanic_data.get("plot_points", [])
965
- reader_questions = mechanic_data.get("reader_questions", [])
966
 
967
- # Get hooks
968
- hook_types = list(themes_data.get("episode_hooks", {}).keys())
 
 
 
 
 
 
 
 
 
969
  selected_hook_type = random.choice(hook_types) if hook_types else "introduction"
970
- hooks = themes_data["episode_hooks"].get(selected_hook_type, [])
971
- selected_hook = random.choice(hooks) if hooks else ""
972
 
973
- # Get items/artifacts
974
- item_categories = list(themes_data.get("key_items_and_artifacts", {}).keys())
975
- if item_categories and genre in ["ํŒํƒ€์ง€", "ํ˜„ํŒ", "๋ฌดํ˜‘"]:
976
- selected_category = random.choice(item_categories)
977
- items = themes_data["key_items_and_artifacts"].get(selected_category, [])
978
- selected_item = random.choice(items) if items else ""
979
- else:
980
- selected_item = ""
 
 
 
 
 
 
981
 
982
- # Get plot twists
983
- twist_categories = list(themes_data.get("plot_twists_and_cliches", {}).keys())
 
 
984
  if twist_categories:
985
  selected_twist_cat = random.choice(twist_categories)
986
- twists = themes_data["plot_twists_and_cliches"].get(selected_twist_cat, [])
987
- selected_twist = random.choice(twists) if twists else ""
988
- else:
989
- selected_twist = ""
990
 
991
  # Check for fusion genres
992
  fusion_genres = themes_data.get("fusion_genres", {})
993
- fusion_options = list(fusion_genres.values())
994
  selected_fusion = random.choice(fusion_options) if fusion_options and random.random() > 0.7 else ""
995
 
 
 
 
996
  # Now use LLM to create a coherent theme from these elements
997
  system = WebNovelSystem()
998
 
@@ -1001,11 +1075,11 @@ def generate_random_webnovel_theme(genre: str, language: str) -> str:
1001
  prompt = f"""๋‹ค์Œ ์š”์†Œ๋“ค์„ ํ™œ์šฉํ•˜์—ฌ {genre} ์žฅ๋ฅด์˜ ๋งค๋ ฅ์ ์ธ ์›น์†Œ์„ค์„ ๊ธฐํšํ•˜์„ธ์š”:
1002
 
1003
  ใ€์„ ํƒ๋œ ์š”์†Œ๋“คใ€‘
1004
- - ํ•ต์‹ฌ ์žฅ๋ฅด: {selected_genre_key}
1005
  - ์บ๋ฆญํ„ฐ: {character_desc}
1006
  - ์บ๋ฆญํ„ฐ ํŠน์„ฑ: {', '.join(character_traits[:3])}
1007
- - ๋ฐฐ๊ฒฝ: {specific_setting}
1008
- - ํ•ต์‹ฌ ๋ฉ”์ปค๋‹ˆ์ฆ˜: {selected_mechanic}
1009
  {"- ์•„์ดํ…œ: " + selected_item if selected_item else ""}
1010
  {"- ๋ฐ˜์ „ ์š”์†Œ: " + selected_twist if selected_twist else ""}
1011
  {"- ํ“จ์ „ ์„ค์ •: " + selected_fusion if selected_fusion else ""}
@@ -1014,7 +1088,7 @@ def generate_random_webnovel_theme(genre: str, language: str) -> str:
1014
  {selected_hook}
1015
 
1016
  ใ€๋…์ž๋ฅผ ์‚ฌ๋กœ์žก์„ ์งˆ๋ฌธ๋“คใ€‘
1017
- {chr(10).join(reader_questions[:2]) if reader_questions else ""}
1018
 
1019
  ๋‹ค์Œ ํ˜•์‹์œผ๋กœ ์ •ํ™•ํžˆ ์ž‘์„ฑํ•˜์„ธ์š”:
1020
 
@@ -1036,11 +1110,11 @@ def generate_random_webnovel_theme(genre: str, language: str) -> str:
1036
  prompt = f"""Create an engaging web novel for {genre} genre using these elements:
1037
 
1038
  ใ€Selected Elementsใ€‘
1039
- - Core genre: {selected_genre_key}
1040
  - Character: {character_desc}
1041
  - Character traits: {', '.join(character_traits[:3])}
1042
- - Setting: {specific_setting}
1043
- - Core mechanism: {selected_mechanic}
1044
  {"- Item: " + selected_item if selected_item else ""}
1045
  {"- Twist: " + selected_twist if selected_twist else ""}
1046
  {"- Fusion: " + selected_fusion if selected_fusion else ""}
@@ -1048,6 +1122,9 @@ def generate_random_webnovel_theme(genre: str, language: str) -> str:
1048
  ใ€Reference Hookใ€‘
1049
  {selected_hook}
1050
 
 
 
 
1051
  Format exactly as follows:
1052
 
1053
  ๐Ÿ“– **Title:**
@@ -1068,10 +1145,11 @@ Format exactly as follows:
1068
  messages = [{"role": "user", "content": prompt}]
1069
  generated_theme = system.call_llm_sync(messages, "writer", language)
1070
 
 
1071
  return generated_theme
1072
 
1073
  except Exception as e:
1074
- logger.error(f"Error generating theme from JSON: {e}")
1075
  return generate_fallback_theme(genre, language)
1076
 
1077
  def generate_fallback_theme(genre: str, language: str) -> str:
 
894
  doc.save(bytes_io)
895
  bytes_io.seek(0)
896
  return bytes_io.getvalue()
897
+
898
+
899
  def generate_random_webnovel_theme(genre: str, language: str) -> str:
900
  """Generate random web novel theme using novel_themes.json and LLM"""
901
  try:
902
+ # Load novel_themes.json with better error handling
903
  json_path = Path("novel_themes.json")
904
  if not json_path.exists():
905
  logger.warning("novel_themes.json not found, using fallback")
 
908
  try:
909
  with open(json_path, 'r', encoding='utf-8') as f:
910
  content = f.read()
911
+
912
+ # Remove comments from JSON (/* */ style)
913
+ content = re.sub(r'/\*.*?\*/', '', content, flags=re.DOTALL)
914
+ # Remove single line comments (// style)
915
+ content = re.sub(r'//.*$', '', content, flags=re.MULTILINE)
916
+ # Remove trailing commas before } or ]
917
+ content = re.sub(r',\s*([}\]])', r'\1', content)
918
+
919
+ # Handle all variations of placeholder patterns
920
+ content = re.sub(r'\.\.\.\s*\(๊ธฐ์กด.*?\)\s*\.\.\.', '[]', content)
921
+ content = re.sub(r'\.\.\.\(๊ธฐ์กด.*?\)\.\.\.', '[]', content)
922
+ content = re.sub(r'\{ \.\.\. \(๊ธฐ์กด ๊ทธ๋Œ€๋กœ\) \}', '{}', content)
923
+ content = re.sub(r'\{\s*\.\.\.\s*\(๊ธฐ์กด ๊ทธ๋Œ€๋กœ\)\s*\}', '{}', content)
924
+ content = re.sub(r'\{ \.\.\. \}', '{}', content)
925
+ content = re.sub(r'\{\s*\.\.\.\s*\}', '{}', content)
926
+ content = re.sub(r'\[ \.\.\. \]', '[]', content)
927
+ content = re.sub(r'\[\s*\.\.\.\s*\]', '[]', content)
928
+
929
+ # Handle ellipsis in strings
930
+ content = re.sub(r'"[^"]*\.\.\.[^"]*"', '""', content)
931
+
932
+ # Debug: save cleaned JSON for inspection
933
+ with open('novel_themes_cleaned.json', 'w', encoding='utf-8') as debug_file:
934
+ debug_file.write(content)
935
+
936
+ # Parse JSON
937
  themes_data = json.loads(content)
938
+ logger.info("Successfully parsed novel_themes.json")
939
+
940
  except json.JSONDecodeError as e:
941
+ logger.error(f"JSON parsing error at line {e.lineno}, column {e.colno}: {e.msg}")
942
+ if hasattr(e, 'pos'):
943
+ error_context = content[max(0, e.pos-100):e.pos+100]
944
+ logger.error(f"Context around error: ...{error_context}...")
945
+ # Save problematic content for debugging
946
+ with open('novel_themes_error.json', 'w', encoding='utf-8') as error_file:
947
+ error_file.write(content)
948
  return generate_theme_with_llm_only(genre, language)
949
 
950
+ # Map genres to theme data - updated mapping
951
  genre_mapping = {
952
+ "๋กœ๋งจ์Šค": ["romance_fantasy_villainess", "villainess_wants_to_be_lazy", "office_romance_rivals", "chaebol_family_intrigue"],
953
+ "๋กœํŒ": ["romance_fantasy_villainess", "BL_novel_transmigration", "regression_childcare", "saeguk_court_intrigue"],
954
+ "ํŒํƒ€์ง€": ["system_constellation_hunter", "tower_ascension_challenger", "necromancer_solo_leveling", "ai_dungeon_masters"],
955
+ "ํ˜„ํŒ": ["system_constellation_hunter", "chaebol_family_intrigue", "post_apocalypse_survival", "esports_king_prodigy", "vr_streamer_ranker"],
956
+ "๋ฌดํ˜‘": ["regression_revenge_pro", "necromancer_solo_leveling", "exorcist_k_cult"],
957
+ "๏ฟฝ๏ฟฝ๏ฟฝ์Šคํ„ฐ๋ฆฌ": ["post_apocalypse_survival", "tower_ascension_challenger", "survival_reality_show"],
958
+ "๋ผ์ดํŠธ๋…ธ๋ฒจ": ["BL_novel_transmigration", "villainess_wants_to_be_lazy", "vr_streamer_ranker", "healing_cafe_fantasy", "idol_regression_superstar"]
959
  }
960
 
961
  # Get relevant core genres for selected genre
962
  relevant_genres = genre_mapping.get(genre, ["regression_revenge_pro"])
 
963
 
964
+ # Filter out genres that might not exist in the JSON
965
+ available_genres = []
966
+ core_genres = themes_data.get("core_genres", {})
967
+
968
+ # Debug log available genres
969
+ logger.debug(f"Available core genres: {list(core_genres.keys())}")
970
+
971
+ for genre_key in relevant_genres:
972
+ if genre_key in core_genres:
973
+ available_genres.append(genre_key)
974
+
975
+ if not available_genres:
976
+ logger.warning(f"No matching genres found for {genre}, available: {list(core_genres.keys())[:5]}...")
977
+ # Try to use any available genre
978
+ available_genres = list(core_genres.keys())[:3]
979
+
980
+ selected_genre_key = random.choice(available_genres)
981
+ logger.debug(f"Selected genre key: {selected_genre_key}")
982
+
983
+ # Get genre data safely
984
+ core_genre = core_genres.get(selected_genre_key, {})
985
  compatible_elements = core_genre.get("compatible_elements", {})
986
 
987
+ # Select random elements with fallbacks
988
  character_keys = compatible_elements.get("characters", [])
989
+ if not character_keys:
990
+ # Get any available characters
991
+ all_characters = list(themes_data.get("characters", {}).keys())
992
+ character_keys = all_characters[:4] if all_characters else ["betrayed_protagonist"]
993
+
994
  selected_character_key = random.choice(character_keys) if character_keys else "betrayed_protagonist"
995
 
996
+ # Get character data safely
997
+ characters_data = themes_data.get("characters", {})
998
+ character_data = characters_data.get(selected_character_key, {})
999
  character_variations = character_data.get("variations", [])
 
 
1000
 
1001
+ # Filter out empty or placeholder variations
1002
+ valid_variations = [v for v in character_variations if v and not v.startswith("...") and len(v) > 10]
1003
+ character_desc = random.choice(valid_variations) if valid_variations else "์ฃผ์ธ๊ณต์€ ํŠน๋ณ„ํ•œ ์šด๋ช…์„ ํƒ€๊ณ ๋‚ฌ๋‹ค."
1004
+
1005
+ character_traits = character_data.get("traits", ["๊ฒฐ๋‹จ๋ ฅ", "์„ฑ์žฅํ˜•", "๋งค๋ ฅ์ "])
1006
+
1007
+ # Get settings safely
1008
  settings = compatible_elements.get("settings", [])
1009
+ if not settings:
1010
+ # Try to get from general settings
1011
+ all_settings_categories = themes_data.get("settings", {})
1012
+ for category_name, category_settings in all_settings_categories.items():
1013
+ if isinstance(category_settings, list):
1014
+ valid_settings = [s for s in category_settings if s and not s.startswith("...") and len(s) > 5]
1015
+ settings.extend(valid_settings)
1016
 
1017
+ selected_setting = random.choice(settings) if settings else "ํ˜„๋Œ€ ๋„์‹œ"
 
 
 
 
 
1018
 
1019
+ # Get mechanics safely
1020
+ mechanics_data = themes_data.get("core_mechanics", {})
1021
+ mechanics_keys = [k for k in mechanics_data.keys() if k]
1022
+ selected_mechanic = random.choice(mechanics_keys) if mechanics_keys else "regression_loop_mastery"
 
 
1023
 
1024
+ mechanic_info = mechanics_data.get(selected_mechanic, {})
1025
+ plot_points = mechanic_info.get("plot_points", [])
1026
+ reader_questions = mechanic_info.get("reader_questions", [])
1027
+
1028
+ # Filter valid plot points and questions
1029
+ valid_plot_points = [p for p in plot_points if p and not p.startswith("...") and len(p) > 10]
1030
+ valid_questions = [q for q in reader_questions if q and not q.startswith("...") and len(q) > 10]
1031
+
1032
+ # Get hooks safely
1033
+ hooks_data = themes_data.get("episode_hooks", {})
1034
+ hook_types = list(hooks_data.keys())
1035
  selected_hook_type = random.choice(hook_types) if hook_types else "introduction"
 
 
1036
 
1037
+ hooks = hooks_data.get(selected_hook_type, [])
1038
+ valid_hooks = [h for h in hooks if h and not h.startswith("...") and len(h) > 10]
1039
+ selected_hook = random.choice(valid_hooks) if valid_hooks else "์šด๋ช…์ ์ธ ๋งŒ๋‚จ์ด ์‹œ์ž‘๋˜์—ˆ๋‹ค."
1040
+
1041
+ # Get items/artifacts for certain genres
1042
+ selected_item = ""
1043
+ if genre in ["ํŒํƒ€์ง€", "ํ˜„ํŒ", "๋ฌดํ˜‘"]:
1044
+ items_data = themes_data.get("key_items_and_artifacts", {})
1045
+ item_categories = list(items_data.keys())
1046
+ if item_categories:
1047
+ selected_category = random.choice(item_categories)
1048
+ items = items_data.get(selected_category, [])
1049
+ valid_items = [i for i in items if i and not i.startswith("...") and len(i) > 10]
1050
+ selected_item = random.choice(valid_items) if valid_items else ""
1051
 
1052
+ # Get plot twists safely
1053
+ twists_data = themes_data.get("plot_twists_and_cliches", {})
1054
+ twist_categories = list(twists_data.keys())
1055
+ selected_twist = ""
1056
  if twist_categories:
1057
  selected_twist_cat = random.choice(twist_categories)
1058
+ twists = twists_data.get(selected_twist_cat, [])
1059
+ valid_twists = [t for t in twists if t and not t.startswith("...") and len(t) > 10]
1060
+ selected_twist = random.choice(valid_twists) if valid_twists else ""
 
1061
 
1062
  # Check for fusion genres
1063
  fusion_genres = themes_data.get("fusion_genres", {})
1064
+ fusion_options = [v for v in fusion_genres.values() if v and not v.startswith("...") and len(v) > 10]
1065
  selected_fusion = random.choice(fusion_options) if fusion_options and random.random() > 0.7 else ""
1066
 
1067
+ # Log selected elements for debugging
1068
+ logger.debug(f"Selected elements - Genre: {selected_genre_key}, Character: {selected_character_key}, Mechanic: {selected_mechanic}")
1069
+
1070
  # Now use LLM to create a coherent theme from these elements
1071
  system = WebNovelSystem()
1072
 
 
1075
  prompt = f"""๋‹ค์Œ ์š”์†Œ๋“ค์„ ํ™œ์šฉํ•˜์—ฌ {genre} ์žฅ๋ฅด์˜ ๋งค๋ ฅ์ ์ธ ์›น์†Œ์„ค์„ ๊ธฐํšํ•˜์„ธ์š”:
1076
 
1077
  ใ€์„ ํƒ๋œ ์š”์†Œ๋“คใ€‘
1078
+ - ํ•ต์‹ฌ ์žฅ๋ฅด: {selected_genre_key.replace('_', ' ')}
1079
  - ์บ๋ฆญํ„ฐ: {character_desc}
1080
  - ์บ๋ฆญํ„ฐ ํŠน์„ฑ: {', '.join(character_traits[:3])}
1081
+ - ๋ฐฐ๊ฒฝ: {selected_setting}
1082
+ - ํ•ต์‹ฌ ๋ฉ”์ปค๋‹ˆ์ฆ˜: {selected_mechanic.replace('_', ' ')}
1083
  {"- ์•„์ดํ…œ: " + selected_item if selected_item else ""}
1084
  {"- ๋ฐ˜์ „ ์š”์†Œ: " + selected_twist if selected_twist else ""}
1085
  {"- ํ“จ์ „ ์„ค์ •: " + selected_fusion if selected_fusion else ""}
 
1088
  {selected_hook}
1089
 
1090
  ใ€๋…์ž๋ฅผ ์‚ฌ๋กœ์žก์„ ์งˆ๋ฌธ๋“คใ€‘
1091
+ {chr(10).join(valid_questions[:2]) if valid_questions else "๋…์ž์˜ ํ˜ธ๊ธฐ์‹ฌ์„ ์ž๊ทนํ•˜๋Š” ์งˆ๋ฌธ๋“ค"}
1092
 
1093
  ๋‹ค์Œ ํ˜•์‹์œผ๋กœ ์ •ํ™•ํžˆ ์ž‘์„ฑํ•˜์„ธ์š”:
1094
 
 
1110
  prompt = f"""Create an engaging web novel for {genre} genre using these elements:
1111
 
1112
  ใ€Selected Elementsใ€‘
1113
+ - Core genre: {selected_genre_key.replace('_', ' ')}
1114
  - Character: {character_desc}
1115
  - Character traits: {', '.join(character_traits[:3])}
1116
+ - Setting: {selected_setting}
1117
+ - Core mechanism: {selected_mechanic.replace('_', ' ')}
1118
  {"- Item: " + selected_item if selected_item else ""}
1119
  {"- Twist: " + selected_twist if selected_twist else ""}
1120
  {"- Fusion: " + selected_fusion if selected_fusion else ""}
 
1122
  ใ€Reference Hookใ€‘
1123
  {selected_hook}
1124
 
1125
+ ใ€Questions to captivate readersใ€‘
1126
+ {chr(10).join(valid_questions[:2]) if valid_questions else "Questions that spark reader curiosity"}
1127
+
1128
  Format exactly as follows:
1129
 
1130
  ๐Ÿ“– **Title:**
 
1145
  messages = [{"role": "user", "content": prompt}]
1146
  generated_theme = system.call_llm_sync(messages, "writer", language)
1147
 
1148
+ logger.info("Successfully generated theme using JSON elements")
1149
  return generated_theme
1150
 
1151
  except Exception as e:
1152
+ logger.error(f"Error generating theme from JSON: {e}", exc_info=True)
1153
  return generate_fallback_theme(genre, language)
1154
 
1155
  def generate_fallback_theme(genre: str, language: str) -> str: