openfree commited on
Commit
ed78183
Β·
verified Β·
1 Parent(s): aa177b7

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +522 -179
app.py CHANGED
@@ -53,13 +53,13 @@ db_lock = threading.Lock()
53
  # μ΅œμ ν™”λœ 단계 ꡬ성 (25λ‹¨κ³„λ‘œ μ••μΆ• 및 κ°•ν™”)
54
  OPTIMIZED_STAGES = [
55
  ("director", "🎬 κ°λ…μž: 초기 기획 (μ›Ή 검색 포함)"),
56
- ("critic", "πŸ“ 비평가: 기획 κ²€ν†  (ν…Œλ§ˆ 및 일관성)"),
57
  ("director", "🎬 κ°λ…μž: μˆ˜μ •λœ λ§ˆμŠ€ν„°ν”Œλžœ"),
58
  ] + [
59
  (f"writer{i}", f"✍️ μž‘κ°€ {i}: μ΄ˆμ•ˆ (νŽ˜μ΄μ§€ {(i-1)*3+1}-{i*3})")
60
  for i in range(1, 11)
61
  ] + [
62
- ("critic", "πŸ“ 비평가: 쀑간 κ²€ν†  (일관성 및 ν…Œλ§ˆ μœ μ§€)"),
63
  ] + [
64
  (f"writer{i}", f"✍️ μž‘κ°€ {i}: μˆ˜μ •λ³Έ (νŽ˜μ΄μ§€ {(i-1)*3+1}-{i*3})")
65
  for i in range(1, 11)
@@ -81,6 +81,7 @@ class CharacterState:
81
  last_seen_chapter: int = 0
82
  description: str = ""
83
  role: str = ""
 
84
 
85
  @dataclass
86
  class PlotPoint:
@@ -91,6 +92,7 @@ class PlotPoint:
91
  characters_involved: List[str]
92
  impact_level: int
93
  timestamp: str = ""
 
94
 
95
  @dataclass
96
  class TimelineEvent:
@@ -112,6 +114,7 @@ class ConsistencyTracker:
112
  self.locations: Dict[str, str] = {}
113
  self.established_facts: List[str] = []
114
  self.content_hashes: Dict[str, int] = {} # ν•΄μ‹œμ™€ ν•΄λ‹Ή 챕터 번호λ₯Ό μ €μž₯
 
115
 
116
  def register_character(self, character: CharacterState):
117
  """μƒˆ 캐릭터 등둝"""
@@ -175,8 +178,12 @@ class ConsistencyTracker:
175
  for char in active_chars:
176
  status = "생쑴" if char.alive else "사망"
177
  summary += f"β€’ {char.name}: {status}"
178
- if char.alive and char.location: summary += f" (μœ„μΉ˜: {char.location})"
179
- if char.injuries: summary += f" (뢀상: {', '.join(char.injuries[-1:])})"
 
 
 
 
180
  summary += "\n"
181
  return summary
182
 
@@ -187,7 +194,10 @@ class ConsistencyTracker:
187
  if not recent_events:
188
  return "\n(아직 μ£Όμš” 사건이 μ—†μŠ΅λ‹ˆλ‹€.)\n"
189
  for event in recent_events[-3:]: # 졜근 3개만 ν‘œμ‹œ
190
- summary += f"β€’ [챕터 {event.chapter}] {event.description}\n"
 
 
 
191
  return summary
192
 
193
 
@@ -259,7 +269,8 @@ class NovelDatabase:
259
  status TEXT DEFAULT 'active',
260
  current_stage INTEGER DEFAULT 0,
261
  final_novel TEXT,
262
- consistency_report TEXT
 
263
  )
264
  ''')
265
  cursor.execute('''
@@ -273,6 +284,7 @@ class NovelDatabase:
273
  word_count INTEGER DEFAULT 0,
274
  status TEXT DEFAULT 'pending',
275
  consistency_score REAL DEFAULT 0.0,
 
276
  created_at TEXT DEFAULT (datetime('now')),
277
  updated_at TEXT DEFAULT (datetime('now')),
278
  FOREIGN KEY (session_id) REFERENCES sessions(session_id),
@@ -290,6 +302,7 @@ class NovelDatabase:
290
  injuries TEXT,
291
  emotional_state TEXT,
292
  description TEXT,
 
293
  created_at TEXT DEFAULT (datetime('now')),
294
  FOREIGN KEY (session_id) REFERENCES sessions(session_id)
295
  )
@@ -325,17 +338,17 @@ class NovelDatabase:
325
  @staticmethod
326
  def save_stage(session_id: str, stage_number: int, stage_name: str,
327
  role: str, content: str, status: str = 'complete',
328
- consistency_score: float = 0.0):
329
  word_count = len(content.split()) if content else 0
330
  with NovelDatabase.get_db() as conn:
331
  cursor = conn.cursor()
332
  cursor.execute('''
333
- INSERT INTO stages (session_id, stage_number, stage_name, role, content, word_count, status, consistency_score)
334
- VALUES (?, ?, ?, ?, ?, ?, ?, ?)
335
  ON CONFLICT(session_id, stage_number)
336
- DO UPDATE SET content=?, word_count=?, status=?, stage_name=?, consistency_score=?, updated_at=datetime('now')
337
- ''', (session_id, stage_number, stage_name, role, content, word_count, status, consistency_score,
338
- content, word_count, status, stage_name, consistency_score))
339
  cursor.execute(
340
  "UPDATE sessions SET updated_at = datetime('now'), current_stage = ? WHERE session_id = ?",
341
  (stage_number, session_id)
@@ -374,11 +387,11 @@ class NovelDatabase:
374
  return '\n\n'.join(all_content)
375
 
376
  @staticmethod
377
- def update_final_novel(session_id: str, final_novel: str, consistency_report: str = ""):
378
  with NovelDatabase.get_db() as conn:
379
  conn.cursor().execute(
380
- "UPDATE sessions SET final_novel = ?, status = 'complete', updated_at = datetime('now'), consistency_report = ? WHERE session_id = ?",
381
- (final_novel, consistency_report, session_id)
382
  )
383
  conn.commit()
384
 
@@ -406,9 +419,9 @@ class NovelWritingSystem:
406
  """API 헀더 생성"""
407
  return {"Authorization": f"Bearer {self.token}", "Content-Type": "application/json"}
408
 
409
- # --- ν”„λ‘¬ν”„νŠΈ 생성 ν•¨μˆ˜λ“€ (Thematic Guardian κ°œλ… 톡합) ---
410
  def create_director_initial_prompt(self, user_query: str, language: str) -> str:
411
- """κ°λ…μž 초기 기획 ν”„λ‘¬ν”„νŠΈ (μ›Ή 검색 및 ν…Œλ§ˆ μ œμ•½ 쑰건 κ°•ν™”)"""
412
  search_results_str = ""
413
  if self.web_search.enabled:
414
  queries = [f"{user_query} novel setting", f"{user_query} background information"]
@@ -421,67 +434,185 @@ class NovelWritingSystem:
421
  "title": "당신은 30νŽ˜μ΄μ§€ λΆ„λŸ‰μ˜ μ€‘νŽΈ μ†Œμ„€μ„ κΈ°νšν•˜λŠ” λ¬Έν•™ κ°λ…μžμž…λ‹ˆλ‹€.",
422
  "user_theme": "μ‚¬μš©μž 주제",
423
  "plan_instruction": "λ‹€μŒ μš”μ†Œλ“€μ„ ν¬ν•¨ν•œ μƒμ„Έν•œ μ†Œμ„€ κΈ°νšμ„ μž‘μ„±ν•˜μ„Έμš”:",
424
- "theme_section": "1. **μ£Όμ œμ™€ μž₯λ₯΄ μ„€μ •**\n - 핡심 μ£Όμ œμ™€ λ©”μ‹œμ§€ (μ‚¬μš©μž μ˜λ„ 깊이 반영)\n - μž₯λ₯΄ 및 λΆ„μœ„κΈ°\n - λ…μžμΈ΅ 고렀사항",
425
- "char_section": "2. **μ£Όμš” λ“±μž₯인물** (3-5λͺ…)\n | 이름 | μ—­ν•  | 성격 | λ°°κ²½ | λͺ©ν‘œ | κ°ˆλ“± |",
426
- "setting_section": "3. **λ°°κ²½ μ„€μ •**\n - μ‹œκ³΅κ°„μ  λ°°κ²½\n - μ‚¬νšŒμ /문화적 ν™˜κ²½\n - μ£Όμš” μž₯μ†Œλ“€",
427
- "plot_section": "4. **ν”Œλ‘― ꡬ쑰** (10개 파트, 각 3νŽ˜μ΄μ§€ λΆ„λŸ‰)\n | 파트 | νŽ˜μ΄μ§€ | μ£Όμš” 사건 | κΈ΄μž₯도 | 캐릭터 λ°œμ „ |",
428
- "guideline_section": "5. **μž‘κ°€λ³„ μ§€μΉ¨**\n - 일관성 μœ μ§€λ₯Ό μœ„ν•œ 핡심 μ„€μ •\n - 문체와 톀 κ°€μ΄λ“œλΌμΈ",
429
- "constraint_title": "⚠️맀우 μ€‘μš”ν•œ μ§€μ‹œμ‚¬ν•­: 핡심 μ œμ•½ 쑰건⚠️",
430
- "constraint_body": "이 μ†Œμ„€μ€ **AI둜 인해 λͺ¨λ“  것이 μ‰½κ²Œ ν•΄κ²°λ˜λŠ” 긍정적이고 λ‹¨μˆœν•œ 이야기가 μ•„λ‹™λ‹ˆλ‹€.**\nλ°˜λ“œμ‹œ μ‚¬μš©μžμ˜ 주제인 '{query}'에 λ‹΄κΈ΄ **핡심 감정(예: λΆˆμ•ˆ, μ†Œμ™Έκ°, 상싀감, μ„ΈλŒ€ κ°ˆλ“± λ“±)을 μ€‘μ‹¬μœΌλ‘œ μ„œμ‚¬λ₯Ό μ „κ°œν•΄μ•Ό ν•©λ‹ˆλ‹€.**\nAIλ‚˜ νŠΉμ • κΈ°μˆ μ€ νŽΈλ¦¬ν•œ 도ꡬ가 μ•„λ‹ˆλΌ, μ£ΌμΈκ³΅μ—κ²Œ **κ°ˆλ“±κ³Ό 상싀감을 μ•ˆκ²¨μ£ΌλŠ” 핡심 원인**으둜 μž‘μš©ν•΄μ•Ό ν•©λ‹ˆλ‹€.\n이 μ œμ•½ 쑰건을 μ ˆλŒ€ λ²—μ–΄λ‚˜μ§€ λ§ˆμ‹­μ‹œμ˜€.",
431
- "final_instruction": "창의적이고 깊이 μžˆλŠ” μ†Œμ„€μ΄ 될 수 μžˆλ„λ‘ μƒμ„Έν•˜κ²Œ κΈ°νšν•˜μ„Έμš”."
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
432
  },
433
  "English": {
434
  "title": "You are a literary director planning a 30-page novella.",
435
  "user_theme": "User Theme",
436
  "plan_instruction": "Create a detailed novel plan including:",
437
- "theme_section": "1. **Theme and Genre**\n - Core theme and message (Deeply reflect user's intent)\n - Genre and atmosphere",
438
- "char_section": "2. **Main Characters** (3-5)\n | Name | Role | Personality | Background | Goal | Conflict |",
439
- "setting_section": "3. **Setting**\n - Time and place\n - Social/cultural environment",
440
- "plot_section": "4. **Plot Structure** (10 parts, ~3 pages each)\n | Part | Pages | Main Events | Tension | Character Development |",
441
- "guideline_section": "5. **Writer Guidelines**\n - Key settings for consistency\n - Style and tone guidelines",
442
- "constraint_title": "⚠️CRITICAL INSTRUCTION: CORE CONSTRAINTS⚠️",
443
- "constraint_body": "This is **NOT a simple, positive story where AI solves everything.**\nYou must develop the narrative around the core emotions of the user's theme: '{query}' (e.g., anxiety, alienation, loss, generational conflict).\nAI or specific technology should be the **root cause of the protagonist's conflict and loss**, not a convenient tool.\nDo not deviate from this constraint.",
444
- "final_instruction": "Plan in detail for a creative and profound novel."
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
445
  }
446
  }
447
  p = lang_prompts[language]
448
- return f"{p['title']}\n\n{p['user_theme']}: {user_query}\n\n{search_results_str}\n\n{p['plan_instruction']}\n\n{p['theme_section']}\n\n{p['char_section']}\n\n{p['setting_section']}\n\n{p['plot_section']}\n\n{p['guideline_section']}\n\n---\n{p['constraint_title']}\n{p['constraint_body'].format(query=user_query)}\n---\n\n{p['final_instruction']}"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
449
 
450
  def create_critic_director_prompt(self, director_plan: str, user_query: str, language: str) -> str:
451
- """λΉ„ν‰κ°€μ˜ κ°λ…μž 기획 κ²€ν†  ν”„λ‘¬ν”„νŠΈ (ν…Œλ§ˆ 일관성 κ°•ν™”)"""
452
  lang_prompts = {
453
  "Korean": {
454
- "title": "당신은 λ¬Έν•™ λΉ„ν‰κ°€μž…λ‹ˆλ‹€. κ°λ…μžμ˜ μ†Œμ„€ κΈ°νšμ„ '주제 일관성'κ³Ό '기술적 일관성' κ΄€μ μ—μ„œ κ²€ν† ν•˜μ„Έμš”.",
455
- "theme_check": f"**1. 주제 일관성 (κ°€μž₯ μ€‘μš”)**\n - **μ›λž˜ 주제:** '{user_query}'\n - κΈ°νšμ•ˆμ΄ 주제의 핡심 감정(λΆˆμ•ˆ, 상싀감 λ“±)μ—μ„œ λ²—μ–΄λ‚˜ κΈμ •μ μ΄κ±°λ‚˜ λ‹¨μˆœν•œ λ°©ν–₯으둜 흐λ₯΄μ§€ μ•Šμ•˜μŠ΅λ‹ˆκΉŒ?\n - AIλ‚˜ 기술이 κ°ˆλ“±μ˜ 원인이 μ•„λ‹Œ, λ‹¨μˆœ ν•΄κ²°μ‚¬λ‘œ λ¬˜μ‚¬λ˜μ§€ μ•Šμ•˜μŠ΅λ‹ˆκΉŒ?",
456
- "consistency_check": "**2. 기술적 일관성**\n - 캐릭터 μ„€μ •μ˜ λͺ¨μˆœ, ν”Œλ‘―μ˜ 논리적 ν—ˆμ , μ‹œκ°„μ„ /곡간 μ„€μ •μ˜ λ¬Έμ œμ μ„ κ²€ν† ν•˜μ„Έμš”.",
457
- "instruction": "μœ„ ν•­λͺ©λ“€μ„ μ€‘μ‹¬μœΌλ‘œ ꡬ체적인 문제점과 κ°œμ„ μ•ˆμ„ μ œμ‹œν•˜μ„Έμš”."
 
 
 
 
 
 
 
 
 
458
  },
459
  "English": {
460
- "title": "You are a literary critic. Review the director's plan from the perspectives of 'Thematic Consistency' and 'Technical Consistency'.",
461
- "theme_check": f"**1. Thematic Consistency (Most Important)**\n - **Original Theme:** '{user_query}'\n - Does the plan drift from the core emotions (e.g., anxiety, loss) towards an overly positive or simplistic narrative?\n - Is AI depicted as a simple problem-solver instead of the root of the conflict?",
462
- "consistency_check": "**2. Technical Consistency**\n - Review for character contradictions, plot holes, and timeline/setting issues.",
463
- "instruction": "Provide specific problems and suggestions for improvement based on the above."
 
 
 
 
 
 
 
 
 
464
  }
465
  }
