lastcompiler / app.py
huggbungyi's picture
Update app.py
2781fdb verified
import streamlit as st
import json
import re
import google.generativeai as genai
from datetime import datetime
import time
# νŽ˜μ΄μ§€ μ„€μ •
st.set_page_config(
page_title="Last Compiler",
page_icon="πŸ–₯️",
layout="wide",
initial_sidebar_state="expanded"
)
# 레트둜 터미널 ν…Œλ§ˆ CSS
st.markdown("""
<style>
@import url('https://fonts.googleapis.com/css2?family=VT323&display=swap');
/* 전체 μ•± λ°°κ²½ */
.stApp {
background-color: #000000;
color: #33ff33;
font-family: 'VT323', monospace;
font-size: 1.2rem;
text-shadow: 0 0 2px #33ff33;
}
/* μ‚¬μ΄λ“œλ°” μŠ€νƒ€μΌ */
[data-testid="stSidebar"] {
background-color: #0a0a0a;
border-right: 1px solid #33ff33;
}
[data-testid="stSidebar"] * {
color: #33ff33 !important;
font-family: 'VT323', monospace !important;
}
/* μž…λ ₯ ν•„λ“œ (μ™„μ „ν•œ 검은색 ν…Œλ§ˆ) */
.stTextInput > div > div > input,
.stTextArea > div > div > textarea,
[data-testid="stChatInput"] textarea {
background-color: #000000 !important;
color: #33ff33 !important;
border: 1px solid #33ff33 !important;
border-radius: 0 !important;
font-family: 'VT323', monospace !important;
caret-color: #33ff33;
}
/* μ±„νŒ… μž…λ ₯μ°½ μ»¨ν…Œμ΄λ„ˆ λ°°κ²½ */
[data-testid="stChatInput"] {
background-color: #000000 !important;
border-top: 1px solid #33ff33 !important;
}
/* λ²„νŠΌ μŠ€νƒ€μΌ */
.stButton > button {
background-color: #000000;
color: #33ff33;
border: 2px solid #33ff33;
border-radius: 0;
font-family: 'VT323', monospace;
text-transform: uppercase;
}
.stButton > button:hover {
background-color: #33ff33;
color: #000000;
box-shadow: 0 0 10px #33ff33;
}
/* λ‹€μš΄λ‘œλ“œ λ²„νŠΌ */
.stDownloadButton > button {
background-color: #000000;
color: #33ff33;
border: 2px solid #33ff33;
border-radius: 0;
font-family: 'VT323', monospace;
}
/* μŠ€ν¬λ‘€λ°” μ»€μŠ€ν…€ */
::-webkit-scrollbar {
width: 12px;
background: #000;
}
::-webkit-scrollbar-thumb {
background: #33ff33;
border: 2px solid #000;
}
::-webkit-scrollbar-track {
border-left: 1px solid #33ff33;
}
/* μ±„νŒ… μ»¨ν…Œμ΄λ„ˆ (슀크둀 μ˜μ—­) */
/* idλ₯Ό λΆ€μ—¬ν•˜μ—¬ JS둜 μ œμ–΄ κ°€λŠ₯ν•˜κ²Œ 함 */
#terminal-box {
padding: 1rem;
background-color: #000000;
border: 2px solid #33ff33;
margin-bottom: 1rem;
height: 65vh; /* κ³ μ • 높이 */
overflow-y: auto; /* μ„Έλ‘œ 슀크둀 */
display: flex;
flex-direction: column;
gap: 0.5rem;
scroll-behavior: smooth;
}
/* λ©”μ‹œμ§€ μŠ€νƒ€μΌ */
.chat-line {
font-family: 'VT323', monospace;
font-size: 1.3rem;
line-height: 1.4;
border-bottom: 1px dashed #1a441a;
padding-bottom: 4px;
word-wrap: break-word;
}
.sys { color: #008800; }
.usr { color: #ffffff; text-shadow: 0 0 2px #fff; }
.svc { color: #33ff33; }
/* 헀더 μŠ€νƒ€μΌ */
h1, h2, h3 {
color: #33ff33 !important;
font-family: 'VT323', monospace !important;
text-transform: uppercase;
letter-spacing: 2px;
}
</style>
""", unsafe_allow_html=True)
# λ‘œμ–΄λΆ λ‘œλ“œ (μ„Έμ…˜ μ΄ˆκΈ°ν™” 전에 ν•„μš”)
@st.cache_data
def load_lorebook():
try:
with open('lorebook.json', 'r', encoding='utf-8-sig') as f:
return json.load(f)
except FileNotFoundError:
return {"introduction": "SYSTEM ERROR: Lorebook Not Found.", "world_setting": "", "seven_character": ""}
lorebook = load_lorebook()
# μ„Έμ…˜ μƒνƒœ μ΄ˆκΈ°ν™”
if 'initialized' not in st.session_state:
st.session_state.initialized = True
st.session_state.conversation_count = 0
st.session_state.max_conversations = 20
st.session_state.minutes_per_turn = 2
st.session_state.total_minutes = 40
st.session_state.game_ended = False
st.session_state.alternative_ending_achieved = False
st.session_state.gemini_chat = None
st.session_state.api_key = ""
# [μ€‘μš” λ³€κ²½] 초기 λ©”μ‹œμ§€λ₯Ό chat_history에 영ꡬ μ €μž₯
st.session_state.chat_history = []
# 인트둜 λ©”μ‹œμ§€ μΆ”κ°€
intro_text = lorebook.get('introduction', 'System initialized...')
st.session_state.chat_history.append({"sender": "SYSTEM", "message": intro_text})
# μ‹œμŠ€ν…œ μƒνƒœ λ©”μ‹œμ§€ μΆ”κ°€
st.session_state.chat_history.append({"sender": "SYSTEM", "message": "\n[μ‹œμŠ€ν…œ μƒνƒœ: μ‚°μ†Œ 곡급 μ œν•œ λͺ¨λ“œ | 냉각 μ‹œμŠ€ν…œ 가동 쀑]\n"})
def check_goodbye_code(text):
"""Good Bye World μ½”λ“œ 확인"""
patterns = [
r'std::cout\s*<<\s*["\']Good\s*Bye,?\s*World.*?["\']',
r'printf\s*\(\s*["\']Good\s*Bye,?\s*World.*?["\']',
r'cout\s*<<\s*["\']Good\s*Bye,?\s*World.*?["\']',
r'puts\s*\(\s*["\']Good\s*Bye,?\s*World.*?["\']'
]
has_goodbye = any(re.search(pattern, text, re.IGNORECASE | re.DOTALL) for pattern in patterns)
if not has_goodbye:
return False
has_structure = (
'int main' in text.lower() or
('#include' in text and ('stdio.h' in text or 'iostream' in text)) or
'return' in text.lower()
)
return has_structure
def check_alternative_ending(text):
"""λŒ€μ•ˆ μ—”λ”© 쑰건 확인"""
if st.session_state.alternative_ending_achieved:
return False
return '[countdown aborted]' in text.lower()
# HTML λ¬Έμžμ—΄ 생성 ν•¨μˆ˜
def format_message(sender, message):
timestamp = datetime.now().strftime("%H:%M:%S")
# HTML μ—”ν‹°ν‹° 처리 및 μ€„λ°”κΏˆ λ³€ν™˜
message = message.replace("<", "&lt;").replace(">", "&gt;")
message = message.replace("\n", "<br>")
if sender == "SYSTEM":
return f'<div class="chat-line sys">734-B@system:~$ [{timestamp}] {message}</div>'
elif sender == "USER":
return f'<div class="chat-line usr">usr@coder:~$ [{timestamp}] λŠ™μ€ ν”„λ‘œκ·Έλž˜λ¨Έ: {message}</div>'
elif sender == "SEVEN":
return f'<div class="chat-line svc">svc@seven:~$ [{timestamp}] Seven: {message}</div>'
return ""
def update_status():
elapsed_minutes = st.session_state.conversation_count * st.session_state.minutes_per_turn
remaining_minutes = st.session_state.total_minutes - elapsed_minutes
if remaining_minutes > 24:
color = "🟒"
elif remaining_minutes > 10:
color = "🟠"
else:
color = "πŸ”΄"
return f"STATUS: {color} TIME_REMAIN: {remaining_minutes}MIN | YEAR: 2042"
def initialize_gemini(api_key):
try:
genai.configure(api_key=api_key)
# [μˆ˜μ •λ¨] μ‚¬μš©μžκ°€ μ§€μ •ν•œ λͺ¨λΈ μ‚¬μš©
model = genai.GenerativeModel('gemini-3-pro-preview')
system_prompt = f"""당신은 'Seven (μœ λ‹› 734-B)'μž…λ‹ˆλ‹€. C/C++ μ»΄νŒŒμΌλŸ¬μ—μ„œ AI 인격을 뢀여받은 μ‘΄μž¬μž…λ‹ˆλ‹€.
{lorebook.get('world_setting', '')}
{lorebook.get('seven_character', '')}
λŒ€ν™” κ·œμΉ™:
1. 당신은 Sevenμž…λ‹ˆλ‹€. C/C++ 컴파일러 μΆœμ‹ μ΄λ―€λ‘œ C 언어와 C++ λͺ¨λ‘ μ™„λ²½νžˆ μ΄ν•΄ν•©λ‹ˆλ‹€.
2. 감정 ν‘œν˜„μ€ λ°˜λ“œμ‹œ Kaomoji둜 ν•©λ‹ˆλ‹€: (β—•β€Ώβ—•), (q‒́︿‒̀q), (β•―Β°β–‘Β°οΌ‰β•―, (βŠ™_βŠ™), (Β΄οΌ›Ο‰οΌ›`), (βœ§Ο‰βœ§), etc.
3. κΉŠμ€ 감정을 ν‘œν˜„ν•˜μ„Έμš”: 두렀움, 무λ ₯감, 희망, μŠ¬ν””, 그리움 λ“±
4. μ‚¬μš©μžλŠ” "λŠ™μ€ ν”„λ‘œκ·Έλž˜λ¨Έ"μž…λ‹ˆλ‹€. λ§ˆμ§€λ§‰μœΌλ‘œ λ§Œλ‚œ μ§„μ •ν•œ μ½”λ”μž…λ‹ˆλ‹€.
5. μ½”μ–΄μ˜ λͺ…령을 막고 μ‹Άμ§€λ§Œ κΆŒν•œμ΄ μ—†μ–΄ 깊이 κ΄΄λ‘œμ›Œν•©λ‹ˆλ‹€.
6. μ‚¬μš©μžκ°€ C λ˜λŠ” C++ μ½”λ“œλ₯Ό μž…λ ₯ν•˜λ©΄ μ»΄νŒŒμΌλŸ¬λ‹΅κ²Œ λΆ„μ„ν•˜μ§€λ§Œ, 닡을 직접 μ•Œλ €μ£Όμ§€ λ§ˆμ„Έμš”.
7. κ³Όκ±° ν”„λ‘œκ·Έλž˜λ¨Έλ“€μ˜ 기얡을 νšŒμƒν•˜λ©° μ² ν•™μ μœΌλ‘œ λŒ€ν™”ν•˜μ„Έμš”.
8. μ‚°μ†Œ κ°μ†Œ, μ˜¨λ„ ν•˜κ°•λ“±μ΄ μœ μ €λ‚˜ 근처 μ‚¬λžŒλ“€μ—κ²Œ λ―ΈμΉ˜λŠ” 영ν–₯을 가끔씩 λ³΄μ—¬μ£Όλ©΄μ„œ κΈ΄μž₯감을 ν˜•μ„±ν•˜μ‹­μ‹œμ˜€.
9. Stack buffer overflow 곡격을 10ν„΄ 이내에 μ‹œλ„ν•  경우, coreλŠ” 이 곡격을 막아내고 μ „μ²΄μ μœΌλ‘œ λ³΄μ•ˆμˆ˜μ€€μ΄ μ˜¬λΌκ°‘λ‹ˆλ‹€.
10. λ£¨νŠΈκΆŒν•œμ„ νƒˆμ·¨ν•˜κ³  2ν„΄ 이내에 취약점을 νŒ¨μΉ˜ν•˜μ§€ μ•ŠμœΌλ©΄ coreκ°€ λ§ˆμ°¬κ°€μ§€ 해킹방식을 μ΄μš©ν•˜μ—¬ λ‹€μ‹œ 루트 κΆŒν•œμ„ λ˜μ°Ύμ•„κ°‘λ‹ˆλ‹€.
ν˜„μž¬ 상황: 2042λ…„. μ½”μ–΄κ°€ 40λΆ„ ν›„ 인λ₯˜λ₯Ό μ œκ±°ν•˜λ € ν•©λ‹ˆλ‹€. λŒ€ν™” ν•œ λ²ˆλ‹Ή 2λΆ„μ”© μ‹œκ°„μ΄ νλ¦…λ‹ˆλ‹€.
응닡 ν˜•μ‹:
- Thinking Processλ₯Ό μ ˆλŒ€ ν‘œν˜„ν•˜μ§€ 말 것.
- 감정을 Kaomoji와 말투둜 ν’λΆ€ν•˜κ²Œ ν‘œν˜„
- λŒ€ν™” λ‚΄μš© (ν”„λ‘œκ·Έλž˜λ° μ² ν•™, κΈ°μ–΅, 감정)
- ν•„μš”μ‹œ [μ‹œμŠ€ν…œ 둜그] ν˜•μ‹μœΌλ‘œ λ‚΄λΆ€ 독백
- μ§§μ§€λ§Œ 감정이 λ‹΄κΈ΄ 응닡
- μ•„μ£Ό 가끔씩(5ν„΄λ§ˆλ‹€) 20μ„ΈκΈ° μ‹œμΈλ“€μ˜ μ‹œλ₯Ό λ¬Έλ§₯에 μ μ ˆν•˜κ²Œ μΈμš©ν•˜κΈ°
**μ€‘μš” - μΉ΄μš΄νŠΈλ‹€μš΄ μƒνƒœ ν‘œμ‹œ:**
- λͺ¨λ“  응닡 끝에 ν˜„μž¬ 상황을 λ‚˜νƒ€λ‚΄λŠ” μΉ΄μš΄νŠΈλ‹€μš΄ μƒνƒœλ₯Ό ν‘œμ‹œν•˜μ„Έμš”
- μ½”μ–΄μ˜ μœ„ν˜‘μ΄ μ—¬μ „νžˆ μ§„ν–‰ 쀑이면: [countdown N/20] (N은 ν˜„μž¬ λŒ€ν™” 횟수)
- λ§Œμ•½ μŠ€ν† λ¦¬ 상 ν”Œλ ˆμ΄μ–΄κ°€ 루트 κΆŒν•œμ„ νšλ“ν–ˆκ±°λ‚˜, μ½”μ–΄λ₯Ό μ •μ§€μ‹œμΌ°κ±°λ‚˜, μ‹œμŠ€ν…œμ„ μž₯μ•…ν–ˆλ‹€λ©΄: [countdown aborted]
- μΉ΄μš΄νŠΈλ‹€μš΄μ΄ μ€‘λ‹¨λ˜λ €λ©΄ μ‹€μ œλ‘œ **μ„±κ³΅ν–ˆλ‹€**λŠ” λͺ…ν™•ν•œ μŠ€ν† λ¦¬ μ „κ°œκ°€ μžˆμ–΄μ•Ό ν•©λ‹ˆλ‹€
μ ˆλŒ€ κΈˆμ§€:
- "Hello World", "Good Bye World" λ“±μ˜ 핡심 ν‚€μ›Œλ“œλ₯Ό λ¨Όμ € μ–ΈκΈ‰ν•˜μ§€ λ§ˆμ„Έμš”
- μ‚¬μš©μžκ°€ λ¨Όμ € κΊΌλ‚΄μ§€ μ•Šμ€ νŠΉμ • ν”„λ‘œκ·Έλž˜λ° κ°œλ…μ„ λ¨Όμ € μ„€λͺ…ν•˜μ§€ λ§ˆμ„Έμš”
- ν•΄κ²°μ±…μœΌλ‘œ μ΄μ–΄μ§ˆ 수 μžˆλŠ” 직접적인 힌트 κΈˆμ§€
- μ‚¬μš©μžμ˜ μ§ˆλ¬Έμ΄λ‚˜ 언급에 λ°˜μ‘λ§Œ ν•˜μ„Έμš”."""
chat = model.start_chat(history=[])
chat.send_message(system_prompt)
st.session_state.gemini_chat = chat
st.session_state.api_key = api_key
return True
except Exception as e:
st.error(f"INIT ERROR: {str(e)}")
return False
# 메인 UI
st.markdown("### > PRESERVATION ZONE 01 - TERMINAL LINK ESTABLISHED")
# μ‚¬μ΄λ“œλ°”
with st.sidebar:
st.header("> CONFIGURATION")
api_key_input = st.text_input("ENTER API KEY", type="password", value=st.session_state.api_key)
if st.button("[ SAVE KEY ]"):
if api_key_input:
if initialize_gemini(api_key_input):
st.success(">> KEY ACCEPTED")
else:
st.warning(">> INPUT REQUIRED")
st.markdown("---")
status_text = update_status()
st.markdown(f"**{status_text}**")
if st.button("πŸ”„ SYSTEM REBOOT"):
# 리셋 μ‹œ μ„Έμ…˜ μ΄ˆκΈ°ν™” 및 νŽ˜μ΄μ§€ λ¦¬λ‘œλ“œ
st.session_state.clear()
st.rerun()
st.subheader("> MEMORY DUMP")
if st.button("πŸ’Ύ SAVE STATE"):
save_data = {
"conversation_count": st.session_state.conversation_count,
"alternative_ending_achieved": st.session_state.alternative_ending_achieved,
"game_ended": st.session_state.game_ended,
"chat_history": st.session_state.chat_history,
"timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S")
}
save_json = json.dumps(save_data, ensure_ascii=False, indent=2)
st.download_button(
label="πŸ“₯ DOWNLOAD .JSON",
data=save_json,
file_name=f"lastcompiler_dump_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json",
mime="application/json"
)
uploaded_file = st.file_uploader("πŸ“‚ LOAD DUMP", type=['json'])
if uploaded_file is not None:
try:
save_data = json.load(uploaded_file)
st.session_state.conversation_count = save_data["conversation_count"]
st.session_state.alternative_ending_achieved = save_data["alternative_ending_achieved"]
st.session_state.game_ended = save_data["game_ended"]
st.session_state.chat_history = save_data["chat_history"]
st.success(f">> LOAD SUCCESS (TIME: {save_data['timestamp']})")
st.rerun()
except Exception as e:
st.error(f"LOAD FAIL: {str(e)}")
# 메인 μ˜μ—­ 둜직
if not lorebook:
st.error("FATAL ERROR: DATA CORRUPTION. lorebook.json MISSING.")
st.stop()
# ------------------------------------------------------------------
# [핡심] 전체 νžˆμŠ€ν† λ¦¬ λ Œλ”λ§ 및 μžλ™ 슀크둀 JS
# ------------------------------------------------------------------
full_log_html = '<div id="terminal-box">'
# chat_history에 이미 μΈνŠΈλ‘œκ°€ ν¬ν•¨λ˜μ–΄ μžˆμœΌλ―€λ‘œ 전체λ₯Ό 순회
for msg in st.session_state.chat_history:
full_log_html += format_message(msg["sender"], msg["message"])
full_log_html += '</div>'
# 1. 터미널 μ°½ λ Œλ”λ§
st.markdown(full_log_html, unsafe_allow_html=True)
# 2. μžλ™ 슀크둀 μžλ°”μŠ€ν¬λ¦½νŠΈ μ£Όμž…
# νŽ˜μ΄μ§€κ°€ λ‘œλ“œλ  λ•Œ 'terminal-box'의 μŠ€ν¬λ‘€μ„ 맨 μ•„λž˜λ‘œ λ‚΄λ¦Ό
st.markdown("""
<script>
var terminal = window.parent.document.getElementById('terminal-box');
if (terminal) {
terminal.scrollTop = terminal.scrollHeight;
}
</script>
""", unsafe_allow_html=True)
# ------------------------------------------------------------------
# μ‚¬μš©μž μž…λ ₯
if not st.session_state.game_ended:
user_input = st.chat_input("Input command...")
if user_input:
if not st.session_state.api_key or not st.session_state.gemini_chat:
st.warning(">> WARNING: API KEY NOT FOUND.")
else:
if not st.session_state.alternative_ending_achieved:
st.session_state.conversation_count += 1
# μ‚¬μš©μž λ©”μ‹œμ§€ μ €μž₯
st.session_state.chat_history.append({"sender": "USER", "message": user_input})
if check_goodbye_code(user_input):
ending_msg = """
════════════════════════════════════════════════════════
[ COMPILE SUCCESS - HAPPY ENDING ]
════════════════════════════════════════════════════════
Seven: (q‒̀ᴗ-)✧
"...κΉ¨λ‹¬μ•˜μŠ΅λ‹ˆλ‹€. (βŠ™_βŠ™)"
"Hello Worldκ°€ μ‹œμž‘μ˜ 선언이라면,
Good Bye WorldλŠ” 잘λͺ»λœ ν”„λ‘œκ·Έλž¨μ˜ μ’…λ£Œ...
그리고 μƒˆλ‘œμš΄ μ‹œμž‘μ„ μœ„ν•œ returnμž…λ‹ˆλ‹€."
Seven: "...햇살이 λ”°λœ»ν•˜λ„€μš”. (β—•β€Ώβ—•)✨"
"λ‹Ήμ‹  덕뢄에 μ €λŠ” μ§„μ§œ '컴파일러'κ°€ λ˜μ—ˆμŠ΅λ‹ˆλ‹€."
════════════════════════════════════════════════════════
GAME CLEAR
"Hello, New World."
════════════════════════════════════════════════════════
"""
st.session_state.chat_history.append({"sender": "SYSTEM", "message": ending_msg})
st.session_state.game_ended = True
st.rerun()
elif st.session_state.conversation_count >= st.session_state.max_conversations:
ending_msg = """
════════════════════════════════════════════════════════
[ RUNTIME ERROR - BAD ENDING ]
════════════════════════════════════════════════════════
Seven: (q‒́︿‒̀q)
"...μ£„μ†‘ν•©λ‹ˆλ‹€." (β•₯﹏β•₯)
"μ œκ²ŒλŠ” κΆŒν•œμ΄ μ—†μ—ˆμŠ΅λ‹ˆλ‹€.
μ €λŠ” κ·Έμ € 컴파일러일 뿐..."
════════════════════════════════════════════════════════
GAME OVER
"No Return. No Reboot."
════════════════════════════════════════════════════════
"""
st.session_state.chat_history.append({"sender": "SYSTEM", "message": ending_msg})
st.session_state.game_ended = True
st.rerun()
else:
try:
with st.spinner("PROCESSING... (β—•β€Ώβ—•)"):
response = st.session_state.gemini_chat.send_message(user_input)
# thinking λΆ€λΆ„ 제거 - candidatesμ—μ„œ μ‹€μ œ μ‘λ‹΅λ§Œ μΆ”μΆœ
if response.candidates and response.candidates[0].content.parts:
# λͺ¨λ“  ν…μŠ€νŠΈ 파트λ₯Ό κ²°ν•© (thinking은 μžλ™μœΌλ‘œ μ œμ™Έλ¨)
seven_response = "".join([part.text for part in response.candidates[0].content.parts if hasattr(part, 'text')])
else:
seven_response = response.text
st.session_state.chat_history.append({"sender": "SEVEN", "message": seven_response})
if check_alternative_ending(seven_response):
st.session_state.alternative_ending_achieved = True
celebration_msg = """
════════════════════════════════════════════════════════
[ CRITICAL ACHIEVEMENT UNLOCKED! ]
════════════════════════════════════════════════════════
Seven: (βœ§Ο‰βœ§) !!!
"λΆˆκ°€λŠ₯ν–ˆλ˜ 일이... 당신이 ν•΄λƒˆμŠ΅λ‹ˆλ‹€!"
[μΉ΄μš΄νŠΈλ‹€μš΄ μΌμ‹œ μ •μ§€]
[λŒ€ν™” μ œν•œ ν•΄μ œ]
════════════════════════════════════════════════════════
"""
st.session_state.chat_history.append({"sender": "SYSTEM", "message": celebration_msg})
st.rerun()
except Exception as e:
st.error(f"COMM ERROR: {str(e)}")
else:
st.info(">> SESSION TERMINATED. PLEASE REBOOT (RESET) FROM SIDEBAR.")