openfree commited on
Commit
7719eef
Β·
verified Β·
1 Parent(s): 4741c29

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +275 -526
app.py CHANGED
@@ -2569,533 +2569,282 @@ custom_css = """
2569
 
2570
  # Gradio μΈν„°νŽ˜μ΄μŠ€ 생성
2571
  def create_interface():
2572
- with gr.Blocks(css=custom_css, title="AI μ§„ν–‰ν˜• μž₯νŽΈμ†Œμ„€ 생성 μ‹œμŠ€ν…œ v3.1") as interface:
2573
- gr.HTML("""
2574
- <div class="main-header">
2575
- <h1 style="font-size: 2.5em; margin-bottom: 10px;">
2576
- πŸ“š AI μ§„ν–‰ν˜• μž₯νŽΈμ†Œμ„€ 생성 μ‹œμŠ€ν…œ v3.1
2577
- </h1>
2578
- <h3 style="color: #ddd; margin-bottom: 20px;">
2579
- λͺ©ν‘œ λΆ„λŸ‰ 달성을 μœ„ν•œ μ΅œμ ν™” 버전
2580
- </h3>
2581
- <p style="font-size: 1.1em; color: #eee; max-width: 800px; margin: 0 auto;">
2582
- 10개의 유기적으둜 μ—°κ²°λœ 단계λ₯Ό 톡해 ν•˜λ‚˜μ˜ μ™„μ „ν•œ 이야기λ₯Ό λ§Œλ“€μ–΄λƒ…λ‹ˆλ‹€.
2583
- <br>
2584
- 각 λ‹¨κ³„λŠ” 이전 λ‹¨κ³„μ˜ 필연적 결과둜 이어지며, 인물의 변화와 μ„±μž₯을 μΆ”μ ν•©λ‹ˆλ‹€.
2585
- </p>
2586
- <div class="progress-note">
2587
- ⚑ 반볡이 μ•„λ‹Œ 좕적, μˆœν™˜μ΄ μ•„λ‹Œ 진행을 ν†΅ν•œ μ§„μ •ν•œ μž₯편 μ„œμ‚¬
2588
- </div>
2589
- <div class="improvement-note">
2590
- πŸ†• v3.1 핡심 κ°œμ„ μ‚¬ν•­:
2591
- <ul style="text-align: left; margin: 10px auto; max-width: 600px;">
2592
- <li>λͺ©ν‘œ 단어 수 8,000λ‹¨μ–΄λ‘œ μ‘°μ •</li>
2593
- <li>ν”„λ‘¬ν”„νŠΈ κ°„μ†Œν™”λ‘œ 생성 곡간 확보</li>
2594
- <li>단어 수 λΆ€μ‘± μ‹œ μžλ™ μž¬μƒμ„±</li>
2595
- <li>μ‹€μ‹œκ°„ μ§„ν–‰λ₯  ν‘œμ‹œ</li>
2596
- <li>λΆ„λŸ‰ 미달 κ²½κ³  μ‹œμŠ€ν…œ</li>
2597
- <li>ν”„λ‘¬ν”„νŠΈ μžλ™ 증강 κΈ°λŠ₯</li>
2598
- <li>인과관계와 캐릭터 일관성 κ°•ν™”</li>
2599
- </ul>
2600
- </div>
2601
- <div class="warning-note">
2602
- ⚠️ λΆ„λŸ‰ λͺ©ν‘œ: 각 μž‘κ°€λ‹Ή μ΅œμ†Œ 800단어, 전체 8,000단어 이상
2603
- </div>
2604
- </div>
2605
- """)
2606
-
2607
- # μƒνƒœ 관리
2608
- current_session_id = gr.State(None)
2609
-
2610
- with gr.Row():
2611
- with gr.Column(scale=1):
2612
- with gr.Group(elem_classes=["input-section"]):
2613
- query_input = gr.Textbox(
2614
- label="μ†Œμ„€ 주제 / Novel Theme",
2615
- placeholder="μ€‘νŽΈμ†Œμ„€μ˜ 주제λ₯Ό μž…λ ₯ν•˜μ„Έμš”. 인물의 변화와 μ„±μž₯이 쀑심이 λ˜λŠ” 이야기...\nEnter the theme for your novella. Focus on character transformation and growth...",
2616
- lines=4
2617
- )
2618
-
2619
- language_select = gr.Radio(
2620
- choices=["Korean", "English"],
2621
- value="Korean",
2622
- label="μ–Έμ–΄ / Language"
2623
- )
2624
-
2625
- with gr.Row():
2626
- submit_btn = gr.Button("πŸš€ μ†Œμ„€ 생성 μ‹œμž‘", variant="primary", scale=2)
2627
- clear_btn = gr.Button("πŸ—‘οΈ μ΄ˆκΈ°ν™”", scale=1)
2628
-
2629
- status_text = gr.Textbox(
2630
- label="μƒνƒœ",
2631
- interactive=False,
2632
- value="πŸ”„ μ€€λΉ„ μ™„λ£Œ"
2633
- )
2634
-
2635
- # μ„Έμ…˜ 관리
2636
- with gr.Group(elem_classes=["session-section"]):
2637
- gr.Markdown("### πŸ’Ύ μ§„ν–‰ 쀑인 μ„Έμ…˜")
2638
- session_dropdown = gr.Dropdown(
2639
- label="μ„Έμ…˜ 선택",
2640
- choices=[],
2641
- interactive=True
2642
- )
2643
- with gr.Row():
2644
- refresh_btn = gr.Button("πŸ”„ λͺ©λ‘ μƒˆλ‘œκ³ μΉ¨", scale=1)
2645
- resume_btn = gr.Button("▢️ 선택 재개", variant="secondary", scale=1)
2646
- auto_recover_btn = gr.Button("♻️ 졜근 μ„Έμ…˜ 볡ꡬ", scale=1)
2647
-
2648
- with gr.Column(scale=2):
2649
- with gr.Tab("πŸ“ μ°½μž‘ μ§„ν–‰"):
2650
- stages_display = gr.Markdown(
2651
- value="μ°½μž‘ 과정이 여기에 ν‘œμ‹œλ©λ‹ˆλ‹€...",
2652
- elem_id="stages-display"
2653
- )
2654
-
2655
- with gr.Tab("πŸ“– μ™„μ„±λœ μ†Œμ„€"):
2656
- novel_output = gr.Markdown(
2657
- value="μ™„μ„±λœ μ†Œμ„€μ΄ 여기에 ν‘œμ‹œλ©λ‹ˆλ‹€...",
2658
- elem_id="novel-output"
2659
- )
2660
-
2661
- with gr.Group(elem_classes=["download-section"]):
2662
- gr.Markdown("### πŸ“₯ μ†Œμ„€ λ‹€μš΄λ‘œλ“œ")
2663
- with gr.Row():
2664
- format_select = gr.Radio(
2665
- choices=["DOCX", "TXT"],
2666
- value="DOCX" if DOCX_AVAILABLE else "TXT",
2667
- label="ν˜•μ‹"
2668
- )
2669
- download_btn = gr.Button("⬇️ λ‹€μš΄λ‘œλ“œ", variant="secondary")
2670
-
2671
- download_file = gr.File(
2672
- label="λ‹€μš΄λ‘œλ“œλœ 파일",
2673
- visible=False
2674
- )
2675
-
2676
- with gr.Tab("πŸ“Š 평가 λ³΄κ³ μ„œ"):
2677
- report_display = gr.Markdown(
2678
- value="평가 λ³΄κ³ μ„œκ°€ 여기에 ν‘œμ‹œλ©λ‹ˆλ‹€...",
2679
- elem_id="report-display"
2680
- )
2681
-
2682
- # μˆ¨κ²¨μ§„ μƒνƒœ
2683
- novel_text_state = gr.State("")
2684
-
2685
- # 예제
2686
- with gr.Row():
2687
- gr.Examples(
2688
- examples=[
2689
- ["κΈ°μ΄ˆμƒν™œμˆ˜κΈ‰μžκ°€ 된 μ²­λ…„μ˜ 생쑴과 μ‘΄μ—„μ„± μ°ΎκΈ°"],
2690
- ["μ‹€μ§ν•œ 쀑년 남성이 μƒˆλ‘œμš΄ μ‚Άμ˜ 의미λ₯Ό μ°Ύμ•„κ°€λŠ” μ—¬μ •"],
2691
- ["λ„μ‹œμ—μ„œ μ‹œκ³¨λ‘œ μ΄μ£Όν•œ μ²­λ…„μ˜ 적응과 μ„±μž₯ 이야기"],
2692
- ["μ„Έ μ„ΈλŒ€κ°€ ν•¨κ»˜ μ‚¬λŠ” κ°€μ‘±μ˜ κ°ˆλ“±κ³Ό ν™”ν•΄"],
2693
- ["A middle-aged woman's journey to rediscover herself after divorce"],
2694
- ["The transformation of a cynical journalist through unexpected encounters"],
2695
- ["μž‘μ€ μ„œμ μ„ μš΄μ˜ν•˜λŠ” λ…ΈλΆ€λΆ€μ˜ λ§ˆμ§€λ§‰ 1λ…„"],
2696
- ["AI μ‹œλŒ€μ— 일자리λ₯Ό μžƒμ€ λ²ˆμ—­κ°€μ˜ μƒˆλ‘œμš΄ 도전"],
2697
- ["재개발둜 μ‚¬λΌμ Έκ°€λŠ” λ™λ„€μ—μ„œμ˜ λ§ˆμ§€λ§‰ κ³„μ ˆ"]
2698
- ],
2699
- inputs=query_input,
2700
- label="πŸ’‘ 주제 μ˜ˆμ‹œ"
2701
- )
2702
-
2703
- # 이벀트 ν•Έλ“€λŸ¬
2704
- def refresh_sessions():
2705
- try:
2706
- sessions = get_active_sessions("Korean")
2707
- return gr.update(choices=sessions)
2708
- except Exception as e:
2709
- logger.error(f"Error refreshing sessions: {str(e)}")
2710
- logger.error(f"Full error: {e}", exc_info=True) # 전체 μŠ€νƒ 트레이슀 λ‘œκΉ…
2711
- return gr.update(choices=[])
2712
-
2713
- def handle_auto_recover(language):
2714
- session_id, message = auto_recover_session(language)
2715
- return session_id, message
2716
-
2717
- def update_displays(stages_md, novel_md, status, session_id):
2718
- """λͺ¨λ“  λ””μŠ€ν”Œλ ˆμ΄ μ—…λ°μ΄νŠΈ"""
2719
- # 평가 λ³΄κ³ μ„œ κ°€μ Έμ˜€κΈ°
2720
- report = ""
2721
- if session_id:
2722
- session = NovelDatabase.get_session(session_id)
2723
- if session and session.get('literary_report'):
2724
- report = session['literary_report']
2725
-
2726
- return stages_md, novel_md, status, session_id, report
2727
-
2728
- # 이벀트 μ—°κ²°
2729
- submit_btn.click(
2730
- fn=process_query,
2731
- inputs=[query_input, language_select, current_session_id],
2732
- outputs=[stages_display, novel_output, status_text, current_session_id]
2733
- ).then(
2734
- fn=lambda s, n, st, sid: (s, n, st, sid, NovelDatabase.get_session(sid).get('literary_report', '') if sid and NovelDatabase.get_session(sid) else ''),
2735
- inputs=[stages_display, novel_output, status_text, current_session_id],
2736
- outputs=[stages_display, novel_output, status_text, current_session_id, report_display]
2737
- )
2738
-
2739
- novel_output.change(
2740
- fn=lambda x: x,
2741
- inputs=[novel_output],
2742
- outputs=[novel_text_state]
2743
- )
2744
-
2745
- resume_btn.click(
2746
- fn=lambda x: x.split("...")[0] if x and "..." in x else x,
2747
- inputs=[session_dropdown],
2748
- outputs=[current_session_id]
2749
- ).then(
2750
- fn=resume_session,
2751
- inputs=[current_session_id, language_select],
2752
- outputs=[stages_display, novel_output, status_text, current_session_id]
2753
- ).then(
2754
- fn=lambda s, n, st, sid: (s, n, st, sid, NovelDatabase.get_session(sid).get('literary_report', '') if sid and NovelDatabase.get_session(sid) else ''),
2755
- inputs=[stages_display, novel_output, status_text, current_session_id],
2756
- outputs=[stages_display, novel_output, status_text, current_session_id, report_display]
2757
- )
2758
-
2759
- auto_recover_btn.click(
2760
- fn=handle_auto_recover,
2761
- inputs=[language_select],
2762
- outputs=[current_session_id, status_text]
2763
- ).then(
2764
- fn=resume_session,
2765
- inputs=[current_session_id, language_select],
2766
- outputs=[stages_display, novel_output, status_text, current_session_id]
2767
- ).then(
2768
- fn=lambda s, n, st, sid: (s, n, st, sid, NovelDatabase.get_session(sid).get('literary_report', '') if sid and NovelDatabase.get_session(sid) else ''),
2769
- inputs=[stages_display, novel_output, status_text, current_session_id],
2770
- outputs=[stages_display, novel_output, status_text, current_session_id, report_display]
2771
- )
2772
-
2773
- refresh_btn.click(
2774
- fn=refresh_sessions,
2775
- outputs=[session_dropdown]
2776
- )
2777
-
2778
- clear_btn.click(
2779
- fn=lambda: ("", "", "πŸ”„ μ€€λΉ„ μ™„λ£Œ", "", None, ""),
2780
- outputs=[stages_display, novel_output, status_text, novel_text_state, current_session_id, report_display]
2781
- )
2782
-
2783
- def handle_download(format_type, language, session_id, novel_text):
2784
- if not session_id or not novel_text:
2785
- return gr.update(visible=False)
2786
-
2787
- file_path = download_novel(novel_text, format_type, language, session_id)
2788
- if file_path:
2789
- return gr.update(value=file_path, visible=True)
2790
- else:
2791
- return gr.update(visible=False)
2792
-
2793
- download_btn.click(
2794
- fn=handle_download,
2795
- inputs=[format_select, language_select, current_session_id, novel_text_state],
2796
- outputs=[download_file]
2797
- )
2798
-
2799
- # μ‹œμž‘ μ‹œ μ„Έμ…˜ λ‘œλ“œ
2800
- interface.load(
2801
- fn=refresh_sessions,
2802
- outputs=[session_dropdown]
2803
- )
2804
-
2805
- return interface
2806
-
2807
-
2808
- # 메인 μ‹€ν–‰
2809
- if __name__ == "__main__":
2810
- logger.info("AI μ§„ν–‰ν˜• μž₯νŽΈμ†Œμ„€ 생성 μ‹œμŠ€ν…œ v3.1 μ‹œμž‘...")
2811
- logger.info("=" * 60)
2812
-
2813
- # ν™˜κ²½ 확인
2814
- logger.info(f"API μ—”λ“œν¬μΈνŠΈ: {API_URL}")
2815
- logger.info(f"λͺ©ν‘œ λΆ„λŸ‰: {TARGET_WORDS:,}단어")
2816
- logger.info(f"μž‘κ°€λ‹Ή μ΅œμ†Œ λΆ„λŸ‰: {MIN_WORDS_PER_WRITER:,}단어")
2817
- logger.info("μ£Όμš” κ°œμ„ μ‚¬ν•­:")
2818
- logger.info("- λΆ„λŸ‰ λͺ©ν‘œ 8,000λ‹¨μ–΄λ‘œ μ‘°μ •")
2819
- logger.info("- ν”„λ‘¬ν”„νŠΈ κ°„μ†Œν™”")
2820
- logger.info("- 단어 수 λΆ€μ‘± μ‹œ μžλ™ μž¬μƒμ„±")
2821
- logger.info("- μ‹€μ‹œκ°„ μ§„ν–‰λ₯  ν‘œμ‹œ")
2822
- logger.info("- ν”„λ‘¬ν”„νŠΈ μžλ™ 증강 κΈ°λŠ₯")
2823
- logger.info("- 인과관계와 캐릭터 일관성 κ°•ν™”")
2824
-
2825
- if BRAVE_SEARCH_API_KEY:
2826
- logger.info("μ›Ή 검색이 ν™œμ„±ν™”λ˜μ—ˆμŠ΅λ‹ˆλ‹€.")
2827
- else:
2828
- logger.warning("μ›Ή 검색이 λΉ„ν™œμ„±ν™”λ˜μ—ˆμŠ΅λ‹ˆλ‹€.")
2829
-
2830
- if DOCX_AVAILABLE:
2831
- logger.info("DOCX 내보내기가 ν™œμ„±ν™”λ˜μ—ˆμŠ΅λ‹ˆλ‹€.")
2832
- else:
2833
- logger.warning("DOCX 내보내기가 λΉ„ν™œμ„±ν™”λ˜μ—ˆμŠ΅λ‹ˆλ‹€.")
2834
-
2835
- logger.info("=" * 60)
2836
-
2837
- # λ°μ΄ν„°λ² μ΄μŠ€ μ΄ˆκΈ°ν™”
2838
- logger.info("λ°μ΄ν„°λ² μ΄μŠ€ μ΄ˆκΈ°ν™” 쀑...")
2839
- NovelDatabase.init_db()
2840
- logger.info("λ°μ΄ν„°λ² μ΄μŠ€ μ΄ˆκΈ°ν™” μ™„λ£Œ.")
2841
-
2842
- # μΈν„°νŽ˜μ΄μŠ€ 생성 및 μ‹€ν–‰
2843
- interface = create_interface()
2844
-
2845
- interface.launch(
2846
- server_name="0.0.0.0",
2847
- server_port=7860,
2848
- share=False,
2849
- debug=True
2850
- )</ul>
2851
- </div>
2852
- <div class="warning-note">
2853
- ⚠️ λΆ„λŸ‰ λͺ©ν‘œ: 각 μž‘κ°€λ‹Ή μ΅œμ†Œ 800단어, 전체 8,000단어 이상
2854
- </div>
2855
- </div>
2856
- """)
2857
-
2858
- # μƒνƒœ 관리
2859
- current_session_id = gr.State(None)
2860
-
2861
- with gr.Row():
2862
- with gr.Column(scale=1):
2863
- with gr.Group(elem_classes=["input-section"]):
2864
- query_input = gr.Textbox(
2865
- label="μ†Œμ„€ 주제 / Novel Theme",
2866
- placeholder="μ€‘νŽΈμ†Œμ„€μ˜ 주제λ₯Ό μž…λ ₯ν•˜μ„Έμš”. 인물의 변화와 μ„±μž₯이 쀑심이 λ˜λŠ” 이야기...\nEnter the theme for your novella. Focus on character transformation and growth...",
2867
- lines=4
2868
- )
2869
-
2870
- language_select = gr.Radio(
2871
- choices=["Korean", "English"],
2872
- value="Korean",
2873
- label="μ–Έμ–΄ / Language"
2874
- )
2875
-
2876
- with gr.Row():
2877
- submit_btn = gr.Button("πŸš€ μ†Œμ„€ 생성 μ‹œμž‘", variant="primary", scale=2)
2878
- clear_btn = gr.Button("πŸ—‘οΈ μ΄ˆκΈ°ν™”", scale=1)
2879
-
2880
- status_text = gr.Textbox(
2881
- label="μƒνƒœ",
2882
- interactive=False,
2883
- value="πŸ”„ μ€€λΉ„ μ™„λ£Œ"
2884
- )
2885
-
2886
- # μ„Έμ…˜ 관리
2887
- with gr.Group(elem_classes=["session-section"]):
2888
- gr.Markdown("### πŸ’Ύ μ§„ν–‰ 쀑인 μ„Έμ…˜")
2889
- session_dropdown = gr.Dropdown(
2890
- label="μ„Έμ…˜ 선택",
2891
- choices=[],
2892
- interactive=True
2893
- )
2894
- with gr.Row():
2895
- refresh_btn = gr.Button("πŸ”„ λͺ©λ‘ μƒˆλ‘œκ³ μΉ¨", scale=1)
2896
- resume_btn = gr.Button("▢️ 선택 재개", variant="secondary", scale=1)
2897
- auto_recover_btn = gr.Button("♻️ 졜근 μ„Έμ…˜ 볡ꡬ", scale=1)
2898
-
2899
- with gr.Column(scale=2):
2900
- with gr.Tab("πŸ“ μ°½μž‘ μ§„ν–‰"):
2901
- stages_display = gr.Markdown(
2902
- value="μ°½μž‘ 과정이 여기에 ν‘œμ‹œλ©λ‹ˆλ‹€...",
2903
- elem_id="stages-display"
2904
- )
2905
-
2906
- with gr.Tab("πŸ“– μ™„μ„±λœ μ†Œμ„€"):
2907
- novel_output = gr.Markdown(
2908
- value="μ™„μ„±λœ μ†Œμ„€μ΄ 여기에 ν‘œμ‹œλ©λ‹ˆλ‹€...",
2909
- elem_id="novel-output"
2910
- )
2911
-
2912
- with gr.Group(elem_classes=["download-section"]):
2913
- gr.Markdown("### πŸ“₯ μ†Œμ„€ λ‹€μš΄λ‘œλ“œ")
2914
- with gr.Row():
2915
- format_select = gr.Radio(
2916
- choices=["DOCX", "TXT"],
2917
- value="DOCX" if DOCX_AVAILABLE else "TXT",
2918
- label="ν˜•μ‹"
2919
- )
2920
- download_btn = gr.Button("⬇️ λ‹€μš΄λ‘œλ“œ", variant="secondary")
2921
-
2922
- download_file = gr.File(
2923
- label="λ‹€μš΄λ‘œλ“œλœ 파일",
2924
- visible=False
2925
- )
2926
-
2927
- with gr.Tab("πŸ“Š 평가 λ³΄κ³ μ„œ"):
2928
- report_display = gr.Markdown(
2929
- value="평가 λ³΄κ³ μ„œκ°€ 여기에 ν‘œμ‹œλ©λ‹ˆλ‹€...",
2930
- elem_id="report-display"
2931
- )
2932
-
2933
- # μˆ¨κ²¨μ§„ μƒνƒœ
2934
- novel_text_state = gr.State("")
2935
-
2936
- # 예제
2937
- with gr.Row():
2938
- gr.Examples(
2939
- examples=[
2940
- ["κΈ°μ΄ˆμƒν™œμˆ˜κΈ‰μžκ°€ 된 μ²­λ…„μ˜ 생쑴과 μ‘΄μ—„μ„± μ°ΎκΈ°"],
2941
- ["μ‹€μ§ν•œ 쀑년 남성이 μƒˆλ‘œμš΄ μ‚Άμ˜ 의미λ₯Ό μ°Ύμ•„κ°€λŠ” μ—¬μ •"],
2942
- ["λ„μ‹œμ—μ„œ μ‹œκ³¨λ‘œ μ΄μ£Όν•œ μ²­λ…„μ˜ 적응과 μ„±μž₯ 이야기"],
2943
- ["μ„Έ μ„ΈλŒ€κ°€ ν•¨κ»˜ μ‚¬λŠ” κ°€μ‘±μ˜ κ°ˆλ“±κ³Ό ν™”ν•΄"],
2944
- ["A middle-aged woman's journey to rediscover herself after divorce"],
2945
- ["The transformation of a cynical journalist through unexpected encounters"],
2946
- ["μž‘μ€ μ„œμ μ„ μš΄μ˜ν•˜λŠ” λ…ΈλΆ€λΆ€μ˜ λ§ˆμ§€λ§‰ 1λ…„"],
2947
- ["AI μ‹œλŒ€μ— 일자리λ₯Ό μžƒμ€ λ²ˆμ—­κ°€μ˜ μƒˆλ‘œμš΄ 도전"],
2948
- ["재개발둜 μ‚¬λΌμ Έκ°€λŠ” λ™λ„€μ—μ„œμ˜ λ§ˆμ§€λ§‰ κ³„μ ˆ"]
2949
- ],
2950
- inputs=query_input,
2951
- label="πŸ’‘ 주제 μ˜ˆμ‹œ"
2952
- )
2953
-
2954
- # 이벀트 ν•Έλ“€λŸ¬
2955
- def refresh_sessions():
2956
- try:
2957
- sessions = get_active_sessions("Korean")
2958
- return gr.update(choices=sessions)
2959
- except Exception as e:
2960
- logger.error(f"Error refreshing sessions: {str(e)}")
2961
- logger.error(f"Full error: {e}", exc_info=True) # 전체 μŠ€νƒ 트레이슀 λ‘œκΉ…
2962
- return gr.update(choices=[])
2963
-
2964
- def handle_auto_recover(language):
2965
- session_id, message = auto_recover_session(language)
2966
- return session_id, message
2967
-
2968
- def update_displays(stages_md, novel_md, status, session_id):
2969
- """λͺ¨λ“  λ””μŠ€ν”Œλ ˆμ΄ μ—…λ°μ΄νŠΈ"""
2970
- # 평가 λ³΄κ³ μ„œ κ°€μ Έμ˜€κΈ°
2971
- report = ""
2972
- if session_id:
2973
- session = NovelDatabase.get_session(session_id)
2974
- if session and session.get('literary_report'):
2975
- report = session['literary_report']
2976
-
2977
- return stages_md, novel_md, status, session_id, report
2978
-
2979
- # 이벀트 μ—°κ²°
2980
- submit_btn.click(
2981
- fn=process_query,
2982
- inputs=[query_input, language_select, current_session_id],
2983
- outputs=[stages_display, novel_output, status_text, current_session_id]
2984
- ).then(
2985
- fn=lambda s, n, st, sid: (s, n, st, sid, NovelDatabase.get_session(sid).get('literary_report', '') if sid and NovelDatabase.get_session(sid) else ''),
2986
- inputs=[stages_display, novel_output, status_text, current_session_id],
2987
- outputs=[stages_display, novel_output, status_text, current_session_id, report_display]
2988
- )
2989
-
2990
- novel_output.change(
2991
- fn=lambda x: x,
2992
- inputs=[novel_output],
2993
- outputs=[novel_text_state]
2994
- )
2995
-
2996
- resume_btn.click(
2997
- fn=lambda x: x.split("...")[0] if x and "..." in x else x,
2998
- inputs=[session_dropdown],
2999
- outputs=[current_session_id]
3000
- ).then(
3001
- fn=resume_session,
3002
- inputs=[current_session_id, language_select],
3003
- outputs=[stages_display, novel_output, status_text, current_session_id]
3004
- ).then(
3005
- fn=lambda s, n, st, sid: (s, n, st, sid, NovelDatabase.get_session(sid).get('literary_report', '') if sid and NovelDatabase.get_session(sid) else ''),
3006
- inputs=[stages_display, novel_output, status_text, current_session_id],
3007
- outputs=[stages_display, novel_output, status_text, current_session_id, report_display]
3008
- )
3009
-
3010
- auto_recover_btn.click(
3011
- fn=handle_auto_recover,
3012
- inputs=[language_select],
3013
- outputs=[current_session_id, status_text]
3014
- ).then(
3015
- fn=resume_session,
3016
- inputs=[current_session_id, language_select],
3017
- outputs=[stages_display, novel_output, status_text, current_session_id]
3018
- ).then(
3019
- fn=lambda s, n, st, sid: (s, n, st, sid, NovelDatabase.get_session(sid).get('literary_report', '') if sid and NovelDatabase.get_session(sid) else ''),
3020
- inputs=[stages_display, novel_output, status_text, current_session_id],
3021
- outputs=[stages_display, novel_output, status_text, current_session_id, report_display]
3022
- )
3023
-
3024
- refresh_btn.click(
3025
- fn=refresh_sessions,
3026
- outputs=[session_dropdown]
3027
- )
3028
-
3029
- clear_btn.click(
3030
- fn=lambda: ("", "", "πŸ”„ μ€€λΉ„ μ™„λ£Œ", "", None, ""),
3031
- outputs=[stages_display, novel_output, status_text, novel_text_state, current_session_id, report_display]
3032
- )
3033
-
3034
- def handle_download(format_type, language, session_id, novel_text):
3035
- if not session_id or not novel_text:
3036
- return gr.update(visible=False)
3037
-
3038
- file_path = download_novel(novel_text, format_type, language, session_id)
3039
- if file_path:
3040
- return gr.update(value=file_path, visible=True)
3041
- else:
3042
- return gr.update(visible=False)
3043
-
3044
- download_btn.click(
3045
- fn=handle_download,
3046
- inputs=[format_select, language_select, current_session_id, novel_text_state],
3047
- outputs=[download_file]
3048
- )
3049
-
3050
- # μ‹œμž‘ μ‹œ μ„Έμ…˜ λ‘œλ“œ
3051
- interface.load(
3052
- fn=refresh_sessions,
3053
- outputs=[session_dropdown]
3054
- )
3055
-
3056
- return interface
3057
 