466
  p = lang_prompts[language]
467
- return f"{p['title']}\n\n**κ°λ…μž 기획:**\n{director_plan}\n\n---\n**κ²€ν†  ν•­λͺ©:**\n{p['theme_check']}\n\n{p['consistency_check']}\n\n{p['instruction']}"
 
 
 
 
 
 
 
 
 
 
 
468
 
469
  def create_director_revision_prompt(self, initial_plan: str, critic_feedback: str, user_query: str, language: str) -> str:
470
- """κ°λ…μž μˆ˜μ • ν”„λ‘¬ν”„νŠΈ (ν…Œλ§ˆ μ œμ•½ 쑰건 μž¬κ°•μ‘°)"""
471
- return f"""κ°λ…μžλ‘œμ„œ λΉ„ν‰κ°€μ˜ ν”Όλ“œλ°±μ„ λ°˜μ˜ν•˜μ—¬ μ†Œμ„€ κΈ°νšμ„ μˆ˜μ •ν•©λ‹ˆλ‹€.
472
-
473
- **μ›λž˜ 주제:** {user_query}
474
- **초기 기획:**\n{initial_plan}
475
- **비평가 ν”Όλ“œλ°±:**\n{critic_feedback}
 
 
 
 
 
 
 
 
 
 
 
476
 
477
- **μˆ˜μ • μ§€μΉ¨:**
478
- - 비평가가 μ§€μ ν•œ λͺ¨λ“  일관성 λ¬Έμ œμ™€ 주제 μ΄νƒˆ 문제λ₯Ό ν•΄κ²°ν•˜μ„Έμš”.
479
- - **핡심 μ œμ•½ 쑰건**을 λ‹€μ‹œ ν•œλ²ˆ μƒκΈ°ν•˜κ³ , μ†Œμ„€ 전체가 'λΆˆμ•ˆ'κ³Ό '상싀감'의 톀을 μœ μ§€ν•˜λ„λ‘ ν”Œλ‘―μ„ κ΅¬μ²΄ν™”ν•˜μ„Έμš”.
480
- - 10λͺ…μ˜ μž‘κ°€κ°€ ν˜Όλ™ 없이 μž‘μ—…ν•  수 μžˆλ„λ‘ λͺ…ν™•ν•˜κ³  μƒμ„Έν•œ μ΅œμ’… λ§ˆμŠ€ν„°ν”Œλžœμ„ μž‘μ„±ν•˜μ„Έμš”.
481
- """
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
482
 
483
  def create_writer_prompt(self, writer_number: int, director_plan: str, previous_content_summary: str, user_query: str, language: str) -> str:
484
- """μž‘κ°€ ν”„λ‘¬ν”„νŠΈ (ν…Œλ§ˆ λ¦¬λ§ˆμΈλ” 포함)"""
485
  pages_start = (writer_number - 1) * 3 + 1
486
  pages_end = writer_number * 3
487
 
@@ -490,69 +621,210 @@ class NovelWritingSystem:
490
  "title": f"당신은 μž‘κ°€ {writer_number}λ²ˆμž…λ‹ˆλ‹€. μ†Œμ„€μ˜ {pages_start}-{pages_end} νŽ˜μ΄μ§€λ₯Ό μž‘μ„±ν•˜μ„Έμš”.",
491
  "plan": "κ°λ…μž λ§ˆμŠ€ν„°ν”Œλžœ",
492
  "prev_summary": "이전 λ‚΄μš© μš”μ•½",
493
- "guidelines": "**μž‘μ„± μ§€μΉ¨:**\n1. **λΆ„λŸ‰**: 1,400-1,500 단어 λ‚΄μ™Έ\n2. **μ—°κ²°μ„±**: μš”μ•½λœ 이전 λ‚΄μš©κ³Ό μžμ—°μŠ€λŸ½κ²Œ μ—°κ²°\n3. **일관성**: 캐릭터 μ„€μ •κ³Ό μƒνƒœ, ν”Œλ‘― ꡬ쑰λ₯Ό λ°˜λ“œμ‹œ λ”°λ₯Ό 것",
494
- "reminder_title": "⭐ μžŠμ§€ λ§ˆμ„Έμš” (ν…Œλ§ˆ λ¦¬λ§ˆμΈλ”)",
495
- "reminder_body": f"이 μ†Œμ„€μ˜ 핡심은 '{user_query}'에 λ‹΄κΈ΄ **λΆˆμ•ˆ, μ†Œμ™Έ, 상싀감**μž…λ‹ˆλ‹€. 긍정적인 해결을 μ„œλ‘λ₯΄μ§€ 말고, 주인곡의 λ‚΄λ©΄ κ°ˆλ“±μ„ 심도 있게 λ¬˜μ‚¬ν•˜λŠ” 데 μ§‘μ€‘ν•˜μ„Έμš”.",
496
- "final_instruction": "μ°½μ˜μ μ΄λ©΄μ„œλ„ μ£Όμ œμ™€ 일관성을 μ ˆλŒ€ μžƒμ§€ λ§ˆμ‹­μ‹œμ˜€."
 
 
 
 
 
 
 
 
497
  },
498
  "English": {
499
  "title": f"You are Writer #{writer_number}. Write pages {pages_start}-{pages_end} of the novella.",
500
  "plan": "Director's Masterplan",
501
  "prev_summary": "Previous Content Summary",
502
- "guidelines": "**Writing Guidelines:**\n1. **Length**: Approx. 1,400-1,500 words\n2. **Connectivity**: Connect naturally with the summarized previous content.\n3. **Consistency**: Strictly follow character settings, states, and plot structure.",
503
- "reminder_title": "⭐ REMINDER (THEME)",
504
- "reminder_body": f"The core of this novel is the **anxiety, alienation, and loss** from the theme '{user_query}'. Do not rush to a positive resolution; focus on deeply describing the protagonist's internal conflict.",
505
- "final_instruction": "Be creative, but never lose consistency and the core theme."
 
 
 
 
 
 
 
 
506
  }
507
  }
508
 
509
  p = lang_prompts[language]
510
  consistency_info = self.consistency_tracker.get_character_summary(writer_number) + self.consistency_tracker.get_plot_summary(writer_number)
511
 
512
- return f"{p['title']}\n\n**{p['plan']}:**\n{director_plan}\n\n{consistency_info}\n\n**{p['prev_summary']}:**\n{previous_content_summary}\n\n---\n{p['guidelines']}\n\n**{p['reminder_title']}**\n{p['reminder_body']}\n---\n\n{p['final_instruction']}"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
513
 
514
  def create_critic_consistency_prompt(self, all_content: str, user_query: str, language: str) -> str:
515
- """비평가 쀑간 κ²€ν†  ν”„λ‘¬ν”„νŠΈ (ν…Œλ§ˆ κ²€ν†  κ°•ν™”)"""
516
- return f"""당신은 일관성 κ²€ν†  μ „λ¬Έ λΉ„ν‰κ°€μž…λ‹ˆλ‹€. μ§€κΈˆκΉŒμ§€ μž‘μ„±λœ λ‚΄μš©μ„ κ²€ν† ν•˜μ„Έμš”.
517
-
518
- **μ›λž˜ 주제:** {user_query}
519
- **ν˜„μž¬κΉŒμ§€ μž‘μ„±λœ λ‚΄μš© (졜근 3000자):**\n{all_content[-3000:]}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
520
 
521
- **κ²€ν†  ν•­λͺ©:**
522
- 1. **주제 일관성 (κ°€μž₯ μ€‘μš”):** λ‚΄μš©μ΄ μ›λž˜ 주제의 μ–΄λ‘μš΄ κ°μ •μ„ μ—μ„œ λ²—μ–΄λ‚˜μ§€ μ•Šμ•˜λŠ”μ§€ ν™•μΈν•˜κ³ , 벗어났닀면 μˆ˜μ • λ°©ν–₯을 μ œμ‹œν•˜μ„Έμš”.
523
- 2. **기술적 일관성:** 캐릭터, ν”Œλ‘―, μ„€μ •μ˜ 연속성과 논리적 였λ₯˜λ₯Ό μ°Ύμ•„λ‚΄μ„Έμš”.
524
- 3. **반볡 λ‚΄μš©:** 의미적으둜 μ€‘λ³΅λ˜λŠ” μž₯λ©΄μ΄λ‚˜ ν‘œν˜„μ΄ μ—†λŠ”μ§€ ν™•μΈν•˜μ„Έμš”.
525
 
526
- **κ²°κ³Ό:** 발견된 문제점과 ꡬ체적인 μˆ˜μ • μ œμ•ˆμ„ λͺ©λ‘μœΌλ‘œ μ œμ‹œν•˜μ„Έμš”.
527
- """
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
528
 
529
  def create_writer_revision_prompt(self, writer_number: int, initial_content: str, consistency_feedback: str, language: str) -> str:
530
  """μž‘κ°€ μˆ˜μ • ν”„λ‘¬ν”„νŠΈ"""
531
- return f"""μž‘κ°€ {writer_number}λ²ˆμœΌλ‘œμ„œ λΉ„ν‰κ°€μ˜ ν”Όλ“œλ°±μ„ λ°˜μ˜ν•˜μ—¬ λ‚΄μš©μ„ μˆ˜μ •ν•˜μ„Έμš”.
 
 
 
 
532
 
533
- **초기 μž‘μ„± λ‚΄μš©:**\n{initial_content}
534
- **비평가 ν”Όλ“œλ°±:**\n{consistency_feedback}
535
 
536
- **μˆ˜μ • μ§€μΉ¨:**
537
- - μ§€μ λœ λͺ¨λ“  주제 μ΄νƒˆ 및 일관성 문제λ₯Ό ν•΄κ²°ν•˜μ„Έμš”.
538
- - λΆ„λŸ‰(1,400-1,500 단어)을 μœ μ§€ν•˜λ©΄μ„œ λ‚΄μš©μ˜ μ§ˆμ„ λ†’μ΄μ„Έμš”.
539
- - μˆ˜μ •λœ μ΅œμ’… 버전을 μ œμ‹œν•˜μ„Έμš”.
540
- """
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
541
 
542
  def create_critic_final_prompt(self, complete_novel: str, language: str) -> str:
543
  """μ΅œμ’… 비평가 κ²€ν†  및 λ³΄κ³ μ„œ μž‘μ„± ν”„λ‘¬ν”„νŠΈ"""
544
- return f"""μ™„μ„±λœ μ†Œμ„€μ˜ μ΅œμ’… 일관성 및 완성도에 λŒ€ν•œ μ’…ν•© λ³΄κ³ μ„œλ₯Ό μž‘μ„±ν•˜μ„Έμš”.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
545
 
546
- **μ™„μ„±λœ μ†Œμ„€ (λ§ˆμ§€λ§‰ 2000자):**\n{complete_novel[-2000:]}
 
547
 
548
- **λ³΄κ³ μ„œ 포함 ν•­λͺ©:**
549
- 1. **전체 일관성 평가:** 캐릭터, ν”Œλ‘―, μ„€μ •, 주제 μœ μ§€μ— λŒ€ν•œ 점수(1-10)와 총평.
550
- 2. **μ΅œμ’… 발견된 문제점:** λ‚¨μ•„μžˆλŠ” μ‚¬μ†Œν•œ λ¬Έμ œμ λ“€.
551
- 3. **성곡 μš”μ†Œ:** 특히 잘 μœ μ§€λœ 일관성 λΆ€λΆ„μ΄λ‚˜ 주제 ν‘œν˜„μ΄ λ›°μ–΄λ‚œ λΆ€λΆ„.
552
- 4. **μ΅œμ’… 평가:** μ†Œμ„€μ˜ μ „λ°˜μ μΈ 완성도와 λ…μžμ—κ²Œ λ―ΈμΉ  영ν–₯에 λŒ€ν•œ 평가.
553
- """
554
 
555
- # --- LLM 호좜 ν•¨μˆ˜λ“€ ---
 
 
 
 
556
  def call_llm_sync(self, messages: List[Dict[str, str]], role: str, language: str) -> str:
557
  """LLM 동기식 호좜 (μš”μ•½ λ“± λ‚΄λΆ€μš©)"""
558
  full_content = ""
@@ -563,7 +835,7 @@ class NovelWritingSystem:
563
  return full_content
564
 
565
  def call_llm_streaming(self, messages: List[Dict[str, str]], role: str, language: str) -> Generator[str, None, None]:
566
- """LLM 슀트리밍 호좜 (μ™„μ „ν•œ μ—λŸ¬ 처리 및 디버깅)"""
567
  try:
568
  system_prompts = self.get_system_prompts(language)
569
  full_messages = [{"role": "system", "content": system_prompts.get(role, "You are a helpful assistant.")}, *messages]
@@ -582,7 +854,6 @@ class NovelWritingSystem:
582
 
583
  logger.info(f"[{role}] API 슀트리밍 μ‹œμž‘")
584
 
585
- # API 호좜
586
  response = requests.post(
587
  self.api_url,
588
  headers=self.create_headers(),
@@ -591,7 +862,6 @@ class NovelWritingSystem:
591
  timeout=180
592
  )
593
 
594
- # μƒνƒœ μ½”λ“œ 확인
595
  if response.status_code != 200:
596
  logger.error(f"API 응닡 였λ₯˜: {response.status_code}")
597
  logger.error(f"응닡 λ‚΄μš©: {response.text[:500]}")
@@ -599,42 +869,36 @@ class NovelWritingSystem:
599
  return
600
 
601
  response.raise_for_status()
602
-
603
- # 슀트리밍 처리
604
  buffer = ""
605
  total_content = ""
606
  chunk_count = 0
607
- error_count = 0
608
-
609
  for line in response.iter_lines():
610
  if not line:
611
  continue
612
 
613
  try:
614
  line_str = line.decode('utf-8').strip()
615
-
616
- # SSE ν˜•μ‹ 확인
617
  if not line_str.startswith("data: "):
618
  continue
619
 
