openfree commited on
Commit
81b2ada
·
verified ·
1 Parent(s): a14346c

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +67 -45
app.py CHANGED
@@ -30,11 +30,16 @@ except ImportError:
30
  logger.warning("python-docx not installed. DOCX export will be disabled.")
31
 
32
  # 환경 변수에서 토큰 가져오기
33
- FRIENDLI_TOKEN = os.getenv("FRIENDLI_TOKEN", "YOUR_FRIENDLI_TOKEN")
34
  API_URL = "https://api.friendli.ai/dedicated/v1/chat/completions"
35
  MODEL_ID = "dep89a2fld32mcm"
36
  TEST_MODE = os.getenv("TEST_MODE", "false").lower() == "true"
37
 
 
 
 
 
 
38
  # 전역 변수
39
  conversation_history = []
40
  selected_language = "English" # 기본 언어
@@ -43,13 +48,20 @@ selected_language = "English" # 기본 언어
43
  DB_PATH = "novel_sessions.db"
44
  db_lock = threading.Lock()
45
 
 
 
 
 
46
  class NovelDatabase:
47
  """Novel session management database"""
48
 
49
  @staticmethod
50
  def init_db():
51
- """Initialize database tables"""
52
  with sqlite3.connect(DB_PATH) as conn:
 
 
 
53
  cursor = conn.cursor()
54
 
55
  # Sessions table
@@ -58,8 +70,8 @@ class NovelDatabase:
58
  session_id TEXT PRIMARY KEY,
59
  user_query TEXT NOT NULL,
60
  language TEXT NOT NULL,
61
- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
62
- updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
63
  status TEXT DEFAULT 'active',
64
  current_stage INTEGER DEFAULT 0,
65
  final_novel TEXT
@@ -76,8 +88,9 @@ class NovelDatabase:
76
  role TEXT NOT NULL,
77
  content TEXT,
78
  status TEXT DEFAULT 'pending',
79
- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
80
- FOREIGN KEY (session_id) REFERENCES sessions(session_id)
 
81
  )
82
  ''')
83
 
@@ -90,9 +103,9 @@ class NovelDatabase:
90
  @staticmethod
91
  @contextmanager
92
  def get_db():
93
- """Database connection context manager"""
94
  with db_lock:
95
- conn = sqlite3.connect(DB_PATH)
96
  conn.row_factory = sqlite3.Row
97
  try:
98
  yield conn
@@ -121,32 +134,19 @@ class NovelDatabase:
121
  with NovelDatabase.get_db() as conn:
122
  cursor = conn.cursor()
123
 
124
- # Check if stage exists
125
  cursor.execute('''
126
- SELECT id FROM stages
127
- WHERE session_id = ? AND stage_number = ?
128
- ''', (session_id, stage_number))
129
-
130
- existing = cursor.fetchone()
131
-
132
- if existing:
133
- # Update existing stage
134
- cursor.execute('''
135
- UPDATE stages
136
- SET content = ?, status = ?, stage_name = ?
137
- WHERE session_id = ? AND stage_number = ?
138
- ''', (content, status, stage_name, session_id, stage_number))
139
- else:
140
- # Insert new stage
141
- cursor.execute('''
142
- INSERT INTO stages (session_id, stage_number, stage_name, role, content, status)
143
- VALUES (?, ?, ?, ?, ?, ?)
144
- ''', (session_id, stage_number, stage_name, role, content, status))
145
 
146
  # Update session
147
  cursor.execute('''
148
  UPDATE sessions
149
- SET updated_at = CURRENT_TIMESTAMP, current_stage = ?
150
  WHERE session_id = ?
151
  ''', (stage_number, session_id))
152
 
@@ -179,11 +179,8 @@ class NovelDatabase:
179
  with NovelDatabase.get_db() as conn:
180
  cursor = conn.cursor()
181
 
182
- # 작가 수정본만 가져오기 (stage_number 5, 8, 11, 14, 17, 20, 23, 26, 29, 32)
183
- writer_revision_stages = [5, 8, 11, 14, 17, 20, 23, 26, 29, 32]
184
-
185
  all_content = []
186
- for stage_num in writer_revision_stages:
187
  cursor.execute('''
188
  SELECT content, stage_name FROM stages
189
  WHERE session_id = ? AND stage_number = ?
@@ -212,7 +209,7 @@ class NovelDatabase:
212
  cursor = conn.cursor()
213
  cursor.execute('''
214
  UPDATE sessions
215
- SET final_novel = ?, status = 'complete', updated_at = CURRENT_TIMESTAMP
216
  WHERE session_id = ?
217
  ''', (final_novel, session_id))
218
  conn.commit()
@@ -231,22 +228,35 @@ class NovelDatabase:
231
  LIMIT 10
