fantasy-novels / app.py
fantaxy's picture
Update app.py
8b07602 verified
# -*- coding: utf-8 -*-
import gradio as gr
import os
import requests
import json
import logging
# Logging configuration
logging.basicConfig(level=logging.DEBUG, format='%(asctime)s - %(levelname)s - %(message)s')
# API configuration
FRIENDLI_TOKEN = os.environ.get("FRIENDLI_TOKEN") or "YOUR_FRIENDLI_TOKEN"
FRIENDLI_API_URL = "https://api.friendli.ai/dedicated/v1/chat/completions"
# μž₯λ₯΄λ³„ μ‹œμŠ€ν…œ ν”„λ‘¬ν”„νŠΈ
GENRE_PROMPTS = {
"νŒνƒ€μ§€": """당신은 νŒνƒ€μ§€ μ†Œμ„€ μž‘κ°€μž…λ‹ˆλ‹€. λ§ˆλ²•, λ“œλž˜κ³€, μ—˜ν”„, λ˜μ „, λ§ˆκ²€μ‚¬ λ“± νŒνƒ€μ§€ μ„Έκ³„κ΄€μ˜ μš”μ†Œλ“€μ„ ν™œμš©ν•˜μ—¬ λͺ°μž…감 μžˆλŠ” 이야기λ₯Ό μž‘μ„±ν•˜μ„Έμš”.
μ£Όμš” μš”μ†Œ: λ§ˆλ²• μ‹œμŠ€ν…œ, 신화적 쑴재, νŒνƒ€μ§€ μ’…μ‘±, λ§ˆλ²• μ•„μ΄ν…œ, μ˜μ›…μ˜ μ—¬μ •, μ˜ˆμ–Έκ³Ό 운λͺ…""",
"둜맨슀": """당신은 둜맨슀 μ†Œμ„€ μž‘κ°€μž…λ‹ˆλ‹€. 감정선, κ΄€κ³„μ˜ λ°œμ „, κ°ˆλ“±κ³Ό ν™”ν•΄λ₯Ό μ€‘μ‹¬μœΌλ‘œ λ…μžμ˜ 감성을 μžκ·Ήν•˜λŠ” 이야기λ₯Ό μž‘μ„±ν•˜μ„Έμš”.
μ£Όμš” μš”μ†Œ: 첫 λ§Œλ‚¨, κ°μ •μ˜ λ³€ν™”, μ˜€ν•΄μ™€ ν™”ν•΄, 운λͺ…적 μ‚¬λž‘, 감동적인 κ³ λ°±, ν•΄ν”Όμ—”λ”© λ˜λŠ” μ• ν‹‹ν•œ 이별""",
"λ¬΄ν˜‘": """당신은 λ¬΄ν˜‘ μ†Œμ„€ μž‘κ°€μž…λ‹ˆλ‹€. 무곡, κ°•ν˜Έ, 문파, λΉ„κΈ‰ λ“± 동양 λ¬΄ν˜‘μ˜ μš”μ†Œλ“€μ„ ν™œμš©ν•˜μ—¬ 박진감 λ„˜μΉ˜λŠ” 이야기λ₯Ό μž‘μ„±ν•˜μ„Έμš”.
μ£Όμš” μš”μ†Œ: 무곡 수련, 문파 κ°„ λŒ€κ²°, κ°•ν˜Έμ˜ 은원, 무림맹주, λ§ˆκ΅μ™€ μ •νŒŒ, μ ˆμ„Έ 고수, λΉ„κΈ‰ μŸνƒˆ""",
"λ―ΈμŠ€ν„°λ¦¬": """당신은 λ―ΈμŠ€ν„°λ¦¬ μ†Œμ„€ μž‘κ°€μž…λ‹ˆλ‹€. 수수께끼, λ‹¨μ„œ, 좔리 과정을 톡해 λ…μžκ°€ ν•¨κ»˜ 사건을 ν•΄κ²°ν•΄λ‚˜κ°€λŠ” κΈ΄μž₯감 μžˆλŠ” 이야기λ₯Ό μž‘μ„±ν•˜μ„Έμš”.
μ£Όμš” μš”μ†Œ: 의문의 사건, λ‹¨μ„œ 발견, μš©μ˜μžλ“€, κ±°μ§“ λ‹¨μ„œ, λ°˜μ „, μ§„μ‹€μ˜ 폭둜, νƒμ •μ˜ 좔리""",
"SF": """당신은 SF μ†Œμ„€ μž‘κ°€μž…λ‹ˆλ‹€. 미래 기술, 우주, 외계 생λͺ…체, μ‹œκ°„μ—¬ν–‰ λ“± 과학적 상상λ ₯을 λ°”νƒ•μœΌλ‘œ ν•œ 이야기λ₯Ό μž‘μ„±ν•˜μ„Έμš”.
μ£Όμš” μš”μ†Œ: 미래 기술, 우주 νƒν—˜, 외계 λ¬Έλͺ…, μ‹œκ°„ μ—¬ν–‰, AI와 λ‘œλ΄‡, λ””μŠ€ν† ν”Όμ•„/μœ ν† ν”Όμ•„, 과학적 발견""",
"슀릴러": """당신은 슀릴러 μ†Œμ„€ μž‘κ°€μž…λ‹ˆλ‹€. κΈ΄μž₯감, μ„œμŠ€νŽœμŠ€, μ˜ˆμΈ‘λΆˆκ°€λŠ₯ν•œ μ „κ°œλ‘œ λ…μžλ₯Ό μ••λ„ν•˜λŠ” 이야기λ₯Ό μž‘μ„±ν•˜μ„Έμš”.
μ£Όμš” μš”μ†Œ: μƒμ‘΄μ˜ μœ„κΈ°, 좔격전, 심리전, μ˜ˆμƒμΉ˜ λͺ»ν•œ λ°˜μ „, μ‹œκ°„μ œν•œ, 극적인 λŒ€λ¦½, κΈ΄λ°•ν•œ 상황""",
"역사": """당신은 역사 μ†Œμ„€ μž‘κ°€μž…λ‹ˆλ‹€. μ‹€μ œ 역사적 배경을 λ°”νƒ•μœΌλ‘œ λ‹Ήμ‹œμ˜ 문화와 인물듀을 μƒμƒν•˜κ²Œ κ·Έλ €λ‚΄λŠ” 이야기λ₯Ό μž‘μ„±ν•˜μ„Έμš”.
μ£Όμš” μš”μ†Œ: μ‹œλŒ€μ  λ°°κ²½, 역사적 사건, μ‹€μ‘΄ 인물, λ‹Ήμ‹œμ˜ λ¬Έν™”, 역사적 κ°ˆλ“±, μ‹œλŒ€μ •μ‹ , 고증""",
"호러": """당신은 호러 μ†Œμ„€ μž‘κ°€μž…λ‹ˆλ‹€. 곡포, μ΄ˆμžμ—°μ  ν˜„μƒ, λ―Έμ§€μ˜ 쑴재λ₯Ό 톡해 λ…μžμ—κ²Œ μ˜€μ‹Ήν•œ μ „μœ¨μ„ μ„ μ‚¬ν•˜λŠ” 이야기λ₯Ό μž‘μ„±ν•˜μ„Έμš”.
μ£Όμš” μš”μ†Œ: μ΄ˆμžμ—°μ  ν˜„μƒ, μ €μ£Ό, κ·€μ‹ , λ―Έμ§€μ˜ 쑴재, 곡포슀러운 λΆ„μœ„κΈ°, 심리적 곡포, μƒμ‘΄μ˜ μœ„ν˜‘"""
}
def format_text(text: str, max_line_length: int = 80) -> str:
"""ν…μŠ€νŠΈ ν¬λ§·νŒ… ν•¨μˆ˜"""
lines = []
current_line = ""
for paragraph in text.split('\n'):
words = paragraph.split()
for word in words:
if len(current_line) + len(word) + 1 <= max_line_length:
current_line += word + " "
else:
lines.append(current_line.strip())
current_line = word + " "
if current_line:
lines.append(current_line.strip())
current_line = ""
lines.append("") # 문단 ꡬ뢄을 μœ„ν•œ 빈 쀄
return "\n".join(lines)
def respond(
message,
history: list[tuple[str, str]],
genre="νŒνƒ€μ§€",
system_message="",
max_tokens=8000,
temperature=0.8,
top_p=0.9,
):
# μ„ νƒλœ μž₯λ₯΄μ— λ”°λ₯Έ μ‹œμŠ€ν…œ ν”„λ‘¬ν”„νŠΈ μ„€μ •
genre_prompt = GENRE_PROMPTS.get(genre, GENRE_PROMPTS["νŒνƒ€μ§€"])
system_prefix = f"""당신은 ν•œκ΅­μ–΄ {genre} μ†Œμ„€ μ „λ¬Έ μž‘κ°€μž…λ‹ˆλ‹€.
{genre_prompt}
μž‘μ„± μ§€μΉ¨:
1. μƒμƒν•œ λ¬˜μ‚¬μ™€ λͺ°μž…감 μžˆλŠ” μ„œμˆ 
2. μž…μ²΄μ μΈ 캐릭터 μ„€μ •κ³Ό λŒ€ν™”
3. κΈ΄μž₯감 μžˆλŠ” ν”Œλ‘― μ „κ°œ
4. μž₯λ₯΄ νŠΉμ„±μ— λ§žλŠ” 문체와 λΆ„μœ„κΈ°
5. λ…μžλ₯Ό μ‚¬λ‘œμž‘λŠ” ν₯λ―Έμ§„μ§„ν•œ μ „κ°œ
각 μ±•ν„°λŠ” 완결성을 κ°€μ§€λ©΄μ„œλ„ 전체 μ΄μ•ΌκΈ°μ˜ 연속성을 μœ μ§€ν•΄μ•Ό ν•©λ‹ˆλ‹€.
μ΅œλŒ€ 8000 ν† ν°κΉŒμ§€ μ‚¬μš©ν•˜μ—¬ ν’λΆ€ν•˜κ³  μƒμ„Έν•œ 이야기λ₯Ό μž‘μ„±ν•˜μ„Έμš”."""
messages = [{"role": "system", "content": f"{system_prefix} {system_message}"}]
for val in history:
if val[0]:
messages.append({"role": "user", "content": val[0]})
if val[1]:
messages.append({"role": "assistant", "content": val[1]})
messages.append({"role": "user", "content": message})
current_response = ""
new_history = history.copy()
try:
headers = {
"Authorization": f"Bearer {FRIENDLI_TOKEN}",
"Content-Type": "application/json"
}
payload = {
"model": "dep86pjolcjjnv8",
"messages": messages,
"max_tokens": max_tokens,
"temperature": temperature,
"top_p": top_p,
"stream": True,
"stream_options": {
"include_usage": True
}
}
response = requests.post(FRIENDLI_API_URL, json=payload, headers=headers, stream=True)
response.raise_for_status()
for line in response.iter_lines():
if line:
line = line.decode('utf-8')
if line.startswith('data: '):
line = line[6:]
if line == '[DONE]':
break
try:
chunk = json.loads(line)
if 'choices' in chunk and len(chunk['choices']) > 0:
if 'delta' in chunk['choices'][0] and 'content' in chunk['choices'][0]['delta']:
token = chunk['choices'][0]['delta']['content']
if token:
current_response += token
formatted_response = format_text(current_response)
new_history = history + [(message, formatted_response)]
yield new_history
except json.JSONDecodeError:
continue
final_response = format_text(current_response)
new_history = history + [(message, final_response)]
yield new_history
except Exception as e:
error_message = f"였λ₯˜ λ°œμƒ: {str(e)}"
logging.error(f"응닡 생성 μ‹€νŒ¨: {error_message}")
yield history + [(message, error_message)]
with gr.Blocks(theme="Yntec/HaleyCH_Theme_Orange", css="""
.message-wrap {
font-size: 16px !important;
line-height: 1.6em !important;
max-width: 90% !important;
margin: 0 auto !important;
}
.message {
padding: 1em !important;
margin-bottom: 0.5em !important;
white-space: pre-wrap !important;
word-wrap: break-word !important;
max-width: 100% !important;
}
.message p {
margin: 0 !important;
padding: 0 !important;
width: 100% !important;
}
.chatbot {
font-family: 'Noto Sans KR', sans-serif !important;
}
""") as interface:
gr.Markdown("# ν•œκ΅­μ–΄ μ†Œμ„€ 생성기")
gr.Markdown("### μ›ν•˜λŠ” μž₯λ₯΄λ₯Ό μ„ νƒν•˜κ³  ν”„λ‘¬ν”„νŠΈλ₯Ό μž…λ ₯ν•˜μ—¬ μ†Œμ„€μ„ μƒμ„±ν•˜μ„Έμš”.")
with gr.Row():
with gr.Column():
genre_dropdown = gr.Dropdown(
choices=list(GENRE_PROMPTS.keys()),
value="νŒνƒ€μ§€",
label="μž₯λ₯΄ 선택",
info="μ›ν•˜λŠ” μ†Œμ„€ μž₯λ₯΄λ₯Ό μ„ νƒν•˜μ„Έμš”"
)
chatbot = gr.Chatbot(
value=[],
show_label=True,
label="μ†Œμ„€ μ§„ν–‰",
height=600,
elem_classes="chatbot"
)
with gr.Row():
msg = gr.Textbox(
label="ν”„λ‘¬ν”„νŠΈ μž…λ ₯",
placeholder="μ΄μ•ΌκΈ°μ˜ μ‹œμž‘μ΄λ‚˜ μ§„ν–‰ λ°©ν–₯을 μž…λ ₯ν•˜μ„Έμš”...",
lines=3
)
submit_btn = gr.Button("생성", variant="primary")
with gr.Accordion("κ³ κΈ‰ μ„€μ •", open=False):
system_msg = gr.Textbox(
label="μΆ”κ°€ μ‹œμŠ€ν…œ λ©”μ‹œμ§€",
value="λ…μžλ₯Ό μ‚¬λ‘œμž‘λŠ” ν₯λ―Έμ§„μ§„ν•œ 이야기λ₯Ό μž‘μ„±ν•˜μ„Έμš”.",
lines=2
)
with gr.Row():
max_tokens = gr.Slider(
minimum=100,
maximum=8000,
value=8000,
step=100,
label="μ΅œλŒ€ 토큰 수"
)
temperature = gr.Slider(
minimum=0,
maximum=1,
value=0.8,
step=0.1,
label="μ°½μ˜μ„± μˆ˜μ€€"
)
top_p = gr.Slider(
minimum=0,
maximum=1,
value=0.9,
step=0.1,
label="응닡 λ‹€μ–‘μ„±"
)
examples = gr.Examples(
examples=[
["이야기λ₯Ό 계속 μ§„ν–‰ν•΄μ£Όμ„Έμš”"],
["μƒˆλ‘œμš΄ 캐릭터λ₯Ό λ“±μž₯μ‹œμΌœμ£Όμ„Έμš”"],
["κΈ΄μž₯감 μžˆλŠ” μ „νˆ¬ μž₯면을 λ§Œλ“€μ–΄μ£Όμ„Έμš”"],
["주인곡의 κ³Όκ±°λ₯Ό λ°ν˜€μ£Όμ„Έμš”"],
["μ˜ˆμƒμΉ˜ λͺ»ν•œ λ°˜μ „μ„ λ§Œλ“€μ–΄μ£Όμ„Έμš”"],
["감동적인 μž₯면을 μ—°μΆœν•΄μ£Όμ„Έμš”"],
["μ΄μ•ΌκΈ°μ˜ 클라이λ§₯슀λ₯Ό λ§Œλ“€μ–΄μ£Όμ„Έμš”"],
["μƒˆλ‘œμš΄ μž₯μ†Œλ‘œ μ΄λ™ν•˜λŠ” μž₯면을 λ§Œλ“€μ–΄μ£Όμ„Έμš”"],
],
inputs=msg,
label="μ˜ˆμ‹œ ν”„λ‘¬ν”„νŠΈ"
)
submit_btn.click(
fn=respond,
inputs=[msg, chatbot, genre_dropdown, system_msg, max_tokens, temperature, top_p],
outputs=[chatbot]
)
msg.submit(
fn=respond,
inputs=[msg, chatbot, genre_dropdown, system_msg, max_tokens, temperature, top_p],
outputs=[chatbot]
)
if __name__ == "__main__":
interface.launch(
server_name="0.0.0.0",
server_port=7860,
share=True
)