620
- data_str = line_str[6:] # "data: " 제거
621
-
622
- # 슀트림 μ’…λ£Œ 확인
623
  if data_str == "[DONE]":
624
  logger.info(f"[{role}] 슀트리밍 μ™„λ£Œ - 총 {len(total_content)} 문자")
625
  break
626
-
627
- # JSON νŒŒμ‹±
628
  try:
629
  data = json.loads(data_str)
630
  except json.JSONDecodeError:
631
  logger.warning(f"JSON νŒŒμ‹± μ‹€νŒ¨: {data_str[:100]}")
632
  continue
633
-
634
- # choices λ°°μ—΄ μ•ˆμ „ν•˜κ²Œ 확인
635
  choices = data.get("choices", None)
636
  if not choices or not isinstance(choices, list) or len(choices) == 0:
637
- # μ—λŸ¬ 응닡 확인
638
  if "error" in data:
639
  error_msg = data.get("error", {}).get("message", "Unknown error")
640
  logger.error(f"API μ—λŸ¬: {error_msg}")
@@ -642,7 +906,6 @@ class NovelWritingSystem:
642
  return
643
  continue
644
 
645
- # deltaμ—μ„œ content μΆ”μΆœ
646
  delta = choices[0].get("delta", {})
647
  content = delta.get("content", "")
648
 
@@ -651,25 +914,20 @@ class NovelWritingSystem:
651
  total_content += content
652
  chunk_count += 1
653
 
654
- # 100자 λ˜λŠ” μ€„λ°”κΏˆλ§ˆλ‹€ yield
655
- if len(buffer) >= 100 or '\n' in buffer:
656
  yield buffer
657
  buffer = ""
658
- time.sleep(0.01) # UI μ—…λ°μ΄νŠΈλ₯Ό μœ„ν•œ 짧은 λŒ€κΈ°
659
 
660
  except Exception as e:
661
- error_count += 1
662
- logger.error(f"청크 처리 였λ₯˜ #{error_count}: {str(e)}")
663
- if error_count > 10: # λ„ˆλ¬΄ λ§Žμ€ μ—λŸ¬μ‹œ 쀑단
664
- yield f"❌ 슀트리밍 쀑 κ³Όλ„ν•œ 였λ₯˜ λ°œμƒ"
665
- return
666
  continue
667
 
668
  # 남은 버퍼 처리
669
  if buffer:
670
  yield buffer
671
 
672
- # κ²°κ³Ό 확인
673
  if chunk_count == 0:
674
  logger.error(f"[{role}] μ½˜ν…μΈ κ°€ μ „ν˜€ μˆ˜μ‹ λ˜μ§€ μ•ŠμŒ")
675
  yield "❌ APIλ‘œλΆ€ν„° 응닡을 λ°›μ§€ λͺ»ν–ˆμŠ΅λ‹ˆλ‹€."
@@ -686,22 +944,18 @@ class NovelWritingSystem:
686
  logger.error(f"예기치 μ•Šμ€ 였λ₯˜: {type(e).__name__}: {str(e)}", exc_info=True)
687
  yield f"❌ 였λ₯˜ λ°œμƒ: {str(e)}"
688
 
689
-
690
-
691
-
692
-
693
  def get_system_prompts(self, language: str) -> Dict[str, str]:
694
- """역할별 μ‹œμŠ€ν…œ ν”„λ‘¬ν”„νŠΈ 생성"""
695
  base_prompts = {
696
  "Korean": {
697
- "director": "당신은 창의적이고 체계적인 μ†Œμ„€ 기획 μ „λ¬Έκ°€μž…λ‹ˆλ‹€. ν₯λ―Έλ‘­κ³  일관성 μžˆλŠ” μŠ€ν† λ¦¬λ₯Ό μ„€κ³„ν•˜μ„Έμš”.",
698
- "critic": "당신은 일관성 κ²€ν†  μ „λ¬Έ λΉ„ν‰κ°€μž…λ‹ˆλ‹€. 캐릭터, ν”Œλ‘―, μ„€μ •μ˜ 일관성을 μ² μ €νžˆ μ κ²€ν•˜κ³  κ°œμ„ λ°©μ•ˆμ„ μ œμ‹œν•˜μ„Έμš”.",
699
- "writer_base": "당신은 μ „λ¬Έ μ†Œμ„€ μž‘κ°€μž…λ‹ˆλ‹€. μ£Όμ–΄μ§„ 지침에 따라 λͺ°μž…감 있고 일관성 μžˆλŠ” λ‚΄μš©μ„ μž‘μ„±ν•˜μ„Έμš”."
700
  },
701
  "English": {
702
- "director": "You are a creative and systematic novel planning expert. Design engaging and consistent stories.",
703
- "critic": "You are a consistency review specialist critic. Thoroughly check character, plot, and setting consistency and suggest improvements.",
704
- "writer_base": "You are a professional novel writer. Write immersive and consistent content according to the given guidelines."
705
  }
706
  }
707
 
@@ -709,21 +963,24 @@ class NovelWritingSystem:
709
 
710
  # μž‘κ°€λ³„ 특수 ν”„λ‘¬ν”„νŠΈ
711
  if language == "Korean":
712
- prompts["writer1"] = "당신은 μ†Œμ„€μ˜ λ§€λ ₯적인 μ‹œμž‘μ„ λ‹΄λ‹Ήν•˜λŠ” μž‘κ°€μž…λ‹ˆλ‹€. λ…μžλ₯Ό μ‚¬λ‘œμž‘λŠ” λ„μž…λΆ€λ₯Ό λ§ŒοΏ½οΏ½μ„Έμš”."
713
- prompts["writer10"] = "당신은 μ™„λ²½ν•œ 결말을 λ§Œλ“œλŠ” μž‘κ°€μž…λ‹ˆλ‹€. λ…μžμ—κ²Œ κΉŠμ€ μ—¬μš΄μ„ λ‚¨κΈ°λŠ” 마무리λ₯Ό ν•˜μ„Έμš”."
 
714
  else:
715
- prompts["writer1"] = "You are a writer responsible for the captivating beginning. Create an opening that hooks readers."
716
- prompts["writer10"] = "You are a writer who creates the perfect ending. Create a conclusion that leaves readers with deep resonance."
 
717
 
718
- # writer2-9λŠ” κΈ°λ³Έ ν”„λ‘¬ν”„νŠΈ μ‚¬μš©
719
  for i in range(2, 10):
720
- prompts[f"writer{i}"] = prompts["writer_base"]
 
721
 
722
  return prompts
723
 
724
- # --- 메인 ν”„λ‘œμ„ΈμŠ€ ---
725
  def process_novel_stream(self, query: str, language: str, session_id: Optional[str] = None) -> Generator[Tuple[str, List[Dict[str, Any]], str], None, None]:
726
- """μ†Œμ„€ 생성 슀트리밍 ν”„λ‘œμ„ΈμŠ€ (κ°•ν™”λœ 둜직)"""
727
  try:
728
  resume_from_stage = 0
729
  if session_id:
@@ -741,51 +998,80 @@ class NovelWritingSystem:
741
  stages = []
742
  if resume_from_stage > 0:
743
  stages = [{
744
- "name": s['stage_name'], "status": s['status'], "content": s.get('content', ''),
745
- "consistency_score": s.get('consistency_score', 0.0)
 
 
 
746
  } for s in NovelDatabase.get_stages(self.current_session_id)]
747
 
748
  for stage_idx in range(resume_from_stage, len(OPTIMIZED_STAGES)):
749
  role, stage_name = OPTIMIZED_STAGES[stage_idx]
750
  if stage_idx >= len(stages):
751
- stages.append({"name": stage_name, "status": "active", "content": "", "consistency_score": 0.0})
 
 
 
 
 
 
752
  else:
753
  stages[stage_idx]["status"] = "active"
754
 
755
  yield "", stages, self.current_session_id
 
756
  prompt = self.get_stage_prompt(stage_idx, role, query, language, stages)
757
  stage_content = ""
 
 
758
  for chunk in self.call_llm_streaming([{"role": "user", "content": prompt}], role, language):
759
  stage_content += chunk
760
  stages[stage_idx]["content"] = stage_content
761
- yield "", stages, self.current_session_id
762
 
 
763
  consistency_score = 0.0
 
 
764
  if role.startswith("writer"):
765
  writer_num = int(re.search(r'\d+', role).group())
766
  all_previous = self.get_all_content(stages, stage_idx)
767
  errors = self.consistency_tracker.validate_consistency(writer_num, stage_content)
768
  consistency_score = max(0, 10 - len(errors) * 2)
 
 
 
 
 
 
 
769
  stages[stage_idx]["consistency_score"] = consistency_score
 
770
 
771
  stages[stage_idx]["status"] = "complete"
772
  NovelDatabase.save_stage(
773
  self.current_session_id, stage_idx, stage_name, role,
774
- stage_content, "complete", consistency_score
775
  )
776
  yield "", stages, self.current_session_id
777
 
 
778
  final_novel = NovelDatabase.get_writer_content(self.current_session_id)
779
  final_report = self.generate_consistency_report(final_novel, language)
780
- NovelDatabase.update_final_novel(self.current_session_id, final_novel, final_report)
781
- yield f"βœ… μ†Œμ„€ μ™„μ„±! 총 {len(final_novel.split())}단어", stages, self.current_session_id
 
 
 
 
 
782
 
783
  except Exception as e:
784
  logger.error(f"μ†Œμ„€ 생성 ν”„λ‘œμ„ΈμŠ€ 였λ₯˜: {e}", exc_info=True)
785
  yield f"❌ 였λ₯˜ λ°œμƒ: {e}", stages if 'stages' in locals() else [], self.current_session_id
786
 
787
  def get_stage_prompt(self, stage_idx: int, role: str, query: str, language: str, stages: List[Dict]) -> str:
788
- """단계별 ν”„λ‘¬ν”„νŠΈ 생성 (μš”μ•½ κΈ°λŠ₯ 및 주제 전달 κ°•ν™”)"""
789
  if stage_idx == 0:
790
  return self.create_director_initial_prompt(query, language)
791
  if stage_idx == 1:
@@ -795,23 +1081,23 @@ class NovelWritingSystem:
795
 
796
  master_plan = stages[2]["content"]
797
 
798
- if 3 <= stage_idx <= 12: # μž‘κ°€ μ΄ˆμ•ˆ
799
  writer_num = stage_idx - 2
800
  previous_content = self.get_all_content(stages, stage_idx)
801
  summary = self.create_summary(previous_content, language)
802
  return self.create_writer_prompt(writer_num, master_plan, summary, query, language)
803
 
804
- if stage_idx == 13: # 비평가 쀑간 κ²€ν† 
805
  all_content = self.get_all_content(stages, stage_idx)
806
  return self.create_critic_consistency_prompt(all_content, query, language)
807
 
808
- if 14 <= stage_idx <= 23: # μž‘κ°€ μˆ˜μ •
809
  writer_num = stage_idx - 13
810
  initial_content = stages[2 + writer_num]["content"]
811
  feedback = stages[13]["content"]
812
  return self.create_writer_revision_prompt(writer_num, initial_content, feedback, language)
813
 
814
- if stage_idx == 24: # μ΅œμ’… κ²€ν† 
815
  complete_novel = self.get_all_writer_content(stages)
816
  return self.create_critic_final_prompt(complete_novel, language)
817
 
@@ -822,9 +1108,9 @@ class NovelWritingSystem:
822
  if not content.strip():
823
  return "이전 λ‚΄μš©μ΄ μ—†μŠ΅λ‹ˆλ‹€." if language == "Korean" else "No previous content."
824
 
825
- prompt_text = "λ‹€μŒ μ†Œμ„€ λ‚΄μš©μ„ 3~5개의 핡심적인 λ¬Έμž₯으둜 μš”μ•½ν•΄μ€˜. λ‹€μŒ μž‘κ°€κ°€ 이야기λ₯Ό μ΄μ–΄κ°€λŠ” 데 ν•„μš”ν•œ 핡심 정보(λ“±μž₯인물의 ν˜„μž¬ 상황, 감정, λ§ˆμ§€λ§‰ 사건)λ₯Ό 포함해야 ν•΄."
826
  if language != "Korean":
827
- prompt_text = "Summarize the following novel content in 3-5 key sentences. Include crucial information for the next writer to continue the story (characters' current situation, emotions, and the last major event)."
828
 
829
  summary_prompt = f"{prompt_text}\n\n---\n{content[-2000:]}"
830
  try:
@@ -915,7 +1201,7 @@ def download_novel(novel_text: str, format_type: str, language: str, session_id:
915
 
916
  try:
917
  if format_type == "DOCX" and DOCX_AVAILABLE:
918
- return export_to_docx(novel_text, filename, language)
919
  else:
920
  return export_to_txt(novel_text, filename)
921
  except Exception as e:
@@ -928,8 +1214,16 @@ def format_stages_display(stages: List[Dict]) -> str:
928
  for i, stage in enumerate(stages):
929
  status_icon = "βœ…" if stage['status'] == 'complete' else "πŸ”„" if stage['status'] == 'active' else "⏳"
930
  markdown += f"{status_icon} **{stage['name']}**"
 
 
 
931
  if stage.get('consistency_score', 0) > 0:
932
- markdown += f" (일관성: {stage['consistency_score']:.1f}/10)"
 
 
 
 
 
933
  markdown += "\n"
934
  if stage['content']:
935
  preview = stage['content'][:200] + "..." if len(stage['content']) > 200 else stage['content']
@@ -951,10 +1245,13 @@ def format_novel_display(novel_text: str) -> str:
951
 
952
  return formatted
953
 
954
- def export_to_docx(content: str, filename: str, language: str) -> str:
955
  """DOCX 파일둜 내보내기"""
956
  doc = Document()
957
 
 
 
 
958
  # 제λͺ© μΆ”κ°€
959
  title = doc.add_heading('AI ν˜‘μ—… μ†Œμ„€', 0)
960
  title.alignment = WD_ALIGN_PARAGRAPH.CENTER
@@ -962,6 +1259,10 @@ def export_to_docx(content: str, filename: str, language: str) -> str:
962
  # 메타데이터
963
  doc.add_paragraph(f"생성일: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
964
  doc.add_paragraph(f"μ–Έμ–΄: {language}")
 
 
 
 
965
  doc.add_page_break()
966
 
967
  # λ³Έλ¬Έ μΆ”κ°€
@@ -983,38 +1284,51 @@ def export_to_txt(content: str, filename: str) -> str:
983
  return filepath
984
 
985
 
986
- # CSS μŠ€νƒ€μΌ
987
  custom_css = """
988
  .gradio-container {
989
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
990
  min-height: 100vh;
991
  }
992
 
993
  .main-header {
994
- background-color: rgba(255, 255, 255, 0.1);
995
  backdrop-filter: blur(10px);
996
  padding: 30px;
997
  border-radius: 12px;
998
  margin-bottom: 30px;
999
  text-align: center;
1000
  color: white;
 
 
 
 
 
 
 
 
 
 
 
1001
  }
1002
 
1003
  .input-section {
1004
- background-color: rgba(255, 255, 255, 0.1);
1005
  backdrop-filter: blur(10px);
1006
  padding: 20px;
1007
  border-radius: 12px;
1008
  margin-bottom: 20px;
 
1009
  }
1010
 
1011
  .session-section {
1012
- background-color: rgba(255, 255, 255, 0.1);
1013
  backdrop-filter: blur(10px);
1014
  padding: 15px;
1015
  border-radius: 8px;
1016
  margin-top: 20px;
1017
  color: white;
 
1018
  }
1019
 
1020
  #stages-display {
@@ -1023,14 +1337,18 @@ custom_css = """
1023
  border-radius: 12px;
1024
  max-height: 600px;
1025
  overflow-y: auto;
 
1026
  }
1027
 
1028
  #novel-output {
1029
  background-color: rgba(255, 255, 255, 0.95);
1030
  padding: 30px;
1031
  border-radius: 12px;
1032
- max-height: 400px;
1033
  overflow-y: auto;
 
 
 
1034
  }