232
  ''')
233
  return cursor.fetchall()
 
 
 
 
 
 
 
 
 
 
234
 
235
  class NovelWritingSystem:
236
  def __init__(self):
237
  self.token = FRIENDLI_TOKEN
238
  self.api_url = API_URL
239
  self.model_id = MODEL_ID
240
- self.test_mode = TEST_MODE or (self.token == "YOUR_FRIENDLI_TOKEN")
241
 
242
  if self.test_mode:
243
- logger.warning("Running in test mode.")
 
 
244
 
245
  # Initialize database
246
  NovelDatabase.init_db()
247
 
248
  # Session management
249
  self.current_session_id = None
 
250
 
251
  def create_headers(self):
252
  """API 헤더 생성"""
@@ -931,7 +941,8 @@ Present a complete 50-page novel integrating all writers' contributions."""
931
  content = chunk["choices"][0].get("delta", {}).get("content", "")
932
  if content:
933
  buffer += content
934
- if len(buffer) > 50 or '\n' in buffer:
 
935
  yield buffer
936
  buffer = ""
937
  except json.JSONDecodeError:
@@ -1175,6 +1186,9 @@ Present a complete 50-page novel integrating all writers' contributions."""
1175
  ("director", f"🎬 {'감독자: 최종 완성본' if language == 'Korean' else 'Director: Final Version'}")
1176
  ])
1177
 
 
 
 
1178
  # Process stages starting from resume point
1179
  for stage_idx in range(resume_from_stage, len(stage_definitions)):
1180
  role, stage_name = stage_definitions[stage_idx]
@@ -1229,7 +1243,7 @@ Present a complete 50-page novel integrating all writers' contributions."""
1229
  yield final_novel, stages
1230
 
1231
  except Exception as e:
1232
- logger.error(f"Error in process_novel_stream: {str(e)}")
1233
 
1234
  # Save error state to DB
1235
  if self.current_session_id:
@@ -1315,7 +1329,7 @@ Present a complete 50-page novel integrating all writers' contributions."""
1315
  )
1316
 
1317
  # Director final - DB에서 모든 작가 내용 가져오기
1318
- elif stage_idx == len(stage_definitions) - 1:
1319
  critic_final_idx = stage_idx - 1
1320
  all_writer_content = NovelDatabase.get_all_writer_content(self.current_session_id)
1321
  logger.info(f"Final director compilation with {len(all_writer_content)} characters of content")
@@ -1349,7 +1363,7 @@ def process_query(query: str, language: str, session_id: str = None) -> Generato
1349
  yield stages_display, final_novel, status
1350
 
1351
  except Exception as e:
1352
- logger.error(f"Error in process_query: {str(e)}")
1353
  if language == "Korean":
1354
  yield "", "", f"❌ 오류 발생: {str(e)}"
1355
  else:
@@ -1378,7 +1392,7 @@ def get_active_sessions(language: str) -> List[Tuple[str, str]]:
1378
 
1379
  choices = []
1380
  for session in sessions:
1381
- created = datetime.fromisoformat(session['created_at'])
1382
  date_str = created.strftime("%Y-%m-%d %H:%M")
1383
  query_preview = session['user_query'][:50] + "..." if len(session['user_query']) > 50 else session['user_query']
1384
  label = f"[{date_str}] {query_preview} (Stage {session['current_stage']})"
@@ -1386,7 +1400,7 @@ def get_active_sessions(language: str) -> List[Tuple[str, str]]:
1386
 
1387
  return choices
1388
  except Exception as e:
1389
- logger.error(f"Error getting active sessions: {str(e)}")
1390
  return []
1391
 
1392
  def resume_session(session_id: str, language: str) -> Generator[Tuple[str, str, str], None, None]:
@@ -1416,9 +1430,11 @@ def download_novel(novel_text: str, format: str, language: str) -> str:
1416
  lines = novel_text.split('\n')
1417
  for line in lines:
1418
  if line.startswith('#'):
1419
- level = len(line.split()[0])
 
1420
  text = line.lstrip('#').strip()
1421
- doc.add_heading(text, level)
 
1422
  elif line.strip():
1423
  doc.add_paragraph(line)
1424
 
@@ -1613,7 +1629,7 @@ def create_interface():
1613
  sessions = get_active_sessions("English")
1614
  return gr.update(choices=sessions)
1615
  except Exception as e:
1616
- logger.error(f"Error refreshing sessions: {str(e)}")
1617
  return gr.update(choices=[])
1618
 
1619
  submit_btn.click(
@@ -1674,6 +1690,12 @@ def create_interface():
1674
  if __name__ == "__main__":
1675
  logger.info("Starting SOMA Novel Writing System...")
1676
 
 
 
 
 
 
 
1677
  # Initialize database on startup
1678
  logger.info("Initializing database...")
1679
  NovelDatabase.init_db()
 
30
  logger.warning("python-docx not installed. DOCX export will be disabled.")
31
 
32
  # 환경 변수에서 토큰 가져오기
33
+ FRIENDLI_TOKEN = os.getenv("FRIENDLI_TOKEN", "")
34
  API_URL = "https://api.friendli.ai/dedicated/v1/chat/completions"
35
  MODEL_ID = "dep89a2fld32mcm"
36
  TEST_MODE = os.getenv("TEST_MODE", "false").lower() == "true"
37
 
38
+ # 환경 변수 검증
39
+ if not FRIENDLI_TOKEN and not TEST_MODE:
40
+ logger.warning("FRIENDLI_TOKEN not set and TEST_MODE is false. Application will run in test mode.")
41
+ TEST_MODE = True
42
+
43
  # 전역 변수
44
  conversation_history = []
45
  selected_language = "English" # 기본 언어
 
48
  DB_PATH = "novel_sessions.db"
49
  db_lock = threading.Lock()
50
 
51
+ # Stage 번호 상수
52
+ WRITER_DRAFT_STAGES = [3, 6, 9, 12, 15, 18, 21, 24, 27, 30] # 작가 초안
53
+ WRITER_REVISION_STAGES = [5, 8, 11, 14, 17, 20, 23, 26, 29, 32] # 작가 수정본
54
+
55
  class NovelDatabase:
56
  """Novel session management database"""
57
 
58
  @staticmethod
59
  def init_db():
60
+ """Initialize database tables with WAL mode for better concurrency"""
61
  with sqlite3.connect(DB_PATH) as conn:
62
+ # Enable WAL mode for better concurrent access
63
+ conn.execute("PRAGMA journal_mode=WAL")
64
+
65
  cursor = conn.cursor()
66
 
67
  # Sessions table
 
70
  session_id TEXT PRIMARY KEY,
71
  user_query TEXT NOT NULL,
72
  language TEXT NOT NULL,
73
+ created_at TEXT DEFAULT (datetime('now')),
74
+ updated_at TEXT DEFAULT (datetime('now')),
75
  status TEXT DEFAULT 'active',
76
  current_stage INTEGER DEFAULT 0,
77
  final_novel TEXT
 
88
  role TEXT NOT NULL,
89
  content TEXT,
90
  status TEXT DEFAULT 'pending',
91
+ created_at TEXT DEFAULT (datetime('now')),
92
+ FOREIGN KEY (session_id) REFERENCES sessions(session_id),
93
+ UNIQUE(session_id, stage_number)
94
  )
95
  ''')