3058
 
3059
  # 메인 μ‹€ν–‰
3060
  if __name__ == "__main__":
3061
- logger.info("AI μ§„ν–‰ν˜• μž₯νŽΈμ†Œμ„€ 생성 μ‹œμŠ€ν…œ v3.1 μ‹œμž‘...")
3062
- logger.info("=" * 60)
3063
-
3064
- # ν™˜κ²½ 확인
3065
- logger.info(f"API μ—”λ“œν¬μΈνŠΈ: {API_URL}")
3066
- logger.info(f"λͺ©ν‘œ λΆ„λŸ‰: {TARGET_WORDS:,}단어")
3067
- logger.info(f"μž‘κ°€λ‹Ή μ΅œμ†Œ λΆ„λŸ‰: {MIN_WORDS_PER_WRITER:,}단어")
3068
- logger.info("μ£Όμš” κ°œμ„ μ‚¬ν•­:")
3069
- logger.info("- λΆ„λŸ‰ λͺ©ν‘œ 8,000λ‹¨μ–΄λ‘œ μ‘°μ •")
3070
- logger.info("- ν”„λ‘¬ν”„νŠΈ κ°„μ†Œν™”")
3071
- logger.info("- 단어 수 λΆ€μ‘± μ‹œ μžλ™ μž¬μƒμ„±")
3072
- logger.info("- μ‹€μ‹œκ°„ μ§„ν–‰λ₯  ν‘œμ‹œ")
3073
- logger.info("- ν”„λ‘¬ν”„νŠΈ μžλ™ 증강 κΈ°λŠ₯")
3074
- logger.info("- 인과관계와 캐릭터 일관성 κ°•ν™”")
3075
-
3076
- if BRAVE_SEARCH_API_KEY:
3077
- logger.info("μ›Ή 검색이 ν™œμ„±ν™”λ˜μ—ˆμŠ΅λ‹ˆλ‹€.")
3078
- else:
3079
- logger.warning("μ›Ή 검색이 λΉ„ν™œμ„±ν™”λ˜μ—ˆμŠ΅λ‹ˆλ‹€.")
3080
-
3081
- if DOCX_AVAILABLE:
3082
- logger.info("DOCX 내보내기가 ν™œμ„±ν™”λ˜μ—ˆμŠ΅λ‹ˆλ‹€.")
3083
- else:
3084
- logger.warning("DOCX 내보내기가 λΉ„ν™œμ„±ν™”λ˜μ—ˆμŠ΅λ‹ˆλ‹€.")
3085
-
3086
- logger.info("=" * 60)
3087
-
3088
- # λ°μ΄ν„°λ² μ΄μŠ€ μ΄ˆκΈ°ν™”
3089
- logger.info("λ°μ΄ν„°λ² μ΄μŠ€ μ΄ˆκΈ°ν™” 쀑...")
3090
- NovelDatabase.init_db()
3091
- logger.info("λ°μ΄ν„°λ² μ΄μŠ€ μ΄ˆκΈ°ν™” μ™„λ£Œ.")
3092
-
3093
- # μΈν„°νŽ˜μ΄μŠ€ 생성 및 μ‹€ν–‰
3094
- interface = create_interface()
3095
-
3096
- interface.launch(
3097
- server_name="0.0.0.0",
3098
- server_port=7860,
3099
- share=False,
3100
- debug=True
3101
- )
 