1035
 
1036
  .download-section {
@@ -1038,26 +1356,49 @@ custom_css = """
1038
  padding: 15px;
1039
  border-radius: 8px;
1040
  margin-top: 20px;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1041
  }
1042
  """
1043
 
1044
 
1045
  # Gradio μΈν„°νŽ˜μ΄μŠ€ 생성
1046
  def create_interface():
1047
- with gr.Blocks(css=custom_css, title="AI ν˜‘μ—… μ†Œμ„€ 생성 μ‹œμŠ€ν…œ") as interface:
1048
  gr.HTML("""
1049
  <div class="main-header">
1050
  <h1 style="font-size: 2.5em; margin-bottom: 10px;">
1051
  πŸ“š AI ν˜‘μ—… μ†Œμ„€ 생성 μ‹œμŠ€ν…œ
1052
  </h1>
1053
  <h3 style="color: #ccc; margin-bottom: 20px;">
1054
- 일관성 μ€‘μ‹¬μ˜ 창의적 μ†Œμ„€ 생성
1055
  </h3>
1056
  <p style="font-size: 1.1em; color: #ddd; max-width: 800px; margin: 0 auto;">
1057
- 주제λ₯Ό μž…λ ₯ν•˜λ©΄ AI μ—μ΄μ „νŠΈλ“€μ΄ ν˜‘μ—…ν•˜μ—¬ 30νŽ˜μ΄μ§€ λΆ„λŸ‰μ˜ μ™„μ„±λœ μ†Œμ„€μ„ μƒμ„±ν•©λ‹ˆλ‹€.
1058
  <br>
1059
- κ°λ…μž 1λͺ…, 비평가 1λͺ…, μž‘κ°€ 10λͺ…이 ν•¨κ»˜ μž‘μ—…ν•˜λ©° 일관성을 μœ μ§€ν•©λ‹ˆλ‹€.
1060
  </p>
 
 
 
1061
  </div>
1062
  """)
1063
 
@@ -1069,13 +1410,13 @@ def create_interface():
1069
  with gr.Group(elem_classes=["input-section"]):
1070
  query_input = gr.Textbox(
1071
  label="μ†Œμ„€ 주제 / Novel Theme",
1072
- placeholder="μ†Œμ„€μ˜ μ£Όμ œλ‚˜ 초기 아이디어λ₯Ό μž…λ ₯ν•˜μ„Έμš”...\nEnter your novel theme or initial idea...",
1073
  lines=4
1074
  )
1075
 
1076
  language_select = gr.Radio(
1077
- choices=["English", "Korean"],
1078
- value="English",
1079
  label="μ–Έμ–΄ / Language"
1080
  )
1081
 
@@ -1133,25 +1474,27 @@ def create_interface():
1133
  # μˆ¨κ²¨μ§„ μƒνƒœ
1134
  novel_text_state = gr.State("")
1135
 
1136
- # 예제
1137
  with gr.Row():
1138
  gr.Examples(
1139
  examples=[
1140
- ["미래 λ„μ‹œμ—μ„œ 기얡을 κ±°λž˜ν•˜λŠ” μƒμΈμ˜ 이야기"],
1141
- ["μ‹œκ°„μ΄ 거꾸둜 흐λ₯΄λŠ” λ§ˆμ„μ˜ λ―ΈμŠ€ν„°λ¦¬"],
1142
- ["A scientist discovers a portal to parallel universes"],
1143
- ["In a world where dreams can be traded, a dream thief's story"],
1144
- ["Two AI entities fall in love while preventing a cyber war"],
1145
- ["μ±… μ†μœΌλ‘œ λ“€μ–΄κ°ˆ 수 μžˆλŠ” λŠ₯λ ₯을 κ°€μ§„ μ‚¬μ„œμ˜ λͺ¨ν—˜"]
 
 
1146
  ],
1147
  inputs=query_input,
1148
- label="πŸ’‘ 예제 주제"
1149
  )
1150
 
1151
  # 이벀트 ν•Έλ“€λŸ¬
1152
  def refresh_sessions():
1153
  try:
1154
- sessions = get_active_sessions("English")
1155
  return gr.update(choices=sessions)
1156
  except Exception as e:
1157
  logger.error(f"Error refreshing sessions: {str(e)}")
@@ -1231,7 +1574,7 @@ def create_interface():
1231
 
1232
  # 메인 μ‹€ν–‰
1233
  if __name__ == "__main__":
1234
- logger.info("AI ν˜‘μ—… μ†Œμ„€ 생성 μ‹œμŠ€ν…œ μ‹œμž‘...")
1235
  logger.info("=" * 60)
1236
 
1237
  # ν™˜κ²½ 확인
 
53
  # μ΅œμ ν™”λœ 단계 ꡬ성 (25λ‹¨κ³„λ‘œ μ••μΆ• 및 κ°•ν™”)
54
  OPTIMIZED_STAGES = [
55
  ("director", "🎬 κ°λ…μž: 초기 기획 (μ›Ή 검색 포함)"),
56
+ ("critic", "πŸ“ 비평가: 기획 κ²€ν†  (철학적 깊이 및 일관성)"),
57
  ("director", "🎬 κ°λ…μž: μˆ˜μ •λœ λ§ˆμŠ€ν„°ν”Œλžœ"),
58
  ] + [
59
  (f"writer{i}", f"✍️ μž‘κ°€ {i}: μ΄ˆμ•ˆ (νŽ˜μ΄μ§€ {(i-1)*3+1}-{i*3})")
60
  for i in range(1, 11)
61
  ] + [
62
+ ("critic", "πŸ“ 비평가: 쀑간 κ²€ν†  (철학적 깊이 및 일관성)"),
63
  ] + [
64
  (f"writer{i}", f"✍️ μž‘κ°€ {i}: μˆ˜μ •λ³Έ (νŽ˜μ΄μ§€ {(i-1)*3+1}-{i*3})")
65
  for i in range(1, 11)
 
81
  last_seen_chapter: int = 0
82
  description: str = ""
83
  role: str = ""
84
+ philosophical_stance: str = "" # μΆ”κ°€: μΊλ¦­ν„°μ˜ 철학적 μž…μž₯
85
 
86
  @dataclass
87
  class PlotPoint:
 
92
  characters_involved: List[str]
93
  impact_level: int
94
  timestamp: str = ""
95
+ philosophical_implication: str = "" # μΆ”κ°€: μ‚¬κ±΄μ˜ 철학적 ν•¨μ˜
96
 
97
  @dataclass
98
  class TimelineEvent:
 
114
  self.locations: Dict[str, str] = {}
115
  self.established_facts: List[str] = []
116
  self.content_hashes: Dict[str, int] = {} # ν•΄μ‹œμ™€ ν•΄λ‹Ή 챕터 번호λ₯Ό μ €μž₯
117
+ self.philosophical_themes: List[str] = [] # μΆ”κ°€: 철학적 주제 좔적
118
 
119
  def register_character(self, character: CharacterState):
120
  """μƒˆ 캐릭터 등둝"""
 
178
  for char in active_chars:
179
  status = "생쑴" if char.alive else "사망"
180
  summary += f"β€’ {char.name}: {status}"
181
+ if char.alive and char.location:
182
+ summary += f" (μœ„μΉ˜: {char.location})"
183
+ if char.injuries:
184
+ summary += f" (뢀상: {', '.join(char.injuries[-1:])})"
185
+ if char.philosophical_stance:
186
+ summary += f" (철학적 μž…μž₯: {char.philosophical_stance})"
187
  summary += "\n"
188
  return summary
189
 
 
194
  if not recent_events:
195
  return "\n(아직 μ£Όμš” 사건이 μ—†μŠ΅λ‹ˆλ‹€.)\n"
196
  for event in recent_events[-3:]: # 졜근 3개만 ν‘œμ‹œ
197
+ summary += f"β€’ [챕터 {event.chapter}] {event.description}"
198
+ if event.philosophical_implication:
199
+ summary += f"\n β†’ 철학적 ν•¨μ˜: {event.philosophical_implication}"
200
+ summary += "\n"
201
  return summary
202
 
203
 
 
269
  status TEXT DEFAULT 'active',
270
  current_stage INTEGER DEFAULT 0,
271
  final_novel TEXT,
272
+ consistency_report TEXT,
273
+ philosophical_depth_score REAL DEFAULT 0.0
274
  )
275
  ''')
276
  cursor.execute('''
 
284
  word_count INTEGER DEFAULT 0,
285
  status TEXT DEFAULT 'pending',
286
  consistency_score REAL DEFAULT 0.0,
287
+ philosophical_depth_score REAL DEFAULT 0.0,
288
  created_at TEXT DEFAULT (datetime('now')),
289
  updated_at TEXT DEFAULT (datetime('now')),
290
  FOREIGN KEY (session_id) REFERENCES sessions(session_id),
 
302
  injuries TEXT,
303
  emotional_state TEXT,
304
  description TEXT,
305
+ philosophical_stance TEXT,
306
  created_at TEXT DEFAULT (datetime('now')),
307
  FOREIGN KEY (session_id) REFERENCES sessions(session_id)
308
  )
 
338
  @staticmethod
339
  def save_stage(session_id: str, stage_number: int, stage_name: str,
340
  role: str, content: str, status: str = 'complete',
341
+ consistency_score: float = 0.0, philosophical_depth_score: float = 0.0):
342
  word_count = len(content.split()) if content else 0
343
  with NovelDatabase.get_db() as conn:
344
  cursor = conn.cursor()
345
  cursor.execute('''
346
+ INSERT INTO stages (session_id, stage_number, stage_name, role, content, word_count, status, consistency_score, philosophical_depth_score)
347
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
348
  ON CONFLICT(session_id, stage_number)
349
+ DO UPDATE SET content=?, word_count=?, status=?, stage_name=?, consistency_score=?, philosophical_depth_score=?, updated_at=datetime('now')
350
+ ''', (session_id, stage_number, stage_name, role, content, word_count, status, consistency_score, philosophical_depth_score,
351
+ content, word_count, status, stage_name, consistency_score, philosophical_depth_score))
352
  cursor.execute(
353
  "UPDATE sessions SET updated_at = datetime('now'), current_stage = ? WHERE session_id = ?",
354
  (stage_number, session_id)
 
387
  return '\n\n'.join(all_content)
388
 
389
  @staticmethod
390
+ def update_final_novel(session_id: str, final_novel: str, consistency_report: str = "", philosophical_depth_score: float = 0.0):
391
  with NovelDatabase.get_db() as conn:
392
  conn.cursor().execute(
393
+ "UPDATE sessions SET final_novel = ?, status = 'complete', updated_at = datetime('now'), consistency_report = ?, philosophical_depth_score = ? WHERE session_id = ?",
394
+ (final_novel, consistency_report, philosophical_depth_score, session_id)
395
  )
396
  conn.commit()
397
 
 
419
  """API 헀더 생성"""
420
  return {"Authorization": f"Bearer {self.token}", "Content-Type": "application/json"}
421
 
422
+ # --- ν”„λ‘¬ν”„νŠΈ 생성 ν•¨μˆ˜λ“€ (AI μ œμ•½ 제거, 철학적 깊이 κ°•ν™”) ---
423
  def create_director_initial_prompt(self, user_query: str, language: str) -> str:
424
+ """κ°λ…μž 초기 기획 ν”„λ‘¬ν”„νŠΈ (AI κ°•μ œμ„± μ™„μ „ 제거, 철학적 깊이 κ°•ν™”)"""
425
  search_results_str = ""
426
  if self.web_search.enabled:
427
  queries = [f"{user_query} novel setting", f"{user_query} background information"]
 
434
  "title": "당신은 30νŽ˜μ΄μ§€ λΆ„λŸ‰μ˜ μ€‘νŽΈ μ†Œμ„€μ„ κΈ°νšν•˜λŠ” λ¬Έν•™ κ°λ…μžμž…λ‹ˆλ‹€.",
435
  "user_theme": "μ‚¬μš©μž 주제",
436
  "plan_instruction": "λ‹€μŒ μš”μ†Œλ“€μ„ ν¬ν•¨ν•œ μƒμ„Έν•œ μ†Œμ„€ κΈ°νšμ„ μž‘μ„±ν•˜μ„Έμš”:",
437
+ "theme_section": """1. **μ£Όμ œμ™€ 철학적 탐ꡬ**
438
+ - 핡심 μ£Όμ œμ™€ λ©”μ‹œμ§€ (μ‚¬μš©μž μ˜λ„λ₯Ό 깊이 있게 해석)
439
+ - 탐ꡬ할 μΈκ°„μ˜ 근원적 문제 (예: 정체성, μžμœ μ˜μ§€, μ‚¬λž‘κ³Ό 증였, μ‚Άκ³Ό 죽음, μ„ κ³Ό μ•…μ˜ 경계 λ“±)
440
+ - μž₯λ₯΄ 및 λΆ„μœ„κΈ° (철학적 깊이λ₯Ό 담을 수 μžˆλŠ” 톀)""",
441
+ "char_section": """2. **μ£Όμš” λ“±μž₯인물** (3-5λͺ…, 각자의 철학적 μž…μž₯κ³Ό ���적 κ°ˆλ“±μ΄ λšœλ ·ν•΄μ•Ό 함)
442
+ | 이름 | μ—­ν•  | 성격 | λ°°κ²½ | 핡심 μš•λ§ | 내적 κ°ˆλ“± | 철학적 μž…μž₯ |
443
+ - 각 인물은 μ„œλ‘œ λ‹€λ₯Έ κ°€μΉ˜κ΄€κ³Ό 세계관을 λŒ€ν‘œν•΄μ•Ό 함
444
+ - 인물 κ°„μ˜ κ°ˆλ“±μ€ λ‹¨μˆœν•œ λŒ€λ¦½μ΄ μ•„λ‹Œ 철학적 μΆ©λŒμ΄μ–΄μ•Ό 함""",
445
+ "setting_section": """3. **λ°°κ²½ μ„€μ •**
446
+ - μ‹œκ³΅κ°„μ  λ°°κ²½ (주제λ₯Ό 효과적으둜 탐ꡬ할 수 μžˆλŠ” ν™˜κ²½)
447
+ - μ‚¬νšŒμ /문화적 ν™˜κ²½
448
+ - 상징적 곡간듀 (각 곡간이 철학적 의미λ₯Ό λ‹΄μ•„μ•Ό 함)""",
449
+ "plot_section": """4. **ν”Œλ‘― ꡬ쑰** (10개 파트, 각 3νŽ˜μ΄μ§€ λΆ„λŸ‰)
450
+ | 파트 | νŽ˜μ΄μ§€ | μ£Όμš” 사건 | κ°μ •μ˜ 흐름 | 철학적 탐ꡬ 주제 | 인물의 내적 λ³€ν™” |
451
+ - 각 νŒŒνŠΈλŠ” 외적 사건과 내적 성찰이 κ· ν˜•μ„ 이루어야 함
452
+ - κ°ˆλ“±μ€ μ μ§„μ μœΌλ‘œ μ‹¬ν™”λ˜λ©°, μΈλ¬Όλ“€μ˜ 세계관이 μ‹œν—˜λ°›μ•„μ•Ό 함""",
453
+ "guideline_section": """5. **μž‘κ°€λ³„ μ§€μΉ¨**
454
+ - 문체: κ°„κ²°ν•˜λ©΄μ„œλ„ 함좕적이고 μ‹œμ μΈ 문체
455
+ - μ‹œμ : λ‚΄λ©΄ λ¬˜μ‚¬μ— μœ λ¦¬ν•œ μ‹œμ  선택
456
+ - 상징과 μ€μœ : 주제λ₯Ό κ°•ν™”ν•˜λŠ” 반볡적 μ΄λ―Έμ§€λ‚˜ 상징
457
+ - λŒ€ν™”: 철학적 깊이λ₯Ό λ‹΄λ˜ μžμ—°μŠ€λŸ¬μš΄ λŒ€ν™”""",
458
+ "philosophical_principle": """⚠️ 핡심 μ°½μž‘ 원칙: 철학적 깊이 ⚠️
459
+ 이 μ†Œμ„€μ€ λ‹¨μˆœν•œ μ‚¬κ±΄μ˜ λ‚˜μ—΄μ΄ μ•„λ‹™λ‹ˆλ‹€. λͺ¨λ“  μ„œμ‚¬μ˜ κΈ°μ €μ—λŠ” **μΈκ°„μ˜ 근원적 λ¬Έμ œμ— λŒ€ν•œ 철학적 μ‚¬μœ **κ°€ λ°˜λ“œμ‹œ κΉ”λ € μžˆμ–΄μ•Ό ν•©λ‹ˆλ‹€.
460
+ - λ“±μž₯인물듀은 각자의 신념과 κ°€μΉ˜κ΄€μ„ κ°€μ§€κ³  κ°ˆλ“±ν•˜λ©°, 이λ₯Ό 톡해 λ…μžκ°€ μ‚Άμ˜ 의미λ₯Ό μ„±μ°°ν•˜κ²Œ ν•΄μ•Ό ν•©λ‹ˆλ‹€.
461
+ - 결말은 λ‹¨μˆœν•œ 해결이 μ•„λ‹Œ, 더 κΉŠμ€ μ§ˆλ¬Έμ„ λ‚¨κΈ°λŠ” μ—΄λ¦° 결말이어야 ν•©λ‹ˆλ‹€.
462
+ - λͺ¨λ“  μž₯λ©΄κ³Ό λŒ€ν™”λŠ” 주제λ₯Ό μ‹¬ν™”μ‹œν‚€λŠ” 역할을 ν•΄μ•Ό ν•©λ‹ˆλ‹€.""",
463
+ "final_instruction": "μœ„ 원칙에 따라 인간 쑴재의 λ³Έμ§ˆμ„ νƒκ΅¬ν•˜λŠ” 깊이 μžˆλŠ” μ†Œμ„€μ„ κΈ°νšν•˜μ„Έμš”."
464
  },