96
 
 
103
  @staticmethod
104
  @contextmanager
105
  def get_db():
106
+ """Database connection context manager with timeout"""
107
  with db_lock:
108
+ conn = sqlite3.connect(DB_PATH, timeout=30.0)
109
  conn.row_factory = sqlite3.Row
110
  try:
111
  yield conn
 
134
  with NovelDatabase.get_db() as conn:
135
  cursor = conn.cursor()
136
 
137
+ # UPSERT operation
138
  cursor.execute('''
139
+ INSERT INTO stages (session_id, stage_number, stage_name, role, content, status)
140
+ VALUES (?, ?, ?, ?, ?, ?)
141
+ ON CONFLICT(session_id, stage_number)
142
+ DO UPDATE SET content=?, status=?, stage_name=?, updated_at=datetime('now')
143
+ ''', (session_id, stage_number, stage_name, role, content, status,
144
+ content, status, stage_name))
 
 
 
 
 
 
 
 
 
 
 
 
 
145
 
146
  # Update session
147
  cursor.execute('''
148
  UPDATE sessions
149
+ SET updated_at = datetime('now'), current_stage = ?
150
  WHERE session_id = ?
151
  ''', (stage_number, session_id))
152
 
 
179
  with NovelDatabase.get_db() as conn:
180
  cursor = conn.cursor()
181
 
 
 
 
182
  all_content = []
183
+ for stage_num in WRITER_REVISION_STAGES:
184
  cursor.execute('''
185
  SELECT content, stage_name FROM stages
186
  WHERE session_id = ? AND stage_number = ?
 
209
  cursor = conn.cursor()
210
  cursor.execute('''
211
  UPDATE sessions
212
+ SET final_novel = ?, status = 'complete', updated_at = datetime('now')
213
  WHERE session_id = ?
214
  ''', (final_novel, session_id))
215
  conn.commit()
 
228
  LIMIT 10
229
  ''')
230
  return cursor.fetchall()
