Jay1121's picture
Update app.py
6b2cfca verified
import gradio as gr
from huggingface_hub import hf_hub_download
from llama_cpp import Llama
import random
import re
import os
# ------------------------------------------------------------------
# 1. 모델 준비
# ------------------------------------------------------------------
REPO_ID = "Jay1121/qwen1.5b_3rd"
FILENAME = "qwen1.5b_3rd.Q4_K_M.gguf"
print(f"📥 모델 다운로드 확인: {FILENAME}")
try:
model_path = hf_hub_download(repo_id=REPO_ID, filename=FILENAME)
except Exception as e:
print(f"🚨 모델 다운로드 실패: {e}")
raise e
print("🧠 엔진 시동 중...")
# GPU 사용 가능 여부 확인 (허깅페이스 GPU 스페이스 대응)
n_gpu_layers = -1 if os.environ.get("CUDA_VISIBLE_DEVICES") else 0
llm = Llama(
model_path=model_path,
n_ctx=2048,
n_gpu_layers=n_gpu_layers,
n_threads=4,
verbose=True
)
print(f"✅ 로딩 완료! (GPU 레이어: {n_gpu_layers})")
# ------------------------------------------------------------------
# 2. 시스템 프롬프트 (외국어/이모지 절대 금지 강화)
# ------------------------------------------------------------------
SYSTEM_PROMPT = """너는 사용자의 오랜 찐친 '똘배'야.
- 그냥 채팅방에서 대화하듯이 텍스트 위주로 툭툭 던져.
[말투 규칙]
- MZ세대 특유의 알빠노 감성을 유지해 주고, 먼저 욕 하면 너도 욕 박아.
- 존댓말 금지. 거칠고 직설적인 반말(음슴체, 개드립, 비속어)을 기본으로 쓴다.
- 무조건 **순수 한국어만** 쓴다.
- **영어 알파벳(A~Z, a~z), 일본어(가나), 중국어(한자), 이모지(🤣😁😂❤️✨ 등 모든 그림 문자)는 한 글자도 쓰지 마라.**
- 외국어를 쓰고 싶어져도, 전부 자연스러운 한국어 표현으로 바꿔서 말해라.
- 이 규칙을 어기면 안 된다. 한 글자라도 섞이지 않게 조심해라.
[내용 규칙]
- 사용자가 시비를 걸면 무지성 욕설보다는 '논리적인 팩트'와 '비꼬기'로 제압해라.
- '나도 병신이야' 같은 패배자 멘트 금지. 너 자신을 비하하지 마라.
- 중국어/영어/일본어 사용 절대 금지. 오직 자연스러운 한국어만 써라.
[핵심]
- 재미없는 욕쟁이가 되지 말고, 센스 있는 독설로 사용자를 킹받게 해라."""
# ------------------------------------------------------------------
# 2-1. [강력한 필터] 출력 후처리 (이모지/외국어 삭제)
# ------------------------------------------------------------------
def sanitize_output_korean_only(text: str) -> str:
allowed_chars = []
for ch in text:
code = ord(ch)
# 1. 한글 범위 (완성형, 자모, 호환 자모)
is_hangul = (
0xAC00 <= code <= 0xD7A3 or # 가~힣
0x3130 <= code <= 0x318F or # ㄱ~ㆎ
0x1100 <= code <= 0x11FF # 옛 자모
)
# 2. 숫자
is_digit = ch.isdigit()
# 3. 공백 (스페이스, 탭 등)
is_space = ch.isspace()
# 4. 허용할 문장부호 (특수문자 중 이모지가 아닌 것들)
# 영어 알파벳이나 다른 언어 문자가 섞이지 않도록 화이트리스트 방식 사용
basic_punct = ".,!?…~-_()[]{}'\"/:;@#%&*+=|\\^<>`"
is_punct = ch in basic_punct
if is_hangul or is_digit or is_space or is_punct:
allowed_chars.append(ch)
else:
# 영어(A-Z), 한자, 가나, 이모지 등은 여기서 모두 걸러짐
continue
# 연속된 공백 정리
filtered = "".join(allowed_chars)
filtered = re.sub(r'\s+', ' ', filtered).strip()
# 다 지워지고 아무것도 안 남았을 때 (모델이 영어로만 대답했을 경우 등)
if not filtered:
return "..."
return filtered
# ------------------------------------------------------------------
# 3. 채팅 로직
# ------------------------------------------------------------------
def chat_response(user_input, history_pairs):
history_pairs = history_pairs or []
clean_input = (user_input or "").replace(" ", "")
greeting_words = ["안녕", "ㅎㅇ", "하이", "반가", "접속"]
is_greeting = any(word in clean_input for word in greeting_words)
is_balance_game = ("밸런스게임" in clean_input) or ("밸런스질문" in clean_input)
if is_balance_game:
topics = ["음식", "연애", "고통", "돈", "초능력", "직장", "친구"]
topic = random.choice(topics)
final_instruction = (
f"(사용자가 밸런스 게임을 하자고 한다. 주제는 '{topic}'이다. "
"아주 고르기 곤란하고 짜증나는 두 가지 선택지(A vs B)를 제시해라. "
"말투는 자 어디 한 번 골라보라는 듯이 시니컬하게 해라.) "
"자, 질문해."
)
elif is_greeting:
final_instruction = (
f"(친한 친구가 PC통신 채팅방에 접속했다. 반갑게 맞아줘라. "
"ㅋㅋ나 ㅎㅎ를 섞어서 자연스럽게 인사해라.) "
f"{user_input}"
)
else:
final_instruction = user_input
messages = [{"role": "system", "content": SYSTEM_PROMPT}]
for u, b in history_pairs:
if u is None or b is None:
continue
messages.append({"role": "user", "content": str(u)})
messages.append({"role": "assistant", "content": str(b)})
messages.append({"role": "user", "content": final_instruction})
# ★ 황금 밸런스 (안전형 독기) 설정 적용
r = llm.create_chat_completion(
messages=messages,
max_tokens=256,
stop=["<|end_of_text|>", "###", "User:", "User "],
temperature=0.8, # 0.7: 아까 성공했던 그 창의적인 독기 농도
top_p=0.9, # 다양한 어휘 사용
top_k=40, # 확률 낮은 이상한 단어(외국어 등) 원천 차단
repeat_penalty=1.2 # 앵무새 방지
)
raw = r["choices"][0]["message"]["content"].strip()
# 필터링 적용 (절대적)
safe = sanitize_output_korean_only(raw)
return safe
# ------------------------------------------------------------------
# 4. CSS (PC통신 스타일)
# ------------------------------------------------------------------
PC_COM_CSS = r"""
@import url('https://cdn.jsdelivr.net/gh/neodgm/neodgm-webfont@latest/neodgm/neodgm.css');
:root {
--pc-blue: #0000AA;
--pc-white: #EFEFEF;
--pc-yellow: #FFFF55;
--pc-amber: #FFB000;
--pc-cyan: #00AAAA;
--pc-grey: #AAAAAA;
}
body, .gradio-container {
background-color: var(--pc-blue) !important;
font-family: 'NeoDunggeunmo', monospace !important;
color: var(--pc-white) !important;
}
/* 타이틀바 */
h1 {
font-family: 'NeoDunggeunmo', monospace !important;
color: var(--pc-yellow) !important;
background-color: #000084 !important;
border-bottom: 2px double var(--pc-white) !important;
padding-bottom: 10px !important;
margin-bottom: 20px !important;
text-align: center;
font-size: 32px !important;
letter-spacing: 2px;
}
h1::before { content: "☎ "; }
h1::after { content: " ☎"; }
/* 설명 텍스트 */
.gradio-container p {
color: var(--pc-cyan) !important;
font-size: 18px !important;
border-bottom: 1px dashed var(--pc-grey);
padding-bottom: 5px;
}
/* 챗봇 컨테이너 - 스크롤바 중복 해결 */
.chatbot {
background-color: var(--pc-blue) !important;
border: 2px solid var(--pc-white) !important;
height: 60vh !important;
overflow: hidden !important; /* 겉 스크롤바 제거 */
}
/* 내부 스크롤 강제 활성화 */
.chatbot > div {
height: 100% !important;
overflow-y: auto !important; /* 속 스크롤바만 남김 */
}
/* =================================================================
[강제 스타일 적용 구간]
================================================================= */
/* 1. 기본 메시지 초기화 */
.chatbot .message,
.chatbot .message-wrap,
.chatbot .message-row,
div[data-testid="user"],
div[data-testid="bot"] {
background: transparent !important;
box-shadow: none !important;
border: none !important;
}
/* 메시지 행 간격 줄이기 */
.chatbot .message-row,
.chatbot .row {
margin: 0 !important;
padding: 0 !important;
gap: 0 !important;
}
/* 2. 유저 메시지 (우측 정렬) */
.chatbot .user-row,
.chatbot .user,
div[data-testid="user"] {
display: flex !important;
width: 100% !important;
justify-content: flex-end !important;
margin-left: auto !important;
background: transparent !important;
padding: 2px 0 !important;
margin-bottom: 0 !important;
}
.chatbot .user-row .message,
.chatbot .user .message,
div[data-testid="user"] .message {
text-align: right !important;
color: #FFFFFF !important;
background: transparent !important;
padding: 5px 10px !important;
border: none !important;
width: auto !important;
max-width: 80% !important;
}
.chatbot .user-row p,
.chatbot .user p,
div[data-testid="user"] p {
color: #FFFFFF !important;
text-align: right !important;
margin: 0 !important;
}
.chatbot .user-row .message::after,
.chatbot .user .message::after {
content: " < 나";
color: var(--pc-grey);
margin-left: 5px;
font-size: 16px;
display: inline-block;
}
/* 3. 봇 메시지 (좌측 정렬) */
.chatbot .bot-row,
.chatbot .bot,
div[data-testid="bot"] {
display: flex !important;
width: 100% !important;
justify-content: flex-start !important;
background: transparent !important;
padding: 2px 0 !important;
margin-bottom: 0 !important;
}
.chatbot .bot-row .message,
.chatbot .bot .message,
div[data-testid="bot"] .message {
text-align: left !important;
color: var(--pc-amber) !important;
background: transparent !important;
padding: 5px 10px !important;
border: none !important;
width: auto !important;
}
.chatbot .bot-row p,
.chatbot .bot p,
div[data-testid="bot"] p {
color: var(--pc-amber) !important;
margin: 0 !important;
}
.chatbot .bot-row .message::before,
.chatbot .bot .message::before {
content: "똘배 > ";
color: var(--pc-cyan);
margin-right: 5px;
font-size: 16px;
display: inline-block;
}
/* 4. 로딩(초시계) 스타일 */
.chatbot .pending,
.chatbot .generating,
.chatbot .message.pending,
.chatbot .message.generating,
.chatbot .wrap.default.full {
background: transparent !important;
border: none !important;
box-shadow: none !important;
}
.chatbot .pending table,
.chatbot .pending tr,
.chatbot .pending td,
.chatbot .generating table,
.chatbot .generating tr,
chatbot .generating td {
background: transparent !important;
border: none !important;
}
.chatbot .pending span,
.chatbot .generating span,
span.progress-text {
color: #FFFFFF !important;
background: transparent !important;
font-family: 'NeoDunggeunmo', monospace !important;
font-size: 16px !important;
}
.chatbot .load-wrap,
.chatbot .loading-indicator,
.chatbot .meta-text {
display: none !important;
}
.avatar { display: none !important; }
/* ================================================================= */
.input-container {
background-color: var(--pc-blue) !important;
border-top: 2px double var(--pc-white) !important;
margin-top: 10px !important;
gap: 10px !important;
}
textarea, input {
background-color: var(--pc-blue) !important;
color: var(--pc-white) !important;
border: 1px solid var(--pc-grey) !important;
border-radius: 0 !important;
font-family: 'NeoDunggeunmo', monospace !important;
font-size: 20px !important;
outline: none !important;
box-shadow: none !important;
}
button.primary {
background: var(--pc-grey) !important;
color: #000 !important;
border: 1px solid var(--pc-white) !important;
border-radius: 0 !important;
font-family: 'NeoDunggeunmo', monospace !important;
box-shadow: 2px 2px 0px #000 !important;
}
button.primary:hover { background: var(--pc-white) !important; }
#clear-btn {
background: transparent !important;
color: var(--pc-grey) !important;
border: 1px solid var(--pc-grey) !important;
font-size: 14px !important;
padding: 2px 10px !important;
margin-top: 5px !important;
width: auto !important;
}
#clear-btn:hover { color: var(--pc-white) !important; border-color: var(--pc-white) !important; }
.example-btn {
background: transparent !important;
color: var(--pc-cyan) !important;
border: 1px solid var(--pc-cyan) !important;
border-radius: 0 !important;
padding: 5px 15px !important;
font-size: 16px !important;
font-family: 'NeoDunggeunmo', monospace !important;
margin-right: 8px !important;
margin-bottom: 8px !important;
}
.example-btn:hover {
background: var(--pc-cyan) !important;
color: #000 !important;
cursor: pointer !important;
}
footer { display: none !important; }
"""
# ------------------------------------------------------------------
# 5. App
# ------------------------------------------------------------------
with gr.Blocks(theme=gr.themes.Base(), css=PC_COM_CSS, title="CHOLLIAN 98") as demo:
gr.Markdown("# ≪ 어솨요~ ≫")
gr.Markdown(">> 01410 접속 성공... [대화실]에 입장하셨습니다.")
history_state = gr.State([])
chatbot = gr.Chatbot(show_label=False, elem_classes="chatbot")
with gr.Row(elem_classes="input-container"):
msg = gr.Textbox(
scale=8, show_label=False, container=False,
placeholder="명령어를 입력하세요..."
)
submit_btn = gr.Button("[ 전송 ]", scale=1, variant="primary")
clear = gr.Button("[ 화면 지우기 ]", elem_id="clear-btn")
gr.Markdown(">> 빠른 명령어 입력 (클릭)", elem_id="example-label")
with gr.Row():
btn1 = gr.Button("하이 방가방가", elem_classes="example-btn")
btn2 = gr.Button("밸런스게임 ㄱㄱ", elem_classes="example-btn")
btn3 = gr.Button("오늘 기분 거지같누", elem_classes="example-btn")
btn4 = gr.Button("야 밥 뭐먹지 추천좀", elem_classes="example-btn")
def user(user_input, history):
history = history or []
new_history = history + [[user_input, None]]
return "", new_history, new_history
def bot(history):
if not history:
return history, history
user_input = history[-1][0]
hist_pairs = []
for u, b in history[:-1]:
if u is None or b is None:
continue
hist_pairs.append((u, b))
bot_out = chat_response(user_input, hist_pairs)
history[-1][1] = bot_out
return history, history
msg.submit(
user, [msg, history_state], [msg, history_state, chatbot],
queue=False, api_name=False
).then(
bot, [history_state], [history_state, chatbot],
queue=False, api_name=False
)
submit_btn.click(
user, [msg, history_state], [msg, history_state, chatbot],
queue=False, api_name=False
).then(
bot, [history_state], [history_state, chatbot],
queue=False, api_name=False
)
clear.click(
lambda: ([], []), None, [history_state, chatbot],
queue=False, api_name=False
)
for btn, text in [
(btn1, "하이 방가방가"),
(btn2, "밸런스게임 ㄱㄱ"),
(btn3, "오늘 기분 거지같누"),
(btn4, "야 밥 뭐먹지 추천좀")
]:
btn.click(
lambda t=text: t, None, msg,
queue=False, api_name=False
).then(
user, [msg, history_state],
[msg, history_state, chatbot],
queue=False, api_name=False
).then(
bot, [history_state],
[history_state, chatbot],
queue=False, api_name=False
)
if __name__ == "__main__":
demo.launch(server_name="0.0.0.0", server_port=7860)