Abobasnik commited on
Commit
1330dfd
·
verified ·
1 Parent(s): 86eeb9f

Update src/streamlit_app.py

Browse files
Files changed (1) hide show
  1. src/streamlit_app.py +128 -61
src/streamlit_app.py CHANGED
@@ -5,19 +5,29 @@ import os
5
  import json
6
  from streamlit_cookies_manager import EncryptedCookieManager
7
 
8
- # --- КУКИ И АВТОРИЗАЦИЯ ---
9
- cookies = EncryptedCookieManager(password="HiperDoubleSecretKey123_Unique")
10
- if not cookies.ready(): st.stop()
 
11
 
 
12
  CLIENT_ID = os.environ.get("GOOGLE_CLIENT_ID")
13
  CLIENT_SECRET = os.environ.get("GOOGLE_CLIENT_SECRET")
14
  host = st.context.headers.get("Host", "")
15
  REDIRECT_URI = f"https://{host}/" if host else ""
16
 
17
  def get_google_auth_url():
18
- params = {"client_id": CLIENT_ID, "redirect_uri": REDIRECT_URI, "response_type": "code", "scope": "openid email profile", "access_type": "offline", "prompt": "select_account"}
 
 
 
 
 
 
 
19
  return f"https://accounts.google.com/o/oauth2/v2/auth?{'&'.join([f'{k}={v}' for k, v in params.items()])}"
20
 
 
21
  if "user_email" not in st.session_state or st.session_state.user_email is None:
22
  st.session_state.user_email = cookies.get("saved_email")
23
  st.session_state.user_name = cookies.get("saved_name", "Пользователь")
@@ -25,37 +35,53 @@ if "user_email" not in st.session_state or st.session_state.user_email is None:
25
  if "code" in st.query_params and not st.session_state.user_email:
26
  try:
27
  code = st.query_params["code"]
28
- res = requests.post("https://oauth2.googleapis.com/token", data={"code": code, "client_id": CLIENT_ID, "client_secret": CLIENT_SECRET, "redirect_uri": REDIRECT_URI, "grant_type": "authorization_code"}).json()
 
 
 
29
  token = res.get("access_token")
30
  if token:
31
- info = requests.get("https://www.googleapis.com/oauth2/v3/userinfo", headers={"Authorization": f"Bearer {token}"}).json()
32
- st.session_state.user_email, st.session_state.user_name = info.get("email"), info.get("name")
33
- cookies["saved_email"], cookies["saved_name"] = info.get("email"), info.get("name")
34
- cookies.save(); st.rerun()
35
- except: pass
 
 
 
 
 
36
 
37
  def logout():
38
- for key in ["saved_email", "saved_name"]:
39
- if key in cookies: del cookies[key]
 
 
40
  cookies.save()
41
  st.session_state.user_email = None
42
  st.session_state.user_name = "Пользователь"
43
  st.rerun()
44
 
45
- def load_chats():
 
46
  u_id = st.session_state.user_email.replace('@','_').replace('.','_') if st.session_state.user_email else 'guest'
47
- db = f"chats_db_{u_id}.json"
48
- if os.path.exists(db):
 
 
 
49
  try:
50
- with open(db, "r", encoding="utf-8") as f: return json.load(f) or {"Чат 1": []}
51
- except: return {"Чат 1": []}
 
 
52
  return {"Чат 1": []}
53
 
54
  def save_chats(chats):
55
- u_id = st.session_state.user_email.replace('@','_').replace('.','_') if st.session_state.user_email else 'guest'
56
- with open(f"chats_db_{u_id}.json", "w", encoding="utf-8") as f: json.dump(chats, f, ensure_ascii=False, indent=4)
57
 