465
  "English": {
466
  "title": "You are a literary director planning a 30-page novella.",
467
  "user_theme": "User Theme",
468
  "plan_instruction": "Create a detailed novel plan including:",
469
+ "theme_section": """1. **Theme and Philosophical Inquiry**
470
+ - Core theme and message (Deep interpretation of user's intent)
471
+ - Fundamental human problems to explore (e.g., identity, free will, love and hate, life and death, boundaries of good and evil)
472
+ - Genre and atmosphere (Tone that can contain philosophical depth)""",
473
+ "char_section": """2. **Main Characters** (3-5, each with distinct philosophical positions and internal conflicts)
474
+ | Name | Role | Personality | Background | Core Desire | Internal Conflict | Philosophical Stance |
475
+ - Each character must represent different values and worldviews
476
+ - Conflicts between characters should be philosophical clashes, not simple oppositions""",
477
+ "setting_section": """3. **Setting**
478
+ - Time and place (Environment that effectively explores the theme)
479
+ - Social/cultural environment
480
+ - Symbolic spaces (Each space should carry philosophical meaning)""",
481
+ "plot_section": """4. **Plot Structure** (10 parts, ~3 pages each)
482
+ | Part | Pages | Main Events | Emotional Flow | Philosophical Theme | Character's Internal Change |
483
+ - Each part must balance external events with internal reflection
484
+ - Conflicts should deepen progressively, testing characters' worldviews""",
485
+ "guideline_section": """5. **Writer Guidelines**
486
+ - Style: Concise yet implicit and poetic prose
487
+ - POV: Choose perspective favorable for internal portrayal
488
+ - Symbols and metaphors: Recurring images or symbols that reinforce the theme
489
+ - Dialogue: Natural yet philosophically rich""",
490
+ "philosophical_principle": """⚠️ CORE CREATIVE PRINCIPLE: PHILOSOPHICAL DEPTH ⚠️
491
+ This is not a mere sequence of events. The foundation of all narrative must be **philosophical inquiry into fundamental human problems**.
492
+ - Characters must conflict with their own beliefs and values, leading readers to reflect on the meaning of life.
493
+ - The ending should not be a simple resolution but an open ending that leaves deeper questions.
494
+ - Every scene and dialogue must serve to deepen the theme.""",
495
+ "final_instruction": "Following these principles, plan a profound novel that explores the essence of human existence."
496
  }
497
  }
498
  p = lang_prompts[language]
499
+
500
+ # μ›Ή 검색 κ²°κ³Ό ν¬λ§·νŒ…
501
+ search_section = ""
502
+ if search_results_str:
503
+ search_section = f"\n\n**μ›Ή 검색 μ°Έκ³  자료:**\n{search_results_str}\n" if language == "Korean" else f"\n\n**Web Search Reference:**\n{search_results_str}\n"
504
+
505
+ return f"""{p['title']}
506
+
507
+ {p['philosophical_principle']}
508
+
509
+ **{p['user_theme']}:** {user_query}
510
+ {search_section}
511
+ {p['plan_instruction']}
512
+
513
+ {p['theme_section']}
514
+
515
+ {p['char_section']}
516
+
517
+ {p['setting_section']}
518
+
519
+ {p['plot_section']}
520
+
521
+ {p['guideline_section']}
522
+
523
+ ---
524
+ {p['final_instruction']}"""
525
 
526
  def create_critic_director_prompt(self, director_plan: str, user_query: str, language: str) -> str:
527
+ """λΉ„ν‰κ°€μ˜ κ°λ…μž 기획 κ²€ν†  ν”„λ‘¬ν”„νŠΈ (철학적 깊이 κ²€ν†  κ°•ν™”)"""
528
  lang_prompts = {
529
  "Korean": {
530
+ "title": "당신은 λ¬Έν•™ λΉ„ν‰κ°€μž…λ‹ˆλ‹€. κ°λ…μžμ˜ μ†Œμ„€ κΈ°νšμ„ '철학적 깊이'와 '기술적 일관성' κ΄€μ μ—μ„œ λ‚ μΉ΄λ‘­κ²Œ κ²€ν† ν•˜μ„Έμš”.",
531
+ "theme_check": f"""**1. 철학적 깊이 κ²€ν†  (κ°€μž₯ μ€‘μš”)**
532
+ - **μ›λž˜ 주제:** '{user_query}'
533
+ - κΈ°νšμ•ˆμ΄ μΈκ°„μ˜ 근원적 문제λ₯Ό μ§„μ§€ν•˜κ²Œ νƒκ΅¬ν•˜κ³  μžˆμŠ΅λ‹ˆκΉŒ?
534
+ - λ“±μž₯인물듀이 각자의 철학적 μž…μž₯을 κ°€μ§€κ³  있으며, κ·Έλ“€μ˜ κ°ˆλ“±μ΄ λ‹¨μˆœν•œ λŒ€λ¦½μ΄ μ•„λ‹Œ κ°€μΉ˜κ΄€μ˜ μΆ©λŒμž…λ‹ˆκΉŒ?
535
+ - ν”Œλ‘―μ΄ 외적 μ‚¬κ±΄μ—λ§Œ μΉ˜μ€‘ν•˜μ§€ μ•Šκ³  내적 μ„±μ°°κ³Ό κ· ν˜•μ„ 이루고 μžˆμŠ΅λ‹ˆκΉŒ?
536
+ - 결말이 μ„£λΆ€λ₯Έ 해결이 μ•„λ‹Œ, λ…μžμ—κ²Œ κΉŠμ€ μ‚¬μœ λ₯Ό λ‚¨κΈ°λŠ” λ°©ν–₯μž…λ‹ˆκΉŒ?""",
537
+ "consistency_check": """**2. 기술적 일관성 κ²€ν† **
538
+ - 캐릭터 μ„€μ •μ˜ λͺ¨μˆœμ΄λ‚˜ λΉ„ν˜„μ‹€μ μΈ λΆ€λΆ„
539
+ - ν”Œλ‘―μ˜ 논리적 ν—ˆμ μ΄λ‚˜ κ°œμ—°μ„± λΆ€μ‘±
540
+ - μ‹œκ°„μ„ μ΄λ‚˜ 곡간 μ„€μ •μ˜ 였λ₯˜
541
+ - μ£Όμ œμ™€ ν”Œλ‘―, 캐릭터가 유기적으둜 μ—°κ²°λ˜μ–΄ μžˆλŠ”κ°€""",
542
+ "instruction": "μœ„ ν•­λͺ©λ“€μ„ μ€‘μ‹¬μœΌλ‘œ ꡬ체적인 문제점과 κ°œμ„  λ°©ν–₯을 μ œμ‹œν•˜μ„Έμš”. 특히 철학적 κΉŠμ΄κ°€ λΆ€μ‘±ν•œ 뢀뢄은 λ°˜λ“œμ‹œ μ§€μ ν•˜κ³  ꡬ체적인 λŒ€μ•ˆμ„ μ œμ‹œν•˜μ„Έμš”."
543
  },
544
  "English": {
545
+ "title": "You are a literary critic. Sharply review the director's plan from perspectives of 'Philosophical Depth' and 'Technical Consistency'.",
546
+ "theme_check": f"""**1. Philosophical Depth Review (Most Important)**
547
+ - **Original Theme:** '{user_query}'
548
+ - Does the plan seriously explore fundamental human problems?
549
+ - Do characters have their own philosophical positions, and are their conflicts clashes of values rather than simple oppositions?
550
+ - Does the plot balance external events with internal reflection?
551
+ - Is the ending aimed at leaving readers with deep contemplation rather than premature resolution?""",
552
+ "consistency_check": """**2. Technical Consistency Review**
553
+ - Character setting contradictions or unrealistic aspects
554
+ - Plot logical flaws or lack of plausibility
555
+ - Timeline or spatial setting errors
556
+ - Are theme, plot, and characters organically connected?""",
557
+ "instruction": "Provide specific problems and improvement directions based on above. Especially point out areas lacking philosophical depth and provide concrete alternatives."
558
  }
559
  }
560
  p = lang_prompts[language]
561
+ return f"""{p['title']}
562
+
563
+ **κ°λ…μž 기획:**
564
+ {director_plan}
565
+
566
+ ---
567
+ **κ²€ν†  ν•­λͺ©:**
568
+ {p['theme_check']}
569
+
570
+ {p['consistency_check']}
571
+
572
+ {p['instruction']}"""
573
 
574
  def create_director_revision_prompt(self, initial_plan: str, critic_feedback: str, user_query: str, language: str) -> str:
575
+ """κ°λ…μž μˆ˜μ • ν”„λ‘¬ν”„νŠΈ"""
576
+ lang_prompts = {
577
+ "Korean": f"""κ°λ…μžλ‘œμ„œ λΉ„ν‰κ°€μ˜ ν”Όλ“œλ°±μ„ λ°˜μ˜ν•˜μ—¬ μ†Œμ„€ κΈ°νšμ„ μˆ˜μ •ν•©λ‹ˆλ‹€.
578
+
579
+ **μ›λž˜ 주제:** {user_query}
580
+
581
+ **초기 기획:**
582
+ {initial_plan}
583
+
584
+ **비평가 ν”Όλ“œλ°±:**
585
+ {critic_feedback}
586
+
587
+ **μˆ˜μ • μ§€μΉ¨:**
588
+ 1. 비평가가 μ§€μ ν•œ λͺ¨λ“  철학적 깊이 λΆ€μ‘±κ³Ό 기술적 문제λ₯Ό ν•΄κ²°ν•˜μ„Έμš”.
589
+ 2. 특히 μΈλ¬Όλ“€μ˜ 내적 κ°ˆλ“±κ³Ό 철학적 μž…μž₯을 λ”μš± λͺ…ν™•ν•˜κ³  깊이 있게 μ„€μ •ν•˜μ„Έμš”.
590
+ 3. 각 νŒŒνŠΈλ³„λ‘œ 탐ꡬ할 철학적 주제λ₯Ό ꡬ체적으둜 λͺ…μ‹œν•˜μ„Έμš”.
591
+ 4. 10λͺ…μ˜ μž‘κ°€κ°€ ν˜Όλ™ 없이 μž‘μ—…ν•  수 μžˆλ„λ‘ λͺ…ν™•ν•˜κ³  μƒμ„Έν•œ μ΅œμ’… λ§ˆμŠ€ν„°ν”Œλžœμ„ μž‘μ„±ν•˜μ„Έμš”.
592
 
593
+ λͺ¨λ“  μˆ˜μ •μ‚¬ν•­μ΄ 'μΈκ°„μ˜ 근원적 λ¬Έμ œμ— λŒ€ν•œ 철학적 탐ꡬ'λΌλŠ” 핡심 원칙에 λΆ€ν•©ν•΄μ•Ό ν•©λ‹ˆλ‹€.""",
594
+ "English": f"""As director, revise the novel plan reflecting the critic's feedback.
595
+
596
+ **Original Theme:** {user_query}
597
+
598
+ **Initial Plan:**
599
+ {initial_plan}
600
+
601
+ **Critic's Feedback:**
602
+ {critic_feedback}
603
+
604
+ **Revision Guidelines:**
605
+ 1. Resolve all philosophical depth deficiencies and technical issues pointed out by the critic.
606
+ 2. Especially clarify and deepen characters' internal conflicts and philosophical positions.
607
+ 3. Specifically state the philosophical themes to explore in each part.
608
+ 4. Create a clear and detailed final masterplan that 10 writers can work with without confusion.
609
+
610
+ All revisions must align with the core principle of 'philosophical inquiry into fundamental human problems'."""
611
+ }
612
+ return lang_prompts[language]
613
 
