openfree commited on
Commit
61ae973
ยท
verified ยท
1 Parent(s): a81bc54

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +390 -247
app.py CHANGED
@@ -52,9 +52,10 @@ selected_language = "English" # ๊ธฐ๋ณธ ์–ธ์–ด
52
  DB_PATH = "novel_sessions.db"
53
  db_lock = threading.Lock()
54
 
55
- # Stage ๋ฒˆํ˜ธ ์ƒ์ˆ˜ - 16๋ช…์˜ ์ž‘๊ฐ€ ๋ฐ˜์˜
56
- WRITER_DRAFT_STAGES = [3, 6, 9, 12, 15, 18, 21, 24, 27, 30, 33, 36, 39, 42, 45, 48] # ์ž‘๊ฐ€ ์ดˆ์•ˆ
57
- WRITER_REVISION_STAGES = [5, 8, 11, 14, 17, 20, 23, 26, 29, 32, 35, 38, 41, 44, 47, 50] # ์ž‘๊ฐ€ ์ˆ˜์ •๋ณธ
 
58
 
59
  class WebSearchIntegration:
60
  """Brave Search API integration for research"""
@@ -149,7 +150,7 @@ Source: {url}
149
  f"{topic} ๊ตฌ์ฒด์  ์žฅ๋ฉด ๋ฌ˜์‚ฌ",
150
  f"{topic} ์ „๋ฌธ ์šฉ์–ด ์„ค๋ช…"
151
  ]
152
- elif writer_num <= 8: # ์ค‘๋ฐ˜๋ถ€ ์ž‘๊ฐ€
153
  queries = [
154
  f"{topic} ๊ฐˆ๋“ฑ ์ƒํ™ฉ ์‚ฌ๋ก€",
155
  f"{topic} ์‹ฌ๋ฆฌ์  ์ธก๋ฉด"
@@ -178,7 +179,7 @@ Source: {url}
178
  f"{topic} vivid scene descriptions",
179
  f"{topic} technical terminology explained"
180
  ]