58
- # --- МОДЕЛИ ---
59
  MODELS_CONFIG = {
60
  "🌌 HiperAi v2.1 (Grew up)": {"engine": "groq", "key_name": "GROQ_API_KEY3", "model": "llama-3.3-70b-versatile", "identity": "HiperAI v2.1 Grew up."},
61
  "🧠 HiperAI v2.3 (CORTEX)": {"engine": "groq", "key_name": "GROQ_API_KEY", "model": "llama-3.3-70b-versatile", "identity": "HiperAI v2.3 Cortex."},
@@ -66,6 +92,7 @@ MODELS_CONFIG = {
66
  "✨ HiperAI v1.1.3 (Stable)": {"engine": "openai", "model": "gpt-4o-mini", "identity": "HiperAI v1.1.3."},
67
  }
68
 
 
69
  st.set_page_config(page_title="HiperDouble AI", page_icon="🧬", layout="wide")
70
 
71
  if "chats" not in st.session_state: st.session_state.chats = load_chats()
@@ -73,7 +100,7 @@ if "current_chat" not in st.session_state or st.session_state.current_chat not i
73
  st.session_state.current_chat = list(st.session_state.chats.keys())[0]
74
  if "edit_mode" not in st.session_state: st.session_state.edit_mode = None
75
 
76
- # --- ГЛАВНЫЙ ФИКС CSS ---
77
  st.markdown("""
78
  <style>
79
  html, body, [data-testid="stAppViewContainer"] {
@@ -82,7 +109,7 @@ st.markdown("""
82
  }
83
 
84
  .main-title {
85
- font-size: clamp(2.3rem, 8vw, 4rem);
86
  font-weight: 900;
87
  text-align: center;
88
  background: linear-gradient(90deg, #00f2fe, #7367f0, #ff00cc, #00f2fe);
@@ -90,108 +117,148 @@ st.markdown("""
90
  -webkit-background-clip: text;
91
  -webkit-text-fill-color: transparent;
92
  animation: shine 4s linear infinite;
93
- padding: 20px 0;
94
  }
95
  @keyframes shine { to { background-position: 200% center; } }
96
 
97
- /* ФИКС КОНТЕЙНЕРА: Сообщения теперь растут СНИЗУ ВВЕРХ */
98
  .main .block-container {
99
  display: flex !important;
100
- flex-direction: column-reverse !important; /* НОВЫЕ СВЕРХУ, СТАРЫЕ ВНИЗУ */
101
- padding-bottom: 150px !important;
102
  padding-top: 20px !important;
103
  }
104
 
 
105
  [data-testid="stBottom"] {
106
  position: fixed !important;
107
  bottom: 0px !important;
108
- background: rgba(10, 10, 20, 0.98) !important;
109
  backdrop-filter: blur(15px);
110
- z-index: 100 !important;
111
- padding: 10px 5% 40px 5% !important;
112
- border-top: 1px solid rgba(115, 103, 240, 0.3);
113
  }
114
 
115
- [data-testid="stSidebar"] { background-color: #0a0a15 !important; z-index: 999999 !important; }
116
-
117
  .chat-bubble {
118
  padding: 18px 22px;
119
- border-radius: 20px;
120
- margin-top: 15px; /* Теперь margin-top, т.к. порядок обратный */
121
  background: rgba(255, 255, 255, 0.05);
122
  border: 1px solid rgba(255, 255, 255, 0.1);
123
- backdrop-filter: blur(5px);
124
- color: #e0e0e0;
 
125
  }
126
- .user-bubble { border-left: 5px solid #ff00cc; background: rgba(255, 0, 204, 0.08); }
 
 
 
 
 
 
127
  </style>
128
  """, unsafe_allow_html=True)
129
 
130
  # --- SIDEBAR ---
131
  with st.sidebar:
132
- st.markdown("### 🧬 HiperDouble")
133
  if st.session_state.user_email:
134
  st.markdown(f"👤 **{st.session_state.user_name}**")
135
  if st.button("🚪 Выйти", use_container_width=True): logout()
136
  else:
137
- st.markdown(f'<a href="{get_google_auth_url()}" target="_self" style="background-color: #4285F4; color: white; padding: 12px; text-decoration: none; border-radius: 8px; display: block; text-align: center;">Войти</a>', unsafe_allow_html=True); st.stop()
 
138
 
139
  st.markdown("---")
140
  lang = st.radio("🌐", ["RU", "EN"], horizontal=True)
141
- T = {"RU":{"new":"➕ Новый чат","clear":"🗑️ Очистить","mod":"🤖 Модель:","in":"Сообщение..."},"EN":{"new":"➕ New Chat","clear":"🗑️ Clear","mod":"🤖 Model:","in":"Message..."}}[lang]
 
142
 
143
- selected_name = st.selectbox(T['mod'], list(MODELS_CONFIG.keys())); cfg = MODELS_CONFIG[selected_name]
 
144
 
145
  if st.button(T['new'], use_container_width=True):
146
- n = f"Чат {len(st.session_state.chats) + 1}"; st.session_state.chats[n] = []; save_chats(st.session_state.chats); st.session_state.current_chat = n; st.rerun()
 
 
 
 
147
 
148
  if st.button(T['clear'], use_container_width=True):
149
- st.session_state.chats[st.session_state.current_chat] = []; save_chats(st.session_state.chats); st.rerun()
 
 
150
 
151
  st.markdown("---")
152
  for chat_name in list(st.session_state.chats.keys()):
153
  col_c, col_e, col_d = st.columns([0.6, 0.2, 0.2])
154
- if col_c.button(f"💬 {chat_name[:12]}", key=f"s_{chat_name}"): st.session_state.current_chat = chat_name; st.rerun()
155
- if col_e.button("✏️", key=f"e_{chat_name}"): st.session_state.edit_mode = chat_name
 
 
 
156
  if col_d.button("🗑️", key=f"d_{chat_name}"):
157
  if len(st.session_state.chats) > 1:
158
  del st.session_state.chats[chat_name]
159
- save_chats(st.session_state.chats); st.session_state.current_chat = list(st.session_state.chats.keys())[0]; st.rerun()
 
 
160
 
161
  if st.session_state.edit_mode == chat_name:
162
  new_n = st.text_input("Имя:", chat_name, key=f"in_{chat_name}")
163
  if st.button("ОК", key=f"ok_{chat_name}"):
164
  st.session_state.chats[new_n] = st.session_state.chats.pop(chat_name)
165
- save_chats(st.session_state.chats); st.session_state.edit_mode = None; st.session_state.current_chat = new_n; st.rerun()
 
 
 
166
 
167
- # --- ЭКРАН ---
168
- # Заголовок теперь ВНИЗУ списка (так как контейнер перевернут, он окажется сверху)
169
- st.markdown('<p class="main-title">HiperDouble AI</p>', unsafe_allow_html=True)
 
170
 
171
  if st.session_state.current_chat in st.session_state.chats:
 
 
 
 
172
  messages = st.session_state.chats[st.session_state.current_chat]
173
- # ВАЖНО: Мы НЕ используем reversed(), так как column-reverse сделает это за нас!
174
  for m in messages:
175
- r_name, r_class = ("Вы", "user-bubble") if m['role'] == 'user' else ("HiperAi", "")
176
- st.markdown(f"<div class='chat-bubble {r_class}'><b>{r_name}:</b><br>{m['content']}</div>", unsafe_allow_html=True)
177
-
178
- # ПОЛЕ ВВОДА
179
- u_input = st.chat_input(T['in'])
180
 
 
181
  if u_input:
182
  st.session_state.chats[st.session_state.current_chat].append({"role": "user", "content": u_input})
183
  try:
184
  key = st.secrets.get(cfg["key_name"]) or os.environ.get(cfg["key_name"], "").strip()
185
  if cfg["engine"] == "titan":
186
- res_text = "HiperAI Titan активен."
187
- else:
 
 
 
 
 
 
 
188
  resp = requests.post("https://api.groq.com/openai/v1/chat/completions",
189
- json={"model": cfg["model"], "messages": [{"role": "system", "content": cfg['identity']}] + st.session_state.chats[st.session_state.current_chat][-6:]},
 
 
 
190
  headers={"Authorization": f"Bearer {key}"}, timeout=25)
191
  res_text = resp.json()['choices'][0]['message']['content']
192
  except Exception as e:
193
- res_text = f"⚠️ Ошибка. ({str(e)})"
194
 
195
  st.session_state.chats[st.session_state.current_chat].append({"role": "assistant", "content": res_text})
196
- save_chats(st.session_state.chats); st.rerun()
 
197
 
 
5
  import json
6
  from streamlit_cookies_manager import EncryptedCookieManager
7
 
8
+ # --- ИНИЦИАЛИЗАЦИЯ КУКИ ---
9
+ cookies = EncryptedCookieManager(password="HiperDouble_Secret_2026_Key")
10
+ if not cookies.ready():
11
+ st.stop()
12
 
13
+ # --- ПАРАМЕТРЫ GOOGLE ---
14
  CLIENT_ID = os.environ.get("GOOGLE_CLIENT_ID")
15
  CLIENT_SECRET = os.environ.get("GOOGLE_CLIENT_SECRET")
16
  host = st.context.headers.get("Host", "")
17
  REDIRECT_URI = f"https://{host}/" if host else ""
18
 
19
  def get_google_auth_url():
20
+ params = {
21
+ "client_id": CLIENT_ID,
22
+ "redirect_uri": REDIRECT_URI,
23
+ "response_type": "code",
24
+ "scope": "openid email profile",
25
+ "access_type": "offline",
26
+ "prompt": "select_account"
27
+ }
28
  return f"https://accounts.google.com/o/oauth2/v2/auth?{'&'.join([f'{k}={v}' for k, v in params.items()])}"
29
 
30
+ # --- ЛОГИКА АККАУНТА ---
31
  if "user_email" not in st.session_state or st.session_state.user_email is None:
32
  st.session_state.user_email = cookies.get("saved_email")
33
  st.session_state.user_name = cookies.get("saved_name", "Пользователь")
 
35
  if "code" in st.query_params and not st.session_state.user_email:
36
  try:
37
  code = st.query_params["code"]
38
+ res = requests.post("https://oauth2.googleapis.com/token", data={
39
+ "code": code, "client_id": CLIENT_ID, "client_secret": CLIENT_SECRET,
40
+ "redirect_uri": REDIRECT_URI, "grant_type": "authorization_code"
41
+ }).json()
42
  token = res.get("access_token")
43
  if token:
44
+ info = requests.get("https://www.googleapis.com/oauth2/v3/userinfo",
45
+ headers={"Authorization": f"Bearer {token}"}).json()
46
+ st.session_state.user_email = info.get("email")
47
+ st.session_state.user_name = info.get("name")
48
+ cookies["saved_email"] = info.get("email")
49
+ cookies["saved_name"] = info.get("name")
50
+ cookies.save()
51
+ st.rerun()
52
+ except:
53
+ pass
54
 
55
  def logout():
56
+ # Безопасная очистка без ошибок кодирования
57
+ for k in ["saved_email", "saved_name"]:
58
+ if k in cookies:
59
+ cookies[k] = "" # Устанавливаем пустую строку вместо None
60
  cookies.save()
61
  st.session_state.user_email = None
62
  st.session_state.user_name = "Пользователь"
63
  st.rerun()
64
 
65
+ # --- РАБОТА С БД ---
66
+ def get_db_path():
67
  u_id = st.session_state.user_email.replace('@','_').replace('.','_') if st.session_state.user_email else 'guest'
68
+ return f"chats_db_{u_id}.json"
69
+
70
+ def load_chats():
71
+ path = get_db_path()
72
+ if os.path.exists(path):
73
  try:
74
+ with open(path, "r", encoding="utf-8") as f:
75
+ return json.load(f) or {"Чат 1": []}
76
+ except:
77
+ return {"Чат 1": []}
78
  return {"Чат 1": []}
79
 
80
  def save_chats(chats):
81
+ with open(get_db_path(), "w", encoding="utf-8") as f:
82
+ json.dump(chats, f, ensure_ascii=False, indent=4)
83
 
84
+ # --- КОНФИГ МОДЕЛЕЙ ---
85
  MODELS_CONFIG = {
86
  "🌌 HiperAi v2.1 (Grew up)": {"engine": "groq", "key_name": "GROQ_API_KEY3", "model": "llama-3.3-70b-versatile", "identity": "HiperAI v2.1 Grew up."},
87
  "🧠 HiperAI v2.3 (CORTEX)": {"engine": "groq", "key_name": "GROQ_API_KEY", "model": "llama-3.3-70b-versatile", "identity": "HiperAI v2.3 Cortex."},
 
92
  "✨ HiperAI v1.1.3 (Stable)": {"engine": "openai", "model": "gpt-4o-mini", "identity": "HiperAI v1.1.3."},
93
  }
94
 
95
+ # --- ИНТЕРФЕЙС ---
96
  st.set_page_config(page_title="HiperDouble AI", page_icon="🧬", layout="wide")
97
 
98
  if "chats" not in st.session_state: st.session_state.chats = load_chats()
 
100
  st.session_state.current_chat = list(st.session_state.chats.keys())[0]
101
  if "edit_mode" not in st.session_state: st.session_state.edit_mode = None
102
 
103
+ # --- СТИЛИ (CSS) ---
104
  st.markdown("""
105
  <style>
106
  html, body, [data-testid="stAppViewContainer"] {
 
109
  }
110
 
111
  .main-title {
112
+ font-size: clamp(2rem, 8vw, 3.5rem);
113
  font-weight: 900;
114
  text-align: center;
115
  background: linear-gradient(90deg, #00f2fe, #7367f0, #ff00cc, #00f2fe);
 
117
  -webkit-background-clip: text;
118
  -webkit-text-fill-color: transparent;
119
  animation: shine 4s linear infinite;
120
+ padding: 15px 0;
121
  }
122
  @keyframes shine { to { background-position: 200% center; } }
123
 
124
+ /* ГЛАВНЫЙ ФИКС СКРОЛЛА: Сообщения растут вверх */
125
  .main .block-container {
126
  display: flex !important;
127
+ flex-direction: column-reverse !important;
128
+ padding-bottom: 200px !important;
129
  padding-top: 20px !important;
130
  }
131
 
132
+ /* ФИКСИРОВАННАЯ НИЖНЯЯ ПАНЕЛЬ */
133
  [data-testid="stBottom"] {
134
  position: fixed !important;
135
  bottom: 0px !important;
136
+ background: rgba(10, 10, 20, 0.96) !important;
137
  backdrop-filter: blur(15px);
138
+ z-index: 1000 !important;
139
+ padding: 10px 5% 45px 5% !important;
140
+ border-top: 1px solid rgba(115, 103, 240, 0.4);
141
  }
142
 
143
+ /* ПУЗЫРИ СООБЩЕНИЙ */
 
144
  .chat-bubble {
145
  padding: 18px 22px;
146
+ border-radius: 22px;
147
+ margin-top: 15px;
148
  background: rgba(255, 255, 255, 0.05);
149
  border: 1px solid rgba(255, 255, 255, 0.1);
150
+ backdrop-filter: blur(8px);
151
+ color: #f0f0f0;
152
+ box-shadow: 0 4px 15px rgba(0,0,0,0.2);
153
  }
154
+ .user-bubble {
155
+ border-left: 5px solid #ff00cc;
156
+ background: rgba(255, 0, 204, 0.07);
157
+ }
158
+
159
+ [data-testid="stSidebar"] { background-color: #080812 !important; }
160
+ header, footer { visibility: hidden; }
161
  </style>
162
  """, unsafe_allow_html=True)
163
 
164
  # --- SIDEBAR ---
165
  with st.sidebar:
166
+ st.markdown("### 🧬 HiperDouble AI")
167
  if st.session_state.user_email:
168
  st.markdown(f"👤 **{st.session_state.user_name}**")
169
  if st.button("🚪 Выйти", use_container_width=True): logout()
170
  else:
171
+ st.markdown(f'<a href="{get_google_auth_url()}" target="_self" style="background-color: #4285F4; color: white; padding: 12px; text-decoration: none; border-radius: 8px; display: block; text-align: center;">Войти через Google</a>', unsafe_allow_html=True)
172
+ st.stop()
173
 
174
  st.markdown("---")
175
  lang = st.radio("🌐", ["RU", "EN"], horizontal=True)
176
+ T = {"RU":{"new":"➕ Новый чат","clear":"🗑️ Очистить","mod":"🤖 Модель:","in":"Сообщение..."},
177
+ "EN":{"new":"➕ New Chat","clear":"🗑️ Clear","mod":"🤖 Model:","in":"Message..."}}[lang]
178
 
179
+ selected_name = st.selectbox(T['mod'], list(MODELS_CONFIG.keys()))
180
+ cfg = MODELS_CONFIG[selected_name]
181
 
182
  if st.button(T['new'], use_container_width=True):
183
+ n = f"Чат {len(st.session_state.chats) + 1}"
184
+ st.session_state.chats[n] = []
185
+ save_chats(st.session_state.chats)
186
+ st.session_state.current_chat = n
187
+ st.rerun()
188
 
189
  if st.button(T['clear'], use_container_width=True):
190
+ st.session_state.chats[st.session_state.current_chat] = []
191
+ save_chats(st.session_state.chats)
192
+ st.rerun()
193
 
194
  st.markdown("---")
195
  for chat_name in list(st.session_state.chats.keys()):
196
  col_c, col_e, col_d = st.columns([0.6, 0.2, 0.2])
197
+ if col_c.button(f"💬 {chat_name[:12]}", key=f"s_{chat_name}"):
198
+ st.session_state.current_chat = chat_name
199
+ st.rerun()
200
+ if col_e.button("✏️", key=f"e_{chat_name}"):
201
+ st.session_state.edit_mode = chat_name
202
  if col_d.button("🗑️", key=f"d_{chat_name}"):
203
  if len(st.session_state.chats) > 1:
204
  del st.session_state.chats[chat_name]
205
+ save_chats(st.session_state.chats)
206
+ st.session_state.current_chat = list(st.session_state.chats.keys())[0]
207
+ st.rerun()
208
 
209
  if st.session_state.edit_mode == chat_name:
210
  new_n = st.text_input("Имя:", chat_name, key=f"in_{chat_name}")
211
  if st.button("ОК", key=f"ok_{chat_name}"):
212
  st.session_state.chats[new_n] = st.session_state.chats.pop(chat_name)
213
+ save_chats(st.session_state.chats)
214
+ st.session_state.edit_mode = None
215
+ st.session_state.current_chat = new_n
216
+ st.rerun()
217
 
218
+ # --- ОСНОВНОЙ ЭКРАН ---
219
+
220
+ # Поле ввода (закреплено в stBottom через CSS)
221
+ u_input = st.chat_input(T['in'])
222
 
223
  if st.session_state.current_chat in st.session_state.chats:
224
+ # 1. НЕВИДИМЫЙ БЛОК-СТОПОР (снизу)
225
+ st.markdown("<div style='height: 25vh; min-height: 180px;'></div>", unsafe_allow_html=True)
226
+
227
+ # 2. СООБЩЕНИЯ (column-reverse сам сделает новые сверху)
228
  messages = st.session_state.chats[st.session_state.current_chat]
 
229
  for m in messages:
230
+ role_label, bubble_style = ("Вы", "user-bubble") if m['role'] == 'user' else ("HiperAi", "")
231
+ st.markdown(f"<div class='chat-bubble {bubble_style}'><b>{role_label}:</b><br>{m['content']}</div>", unsafe_allow_html=True)
232
+
233
+ # 3. ЗАГОЛОВОК (будет в самом верху)
234
+ st.markdown('<p class="main-title">HiperDouble AI</p>', unsafe_allow_html=True)
235
 
236
+ # --- ЛОГИКА ОТПРАВКИ ---
237
  if u_input:
238
  st.session_state.chats[st.session_state.current_chat].append({"role": "user", "content": u_input})
239
  try:
240
  key = st.secrets.get(cfg["key_name"]) or os.environ.get(cfg["key_name"], "").strip()
241
  if cfg["engine"] == "titan":
242
+ res_text = "Модель HiperAI Titan активна. Текстовый режим подтвержден."
243
+ elif cfg["engine"] == "openai":
244
+ client = OpenAI(api_key=st.secrets.get("OPENAI_API_KEY") or os.environ.get("OPENAI_API_KEY", ""))
245
+ r = client.chat.completions.create(model=cfg["model"], messages=[
246
+ {"role": "system", "content": cfg['identity']},
247
+ {"role": "user", "content": u_input}
248
+ ])
249
+ res_text = r.choices[0].message.content
250
+ else: # GROQ
251
  resp = requests.post("https://api.groq.com/openai/v1/chat/completions",
252
+ json={
253
+ "model": cfg["model"],
254
+ "messages": [{"role": "system", "content": cfg['identity']}] + st.session_state.chats[st.session_state.current_chat][-6:]
255
+ },
256
  headers={"Authorization": f"Bearer {key}"}, timeout=25)
257
  res_text = resp.json()['choices'][0]['message']['content']
258
  except Exception as e:
259
+ res_text = f"⚠️ Ошибка связи с моделью. Попробуйте позже. ({str(e)})"
260
 
261
  st.session_state.chats[st.session_state.current_chat].append({"role": "assistant", "content": res_text})
262
+ save_chats(st.session_state.chats)
263
+ st.rerun()
264