Spaces:
Running
Running
Update app-backup.py
Browse files- app-backup.py +137 -38
app-backup.py
CHANGED
@@ -1976,6 +1976,40 @@ def auto_recover_session(language: str) -> Tuple[str, str]:
|
|
1976 |
|
1977 |
def download_novel(novel_text: str, format: str, language: str, session_id: str = None) -> str:
|
1978 |
"""Download novel - DB์์ ์ง์ ์๊ฐ ๋ด์ฉ์ ๊ฐ์ ธ์์ ํตํฉ"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1979 |
if not session_id:
|
1980 |
logger.error("No session_id provided for download")
|
1981 |
return None
|
@@ -2022,8 +2056,12 @@ def download_novel(novel_text: str, format: str, language: str, session_id: str
|
|
2022 |
|
2023 |
# ์ ๋ชฉ ํ์ด์ง
|
2024 |
title_para = doc.add_paragraph()
|
2025 |
-
|
2026 |
-
|
|
|
|
|
|
|
|
|
2027 |
title_run.font.bold = True
|
2028 |
title_para.alignment = WD_ALIGN_PARAGRAPH.CENTER
|
2029 |
|
@@ -2031,15 +2069,17 @@ def download_novel(novel_text: str, format: str, language: str, session_id: str
|
|
2031 |
doc.add_paragraph()
|
2032 |
|
2033 |
theme_para = doc.add_paragraph()
|
2034 |
-
|
2035 |
-
theme_run
|
|
|
2036 |
theme_para.alignment = WD_ALIGN_PARAGRAPH.CENTER
|
2037 |
|
2038 |
doc.add_paragraph()
|
2039 |
|
2040 |
date_para = doc.add_paragraph()
|
2041 |
-
|
2042 |
-
date_run
|
|
|
2043 |
date_para.alignment = WD_ALIGN_PARAGRAPH.CENTER
|
2044 |
|
2045 |
doc.add_page_break()
|
@@ -2148,23 +2188,42 @@ def download_novel(novel_text: str, format: str, language: str, session_id: str
|
|
2148 |
logger.info(f"Writer contents: {len(writer_contents)} entries")
|
2149 |
|
2150 |
# ํต๊ณ ํ์ด์ง
|
2151 |
-
|
2152 |
-
doc.
|
2153 |
-
|
2154 |
-
|
2155 |
-
|
2156 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2157 |
doc.add_page_break()
|
2158 |
|
2159 |
# ๋ชฉ์ฐจ
|
2160 |
if writer_contents:
|
2161 |
-
|
|
|
2162 |
# writer_contents๋ฅผ writer_num์ผ๋ก ์ ๋ ฌ
|
2163 |
sorted_contents = sorted(writer_contents, key=lambda x: x['writer_num'])
|
2164 |
|
2165 |
for item in sorted_contents:
|
2166 |
chapter_num = item['writer_num']
|
2167 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2168 |
doc.add_page_break()
|
2169 |
|
2170 |
# ๊ฐ ์๊ฐ์ ๋ด์ฉ ์ถ๊ฐ (์ ๋ ฌ๋ ์์๋๋ก)
|
@@ -2174,9 +2233,18 @@ def download_novel(novel_text: str, format: str, language: str, session_id: str
|
|
2174 |
word_count = writer_data['word_count']
|
2175 |
|
2176 |
# ์ฑํฐ ํค๋
|
2177 |
-
|
2178 |
-
doc.
|
2179 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2180 |
doc.add_paragraph()
|
2181 |
|
2182 |
# ์๊ฐ ๋ด์ฉ ์ถ๊ฐ
|
@@ -2184,22 +2252,25 @@ def download_novel(novel_text: str, format: str, language: str, session_id: str
|
|
2184 |
for para_text in paragraphs:
|
2185 |
if para_text.strip():
|
2186 |
para = doc.add_paragraph(para_text.strip())
|
2187 |
-
para.style.font.size = Pt(
|
2188 |
|
2189 |
if idx < len(sorted_contents) - 1: # ๋ง์ง๋ง ์ฑํฐ ํ์๋ ํ์ด์ง ๊ตฌ๋ถ ์์
|
2190 |
doc.add_page_break()
|
2191 |
else:
|
2192 |
logger.warning("No writer contents found! Creating empty document.")
|
2193 |
-
|
|
|
|
|
|
|
2194 |
|
2195 |
-
# ํ์ด์ง ์ค์
|
2196 |
for section in doc.sections:
|
2197 |
-
section.
|
2198 |
-
section.
|
2199 |
-
section.left_margin = Inches(
|
2200 |
-
section.right_margin = Inches(
|
2201 |
-
section.top_margin = Inches(
|
2202 |
-
section.bottom_margin = Inches(
|
2203 |
|
2204 |
# Save
|
2205 |
temp_dir = tempfile.gettempdir()
|
@@ -2221,12 +2292,22 @@ def download_novel(novel_text: str, format: str, language: str, session_id: str
|
|
2221 |
|
2222 |
with open(filepath, 'w', encoding='utf-8') as f:
|
2223 |
f.write("="*60 + "\n")
|
2224 |
-
|
|
|
|
|
|
|
2225 |
f.write("="*60 + "\n")
|
2226 |
-
|
2227 |
-
|
2228 |
-
|
2229 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2230 |
f.write("="*60 + "\n\n")
|
2231 |
|
2232 |
total_words = 0
|
@@ -2251,8 +2332,13 @@ def download_novel(novel_text: str, format: str, language: str, session_id: str
|
|
2251 |
writer_count = 1
|
2252 |
|
2253 |
f.write(f"\n{'='*40}\n")
|
2254 |
-
f
|
2255 |
-
f.write(f"
|
|
|
|
|
|
|
|
|
|
|
2256 |
f.write(f"{'='*40}\n\n")
|
2257 |
f.write(content)
|
2258 |
f.write("\n\n")
|
@@ -2273,8 +2359,13 @@ def download_novel(novel_text: str, format: str, language: str, session_id: str
|
|
2273 |
writer_count = max(writer_count, chapter_num)
|
2274 |
|
2275 |
f.write(f"\n{'='*40}\n")
|
2276 |
-
f
|
2277 |
-
f.write(f"
|
|
|
|
|
|
|
|
|
|
|
2278 |
f.write(f"{'='*40}\n\n")
|
2279 |
f.write(chapter_content)
|
2280 |
f.write("\n\n")
|
@@ -2307,14 +2398,22 @@ def download_novel(novel_text: str, format: str, language: str, session_id: str
|
|
2307 |
writer_count += 1
|
2308 |
|
2309 |
f.write(f"\n{'='*40}\n")
|
2310 |
-
f
|
2311 |
-
f.write(f"
|
|
|
|
|
|
|
|
|
|
|
2312 |
f.write(f"{'='*40}\n\n")
|
2313 |
f.write(content)
|
2314 |
f.write("\n\n")
|
2315 |
|
2316 |
f.write(f"\n{'='*60}\n")
|
2317 |
-
|
|
|
|
|
|
|
2318 |
f.write(f"{'='*60}\n")
|
2319 |
|
2320 |
logger.info(f"TXT saved successfully: {filepath} ({total_words} words)")
|
|
|
1976 |
|
1977 |
def download_novel(novel_text: str, format: str, language: str, session_id: str = None) -> str:
|
1978 |
"""Download novel - DB์์ ์ง์ ์๊ฐ ๋ด์ฉ์ ๊ฐ์ ธ์์ ํตํฉ"""
|
1979 |
+
|
1980 |
+
def extract_chapter_title(content: str, chapter_num: int, language: str) -> str:
|
1981 |
+
"""์ฑํฐ ๋ด์ฉ์์ ์ ๋ชฉ ์ถ์ถ"""
|
1982 |
+
# ๋ด์ฉ์ ์ค ๋จ์๋ก ๋ถ๋ฆฌ
|
1983 |
+
lines = content.strip().split('\n')
|
1984 |
+
|
1985 |
+
# ์ฒซ ๋ฒ์งธ ์๋ฏธ์๋ ๋ฌธ์ฅ ์ฐพ๊ธฐ (๋น ์ค ์ ์ธ)
|
1986 |
+
for line in lines[:5]: # ์ฒ์ 5์ค ๋ด์์ ์ฐพ๊ธฐ
|
1987 |
+
line = line.strip()
|
1988 |
+
if len(line) > 10: # 10์ ์ด์์ ์๋ฏธ์๋ ๋ฌธ์ฅ
|
1989 |
+
# ๋ํ๋ฌธ์ด๋ฉด ์คํต (๋ฐ์ดํ๋ก ์์ํ๋ ๊ฒฝ์ฐ)
|
1990 |
+
if line.startswith('"') or line.startswith("'") or line.startswith('"') or line.startswith("'"):
|
1991 |
+
continue
|
1992 |
+
|
1993 |
+
# ๋ง์นจํ, ๋๋ํ, ๋ฌผ์ํ๋ก ๋๋๋ ์ฒซ ๋ฌธ์ฅ๋ง ์ถ์ถ
|
1994 |
+
import re
|
1995 |
+
match = re.match(r'^[^.!?]+[.!?]', line)
|
1996 |
+
if match:
|
1997 |
+
title = match.group(0).strip()
|
1998 |
+
else:
|
1999 |
+
title = line
|
2000 |
+
|
2001 |
+
# ๋๋ฌด ๊ธธ๋ฉด ์๋ฅด๊ธฐ
|
2002 |
+
if len(title) > 50:
|
2003 |
+
title = title[:47] + "..."
|
2004 |
+
|
2005 |
+
return title
|
2006 |
+
|
2007 |
+
# ์ ์ ํ ์ ๋ชฉ์ ์ฐพ์ง ๋ชปํ ๊ฒฝ์ฐ ๊ธฐ๋ณธ ์ ๋ชฉ
|
2008 |
+
if language == "Korean":
|
2009 |
+
return f"์ {chapter_num}์ฅ"
|
2010 |
+
else:
|
2011 |
+
return f"Part {chapter_num}"
|
2012 |
+
|
2013 |
if not session_id:
|
2014 |
logger.error("No session_id provided for download")
|
2015 |
return None
|
|
|
2056 |
|
2057 |
# ์ ๋ชฉ ํ์ด์ง
|
2058 |
title_para = doc.add_paragraph()
|
2059 |
+
if actual_language == 'Korean':
|
2060 |
+
main_title = 'AI ํ์
์์ค' + (' - ํ
์คํธ ๋ชจ๋' if is_test_mode else '')
|
2061 |
+
else:
|
2062 |
+
main_title = 'AI Collaborative Novel' + (' - Test Mode' if is_test_mode else '')
|
2063 |
+
title_run = title_para.add_run(main_title)
|
2064 |
+
title_run.font.size = Pt(20) # ์ ๊ตญํ์ ๋ง๊ฒ ํฌ๊ธฐ ์กฐ์
|
2065 |
title_run.font.bold = True
|
2066 |
title_para.alignment = WD_ALIGN_PARAGRAPH.CENTER
|
2067 |
|
|
|
2069 |
doc.add_paragraph()
|
2070 |
|
2071 |
theme_para = doc.add_paragraph()
|
2072 |
+
theme_label = '์ฃผ์ : ' if actual_language == 'Korean' else 'Theme: '
|
2073 |
+
theme_run = theme_para.add_run(f'{theme_label}{session["user_query"]}')
|
2074 |
+
theme_run.font.size = Pt(12) # ํฌ๊ธฐ ์กฐ์
|
2075 |
theme_para.alignment = WD_ALIGN_PARAGRAPH.CENTER
|
2076 |
|
2077 |
doc.add_paragraph()
|
2078 |
|
2079 |
date_para = doc.add_paragraph()
|
2080 |
+
date_label = '์์ฑ์ผ: ' if actual_language == 'Korean' else 'Created: '
|
2081 |
+
date_run = date_para.add_run(f'{date_label}{datetime.now().strftime("%Y-%m-%d")}')
|
2082 |
+
date_run.font.size = Pt(10) # ํฌ๊ธฐ ์กฐ์
|
2083 |
date_para.alignment = WD_ALIGN_PARAGRAPH.CENTER
|
2084 |
|
2085 |
doc.add_page_break()
|
|
|
2188 |
logger.info(f"Writer contents: {len(writer_contents)} entries")
|
2189 |
|
2190 |
# ํต๊ณ ํ์ด์ง
|
2191 |
+
stats_heading = '์์ค ํต๊ณ' if actual_language == 'Korean' else 'Novel Statistics'
|
2192 |
+
doc.add_heading(stats_heading, 1)
|
2193 |
+
if actual_language == 'Korean':
|
2194 |
+
doc.add_paragraph(f'๋ชจ๋: {"ํ
์คํธ ๋ชจ๋ (์ค์ ์์ฑ๋ ์ฑํฐ)" if is_test_mode else "์ ์ฒด ๋ชจ๋ (10๊ฐ ์ฑํฐ)"}')
|
2195 |
+
doc.add_paragraph(f'์ด ์ฑํฐ ์: {writer_count}')
|
2196 |
+
doc.add_paragraph(f'์ด ๋จ์ด ์: {total_words:,}')
|
2197 |
+
doc.add_paragraph(f'์์ ํ์ด์ง ์: ์ฝ {total_words/300:.0f}') # ํ๊ตญ์ด๋ ํ์ด์ง๋น 300๋จ์ด ๊ธฐ์ค
|
2198 |
+
doc.add_paragraph(f'์ธ์ด: ํ๊ตญ์ด')
|
2199 |
+
else:
|
2200 |
+
doc.add_paragraph(f'Mode: {"Test Mode (Actual chapters written)" if is_test_mode else "Full Mode (10 chapters)"}')
|
2201 |
+
doc.add_paragraph(f'Total Chapters: {writer_count}')
|
2202 |
+
doc.add_paragraph(f'Total Words: {total_words:,}')
|
2203 |
+
doc.add_paragraph(f'Estimated Pages: ~{total_words/250:.0f}') # ์์ด๋ ํ์ด์ง๋น 250๋จ์ด ๊ธฐ์ค
|
2204 |
+
doc.add_paragraph(f'Language: English')
|
2205 |
doc.add_page_break()
|
2206 |
|
2207 |
# ๋ชฉ์ฐจ
|
2208 |
if writer_contents:
|
2209 |
+
toc_heading = '๋ชฉ์ฐจ' if actual_language == 'Korean' else 'Table of Contents'
|
2210 |
+
doc.add_heading(toc_heading, 1)
|
2211 |
# writer_contents๋ฅผ writer_num์ผ๋ก ์ ๋ ฌ
|
2212 |
sorted_contents = sorted(writer_contents, key=lambda x: x['writer_num'])
|
2213 |
|
2214 |
for item in sorted_contents:
|
2215 |
chapter_num = item['writer_num']
|
2216 |
+
content = item['content']
|
2217 |
+
|
2218 |
+
# ์ฑํฐ ์ ๋ชฉ ์ถ์ถ - ์ฒซ ๋ฒ์งธ ์๋ฏธ์๋ ๋ฌธ์ฅ ๋๋ ๋จ๋ฝ
|
2219 |
+
chapter_title = extract_chapter_title(content, chapter_num, actual_language)
|
2220 |
+
|
2221 |
+
# ๋ชฉ์ฐจ ํญ๋ชฉ ์ถ๊ฐ (ํ์ด์ง ๋ฒํธ ์์ด)
|
2222 |
+
if actual_language == 'Korean':
|
2223 |
+
toc_entry = f"์ {chapter_num}์ฅ: {chapter_title}"
|
2224 |
+
else:
|
2225 |
+
toc_entry = f"Chapter {chapter_num}: {chapter_title}"
|
2226 |
+
doc.add_paragraph(toc_entry)
|
2227 |
doc.add_page_break()
|
2228 |
|
2229 |
# ๊ฐ ์๊ฐ์ ๋ด์ฉ ์ถ๊ฐ (์ ๋ ฌ๋ ์์๋๋ก)
|
|
|
2233 |
word_count = writer_data['word_count']
|
2234 |
|
2235 |
# ์ฑํฐ ํค๋
|
2236 |
+
chapter_header = f'์ {writer_num}์ฅ' if actual_language == 'Korean' else f'Chapter {writer_num}'
|
2237 |
+
doc.add_heading(chapter_header, 1)
|
2238 |
+
|
2239 |
+
# ์ฑํฐ ์ ๋ชฉ ์ถ๊ฐ
|
2240 |
+
chapter_title = extract_chapter_title(content, writer_num, actual_language)
|
2241 |
+
title_para = doc.add_paragraph(chapter_title)
|
2242 |
+
title_para.style.font.size = Pt(14)
|
2243 |
+
title_para.style.font.bold = True
|
2244 |
+
title_para.alignment = WD_ALIGN_PARAGRAPH.CENTER
|
2245 |
+
|
2246 |
+
word_count_label = f'๋จ์ด ์: {word_count:,}' if actual_language == 'Korean' else f'Word Count: {word_count:,}'
|
2247 |
+
doc.add_paragraph(word_count_label)
|
2248 |
doc.add_paragraph()
|
2249 |
|
2250 |
# ์๊ฐ ๋ด์ฉ ์ถ๊ฐ
|
|
|
2252 |
for para_text in paragraphs:
|
2253 |
if para_text.strip():
|
2254 |
para = doc.add_paragraph(para_text.strip())
|
2255 |
+
para.style.font.size = Pt(10.5) # ํ๊ตญ ์์ค ํ์ค ํฌ๊ธฐ
|
2256 |
|
2257 |
if idx < len(sorted_contents) - 1: # ๋ง์ง๋ง ์ฑํฐ ํ์๋ ํ์ด์ง ๊ตฌ๋ถ ์์
|
2258 |
doc.add_page_break()
|
2259 |
else:
|
2260 |
logger.warning("No writer contents found! Creating empty document.")
|
2261 |
+
if actual_language == 'Korean':
|
2262 |
+
doc.add_paragraph("๋ด์ฉ์ ์ฐพ์ ์ ์์ต๋๋ค. ์์ค ์์ฑ์ด ์ ์์ ์ผ๋ก ์๋ฃ๋์๋์ง ํ์ธํด์ฃผ์ธ์.")
|
2263 |
+
else:
|
2264 |
+
doc.add_paragraph("No content found. Please check if the novel generation completed successfully.")
|
2265 |
|
2266 |
+
# ํ์ด์ง ์ค์ - ํ๊ตญ ์ ๊ตญํ (152 ร 225mm)
|
2267 |
for section in doc.sections:
|
2268 |
+
section.page_width = Inches(5.98) # 152mm
|
2269 |
+
section.page_height = Inches(8.86) # 225mm
|
2270 |
+
section.left_margin = Inches(0.79) # 20mm
|
2271 |
+
section.right_margin = Inches(0.79) # 20mm
|
2272 |
+
section.top_margin = Inches(0.79) # 20mm
|
2273 |
+
section.bottom_margin = Inches(0.79) # 20mm
|
2274 |
|
2275 |
# Save
|
2276 |
temp_dir = tempfile.gettempdir()
|
|
|
2292 |
|
2293 |
with open(filepath, 'w', encoding='utf-8') as f:
|
2294 |
f.write("="*60 + "\n")
|
2295 |
+
if actual_language == 'Korean':
|
2296 |
+
f.write(f"AI ํ์
์์ค - {'ํ
์คํธ ๋ชจ๋' if is_test_mode else '์์ฑ๋ณธ'}\n")
|
2297 |
+
else:
|
2298 |
+
f.write(f"AI COLLABORATIVE NOVEL - {'TEST MODE' if is_test_mode else 'COMPLETE VERSION'}\n")
|
2299 |
f.write("="*60 + "\n")
|
2300 |
+
|
2301 |
+
if actual_language == 'Korean':
|
2302 |
+
f.write(f"์ฃผ์ : {session['user_query']}\n")
|
2303 |
+
f.write(f"์ธ์ด: ํ๊ตญ์ด\n")
|
2304 |
+
f.write(f"์์ฑ์ผ: {datetime.now()}\n")
|
2305 |
+
f.write(f"๋ชจ๋: {'ํ
์คํธ ๋ชจ๋ (์ค์ ์์ฑ๋ ์ฑํฐ)' if is_test_mode else '์ ์ฒด ๋ชจ๋ (10๊ฐ ์ฑํฐ)'}\n")
|
2306 |
+
else:
|
2307 |
+
f.write(f"Theme: {session['user_query']}\n")
|
2308 |
+
f.write(f"Language: English\n")
|
2309 |
+
f.write(f"Created: {datetime.now()}\n")
|
2310 |
+
f.write(f"Mode: {'Test Mode (Actual chapters)' if is_test_mode else 'Full Mode (10 chapters)'}\n")
|
2311 |
f.write("="*60 + "\n\n")
|
2312 |
|
2313 |
total_words = 0
|
|
|
2332 |
writer_count = 1
|
2333 |
|
2334 |
f.write(f"\n{'='*40}\n")
|
2335 |
+
chapter_label = f"์ 1์ฅ" if actual_language == 'Korean' else "CHAPTER 1"
|
2336 |
+
f.write(f"{chapter_label}\n")
|
2337 |
+
# ์ฑํฐ ์ ๋ชฉ ์ถ์ถ ๋ฐ ์ถ๋ ฅ
|
2338 |
+
chapter_title = extract_chapter_title(content, 1, actual_language)
|
2339 |
+
f.write(f"{chapter_title}\n")
|
2340 |
+
word_count_label = f"๋จ์ด ์: {word_count:,}" if actual_language == 'Korean' else f"Word Count: {word_count:,}"
|
2341 |
+
f.write(f"{word_count_label}\n")
|
2342 |
f.write(f"{'='*40}\n\n")
|
2343 |
f.write(content)
|
2344 |
f.write("\n\n")
|
|
|
2359 |
writer_count = max(writer_count, chapter_num)
|
2360 |
|
2361 |
f.write(f"\n{'='*40}\n")
|
2362 |
+
chapter_label = f"์ {chapter_num}์ฅ" if actual_language == 'Korean' else f"CHAPTER {chapter_num}"
|
2363 |
+
f.write(f"{chapter_label}\n")
|
2364 |
+
# ์ฑํฐ ์ ๋ชฉ ์ถ์ถ ๋ฐ ์ถ๋ ฅ
|
2365 |
+
chapter_title = extract_chapter_title(chapter_content, chapter_num, actual_language)
|
2366 |
+
f.write(f"{chapter_title}\n")
|
2367 |
+
word_count_label = f"๋จ์ด ์: {word_count:,}" if actual_language == 'Korean' else f"Word Count: {word_count:,}"
|
2368 |
+
f.write(f"{word_count_label}\n")
|
2369 |
f.write(f"{'='*40}\n\n")
|
2370 |
f.write(chapter_content)
|
2371 |
f.write("\n\n")
|
|
|
2398 |
writer_count += 1
|
2399 |
|
2400 |
f.write(f"\n{'='*40}\n")
|
2401 |
+
chapter_label = f"์ {writer_num}์ฅ" if actual_language == 'Korean' else f"CHAPTER {writer_num}"
|
2402 |
+
f.write(f"{chapter_label}\n")
|
2403 |
+
# ์ฑํฐ ์ ๋ชฉ ์ถ์ถ ๋ฐ ์ถ๋ ฅ
|
2404 |
+
chapter_title = extract_chapter_title(content, writer_num, actual_language)
|
2405 |
+
f.write(f"{chapter_title}\n")
|
2406 |
+
word_count_label = f"๋จ์ด ์: {word_count:,}" if actual_language == 'Korean' else f"Word Count: {word_count:,}"
|
2407 |
+
f.write(f"{word_count_label}\n")
|
2408 |
f.write(f"{'='*40}\n\n")
|
2409 |
f.write(content)
|
2410 |
f.write("\n\n")
|
2411 |
|
2412 |
f.write(f"\n{'='*60}\n")
|
2413 |
+
if actual_language == 'Korean':
|
2414 |
+
f.write(f"์ด๊ณ: {writer_count}๊ฐ ์ฑํฐ, {total_words:,} ๋จ์ด\n")
|
2415 |
+
else:
|
2416 |
+
f.write(f"TOTAL: {writer_count} chapters, {total_words:,} words\n")
|
2417 |
f.write(f"{'='*60}\n")
|
2418 |
|
2419 |
logger.info(f"TXT saved successfully: {filepath} ({total_words} words)")
|