Spaces:
Sleeping
Sleeping
| 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) | |
| # λ‘μ΄λΆ λ‘λ (μΈμ μ΄κΈ°ν μ μ νμ) | |
| 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("<", "<").replace(">", ">") | |
| 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.") |