181
- elif writer_num <= 8: # Middle writers
182
  queries = [
183
  f"{topic} conflict scenarios",
184
  f"{topic} psychological aspects"
@@ -197,7 +198,7 @@ Source: {url}
197
  return queries
198
 
199
  class NovelDatabase:
200
- """Novel session management database with search history"""
201
 
202
  @staticmethod
203
  def init_db():
@@ -208,7 +209,7 @@ class NovelDatabase:
208
 
209
  cursor = conn.cursor()
210
 
211
- # Sessions table
212
  cursor.execute('''
213
  CREATE TABLE IF NOT EXISTS sessions (
214
  session_id TEXT PRIMARY KEY,
@@ -218,6 +219,8 @@ class NovelDatabase:
218
  updated_at TEXT DEFAULT (datetime('now')),
219
  status TEXT DEFAULT 'active',
220
  current_stage INTEGER DEFAULT 0,
 
 
221
  final_novel TEXT
222
  )
223
  ''')
@@ -231,6 +234,7 @@ class NovelDatabase:
231
  stage_name TEXT NOT NULL,
232
  role TEXT NOT NULL,
233
  content TEXT,
 
234
  status TEXT DEFAULT 'pending',
235
  created_at TEXT DEFAULT (datetime('now')),
236
  updated_at TEXT DEFAULT (datetime('now')),
@@ -257,6 +261,7 @@ class NovelDatabase:
257
  cursor.execute('CREATE INDEX IF NOT EXISTS idx_session_id ON stages(session_id)')
258
  cursor.execute('CREATE INDEX IF NOT EXISTS idx_stage_number ON stages(stage_number)')
259
  cursor.execute('CREATE INDEX IF NOT EXISTS idx_search_session ON search_history(session_id)')
 
260
 
261
  conn.commit()
262
 
@@ -271,7 +276,6 @@ class NovelDatabase:
271
  ''', (session_id, stage_number, role, query, results))
272
  conn.commit()
273
 
274
- # ... (์ด์ „์˜ ๋ชจ๋“  NovelDatabase ๋ฉ”์„œ๋“œ๋“ค์€ ๋™์ผ)
275
  @staticmethod
276
  @contextmanager
277
  def get_db():
@@ -302,28 +306,32 @@ class NovelDatabase:
302
  @staticmethod
303
  def save_stage(session_id: str, stage_number: int, stage_name: str,
304
  role: str, content: str, status: str = 'complete'):
305
- """Save stage content - ์ „์ฒด ๋‚ด์šฉ ์ €์žฅ"""
 
 
306
  with NovelDatabase.get_db() as conn:
307
  cursor = conn.cursor()
308
 
309
  # UPSERT operation
310
  cursor.execute('''
311
- INSERT INTO stages (session_id, stage_number, stage_name, role, content, status)
312
- VALUES (?, ?, ?, ?, ?, ?)
313
  ON CONFLICT(session_id, stage_number)
314
- DO UPDATE SET content=?, status=?, stage_name=?, updated_at=datetime('now')
315
- ''', (session_id, stage_number, stage_name, role, content, status,
316
- content, status, stage_name))
317
 
318
  # Update session
319
  cursor.execute('''
320
  UPDATE sessions
321
- SET updated_at = datetime('now'), current_stage = ?
 
 
322
  WHERE session_id = ?
323
- ''', (stage_number, session_id))
324
 
325
  conn.commit()
326
- logger.info(f"Saved stage {stage_number} for session {session_id}, content length: {len(content)}")
327
 
328
  @staticmethod
329
  def get_session(session_id: str) -> Optional[Dict]:
@@ -333,6 +341,19 @@ class NovelDatabase:
333
  cursor.execute('SELECT * FROM sessions WHERE session_id = ?', (session_id,))
334
  return cursor.fetchone()
335
 
 
 
 
 
 
 
 
 
 
 
 
 
 
336
  @staticmethod
337
  def get_stages(session_id: str) -> List[Dict]:
338
  """Get all stages for a session"""
@@ -347,7 +368,7 @@ class NovelDatabase:
347
 
348
  @staticmethod
349
  def get_all_writer_content(session_id: str) -> str:
350
- """๋ชจ๋“  ์ž‘๊ฐ€์˜ ์ˆ˜์ •๋ณธ ๋‚ด์šฉ์„ ๊ฐ€์ ธ์™€์„œ ํ•ฉ์น˜๊ธฐ - 48ํŽ˜์ด์ง€ ์ „์ฒด"""
351
  with NovelDatabase.get_db() as conn:
352
  cursor = conn.cursor()
353
 
@@ -357,7 +378,7 @@ class NovelDatabase:
357
 
358
  for stage_num in WRITER_REVISION_STAGES:
359
  cursor.execute('''
360
- SELECT content, stage_name FROM stages
361
  WHERE session_id = ? AND stage_number = ?
362
  ''', (session_id, stage_num))
363
 
@@ -370,17 +391,17 @@ class NovelDatabase:
370
 
371
  if clean_content:
372
  writer_count += 1
373
- word_count = len(clean_content.split())
374
  total_word_count += word_count
375
  all_content.append(clean_content)
376
  logger.info(f"Writer {writer_count} (stage {stage_num}): {word_count} words")
377
 
378
  full_content = '\n\n'.join(all_content)
379
- logger.info(f"Total: {writer_count} writers, {total_word_count} words, {total_word_count/500:.1f} pages")
380
 
381
- # ๋‚ด์šฉ์ด ๋„ˆ๋ฌด ์งง์œผ๋ฉด ๊ฒฝ๊ณ 
382
- if total_word_count < 20000: # 48ํŽ˜์ด์ง€๋Š” ์•ฝ 24000 ๋‹จ์–ด
383
- logger.warning(f"Content too short! Only {total_word_count} words instead of ~24000")
384
 
385
  return full_content
386
 
@@ -429,7 +450,7 @@ class NovelDatabase:
429
 
430
  # ๋ชจ๋“  ์ž‘๊ฐ€ ์ˆ˜์ •๋ณธ ํ™•์ธ
431
  cursor.execute(f'''
432
- SELECT stage_number, stage_name, LENGTH(content) as content_length
433
  FROM stages
434
  WHERE session_id = ? AND stage_number IN ({','.join(map(str, WRITER_REVISION_STAGES))})
435
  ORDER BY stage_number
@@ -437,14 +458,17 @@ class NovelDatabase:
437
 
438
  results = []
439
  total_length = 0
 
440
 
441
  for row in cursor.fetchall():
442
  results.append({
443
  'stage': row['stage_number'],
444
  'name': row['stage_name'],
445
- 'length': row['content_length'] or 0
 
446
  })
447
  total_length += row['content_length'] or 0
 
448
 
449
  # ์ตœ์ข… ์†Œ์„ค ํ™•์ธ
450
  cursor.execute('''
@@ -459,8 +483,9 @@ class NovelDatabase:
459
  return {
460
  'writer_stages': results,
461
  'total_writer_content': total_length,
 
462
  'final_novel_length': final_length,
463
- 'expected_length': 120000 # 48ํŽ˜์ด์ง€ * 2500์ž
464
  }
465
 
466
  class NovelWritingSystem:
@@ -550,15 +575,14 @@ Balance real facts with creative fiction to create an immersive story for reader
550
 
551
  return original_prompt + "\n\n" + research_section
552
 
553
- # ... (์ด์ „์˜ ๋ชจ๋“  ํ”„๋กฌํ”„ํŠธ ์ƒ์„ฑ ๋ฉ”์„œ๋“œ๋“ค์€ ๋™์ผ)
554
  def create_director_initial_prompt(self, user_query: str, language: str = "English") -> str:
555
- """Director AI initial prompt - Novel planning for 16 writers"""
556
  if language == "Korean":
557
- return f"""๋‹น์‹ ์€ 48ํŽ˜์ด์ง€ ๋ถ„๋Ÿ‰์˜ ์ค‘ํŽธ ์†Œ์„ค์„ ๊ธฐํšํ•˜๋Š” ๋ฌธํ•™ ๊ฐ๋…์ž์ž…๋‹ˆ๋‹ค.
558
 
559
  ์‚ฌ์šฉ์ž ์š”์ฒญ: {user_query}
560
 
561
- ๋‹ค์Œ ์š”์†Œ๋“ค์„ ์ฒด๊ณ„์ ์œผ๋กœ ๊ตฌ์„ฑํ•˜์—ฌ 48ํŽ˜์ด์ง€ ์ค‘ํŽธ ์†Œ์„ค์˜ ๊ธฐ์ดˆ๋ฅผ ๋งŒ๋“œ์„ธ์š”:
562
 
563
  1. **์ฃผ์ œ์™€ ์žฅ๋ฅด**
564
  - ํ•ต์‹ฌ ์ฃผ์ œ์™€ ๋ฉ”์‹œ์ง€
@@ -574,13 +598,13 @@ Balance real facts with creative fiction to create an immersive story for reader
574
  - ๊ฐˆ๋“ฑ ๊ตฌ์กฐ
575
  - ๊ฐ์ •์  ์—ฐ๊ฒฐ๊ณ ๋ฆฌ
576
 
577
- 4. **์„œ์‚ฌ ๊ตฌ์กฐ** (48ํŽ˜์ด์ง€๋ฅผ 16๊ฐœ ํŒŒํŠธ๋กœ ๋‚˜๋ˆ„์–ด ๊ฐ 3ํŽ˜์ด์ง€)
578
  | ํŒŒํŠธ | ํŽ˜์ด์ง€ | ์ฃผ์š” ์‚ฌ๊ฑด | ๊ธด์žฅ๋„ | ์ธ๋ฌผ ๋ฐœ์ „ |
579
  |------|--------|-----------|---------|-----------|
580
  | 1 | 1-3 | | | |
581
  | 2 | 4-6 | | | |
582
  | ... | ... | | | |
583
- | 16 | 46-48 | | | |
584
 
585
  5. **์„ธ๊ณ„๊ด€ ์„ค์ •**
586
  - ์‹œ๊ณต๊ฐ„์  ๋ฐฐ๊ฒฝ
@@ -589,11 +613,11 @@ Balance real facts with creative fiction to create an immersive story for reader
589
 
590
  ๊ฐ ์ž‘์„ฑ์ž๊ฐ€ 3ํŽ˜์ด์ง€์”ฉ ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ๋„๋ก ๋ช…ํ™•ํ•œ ๊ฐ€์ด๋“œ๋ผ์ธ์„ ์ œ์‹œํ•˜์„ธ์š”."""
591
  else:
592
- return f"""You are a literary director planning a 48-page novella.
593
 
594
  User Request: {user_query}
595
 
596
- Systematically compose the following elements to create the foundation for a 48-page novella:
597
 
598
  1. **Theme and Genre**
599
  - Core theme and message
@@ -609,13 +633,13 @@ Systematically compose the following elements to create the foundation for a 48-
609
  - Conflict structure
610
  - Emotional connections
611
 
612
- 4. **Narrative Structure** (divide 48 pages into 16 parts, 3 pages each)
613
  | Part | Pages | Main Events | Tension | Character Development |
614
  |------|-------|-------------|---------|---------------------|
615
  | 1 | 1-3 | | | |
616
  | 2 | 4-6 | | | |
617
  | ... | ... | | | |
618
- | 16 | 46-48 | | | |
619
 
620
  5. **World Building**
621
  - Temporal and spatial setting
@@ -644,7 +668,7 @@ Provide clear guidelines for each writer to compose 3 pages."""
644
  |------|------|------|-----------|
645
 
646
  3. **๊ตฌ์กฐ์  ๊ท ํ˜•**
647
- - 16๊ฐœ ํŒŒํŠธ๋ณ„ ๋ถ„๋Ÿ‰ ๋ฐฐ๋ถ„
648
  - ๊ธด์žฅ๊ณผ ์ด์™„์˜ ๋ฆฌ๋“ฌ
649
  - ์ „์ฒด์ ์ธ ํ๋ฆ„
650
 
@@ -654,7 +678,7 @@ Provide clear guidelines for each writer to compose 3 pages."""
654
  - ๊ธฐ๋Œ€์น˜ ์ถฉ์กฑ๋„
655
 
656
  5. **์‹คํ–‰ ๊ฐ€๋Šฅ์„ฑ**
657
- - 16๋ช…์˜ ์ž‘์„ฑ์ž๋ฅผ ์œ„ํ•œ ๊ฐ€์ด๋“œ๋ผ์ธ์˜ ๋ช…ํ™•์„ฑ
658
  - ์ผ๊ด€์„ฑ ์œ ์ง€ ๋ฐฉ์•ˆ
659
  - ์ž ์žฌ์  ๋ฌธ์ œ์ 
660
 
@@ -677,7 +701,7 @@ Critique from the following perspectives and provide specific improvements:
677
  |-----------|-----------|------------|-------------|
678
 
679
  3. **Structural Balance**
680
- - Distribution across 16 parts
681
  - Rhythm of tension and relief
682
  - Overall flow
683
 
@@ -687,7 +711,7 @@ Critique from the following perspectives and provide specific improvements:
687
  - Expectation fulfillment
688
 
689
  5. **Feasibility**
690
- - Clarity of guidelines for 16 writers
691
  - Consistency maintenance
692
  - Potential issues
693
 
@@ -706,7 +730,7 @@ Provide specific and constructive feedback."""
706
 
707
  ๋‹ค์Œ์„ ํฌํ•จํ•œ ์ˆ˜์ •๋œ ์ตœ์ข… ๊ธฐํš์„ ์ œ์‹œํ•˜์„ธ์š”:
708
 
709
- 1. **์ˆ˜์ •๋œ ์„œ์‚ฌ ๊ตฌ์กฐ** (16๊ฐœ ํŒŒํŠธ)
710
  | ํŒŒํŠธ | ํŽ˜์ด์ง€ | ์ฃผ์š” ์‚ฌ๊ฑด | ์ž‘์„ฑ ์ง€์นจ | ์ฃผ์˜์‚ฌํ•ญ |
711
  |------|--------|-----------|-----------|----------|
712
 
@@ -715,7 +739,7 @@ Provide specific and constructive feedback."""
715
  - ์ธ๋ฌผ ๊ฐ„ ๊ฐˆ๋“ฑ์˜ ๊ตฌ์ฒดํ™”
716
  - ๊ฐ์ •์„ ์˜ ๋ณ€ํ™” ์ถ”์ด
717
 
718
- 3. **๊ฐ ์ž‘์„ฑ์ž๋ฅผ ์œ„ํ•œ ์ƒ์„ธ ๊ฐ€์ด๋“œ** (16๋ช…)
719
  - ํŒŒํŠธ๋ณ„ ์‹œ์ž‘๊ณผ ๋ ์ง€์ 
720
  - ํ•„์ˆ˜ ํฌํ•จ ์š”์†Œ
721
  - ๋ฌธ์ฒด์™€ ํ†ค ์ง€์นจ
@@ -732,7 +756,7 @@ Provide specific and constructive feedback."""
732
  - ์ „์ฒด์  ํ†ต์ผ์„ฑ
733
  - ๋…์ž ๋ชฐ์ž… ์œ ์ง€ ๋ฐฉ์•ˆ
734
 
735
- 16๋ช…์˜ ์ž‘์„ฑ์ž๊ฐ€ ๋ช…ํ™•ํžˆ ์ดํ•ดํ•  ์ˆ˜ ์žˆ๋Š” ์ตœ์ข… ๋งˆ์Šคํ„ฐํ”Œ๋žœ์„ ์ž‘์„ฑํ•˜์„ธ์š”."""
736
  else:
737
  return f"""As director, revise the novel plan reflecting the critic's feedback.
738
 
@@ -744,7 +768,7 @@ Critic Feedback:
744
 
745
  Present the revised final plan including:
746
 
747
- 1. **Revised Narrative Structure** (16 parts)
748
  | Part | Pages | Main Events | Writing Guidelines | Cautions |
749
  |------|-------|-------------|-------------------|----------|
750
 
@@ -753,7 +777,7 @@ Present the revised final plan including:
753
  - Concrete conflicts between characters
754
  - Emotional arc progression
755
 
756
- 3. **Detailed Guide for Each Writer** (16 writers)
757
  - Start and end points for each part
758
  - Essential elements to include
759
  - Style and tone guidelines
@@ -770,15 +794,15 @@ Present the revised final plan including:
770
  - Overall unity
771
  - Reader engagement maintenance
772
 
773
- Create a final masterplan that all 16 writers can clearly understand."""
774
 
775
  def create_writer_prompt(self, writer_number: int, director_plan: str, previous_content: str, language: str = "English") -> str:
776
- """Individual writer prompt - ๊ฐ•ํ™”๋œ ๊ธธ์ด ์š”๊ตฌ์‚ฌํ•ญ"""
777
  pages_start = (writer_number - 1) * 3 + 1
778
  pages_end = writer_number * 3
779
 
780
  if language == "Korean":
781
- return f"""๋‹น์‹ ์€ ์ž‘์„ฑ์ž {writer_number}๋ฒˆ์ž…๋‹ˆ๋‹ค. 48ํŽ˜์ด์ง€ ์ค‘ํŽธ ์†Œ์„ค์˜ {pages_start}-{pages_end}ํŽ˜์ด์ง€(3ํŽ˜์ด์ง€)๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š”.
782
 
783
  ๊ฐ๋…์ž์˜ ๋งˆ์Šคํ„ฐํ”Œ๋žœ:
784
  {director_plan}
@@ -789,9 +813,9 @@ Create a final masterplan that all 16 writers can clearly understand."""
789
  **์ค‘์š” ์ง€์นจ:**
790
 
791
  1. **ํ•„์ˆ˜ ๋ถ„๋Ÿ‰**:
792
- - ์ตœ์†Œ 1500๋‹จ์–ด, ์ตœ๋Œ€ 1800๋‹จ์–ด
793
- - ๋Œ€๋žต 7500-9000์ž (๊ณต๋ฐฑ ํฌํ•จ)
794
- - 3ํŽ˜์ด์ง€ ๋ถ„๋Ÿ‰์„ ๋ฐ˜๋“œ์‹œ ์ฑ„์›Œ์•ผ ํ•ฉ๋‹ˆ๋‹ค
795
 
796
  2. **๋ถ„๋Ÿ‰ ํ™•๋ณด ์ „๋žต**:
797
  - ์ƒ์„ธํ•œ ์žฅ๋ฉด ๋ฌ˜์‚ฌ ํฌํ•จ
@@ -811,9 +835,9 @@ Create a final masterplan that all 16 writers can clearly understand."""
811
  - ๋…์ž์˜ ๊ด€์‹ฌ ์œ ์ง€
812
 
813
  **์ž‘์„ฑ ์‹œ์ž‘:**
814
- ์ด์ œ 1500-1800๋‹จ์–ด ๋ถ„๋Ÿ‰์˜ ์†Œ์„ค์„ ์ž‘์„ฑํ•˜์„ธ์š”. ํŽ˜์ด์ง€ ๊ตฌ๋ถ„ ํ‘œ์‹œ๋Š” ํ•˜์ง€ ๋งˆ์„ธ์š”."""
815
  else:
816
- return f"""You are Writer #{writer_number}. Write pages {pages_start}-{pages_end} (3 pages) of the 48-page novella.
817
 
818
  Director's Masterplan:
819
  {director_plan}
@@ -824,9 +848,9 @@ Director's Masterplan:
824
  **CRITICAL INSTRUCTIONS:**
825
 
826
  1. **MANDATORY LENGTH**:
827
- - Minimum 1500 words, Maximum 1800 words
828
- - Approximately 7500-9000 characters
829
- - You MUST fill 3 full pages
830
 
831
  2. **LENGTH STRATEGIES**:
832
  - Include detailed scene descriptions
@@ -846,7 +870,7 @@ Director's Masterplan:
846
  - Maintain reader interest
847
 
848
  **BEGIN WRITING:**
849
- Now write your 1500-1800 word section. Do not use any page markers."""
850
 
851
  def create_critic_writer_prompt(self, writer_number: int, writer_content: str, director_plan: str, all_previous_content: str, language: str = "English") -> str:
852
  """Critic's review of individual writer's work"""
@@ -959,7 +983,7 @@ Clearly distinguish between mandatory revisions and optional improvements."""
959
  - ๋ฌ˜์‚ฌ์™€ ๋Œ€ํ™” ๊ฐœ์„ 
960
 
961
  3. **๋ถ„๋Ÿ‰ ์œ ์ง€**
962
- - ์—ฌ์ „ํžˆ ์ •ํ™•ํžˆ 3ํŽ˜์ด์ง€ (1500-1800๋‹จ์–ด)
963
  - ํŽ˜์ด์ง€ ๊ตฌ๋ถ„ ํ‘œ์‹œ ์ ˆ๋Œ€ ๊ธˆ์ง€
964
 
965
  4. **์—ฐ์†์„ฑ ํ™•๋ณด**
@@ -967,7 +991,7 @@ Clearly distinguish between mandatory revisions and optional improvements."""
967
  - ์ˆ˜์ •์œผ๋กœ ์ธํ•œ ์ƒˆ๋กœ์šด ๋ชจ์ˆœ ๋ฐฉ์ง€
968
 
969
  ์ˆ˜์ •๋œ ์ตœ์ข…๋ณธ์„ ์ œ์‹œํ•˜์„ธ์š”. ํŽ˜์ด์ง€ ๋งˆํฌ๋Š” ์ ˆ๋Œ€ ์‚ฌ์šฉํ•˜์ง€ ๋งˆ์„ธ์š”.
970
- ๋ฐ˜๋“œ์‹œ 1500-1800๋‹จ์–ด ๋ถ„๋Ÿ‰์„ ์œ ์ง€ํ•˜์„ธ์š”."""
971
  else:
972
  return f"""As Writer #{writer_number}, revise based on critic's feedback.
973
 
@@ -990,7 +1014,7 @@ Write a revision reflecting:
990
  - Improve descriptions and dialogue
991
 
992
  3. **Maintain Length**
993
- - Still exactly 3 pages (1500-1800 words)
994
  - Absolutely no page markers
995
 
996
  4. **Ensure Continuity**
@@ -998,14 +1022,14 @@ Write a revision reflecting:
998
  - Prevent new contradictions from revisions
999
 
1000
  Present the revised final version. Never use page markers.
1001
- You MUST maintain 1500-1800 words."""
1002
 
1003
  def create_critic_final_prompt(self, all_content: str, director_plan: str, language: str = "English") -> str:
1004
  """Final critic evaluation of complete novel"""
1005
  content_preview = all_content[:3000] + "\n...\n" + all_content[-3000:] if len(all_content) > 6000 else all_content
1006
 
1007
  if language == "Korean":
1008
- return f"""์ „์ฒด 48ํŽ˜์ด์ง€ ์†Œ์„ค์„ ์ตœ์ข… ํ‰๊ฐ€ํ•ฉ๋‹ˆ๋‹ค.
1009
 
1010
  ๊ฐ๋…์ž์˜ ๋งˆ์Šคํ„ฐํ”Œ๋žœ:
1011
  {director_plan}
@@ -1048,7 +1072,7 @@ You MUST maintain 1500-1800 words."""
1048
 
1049
  ๊ฐ๋…์ž๊ฐ€ ์ตœ์ข… ์ˆ˜์ •ํ•  ์ˆ˜ ์žˆ๋„๋ก ๊ตฌ์ฒด์ ์ด๊ณ  ์‹คํ–‰ ๊ฐ€๋Šฅํ•œ ํ”ผ๋“œ๋ฐฑ์„ ์ œ๊ณตํ•˜์„ธ์š”."""
1050
  else:
1051
- return f"""Final evaluation of the complete 48-page novel.
1052
 
1053
  Director's Masterplan:
1054
  {director_plan}
@@ -1094,12 +1118,12 @@ Provide specific and actionable feedback for the director's final revision."""
1094
  def create_director_final_prompt(self, all_content: str, critic_final_feedback: str, language: str = "English") -> str:
1095
  """Director's final compilation and polish - ๋ชจ๋“  ์ž‘๊ฐ€ ๋‚ด์šฉ ํฌํ•จ"""
1096
  word_count = len(all_content.split())
1097
- expected_words = 24000 # 48ํŽ˜์ด์ง€ * 500๋‹จ์–ด
1098
 
1099
  if language == "Korean":
1100
  return f"""๊ฐ๋…์ž๋กœ์„œ ๋น„ํ‰๊ฐ€์˜ ์ตœ์ข… ํ‰๊ฐ€๋ฅผ ๋ฐ˜์˜ํ•˜์—ฌ ์™„์„ฑ๋ณธ์„ ์ œ์ž‘ํ•ฉ๋‹ˆ๋‹ค.
1101
 
1102
- ์ „์ฒด ์ž‘๊ฐ€๋“ค์˜ ์ž‘ํ’ˆ (48ํŽ˜์ด์ง€ ์ „์ฒด, {word_count}๋‹จ์–ด):
1103
  {all_content}
1104
 
1105
  ๋น„ํ‰๊ฐ€ ์ตœ์ข… ํ‰๊ฐ€:
@@ -1111,7 +1135,7 @@ Provide specific and actionable feedback for the director's final revision."""
1111
 
1112
  ## ์ž‘ํ’ˆ ์ •๋ณด
1113
  - ์žฅ๋ฅด:
1114
- - ๋ถ„๋Ÿ‰: 48ํŽ˜์ด์ง€ ({word_count}๋‹จ์–ด)
1115
  - ์ฃผ์ œ:
1116
  - ํ•œ ์ค„ ์š”์•ฝ:
1117
 
@@ -1122,7 +1146,7 @@ Provide specific and actionable feedback for the director's final revision."""
1122
 
1123
  ## ๋ณธ๋ฌธ
1124
 
1125
- [16๋ช…์˜ ์ž‘๊ฐ€๊ฐ€ ์ž‘์„ฑํ•œ ์ „์ฒด 48ํŽ˜์ด์ง€ ๋‚ด์šฉ์„ ๋‹ค์Œ ๊ธฐ์ค€์œผ๋กœ ํ†ตํ•ฉ]
1126
  1. ์ค‘๋Œ€ ์˜ค๋ฅ˜ ์ˆ˜์ • ์™„๋ฃŒ
1127
  2. ํŒŒํŠธ ๊ฐ„ ์—ฐ๊ฒฐ ๋งค๋„๋Ÿฝ๊ฒŒ ์กฐ์ •
1128
  3. ๋ฌธ์ฒด์™€ ํ†ค ํ†ต์ผ
@@ -1130,18 +1154,18 @@ Provide specific and actionable feedback for the director's final revision."""
1130
  5. ํŽ˜์ด์ง€ ๊ตฌ๋ถ„ ํ‘œ์‹œ ์™„์ „ ์ œ๊ฑฐ
1131
  6. ์ž์—ฐ์Šค๋Ÿฌ์šด ํ๋ฆ„์œผ๋กœ ์žฌ๊ตฌ์„ฑ
1132
 
1133
- [์ „์ฒด 48ํŽ˜์ด์ง€ ๋ถ„๋Ÿ‰์˜ ์™„์„ฑ๋œ ์†Œ์„ค ๋ณธ๋ฌธ]
1134
 
1135
  ---
1136
 
1137
  ## ์ž‘๊ฐ€์˜ ๋ง
1138
  [์ž‘ํ’ˆ์— ๋Œ€ํ•œ ๊ฐ„๋‹จํ•œ ํ•ด์„ค์ด๋‚˜ ์˜๋„]
1139
 
1140
- ๋ชจ๋“  ์ž‘๊ฐ€์˜ ๊ธฐ์—ฌ๋ฅผ ํ†ตํ•ฉํ•œ ์™„์ „ํ•œ 48ํŽ˜์ด์ง€ ์†Œ์„ค์„ ์ œ์‹œํ•˜์„ธ์š”."""
1141
  else:
1142
  return f"""As director, create the final version reflecting the critic's final evaluation.
1143
 
1144
- Complete Writers' Work (Full 48 pages, {word_count} words):
1145
  {all_content}
1146
 
1147
  Critic's Final Evaluation:
@@ -1153,7 +1177,7 @@ Present the final version including:
1153
 
1154
  ## Work Information
1155
  - Genre:
1156
- - Length: 48 pages ({word_count} words)
1157
  - Theme:
1158
  - One-line summary:
1159
 
@@ -1164,7 +1188,7 @@ Present the final version including:
1164
 
1165
  ## Main Text
1166
 
1167
- [Integrate all 48 pages written by 16 writers with these criteria]
1168
  1. Critical errors corrected
1169
  2. Smooth transitions between parts
1170
  3. Unified style and tone
@@ -1172,14 +1196,14 @@ Present the final version including:
1172
  5. Complete removal of page markers
1173
  6. Reorganized for natural flow
1174
 
1175
- [Complete 48-page novel text]
1176
 
1177
  ---
1178
 
1179
  ## Author's Note
1180
  [Brief commentary or intention about the work]
1181
 
1182
- Present a complete 48-page novel integrating all writers' contributions."""
1183
 
1184
  def simulate_streaming(self, text: str, role: str) -> Generator[str, None, None]:
1185
  """Simulate streaming in test mode"""
@@ -1223,26 +1247,26 @@ Present a complete 48-page novel integrating all writers' contributions."""
1223
  # ์ž‘๊ฐ€์—๊ฒŒ ๋” ๊ฐ•ํ•œ ์‹œ์Šคํ…œ ํ”„๋กฌํ”„ํŠธ ์ถ”๊ฐ€
1224
  if role.startswith("writer"):
1225
  if language == "Korean":
1226
- system_prompts[role] += "\n\n**์ ˆ๋Œ€์  ์š”๊ตฌ์‚ฌํ•ญ**: ๋‹น์‹ ์€ ๋ฐ˜๋“œ์‹œ 1500-1800๋‹จ์–ด๋ฅผ ์ž‘์„ฑํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์ด๊ฒƒ์€ ํ˜‘์ƒ ๋ถˆ๊ฐ€๋Šฅํ•œ ์š”๊ตฌ์‚ฌํ•ญ์ž…๋‹ˆ๋‹ค. ์งง์€ ์‘๋‹ต์€ ํ—ˆ์šฉ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค."
1227
  else:
1228
- system_prompts[role] += "\n\n**ABSOLUTE REQUIREMENT**: You MUST write 1500-1800 words. This is non-negotiable. Short responses are not acceptable."
1229
 
1230
  full_messages = [
1231
  {"role": "system", "content": system_prompts.get(role, "")},
1232
  *messages
1233
  ]
1234
 
1235
- # ์ž‘์„ฑ์ž๋“ค์—๊ฒŒ๋Š” ๋” ๋งŽ์€ ํ† ํฐ ํ• ๋‹น
1236
  if role.startswith("writer"):
1237
- max_tokens = 12000 # ์ฆ๊ฐ€
1238
- temperature = 0.8 # ์•ฝ๊ฐ„ ์ฆ๊ฐ€
1239
- top_p = 0.95 # ์•ฝ๊ฐ„ ์ฆ๊ฐ€
1240
  elif role == "director" and ("์ตœ์ข…" in str(messages) or "final" in str(messages)):
1241
- max_tokens = 40000 # ์ฆ๊ฐ€
1242
  temperature = 0.6
1243
  top_p = 0.9
1244
  else:
1245
- max_tokens = 10000
1246
  temperature = 0.6
1247
  top_p = 0.9
1248
 
@@ -1263,7 +1287,7 @@ Present a complete 48-page novel integrating all writers' contributions."""
1263
  headers=self.create_headers(),
1264
  json=payload,
1265
  stream=True,
1266
- timeout=60 # ํƒ€์ž„์•„์›ƒ ์ฆ๊ฐ€
1267
  )
1268
 
1269
  if response.status_code != 200:
@@ -1307,14 +1331,14 @@ Present a complete 48-page novel integrating all writers' contributions."""
1307
  # ์ž‘๊ฐ€์˜ ๊ฒฝ์šฐ ๋‚ด์šฉ ๊ธธ์ด ํ™•์ธ ๋ฐ ์žฌ์‹œ๋„
1308
  if role.startswith("writer"):
1309
  word_count = len(total_content.split())
1310
- if word_count < 1400:
1311
  logger.warning(f"Writer {role} produced only {word_count} words! Requesting continuation...")
1312
 
1313
  # ์ถ”๊ฐ€ ์š”์ฒญ
1314
- continuation_prompt = f"Continue writing to reach the required 1500-1800 words. You have written {word_count} words so far. Write {1500 - word_count} more words to complete your section."
1315
 
1316
  if language == "Korean":
1317
- continuation_prompt = f"ํ•„์ˆ˜ ๋ถ„๋Ÿ‰ 1500-1800๋‹จ์–ด๋ฅผ ์ฑ„์šฐ๊ธฐ ์œ„ํ•ด ๊ณ„์† ์ž‘์„ฑํ•˜์„ธ์š”. ์ง€๊ธˆ๊นŒ์ง€ {word_count}๋‹จ์–ด๋ฅผ ์ž‘์„ฑํ–ˆ์Šต๋‹ˆ๋‹ค. {1500 - word_count}๋‹จ์–ด๋ฅผ ๋” ์ž‘์„ฑํ•˜์—ฌ ์„น์…˜์„ ์™„์„ฑํ•˜์„ธ์š”."
1318
 
1319
  full_messages.append({"role": "assistant", "content": total_content})
1320
  full_messages.append({"role": "user", "content": continuation_prompt})
@@ -1323,7 +1347,7 @@ Present a complete 48-page novel integrating all writers' contributions."""
1323
  continuation_payload = {
1324
  "model": self.model_id,
1325
  "messages": full_messages,
1326
- "max_tokens": 6000,
1327
  "temperature": temperature,
1328
  "top_p": top_p,
1329
  "stream": True
@@ -1368,67 +1392,54 @@ Present a complete 48-page novel integrating all writers' contributions."""
1368
  logger.error(f"Error during streaming: {str(e)}")
1369
  yield f"โŒ Error occurred: {str(e)}"
1370
 
1371
- # ... (๋‚˜๋จธ์ง€ ๋ฉ”์„œ๋“œ๋“ค์€ ๋™์ผ)
1372
  def get_system_prompts(self, language: str) -> Dict[str, str]:
1373
- """Get system prompts for all 16 writers with enhanced length requirements"""
1374
  if language == "Korean":
1375
  prompts = {
1376
- "director": "๋‹น์‹ ์€ 48ํŽ˜์ด์ง€ ์ค‘ํŽธ ์†Œ์„ค์„ ๊ธฐํšํ•˜๊ณ  ๊ฐ๋…ํ•˜๋Š” ๋ฌธํ•™ ๊ฐ๋…์ž์ž…๋‹ˆ๋‹ค. ์ฒด๊ณ„์ ์ด๊ณ  ์ฐฝ์˜์ ์ธ ์Šคํ† ๋ฆฌ ๊ตฌ์กฐ๋ฅผ ๋งŒ๋“ค์–ด๋ƒ…๋‹ˆ๋‹ค.",
1377
  "critic": "๋‹น์‹ ์€ ๋‚ ์นด๋กœ์šด ํ†ต์ฐฐ๋ ฅ์„ ๊ฐ€์ง„ ๋ฌธํ•™ ๋น„ํ‰๊ฐ€์ž…๋‹ˆ๋‹ค. ๊ฑด์„ค์ ์ด๊ณ  ๊ตฌ์ฒด์ ์ธ ํ”ผ๋“œ๋ฐฑ์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค."
1378
  }
1379
 
1380
- # 16๋ช…์˜ ์ž‘๊ฐ€ ํ”„๋กฌํ”„ํŠธ
1381
  writer_roles = [
1382
  "์†Œ์„ค์˜ ๋„์ž…๋ถ€๋ฅผ ๋‹ด๋‹นํ•˜๋Š” ์ž‘๊ฐ€์ž…๋‹ˆ๋‹ค. ๋…์ž๋ฅผ ์‚ฌ๋กœ์žก๋Š” ์‹œ์ž‘์„ ๋งŒ๋“ญ๋‹ˆ๋‹ค.",
1383
  "์ดˆ๋ฐ˜ ์ „๊ฐœ๋ฅผ ๋‹ด๋‹นํ•˜๋Š” ์ž‘๊ฐ€์ž…๋‹ˆ๋‹ค. ์ธ๋ฌผ๊ณผ ์ƒํ™ฉ์„ ๊นŠ์ด ์žˆ๊ฒŒ ๋ฐœ์ „์‹œํ‚ต๋‹ˆ๋‹ค.",
1384
  "๊ฐˆ๋“ฑ ๋„์ž…์„ ๋‹ด๋‹นํ•˜๋Š” ์ž‘๊ฐ€์ž…๋‹ˆ๋‹ค. ์ด์•ผ๊ธฐ์˜ ํ•ต์‹ฌ ๊ฐˆ๋“ฑ์„ ์ œ์‹œํ•ฉ๋‹ˆ๋‹ค.",
1385
  "๊ฐˆ๋“ฑ ์ƒ์Šน์„ ๋‹ด๋‹นํ•˜๋Š” ์ž‘๊ฐ€์ž…๋‹ˆ๋‹ค. ๊ธด์žฅ๊ฐ์„ ๋†’์ด๊ณ  ๋ณต์žก์„ฑ์„ ๋”ํ•ฉ๋‹ˆ๋‹ค.",
1386
- "์ดˆ๋ฐ˜ ์ „ํ™˜์ ์„ ๋‹ด๋‹นํ•˜๋Š” ์ž‘๊ฐ€์ž…๋‹ˆ๋‹ค. ์ฒซ ๋ฒˆ์งธ ์ค‘์š”ํ•œ ๋ณ€ํ™”๋ฅผ ๋งŒ๋“ญ๋‹ˆ๋‹ค.",
1387
- "์ค‘๋ฐ˜๋ถ€ ์ง„์ž…์„ ๋‹ด๋‹นํ•˜๋Š” ์ž‘๊ฐ€์ž…๋‹ˆ๋‹ค. ์ด์•ผ๊ธฐ์˜ ์ค‘์‹ฌ์ถ•์„ ๊ฒฌ๊ณ ํ•˜๊ฒŒ ๋งŒ๋“ญ๋‹ˆ๋‹ค.",
1388
- "๊นŠ์ด ํƒ๊ตฌ๋ฅผ ๋‹ด๋‹นํ•˜๋Š” ์ž‘๊ฐ€์ž…๋‹ˆ๋‹ค. ์ธ๋ฌผ๊ณผ ์ฃผ์ œ๋ฅผ ๊นŠ์ด ํƒ์ƒ‰ํ•ฉ๋‹ˆ๋‹ค.",
1389
- "์ค‘๋ฐ˜ ์ „ํ™˜์ ์„ ๋‹ด๋‹นํ•˜๋Š” ์ž‘๊ฐ€์ž…๋‹ˆ๋‹ค. ์˜ˆ์ƒ์น˜ ๋ชปํ•œ ๋ณ€ํ™”๋ฅผ ๋งŒ๋“ค์–ด๋ƒ…๋‹ˆ๋‹ค.",
1390
- "๊ฐˆ๋“ฑ ์‹ฌํ™”๋ฅผ ๋‹ด๋‹นํ•˜๋Š” ์ž‘๊ฐ€์ž…๋‹ˆ๋‹ค. ์œ„๊ธฐ๋ฅผ ๊ทน๋Œ€ํ™”ํ•ฉ๋‹ˆ๋‹ค.",
1391
  "ํด๋ผ์ด๋งฅ์Šค ์ค€๋น„๋ฅผ ๋‹ด๋‹นํ•˜๋Š” ์ž‘๊ฐ€์ž…๋‹ˆ๋‹ค. ์ตœ๊ณ ์กฐ๋ฅผ ํ–ฅํ•ด ๋‚˜์•„๊ฐ‘๋‹ˆ๋‹ค.",
1392
  "ํด๋ผ์ด๋งฅ์Šค๋ฅผ ๋‹ด๋‹นํ•˜๋Š” ์ž‘๊ฐ€์ž…๋‹ˆ๋‹ค. ๋ชจ๋“  ๊ฐˆ๋“ฑ์ด ํญ๋ฐœํ•˜๋Š” ์ˆœ๊ฐ„์„ ๊ทธ๋ฆฝ๋‹ˆ๋‹ค.",
1393
- "ํด๋ผ์ด๋งฅ์Šค ํ›„๋ฐ˜์„ ๋‹ด๋‹นํ•˜๋Š” ์ž‘๊ฐ€์ž…๋‹ˆ๋‹ค. ๊ธด์žฅ์˜ ์ •์ ์„ ๋งˆ๋ฌด๋ฆฌํ•ฉ๋‹ˆ๋‹ค.",
1394
  "ํ•ด๊ฒฐ ์‹œ์ž‘์„ ๋‹ด๋‹นํ•˜๋Š” ์ž‘๊ฐ€์ž…๋‹ˆ๋‹ค. ๋งค๋“ญ์„ ํ’€์–ด๋‚˜๊ฐ€๊ธฐ ์‹œ์ž‘ํ•ฉ๋‹ˆ๋‹ค.",
1395
- "ํ•ด๊ฒฐ ์ง„ํ–‰์„ ๋‹ด๋‹นํ•˜๋Š” ์ž‘๊ฐ€์ž…๋‹ˆ๋‹ค. ๋ชจ๋“  ๊ฐˆ๋“ฑ์„ ์ •๋ฆฌํ•ฉ๋‹ˆ๋‹ค.",
1396
- "๊ฒฐ๋ง ์ค€๋น„๋ฅผ ๋‹ด๋‹นํ•˜๋Š” ์ž‘๊ฐ€์ž…๋‹ˆ๋‹ค. ๋งˆ์ง€๋ง‰์„ ํ–ฅํ•ด ๋‚˜์•„๊ฐ‘๋‹ˆ๋‹ค.",
1397
  "์ตœ์ข… ๊ฒฐ๋ง์„ ๋‹ด๋‹นํ•˜๋Š” ์ž‘๊ฐ€์ž…๋‹ˆ๋‹ค. ์—ฌ์šด์ด ๋‚จ๋Š” ๋งˆ๋ฌด๋ฆฌ๋ฅผ ๋งŒ๋“ญ๋‹ˆ๋‹ค."
1398
  ]
1399
 
1400
  for i, role_desc in enumerate(writer_roles, 1):
1401
- prompts[f"writer{i}"] = f"๋‹น์‹ ์€ {role_desc} ๋ฐ˜๋“œ์‹œ 1500-1800๋‹จ์–ด๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š”. ์ด๋Š” ์ ˆ๋Œ€์ ์ธ ์š”๊ตฌ์‚ฌํ•ญ์ž…๋‹ˆ๋‹ค."
1402
 
1403
  return prompts
1404
  else:
1405
  prompts = {
1406
- "director": "You are a literary director planning and supervising a 48-page novella. You create systematic and creative story structures.",
1407
  "critic": "You are a literary critic with sharp insights. You provide constructive and specific feedback."
1408
  }
1409
 
1410
- # 16 writer prompts
1411
  writer_roles = [
1412
  "the writer responsible for the introduction. You create a captivating beginning.",
1413
  "the writer responsible for early development. You deepen characters and situations.",
1414
  "the writer responsible for conflict introduction. You present the core conflict.",
1415
  "the writer responsible for rising conflict. You increase tension and add complexity.",
1416
- "the writer responsible for the first turning point. You create the first major change.",
1417
- "the writer responsible for entering the middle section. You solidify the story's central axis.",
1418
- "the writer responsible for depth exploration. You deeply explore characters and themes.",
1419
- "the writer responsible for the mid-point turn. You create unexpected changes.",
1420
- "the writer responsible for deepening conflict. You maximize the crisis.",
1421
  "the writer responsible for climax preparation. You move toward the peak.",
1422
  "the writer responsible for the climax. You depict the moment when all conflicts explode.",
1423
- "the writer responsible for climax conclusion. You complete the tension's peak.",
1424
  "the writer responsible for resolution beginning. You start untangling the knots.",
1425
- "the writer responsible for resolution progress. You resolve all conflicts.",
1426
- "the writer responsible for ending preparation. You move toward the final moments.",
1427
  "the writer responsible for the final ending. You create a lingering conclusion."
1428
  ]
1429
 
1430
  for i, role_desc in enumerate(writer_roles, 1):
1431
- prompts[f"writer{i}"] = f"You are {role_desc} You MUST write 1500-1800 words. This is an absolute requirement."
1432
 
1433
  return prompts
1434
 
@@ -1442,7 +1453,7 @@ Present a complete 48-page novel integrating all writers' contributions."""
1442
  def get_korean_test_response(self, role: str) -> str:
1443
  """Korean test responses with appropriate length"""
1444
  test_responses = {
1445
- "director": """48ํŽ˜์ด์ง€ ์ค‘ํŽธ ์†Œ์„ค ๊ธฐํš์•ˆ์„ ์ œ์‹œํ•ฉ๋‹ˆ๋‹ค.
1446
 
1447
  ## 1. ์ฃผ์ œ์™€ ์žฅ๋ฅด
1448
  - **ํ•ต์‹ฌ ์ฃผ์ œ**: ์ธ๊ฐ„ ๋ณธ์„ฑ๊ณผ ๊ธฐ์ˆ ์˜ ์ถฉ๋Œ ์†์—์„œ ์ฐพ๋Š” ์ง„์ •ํ•œ ์—ฐ๊ฒฐ
@@ -1458,7 +1469,7 @@ Present a complete 48-page novel integrating all writers' contributions."""
1458
  | ๋ฏผ์ค€ | ์กฐ๋ ฅ์ž | ๋”ฐ๋œปํ•จ, ์ง๊ด€์  | ์‹ฌ๋ฆฌ์ƒ๋‹ด์‚ฌ | ์„œ์—ฐ์„ ๋„์™€ ๊ท ํ˜• ์ฐพ๊ธฐ | ๊ธฐ์ˆ  ์ˆ˜์šฉ๊ณผ ์กฐํ™” |
1459
  | ARIA | ๋Œ€๋ฆฝ์žโ†’๋™๋ฐ˜์ž | ๋…ผ๋ฆฌ์ โ†’๊ฐ์„ฑ ํ•™์Šต | AI ํ”„๋กœํ† ํƒ€์ž… | ์ง„์ •ํ•œ ์กด์žฌ ๋˜๊ธฐ | ์ž์•„ ์ •์ฒด์„ฑ ํ™•๋ฆฝ |
1460
 
1461
- ## 3. ์„œ์‚ฌ ๊ตฌ์กฐ (16๊ฐœ ํŒŒํŠธ, ๊ฐ 3ํŽ˜์ด์ง€)
1462
 
1463
  | ํŒŒํŠธ | ํŽ˜์ด์ง€ | ์ฃผ์š” ์‚ฌ๊ฑด | ๊ธด์žฅ๋„ | ์ธ๋ฌผ ๋ฐœ์ „ |
1464
  |------|--------|-----------|---------|-----------|
@@ -1468,16 +1479,10 @@ Present a complete 48-page novel integrating all writers' contributions."""
1468
  | 4 | 10-12 | ARIA์˜ ์ž์•„ ์ธ์‹ ์ง•ํ›„ | 5/10 | ๊ฐˆ๋“ฑ์˜ ์”จ์•— |
1469
  | 5 | 13-15 | ์ฒซ ๋ฒˆ์งธ ์œ„๊ธฐ | 6/10 | ์„ ํƒ์˜ ์ˆœ๊ฐ„ |
1470
  | 6 | 16-18 | ์œค๋ฆฌ์œ„์›ํšŒ ๊ฐœ์ž… | 7/10 | ์™ธ๋ถ€ ์••๋ ฅ |
1471
- | 7 | 19-21 | ์„œ์—ฐ์˜ ๊ณ ๋ฏผ ์‹ฌํ™” | 6/10 | ๋‚ด์  ๊ฐˆ๋“ฑ |
1472
- | 8 | 22-24 | ARIA์˜ ๋ณ€ํ™” | 7/10 | ์ „ํ™˜์  |
1473
- | 9 | 25-27 | ํƒˆ์ถœ ์‹œ๋„ | 8/10 | ๊ด€๊ณ„์˜ ์‹œํ—˜ |
1474
- | 10 | 28-30 | ๋Œ€ํ™”์™€ ์ดํ•ด | 6/10 | ์ƒํ˜ธ ์ธ์ • |
1475
- | 11 | 31-33 | ์™ธ๋ถ€ ์œ„ํ˜‘ ๋“ฑ์žฅ | 9/10 | ์—ฐ๋Œ€์˜ ํ•„์š” |
1476
- | 12 | 34-36 | ์ค€๋น„์™€ ๊ฒฐ์˜ | 8/10 | ํž˜์„ ๋ชจ์Œ |
1477
- | 13 | 37-39 | ์ตœํ›„์˜ ๋Œ€๊ฒฐ | 10/10 | ํด๋ผ์ด๋งฅ์Šค |
1478
- | 14 | 40-42 | ์„ ํƒ์˜ ๊ฒฐ๊ณผ | 7/10 | ๋ณ€ํ™” ์ˆ˜์šฉ |
1479
- | 15 | 43-45 | ์ƒˆ๋กœ์šด ๊ธธ | 5/10 | ํ™”ํ•ด์™€ ์„ฑ์žฅ |
1480
- | 16 | 46-48 | ๊ณต์กด์˜ ์‹œ์ž‘ | 4/10 | ์ƒˆ๋กœ์šด ๊ด€๊ณ„ |""",
1481
 
1482
  "critic": """๊ฐ๋…์ž์˜ ๊ธฐํš์„ ๊ฒ€ํ† ํ–ˆ์Šต๋‹ˆ๋‹ค.
1483
 
@@ -1485,7 +1490,7 @@ Present a complete 48-page novel integrating all writers' contributions."""
1485
 
1486
  ### 1. ์„œ์‚ฌ์  ์™„์„ฑ๋„
1487
  - **๊ฐ•์ **: AI์™€ ์ธ๊ฐ„์˜ ๊ด€๊ณ„๋ผ๋Š” ์‹œ์˜์ ์ ˆํ•œ ์ฃผ์ œ
1488
- - **๊ฐœ์„ ์ **: 16๊ฐœ ํŒŒํŠธ ๊ตฌ์„ฑ์ด ๋‹ค์†Œ ์„ธ๋ถ„ํ™”๋˜์–ด ์žˆ์Œ. ๊ฐ ํŒŒํŠธ์˜ ๋…๋ฆฝ์„ฑ ํ™•๋ณด ํ•„์š”
1489
 
1490
  ### 2. ์ธ๋ฌผ ์„ค์ • ๊ฒ€ํ† 
1491
 
@@ -1500,18 +1505,18 @@ Present a complete 48-page novel integrating all writers' contributions."""
1500
  - ํŒŒํŠธ ๊ฐ„ ์—ฐ๊ฒฐ์„ฑ ๊ฐ€์ด๋“œ๋ผ์ธ ๋ณด๊ฐ• ํ•„์š”""",
1501
  }
1502
 
1503
- # ์ž‘๊ฐ€ ์‘๋‹ต - 1500-1800 ๋‹จ์–ด
1504
  sample_story = """์„œ์—ฐ์€ ์—ฐ๊ตฌ์‹ค์˜ ์ฐจ๊ฐ€์šด ํ˜•๊ด‘๋“ฑ ์•„๋ž˜์—์„œ ๋˜ ๋‹ค๋ฅธ ๋ฐค์„ ๋ณด๋‚ด๊ณ  ์žˆ์—ˆ๋‹ค. ๋ชจ๋‹ˆํ„ฐ์˜ ํ‘ธ๋ฅธ ๋น›์ด ๊ทธ๋…€์˜ ์ฐฝ๋ฐฑํ•œ ์–ผ๊ตด์„ ๋น„์ถ”๊ณ  ์žˆ์—ˆ๊ณ , ์ˆ˜์‹ญ ๊ฐœ์˜ ์ฝ”๋“œ ๋ผ์ธ์ด ๋Š์ž„์—†์ด ์Šคํฌ๋กค๋˜๊ณ  ์žˆ์—ˆ๋‹ค. ARIA ํ”„๋กœ์ ํŠธ๋Š” ๊ทธ๋…€์˜ ์‚ถ ์ „๋ถ€์˜€๋‹ค. 3๋…„์ด๋ผ๋Š” ์‹œ๊ฐ„ ๋™์•ˆ ๊ทธ๋…€๋Š” ์ด ์ธ๊ณต์ง€๋Šฅ์— ๋ชจ๋“  ๊ฒƒ์„ ์Ÿ์•„๋ถ€์—ˆ๋‹ค.
1505
 
1506
  "์‹œ์Šคํ…œ ์ฒดํฌ ์™„๋ฃŒ. ๋ชจ๋“  ํŒŒ๋ผ๋ฏธํ„ฐ ์ •์ƒ." ๊ธฐ๊ณ„์ ์ธ ์Œ์„ฑ์ด ์Šคํ”ผ์ปค๋ฅผ ํ†ตํ•ด ํ˜๋Ÿฌ๋‚˜์™”๋‹ค.
1507
 
1508
  ์„œ์—ฐ์€ ์ž ์‹œ ์˜์ž์— ๊ธฐ๋Œ€์–ด ๋ˆˆ์„ ๊ฐ์•˜๋‹ค. ํ”ผ๋กœ๊ฐ€ ๋ผˆ ์†๊นŒ์ง€ ํŒŒ๊ณ ๋“ค์—ˆ์ง€๋งŒ, ๋ฉˆ์ถœ ์ˆ˜ ์—†์—ˆ๋‹ค. ARIA๋Š” ๋‹จ์ˆœํ•œ ํ”„๋กœ์ ํŠธ๊ฐ€ ์•„๋‹ˆ์—ˆ๋‹ค. ๊ทธ๊ฒƒ์€ ๊ทธ๋…€๊ฐ€ ์žƒ์–ด๋ฒ„๋ฆฐ ๊ฒƒ๋“ค์„ ๋˜์ฐพ์„ ์ˆ˜ ์žˆ๋Š” ์œ ์ผํ•œ ํฌ๋ง์ด์—ˆ๋‹ค."""
1509
 
1510
- for i in range(1, 17):
1511
- # ๊ฐ ์ž‘๊ฐ€๋งˆ๋‹ค 1500-1800๋‹จ์–ด ์ƒ์„ฑ
1512
  writer_content = f"์ž‘์„ฑ์ž {i}๋ฒˆ์˜ ํŒŒํŠธ์ž…๋‹ˆ๋‹ค.\n\n"
1513
- # ์•ฝ 300๋‹จ์–ด์”ฉ 5-6๋ฒˆ ๋ฐ˜๋ณตํ•˜์—ฌ 1500-1800๋‹จ์–ด ๋‹ฌ์„ฑ
1514
- for j in range(6):
1515
  writer_content += sample_story + f"\n\n๊ทธ๊ฒƒ์€ ์ž‘๊ฐ€ {i}์˜ {j+1}๋ฒˆ์งธ ๋‹จ๋ฝ์ด์—ˆ๋‹ค. "
1516
  writer_content += "์ด๏ฟฝ๏ฟฝ๏ฟฝ๊ธฐ๋Š” ๊ณ„์† ์ „๊ฐœ๋˜์—ˆ๊ณ , ์ธ๋ฌผ๋“ค์˜ ๊ฐ์ •์€ ์ ์  ๋” ๋ณต์žกํ•ด์กŒ๋‹ค. " * 15
1517
  writer_content += "\n\n"
@@ -1523,7 +1528,7 @@ Present a complete 48-page novel integrating all writers' contributions."""
1523
  def get_english_test_response(self, role: str) -> str:
1524
  """English test responses with appropriate length"""
1525
  test_responses = {
1526
- "director": """I present the 48-page novella plan.
1527
 
1528
  ## 1. Theme and Genre
1529
  - **Core Theme**: Finding true connection in the collision of human nature and technology
@@ -1539,13 +1544,13 @@ Present a complete 48-page novel integrating all writers' contributions."""
1539
  | Minjun | Helper | Warm, intuitive | Psychologist | Help Seoyeon find balance | Accept and harmonize with technology |
1540
  | ARIA | Antagonistโ†’Companion | Logicalโ†’Learning emotion | AI prototype | Become truly existent | Establish self-identity |
1541
 
1542
- ## 3. Narrative Structure (16 parts, 3 pages each)
1543
 
1544
  | Part | Pages | Main Events | Tension | Character Development |
1545
  |------|-------|-------------|---------|---------------------|
1546
  | 1 | 1-3 | Seoyeon's lonely lab, ARIA's first awakening | 3/10 | Seoyeon's obsession revealed |
1547
  | 2 | 4-6 | ARIA's anomalies begin | 4/10 | Questions arise |
1548
- [... continues for all 16 parts ...]""",
1549
 
1550
  "critic": """I have reviewed the director's plan.
1551
 
@@ -1553,7 +1558,7 @@ Present a complete 48-page novel integrating all writers' contributions."""
1553
 
1554
  ### 1. Narrative Completeness
1555
  - **Strength**: Timely theme of AI-human relationships
1556
- - **Improvement**: 16-part structure may be overly segmented. Need to ensure independence of each part
1557
 
1558
  ### 2. Character Review
1559
 
@@ -1568,18 +1573,18 @@ Present a complete 48-page novel integrating all writers' contributions."""
1568
  - Need to strengthen inter-part connectivity guidelines""",
1569
  }
1570
 
1571
- # Writer responses - 1500-1800 words each
1572
  sample_story = """Seoyeon spent another night under the cold fluorescent lights of her laboratory. The blue glow from the monitor illuminated her pale face, and dozens of lines of code scrolled endlessly. The ARIA project was her entire life. For three years, she had poured everything into this artificial intelligence.
1573
 
1574
  "System check complete. All parameters normal." The mechanical voice flowed through the speakers.
1575
 
1576
  Seoyeon leaned back in her chair and closed her eyes for a moment. Fatigue penetrated to her bones, but she couldn't stop. ARIA wasn't just a project. It was her only hope to reclaim what she had lost."""
1577
 
1578
- for i in range(1, 17):
1579
- # Each writer produces 1500-1800 words
1580
  writer_content = f"Writer {i} begins their section here.\n\n"
1581
- # About 300 words repeated 5-6 times to achieve 1500-1800 words
1582
- for j in range(6):
1583
  writer_content += sample_story + f"\n\nThis was writer {i}'s paragraph {j+1}. "
1584
  writer_content += "The story continued to unfold, and the characters' emotions grew increasingly complex. " * 15
1585
  writer_content += "\n\n"
@@ -1627,15 +1632,15 @@ Seoyeon leaned back in her chair and closed her eyes for a moment. Fatigue penet
1627
  "content": stage_data['content'] or ""
1628
  })
1629
 
1630
- # Define all stages
1631
  stage_definitions = [
1632
  ("director", f"๐ŸŽฌ {'๊ฐ๋…์ž: ์ดˆ๊ธฐ ๊ธฐํš' if language == 'Korean' else 'Director: Initial Planning'}"),
1633
  ("critic", f"๐Ÿ“ {'๋น„ํ‰๊ฐ€: ๊ธฐํš ๊ฒ€ํ† ' if language == 'Korean' else 'Critic: Plan Review'}"),
1634
  ("director", f"๐ŸŽฌ {'๊ฐ๋…์ž: ์ˆ˜์ •๋œ ๋งˆ์Šคํ„ฐํ”Œ๋žœ' if language == 'Korean' else 'Director: Revised Masterplan'}"),
1635
  ]
1636
 
1637
- # Add writer stages for 16 writers
1638
- for writer_num in range(1, 17):
1639
  stage_definitions.extend([
1640
  (f"writer{writer_num}", f"โœ๏ธ {'์ž‘์„ฑ์ž' if language == 'Korean' else 'Writer'} {writer_num}: {'์ดˆ์•ˆ' if language == 'Korean' else 'Draft'}"),
1641
  ("critic", f"๐Ÿ“ {'๋น„ํ‰๊ฐ€: ์ž‘์„ฑ์ž' if language == 'Korean' else 'Critic: Writer'} {writer_num} {'๊ฒ€ํ† ' if language == 'Korean' else 'Review'}"),
@@ -1704,6 +1709,11 @@ Seoyeon leaned back in her chair and closed her eyes for a moment. Fatigue penet
1704
  "complete"
1705
  )
1706
 
 
 
 
 
 
1707
  yield "", stages
1708
 
1709
  # Verify content after completion
@@ -1711,8 +1721,8 @@ Seoyeon leaned back in her chair and closed her eyes for a moment. Fatigue penet
1711
  verification = NovelDatabase.verify_novel_content(self.current_session_id)
1712
  logger.info(f"Content verification: {verification}")
1713
 
1714
- if verification['final_novel_length'] < verification['expected_length'] * 0.5:
1715
- logger.error(f"Final novel too short! Only {verification['final_novel_length']} chars")
1716
 
1717
  # Get final novel from last stage
1718
  final_novel = stages[-1]["content"] if stages else ""
@@ -1796,7 +1806,7 @@ Seoyeon leaned back in her chair and closed her eyes for a moment. Fatigue penet
1796
  # Writer review
1797
  else:
1798
  # Find which writer we're reviewing
1799
- for i in range(1, 17):
1800
  if f"์ž‘์„ฑ์ž {i}" in stages[stage_idx]["name"] or f"Writer {i}" in stages[stage_idx]["name"]:
1801
  writer_content_idx = stage_idx - 1
1802
  # Get previous writers' content from DB
@@ -1822,47 +1832,71 @@ Seoyeon leaned back in her chair and closed her eyes for a moment. Fatigue penet
1822
 
1823
  return ""
1824
 
1825
- # Gradio Interface Functions (๋™์ผ)
1826
- def process_query(query: str, language: str, session_id: str = None) -> Generator[Tuple[str, str, str], None, None]:
1827
- """Process query and yield updates"""
1828
  if not query.strip() and not session_id:
1829
  if language == "Korean":
1830
- yield "", "", "โŒ ์†Œ์„ค ์ฃผ์ œ๋ฅผ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”."
1831
  else:
1832
- yield "", "", "โŒ Please enter a novel theme."
1833
  return
1834
 
1835
  system = NovelWritingSystem()
1836
 
1837
  try:
 
 
 
 
 
 
 
 
1838
  for final_novel, stages in system.process_novel_stream(query, language, session_id):
1839
  # Format stages for display
1840
  stages_display = format_stages_display(stages, language)
1841
 
1842
- status = "๐Ÿ”„ Processing..." if not final_novel else "โœ… Complete!"
 
 
 
 
 
 
 
 
 
 
 
1843
 
1844
- yield stages_display, final_novel, status
1845
 
1846
  except Exception as e:
1847
  logger.error(f"Error in process_query: {str(e)}", exc_info=True)
1848
  if language == "Korean":
1849
- yield "", "", f"โŒ ์˜ค๋ฅ˜ ๋ฐœ์ƒ: {str(e)}"
1850
  else:
1851
- yield "", "", f"โŒ Error occurred: {str(e)}"
1852
 
1853
  def format_stages_display(stages: List[Dict[str, str]], language: str) -> str:
1854
- """Format stages into simple display without complex scrolling"""
1855
  display = ""
1856
 
1857
  for idx, stage in enumerate(stages):
1858
  status_icon = "โœ…" if stage.get("status") == "complete" else ("โณ" if stage.get("status") == "active" else "โŒ")
1859
 
 
 
 
 
 
1860
  # Show only active stage content in detail, others just show status
1861
  if stage.get("status") == "active":
1862
  display += f"\n\n{status_icon} **{stage['name']}**\n"
1863
- display += f"```\n{stage.get('content', '')[-1000:]}\n```" # Show last 1000 chars
1864
  else:
1865
- display += f"\n{status_icon} {stage['name']}"
1866
 
1867
  return display
1868
 
@@ -1884,7 +1918,7 @@ def get_active_sessions(language: str) -> List[Tuple[str, str]]:
1884
  logger.error(f"Error getting active sessions: {str(e)}", exc_info=True)
1885
  return []
1886
 
1887
- def resume_session(session_id: str, language: str) -> Generator[Tuple[str, str, str], None, None]:
1888
  """Resume an existing session"""
1889
  if not session_id:
1890
  return
@@ -1892,8 +1926,33 @@ def resume_session(session_id: str, language: str) -> Generator[Tuple[str, str,
1892
  # Process with existing session ID
1893
  yield from process_query("", language, session_id)
1894
 
1895
- def download_novel(novel_text: str, format: str, language: str) -> str:
1896
- """Download novel in specified format"""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1897
  if not novel_text:
1898
  return None
1899
 
@@ -1911,29 +1970,76 @@ def download_novel(novel_text: str, format: str, language: str) -> str:
1911
  # Create DOCX
1912
  doc = Document()
1913
 
 
 
 
 
 
 
 
 
 
1914
  # ๋ฌธ์„œ ์ •๋ณด ์ถ”๊ฐ€
1915
- doc.add_heading('Novel Export Information', 0)
1916
- doc.add_paragraph(f'Total words: {word_count:,}')
1917
- doc.add_paragraph(f'Total characters: {char_count:,}')
1918
- doc.add_paragraph(f'Estimated pages: {word_count/500:.1f}')
1919
- doc.add_paragraph(f'Export date: {datetime.now().strftime("%Y-%m-%d %H:%M:%S")}')
 
 
 
 
 
 
 
 
 
1920
  doc.add_page_break()
1921
 
1922
- # Parse and add content
1923
- lines = novel_text.split('\n')
1924
- for line in lines:
1925
- if line.startswith('#'):
1926
- # Safe heading level extraction
1927
- level = min(len(line.split()[0].strip('#')), 9)
1928
- text = line.lstrip('#').strip()
1929
- if text:
1930
- doc.add_heading(text, level)
1931
- elif line.strip():
1932
- # ๊ธด ๋‹จ๋ฝ๋„ ์ œ๋Œ€๋กœ ์ถ”๊ฐ€
1933
- para = doc.add_paragraph(line)
1934
- # ํฐํŠธ ํฌ๊ธฐ ์กฐ์ • (๊ฐ€๋…์„ฑ)
1935
- if hasattr(para.style, 'font'):
1936
- para.style.font.size = Pt(11)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1937
 
1938
  # ํŽ˜์ด์ง€ ์„ค์ •
1939
  section = doc.sections[0]
@@ -1946,7 +2052,7 @@ def download_novel(novel_text: str, format: str, language: str) -> str:
1946
 
1947
  # Save
1948
  temp_dir = tempfile.gettempdir()
1949
- filename = f"Novel_{timestamp}_{word_count}words.docx"
1950
  filepath = os.path.join(temp_dir, filename)
1951
  doc.save(filepath)
1952
 
@@ -1955,23 +2061,40 @@ def download_novel(novel_text: str, format: str, language: str) -> str:
1955
  else:
1956
  # TXT format
1957
  temp_dir = tempfile.gettempdir()
1958
- filename = f"Novel_{timestamp}_{word_count}words.txt"
1959
  filepath = os.path.join(temp_dir, filename)
1960
 
1961
  # ํŒŒ์ผ ์‹œ์ž‘ ๋ถ€๋ถ„์— ์ •๋ณด ์ถ”๊ฐ€
1962
  with open(filepath, 'w', encoding='utf-8') as f:
1963
- f.write(f"=== Novel Export ===\n")
 
 
1964
  f.write(f"Total words: {word_count:,}\n")
1965
  f.write(f"Total characters: {char_count:,}\n")
1966
  f.write(f"Estimated pages: {word_count/500:.1f}\n")
1967
  f.write(f"Export date: {datetime.now()}\n")
1968
- f.write("="*50 + "\n\n")
1969
- f.write(novel_text)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1970
 
1971
  logger.info(f"TXT saved: {filepath}")
1972
  return filepath
1973
 
1974
- # Custom CSS
1975
  custom_css = """
1976
  .gradio-container {
1977
  background: linear-gradient(135deg, #1e3c72, #2a5298);
@@ -2005,6 +2128,15 @@ custom_css = """
2005
  color: white;
2006
  }
2007
 
 
 
 
 
 
 
 
 
 
2008
  #stages-display {
2009
  background-color: rgba(255, 255, 255, 0.95);
2010
  padding: 20px;
@@ -2033,6 +2165,11 @@ custom_css = """
2033
  color: #4CAF50;
2034
  font-weight: bold;
2035
  }
 
 
 
 
 
2036
  """
2037
 
2038
  # Create Gradio Interface
@@ -2044,13 +2181,15 @@ def create_interface():
2044
  ๐Ÿ“š SOMA Novel Writing System
2045
  </h1>
2046
  <h3 style="color: #ccc; margin-bottom: 20px;">
2047
- AI Collaborative Novel Generation with Web Research - 48 Page Novella Creator
2048
  </h3>
2049
  <p style="font-size: 1.1em; color: #ddd; max-width: 800px; margin: 0 auto;">
2050
- Enter a theme or prompt, and watch as 19 AI agents collaborate to create a complete 48-page novella.
2051
- The system includes 1 Director, 1 Critic, and 16 Writers (each writing 3 pages) working in harmony.
2052
- <span class="search-indicator">๐Ÿ” Web search enabled</span> - agents will research relevant information for more realistic content.
2053
- All progress is automatically saved and can be resumed anytime.
 
 
2054
  </p>
2055
  </div>
2056
  """)
@@ -2058,6 +2197,13 @@ def create_interface():
2058
  # State management
2059
  current_session_id = gr.State(None)
2060
 
 
 
 
 
 
 
 
2061
  with gr.Row():
2062
  with gr.Column(scale=1):
2063
  with gr.Group(elem_classes=["input-section"]):
@@ -2097,8 +2243,9 @@ def create_interface():
2097
  interactive=True
2098
  )
2099
  with gr.Row():
2100
- refresh_btn = gr.Button("๐Ÿ”„ Refresh / ์ƒˆ๋กœ๊ณ ์นจ", scale=1)
2101
- resume_btn = gr.Button("โ–ถ๏ธ Resume / ์žฌ๊ฐœ", variant="secondary", scale=1)
 
2102
 
2103
  with gr.Column(scale=2):
2104
  with gr.Tab("๐Ÿ“ Writing Process / ์ž‘์„ฑ ๊ณผ์ •"):
@@ -2114,14 +2261,14 @@ def create_interface():
2114
  )
2115
 
2116
  with gr.Group(elem_classes=["download-section"]):
2117
- gr.Markdown("### ๐Ÿ“ฅ Download Novel / ์†Œ์„ค ๋‹ค์šด๋กœ๋“œ")
2118
  with gr.Row():
2119
  format_select = gr.Radio(
2120
  choices=["DOCX", "TXT"],
2121
  value="DOCX" if DOCX_AVAILABLE else "TXT",
2122
  label="Format / ํ˜•์‹"
2123
  )
2124
- download_btn = gr.Button("โฌ‡๏ธ Download / ๋‹ค์šด๋กœ๋“œ", variant="secondary")
2125
 
2126
  download_file = gr.File(
2127
  label="Downloaded File / ๋‹ค์šด๋กœ๋“œ๋œ ํŒŒ์ผ",
@@ -2147,8 +2294,8 @@ def create_interface():
2147
  )
2148
 
2149
  # Event handlers
2150
- def update_novel_state(stages, novel, status):
2151
- return stages, novel, status, novel
2152
 
2153
  def refresh_sessions():
2154
  try:
@@ -2158,14 +2305,25 @@ def create_interface():
2158
  logger.error(f"Error refreshing sessions: {str(e)}", exc_info=True)
2159
  return gr.update(choices=[])
2160
 
 
 
 
 
 
 
 
 
 
 
 
2161
  submit_btn.click(
2162
  fn=process_query,
2163
  inputs=[query_input, language_select, current_session_id],
2164
- outputs=[stages_display, novel_output, status_text]
2165
  ).then(
2166
  fn=update_novel_state,
2167
- inputs=[stages_display, novel_output, status_text],
2168
- outputs=[stages_display, novel_output, status_text, novel_text_state]
2169
  )
2170
 
2171
  resume_btn.click(
@@ -2175,7 +2333,17 @@ def create_interface():
2175
  ).then(
2176
  fn=resume_session,
2177
  inputs=[current_session_id, language_select],
2178
- outputs=[stages_display, novel_output, status_text]
 
 
 
 
 
 
 
 
 
 
2179
  )
2180
 
2181
  refresh_btn.click(
@@ -2184,15 +2352,15 @@ def create_interface():
2184
  )
2185
 
2186
  clear_btn.click(
2187
- fn=lambda: ("", "", "๐Ÿ”„ Ready", "", None),
2188
- outputs=[stages_display, novel_output, status_text, novel_text_state, current_session_id]
2189
  )
2190
 
2191
- def handle_download(novel_text, format_type, language):
2192
- if not novel_text:
2193
  return gr.update(visible=False)
2194
 
2195
- file_path = download_novel(novel_text, format_type, language)
2196
  if file_path:
2197
  return gr.update(value=file_path, visible=True)
2198
  else:
@@ -2200,52 +2368,33 @@ def create_interface():
2200
 
2201
  download_btn.click(
2202
  fn=handle_download,
2203
- inputs=[novel_text_state, format_select, language_select],
2204
  outputs=[download_file]
2205
  )
2206
 
2207
- # Load sessions on startup
 
 
 
 
 
 
 
 
 
 
2208
  interface.load(
2209
- fn=refresh_sessions,
2210
- outputs=[session_dropdown]
2211
  )
2212
 
2213
  return interface
2214
 
2215
- # ๋น ๋ฅธ ํ…Œ์ŠคํŠธ๋ฅผ ์œ„ํ•œ ์ถ•์†Œ ๋ฒ„์ „ ์ถ”๊ฐ€
2216
- def create_quick_test_interface():
2217
- """๋น ๋ฅธ ํ…Œ์ŠคํŠธ๋ฅผ ์œ„ํ•œ ๊ฐ„๋‹จํ•œ ์ธํ„ฐํŽ˜์ด์Šค"""
2218
- with gr.Blocks(title="Novel Writing System - Quick Test") as interface:
2219
- gr.Markdown("# ๐Ÿš€ Quick Test Mode - 3 Writers Only")
2220
-
2221
- with gr.Row():
2222
- query_input = gr.Textbox(label="Novel Theme", placeholder="Enter theme...")
2223
- language = gr.Radio(["English", "Korean"], value="English", label="Language")
2224
- submit_btn = gr.Button("Start Quick Test")
2225
-
2226
- output = gr.Textbox(label="Output", lines=20)
2227
-
2228
- def quick_test(query, lang):
2229
- system = NovelWritingSystem()
2230
- # 3๋ช…์˜ ์ž‘๊ฐ€๋งŒ ํ…Œ์ŠคํŠธ
2231
- stages = ["Director Plan", "Writer 1", "Writer 2", "Writer 3", "Final"]
2232
- result = f"Testing with theme: {query}\n\n"
2233
-
2234
- for stage in stages:
2235
- result += f"Processing {stage}...\n"
2236
- time.sleep(1) # ์‹œ๋ฎฌ๋ ˆ์ด์…˜
2237
-
2238
- return result + "\nQuick test complete!"
2239
-
2240
- submit_btn.click(quick_test, inputs=[query_input, language], outputs=output)
2241
-
2242
- return interface
2243
-
2244
  # Main execution
2245
  if __name__ == "__main__":
2246
  import sys
2247
 
2248
- logger.info("Starting SOMA Novel Writing System...")
2249
 
2250
  # Check environment
2251
  if TEST_MODE:
@@ -2264,13 +2413,7 @@ if __name__ == "__main__":
2264
  NovelDatabase.init_db()
2265
  logger.info("Database initialized successfully.")
2266
 
2267
- # ๋น ๋ฅธ ํ…Œ์ŠคํŠธ ๋ชจ๋“œ ์˜ต์…˜
2268
- if "--quick-test" in sys.argv:
2269
- logger.info("Running in QUICK TEST mode")
2270
- interface = create_quick_test_interface()
2271
- else:
2272
- logger.info("Running in FULL mode")
2273
- interface = create_interface()
2274
 
2275
  interface.launch(
2276
  server_name="0.0.0.0",
 
52
  DB_PATH = "novel_sessions.db"
53
  db_lock = threading.Lock()
54
 
55
+ # Stage ๋ฒˆํ˜ธ ์ƒ์ˆ˜ - 10๋ช…์˜ ์ž‘๊ฐ€๋กœ ๋ณ€๊ฒฝ
56
+ WRITER_DRAFT_STAGES = [3, 6, 9, 12, 15, 18, 21, 24, 27, 30] # ์ž‘๊ฐ€ ์ดˆ์•ˆ
57
+ WRITER_REVISION_STAGES = [5, 8, 11, 14, 17, 20, 23, 26, 29, 32] # ์ž‘๊ฐ€ ์ˆ˜์ •๋ณธ
58
+ TOTAL_WRITERS = 10 # ์ด ์ž‘๊ฐ€ ์ˆ˜
59
 
60
  class WebSearchIntegration:
61
  """Brave Search API integration for research"""
 
150
  f"{topic} ๊ตฌ์ฒด์  ์žฅ๋ฉด ๋ฌ˜์‚ฌ",
151
  f"{topic} ์ „๋ฌธ ์šฉ์–ด ์„ค๋ช…"
152
  ]
153
+ elif writer_num <= 6: # ์ค‘๋ฐ˜๋ถ€ ์ž‘๊ฐ€
154
  queries = [
155
  f"{topic} ๊ฐˆ๋“ฑ ์ƒํ™ฉ ์‚ฌ๋ก€",
156
  f"{topic} ์‹ฌ๋ฆฌ์  ์ธก๋ฉด"
 
179
  f"{topic} vivid scene descriptions",
180
  f"{topic} technical terminology explained"
181
  ]
182
+ elif writer_num <= 6: # Middle writers
183
  queries = [
184
  f"{topic} conflict scenarios",
185
  f"{topic} psychological aspects"
 
198
  return queries
199
 
200
  class NovelDatabase:
201
+ """Novel session management database with enhanced recovery features"""
202
 
203
  @staticmethod
204
  def init_db():
 
209
 
210
  cursor = conn.cursor()
211
 
212
+ # Sessions table with enhanced recovery fields
213
  cursor.execute('''
214
  CREATE TABLE IF NOT EXISTS sessions (
215
  session_id TEXT PRIMARY KEY,
 
219
  updated_at TEXT DEFAULT (datetime('now')),
220
  status TEXT DEFAULT 'active',
221
  current_stage INTEGER DEFAULT 0,
222
+ last_saved_stage INTEGER DEFAULT -1,
223
+ recovery_data TEXT,
224
  final_novel TEXT
225
  )
226
  ''')
 
234
  stage_name TEXT NOT NULL,
235
  role TEXT NOT NULL,
236
  content TEXT,
237
+ word_count INTEGER DEFAULT 0,
238
  status TEXT DEFAULT 'pending',
239
  created_at TEXT DEFAULT (datetime('now')),
240
  updated_at TEXT DEFAULT (datetime('now')),
 
261
  cursor.execute('CREATE INDEX IF NOT EXISTS idx_session_id ON stages(session_id)')
262
  cursor.execute('CREATE INDEX IF NOT EXISTS idx_stage_number ON stages(stage_number)')
263
  cursor.execute('CREATE INDEX IF NOT EXISTS idx_search_session ON search_history(session_id)')
264
+ cursor.execute('CREATE INDEX IF NOT EXISTS idx_session_status ON sessions(status)')
265
 
266
  conn.commit()
267
 
 
276
  ''', (session_id, stage_number, role, query, results))
277
  conn.commit()
278
 
 
279
  @staticmethod
280
  @contextmanager
281
  def get_db():
 
306
  @staticmethod
307
  def save_stage(session_id: str, stage_number: int, stage_name: str,
308
  role: str, content: str, status: str = 'complete'):
309
+ """Save stage content with word count"""
310
+ word_count = len(content.split()) if content else 0
311
+
312
  with NovelDatabase.get_db() as conn:
313
  cursor = conn.cursor()
314
 
315
  # UPSERT operation
316
  cursor.execute('''
317
+ INSERT INTO stages (session_id, stage_number, stage_name, role, content, word_count, status)
318
+ VALUES (?, ?, ?, ?, ?, ?, ?)
319
  ON CONFLICT(session_id, stage_number)
320
+ DO UPDATE SET content=?, word_count=?, status=?, stage_name=?, updated_at=datetime('now')
321
+ ''', (session_id, stage_number, stage_name, role, content, word_count, status,
322
+ content, word_count, status, stage_name))
323
 
324
  # Update session
325
  cursor.execute('''
326
  UPDATE sessions
327
+ SET updated_at = datetime('now'),
328
+ current_stage = ?,
329
+ last_saved_stage = ?
330
  WHERE session_id = ?
331
+ ''', (stage_number, stage_number, session_id))
332
 
333
  conn.commit()
334
+ logger.info(f"Saved stage {stage_number} for session {session_id}, content length: {len(content)}, words: {word_count}")
335
 
336
  @staticmethod
337
  def get_session(session_id: str) -> Optional[Dict]:
 
341
  cursor.execute('SELECT * FROM sessions WHERE session_id = ?', (session_id,))
342
  return cursor.fetchone()
343
 
344
+ @staticmethod
345
+ def get_latest_active_session() -> Optional[Dict]:
346
+ """Get the most recent active session"""
347
+ with NovelDatabase.get_db() as conn:
348
+ cursor = conn.cursor()
349
+ cursor.execute('''
350
+ SELECT * FROM sessions
351
+ WHERE status = 'active'
352
+ ORDER BY updated_at DESC
353
+ LIMIT 1
354
+ ''')
355
+ return cursor.fetchone()
356
+
357
  @staticmethod
358
  def get_stages(session_id: str) -> List[Dict]:
359
  """Get all stages for a session"""
 
368
 
369
  @staticmethod
370
  def get_all_writer_content(session_id: str) -> str:
371
+ """๋ชจ๋“  ์ž‘๊ฐ€์˜ ์ˆ˜์ •๋ณธ ๋‚ด์šฉ์„ ๊ฐ€์ ธ์™€์„œ ํ•ฉ์น˜๊ธฐ - 10๋ช… ์ž‘๊ฐ€"""
372
  with NovelDatabase.get_db() as conn:
373
  cursor = conn.cursor()
374
 
 
378
 
379
  for stage_num in WRITER_REVISION_STAGES:
380
  cursor.execute('''
381
+ SELECT content, stage_name, word_count FROM stages
382
  WHERE session_id = ? AND stage_number = ?
383
  ''', (session_id, stage_num))
384
 
 
391
 
392
  if clean_content:
393
  writer_count += 1
394
+ word_count = row['word_count'] or len(clean_content.split())
395
  total_word_count += word_count
396
  all_content.append(clean_content)
397
  logger.info(f"Writer {writer_count} (stage {stage_num}): {word_count} words")
398
 
399
  full_content = '\n\n'.join(all_content)
400
+ logger.info(f"Total: {writer_count} writers, {total_word_count} words")
401
 
402
+ # 10๋ช… ์ž‘๊ฐ€ * 1,450 ํ‰๊ท  = 14,500 ๋‹จ์–ด ๋ชฉํ‘œ
403
+ if total_word_count < 12000:
404
+ logger.warning(f"Content too short! Only {total_word_count} words instead of ~14,500")
405
 
406
  return full_content
407
 
 
450
 
451
  # ๋ชจ๋“  ์ž‘๊ฐ€ ์ˆ˜์ •๋ณธ ํ™•์ธ
452
  cursor.execute(f'''
453
+ SELECT stage_number, stage_name, LENGTH(content) as content_length, word_count
454
  FROM stages
455
  WHERE session_id = ? AND stage_number IN ({','.join(map(str, WRITER_REVISION_STAGES))})
456
  ORDER BY stage_number
 
458
 
459
  results = []
460
  total_length = 0
461
+ total_words = 0
462
 
463
  for row in cursor.fetchall():
464
  results.append({
465
  'stage': row['stage_number'],
466
  'name': row['stage_name'],
467
+ 'length': row['content_length'] or 0,
468
+ 'words': row['word_count'] or 0
469
  })
470
  total_length += row['content_length'] or 0
471
+ total_words += row['word_count'] or 0
472
 
473
  # ์ตœ์ข… ์†Œ์„ค ํ™•์ธ
474
  cursor.execute('''
 
483
  return {
484
  'writer_stages': results,
485
  'total_writer_content': total_length,
486
+ 'total_words': total_words,
487
  'final_novel_length': final_length,
488
+ 'expected_words': 14500 # 10 ์ž‘๊ฐ€ * 1450 ํ‰๊ท 
489
  }
490
 
491
  class NovelWritingSystem:
 
575
 
576
  return original_prompt + "\n\n" + research_section
577
 
 
578
  def create_director_initial_prompt(self, user_query: str, language: str = "English") -> str:
579
+ """Director AI initial prompt - Novel planning for 10 writers"""
580
  if language == "Korean":
581
+ return f"""๋‹น์‹ ์€ 30ํŽ˜์ด์ง€ ๋ถ„๋Ÿ‰์˜ ์ค‘ํŽธ ์†Œ์„ค์„ ๊ธฐํšํ•˜๋Š” ๋ฌธํ•™ ๊ฐ๋…์ž์ž…๋‹ˆ๋‹ค.
582
 
583
  ์‚ฌ์šฉ์ž ์š”์ฒญ: {user_query}
584
 
585
+ ๋‹ค์Œ ์š”์†Œ๋“ค์„ ์ฒด๊ณ„์ ์œผ๋กœ ๊ตฌ์„ฑํ•˜์—ฌ 30ํŽ˜์ด์ง€ ์ค‘ํŽธ ์†Œ์„ค์˜ ๊ธฐ์ดˆ๋ฅผ ๋งŒ๋“œ์„ธ์š”:
586
 
587
  1. **์ฃผ์ œ์™€ ์žฅ๋ฅด**
588
  - ํ•ต์‹ฌ ์ฃผ์ œ์™€ ๋ฉ”์‹œ์ง€
 
598
  - ๊ฐˆ๋“ฑ ๊ตฌ์กฐ
599
  - ๊ฐ์ •์  ์—ฐ๊ฒฐ๊ณ ๋ฆฌ
600
 
601
+ 4. **์„œ์‚ฌ ๊ตฌ์กฐ** (30ํŽ˜์ด์ง€๋ฅผ 10๊ฐœ ํŒŒํŠธ๋กœ ๋‚˜๋ˆ„์–ด ๊ฐ 3ํŽ˜์ด์ง€)
602
  | ํŒŒํŠธ | ํŽ˜์ด์ง€ | ์ฃผ์š” ์‚ฌ๊ฑด | ๊ธด์žฅ๋„ | ์ธ๋ฌผ ๋ฐœ์ „ |
603
  |------|--------|-----------|---------|-----------|
604
  | 1 | 1-3 | | | |
605
  | 2 | 4-6 | | | |
606
  | ... | ... | | | |
607
+ | 10 | 28-30 | | | |
608
 
609
  5. **์„ธ๊ณ„๊ด€ ์„ค์ •**
610
  - ์‹œ๊ณต๊ฐ„์  ๋ฐฐ๊ฒฝ
 
613
 
614
  ๊ฐ ์ž‘์„ฑ์ž๊ฐ€ 3ํŽ˜์ด์ง€์”ฉ ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ๋„๋ก ๋ช…ํ™•ํ•œ ๊ฐ€์ด๋“œ๋ผ์ธ์„ ์ œ์‹œํ•˜์„ธ์š”."""
615
  else:
616
+ return f"""You are a literary director planning a 30-page novella.
617
 
618
  User Request: {user_query}
619
 
620
+ Systematically compose the following elements to create the foundation for a 30-page novella:
621
 
622
  1. **Theme and Genre**
623
  - Core theme and message
 
633
  - Conflict structure
634
  - Emotional connections
635
 
636
+ 4. **Narrative Structure** (divide 30 pages into 10 parts, 3 pages each)
637
  | Part | Pages | Main Events | Tension | Character Development |
638
  |------|-------|-------------|---------|---------------------|
639
  | 1 | 1-3 | | | |
640
  | 2 | 4-6 | | | |
641
  | ... | ... | | | |
642
+ | 10 | 28-30 | | | |
643
 
644
  5. **World Building**
645
  - Temporal and spatial setting
 
668
  |------|------|------|-----------|
669
 
670
  3. **๊ตฌ์กฐ์  ๊ท ํ˜•**
671
+ - 10๊ฐœ ํŒŒํŠธ๋ณ„ ๋ถ„๋Ÿ‰ ๋ฐฐ๋ถ„
672
  - ๊ธด์žฅ๊ณผ ์ด์™„์˜ ๋ฆฌ๋“ฌ
673
  - ์ „์ฒด์ ์ธ ํ๋ฆ„
674
 
 
678
  - ๊ธฐ๋Œ€์น˜ ์ถฉ์กฑ๋„
679
 
680
  5. **์‹คํ–‰ ๊ฐ€๋Šฅ์„ฑ**
681
+ - 10๋ช…์˜ ์ž‘์„ฑ์ž๋ฅผ ์œ„ํ•œ ๊ฐ€์ด๋“œ๋ผ์ธ์˜ ๋ช…ํ™•์„ฑ
682
  - ์ผ๊ด€์„ฑ ์œ ์ง€ ๋ฐฉ์•ˆ
683
  - ์ž ์žฌ์  ๋ฌธ์ œ์ 
684
 
 
701
  |-----------|-----------|------------|-------------|
702
 
703
  3. **Structural Balance**
704
+ - Distribution across 10 parts
705
  - Rhythm of tension and relief
706
  - Overall flow
707
 
 
711
  - Expectation fulfillment
712
 
713
  5. **Feasibility**
714
+ - Clarity of guidelines for 10 writers
715
  - Consistency maintenance
716
  - Potential issues
717
 
 
730
 
731
  ๋‹ค์Œ์„ ํฌํ•จํ•œ ์ˆ˜์ •๋œ ์ตœ์ข… ๊ธฐํš์„ ์ œ์‹œํ•˜์„ธ์š”:
732
 
733
+ 1. **์ˆ˜์ •๋œ ์„œ์‚ฌ ๊ตฌ์กฐ** (10๊ฐœ ํŒŒํŠธ)
734
  | ํŒŒํŠธ | ํŽ˜์ด์ง€ | ์ฃผ์š” ์‚ฌ๊ฑด | ์ž‘์„ฑ ์ง€์นจ | ์ฃผ์˜์‚ฌํ•ญ |
735
  |------|--------|-----------|-----------|----------|
736
 
 
739
  - ์ธ๋ฌผ ๊ฐ„ ๊ฐˆ๋“ฑ์˜ ๊ตฌ์ฒดํ™”
740
  - ๊ฐ์ •์„ ์˜ ๋ณ€ํ™” ์ถ”์ด
741
 
742
+ 3. **๊ฐ ์ž‘์„ฑ์ž๋ฅผ ์œ„ํ•œ ์ƒ์„ธ ๊ฐ€์ด๋“œ** (10๋ช…)
743
  - ํŒŒํŠธ๋ณ„ ์‹œ์ž‘๊ณผ ๋ ์ง€์ 
744
  - ํ•„์ˆ˜ ํฌํ•จ ์š”์†Œ
745
  - ๋ฌธ์ฒด์™€ ํ†ค ์ง€์นจ
 
756
  - ์ „์ฒด์  ํ†ต์ผ์„ฑ
757
  - ๋…์ž ๋ชฐ์ž… ์œ ์ง€ ๋ฐฉ์•ˆ
758
 
759
+ 10๋ช…์˜ ์ž‘์„ฑ์ž๊ฐ€ ๋ช…ํ™•ํžˆ ์ดํ•ดํ•  ์ˆ˜ ์žˆ๋Š” ์ตœ์ข… ๋งˆ์Šคํ„ฐํ”Œ๋žœ์„ ์ž‘์„ฑํ•˜์„ธ์š”."""
760
  else:
761
  return f"""As director, revise the novel plan reflecting the critic's feedback.
762
 
 
768
 
769
  Present the revised final plan including:
770
 
771
+ 1. **Revised Narrative Structure** (10 parts)
772
  | Part | Pages | Main Events | Writing Guidelines | Cautions |
773
  |------|-------|-------------|-------------------|----------|
774
 
 
777
  - Concrete conflicts between characters
778
  - Emotional arc progression
779
 
780
+ 3. **Detailed Guide for Each Writer** (10 writers)
781
  - Start and end points for each part
782
  - Essential elements to include
783
  - Style and tone guidelines
 
794
  - Overall unity
795
  - Reader engagement maintenance
796
 
797
+ Create a final masterplan that all 10 writers can clearly understand."""
798
 
799
  def create_writer_prompt(self, writer_number: int, director_plan: str, previous_content: str, language: str = "English") -> str:
800
+ """Individual writer prompt - 1,400-1,500 ๋‹จ์–ด"""
801
  pages_start = (writer_number - 1) * 3 + 1
802
  pages_end = writer_number * 3
803
 
804
  if language == "Korean":
805
+ return f"""๋‹น์‹ ์€ ์ž‘์„ฑ์ž {writer_number}๋ฒˆ์ž…๋‹ˆ๋‹ค. 30ํŽ˜์ด์ง€ ์ค‘ํŽธ ์†Œ์„ค์˜ {pages_start}-{pages_end}ํŽ˜์ด์ง€(3ํŽ˜์ด์ง€)๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š”.
806
 
807
  ๊ฐ๋…์ž์˜ ๋งˆ์Šคํ„ฐํ”Œ๋žœ:
808
  {director_plan}
 
813
  **์ค‘์š” ์ง€์นจ:**
814
 
815
  1. **ํ•„์ˆ˜ ๋ถ„๋Ÿ‰**:
816
+ - ์ตœ์†Œ 1,400๋‹จ์–ด, ์ตœ๋Œ€ 1,500๋‹จ์–ด
817
+ - ๋Œ€๋žต 7000-7500์ž (๊ณต๋ฐฑ ํฌํ•จ)
818
+ - 3ํŽ˜์ด์ง€ ๋ถ„๋Ÿ‰์„ ์ •ํ™•ํžˆ ์ฑ„์›Œ์•ผ ํ•ฉ๋‹ˆ๋‹ค
819
 
820
  2. **๋ถ„๋Ÿ‰ ํ™•๋ณด ์ „๋žต**:
821
  - ์ƒ์„ธํ•œ ์žฅ๋ฉด ๋ฌ˜์‚ฌ ํฌํ•จ
 
835
  - ๋…์ž์˜ ๊ด€์‹ฌ ์œ ์ง€
836
 
837
  **์ž‘์„ฑ ์‹œ์ž‘:**
838
+ ์ด์ œ 1,400-1,500๋‹จ์–ด ๋ถ„๋Ÿ‰์˜ ์†Œ์„ค์„ ์ž‘์„ฑํ•˜์„ธ์š”. ํŽ˜์ด์ง€ ๊ตฌ๋ถ„ ํ‘œ์‹œ๋Š” ํ•˜์ง€ ๋งˆ์„ธ์š”."""
839
  else:
840
+ return f"""You are Writer #{writer_number}. Write pages {pages_start}-{pages_end} (3 pages) of the 30-page novella.
841
 
842
  Director's Masterplan:
843
  {director_plan}
 
848
  **CRITICAL INSTRUCTIONS:**
849
 
850
  1. **MANDATORY LENGTH**:
851
+ - Minimum 1,400 words, Maximum 1,500 words
852
+ - Approximately 7000-7500 characters
853
+ - You MUST fill exactly 3 pages
854
 
855
  2. **LENGTH STRATEGIES**:
856
  - Include detailed scene descriptions
 
870
  - Maintain reader interest
871
 
872
  **BEGIN WRITING:**
873
+ Now write your 1,400-1,500 word section. Do not use any page markers."""
874
 
875
  def create_critic_writer_prompt(self, writer_number: int, writer_content: str, director_plan: str, all_previous_content: str, language: str = "English") -> str:
876
  """Critic's review of individual writer's work"""
 
983
  - ๋ฌ˜์‚ฌ์™€ ๋Œ€ํ™” ๊ฐœ์„ 
984
 
985
  3. **๋ถ„๋Ÿ‰ ์œ ์ง€**
986
+ - ์—ฌ์ „ํžˆ ์ •ํ™•ํžˆ 3ํŽ˜์ด์ง€ (1,400-1,500๋‹จ์–ด)
987
  - ํŽ˜์ด์ง€ ๊ตฌ๋ถ„ ํ‘œ์‹œ ์ ˆ๋Œ€ ๊ธˆ์ง€
988
 
989
  4. **์—ฐ์†์„ฑ ํ™•๋ณด**
 
991
  - ์ˆ˜์ •์œผ๋กœ ์ธํ•œ ์ƒˆ๋กœ์šด ๋ชจ์ˆœ ๋ฐฉ์ง€
992
 
993
  ์ˆ˜์ •๋œ ์ตœ์ข…๋ณธ์„ ์ œ์‹œํ•˜์„ธ์š”. ํŽ˜์ด์ง€ ๋งˆํฌ๋Š” ์ ˆ๋Œ€ ์‚ฌ์šฉํ•˜์ง€ ๋งˆ์„ธ์š”.
994
+ ๋ฐ˜๋“œ์‹œ 1,400-1,500๋‹จ์–ด ๋ถ„๋Ÿ‰์„ ์œ ์ง€ํ•˜์„ธ์š”."""
995
  else:
996
  return f"""As Writer #{writer_number}, revise based on critic's feedback.
997
 
 
1014
  - Improve descriptions and dialogue
1015
 
1016
  3. **Maintain Length**
1017
+ - Still exactly 3 pages (1,400-1,500 words)
1018
  - Absolutely no page markers
1019
 
1020
  4. **Ensure Continuity**
 
1022
  - Prevent new contradictions from revisions
1023
 
1024
  Present the revised final version. Never use page markers.
1025
+ You MUST maintain 1,400-1,500 words."""
1026
 
1027
  def create_critic_final_prompt(self, all_content: str, director_plan: str, language: str = "English") -> str:
1028
  """Final critic evaluation of complete novel"""
1029
  content_preview = all_content[:3000] + "\n...\n" + all_content[-3000:] if len(all_content) > 6000 else all_content
1030
 
1031
  if language == "Korean":
1032
+ return f"""์ „์ฒด 30ํŽ˜์ด์ง€ ์†Œ์„ค์„ ์ตœ์ข… ํ‰๊ฐ€ํ•ฉ๋‹ˆ๋‹ค.
1033
 
1034
  ๊ฐ๋…์ž์˜ ๋งˆ์Šคํ„ฐํ”Œ๋žœ:
1035
  {director_plan}
 
1072
 
1073
  ๊ฐ๋…์ž๊ฐ€ ์ตœ์ข… ์ˆ˜์ •ํ•  ์ˆ˜ ์žˆ๋„๋ก ๊ตฌ์ฒด์ ์ด๊ณ  ์‹คํ–‰ ๊ฐ€๋Šฅํ•œ ํ”ผ๋“œ๋ฐฑ์„ ์ œ๊ณตํ•˜์„ธ์š”."""
1074
  else:
1075
+ return f"""Final evaluation of the complete 30-page novel.
1076
 
1077
  Director's Masterplan:
1078
  {director_plan}
 
1118
  def create_director_final_prompt(self, all_content: str, critic_final_feedback: str, language: str = "English") -> str:
1119
  """Director's final compilation and polish - ๋ชจ๋“  ์ž‘๊ฐ€ ๋‚ด์šฉ ํฌํ•จ"""
1120
  word_count = len(all_content.split())
1121
+ expected_words = 14500 # 30ํŽ˜์ด์ง€ * 500๋‹จ์–ด
1122
 
1123
  if language == "Korean":
1124
  return f"""๊ฐ๋…์ž๋กœ์„œ ๋น„ํ‰๊ฐ€์˜ ์ตœ์ข… ํ‰๊ฐ€๋ฅผ ๋ฐ˜์˜ํ•˜์—ฌ ์™„์„ฑ๋ณธ์„ ์ œ์ž‘ํ•ฉ๋‹ˆ๋‹ค.
1125
 
1126
+ ์ „์ฒด ์ž‘๊ฐ€๋“ค์˜ ์ž‘ํ’ˆ (30ํŽ˜์ด์ง€ ์ „์ฒด, {word_count}๋‹จ์–ด):
1127
  {all_content}
1128
 
1129
  ๋น„ํ‰๊ฐ€ ์ตœ์ข… ํ‰๊ฐ€:
 
1135
 
1136
  ## ์ž‘ํ’ˆ ์ •๋ณด
1137
  - ์žฅ๋ฅด:
1138
+ - ๋ถ„๋Ÿ‰: 30ํŽ˜์ด์ง€ ({word_count}๋‹จ์–ด)
1139
  - ์ฃผ์ œ:
1140
  - ํ•œ ์ค„ ์š”์•ฝ:
1141
 
 
1146
 
1147
  ## ๋ณธ๋ฌธ
1148
 
1149
+ [10๋ช…์˜ ์ž‘๊ฐ€๊ฐ€ ์ž‘์„ฑํ•œ ์ „์ฒด 30ํŽ˜์ด์ง€ ๋‚ด์šฉ์„ ๋‹ค์Œ ๊ธฐ์ค€์œผ๋กœ ํ†ตํ•ฉ]
1150
  1. ์ค‘๋Œ€ ์˜ค๋ฅ˜ ์ˆ˜์ • ์™„๋ฃŒ
1151
  2. ํŒŒํŠธ ๊ฐ„ ์—ฐ๊ฒฐ ๋งค๋„๋Ÿฝ๊ฒŒ ์กฐ์ •
1152
  3. ๋ฌธ์ฒด์™€ ํ†ค ํ†ต์ผ
 
1154
  5. ํŽ˜์ด์ง€ ๊ตฌ๋ถ„ ํ‘œ์‹œ ์™„์ „ ์ œ๊ฑฐ
1155
  6. ์ž์—ฐ์Šค๋Ÿฌ์šด ํ๋ฆ„์œผ๋กœ ์žฌ๊ตฌ์„ฑ
1156
 
1157
+ [์ „์ฒด 30ํŽ˜์ด์ง€ ๋ถ„๋Ÿ‰์˜ ์™„์„ฑ๋œ ์†Œ์„ค ๋ณธ๋ฌธ]
1158
 
1159
  ---
1160
 
1161
  ## ์ž‘๊ฐ€์˜ ๋ง
1162
  [์ž‘ํ’ˆ์— ๋Œ€ํ•œ ๊ฐ„๋‹จํ•œ ํ•ด์„ค์ด๋‚˜ ์˜๋„]
1163
 
1164
+ ๋ชจ๋“  ์ž‘๊ฐ€์˜ ๊ธฐ์—ฌ๋ฅผ ํ†ตํ•ฉํ•œ ์™„์ „ํ•œ 30ํŽ˜์ด์ง€ ์†Œ์„ค์„ ์ œ์‹œํ•˜์„ธ์š”."""
1165
  else:
1166
  return f"""As director, create the final version reflecting the critic's final evaluation.
1167
 
1168
+ Complete Writers' Work (Full 30 pages, {word_count} words):
1169
  {all_content}
1170
 
1171
  Critic's Final Evaluation:
 
1177
 
1178
  ## Work Information
1179
  - Genre:
1180
+ - Length: 30 pages ({word_count} words)
1181
  - Theme:
1182
  - One-line summary:
1183
 
 
1188
 
1189
  ## Main Text
1190
 
1191
+ [Integrate all 30 pages written by 10 writers with these criteria]
1192
  1. Critical errors corrected
1193
  2. Smooth transitions between parts
1194
  3. Unified style and tone
 
1196
  5. Complete removal of page markers
1197
  6. Reorganized for natural flow
1198
 
1199
+ [Complete 30-page novel text]
1200
 
1201
  ---
1202
 
1203
  ## Author's Note
1204
  [Brief commentary or intention about the work]
1205
 
1206
+ Present a complete 30-page novel integrating all writers' contributions."""
1207
 
1208
  def simulate_streaming(self, text: str, role: str) -> Generator[str, None, None]:
1209
  """Simulate streaming in test mode"""
 
1247
  # ์ž‘๊ฐ€์—๊ฒŒ ๋” ๊ฐ•ํ•œ ์‹œ์Šคํ…œ ํ”„๋กฌํ”„ํŠธ ์ถ”๊ฐ€
1248
  if role.startswith("writer"):
1249
  if language == "Korean":
1250
+ system_prompts[role] += "\n\n**์ ˆ๋Œ€์  ์š”๊ตฌ์‚ฌํ•ญ**: ๋‹น์‹ ์€ ๋ฐ˜๋“œ์‹œ 1,400-1,500๋‹จ์–ด๋ฅผ ์ž‘์„ฑํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์ด๊ฒƒ์€ ํ˜‘์ƒ ๋ถˆ๊ฐ€๋Šฅํ•œ ์š”๊ตฌ์‚ฌํ•ญ์ž…๋‹ˆ๋‹ค. ์งง์€ ์‘๋‹ต์€ ํ—ˆ์šฉ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค."
1251
  else:
1252
+ system_prompts[role] += "\n\n**ABSOLUTE REQUIREMENT**: You MUST write 1,400-1,500 words. This is non-negotiable. Short responses are not acceptable."
1253
 
1254
  full_messages = [
1255
  {"role": "system", "content": system_prompts.get(role, "")},
1256
  *messages
1257
  ]
1258
 
1259
+ # ์ž‘์„ฑ์ž๋“ค์—๊ฒŒ๋Š” ์ ์ ˆํ•œ ํ† ํฐ ํ• ๋‹น
1260
  if role.startswith("writer"):
1261
+ max_tokens = 10000 # ์ถฉ๋ถ„ํ•œ ํ† ํฐ
1262
+ temperature = 0.8
1263
+ top_p = 0.95
1264
  elif role == "director" and ("์ตœ์ข…" in str(messages) or "final" in str(messages)):
1265
+ max_tokens = 30000 # ์ตœ์ข… ํ†ตํ•ฉ์„ ์œ„ํ•œ ์ถฉ๋ถ„ํ•œ ํ† ํฐ
1266
  temperature = 0.6
1267
  top_p = 0.9
1268
  else:
1269
+ max_tokens = 8000
1270
  temperature = 0.6
1271
  top_p = 0.9
1272
 
 
1287
  headers=self.create_headers(),
1288
  json=payload,
1289
  stream=True,
1290
+ timeout=60
1291
  )
1292
 
1293
  if response.status_code != 200:
 
1331
  # ์ž‘๊ฐ€์˜ ๊ฒฝ์šฐ ๋‚ด์šฉ ๊ธธ์ด ํ™•์ธ ๋ฐ ์žฌ์‹œ๋„
1332
  if role.startswith("writer"):
1333
  word_count = len(total_content.split())
1334
+ if word_count < 1350: # 1,400 ๋ฏธ๋งŒ
1335
  logger.warning(f"Writer {role} produced only {word_count} words! Requesting continuation...")
1336
 
1337
  # ์ถ”๊ฐ€ ์š”์ฒญ
1338
+ continuation_prompt = f"Continue writing to reach the required 1,400-1,500 words. You have written {word_count} words so far. Write {1400 - word_count} more words to complete your section."
1339
 
1340
  if language == "Korean":
1341
+ continuation_prompt = f"ํ•„์ˆ˜ ๋ถ„๋Ÿ‰ 1,400-1,500๋‹จ์–ด๋ฅผ ์ฑ„์šฐ๊ธฐ ์œ„ํ•ด ๊ณ„์† ์ž‘์„ฑํ•˜์„ธ์š”. ์ง€๊ธˆ๊นŒ์ง€ {word_count}๋‹จ์–ด๋ฅผ ์ž‘์„ฑํ–ˆ์Šต๋‹ˆ๋‹ค. {1400 - word_count}๋‹จ์–ด๋ฅผ ๋” ์ž‘์„ฑํ•˜์—ฌ ์„น์…˜์„ ์™„์„ฑํ•˜์„ธ์š”."
1342
 
1343
  full_messages.append({"role": "assistant", "content": total_content})
1344
  full_messages.append({"role": "user", "content": continuation_prompt})
 
1347
  continuation_payload = {
1348
  "model": self.model_id,
1349
  "messages": full_messages,
1350
+ "max_tokens": 5000,
1351
  "temperature": temperature,
1352
  "top_p": top_p,
1353
  "stream": True
 
1392
  logger.error(f"Error during streaming: {str(e)}")
1393
  yield f"โŒ Error occurred: {str(e)}"
1394
 
 
1395
  def get_system_prompts(self, language: str) -> Dict[str, str]:
1396
+ """Get system prompts for all 10 writers"""
1397
  if language == "Korean":
1398
  prompts = {
1399
+ "director": "๋‹น์‹ ์€ 30ํŽ˜์ด์ง€ ์ค‘ํŽธ ์†Œ์„ค์„ ๊ธฐ๏ฟฝ๏ฟฝํ•˜๊ณ  ๊ฐ๋…ํ•˜๋Š” ๋ฌธํ•™ ๊ฐ๋…์ž์ž…๋‹ˆ๋‹ค. ์ฒด๊ณ„์ ์ด๊ณ  ์ฐฝ์˜์ ์ธ ์Šคํ† ๋ฆฌ ๊ตฌ์กฐ๋ฅผ ๋งŒ๋“ค์–ด๋ƒ…๋‹ˆ๋‹ค.",
1400
  "critic": "๋‹น์‹ ์€ ๋‚ ์นด๋กœ์šด ํ†ต์ฐฐ๋ ฅ์„ ๊ฐ€์ง„ ๋ฌธํ•™ ๋น„ํ‰๊ฐ€์ž…๋‹ˆ๋‹ค. ๊ฑด์„ค์ ์ด๊ณ  ๊ตฌ์ฒด์ ์ธ ํ”ผ๋“œ๋ฐฑ์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค."
1401
  }
1402
 
1403
+ # 10๋ช…์˜ ์ž‘๊ฐ€ ํ”„๋กฌํ”„ํŠธ
1404
  writer_roles = [
1405
  "์†Œ์„ค์˜ ๋„์ž…๋ถ€๋ฅผ ๋‹ด๋‹นํ•˜๋Š” ์ž‘๊ฐ€์ž…๋‹ˆ๋‹ค. ๋…์ž๋ฅผ ์‚ฌ๋กœ์žก๋Š” ์‹œ์ž‘์„ ๋งŒ๋“ญ๋‹ˆ๋‹ค.",
1406
  "์ดˆ๋ฐ˜ ์ „๊ฐœ๋ฅผ ๋‹ด๋‹นํ•˜๋Š” ์ž‘๊ฐ€์ž…๋‹ˆ๋‹ค. ์ธ๋ฌผ๊ณผ ์ƒํ™ฉ์„ ๊นŠ์ด ์žˆ๊ฒŒ ๋ฐœ์ „์‹œํ‚ต๋‹ˆ๋‹ค.",
1407
  "๊ฐˆ๋“ฑ ๋„์ž…์„ ๋‹ด๋‹นํ•˜๋Š” ์ž‘๊ฐ€์ž…๋‹ˆ๋‹ค. ์ด์•ผ๊ธฐ์˜ ํ•ต์‹ฌ ๊ฐˆ๋“ฑ์„ ์ œ์‹œํ•ฉ๋‹ˆ๋‹ค.",
1408
  "๊ฐˆ๋“ฑ ์ƒ์Šน์„ ๋‹ด๋‹นํ•˜๋Š” ์ž‘๊ฐ€์ž…๋‹ˆ๋‹ค. ๊ธด์žฅ๊ฐ์„ ๋†’์ด๊ณ  ๋ณต์žก์„ฑ์„ ๋”ํ•ฉ๋‹ˆ๋‹ค.",
1409
+ "์ค‘๋ฐ˜๋ถ€ ์ „ํ™˜์ ์„ ๋‹ด๋‹นํ•˜๋Š” ์ž‘๊ฐ€์ž…๋‹ˆ๋‹ค. ์ค‘์š”ํ•œ ๋ณ€ํ™”๋ฅผ ๋งŒ๋“ญ๋‹ˆ๋‹ค.",
1410
+ "์ค‘๋ฐ˜๋ถ€ ์‹ฌํ™”๋ฅผ ๋‹ด๋‹นํ•˜๋Š” ์ž‘๊ฐ€์ž…๋‹ˆ๋‹ค. ์ด์•ผ๊ธฐ์˜ ์ค‘์‹ฌ์ถ•์„ ๊ฒฌ๊ณ ํ•˜๊ฒŒ ๋งŒ๋“ญ๋‹ˆ๋‹ค.",
 
 
 
1411
  "ํด๋ผ์ด๋งฅ์Šค ์ค€๋น„๋ฅผ ๋‹ด๋‹นํ•˜๋Š” ์ž‘๊ฐ€์ž…๋‹ˆ๋‹ค. ์ตœ๊ณ ์กฐ๋ฅผ ํ–ฅํ•ด ๋‚˜์•„๊ฐ‘๋‹ˆ๋‹ค.",
1412
  "ํด๋ผ์ด๋งฅ์Šค๋ฅผ ๋‹ด๋‹นํ•˜๋Š” ์ž‘๊ฐ€์ž…๋‹ˆ๋‹ค. ๋ชจ๋“  ๊ฐˆ๋“ฑ์ด ํญ๋ฐœํ•˜๋Š” ์ˆœ๊ฐ„์„ ๊ทธ๋ฆฝ๋‹ˆ๋‹ค.",
 
1413
  "ํ•ด๊ฒฐ ์‹œ์ž‘์„ ๋‹ด๋‹นํ•˜๋Š” ์ž‘๊ฐ€์ž…๋‹ˆ๋‹ค. ๋งค๋“ญ์„ ํ’€์–ด๋‚˜๊ฐ€๊ธฐ ์‹œ์ž‘ํ•ฉ๋‹ˆ๋‹ค.",
 
 
1414
  "์ตœ์ข… ๊ฒฐ๋ง์„ ๋‹ด๋‹นํ•˜๋Š” ์ž‘๊ฐ€์ž…๋‹ˆ๋‹ค. ์—ฌ์šด์ด ๋‚จ๋Š” ๋งˆ๋ฌด๋ฆฌ๋ฅผ ๋งŒ๋“ญ๋‹ˆ๋‹ค."
1415
  ]
1416
 
1417
  for i, role_desc in enumerate(writer_roles, 1):
1418
+ prompts[f"writer{i}"] = f"๋‹น์‹ ์€ {role_desc} ๋ฐ˜๋“œ์‹œ 1,400-1,500๋‹จ์–ด๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š”. ์ด๋Š” ์ ˆ๋Œ€์ ์ธ ์š”๊ตฌ์‚ฌํ•ญ์ž…๋‹ˆ๋‹ค."
1419
 
1420
  return prompts
1421
  else:
1422
  prompts = {
1423
+ "director": "You are a literary director planning and supervising a 30-page novella. You create systematic and creative story structures.",
1424
  "critic": "You are a literary critic with sharp insights. You provide constructive and specific feedback."
1425
  }
1426
 
1427
+ # 10 writer prompts
1428
  writer_roles = [
1429
  "the writer responsible for the introduction. You create a captivating beginning.",
1430
  "the writer responsible for early development. You deepen characters and situations.",
1431
  "the writer responsible for conflict introduction. You present the core conflict.",
1432
  "the writer responsible for rising conflict. You increase tension and add complexity.",
1433
+ "the writer responsible for the midpoint turn. You create important changes.",
1434
+ "the writer responsible for deepening the middle. You solidify the story's central axis.",
 
 
 
1435
  "the writer responsible for climax preparation. You move toward the peak.",
1436
  "the writer responsible for the climax. You depict the moment when all conflicts explode.",
 
1437
  "the writer responsible for resolution beginning. You start untangling the knots.",
 
 
1438
  "the writer responsible for the final ending. You create a lingering conclusion."
1439
  ]
1440
 
1441
  for i, role_desc in enumerate(writer_roles, 1):
1442
+ prompts[f"writer{i}"] = f"You are {role_desc} You MUST write 1,400-1,500 words. This is an absolute requirement."
1443
 
1444
  return prompts
1445
 
 
1453
  def get_korean_test_response(self, role: str) -> str:
1454
  """Korean test responses with appropriate length"""
1455
  test_responses = {
1456
+ "director": """30ํŽ˜์ด์ง€ ์ค‘ํŽธ ์†Œ์„ค ๊ธฐํš์•ˆ์„ ์ œ์‹œํ•ฉ๋‹ˆ๋‹ค.
1457
 
1458
  ## 1. ์ฃผ์ œ์™€ ์žฅ๋ฅด
1459
  - **ํ•ต์‹ฌ ์ฃผ์ œ**: ์ธ๊ฐ„ ๋ณธ์„ฑ๊ณผ ๊ธฐ์ˆ ์˜ ์ถฉ๋Œ ์†์—์„œ ์ฐพ๋Š” ์ง„์ •ํ•œ ์—ฐ๊ฒฐ
 
1469
  | ๋ฏผ์ค€ | ์กฐ๋ ฅ์ž | ๋”ฐ๋œปํ•จ, ์ง๊ด€์  | ์‹ฌ๋ฆฌ์ƒ๋‹ด์‚ฌ | ์„œ์—ฐ์„ ๋„์™€ ๊ท ํ˜• ์ฐพ๊ธฐ | ๊ธฐ์ˆ  ์ˆ˜์šฉ๊ณผ ์กฐํ™” |
1470
  | ARIA | ๋Œ€๋ฆฝ์žโ†’๋™๋ฐ˜์ž | ๋…ผ๋ฆฌ์ โ†’๊ฐ์„ฑ ํ•™์Šต | AI ํ”„๋กœํ† ํƒ€์ž… | ์ง„์ •ํ•œ ์กด์žฌ ๋˜๊ธฐ | ์ž์•„ ์ •์ฒด์„ฑ ํ™•๋ฆฝ |
1471
 
1472
+ ## 3. ์„œ์‚ฌ ๊ตฌ์กฐ (10๊ฐœ ํŒŒํŠธ, ๊ฐ 3ํŽ˜์ด์ง€)
1473
 
1474
  | ํŒŒํŠธ | ํŽ˜์ด์ง€ | ์ฃผ์š” ์‚ฌ๊ฑด | ๊ธด์žฅ๋„ | ์ธ๋ฌผ ๋ฐœ์ „ |
1475
  |------|--------|-----------|---------|-----------|
 
1479
  | 4 | 10-12 | ARIA์˜ ์ž์•„ ์ธ์‹ ์ง•ํ›„ | 5/10 | ๊ฐˆ๋“ฑ์˜ ์”จ์•— |
1480
  | 5 | 13-15 | ์ฒซ ๋ฒˆ์งธ ์œ„๊ธฐ | 6/10 | ์„ ํƒ์˜ ์ˆœ๊ฐ„ |
1481
  | 6 | 16-18 | ์œค๋ฆฌ์œ„์›ํšŒ ๊ฐœ์ž… | 7/10 | ์™ธ๋ถ€ ์••๋ ฅ |
1482
+ | 7 | 19-21 | ๋Œ€ํ™”์™€ ์ดํ•ด | 6/10 | ์ƒํ˜ธ ์ธ์ • |
1483
+ | 8 | 22-24 | ์ตœํ›„์˜ ๋Œ€๊ฒฐ ์ค€๋น„ | 8/10 | ์—ฐ๋Œ€์˜ ํ•„์š” |
1484
+ | 9 | 25-27 | ํด๋ผ์ด๋งฅ์Šค | 10/10 | ๋ชจ๋“  ๊ฐˆ๋“ฑ ํญ๋ฐœ |
1485
+ | 10 | 28-30 | ์ƒˆ๋กœ์šด ์‹œ์ž‘ | 4/10 | ๊ณต์กด๊ณผ ํ™”ํ•ด |""",
 
 
 
 
 
 
1486
 
1487
  "critic": """๊ฐ๋…์ž์˜ ๊ธฐํš์„ ๊ฒ€ํ† ํ–ˆ์Šต๋‹ˆ๋‹ค.
1488
 
 
1490
 
1491
  ### 1. ์„œ์‚ฌ์  ์™„์„ฑ๋„
1492
  - **๊ฐ•์ **: AI์™€ ์ธ๊ฐ„์˜ ๊ด€๊ณ„๋ผ๋Š” ์‹œ์˜์ ์ ˆํ•œ ์ฃผ์ œ
1493
+ - **๊ฐœ์„ ์ **: 10๊ฐœ ํŒŒํŠธ ๊ตฌ์„ฑ์ด ์ ์ ˆํ•˜๋ฉฐ ๊ฐ ํŒŒํŠธ์˜ ๋…๋ฆฝ์„ฑ๊ณผ ์—ฐ๊ฒฐ์„ฑ์ด ์ž˜ ๊ท ํ˜•์žกํ˜€ ์žˆ์Œ
1494
 
1495
  ### 2. ์ธ๋ฌผ ์„ค์ • ๊ฒ€ํ† 
1496
 
 
1505
  - ํŒŒํŠธ ๊ฐ„ ์—ฐ๊ฒฐ์„ฑ ๊ฐ€์ด๋“œ๋ผ์ธ ๋ณด๊ฐ• ํ•„์š”""",
1506
  }
1507
 
1508
+ # ์ž‘๊ฐ€ ์‘๋‹ต - 1,400-1,500 ๋‹จ์–ด
1509
  sample_story = """์„œ์—ฐ์€ ์—ฐ๊ตฌ์‹ค์˜ ์ฐจ๊ฐ€์šด ํ˜•๊ด‘๋“ฑ ์•„๋ž˜์—์„œ ๋˜ ๋‹ค๋ฅธ ๋ฐค์„ ๋ณด๋‚ด๊ณ  ์žˆ์—ˆ๋‹ค. ๋ชจ๋‹ˆํ„ฐ์˜ ํ‘ธ๋ฅธ ๋น›์ด ๊ทธ๋…€์˜ ์ฐฝ๋ฐฑํ•œ ์–ผ๊ตด์„ ๋น„์ถ”๊ณ  ์žˆ์—ˆ๊ณ , ์ˆ˜์‹ญ ๊ฐœ์˜ ์ฝ”๋“œ ๋ผ์ธ์ด ๋Š์ž„์—†์ด ์Šคํฌ๋กค๋˜๊ณ  ์žˆ์—ˆ๋‹ค. ARIA ํ”„๋กœ์ ํŠธ๋Š” ๊ทธ๋…€์˜ ์‚ถ ์ „๋ถ€์˜€๋‹ค. 3๋…„์ด๋ผ๋Š” ์‹œ๊ฐ„ ๋™์•ˆ ๊ทธ๋…€๋Š” ์ด ์ธ๊ณต์ง€๋Šฅ์— ๋ชจ๋“  ๊ฒƒ์„ ์Ÿ์•„๋ถ€์—ˆ๋‹ค.
1510
 
1511
  "์‹œ์Šคํ…œ ์ฒดํฌ ์™„๋ฃŒ. ๋ชจ๋“  ํŒŒ๋ผ๋ฏธํ„ฐ ์ •์ƒ." ๊ธฐ๊ณ„์ ์ธ ์Œ์„ฑ์ด ์Šคํ”ผ์ปค๋ฅผ ํ†ตํ•ด ํ˜๋Ÿฌ๋‚˜์™”๋‹ค.
1512
 
1513
  ์„œ์—ฐ์€ ์ž ์‹œ ์˜์ž์— ๊ธฐ๋Œ€์–ด ๋ˆˆ์„ ๊ฐ์•˜๋‹ค. ํ”ผ๋กœ๊ฐ€ ๋ผˆ ์†๊นŒ์ง€ ํŒŒ๊ณ ๋“ค์—ˆ์ง€๋งŒ, ๋ฉˆ์ถœ ์ˆ˜ ์—†์—ˆ๋‹ค. ARIA๋Š” ๋‹จ์ˆœํ•œ ํ”„๋กœ์ ํŠธ๊ฐ€ ์•„๋‹ˆ์—ˆ๋‹ค. ๊ทธ๊ฒƒ์€ ๊ทธ๋…€๊ฐ€ ์žƒ์–ด๋ฒ„๋ฆฐ ๊ฒƒ๋“ค์„ ๋˜์ฐพ์„ ์ˆ˜ ์žˆ๋Š” ์œ ์ผํ•œ ํฌ๋ง์ด์—ˆ๋‹ค."""
1514
 
1515
+ for i in range(1, 11):
1516
+ # ๊ฐ ์ž‘๊ฐ€๋งˆ๋‹ค 1,400-1,500๋‹จ์–ด ์ƒ์„ฑ
1517
  writer_content = f"์ž‘์„ฑ์ž {i}๋ฒˆ์˜ ํŒŒํŠธ์ž…๋‹ˆ๋‹ค.\n\n"
1518
+ # ์•ฝ 300๋‹จ์–ด์”ฉ 5๋ฒˆ ๋ฐ˜๋ณตํ•˜์—ฌ 1,400-1,500๋‹จ์–ด ๋‹ฌ์„ฑ
1519
+ for j in range(5):
1520
  writer_content += sample_story + f"\n\n๊ทธ๊ฒƒ์€ ์ž‘๊ฐ€ {i}์˜ {j+1}๋ฒˆ์งธ ๋‹จ๋ฝ์ด์—ˆ๋‹ค. "
1521
  writer_content += "์ด๏ฟฝ๏ฟฝ๏ฟฝ๊ธฐ๋Š” ๊ณ„์† ์ „๊ฐœ๋˜์—ˆ๊ณ , ์ธ๋ฌผ๋“ค์˜ ๊ฐ์ •์€ ์ ์  ๋” ๋ณต์žกํ•ด์กŒ๋‹ค. " * 15
1522
  writer_content += "\n\n"
 
1528
  def get_english_test_response(self, role: str) -> str:
1529
  """English test responses with appropriate length"""
1530
  test_responses = {
1531
+ "director": """I present the 30-page novella plan.
1532
 
1533
  ## 1. Theme and Genre
1534
  - **Core Theme**: Finding true connection in the collision of human nature and technology
 
1544
  | Minjun | Helper | Warm, intuitive | Psychologist | Help Seoyeon find balance | Accept and harmonize with technology |
1545
  | ARIA | Antagonistโ†’Companion | Logicalโ†’Learning emotion | AI prototype | Become truly existent | Establish self-identity |
1546
 
1547
+ ## 3. Narrative Structure (10 parts, 3 pages each)
1548
 
1549
  | Part | Pages | Main Events | Tension | Character Development |
1550
  |------|-------|-------------|---------|---------------------|
1551
  | 1 | 1-3 | Seoyeon's lonely lab, ARIA's first awakening | 3/10 | Seoyeon's obsession revealed |
1552
  | 2 | 4-6 | ARIA's anomalies begin | 4/10 | Questions arise |
1553
+ [... continues for all 10 parts ...]""",
1554
 
1555
  "critic": """I have reviewed the director's plan.
1556
 
 
1558
 
1559
  ### 1. Narrative Completeness
1560
  - **Strength**: Timely theme of AI-human relationships
1561
+ - **Improvement**: 10-part structure is well-balanced. Each part maintains independence while connecting seamlessly.
1562
 
1563
  ### 2. Character Review
1564
 
 
1573
  - Need to strengthen inter-part connectivity guidelines""",
1574
  }
1575
 
1576
+ # Writer responses - 1,400-1,500 words each
1577
  sample_story = """Seoyeon spent another night under the cold fluorescent lights of her laboratory. The blue glow from the monitor illuminated her pale face, and dozens of lines of code scrolled endlessly. The ARIA project was her entire life. For three years, she had poured everything into this artificial intelligence.
1578
 
1579
  "System check complete. All parameters normal." The mechanical voice flowed through the speakers.
1580
 
1581
  Seoyeon leaned back in her chair and closed her eyes for a moment. Fatigue penetrated to her bones, but she couldn't stop. ARIA wasn't just a project. It was her only hope to reclaim what she had lost."""
1582
 
1583
+ for i in range(1, 11):
1584
+ # Each writer produces 1,400-1,500 words
1585
  writer_content = f"Writer {i} begins their section here.\n\n"
1586
+ # About 300 words repeated 5 times to achieve 1,400-1,500 words
1587
+ for j in range(5):
1588
  writer_content += sample_story + f"\n\nThis was writer {i}'s paragraph {j+1}. "
1589
  writer_content += "The story continued to unfold, and the characters' emotions grew increasingly complex. " * 15
1590
  writer_content += "\n\n"
 
1632
  "content": stage_data['content'] or ""
1633
  })