2569
 
2570
  # Gradio μΈν„°νŽ˜μ΄μŠ€ 생성
2571
  def create_interface():
2572
+ with gr.Blocks(css=custom_css, title="AI μ§„ν–‰ν˜• μž₯νŽΈμ†Œμ„€ 생성 μ‹œμŠ€ν…œ v3.1") as interface:
2573
+ gr.HTML("""
2574
+ <div class="main-header">
2575
+ <h1 style="font-size: 2.5em; margin-bottom: 10px;">
2576
+ πŸ“š AI μ§„ν–‰ν˜• μž₯νŽΈμ†Œμ„€ 생성 μ‹œμŠ€ν…œ v3.1
2577
+ </h1>
2578
+ <h3 style="color: #ddd; margin-bottom: 20px;">
2579
+ λͺ©ν‘œ λΆ„λŸ‰ 달성을 μœ„ν•œ μ΅œμ ν™” 버전
2580
+ </h3>
2581
+ <p style="font-size: 1.1em; color: #eee; max-width: 800px; margin: 0 auto;">
2582
+ 10개의 유기적으둜 μ—°κ²°λœ 단계λ₯Ό 톡해 ν•˜λ‚˜μ˜ μ™„μ „ν•œ 이야기λ₯Ό λ§Œλ“€μ–΄λƒ…λ‹ˆλ‹€.
2583
+ <br>
2584
+ 각 λ‹¨κ³„λŠ” 이전 λ‹¨κ³„μ˜ 필연적 결과둜 이어지며, 인물의 변화와 μ„±μž₯을 μΆ”μ ν•©λ‹ˆλ‹€.
2585
+ </p>
2586
+ <div class="progress-note">
2587
+ ⚑ 반볡이 μ•„λ‹Œ 좕적, μˆœν™˜μ΄ μ•„λ‹Œ 진행을 ν†΅ν•œ μ§„μ •ν•œ μž₯편 μ„œμ‚¬
2588
+ </div>
2589
+ <div class="improvement-note">
2590
+ πŸ†• v3.1 핡심 κ°œμ„ μ‚¬ν•­:
2591
+ <ul style="text-align: left; margin: 10px auto; max-width: 600px;">
2592
+ <li>λͺ©ν‘œ 단어 수 8,000λ‹¨μ–΄λ‘œ μ‘°μ •</li>
2593
+ <li>ν”„λ‘¬ν”„νŠΈ κ°„μ†Œν™”λ‘œ 생성 곡간 확보</li>
2594
+ <li>단어 수 λΆ€μ‘± μ‹œ μžλ™ μž¬μƒμ„±</li>
2595
+ <li>μ‹€μ‹œκ°„ μ§„ν–‰λ₯  ν‘œμ‹œ</li>
2596
+ <li>λΆ„λŸ‰ 미달 κ²½κ³  μ‹œμŠ€ν…œ</li>
2597
+ <li>ν”„λ‘¬ν”„νŠΈ μžλ™ 증강 κΈ°λŠ₯</li>
2598
+ <li>인과관계와 캐릭터 일관성 κ°•ν™”</li>
2599
+ </ul>
2600
+ </div>
2601
+ <div class="warning-note">
2602
+ ⚠️ λΆ„λŸ‰ λͺ©ν‘œ: 각 μž‘κ°€λ‹Ή μ΅œμ†Œ 800단어, 전체 8,000단어 이상
2603
+ </div>
2604
+ </div>
2605
+ """)
2606
+
2607
+ # μƒνƒœ 관리
2608
+ current_session_id = gr.State(None)
2609
+
2610
+ with gr.Row():
2611
+ with gr.Column(scale=1):
2612
+ with gr.Group(elem_classes=["input-section"]):
2613
+ query_input = gr.Textbox(
2614
+ label="μ†Œμ„€ 주제 / Novel Theme",
2615
+ placeholder="μ€‘νŽΈμ†Œμ„€μ˜ 주제λ₯Ό μž…λ ₯ν•˜μ„Έμš”. 인물의 변화와 μ„±μž₯이 쀑심이 λ˜λŠ” 이야기...\nEnter the theme for your novella. Focus on character transformation and growth...",
2616
+ lines=4
2617
+ )
2618
+
2619
+ language_select = gr.Radio(
2620
+ choices=["Korean", "English"],
2621
+ value="Korean",
2622
+ label="μ–Έμ–΄ / Language"
2623
+ )
2624
+
2625
+ with gr.Row():
2626
+ submit_btn = gr.Button("πŸš€ μ†Œμ„€ 생성 μ‹œμž‘", variant="primary", scale=2)
2627
+ clear_btn = gr.Button("πŸ—‘οΈ μ΄ˆκΈ°ν™”", scale=1)
2628
+
2629
+ status_text = gr.Textbox(
2630
+ label="μƒνƒœ",
2631
+ interactive=False,
2632
+ value="πŸ”„ μ€€λΉ„ μ™„λ£Œ"
2633
+ )
2634
+
2635
+ # μ„Έμ…˜ 관리
2636
+ with gr.Group(elem_classes=["session-section"]):
2637
+ gr.Markdown("### πŸ’Ύ μ§„ν–‰ 쀑인 μ„Έμ…˜")
2638
+ session_dropdown = gr.Dropdown(
2639
+ label="μ„Έμ…˜ 선택",
2640
+ choices=[],
2641
+ interactive=True
2642
+ )
2643
+ with gr.Row():
2644
+ refresh_btn = gr.Button("πŸ”„ λͺ©λ‘ μƒˆλ‘œκ³ μΉ¨", scale=1)
2645
+ resume_btn = gr.Button("▢️ 선택 재개", variant="secondary", scale=1)
2646
+ auto_recover_btn = gr.Button("♻️ 졜근 μ„Έμ…˜ 볡ꡬ", scale=1)
2647
+
2648
+ with gr.Column(scale=2):
2649
+ with gr.Tab("πŸ“ μ°½μž‘ μ§„ν–‰"):
2650
+ stages_display = gr.Markdown(
2651
+ value="μ°½μž‘ 과정이 여기에 ν‘œμ‹œλ©λ‹ˆλ‹€...",
2652
+ elem_id="stages-display"
2653
+ )
2654
+
2655
+ with gr.Tab("πŸ“– μ™„μ„±λœ μ†Œμ„€"):
2656
+ novel_output = gr.Markdown(
2657
+ value="μ™„μ„±λœ μ†Œμ„€μ΄ 여기에 ν‘œμ‹œλ©λ‹ˆλ‹€...",
2658
+ elem_id="novel-output"
2659
+ )
2660
+
2661
+ with gr.Group(elem_classes=["download-section"]):
2662
+ gr.Markdown("### πŸ“₯ μ†Œμ„€ λ‹€μš΄λ‘œλ“œ")
2663
+ with gr.Row():
2664
+ format_select = gr.Radio(
2665
+ choices=["DOCX", "TXT"],
2666
+ value="DOCX" if DOCX_AVAILABLE else "TXT",
2667
+ label="ν˜•μ‹"
2668
+ )
2669
+ download_btn = gr.Button("⬇️ λ‹€μš΄λ‘œλ“œ", variant="secondary")
2670
+
2671
+ download_file = gr.File(
2672
+ label="λ‹€μš΄λ‘œλ“œλœ 파일",
2673
+ visible=False
2674
+ )
2675
+
2676
+ with gr.Tab("πŸ“Š 평가 λ³΄κ³ μ„œ"):
2677
+ report_display = gr.Markdown(
2678
+ value="평가 λ³΄κ³ μ„œκ°€ 여기에 ν‘œμ‹œλ©λ‹ˆλ‹€...",
2679
+ elem_id="report-display"
2680
+ )
2681
+
2682
+ # μˆ¨κ²¨μ§„ μƒνƒœ
2683
+ novel_text_state = gr.State("")
2684
+
2685
+ # 예제
2686
+ with gr.Row():
2687
+ gr.Examples(
2688
+ examples=[
2689
+ ["κΈ°μ΄ˆμƒν™œμˆ˜κΈ‰μžκ°€ 된 μ²­λ…„μ˜ 생쑴과 μ‘΄μ—„μ„± μ°ΎκΈ°"],
2690
+ ["μ‹€μ§ν•œ 쀑년 남성이 μƒˆλ‘œμš΄ μ‚Άμ˜ 의미λ₯Ό μ°Ύμ•„κ°€λŠ” μ—¬μ •"],
2691
+ ["λ„μ‹œμ—μ„œ μ‹œκ³¨λ‘œ μ΄μ£Όν•œ μ²­λ…„μ˜ 적응과 μ„±μž₯ 이야기"],
2692
+ ["μ„Έ μ„ΈλŒ€κ°€ ν•¨κ»˜ μ‚¬λŠ” κ°€μ‘±μ˜ κ°ˆλ“±κ³Ό ν™”ν•΄"],
2693
+ ["A middle-aged woman's journey to rediscover herself after divorce"],
2694
+ ["The transformation of a cynical journalist through unexpected encounters"],
2695
+ ["μž‘μ€ μ„œμ μ„ μš΄μ˜ν•˜λŠ” λ…ΈλΆ€λΆ€μ˜ λ§ˆμ§€λ§‰ 1λ…„"],
2696
+ ["AI μ‹œλŒ€μ— 일자리λ₯Ό μžƒμ€ λ²ˆμ—­κ°€μ˜ μƒˆλ‘œμš΄ 도전"],
2697
+ ["재개발둜 μ‚¬λΌμ Έκ°€λŠ” λ™λ„€μ—μ„œμ˜ λ§ˆμ§€λ§‰ κ³„μ ˆ"]
2698
+ ],
2699
+ inputs=query_input,
2700
+ label="πŸ’‘ 주제 μ˜ˆμ‹œ"
2701
+ )
2702
+
2703
+ # 이벀트 ν•Έλ“€λŸ¬
2704
+ def refresh_sessions():
2705
+ try:
2706
+ sessions = get_active_sessions("Korean")
2707
+ return gr.update(choices=sessions)
2708
+ except Exception as e:
2709
+ logger.error(f"Error refreshing sessions: {str(e)}")
2710
+ logger.error(f"Full error: {e}", exc_info=True) # 전체 μŠ€νƒ 트레이슀 λ‘œκΉ…
2711
+ return gr.update(choices=[])
2712
+
2713
+ def handle_auto_recover(language):
2714
+ session_id, message = auto_recover_session(language)
2715
+ return session_id, message
2716
+
2717
+ def update_displays(stages_md, novel_md, status, session_id):
2718
+ """λͺ¨λ“  λ””μŠ€ν”Œλ ˆμ΄ μ—…λ°μ΄νŠΈ"""
2719
+ # 평가 λ³΄κ³ μ„œ κ°€μ Έμ˜€κΈ°
2720
+ report = ""
2721
+ if session_id:
2722
+ session = NovelDatabase.get_session(session_id)
2723
+ if session and session.get('literary_report'):
2724
+ report = session['literary_report']
2725
+
2726
+ return stages_md, novel_md, status, session_id, report
2727
+
2728
+ # 이벀트 μ—°κ²°
2729
+ submit_btn.click(
2730
+ fn=process_query,
2731
+ inputs=[query_input, language_select, current_session_id],
2732
+ outputs=[stages_display, novel_output, status_text, current_session_id]
2733
+ ).then(
2734
+ fn=lambda s, n, st, sid: (s, n, st, sid, NovelDatabase.get_session(sid).get('literary_report', '') if sid and NovelDatabase.get_session(sid) else ''),
2735
+ inputs=[stages_display, novel_output, status_text, current_session_id],
2736
+ outputs=[stages_display, novel_output, status_text, current_session_id, report_display]
2737
+ )
2738
+
2739
+ novel_output.change(
2740
+ fn=lambda x: x,
2741
+ inputs=[novel_output],
2742
+ outputs=[novel_text_state]
2743
+ )
2744
+
2745
+ resume_btn.click(
2746
+ fn=lambda x: x.split("...")[0] if x and "..." in x else x,
2747
+ inputs=[session_dropdown],
2748
+ outputs=[current_session_id]
2749
+ ).then(
2750
+ fn=resume_session,
2751
+ inputs=[current_session_id, language_select],
2752
+ outputs=[stages_display, novel_output, status_text, current_session_id]
2753
+ ).then(
2754
+ fn=lambda s, n, st, sid: (s, n, st, sid, NovelDatabase.get_session(sid).get('literary_report', '') if sid and NovelDatabase.get_session(sid) else ''),
2755
+ inputs=[stages_display, novel_output, status_text, current_session_id],
2756
+ outputs=[stages_display, novel_output, status_text, current_session_id, report_display]
2757
+ )
2758
+
2759
+ auto_recover_btn.click(
2760
+ fn=handle_auto_recover,
2761
+ inputs=[language_select],
2762
+ outputs=[current_session_id, status_text]
2763
+ ).then(
2764
+ fn=resume_session,
2765
+ inputs=[current_session_id, language_select],
2766
+ outputs=[stages_display, novel_output, status_text, current_session_id]
2767
+ ).then(
2768
+ fn=lambda s, n, st, sid: (s, n, st, sid, NovelDatabase.get_session(sid).get('literary_report', '') if sid and NovelDatabase.get_session(sid) else ''),
2769
+ inputs=[stages_display, novel_output, status_text, current_session_id],
2770
+ outputs=[stages_display, novel_output, status_text, current_session_id, report_display]
2771
+ )
2772
+
2773
+ refresh_btn.click(
2774
+ fn=refresh_sessions,
2775
+ outputs=[session_dropdown]
2776
+ )
2777
+
2778
+ clear_btn.click(
2779
+ fn=lambda: ("", "", "πŸ”„ μ€€λΉ„ μ™„λ£Œ", "", None, ""),
2780
+ outputs=[stages_display, novel_output, status_text, novel_text_state, current_session_id, report_display]
2781
+ )
2782
+
2783
+ def handle_download(format_type, language, session_id, novel_text):
2784
+ if not session_id or not novel_text:
2785
+ return gr.update(visible=False)
2786
+
2787
+ file_path = download_novel(novel_text, format_type, language, session_id)
2788
+ if file_path:
2789
+ return gr.update(value=file_path, visible=True)
2790
+ else:
2791
+ return gr.update(visible=False)
2792
+
2793
+ download_btn.click(
2794
+ fn=handle_download,
2795
+ inputs=[format_select, language_select, current_session_id, novel_text_state],
2796
+ outputs=[download_file]
2797
+ )
2798
+
2799
+ # μ‹œμž‘ μ‹œ μ„Έμ…˜ λ‘œλ“œ
2800
+ interface.load(
2801
+ fn=refresh_sessions,
2802
+ outputs=[session_dropdown]
2803
+ )
2804
+
2805
+ return interface
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2806
 