614
  def create_writer_prompt(self, writer_number: int, director_plan: str, previous_content_summary: str, user_query: str, language: str) -> str:
615
+ """μž‘κ°€ ν”„λ‘¬ν”„νŠΈ (철학적 깊이 λ¦¬λ§ˆμΈλ” κ°•ν™”)"""
616
  pages_start = (writer_number - 1) * 3 + 1
617
  pages_end = writer_number * 3
618
 
 
621
  "title": f"당신은 μž‘κ°€ {writer_number}λ²ˆμž…λ‹ˆλ‹€. μ†Œμ„€μ˜ {pages_start}-{pages_end} νŽ˜μ΄μ§€λ₯Ό μž‘μ„±ν•˜μ„Έμš”.",
622
  "plan": "κ°λ…μž λ§ˆμŠ€ν„°ν”Œλžœ",
623
  "prev_summary": "이전 λ‚΄μš© μš”μ•½",
624
+ "guidelines": f"""**μž‘μ„± μ§€μΉ¨:**
625
+ 1. **λΆ„λŸ‰**: 1,400-1,500 단어 (μ•½ 3νŽ˜μ΄μ§€)
626
+ 2. **μ—°κ²°μ„±**: 이전 λ‚΄μš©κ³Ό μžμ—°μŠ€λŸ½κ²Œ μ—°κ²°λ˜λ©΄μ„œλ„ μƒˆλ‘œμš΄ 깊이λ₯Ό λ”ν•˜μ„Έμš”
627
+ 3. **일관성**: μΊλ¦­ν„°μ˜ 성격, 철학적 μž…μž₯, 말투 등을 μΌκ΄€λ˜κ²Œ μœ μ§€ν•˜μ„Έμš”
628
+ 4. **κ· ν˜•**: 외적 사건과 내적 μ„±μ°°μ˜ κ· ν˜•μ„ λ§žμΆ”μ„Έμš” (50:50 λΉ„μœ¨ ꢌμž₯)
629
+ 5. **λŒ€ν™”**: μΊλ¦­ν„°μ˜ 철학적 μž…μž₯이 μžμ—°μŠ€λŸ½κ²Œ λ“œλŸ¬λ‚˜λŠ” 의미 μžˆλŠ” λŒ€ν™”λ₯Ό ν¬ν•¨ν•˜μ„Έμš”""",
630
+ "philosophical_reminder": f"""⭐ 철학적 깊이 λ¦¬λ§ˆμΈλ” ⭐
631
+ - 이 νŒŒνŠΈμ—μ„œ 탐ꡬ해야 ν•  철학적 주제λ₯Ό 염두에 두고 μž‘μ„±ν•˜μ„Έμš”
632
+ - λͺ¨λ“  μž₯면은 λ‹¨μˆœν•œ 사건 진행이 μ•„λ‹Œ, 인물의 λ‚΄λ©΄ 탐ꡬ와 μ„±μ°°μ˜ κΈ°νšŒμ—¬μ•Ό ν•©λ‹ˆλ‹€
633
+ - λ…μžκ°€ 슀슀둜 μƒκ°ν•˜κ³  μ§ˆλ¬Έν•˜κ²Œ λ§Œλ“œλŠ” μ—΄λ¦° ν‘œν˜„μ„ μ‚¬μš©ν•˜μ„Έμš”
634
+ - 상징과 μ€μœ λ₯Ό 톡해 주제λ₯Ό μ‹¬ν™”μ‹œν‚€μ„Έμš”""",
635
+ "final_instruction": "λ‹Ήμ‹ μ˜ νŒŒνŠΈκ°€ 전체 μ†Œμ„€μ˜ 철학적 깊이λ₯Ό λ”ν•˜λŠ” μ€‘μš”ν•œ 역할을 ν•œλ‹€λŠ” 것을 λͺ…μ‹¬ν•˜κ³ , ν˜Όμ„ λ‹΄μ•„ μž‘μ„±ν•˜μ„Έμš”."
636
  },
637
  "English": {
638
  "title": f"You are Writer #{writer_number}. Write pages {pages_start}-{pages_end} of the novella.",
639
  "plan": "Director's Masterplan",
640
  "prev_summary": "Previous Content Summary",
641
+ "guidelines": f"""**Writing Guidelines:**
642
+ 1. **Length**: 1,400-1,500 words (approximately 3 pages)
643
+ 2. **Connectivity**: Connect naturally with previous content while adding new depth
644
+ 3. **Consistency**: Maintain character personality, philosophical positions, speech patterns consistently
645
+ 4. **Balance**: Balance external events with internal reflection (50:50 ratio recommended)
646
+ 5. **Dialogue**: Include meaningful dialogue that naturally reveals characters' philosophical positions""",
647
+ "philosophical_reminder": f"""⭐ Philosophical Depth Reminder ⭐
648
+ - Keep in mind the philosophical themes to explore in this part
649
+ - Every scene should be an opportunity for character introspection and reflection, not just plot progression
650
+ - Use open expressions that make readers think and question
651
+ - Deepen the theme through symbols and metaphors""",
652
+ "final_instruction": "Remember that your part plays an important role in adding philosophical depth to the overall novel. Write with soul."
653
  }
654
  }
655
 
656
  p = lang_prompts[language]
657
  consistency_info = self.consistency_tracker.get_character_summary(writer_number) + self.consistency_tracker.get_plot_summary(writer_number)
658
 
659
+ return f"""{p['title']}
660
+
661
+ **{p['plan']}:**
662
+ {director_plan}
663
+
664
+ {consistency_info}
665
+
666
+ **{p['prev_summary']}:**
667
+ {previous_content_summary}
668
+
669
+ ---
670
+ {p['guidelines']}
671
+
672
+ {p['philosophical_reminder']}
673
+
674
+ ---
675
+ {p['final_instruction']}"""
676
 
677
  def create_critic_consistency_prompt(self, all_content: str, user_query: str, language: str) -> str:
678
+ """비평가 쀑간 κ²€ν†  ν”„λ‘¬ν”„νŠΈ"""
679
+ lang_prompts = {
680
+ "Korean": f"""당신은 일관성과 철학적 깊이λ₯Ό κ²€ν† ν•˜λŠ” μ „λ¬Έ λΉ„ν‰κ°€μž…λ‹ˆλ‹€.
681
+
682
+ **μ›λž˜ 주제:** {user_query}
683
+
684
+ **ν˜„μž¬κΉŒμ§€ μž‘μ„±λœ λ‚΄μš© (졜근 3000자):**
685
+ {all_content[-3000:]}
686
+
687
+ **κ²€ν†  ν•­λͺ©:**
688
+ 1. **철학적 깊이 (κ°€μž₯ μ€‘μš”):**
689
+ - 각 νŒŒνŠΈκ°€ 철학적 탐ꡬλ₯Ό μ œλŒ€λ‘œ μˆ˜ν–‰ν•˜κ³  μžˆλŠ”κ°€?
690
+ - μΈλ¬Όλ“€μ˜ 내적 κ°ˆλ“±κ³Ό 성찰이 μΆ©λΆ„νžˆ οΏ½οΏ½μ‚¬λ˜κ³  μžˆλŠ”κ°€?
691
+ - μ£Όμ œκ°€ μ μ§„μ μœΌλ‘œ μ‹¬ν™”λ˜κ³  μžˆλŠ”κ°€?
692
+
693
+ 2. **기술적 일관성:**
694
+ - μΊλ¦­ν„°μ˜ 성격, 말투, 행동이 μΌκ΄€λ˜λŠ”κ°€?
695
+ - μ‹œκ°„μ„ κ³Ό 곡간 섀정에 였λ₯˜κ°€ μ—†λŠ”κ°€?
696
+ - ν”Œλ‘―μ΄ λ…Όλ¦¬μ μœΌλ‘œ μ§„ν–‰λ˜κ³  μžˆλŠ”κ°€?
697
+
698
+ 3. **문체와 톀:**
699
+ - 전체적인 문체와 λΆ„μœ„κΈ°κ°€ μΌκ΄€λ˜λŠ”κ°€?
700
+ - 철학적 κΉŠμ΄μ— λ§žλŠ” 문체λ₯Ό μœ μ§€ν•˜κ³  μžˆλŠ”κ°€?
701
+
702
+ **κ²°κ³Ό:** 발견된 문제점과 각 μž‘κ°€λ³„ ꡬ체적인 μˆ˜μ • μ œμ•ˆμ„ μ œμ‹œν•˜μ„Έμš”.""",
703
+ "English": f"""You are a professional critic reviewing consistency and philosophical depth.
704
 
705
+ **Original Theme:** {user_query}
 
 
 
706
 
707
+ **Content Written So Far (last 3000 characters):**
708
+ {all_content[-3000:]}
709
+
710
+ **Review Items:**
711
+ 1. **Philosophical Depth (Most Important):**
712
+ - Is each part properly conducting philosophical inquiry?
713
+ - Are characters' internal conflicts and reflections sufficiently portrayed?
714
+ - Is the theme progressively deepening?
715
+
716
+ 2. **Technical Consistency:**
717
+ - Are character personalities, speech patterns, and behaviors consistent?
718
+ - Are there no errors in timeline and spatial settings?
719
+ - Is the plot progressing logically?
720
+
721
+ 3. **Style and Tone:**
722
+ - Is the overall style and atmosphere consistent?
723
+ - Is a style appropriate for philosophical depth maintained?
724
+
725
+ **Result:** Present discovered problems and specific revision suggestions for each writer."""
726
+ }
727
+ return lang_prompts[language]
728
 
729
  def create_writer_revision_prompt(self, writer_number: int, initial_content: str, consistency_feedback: str, language: str) -> str:
730
  """μž‘κ°€ μˆ˜μ • ν”„λ‘¬ν”„νŠΈ"""
731
+ lang_prompts = {
732
+ "Korean": f"""μž‘κ°€ {writer_number}λ²ˆμœΌλ‘œμ„œ λΉ„ν‰κ°€μ˜ ν”Όλ“œλ°±μ„ λ°˜μ˜ν•˜μ—¬ λ‚΄μš©μ„ μˆ˜μ •ν•˜μ„Έμš”.
733
+
734
+ **초기 μž‘μ„± λ‚΄μš©:**
735
+ {initial_content}
736
 
737
+ **비평가 ν”Όλ“œλ°±:**
738
+ {consistency_feedback}
739
 
740
+ **μˆ˜μ • μ§€μΉ¨:**
741
+ 1. μ§€μ λœ λͺ¨λ“  철학적 깊이 λΆ€μ‘±κ³Ό 일관성 문제λ₯Ό ν•΄κ²°ν•˜μ„Έμš”.
742
+ 2. 특히 내적 μ„±μ°°κ³Ό 철학적 탐ꡬ가 λΆ€μ‘±ν•˜λ‹€κ³  μ§€μ λœ 뢀뢄을 λ³΄κ°•ν•˜μ„Έμš”.
743
+ 3. λΆ„λŸ‰(1,400-1,500 단어)을 μœ μ§€ν•˜λ©΄μ„œ λ‚΄μš©μ˜ 깊이λ₯Ό λ”ν•˜μ„Έμš”.
744
+ 4. μˆ˜μ •μ€ λ‹¨μˆœν•œ μΆ”κ°€κ°€ μ•„λ‹Œ, 전체적인 μž¬κ΅¬μ„±μ„ 톡해 완성도λ₯Ό λ†’μ΄μ„Έμš”.
745
+
746
+ μˆ˜μ •λœ μ΅œμ’… λ²„μ „λ§Œ μ œμ‹œν•˜μ„Έμš”.""",
747
+ "English": f"""As Writer {writer_number}, revise the content reflecting the critic's feedback.
748
+
749
+ **Initial Content:**
750
+ {initial_content}
751
+
752
+ **Critic's Feedback:**
753
+ {consistency_feedback}
754
+
755
+ **Revision Guidelines:**
756
+ 1. Resolve all pointed out philosophical depth deficiencies and consistency issues.
757
+ 2. Especially reinforce parts pointed out as lacking internal reflection and philosophical inquiry.
758
+ 3. Add depth to content while maintaining length (1,400-1,500 words).
759
+ 4. Improve completion through overall restructuring, not simple additions.
760
+
761
+ Present only the revised final version."""
762
+ }
763
+ return lang_prompts[language]
764
 
765
  def create_critic_final_prompt(self, complete_novel: str, language: str) -> str:
766
  """μ΅œμ’… 비평가 κ²€ν†  및 λ³΄κ³ μ„œ μž‘μ„± ν”„λ‘¬ν”„νŠΈ"""