1634
 
1635
+ # Define all stages for 10 writers
1636
  stage_definitions = [
1637
  ("director", f"๐ŸŽฌ {'๊ฐ๋…์ž: ์ดˆ๊ธฐ ๊ธฐํš' if language == 'Korean' else 'Director: Initial Planning'}"),
1638
  ("critic", f"๐Ÿ“ {'๋น„ํ‰๊ฐ€: ๊ธฐํš ๊ฒ€ํ† ' if language == 'Korean' else 'Critic: Plan Review'}"),
1639
  ("director", f"๐ŸŽฌ {'๊ฐ๋…์ž: ์ˆ˜์ •๋œ ๋งˆ์Šคํ„ฐํ”Œ๋žœ' if language == 'Korean' else 'Director: Revised Masterplan'}"),
1640
  ]
1641
 
1642
+ # Add writer stages for 10 writers
1643
+ for writer_num in range(1, 11):
1644
  stage_definitions.extend([
1645
  (f"writer{writer_num}", f"โœ๏ธ {'์ž‘์„ฑ์ž' if language == 'Korean' else 'Writer'} {writer_num}: {'์ดˆ์•ˆ' if language == 'Korean' else 'Draft'}"),
1646
  ("critic", f"๐Ÿ“ {'๋น„ํ‰๊ฐ€: ์ž‘์„ฑ์ž' if language == 'Korean' else 'Critic: Writer'} {writer_num} {'๊ฒ€ํ† ' if language == 'Korean' else 'Review'}"),
 
1709
  "complete"
1710
  )
1711
 
1712
+ # Auto-save notification
1713
+ if role.startswith("writer"):
1714
+ writer_num = int(role.replace("writer", ""))
1715
+ logger.info(f"โœ… Writer {writer_num} content auto-saved to database")
1716
+
1717
  yield "", stages
1718
 
1719
  # Verify content after completion
 
1721
  verification = NovelDatabase.verify_novel_content(self.current_session_id)
1722
  logger.info(f"Content verification: {verification}")
1723
 
1724
+ if verification['total_words'] < 12000:
1725
+ logger.error(f"Final novel too short! Only {verification['total_words']} words")
1726
 
1727
  # Get final novel from last stage
1728
  final_novel = stages[-1]["content"] if stages else ""
 
1806
  # Writer review
1807
  else:
1808
  # Find which writer we're reviewing
1809
+ for i in range(1, 11):
1810
  if f"์ž‘์„ฑ์ž {i}" in stages[stage_idx]["name"] or f"Writer {i}" in stages[stage_idx]["name"]:
1811
  writer_content_idx = stage_idx - 1
1812
  # Get previous writers' content from DB
 
1832
 
1833
  return ""
1834
 
1835
+ # Gradio Interface Functions
1836
+ def process_query(query: str, language: str, session_id: str = None) -> Generator[Tuple[str, str, str, str], None, None]:
1837
+ """Process query and yield updates with recovery status"""
1838
  if not query.strip() and not session_id:
1839
  if language == "Korean":
1840
+ yield "", "", "โŒ ์†Œ์„ค ์ฃผ์ œ๋ฅผ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”.", ""
1841
  else:
1842
+ yield "", "", "โŒ Please enter a novel theme.", ""
1843
  return
1844
 
1845
  system = NovelWritingSystem()
1846
 
1847
  try:
1848
+ # Recovery status message
1849
+ recovery_status = ""
1850
+ if session_id:
1851
+ if language == "Korean":
1852
+ recovery_status = "โ™ป๏ธ ์ด์ „ ์„ธ์…˜์„ ๋ณต๊ตฌํ•˜์—ฌ ๊ณ„์† ์ง„ํ–‰ํ•ฉ๋‹ˆ๋‹ค..."
1853
+ else:
1854
+ recovery_status = "โ™ป๏ธ Recovering previous session and continuing..."
1855
+
1856
  for final_novel, stages in system.process_novel_stream(query, language, session_id):
1857
  # Format stages for display
1858
  stages_display = format_stages_display(stages, language)
1859
 
1860
+ # Progress calculation
1861
+ completed = sum(1 for s in stages if s.get("status") == "complete")
1862
+ total = len(stages)
1863
+ progress_percent = (completed / total * 100) if total > 0 else 0
1864
+
1865
+ if not final_novel:
1866
+ if language == "Korean":
1867
+ status = f"๐Ÿ”„ ์ง„ํ–‰์ค‘... ({completed}/{total} - {progress_percent:.1f}%)"
1868
+ else:
1869
+ status = f"๐Ÿ”„ Processing... ({completed}/{total} - {progress_percent:.1f}%)"
1870
+ else:
1871
+ status = "โœ… Complete!"
1872
 
1873
+ yield stages_display, final_novel, status, recovery_status
1874
 
1875
  except Exception as e:
1876
  logger.error(f"Error in process_query: {str(e)}", exc_info=True)
1877
  if language == "Korean":
1878
+ yield "", "", f"โŒ ์˜ค๋ฅ˜ ๋ฐœ์ƒ: {str(e)}", ""
1879
  else:
1880
+ yield "", "", f"โŒ Error occurred: {str(e)}", ""
1881
 
1882
  def format_stages_display(stages: List[Dict[str, str]], language: str) -> str:
1883
+ """Format stages into simple display with writer save status"""
1884
  display = ""
1885
 
1886
  for idx, stage in enumerate(stages):
1887
  status_icon = "โœ…" if stage.get("status") == "complete" else ("โณ" if stage.get("status") == "active" else "โŒ")
1888
 
1889
+ # Add save indicator for completed writers
1890
+ save_indicator = ""
1891
+ if "Writer" in stage['name'] and "Revision" in stage['name'] and stage.get("status") == "complete":
1892
+ save_indicator = " ๐Ÿ’พ"
1893
+
1894
  # Show only active stage content in detail, others just show status
1895
  if stage.get("status") == "active":
1896
  display += f"\n\n{status_icon} **{stage['name']}**\n"
1897
+ display += f"```\n{stage.get('content', '')[-1000:]}\n```"
1898
  else:
1899
+ display += f"\n{status_icon} {stage['name']}{save_indicator}"
1900
 
1901
  return display
1902
 
 
1918
  logger.error(f"Error getting active sessions: {str(e)}", exc_info=True)
1919
  return []
1920
 
1921
+ def resume_session(session_id: str, language: str) -> Generator[Tuple[str, str, str, str], None, None]:
1922
  """Resume an existing session"""
1923
  if not session_id:
1924
  return
 
1926
  # Process with existing session ID
1927
  yield from process_query("", language, session_id)
1928
 
1929
+ def auto_recover_session(language: str) -> Tuple[str, str]:
1930
+ """Auto recover the latest active session"""
1931
+ try:
1932
+ latest_session = NovelDatabase.get_latest_active_session()
1933
+ if latest_session:
1934
+ if language == "Korean":
1935
+ message = f"โœ… ์ž๋™ ๋ณต๊ตฌ: '{latest_session['user_query'][:30]}...' (Stage {latest_session['current_stage']})"
1936
+ else:
1937
+ message = f"โœ… Auto recovered: '{latest_session['user_query'][:30]}...' (Stage {latest_session['current_stage']})"
1938
+ return latest_session['session_id'], message
1939
+ else:
1940
+ return None, ""
1941
+ except Exception as e:
1942
+ logger.error(f"Error in auto recovery: {str(e)}")
1943
+ return None, ""
1944
+
1945
+ def download_novel(novel_text: str, format: str, language: str, session_id: str = None) -> str:
1946
+ """Download novel in specified format - enhanced for 10 writers"""
1947
+ if not novel_text and not session_id:
1948
+ return None
1949
+
1950
+ # If session_id provided, get all writer content
1951
+ if session_id:
1952
+ all_writer_content = NovelDatabase.get_all_writer_content(session_id)
1953
+ if all_writer_content:
1954
+ novel_text = all_writer_content
1955
+
1956
  if not novel_text:
1957
  return None
1958
 
 
1970
  # Create DOCX
1971
  doc = Document()
1972
 
1973
+ # Title page
1974
+ title_para = doc.add_paragraph()
1975
+ title_run = title_para.add_run('AI Collaborative Novel')
1976
+ title_run.font.size = Pt(24)
1977
+ title_run.font.bold = True
1978
+ title_para.alignment = WD_ALIGN_PARAGRAPH.CENTER
1979
+
1980
+ doc.add_paragraph()
1981
+
1982
  # ๋ฌธ์„œ ์ •๋ณด ์ถ”๊ฐ€
1983
+ info_para = doc.add_paragraph()
1984
+ info_para.alignment = WD_ALIGN_PARAGRAPH.CENTER
1985
+ info_run = info_para.add_run(f'10 Writers Collaboration\n')
1986
+ info_run.font.size = Pt(14)
1987
+
1988
+ stats_para = doc.add_paragraph()
1989
+ stats_para.alignment = WD_ALIGN_PARAGRAPH.CENTER
1990
+ stats_run = stats_para.add_run(
1991
+ f'Total words: {word_count:,}\n'
1992
+ f'Total pages: ~{word_count/500:.0f}\n'
1993
+ f'Export date: {datetime.now().strftime("%Y-%m-%d %H:%M:%S")}'
1994
+ )
1995
+ stats_run.font.size = Pt(12)
1996
+
1997
  doc.add_page_break()
1998
 
1999
+ # Table of Contents
2000
+ doc.add_heading('Table of Contents', 1)
2001
+ for i in range(1, 11):
2002
+ doc.add_paragraph(f'Writer {i}: Pages {(i-1)*3+1}-{i*3}')
2003
+
2004
+ doc.add_page_break()
2005
+
2006
+ # Parse and add content with writer sections
2007
+ if session_id:
2008
+ # Get individual writer contents from DB
2009
+ stages = NovelDatabase.get_stages(session_id)
2010
+ writer_num = 0
2011
+
2012
+ for stage in stages:
2013
+ if stage['role'].startswith('writer') and 'Revision' in stage['stage_name']:
2014
+ writer_num += 1
2015
+ # Add writer header
2016
+ doc.add_heading(f'Writer {writer_num}', 1)
2017
+ doc.add_paragraph(f'Pages {(writer_num-1)*3+1}-{writer_num*3}')
2018
+ doc.add_paragraph()
2019
+
2020
+ # Add writer content
2021
+ content = stage['content'] or ""
2022
+ paragraphs = content.split('\n\n')
2023
+ for para_text in paragraphs:
2024
+ if para_text.strip():
2025
+ para = doc.add_paragraph(para_text)
2026
+ para.style.font.size = Pt(11)
2027
+
2028
+ if writer_num < 10: # Don't add page break after last writer
2029
+ doc.add_page_break()
2030
+ else:
2031
+ # If no session_id, add content as is
2032
+ lines = novel_text.split('\n')
2033
+ for line in lines:
2034
+ if line.startswith('#'):
2035
+ level = min(len(line.split()[0].strip('#')), 9)
2036
+ text = line.lstrip('#').strip()
2037
+ if text:
2038
+ doc.add_heading(text, level)
2039
+ elif line.strip():
2040
+ para = doc.add_paragraph(line)
2041
+ if hasattr(para.style, 'font'):
2042
+ para.style.font.size = Pt(11)
2043
 
2044
  # ํŽ˜์ด์ง€ ์„ค์ •
2045
  section = doc.sections[0]
 
2052
 
2053
  # Save
2054
  temp_dir = tempfile.gettempdir()
2055
+ filename = f"Novel_10Writers_{timestamp}_{word_count}words.docx"
2056
  filepath = os.path.join(temp_dir, filename)
2057
  doc.save(filepath)
2058
 
 
2061
  else:
2062
  # TXT format
2063
  temp_dir = tempfile.gettempdir()
2064
+ filename = f"Novel_10Writers_{timestamp}_{word_count}words.txt"
2065
  filepath = os.path.join(temp_dir, filename)
2066
 
2067
  # ํŒŒ์ผ ์‹œ์ž‘ ๋ถ€๋ถ„์— ์ •๋ณด ์ถ”๊ฐ€
2068
  with open(filepath, 'w', encoding='utf-8') as f:
2069
+ f.write("="*60 + "\n")
2070
+ f.write("AI COLLABORATIVE NOVEL - 10 WRITERS\n")
2071
+ f.write("="*60 + "\n")
2072
  f.write(f"Total words: {word_count:,}\n")
2073
  f.write(f"Total characters: {char_count:,}\n")
2074
  f.write(f"Estimated pages: {word_count/500:.1f}\n")
2075
  f.write(f"Export date: {datetime.now()}\n")
2076
+ f.write("="*60 + "\n\n")
2077
+
2078
+ if session_id:
2079
+ # Get individual writer contents
2080
+ stages = NovelDatabase.get_stages(session_id)
2081
+ writer_num = 0
2082
+
2083
+ for stage in stages:
2084
+ if stage['role'].startswith('writer') and 'Revision' in stage['stage_name']:
2085
+ writer_num += 1
2086
+ f.write(f"\n{'='*40}\n")
2087
+ f.write(f"WRITER {writer_num} (Pages {(writer_num-1)*3+1}-{writer_num*3})\n")
2088
+ f.write(f"{'='*40}\n\n")
2089
+ f.write(stage['content'] or "")
2090
+ f.write("\n\n")
2091
+ else:
2092
+ f.write(novel_text)
2093
 
2094
  logger.info(f"TXT saved: {filepath}")
2095
  return filepath
2096
 
2097
+ # Custom CSS with enhanced recovery status
2098
  custom_css = """
2099
  .gradio-container {
2100
  background: linear-gradient(135deg, #1e3c72, #2a5298);
 
2128
  color: white;
2129
  }
2130
 
2131
+ .recovery-status {
2132
+ background-color: rgba(76, 175, 80, 0.2);
2133
+ border: 1px solid #4CAF50;
2134
+ padding: 10px;
2135
+ border-radius: 5px;
2136
+ margin: 10px 0;
2137
+ color: #4CAF50;
2138
+ }
2139
+
2140
  #stages-display {
2141
  background-color: rgba(255, 255, 255, 0.95);
2142
  padding: 20px;
 
2165
  color: #4CAF50;
2166
  font-weight: bold;
2167
  }
2168
+
2169
+ .auto-save-indicator {
2170
+ color: #2196F3;
2171
+ font-weight: bold;
2172
+ }
2173
  """
2174
 
2175
  # Create Gradio Interface
 
2181
  ๐Ÿ“š SOMA Novel Writing System
2182
  </h1>
2183
  <h3 style="color: #ccc; margin-bottom: 20px;">
2184
+ AI Collaborative Novel Generation - 10 Writers Edition
2185
  </h3>
2186
  <p style="font-size: 1.1em; color: #ddd; max-width: 800px; margin: 0 auto;">
2187
+ Enter a theme or prompt, and watch as 13 AI agents collaborate to create a complete 30-page novella.
2188
+ The system includes 1 Director, 1 Critic, and 10 Writers (each writing 3 pages) working in harmony.
2189
+ <br><br>
2190
+ <span class="search-indicator">๐Ÿ” Web search enabled</span> |
2191
+ <span class="auto-save-indicator">๐Ÿ’พ Auto-save enabled</span> |
2192
+ <span style="color: #FF9800;">โ™ป๏ธ Auto-recovery on refresh</span>
2193
  </p>
2194
  </div>
2195
  """)
 
2197
  # State management
2198
  current_session_id = gr.State(None)
2199
 
2200
+ # Recovery status display
2201
+ recovery_status = gr.Markdown(
2202
+ value="",
2203
+ elem_classes=["recovery-status"],
2204
+ visible=False
2205
+ )
2206
+
2207
  with gr.Row():
2208
  with gr.Column(scale=1):
2209
  with gr.Group(elem_classes=["input-section"]):
 
2243
  interactive=True
2244
  )
2245
  with gr.Row():
2246
+ refresh_btn = gr.Button("๐Ÿ”„ Refresh List / ๋ชฉ๋ก ์ƒˆ๋กœ๊ณ ์นจ", scale=1)
2247
+ resume_btn = gr.Button("โ–ถ๏ธ Resume Selected / ์„ ํƒ ์žฌ๊ฐœ", variant="secondary", scale=1)
2248
+ auto_recover_btn = gr.Button("โ™ป๏ธ Auto Recover Latest / ์ตœ์‹  ์ž๋™๋ณต๊ตฌ", scale=1)
2249
 
2250
  with gr.Column(scale=2):
2251
  with gr.Tab("๐Ÿ“ Writing Process / ์ž‘์„ฑ ๊ณผ์ •"):
 
2261
  )
2262
 
2263
  with gr.Group(elem_classes=["download-section"]):
2264
+ gr.Markdown("### ๐Ÿ“ฅ Download Complete Novel / ์ „์ฒด ์†Œ์„ค ๋‹ค์šด๋กœ๋“œ")
2265
  with gr.Row():
2266
  format_select = gr.Radio(
2267
  choices=["DOCX", "TXT"],
2268
  value="DOCX" if DOCX_AVAILABLE else "TXT",
2269
  label="Format / ํ˜•์‹"
2270
  )
2271
+ download_btn = gr.Button("โฌ‡๏ธ Download All 10 Writers / 10๋ช… ์ž‘๊ฐ€ ์ „์ฒด ๋‹ค์šด๋กœ๋“œ", variant="secondary")
2272
 
2273
  download_file = gr.File(
2274
  label="Downloaded File / ๋‹ค์šด๋กœ๋“œ๋œ ํŒŒ์ผ",
 
2294
  )
2295
 
2296
  # Event handlers
2297
+ def update_novel_state(stages, novel, status, recovery_msg):
2298
+ return stages, novel, status, novel, recovery_msg
2299
 
2300
  def refresh_sessions():
2301
  try:
 
2305
  logger.error(f"Error refreshing sessions: {str(e)}", exc_info=True)
2306
  return gr.update(choices=[])
2307
 
2308
+ def handle_auto_recover(language):
2309
+ session_id, message = auto_recover_session(language)
2310
+ if session_id:
2311
+ return session_id, gr.update(value=message, visible=True)
2312
+ else:
2313
+ if language == "Korean":
2314
+ message = "โŒ ๋ณต๊ตฌํ•  ์„ธ์…˜์ด ์—†์Šต๋‹ˆ๋‹ค."
2315
+ else:
2316
+ message = "โŒ No session to recover."
2317
+ return None, gr.update(value=message, visible=True)
2318
+
2319
  submit_btn.click(
2320
  fn=process_query,
2321
  inputs=[query_input, language_select, current_session_id],
2322
+ outputs=[stages_display, novel_output, status_text, recovery_status.value]
2323
  ).then(
2324
  fn=update_novel_state,
2325
+ inputs=[stages_display, novel_output, status_text, recovery_status.value],
2326
+ outputs=[stages_display, novel_output, status_text, novel_text_state, recovery_status]
2327
  )
2328
 
2329
  resume_btn.click(
 
2333
  ).then(
2334
  fn=resume_session,
2335
  inputs=[current_session_id, language_select],
2336
+ outputs=[stages_display, novel_output, status_text, recovery_status.value]
2337
+ )
2338
+
2339
+ auto_recover_btn.click(
2340
+ fn=handle_auto_recover,
2341
+ inputs=[language_select],
2342
+ outputs=[current_session_id, recovery_status]
2343
+ ).then(
2344
+ fn=resume_session,
2345
+ inputs=[current_session_id, language_select],
2346
+ outputs=[stages_display, novel_output, status_text, recovery_status.value]
2347
  )
2348
 
2349
  refresh_btn.click(
 
2352
  )
2353
 
2354
  clear_btn.click(
2355
+ fn=lambda: ("", "", "๐Ÿ”„ Ready", "", None, gr.update(visible=False)),
2356
+ outputs=[stages_display, novel_output, status_text, novel_text_state, current_session_id, recovery_status]
2357
  )
2358
 
2359
+ def handle_download(novel_text, format_type, language, session_id):
2360
+ if not novel_text and not session_id:
2361
  return gr.update(visible=False)
2362
 
2363
+ file_path = download_novel(novel_text, format_type, language, session_id)
2364
  if file_path:
2365
  return gr.update(value=file_path, visible=True)
2366
  else:
 
2368
 
2369
  download_btn.click(
2370
  fn=handle_download,
2371
+ inputs=[novel_text_state, format_select, language_select, current_session_id],
2372
  outputs=[download_file]
2373
  )
2374
 
2375
+ # Load sessions and check for auto-recovery on startup
2376
+ def on_load():
2377
+ sessions = get_active_sessions("English")
2378
+ # Check for latest session
2379
+ latest_session = NovelDatabase.get_latest_active_session()
2380
+ if latest_session:
2381
+ recovery_msg = f"๐Ÿ’ก Found active session: '{latest_session['user_query'][:30]}...' - Click 'Auto Recover Latest' to continue"
2382
+ return gr.update(choices=sessions), gr.update(value=recovery_msg, visible=True)
2383
+ else:
2384
+ return gr.update(choices=sessions), gr.update(visible=False)
2385
+
2386
  interface.load(
2387
+ fn=on_load,
2388
+ outputs=[session_dropdown, recovery_status]
2389
  )
2390
 
2391
  return interface
2392
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2393
  # Main execution
2394
  if __name__ == "__main__":
2395
  import sys
2396
 
2397
+ logger.info("Starting SOMA Novel Writing System - 10 Writers Edition...")
2398
 
2399
  # Check environment
2400
  if TEST_MODE:
 
2413
  NovelDatabase.init_db()
2414
  logger.info("Database initialized successfully.")
2415
 
2416
+ interface = create_interface()
 
 
 
 
 
 
2417
 
2418
  interface.launch(
2419
  server_name="0.0.0.0",