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 ์คํ์ผ
|