231
+
232
+ @staticmethod
233
+ def parse_datetime(datetime_str: str) -> datetime:
234
+ """Parse SQLite datetime string safely"""
235
+ try:
236
+ # Try ISO format first
237
+ return datetime.fromisoformat(datetime_str)
238
+ except:
239
+ # Fallback to SQLite default format
240
+ return datetime.strptime(datetime_str, "%Y-%m-%d %H:%M:%S")
241
 
242
  class NovelWritingSystem:
243
  def __init__(self):
244
  self.token = FRIENDLI_TOKEN
245
  self.api_url = API_URL
246
  self.model_id = MODEL_ID
247
+ self.test_mode = TEST_MODE or not self.token
248
 
249
  if self.test_mode:
250
+ logger.warning("Running in test mode - no actual API calls will be made.")
251
+ else:
252
+ logger.info("Running in production mode with API calls enabled.")
253
 
254
  # Initialize database
255
  NovelDatabase.init_db()
256
 
257
  # Session management
258
  self.current_session_id = None
259
+ self.total_stages = 0 # Will be set in process_novel_stream
260
 
261
  def create_headers(self):
262
  """API 헤더 생성"""
 
941
  content = chunk["choices"][0].get("delta", {}).get("content", "")
942
  if content:
943
  buffer += content
944
+ # 버퍼로 수정 (긴 단어 대응)
945
+ if len(buffer) > 100 or '\n\n' in buffer:
946
  yield buffer
947
  buffer = ""
948
  except json.JSONDecodeError:
 
1186
  ("director", f"🎬 {'감독자: 최종 완성본' if language == 'Korean' else 'Director: Final Version'}")
1187
  ])
1188
 
1189
+ # Store total stages for get_stage_prompt
1190
+ self.total_stages = len(stage_definitions)
1191
+
1192
  # Process stages starting from resume point
1193
  for stage_idx in range(resume_from_stage, len(stage_definitions)):
1194
  role, stage_name = stage_definitions[stage_idx]
 
1243
  yield final_novel, stages
1244
 
1245
  except Exception as e:
1246
+ logger.error(f"Error in process_novel_stream: {str(e)}", exc_info=True)
1247
 
1248
  # Save error state to DB
1249
  if self.current_session_id:
 
1329
  )
1330
 
1331
  # Director final - DB에서 모든 작가 내용 가져오기
1332
+ elif stage_idx == self.total_stages - 1: # Fixed: using self.total_stages
1333
  critic_final_idx = stage_idx - 1
1334
  all_writer_content = NovelDatabase.get_all_writer_content(self.current_session_id)
1335
  logger.info(f"Final director compilation with {len(all_writer_content)} characters of content")
 
1363
  yield stages_display, final_novel, status
1364
 
1365
  except Exception as e:
1366
+ logger.error(f"Error in process_query: {str(e)}", exc_info=True)
1367
  if language == "Korean":
1368
  yield "", "", f"❌ 오류 발생: {str(e)}"
1369
  else:
 
1392
 
1393
  choices = []
1394
  for session in sessions:
1395
+ created = NovelDatabase.parse_datetime(session['created_at'])
1396
  date_str = created.strftime("%Y-%m-%d %H:%M")
1397
  query_preview = session['user_query'][:50] + "..." if len(session['user_query']) > 50 else session['user_query']
1398
  label = f"[{date_str}] {query_preview} (Stage {session['current_stage']})"
 
1400
 
1401
  return choices
1402
  except Exception as e:
1403
+ logger.error(f"Error getting active sessions: {str(e)}", exc_info=True)
1404
  return []
1405
 
1406
  def resume_session(session_id: str, language: str) -> Generator[Tuple[str, str, str], None, None]:
 
1430
  lines = novel_text.split('\n')
1431
  for line in lines:
1432
  if line.startswith('#'):
1433
+ # Safe heading level extraction
1434
+ level = min(len(line.split()[0].strip('#')), 9)
1435
  text = line.lstrip('#').strip()
1436
+ if text:
1437
+ doc.add_heading(text, level)
1438
  elif line.strip():
1439
  doc.add_paragraph(line)
1440
 
 
1629
  sessions = get_active_sessions("English")
1630
  return gr.update(choices=sessions)
1631
  except Exception as e:
1632
+ logger.error(f"Error refreshing sessions: {str(e)}", exc_info=True)
1633
  return gr.update(choices=[])
1634
 
1635
  submit_btn.click(
 
1690
  if __name__ == "__main__":
1691
  logger.info("Starting SOMA Novel Writing System...")
1692
 
1693
+ # Check environment
1694
+ if TEST_MODE:
1695
+ logger.warning("Running in TEST MODE - no actual API calls will be made")
1696
+ else:
1697
+ logger.info(f"Running in PRODUCTION MODE with API endpoint: {API_URL}")
1698
+
1699
  # Initialize database on startup
1700
  logger.info("Initializing database...")
1701
  NovelDatabase.init_db()