Spaces:
Running
Running
Update app.py
Browse files
app.py
CHANGED
@@ -607,21 +607,34 @@ class NovelWritingSystem:
|
|
607 |
|
608 |
def get_system_prompts(self, language: str) -> Dict[str, str]:
|
609 |
"""μν λ³ μμ€ν
ν둬ννΈ μμ±"""
|
610 |
-
|
611 |
-
|
612 |
-
return {
|
613 |
"director": "λΉμ μ μ°½μμ μ΄κ³ 체κ³μ μΈ μμ€ κΈ°ν μ λ¬Έκ°μ
λλ€. ν₯λ―Έλ‘κ³ μΌκ΄μ± μλ μ€ν 리λ₯Ό μ€κ³νμΈμ.",
|
614 |
"critic": "λΉμ μ μΌκ΄μ± κ²ν μ λ¬Έ λΉνκ°μ
λλ€. μΊλ¦ν°, νλ‘―, μ€μ μ μΌκ΄μ±μ μ² μ ν μ κ²νκ³ κ°μ λ°©μμ μ μνμΈμ.",
|
615 |
-
"
|
616 |
-
|
617 |
-
|
618 |
-
else:
|
619 |
-
return {
|
620 |
"director": "You are a creative and systematic novel planning expert. Design engaging and consistent stories.",
|
621 |
"critic": "You are a consistency review specialist critic. Thoroughly check character, plot, and setting consistency and suggest improvements.",
|
622 |
-
"
|
623 |
-
"writer10": "You are a writer who creates the perfect ending. Create a conclusion that leaves readers with deep resonance."
|
624 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
625 |
|
626 |
# --- λ©μΈ νλ‘μΈμ€ ---
|
627 |
def process_novel_stream(self, query: str, language: str, session_id: Optional[str] = None) -> Generator[Tuple[str, List[Dict[str, Any]], str], None, None]:
|
@@ -755,6 +768,134 @@ class NovelWritingSystem:
|
|
755 |
return "λ³΄κ³ μ μμ± μ€ μ€λ₯ λ°μ"
|
756 |
|
757 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
758 |
|
759 |
|
760 |
# CSS μ€νμΌ
|
|
|
607 |
|
608 |
def get_system_prompts(self, language: str) -> Dict[str, str]:
|
609 |
"""μν λ³ μμ€ν
ν둬ννΈ μμ±"""
|
610 |
+
base_prompts = {
|
611 |
+
"Korean": {
|
|
|
612 |
"director": "λΉμ μ μ°½μμ μ΄κ³ 체κ³μ μΈ μμ€ κΈ°ν μ λ¬Έκ°μ
λλ€. ν₯λ―Έλ‘κ³ μΌκ΄μ± μλ μ€ν 리λ₯Ό μ€κ³νμΈμ.",
|
613 |
"critic": "λΉμ μ μΌκ΄μ± κ²ν μ λ¬Έ λΉνκ°μ
λλ€. μΊλ¦ν°, νλ‘―, μ€μ μ μΌκ΄μ±μ μ² μ ν μ κ²νκ³ κ°μ λ°©μμ μ μνμΈμ.",
|
614 |
+
"writer_base": "λΉμ μ μ λ¬Έ μμ€ μκ°μ
λλ€. μ£Όμ΄μ§ μ§μΉ¨μ λ°λΌ λͺ°μ
κ° μκ³ μΌκ΄μ± μλ λ΄μ©μ μμ±νμΈμ."
|
615 |
+
},
|
616 |
+
"English": {
|
|
|
|
|
617 |
"director": "You are a creative and systematic novel planning expert. Design engaging and consistent stories.",
|
618 |
"critic": "You are a consistency review specialist critic. Thoroughly check character, plot, and setting consistency and suggest improvements.",
|
619 |
+
"writer_base": "You are a professional novel writer. Write immersive and consistent content according to the given guidelines."
|
|
|
620 |
}
|
621 |
+
}
|
622 |
+
|
623 |
+
prompts = base_prompts[language].copy()
|
624 |
+
|
625 |
+
# μκ°λ³ νΉμ ν둬ννΈ
|
626 |
+
if language == "Korean":
|
627 |
+
prompts["writer1"] = "λΉμ μ μμ€μ λ§€λ ₯μ μΈ μμμ λ΄λΉνλ μκ°μ
λλ€. λ
μλ₯Ό μ¬λ‘μ‘λ λμ
λΆλ₯Ό λ§λμΈμ."
|
628 |
+
prompts["writer10"] = "λΉμ μ μλ²½ν κ²°λ§μ λ§λλ μκ°μ
λλ€. λ
μμκ² κΉμ μ¬μ΄μ λ¨κΈ°λ λ§λ¬΄λ¦¬λ₯Ό νμΈμ."
|
629 |
+
else:
|
630 |
+
prompts["writer1"] = "You are a writer responsible for the captivating beginning. Create an opening that hooks readers."
|
631 |
+
prompts["writer10"] = "You are a writer who creates the perfect ending. Create a conclusion that leaves readers with deep resonance."
|
632 |
+
|
633 |
+
# writer2-9λ κΈ°λ³Έ ν둬ννΈ μ¬μ©
|
634 |
+
for i in range(2, 10):
|
635 |
+
prompts[f"writer{i}"] = prompts["writer_base"]
|
636 |
+
|
637 |
+
return prompts
|
638 |
|
639 |
# --- λ©μΈ νλ‘μΈμ€ ---
|
640 |
def process_novel_stream(self, query: str, language: str, session_id: Optional[str] = None) -> Generator[Tuple[str, List[Dict[str, Any]], str], None, None]:
|
|
|
768 |
return "λ³΄κ³ μ μμ± μ€ μ€λ₯ λ°μ"
|
769 |
|
770 |
|
771 |
+
# --- μ νΈλ¦¬ν° ν¨μλ€ ---
|
772 |
+
def process_query(query: str, language: str, session_id: Optional[str] = None) -> Generator[Tuple[str, str, str, str], None, None]:
|
773 |
+
"""λ©μΈ 쿼리 μ²λ¦¬ ν¨μ"""
|
774 |
+
if not query.strip():
|
775 |
+
yield "", "", "β μ£Όμ λ₯Ό μ
λ ₯ν΄μ£ΌμΈμ.", session_id
|
776 |
+
return
|
777 |
+
|
778 |
+
system = NovelWritingSystem()
|
779 |
+
stages_markdown = ""
|
780 |
+
novel_content = ""
|
781 |
+
|
782 |
+
for status, stages, current_session_id in system.process_novel_stream(query, language, session_id):
|
783 |
+
stages_markdown = format_stages_display(stages)
|
784 |
+
|
785 |
+
# μ΅μ’
μμ€ λ΄μ© κ°μ Έμ€κΈ°
|
786 |
+
if stages and all(s.get("status") == "complete" for s in stages[-10:]):
|
787 |
+
novel_content = NovelDatabase.get_writer_content(current_session_id)
|
788 |
+
novel_content = format_novel_display(novel_content)
|
789 |
+
|
790 |
+
yield stages_markdown, novel_content, status or "π μ²λ¦¬ μ€...", current_session_id
|
791 |
+
|
792 |
+
def get_active_sessions(language: str) -> List[str]:
|
793 |
+
"""νμ± μΈμ
λͺ©λ‘ κ°μ Έμ€κΈ°"""
|
794 |
+
sessions = NovelDatabase.get_active_sessions()
|
795 |
+
return [f"{s['session_id'][:8]}... - {s['user_query'][:50]}... ({s['created_at']})"
|
796 |
+
for s in sessions]
|
797 |
+
|
798 |
+
def auto_recover_session(language: str) -> Tuple[Optional[str], str]:
|
799 |
+
"""κ°μ₯ μ΅κ·Ό νμ± μΈμ
μλ 볡ꡬ"""
|
800 |
+
latest_session = NovelDatabase.get_latest_active_session()
|
801 |
+
if latest_session:
|
802 |
+
return latest_session['session_id'], f"μΈμ
{latest_session['session_id'][:8]}... 볡ꡬλ¨"
|
803 |
+
return None, "볡ꡬν μΈμ
μ΄ μμ΅λλ€."
|
804 |
+
|
805 |
+
def resume_session(session_id: str, language: str) -> Generator[Tuple[str, str, str, str], None, None]:
|
806 |
+
"""μΈμ
μ¬κ° ν¨μ"""
|
807 |
+
if not session_id:
|
808 |
+
yield "", "", "β μΈμ
IDκ° μμ΅λλ€.", session_id
|
809 |
+
return
|
810 |
+
|
811 |
+
# λλ‘λ€μ΄μμ μΈμ
ID μΆμΆ
|
812 |
+
if "..." in session_id:
|
813 |
+
session_id = session_id.split("...")[0]
|
814 |
+
|
815 |
+
session = NovelDatabase.get_session(session_id)
|
816 |
+
if not session:
|
817 |
+
yield "", "", "β μΈμ
μ μ°Ύμ μ μμ΅λλ€.", None
|
818 |
+
return
|
819 |
+
|
820 |
+
# process_queryλ₯Ό ν΅ν΄ μ¬κ°
|
821 |
+
yield from process_query(session['user_query'], session['language'], session_id)
|
822 |
+
|
823 |
+
def download_novel(novel_text: str, format_type: str, language: str, session_id: str) -> Optional[str]:
|
824 |
+
"""μμ€ λ€μ΄λ‘λ νμΌ μμ±"""
|
825 |
+
if not novel_text or not session_id:
|
826 |
+
return None
|
827 |
+
|
828 |
+
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
829 |
+
filename = f"novel_{session_id[:8]}_{timestamp}"
|
830 |
+
|
831 |
+
try:
|
832 |
+
if format_type == "DOCX" and DOCX_AVAILABLE:
|
833 |
+
return export_to_docx(novel_text, filename, language)
|
834 |
+
else:
|
835 |
+
return export_to_txt(novel_text, filename)
|
836 |
+
except Exception as e:
|
837 |
+
logger.error(f"νμΌ μμ± μ€ν¨: {e}")
|
838 |
+
return None
|
839 |
+
|
840 |
+
def format_stages_display(stages: List[Dict]) -> str:
|
841 |
+
"""λ¨κ³λ³ μ§ν μν© λ§ν¬λ€μ΄ ν¬λ§·ν
"""
|
842 |
+
markdown = "## π¬ μ§ν μν©\n\n"
|
843 |
+
for i, stage in enumerate(stages):
|
844 |
+
status_icon = "β
" if stage['status'] == 'complete' else "π" if stage['status'] == 'active' else "β³"
|
845 |
+
markdown += f"{status_icon} **{stage['name']}**"
|
846 |
+
if stage.get('consistency_score', 0) > 0:
|
847 |
+
markdown += f" (μΌκ΄μ±: {stage['consistency_score']:.1f}/10)"
|
848 |
+
markdown += "\n"
|
849 |
+
if stage['content']:
|
850 |
+
preview = stage['content'][:200] + "..." if len(stage['content']) > 200 else stage['content']
|
851 |
+
markdown += f"> {preview}\n\n"
|
852 |
+
return markdown
|
853 |
+
|
854 |
+
def format_novel_display(novel_text: str) -> str:
|
855 |
+
"""μμ€ λ΄μ© λ§ν¬λ€μ΄ ν¬λ§·ν
"""
|
856 |
+
if not novel_text:
|
857 |
+
return "μμ§ μμ±λ λ΄μ©μ΄ μμ΅λλ€."
|
858 |
+
|
859 |
+
# νμ΄μ§ κ΅¬λΆ μΆκ°
|
860 |
+
pages = novel_text.split('\n\n')
|
861 |
+
formatted = "# π μμ±λ μμ€\n\n"
|
862 |
+
|
863 |
+
for i, page in enumerate(pages):
|
864 |
+
if page.strip():
|
865 |
+
formatted += f"### νμ΄μ§ {i+1}\n\n{page}\n\n---\n\n"
|
866 |
+
|
867 |
+
return formatted
|
868 |
+
|
869 |
+
def export_to_docx(content: str, filename: str, language: str) -> str:
|
870 |
+
"""DOCX νμΌλ‘ λ΄λ³΄λ΄κΈ°"""
|
871 |
+
doc = Document()
|
872 |
+
|
873 |
+
# μ λͺ© μΆκ°
|
874 |
+
title = doc.add_heading('AI νμ
μμ€', 0)
|
875 |
+
title.alignment = WD_ALIGN_PARAGRAPH.CENTER
|
876 |
+
|
877 |
+
# λ©νλ°μ΄ν°
|
878 |
+
doc.add_paragraph(f"μμ±μΌ: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
|
879 |
+
doc.add_paragraph(f"μΈμ΄: {language}")
|
880 |
+
doc.add_page_break()
|
881 |
+
|
882 |
+
# λ³Έλ¬Έ μΆκ°
|
883 |
+
paragraphs = content.split('\n\n')
|
884 |
+
for para in paragraphs:
|
885 |
+
if para.strip():
|
886 |
+
doc.add_paragraph(para.strip())
|
887 |
+
|
888 |
+
# νμΌ μ μ₯
|
889 |
+
filepath = f"{filename}.docx"
|
890 |
+
doc.save(filepath)
|
891 |
+
return filepath
|
892 |
+
|
893 |
+
def export_to_txt(content: str, filename: str) -> str:
|
894 |
+
"""TXT νμΌλ‘ λ΄λ³΄λ΄κΈ°"""
|
895 |
+
filepath = f"{filename}.txt"
|
896 |
+
with open(filepath, 'w', encoding='utf-8') as f:
|
897 |
+
f.write(content)
|
898 |
+
return filepath
|
899 |
|
900 |
|
901 |
# CSS μ€νμΌ
|