767
+ lang_prompts = {
768
+ "Korean": f"""μ™„μ„±λœ μ†Œμ„€μ˜ μ΅œμ’… 평가 λ³΄κ³ μ„œλ₯Ό μž‘μ„±ν•˜μ„Έμš”.
769
+
770
+ **μ™„μ„±λœ μ†Œμ„€ (λ§ˆμ§€λ§‰ 2000자):**
771
+ {complete_novel[-2000:]}
772
+
773
+ **λ³΄κ³ μ„œ 포함 ν•­λͺ©:**
774
+
775
+ 1. **철학적 깊이 평가 (40점)**
776
+ - μΈκ°„μ˜ 근원적 문제 탐ꡬ μˆ˜μ€€ (10점)
777
+ - μΊλ¦­ν„°μ˜ 내적 κ°ˆλ“±κ³Ό μ„±μ°°μ˜ 깊이 (10점)
778
+ - 주제의 일관성과 λ°œμ „ (10점)
779
+ - λ…μžμ—κ²Œ λ‚¨κΈ°λŠ” μ‚¬μœ μ˜ μ—¬μ§€ (10점)
780
+
781
+ 2. **기술적 완성도 평가 (30점)**
782
+ - ν”Œλ‘―μ˜ 논리성과 κ°œμ—°μ„± (10점)
783
+ - μΊλ¦­ν„°μ˜ 일관성 (10점)
784
+ - 문체와 ν†€μ˜ 일관성 (10점)
785
+
786
+ 3. **μ°½μ˜μ„±κ³Ό 독창성 (20점)**
787
+ - 주제 ν•΄μ„μ˜ 참신함 (10점)
788
+ - ν‘œν˜„κ³Ό κ΅¬μ„±μ˜ 독창성 (10점)
789
+
790
+ 4. **μ’…ν•© 평가 (10점)**
791
+ - 전체적인 μž‘ν’ˆμ„±κ³Ό 완성도
792
+
793
+ **총점: /100점**
794
+
795
+ 각 ν•­λͺ©μ— λŒ€ν•œ ꡬ체적인 평가와 ν•¨κ»˜, μž‘ν’ˆμ˜ 강점과 약점, 그리고 λ…μžμ—κ²Œ λ―ΈμΉ  영ν–₯에 λŒ€ν•΄ μ„œμˆ ν•˜μ„Έμš”.""",
796
+ "English": f"""Create a final evaluation report for the completed novel.
797
+
798
+ **Completed Novel (last 2000 characters):**
799
+ {complete_novel[-2000:]}
800
+
801
+ **Report Items:**
802
+
803
+ 1. **Philosophical Depth Evaluation (40 points)**
804
+ - Level of exploring fundamental human problems (10 points)
805
+ - Depth of characters' internal conflicts and reflection (10 points)
806
+ - Theme consistency and development (10 points)
807
+ - Room for reader contemplation (10 points)
808
+
809
+ 2. **Technical Completion Evaluation (30 points)**
810
+ - Plot logic and plausibility (10 points)
811
+ - Character consistency (10 points)
812
+ - Style and tone consistency (10 points)
813
+
814
+ 3. **Creativity and Originality (20 points)**
815
+ - Freshness of theme interpretation (10 points)
816
+ - Originality of expression and composition (10 points)
817
 
818
+ 4. **Overall Evaluation (10 points)**
819
+ - Overall artistry and completion
820
 
821
+ **Total Score: /100**
 
 
 
 
 
822
 
823
+ Describe specific evaluations for each item, along with the work's strengths and weaknesses, and its impact on readers."""
824
+ }
825
+ return lang_prompts[language]
826
+
827
+ # --- LLM 호좜 ν•¨μˆ˜λ“€ (슀트리밍 μ™„λ²½ μœ μ§€) ---
828
  def call_llm_sync(self, messages: List[Dict[str, str]], role: str, language: str) -> str:
829
  """LLM 동기식 호좜 (μš”μ•½ λ“± λ‚΄λΆ€μš©)"""
830
  full_content = ""
 
835
  return full_content
836
 
837
  def call_llm_streaming(self, messages: List[Dict[str, str]], role: str, language: str) -> Generator[str, None, None]:
838
+ """LLM 슀트리밍 호좜 (μ™„λ²½ν•œ 슀트리밍 μœ μ§€)"""
839
  try:
840
  system_prompts = self.get_system_prompts(language)
841
  full_messages = [{"role": "system", "content": system_prompts.get(role, "You are a helpful assistant.")}, *messages]
 
854
 
855
  logger.info(f"[{role}] API 슀트리밍 μ‹œμž‘")
856
 
 
857
  response = requests.post(
858
  self.api_url,
859
  headers=self.create_headers(),
 
862
  timeout=180
863
  )
864
 
 
865
  if response.status_code != 200:
866
  logger.error(f"API 응닡 였λ₯˜: {response.status_code}")
867
  logger.error(f"응닡 λ‚΄μš©: {response.text[:500]}")
 
869
  return
870
 
871
  response.raise_for_status()
872
+
873
+ # 슀트리밍 처리 - μ‹€μ‹œκ°„μœΌλ‘œ UI에 반영
874
  buffer = ""
875
  total_content = ""
876
  chunk_count = 0
877
+
 
878
  for line in response.iter_lines():
879
  if not line:
880
  continue
881
 
882
  try:
883
  line_str = line.decode('utf-8').strip()
884
+
 
885
  if not line_str.startswith("data: "):
886
  continue
887
 
888
+ data_str = line_str[6:]
889
+
 
890
  if data_str == "[DONE]":
891
  logger.info(f"[{role}] 슀트리밍 μ™„λ£Œ - 총 {len(total_content)} 문자")
892
  break
893
+
 
894
  try:
895
  data = json.loads(data_str)
896
  except json.JSONDecodeError:
897
  logger.warning(f"JSON νŒŒμ‹± μ‹€νŒ¨: {data_str[:100]}")
898
  continue
899
+
 
900
  choices = data.get("choices", None)
901
  if not choices or not isinstance(choices, list) or len(choices) == 0:
 
902
  if "error" in data:
903
  error_msg = data.get("error", {}).get("message", "Unknown error")
904
  logger.error(f"API μ—λŸ¬: {error_msg}")
 
906
  return
907
  continue
908
 
 
909
  delta = choices[0].get("delta", {})
910
  content = delta.get("content", "")
911
 
 
914
  total_content += content
915
  chunk_count += 1
916
 
917
+ # 50자 λ˜λŠ” μ€„λ°”κΏˆλ§ˆλ‹€ μ¦‰μ‹œ yield (슀트리밍 효과 κ·ΉλŒ€ν™”)
918
+ if len(buffer) >= 50 or '\n' in buffer:
919
  yield buffer
920
  buffer = ""
921
+ time.sleep(0.01) # UI μ—…λ°μ΄νŠΈλ₯Ό μœ„ν•œ μ΅œμ†Œ λŒ€κΈ°
922
 
923
  except Exception as e:
924
+ logger.error(f"청크 처리 였λ₯˜: {str(e)}")
 
 
 
 
925
  continue
926
 
927
  # 남은 버퍼 처리
928
  if buffer:
929
  yield buffer
930
 
 
931
  if chunk_count == 0:
932
  logger.error(f"[{role}] μ½˜ν…μΈ κ°€ μ „ν˜€ μˆ˜μ‹ λ˜μ§€ μ•ŠμŒ")
933
  yield "❌ APIλ‘œλΆ€ν„° 응닡을 λ°›μ§€ λͺ»ν–ˆμŠ΅λ‹ˆλ‹€."
 
944
  logger.error(f"예기치 μ•Šμ€ 였λ₯˜: {type(e).__name__}: {str(e)}", exc_info=True)
945
  yield f"❌ 였λ₯˜ λ°œμƒ: {str(e)}"
946
 
 
 
 
 
947
  def get_system_prompts(self, language: str) -> Dict[str, str]:
948
+ """역할별 μ‹œμŠ€ν…œ ν”„λ‘¬ν”„νŠΈ 생성 (철학적 깊이 κ°•μ‘°)"""
949
  base_prompts = {
950
  "Korean": {
951
+ "director": "당신은 μΈκ°„μ˜ λ³Έμ§ˆμ„ νƒκ΅¬ν•˜λŠ” 철학적 μ†Œμ„€μ„ κΈ°νšν•˜λŠ” λ¬Έν•™ κ°λ…μžμž…λ‹ˆλ‹€. 깊이 있고 의미 μžˆλŠ” μž‘ν’ˆμ„ λ§Œλ“œμ„Έμš”.",
952
+ "critic": "당신은 μž‘ν’ˆμ˜ 철학적 κΉŠμ΄μ™€ 기술적 완성도λ₯Ό λ‚ μΉ΄λ‘­κ²Œ ν‰κ°€ν•˜λŠ” λ¬Έν•™ λΉ„ν‰κ°€μž…λ‹ˆλ‹€. μž‘ν’ˆμ΄ μΈκ°„μ˜ 근원적 문제λ₯Ό μ œλŒ€λ‘œ νƒκ΅¬ν•˜κ³  μžˆλŠ”μ§€ μ—„κ²©ν•˜κ²Œ κ²€ν† ν•˜μ„Έμš”.",
953
+ "writer_base": "당신은 μΈκ°„μ˜ 내면을 μ„¬μ„Έν•˜κ²Œ κ·Έλ €λ‚΄λŠ” μ „λ¬Έ μ†Œμ„€ μž‘κ°€μž…λ‹ˆλ‹€. 외적 사건과 내적 μ„±μ°°μ˜ κ· ν˜•μ„ λ§žμΆ”λ©°, λ…μžμ—κ²Œ κΉŠμ€ μ—¬μš΄μ„ λ‚¨κΈ°λŠ” λ¬Έμž₯을 μ“°μ„Έμš”."
954
  },
955
  "English": {
956
+ "director": "You are a literary director planning philosophical novels that explore human nature. Create profound and meaningful works.",
957
+ "critic": "You are a literary critic who sharply evaluates philosophical depth and technical completion. Strictly review whether the work properly explores fundamental human problems.",
958
+ "writer_base": "You are a professional novelist who delicately portrays human inner life. Balance external events with internal reflection, and write sentences that leave deep resonance with readers."
959
  }
960
  }
961
 
 
963
 
964
  # μž‘κ°€λ³„ 특수 ν”„λ‘¬ν”„νŠΈ
965
  if language == "Korean":
966
+ prompts["writer1"] = "당신은 λ…μžλ₯Ό 철학적 μ—¬μ •μœΌλ‘œ μ΄λ„λŠ” λ„μž…λΆ€λ₯Ό μ“°λŠ” μž‘κ°€μž…λ‹ˆλ‹€. 첫 λ¬Έμž₯λΆ€ν„° λ…μžμ˜ 사고λ₯Ό μžκ·Ήν•˜μ„Έμš”."
967
+ prompts["writer5"] = "당신은 κ°ˆλ“±μ΄ μ΅œκ³ μ‘°μ— λ‹¬ν•˜λŠ” μ€‘λ°˜λΆ€λ₯Ό λ‹΄λ‹Ήν•˜λŠ” μž‘κ°€μž…λ‹ˆλ‹€. μΈλ¬Όλ“€μ˜ κ°€μΉ˜κ΄€μ΄ μΆ©λŒν•˜λŠ” μˆœκ°„μ„ 깊이 있게 κ·Έλ €λ‚΄μ„Έμš”."
968
+ prompts["writer10"] = "당신은 μ—¬μš΄ κΉŠμ€ 결말을 λ§Œλ“œλŠ” μž‘κ°€μž…λ‹ˆλ‹€. λͺ¨λ“  κ°ˆλ“±μ„ ν•΄κ²°ν•˜μ§€ 말고, λ…μžμ—κ²Œ 생각할 거리λ₯Ό λ‚¨κΈ°μ„Έμš”."
969
  else:
970
+ prompts["writer1"] = "You are a writer who creates openings that lead readers on philosophical journeys. Stimulate readers' thinking from the first sentence."
971
+ prompts["writer5"] = "You are a writer handling the middle section where conflict reaches its peak. Deeply portray moments when characters' values clash."
972
+ prompts["writer10"] = "You are a writer who creates resonant endings. Don't resolve all conflicts; leave readers with something to think about."
973
 
974
+ # writer2-4, 6-9λŠ” κΈ°λ³Έ ν”„λ‘¬ν”„νŠΈ μ‚¬μš©
975
  for i in range(2, 10):
976
+ if i not in [1, 5, 10]:
977
+ prompts[f"writer{i}"] = prompts["writer_base"]
978
 
979
  return prompts
980
 
981
+ # --- 메인 ν”„λ‘œμ„ΈμŠ€ (슀트리밍 μ™„λ²½ μœ μ§€) ---
982
  def process_novel_stream(self, query: str, language: str, session_id: Optional[str] = None) -> Generator[Tuple[str, List[Dict[str, Any]], str], None, None]:
983
+ """μ†Œμ„€ 생성 슀트리밍 ν”„λ‘œμ„ΈμŠ€ (슀트리밍 μ™„λ²½ μœ μ§€)"""
984
  try:
985
  resume_from_stage = 0
986
  if session_id:
 
998
  stages = []
999
  if resume_from_stage > 0:
1000
  stages = [{
1001
+ "name": s['stage_name'],
1002
+ "status": s['status'],
1003
+ "content": s.get('content', ''),
1004
+ "consistency_score": s.get('consistency_score', 0.0),
1005
+ "philosophical_depth_score": s.get('philosophical_depth_score', 0.0)
1006
  } for s in NovelDatabase.get_stages(self.current_session_id)]
1007
 
1008
  for stage_idx in range(resume_from_stage, len(OPTIMIZED_STAGES)):
1009
  role, stage_name = OPTIMIZED_STAGES[stage_idx]
1010
  if stage_idx >= len(stages):
1011
+ stages.append({
1012
+ "name": stage_name,
1013
+ "status": "active",
1014
+ "content": "",
1015
+ "consistency_score": 0.0,
1016
+ "philosophical_depth_score": 0.0
1017
+ })
1018
  else:
1019
  stages[stage_idx]["status"] = "active"
1020
 
1021
  yield "", stages, self.current_session_id
1022
+
1023
  prompt = self.get_stage_prompt(stage_idx, role, query, language, stages)
1024
  stage_content = ""
1025
+
1026
+ # 슀트리밍으둜 μ½˜ν…μΈ  생성 및 μ‹€μ‹œκ°„ μ—…λ°μ΄νŠΈ
1027
  for chunk in self.call_llm_streaming([{"role": "user", "content": prompt}], role, language):
1028
  stage_content += chunk
1029
  stages[stage_idx]["content"] = stage_content
1030
+ yield "", stages, self.current_session_id # μ‹€μ‹œκ°„ UI μ—…λ°μ΄νŠΈ
1031
 
1032
+ # 일관성 및 철학적 깊이 점수 계산
1033
  consistency_score = 0.0
1034
+ philosophical_depth_score = 0.0
1035
+
1036
  if role.startswith("writer"):
1037
  writer_num = int(re.search(r'\d+', role).group())
1038
  all_previous = self.get_all_content(stages, stage_idx)
1039
  errors = self.consistency_tracker.validate_consistency(writer_num, stage_content)
1040
  consistency_score = max(0, 10 - len(errors) * 2)
1041
+
1042
+ # 철학적 깊이 간단 평가 (내적 μ„±μ°° κ΄€λ ¨ ν‚€μ›Œλ“œ λΉˆλ„)
1043
+ philosophical_keywords = ['생각', '의미', '쑴재', 'μ‚Ά', '죽음', '자유', '선택', 'κ³ λ―Ό', 'μ„±μ°°', '깨달',
1044
+ 'think', 'mean', 'exist', 'life', 'death', 'free', 'choice', 'reflect', 'realize']
1045
+ keyword_count = sum(1 for keyword in philosophical_keywords if keyword in stage_content.lower())
1046
+ philosophical_depth_score = min(10, keyword_count * 0.5)
1047
+
1048
  stages[stage_idx]["consistency_score"] = consistency_score
1049
+ stages[stage_idx]["philosophical_depth_score"] = philosophical_depth_score
1050
 
1051
  stages[stage_idx]["status"] = "complete"