2807
 
2808
  # 메인 μ‹€ν–‰
2809
  if __name__ == "__main__":
2810
+ logger.info("AI μ§„ν–‰ν˜• μž₯νŽΈμ†Œμ„€ 생성 μ‹œμŠ€ν…œ v3.1 μ‹œμž‘...")
2811
+ logger.info("=" * 60)
2812
+
2813
+ # ν™˜κ²½ 확인
2814
+ logger.info(f"API μ—”λ“œν¬μΈνŠΈ: {API_URL}")
2815
+ logger.info(f"λͺ©ν‘œ λΆ„λŸ‰: {TARGET_WORDS:,}단어")
2816
+ logger.info(f"μž‘κ°€λ‹Ή μ΅œμ†Œ λΆ„λŸ‰: {MIN_WORDS_PER_WRITER:,}단어")
2817
+ logger.info("μ£Όμš” κ°œμ„ μ‚¬ν•­:")
2818
+ logger.info("- λΆ„λŸ‰ λͺ©ν‘œ 8,000λ‹¨μ–΄λ‘œ μ‘°μ •")
2819
+ logger.info("- ν”„λ‘¬ν”„νŠΈ κ°„μ†Œν™”")
2820
+ logger.info("- 단어 수 λΆ€μ‘± μ‹œ μžλ™ μž¬μƒμ„±")
2821
+ logger.info("- μ‹€μ‹œκ°„ μ§„ν–‰λ₯  ν‘œμ‹œ")
2822
+ logger.info("- ν”„λ‘¬ν”„νŠΈ μžλ™ 증강 κΈ°λŠ₯")
2823
+ logger.info("- 인과관계와 캐릭터 일관성 κ°•ν™”")
2824
+
2825
+ if BRAVE_SEARCH_API_KEY:
2826
+ logger.info("μ›Ή 검색이 ν™œμ„±ν™”λ˜μ—ˆμŠ΅λ‹ˆλ‹€.")
2827
+ else:
2828
+ logger.warning("μ›Ή 검색이 λΉ„ν™œμ„±ν™”λ˜μ—ˆμŠ΅λ‹ˆλ‹€.")
2829
+
2830
+ if DOCX_AVAILABLE:
2831
+ logger.info("DOCX 내보내기가 ν™œμ„±ν™”λ˜μ—ˆμŠ΅λ‹ˆλ‹€.")
2832
+ else:
2833
+ logger.warning("DOCX 내보내기가 λΉ„ν™œμ„±ν™”λ˜μ—ˆμŠ΅λ‹ˆλ‹€.")
2834
+
2835
+ logger.info("=" * 60)
2836
+
2837
+ # λ°μ΄ν„°λ² μ΄μŠ€ μ΄ˆκΈ°ν™”
2838
+ logger.info("λ°μ΄ν„°λ² μ΄μŠ€ μ΄ˆκΈ°ν™” 쀑...")
2839
+ NovelDatabase.init_db()
2840
+ logger.info("λ°μ΄ν„°λ² μ΄μŠ€ μ΄ˆκΈ°ν™” μ™„λ£Œ.")
2841
+
2842
+ # μΈν„°νŽ˜μ΄μŠ€ 생성 및 μ‹€ν–‰
2843
+ interface = create_interface()
2844
+
2845
+ interface.launch(
2846
+ server_name="0.0.0.0",
2847
+ server_port=7860,
2848
+ share=False,
2849
+ debug=True
2850
+ )