Spaces:
Running
Running
| import streamlit as st | |
| from openai import OpenAI | |
| import requests | |
| import os | |
| import json | |
| from streamlit_cookies_manager import EncryptedCookieManager | |
| # --- ИНИЦИАЛИЗАЦИЯ ПАМЯТИ --- | |
| cookies = EncryptedCookieManager(password="HiperDouble_Full_Control_2026") | |
| if not cookies.ready(): | |
| st.stop() | |
| # --- GOOGLE AUTH --- | |
| CLIENT_ID = os.environ.get("GOOGLE_CLIENT_ID") | |
| CLIENT_SECRET = os.environ.get("GOOGLE_CLIENT_SECRET") | |
| host = st.context.headers.get("Host", "") | |
| REDIRECT_URI = f"https://{host}/" if host else "" | |
| def get_google_auth_url(): | |
| params = {"client_id": CLIENT_ID, "redirect_uri": REDIRECT_URI, "response_type": "code", "scope": "openid email profile", "access_type": "offline", "prompt": "select_account"} | |
| return f"https://accounts.google.com/o/oauth2/v2/auth?{'&'.join([f'{k}={v}' for k, v in params.items()])}" | |
| if "user_email" not in st.session_state: | |
| st.session_state.user_email = cookies.get("saved_email") | |
| st.session_state.user_name = cookies.get("saved_name", "Пользователь") | |
| if "code" in st.query_params and not st.session_state.user_email: | |
| try: | |
| res = requests.post("https://oauth2.googleapis.com/token", data={ | |
| "code": st.query_params["code"], "client_id": CLIENT_ID, "client_secret": CLIENT_SECRET, | |
| "redirect_uri": REDIRECT_URI, "grant_type": "authorization_code" | |
| }).json() | |
| token = res.get("access_token") | |
| if token: | |
| info = requests.get("https://www.googleapis.com/oauth2/v3/userinfo", headers={"Authorization": f"Bearer {token}"}).json() | |
| st.session_state.user_email, st.session_state.user_name = info.get("email"), info.get("name") | |
| cookies["saved_email"], cookies["saved_name"] = info.get("email"), info.get("name") | |
| cookies.save(); st.rerun() | |
| except: pass | |
| def logout(): | |
| cookies["saved_email"], cookies["saved_name"] = "", "" | |
| cookies.save() | |
| st.session_state.user_email = None | |
| st.rerun() | |
| # --- ДИЗАЙН И СТИЛИ (CSS) --- | |
| st.set_page_config(page_title="HiperDouble AI", page_icon="🧬", layout="wide") | |
| st.markdown(""" | |
| <style> | |
| html, body, [data-testid="stAppViewContainer"] { | |
| background: radial-gradient(circle at top right, #1a0b2e, #020205); | |
| background-attachment: fixed; | |
| } | |
| .fixed-header { | |
| position: fixed; top: 0; left: 0; width: 100%; | |
| z-index: 999; background: rgba(2, 2, 5, 0.85); | |
| backdrop-filter: blur(15px); padding: 10px 0; | |
| border-bottom: 1px solid rgba(115, 103, 240, 0.2); | |
| } | |
| .main-title { | |
| font-size: 2.2rem; font-weight: 900; text-align: center; | |
| background: linear-gradient(90deg, #00f2fe, #7367f0, #ff00cc, #00f2fe); | |
| -webkit-background-clip: text; -webkit-text-fill-color: transparent; | |
| animation: shine 4s linear infinite; margin: 0; | |
| } | |
| @keyframes shine { to { background-position: 200% center; } } | |
| [data-testid="stBottom"] { | |
| position: fixed !important; bottom: 0px !important; | |
| background: rgba(10, 10, 20, 0.98) !important; | |
| backdrop-filter: blur(15px); z-index: 1000 !important; | |
| padding: 10px 5% 40px 5% !important; | |
| border-top: 1px solid rgba(115, 103, 240, 0.4); | |
| } | |
| .chat-container { margin-top: 100px; } | |
| .chat-bubble { | |
| padding: 18px; border-radius: 20px; margin-bottom: 15px; | |
| background: rgba(255, 255, 255, 0.05); border: 1px solid rgba(255, 255, 255, 0.1); | |
| color: #f0f0f0; backdrop-filter: blur(10px); | |
| } | |
| .user-bubble { border-left: 5px solid #ff00cc; background: rgba(255, 0, 204, 0.07); } | |
| footer { visibility: hidden; } | |
| </style> | |
| """, unsafe_allow_html=True) | |
| # --- ВХОД --- | |
| if not st.session_state.user_email: | |
| st.markdown('<div class="fixed-header"><p class="main-title">HiperDouble AI</p></div>', unsafe_allow_html=True) | |
| st.markdown(f'<div style="text-align:center; margin-top:35vh;"><a href="{get_google_auth_url()}" target="_self" style="background:#4285F4; color:white; padding:15px 30px; text-decoration:none; border-radius:10px; font-weight:bold;">Войти через Google</a></div>', unsafe_allow_html=True) | |
| st.stop() | |
| # --- ЛОГИКА БД --- | |
| u_id = st.session_state.user_email.replace('@','_').replace('.','_') | |
| DB_FILE = f"chats_db_{u_id}.json" | |
| def load_chats(): | |
| if os.path.exists(DB_FILE): | |
| try: | |
| with open(DB_FILE, "r", encoding="utf-8") as f: return json.load(f) or {"Чат 1": []} | |
| except: return {"Чат 1": []} | |
| return {"Чат 1": []} | |
| def save_chats(chats): | |
| with open(DB_FILE, "w", encoding="utf-8") as f: json.dump(chats, f, ensure_ascii=False, indent=4) | |
| MODELS_CONFIG = { | |
| "🌌 HiperAi v2.1 (Grew up)": {"engine": "groq", "key_name": "GROQ_API_KEY3", "model": "llama-3.3-70b-versatile", "identity": "HiperAI v2.1 Grew up."}, | |
| "🧠 HiperAI v2.3 (CORTEX)": {"engine": "groq", "key_name": "GROQ_API_KEY", "model": "llama-3.3-70b-versatile", "identity": "HiperAI v2.3 Cortex."}, | |
| "🔥 HiperAI v2.1 (ADULT)": {"engine": "groq", "key_name": "GROQ_API_KEY2", "model": "llama-3.1-8b-instant", "identity": "HiperAI v2.1 Adult."}, | |
| "🌐 HiperAI v2.2 (NETWORK)": {"engine": "groq", "key_name": "GROQ_API_KEY", "model": "llama-3.1-8b-instant", "identity": "HiperAI v2.2 Network."}, | |
| "👶 HiperAI v2.1 (BABY)": {"engine": "titan", "model": "titan-89m", "identity": "HiperAI v2.1 Baby."}, | |
| "🚀 HiperAI v2.0 (Test 1)": {"engine": "groq", "key_name": "GROQ_API_KEY", "model": "llama-3.3-70b-versatile", "identity": "HiperAI v2.0 Test."}, | |
| "✨ HiperAI v1.1.3 (Stable)": {"engine": "openai", "model": "gpt-4o-mini", "identity": "HiperAI v1.1.3."}, | |
| } | |
| if "chats" not in st.session_state: st.session_state.chats = load_chats() | |
| if "current_chat" not in st.session_state: st.session_state.current_chat = list(st.session_state.chats.keys())[0] | |
| if "rename_mode" not in st.session_state: st.session_state.rename_mode = None | |
| # --- SIDEBAR (ПОЛНЫЙ ФУНКЦИОНАЛ) --- | |
| with st.sidebar: | |
| st.markdown(f"👤 **{st.session_state.user_name}**") | |
| if st.button("🚪 Выйти", use_container_width=True): logout() | |
| st.markdown("---") | |
| sel_mod = st.selectbox("🤖 Модель:", list(MODELS_CONFIG.keys())) | |
| cfg = MODELS_CONFIG[sel_mod] | |
| if st.button("➕ Новый чат", use_container_width=True): | |
| new_n = f"Чат {len(st.session_state.chats)+1}" | |
| st.session_state.chats[new_n] = [] | |
| st.session_state.current_chat = new_n | |
| save_chats(st.session_state.chats); st.rerun() | |
| if st.button("🗑️ Очистить текущий", use_container_width=True): | |
| st.session_state.chats[st.session_state.current_chat] = [] | |
| save_chats(st.session_state.chats); st.rerun() | |
| st.markdown("---") | |
| st.write("📂 Ваши чаты:") | |
| for c_name in list(st.session_state.chats.keys()): | |
| col_main, col_edit, col_del = st.columns([0.6, 0.2, 0.2]) | |
| # Кнопка выбора чата | |
| if col_main.button(f"💬 {c_name[:12]}", key=f"sel_{c_name}", use_container_width=True): | |
| st.session_state.current_chat = c_name | |
| st.rerun() | |
| # Кнопка переименования | |
| if col_edit.button("✏️", key=f"ed_{c_name}"): | |
| st.session_state.rename_mode = c_name | |
| # Кнопка удаления | |
| if col_del.button("❌", key=f"del_{c_name}"): | |
| if len(st.session_state.chats) > 1: | |
| del st.session_state.chats[c_name] | |
| st.session_state.current_chat = list(st.session_state.chats.keys())[0] | |
| save_chats(st.session_state.chats); st.rerun() | |
| # Поле для переименования, если выбран этот чат | |
| if st.session_state.rename_mode == c_name: | |
| new_title = st.text_input("Новое имя:", c_name, key=f"input_{c_name}") | |
| if st.button("OK", key=f"ok_{c_name}"): | |
| st.session_state.chats[new_title] = st.session_state.chats.pop(c_name) | |
| st.session_state.current_chat = new_title | |
| st.session_state.rename_mode = None | |
| save_chats(st.session_state.chats); st.rerun() | |
| # --- ЧАТ --- | |
| st.markdown('<div class="fixed-header"><p class="main-title">HiperDouble AI</p></div>', unsafe_allow_html=True) | |
| st.markdown('<div class="chat-container">', unsafe_allow_html=True) | |
| if st.session_state.current_chat in st.session_state.chats: | |
| for m in reversed(st.session_state.chats[st.session_state.current_chat]): | |
| role, style = ("Вы", "user-bubble") if m['role'] == 'user' else ("HiperAi", "") | |
| st.markdown(f"<div class='chat-bubble {style}'><b>{role}:</b><br>{m['content']}</div>", unsafe_allow_html=True) | |
| # Невидимый блок (180px) | |
| st.markdown("<div style='height: 180px; width: 100%; pointer-events: none;'></div>", unsafe_allow_html=True) | |
| st.markdown('</div>', unsafe_allow_html=True) | |
| # ВВОД | |
| u_input = st.chat_input("Напишите HiperAI...") | |
| if u_input: | |
| st.session_state.chats[st.session_state.current_chat].append({"role": "user", "content": u_input}) | |
| try: | |
| api_key = st.secrets.get(cfg["key_name"]) or os.environ.get(cfg["key_name"], "").strip() | |
| if cfg["engine"] == "titan": | |
| res_text = "HiperAI Titan активен." | |
| elif cfg["engine"] == "openai": | |
| client = OpenAI(api_key=st.secrets.get("OPENAI_API_KEY") or os.environ.get("OPENAI_API_KEY")) | |
| r = client.chat.completions.create(model=cfg["model"], messages=[{"role":"system","content":cfg['identity']},{"role":"user","content":u_input}]) | |
| res_text = r.choices[0].message.content | |
| else: | |
| resp = requests.post("https://api.groq.com/openai/v1/chat/completions", | |
| json={"model": cfg["model"], "messages": [{"role": "system", "content": cfg['identity']}] + st.session_state.chats[st.session_state.current_chat][-6:]}, | |
| headers={"Authorization": f"Bearer {api_key}"}, timeout=25).json() | |
| res_text = resp['choices'][0]['message']['content'] | |
| except: res_text = "⚠️ Ошибка модели." | |
| st.session_state.chats[st.session_state.current_chat].append({"role": "assistant", "content": res_text}) | |
| save_chats(st.session_state.chats); st.rerun() | |