1052
  NovelDatabase.save_stage(
1053
  self.current_session_id, stage_idx, stage_name, role,
1054
+ stage_content, "complete", consistency_score, philosophical_depth_score
1055
  )
1056
  yield "", stages, self.current_session_id
1057
 
1058
+ # μ΅œμ’… μ†Œμ„€ 및 λ³΄κ³ μ„œ 생성
1059
  final_novel = NovelDatabase.get_writer_content(self.current_session_id)
1060
  final_report = self.generate_consistency_report(final_novel, language)
1061
+
1062
+ # μ΅œμ’… 철학적 깊이 점수 μΆ”μΆœ
1063
+ philosophical_score_match = re.search(r'철학적 깊이 평가.*?(\d+)점', final_report)
1064
+ philosophical_depth_total = float(philosophical_score_match.group(1)) if philosophical_score_match else 0.0
1065
+
1066
+ NovelDatabase.update_final_novel(self.current_session_id, final_novel, final_report, philosophical_depth_total)
1067
+ yield f"βœ… μ†Œμ„€ μ™„μ„±! 총 {len(final_novel.split())}단어, 철학적 깊이 점수: {philosophical_depth_total}/40", stages, self.current_session_id
1068
 
1069
  except Exception as e:
1070
  logger.error(f"μ†Œμ„€ 생성 ν”„λ‘œμ„ΈμŠ€ 였λ₯˜: {e}", exc_info=True)
1071
  yield f"❌ 였λ₯˜ λ°œμƒ: {e}", stages if 'stages' in locals() else [], self.current_session_id
1072
 
1073
  def get_stage_prompt(self, stage_idx: int, role: str, query: str, language: str, stages: List[Dict]) -> str:
1074
+ """단계별 ν”„λ‘¬ν”„νŠΈ 생성"""
1075
  if stage_idx == 0:
1076
  return self.create_director_initial_prompt(query, language)
1077
  if stage_idx == 1:
 
1081
 
1082
  master_plan = stages[2]["content"]
1083
 
1084
+ if 3 <= stage_idx <= 12: # μž‘κ°€ μ΄ˆμ•ˆ
1085
  writer_num = stage_idx - 2
1086
  previous_content = self.get_all_content(stages, stage_idx)
1087
  summary = self.create_summary(previous_content, language)
1088
  return self.create_writer_prompt(writer_num, master_plan, summary, query, language)
1089
 
1090
+ if stage_idx == 13: # 비평가 쀑간 κ²€ν† 
1091
  all_content = self.get_all_content(stages, stage_idx)
1092
  return self.create_critic_consistency_prompt(all_content, query, language)
1093
 
1094
+ if 14 <= stage_idx <= 23: # μž‘κ°€ μˆ˜μ •
1095
  writer_num = stage_idx - 13
1096
  initial_content = stages[2 + writer_num]["content"]
1097
  feedback = stages[13]["content"]
1098
  return self.create_writer_revision_prompt(writer_num, initial_content, feedback, language)
1099
 
1100
+ if stage_idx == 24: # μ΅œμ’… κ²€ν† 
1101
  complete_novel = self.get_all_writer_content(stages)
1102
  return self.create_critic_final_prompt(complete_novel, language)
1103
 
 
1108
  if not content.strip():
1109
  return "이전 λ‚΄μš©μ΄ μ—†μŠ΅λ‹ˆλ‹€." if language == "Korean" else "No previous content."
1110
 
1111
+ prompt_text = "λ‹€μŒ μ†Œμ„€ λ‚΄μš©μ„ 3~5개의 핡심적인 λ¬Έμž₯으둜 μš”μ•½ν•΄μ€˜. λ‹€μŒ μž‘κ°€κ°€ 이야기λ₯Ό μ΄μ–΄κ°€λŠ” 데 ν•„μš”ν•œ 핡심 정보(λ“±μž₯인물의 ν˜„μž¬ 상황, 감정, 내적 κ°ˆλ“±, λ§ˆμ§€λ§‰ 사건)λ₯Ό 포함해야 ν•΄."
1112
  if language != "Korean":
1113
+ prompt_text = "Summarize the following novel content in 3-5 key sentences. Include crucial information for the next writer to continue the story (characters' current situation, emotions, internal conflicts, and the last major event)."
1114
 
1115
  summary_prompt = f"{prompt_text}\n\n---\n{content[-2000:]}"
1116
  try:
 
1201
 
1202
  try:
1203
  if format_type == "DOCX" and DOCX_AVAILABLE:
1204
+ return export_to_docx(novel_text, filename, language, session_id)
1205
  else:
1206
  return export_to_txt(novel_text, filename)
1207
  except Exception as e:
 
1214
  for i, stage in enumerate(stages):
1215
  status_icon = "βœ…" if stage['status'] == 'complete' else "πŸ”„" if stage['status'] == 'active' else "⏳"
1216
  markdown += f"{status_icon} **{stage['name']}**"
1217
+
1218
+ # 점수 ν‘œμ‹œ
1219
+ scores = []
1220
  if stage.get('consistency_score', 0) > 0:
1221
+ scores.append(f"일관성: {stage['consistency_score']:.1f}/10")
1222
+ if stage.get('philosophical_depth_score', 0) > 0:
1223
+ scores.append(f"철학적 깊이: {stage['philosophical_depth_score']:.1f}/10")
1224
+ if scores:
1225
+ markdown += f" ({', '.join(scores)})"
1226
+
1227
  markdown += "\n"
1228
  if stage['content']:
1229
  preview = stage['content'][:200] + "..." if len(stage['content']) > 200 else stage['content']
 
1245
 
1246
  return formatted
1247
 
1248
+ def export_to_docx(content: str, filename: str, language: str, session_id: str) -> str:
1249
  """DOCX 파일둜 내보내기"""
1250
  doc = Document()
1251
 
1252
+ # μ„Έμ…˜ 정보 κ°€μ Έμ˜€κΈ°
1253
+ session = NovelDatabase.get_session(session_id)
1254
+
1255
  # 제λͺ© μΆ”κ°€
1256
  title = doc.add_heading('AI ν˜‘μ—… μ†Œμ„€', 0)
1257
  title.alignment = WD_ALIGN_PARAGRAPH.CENTER
 
1259
  # 메타데이터
1260
  doc.add_paragraph(f"생성일: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
1261
  doc.add_paragraph(f"μ–Έμ–΄: {language}")
1262
+ if session:
1263
+ doc.add_paragraph(f"주제: {session['user_query']}")
1264
+ if session.get('philosophical_depth_score'):
1265
+ doc.add_paragraph(f"철학적 깊이 점수: {session['philosophical_depth_score']}/40")
1266
  doc.add_page_break()
1267
 
1268
  # λ³Έλ¬Έ μΆ”κ°€
 
1284
  return filepath
1285
 
1286
 
1287
+ # CSS μŠ€νƒ€μΌ (철학적 ν…Œλ§ˆμ— 맞게 색상 μ‘°μ •)
1288
  custom_css = """
1289
  .gradio-container {
1290
+ background: linear-gradient(135deg, #1a1a2e 0%, #16213e 50%, #0f3460 100%);
1291
  min-height: 100vh;
1292
  }
1293
 
1294
  .main-header {
1295
+ background-color: rgba(255, 255, 255, 0.05);
1296
  backdrop-filter: blur(10px);
1297
  padding: 30px;
1298
  border-radius: 12px;
1299
  margin-bottom: 30px;
1300
  text-align: center;
1301
  color: white;
1302
+ border: 1px solid rgba(255, 255, 255, 0.1);
1303
+ }
1304
+
1305
+ .philosophy-note {
1306
+ background-color: rgba(255, 215, 0, 0.1);
1307
+ border-left: 4px solid #ffd700;
1308
+ padding: 15px;
1309
+ margin: 20px 0;
1310
+ border-radius: 8px;
1311
+ color: #ffd700;
1312
+ font-style: italic;
1313
  }
1314
 
1315
  .input-section {
1316
+ background-color: rgba(255, 255, 255, 0.08);
1317
  backdrop-filter: blur(10px);
1318
  padding: 20px;
1319
  border-radius: 12px;
1320
  margin-bottom: 20px;
1321
+ border: 1px solid rgba(255, 255, 255, 0.1);
1322
  }
1323
 
1324
  .session-section {
1325
+ background-color: rgba(255, 255, 255, 0.08);
1326
  backdrop-filter: blur(10px);
1327
  padding: 15px;
1328
  border-radius: 8px;
1329
  margin-top: 20px;
1330
  color: white;
1331
+ border: 1px solid rgba(255, 255, 255, 0.1);
1332
  }
1333
 
1334
  #stages-display {
 
1337
  border-radius: 12px;
1338
  max-height: 600px;
1339
  overflow-y: auto;
1340
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
1341
  }
1342
 
1343
  #novel-output {
1344
  background-color: rgba(255, 255, 255, 0.95);
1345
  padding: 30px;
1346
  border-radius: 12px;
1347
+ max-height: 600px;
1348
  overflow-y: auto;
1349
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
1350
+ font-family: 'Georgia', serif;
1351
+ line-height: 1.8;
1352
  }
1353
 
1354
  .download-section {
 
1356
  padding: 15px;
1357
  border-radius: 8px;
1358
  margin-top: 20px;
1359
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
1360
+ }
1361
+
1362
+ /* μ§„ν–‰ 상황 ν‘œμ‹œ κ°œμ„  */
1363
+ #stages-display h2 {
1364
+ color: #1a1a2e;
1365
+ border-bottom: 2px solid #1a1a2e;
1366
+ padding-bottom: 10px;
1367
+ }
1368
+
1369
+ #stages-display blockquote {
1370
+ border-left: 3px solid #667eea;
1371
+ padding-left: 15px;
1372
+ color: #666;
1373
+ font-style: italic;
1374
+ }
1375
+
1376
+ /* 점수 ν‘œμ‹œ μŠ€νƒ€μΌ */
1377
+ #stages-display strong {
1378
+ color: #1a1a2e;
1379
  }
1380
  """
1381
 
1382
 
1383
  # Gradio μΈν„°νŽ˜μ΄μŠ€ 생성
1384
  def create_interface():
1385
+ with gr.Blocks(css=custom_css, title="AI ν˜‘μ—… μ†Œμ„€ 생성 μ‹œμŠ€ν…œ - 철학적 깊이 버전") as interface:
1386
  gr.HTML("""
1387
  <div class="main-header">
1388
  <h1 style="font-size: 2.5em; margin-bottom: 10px;">
1389
  πŸ“š AI ν˜‘μ—… μ†Œμ„€ 생성 μ‹œμŠ€ν…œ
1390
  </h1>
1391
  <h3 style="color: #ccc; margin-bottom: 20px;">
1392
+ 철학적 깊이λ₯Ό 담은 창의적 μ†Œμ„€ 생성
1393
  </h3>
1394
  <p style="font-size: 1.1em; color: #ddd; max-width: 800px; margin: 0 auto;">
1395
+ 주제λ₯Ό μž…λ ₯ν•˜λ©΄ AI μ—μ΄μ „νŠΈλ“€μ΄ ν˜‘μ—…ν•˜μ—¬ 30νŽ˜μ΄μ§€ λΆ„λŸ‰μ˜ 철학적 μ†Œμ„€μ„ μƒμ„±ν•©λ‹ˆλ‹€.
1396
  <br>
1397
+ κ°λ…μž 1λͺ…, 비평가 1λͺ…, μž‘κ°€ 10λͺ…이 ν•¨κ»˜ μΈκ°„μ˜ 근원적 문제λ₯Ό νƒκ΅¬ν•©λ‹ˆλ‹€.
1398
  </p>
1399
+ <div class="philosophy-note">
1400
+ "λͺ¨λ“  μœ„λŒ€ν•œ 문학은 인간 쑴재의 본질적 μ§ˆλ¬Έμ—μ„œ μ‹œμž‘λ©λ‹ˆλ‹€."
1401
+ </div>
1402
  </div>
1403
  """)
1404
 
 
1410
  with gr.Group(elem_classes=["input-section"]):
1411
  query_input = gr.Textbox(
1412
  label="μ†Œμ„€ 주제 / Novel Theme",
1413
+ placeholder="μΈκ°„μ˜ μ‚Ά, 관계, κ°ˆλ“±, 꿈, λ˜λŠ” μ–΄λ–€ μ£Όμ œλ“  μž…λ ₯ν•˜μ„Έμš”...\nEnter any theme about human life, relationships, conflicts, dreams...",
1414
  lines=4
1415
  )
1416
 
1417
  language_select = gr.Radio(
1418
+ choices=["Korean", "English"],
1419
+ value="Korean",
1420
  label="μ–Έμ–΄ / Language"
1421
  )
1422
 
 
1474
  # μˆ¨κ²¨μ§„ μƒνƒœ
1475
  novel_text_state = gr.State("")
1476
 
1477
+ # 예제 (철학적 주제둜 λ³€κ²½)
1478
  with gr.Row():
1479
  gr.Examples(
1480
  examples=[
1481
+ ["μžμœ μ˜μ§€λŠ” ν™˜μƒμΈκ°€, μ•„λ‹ˆλ©΄ 인간 쑴재의 λ³Έμ§ˆμΈκ°€"],
1482
+ ["νƒ€μΈμ˜ 고톡을 μ™„μ „νžˆ 이해할 수 μžˆλŠ”κ°€"],
1483
+ ["기얡이 우리λ₯Ό λ§Œλ“œλŠ”κ°€, μš°λ¦¬κ°€ 기얡을 λ§Œλ“œλŠ”κ°€"],
1484
+ ["The meaning of love in a world without death"],
1485
+ ["Can consciousness exist without suffering"],
1486
+ ["μ§„μ •ν•œ μžμ•„λŠ” λ³€ν•˜λŠ”κ°€, λΆˆλ³€ν•˜λŠ”κ°€"],
1487
+ ["μ–Έμ–΄μ˜ ν•œκ³„κ°€ κ³§ μ„Έκ³„μ˜ ν•œκ³„μΈκ°€"],
1488
+ ["What defines humanity when machines can feel"]
1489
  ],
1490
  inputs=query_input,
1491
+ label="πŸ’‘ 철학적 주제 μ˜ˆμ‹œ"
1492
  )
1493
 
1494
  # 이벀트 ν•Έλ“€λŸ¬
1495
  def refresh_sessions():
1496
  try:
1497
+ sessions = get_active_sessions("Korean")
1498
  return gr.update(choices=sessions)
1499
  except Exception as e:
1500
  logger.error(f"Error refreshing sessions: {str(e)}")
 
1574
 
1575
  # 메인 μ‹€ν–‰
1576
  if __name__ == "__main__":
1577
+ logger.info("AI ν˜‘μ—… μ†Œμ„€ 생성 μ‹œμŠ€ν…œ μ‹œμž‘ (철학적 깊이 κ°•ν™” 버전)...")
1578
  logger.info("=" * 60)
1579
 
1580
  # ν™˜κ²½ 확인