X commited on
Commit
e6b6811
·
verified ·
1 Parent(s): 4bc8cbc

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +286 -12
app.py CHANGED
@@ -1,9 +1,17 @@
1
- # === ЗАГРУЗКА МОДЕЛИ (ИСПРАВЛЕННАЯ ЧАСТЬ) ===
 
 
 
 
 
 
 
 
2
  print("🚀 Загрузка модели...")
3
  model_id = "OpenRussianAI/OpenAirAI-X"
4
  tokenizer = AutoTokenizer.from_pretrained(model_id)
5
 
6
- # ВАЖНО: Убеждаемся, что у токенизатора есть pad_token
7
  if tokenizer.pad_token is None:
8
  tokenizer.pad_token = tokenizer.eos_token
9
 
@@ -12,7 +20,43 @@ device = "cuda" if torch.cuda.is_available() else "cpu"
12
  model = model.to(device).eval()
13
  print(f"✅ Модель загружена на {device}")
14
 
15
- # === ГЕНЕРАЦИЯ ОТВЕТА (ПОЛНОСТЬЮ ПЕРЕПИСАННАЯ) ===
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
16
  def generate_response(message, history, username, current_chat_id):
17
  if not username:
18
  gr.Warning("Сначала введите имя пользователя!")
@@ -38,35 +82,34 @@ def generate_response(message, history, username, current_chat_id):
38
  with torch.no_grad():
39
  outputs = model.generate(
40
  **inputs,
41
- max_new_tokens=128, # Уменьшим, чтобы модель не уходила в бред
42
  temperature=0.7,
43
  top_p=0.9,
44
  do_sample=True,
45
  pad_token_id=tokenizer.eos_token_id,
46
- eos_token_id=tokenizer.eos_token_id, # Важно!
47
- repetition_penalty=1.2 # Штраф за повторения, чтобы убрать цикл "Я OpenAI"
48
  )
49
 
50
- # Декодируем только НОВУЮ часть текста
51
  generated_ids = outputs[0][inputs.input_ids.shape[-1]:]
52
  response_text = tokenizer.decode(generated_ids, skip_special_tokens=True)
53
 
54
- # Очистка от мусора и обрезка
55
- # Обычно модель может начать ответ с пробела или новой строки
56
  ai_response = response_text.strip()
57
 
58
- # Если модель все же написала "Пользователь:" или "AI:" в конце, обрезаем
59
  stop_words = ["Пользователь:", "User:", "\n\n"]
60
  for stop_word in stop_words:
61
  if stop_word in ai_response:
62
  ai_response = ai_response.split(stop_word)[0].strip()
63
 
64
- # Если ответ пустой после очистки
65
  if not ai_response:
66
  ai_response = "..."
67
 
68
  history = history + [{"role": "assistant", "content": ai_response}]
69
 
 
70
  if username and current_chat_id:
71
  history_data = load_history(username)
72
  if current_chat_id not in history_data:
@@ -79,4 +122,235 @@ def generate_response(message, history, username, current_chat_id):
79
  save_history(username, history_data)
80
 
81
  chat_list_choices = get_chat_list(username)
82
- return history, gr.update(value=""), current_chat_id, gr.update(choices=chat_list_choices)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ from transformers import AutoModelForCausalLM, AutoTokenizer
3
+ import torch
4
+ import json
5
+ from pathlib import Path
6
+ from datetime import datetime
7
+ import requests
8
+
9
+ # === ЗАГРУЗКА МОДЕЛИ ===
10
  print("🚀 Загрузка модели...")
11
  model_id = "OpenRussianAI/OpenAirAI-X"
12
  tokenizer = AutoTokenizer.from_pretrained(model_id)
13
 
14
+ # Важно: устанавливаем pad_token, если его нет, чтобы избежать ошибок при генерации
15
  if tokenizer.pad_token is None:
16
  tokenizer.pad_token = tokenizer.eos_token
17
 
 
20
  model = model.to(device).eval()
21
  print(f"✅ Модель загружена на {device}")
22
 
23
+ # === ХРАНИЛИЩЕ ИСТОРИИ ===
24
+ HISTORY_DIR = Path("chat_history")
25
+ HISTORY_DIR.mkdir(exist_ok=True)
26
+
27
+ def get_history_path(username):
28
+ safe_name = "".join(c for c in username if c.isalnum() or c in ('-', '_')).lower()
29
+ return HISTORY_DIR / f"{safe_name}.json"
30
+
31
+ def load_history(username):
32
+ path = get_history_path(username)
33
+ if path.exists():
34
+ with open(path, "r", encoding="utf-8") as f:
35
+ return json.load(f)
36
+ return {}
37
+
38
+ def save_history(username, history_data):
39
+ path = get_history_path(username)
40
+ with open(path, "w", encoding="utf-8") as f:
41
+ json.dump(history_data, f, ensure_ascii=False, indent=2)
42
+
43
+ def check_if_pro(token):
44
+ if not token or not token.startswith("hf_"):
45
+ return False
46
+ try:
47
+ response = requests.get(
48
+ "https://huggingface.co/api/whoami-v2",
49
+ headers={"Authorization": f"Bearer {token}"},
50
+ timeout=5
51
+ )
52
+ if response.status_code == 200:
53
+ data = response.json()
54
+ return data.get("isPro", False) or data.get("plan", {}).get("name") == "PRO"
55
+ except Exception as e:
56
+ print(f"Ошибка проверки PRO: {e}")
57
+ return False
58
+
59
+ # === ГЕНЕРАЦИЯ ОТВЕТА (ИСПРАВЛЕННАЯ) ===
60
  def generate_response(message, history, username, current_chat_id):
61
  if not username:
62
  gr.Warning("Сначала введите имя пользователя!")
 
82
  with torch.no_grad():
83
  outputs = model.generate(
84
  **inputs,
85
+ max_new_tokens=128, # Ограничиваем длину, чтобы модель не "уходила в бред"
86
  temperature=0.7,
87
  top_p=0.9,
88
  do_sample=True,
89
  pad_token_id=tokenizer.eos_token_id,
90
+ eos_token_id=tokenizer.eos_token_id, # Токен конца строки
91
+ repetition_penalty=1.2 # Штраф за повторения (убирает цикл "Я OpenAI")
92
  )
93
 
94
+ # Декодируем ТОЛЬКО новую часть текста (ответ модели)
95
  generated_ids = outputs[0][inputs.input_ids.shape[-1]:]
96
  response_text = tokenizer.decode(generated_ids, skip_special_tokens=True)
97
 
98
+ # Очистка ответа
 
99
  ai_response = response_text.strip()
100
 
101
+ # Если модель начала писать следующий вопрос или метку, обрезаем
102
  stop_words = ["Пользователь:", "User:", "\n\n"]
103
  for stop_word in stop_words:
104
  if stop_word in ai_response:
105
  ai_response = ai_response.split(stop_word)[0].strip()
106
 
 
107
  if not ai_response:
108
  ai_response = "..."
109
 
110
  history = history + [{"role": "assistant", "content": ai_response}]
111
 
112
+ # Сохранение истории
113
  if username and current_chat_id:
114
  history_data = load_history(username)
115
  if current_chat_id not in history_data:
 
122
  save_history(username, history_data)
123
 
124
  chat_list_choices = get_chat_list(username)
125
+ return history, gr.update(value=""), current_chat_id, gr.update(choices=chat_list_choices)
126
+
127
+ # === УПРАВЛЕНИЕ ЧАТАМИ ===
128
+ def new_chat(username):
129
+ if not username:
130
+ return [], None, gr.update(choices=[])
131
+ chat_id = datetime.now().strftime("%Y%m%d_%H%M%S")
132
+ history_data = load_history(username)
133
+ history_data[chat_id] = {
134
+ "title": "Новый чат",
135
+ "created": datetime.now().isoformat(),
136
+ "messages": []
137
+ }
138
+ save_history(username, history_data)
139
+ chat_list_choices = get_chat_list(username)
140
+ return [], chat_id, gr.update(choices=chat_list_choices, value=None)
141
+
142
+ def load_chat(chat_title, username, current_chat_id):
143
+ if not username or not chat_title:
144
+ return [], current_chat_id
145
+ history_data = load_history(username)
146
+ for cid, data in history_data.items():
147
+ if data["title"] == chat_title:
148
+ return data["messages"], cid
149
+ return [], current_chat_id
150
+
151
+ def get_chat_list(username):
152
+ if not username:
153
+ return []
154
+ history_data = load_history(username)
155
+ sorted_chats = sorted(
156
+ history_data.items(),
157
+ key=lambda x: x[1].get("created", ""),
158
+ reverse=True
159
+ )
160
+ return [data["title"] for _, data in sorted_chats]
161
+
162
+ def delete_chat(chat_title, username):
163
+ if not username or not chat_title:
164
+ return [], {}, None, gr.update(choices=[])
165
+ history_data = load_history(username)
166
+ for cid, data in list(history_data.items()):
167
+ if data["title"] == chat_title:
168
+ del history_data[cid]
169
+ break
170
+ save_history(username, history_data)
171
+ chat_list_choices = get_chat_list(username)
172
+ return [], history_data, None, gr.update(choices=chat_list_choices, value=None)
173
+
174
+ # === CSS ===
175
+ CUSTOM_CSS = """
176
+ .main-header {
177
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
178
+ color: white;
179
+ padding: 20px;
180
+ border-radius: 12px;
181
+ margin-bottom: 20px;
182
+ text-align: center;
183
+ }
184
+ .main-header h1 { margin: 0; font-size: 2em; }
185
+ .pro-badge {
186
+ background: linear-gradient(135deg, #ffd700 0%, #ffed4e 100%);
187
+ color: #333;
188
+ padding: 4px 12px;
189
+ border-radius: 20px;
190
+ font-weight: bold;
191
+ display: inline-block;
192
+ margin-left: 10px;
193
+ box-shadow: 0 2px 8px rgba(255, 215, 0, 0.4);
194
+ }
195
+ .user-info {
196
+ padding: 10px;
197
+ background: white;
198
+ border-radius: 8px;
199
+ margin-bottom: 10px;
200
+ text-align: center;
201
+ font-weight: 600;
202
+ }
203
+ .privacy-banner {
204
+ background: linear-gradient(135deg, #10b981 0%, #059669 100%);
205
+ color: white;
206
+ padding: 15px 20px;
207
+ border-radius: 10px;
208
+ margin: 15px 0;
209
+ display: flex;
210
+ align-items: center;
211
+ gap: 12px;
212
+ font-size: 0.95em;
213
+ box-shadow: 0 2px 8px rgba(16, 185, 129, 0.3);
214
+ }
215
+ .privacy-banner .icon { font-size: 1.8em; }
216
+ .privacy-banner strong { display: block; font-size: 1.1em; margin-bottom: 4px; }
217
+ """
218
+
219
+ PRIVACY_BANNER = """
220
+ <div class="privacy-banner">
221
+ <div class="icon">🔒</div>
222
+ <div>
223
+ <strong>Ваши данные в безопасности</strong>
224
+ Все сообщения обрабатываются локально.
225
+ <b>Данные НЕ передаются в OpenRussianAI</b>.
226
+ История хранится только внутри контейнера.
227
+ </div>
228
+ </div>
229
+ """
230
+
231
+ # === ИНТЕРФЕЙС ===
232
+ with gr.Blocks(title="OpenAirAI-X Chat") as demo:
233
+ username_state = gr.State("")
234
+ history_state = gr.State({})
235
+ current_chat_id_state = gr.State(None)
236
+
237
+ with gr.Column(visible=False) as main_interface:
238
+ gr.HTML("<div class='main-header'><h1>🤖 OpenAirAI-X Chat</h1><p>Русскоязычный AI-ассистент</p></div>")
239
+ gr.HTML(PRIVACY_BANNER)
240
+
241
+ with gr.Row():
242
+ with gr.Column(scale=1, min_width=250):
243
+ user_info = gr.Textbox(label="Пользователь", value="Не авторизован", interactive=False, elem_classes="user-info")
244
+ pro_badge = gr.HTML('<div class="pro-badge">👑 PRO</div>', visible=False)
245
+ new_chat_btn = gr.Button("➕ Новый чат", variant="primary")
246
+ gr.Markdown("### 📚 История чатов")
247
+ chat_list = gr.Dropdown(choices=[], label="Ваши чаты", interactive=True, allow_custom_value=False, value=None)
248
+ delete_chat_btn = gr.Button("🗑️ Удалить выбранный чат", variant="stop", size="sm")
249
+ logout_btn = gr.Button("🚪 Выйти", size="sm")
250
+
251
+ with gr.Column(scale=3):
252
+ chatbot = gr.Chatbot(label="Диалог", height=500)
253
+ with gr.Row():
254
+ msg_input = gr.Textbox(placeholder="Напишите сообщение...", lines=2, scale=5, show_label=False)
255
+ send_btn = gr.Button("📤 О��править", variant="primary", scale=1)
256
+
257
+ with gr.Column(visible=True) as login_screen:
258
+ gr.HTML("<div class='main-header'><h1>🤖 OpenAirAI-X Chat</h1><p>Введите данные для входа</p></div>")
259
+ gr.HTML(PRIVACY_BANNER)
260
+ gr.Markdown("### 🔐 Вход в систему\nВведите ваше имя пользователя. История чатов будет привязана к этому имени.")
261
+ username_input = gr.Textbox(label="Имя пользователя (HF Username)", placeholder="Например: RootLinux21")
262
+ token_input = gr.Textbox(label="HF Token (необязательно, для PRO)", placeholder="hf_...")
263
+ login_btn = gr.Button("🔑 Войти", variant="primary")
264
+
265
+ # === ОБРАБОТЧИКИ ===
266
+
267
+ def handle_login(username, token):
268
+ if not username.strip():
269
+ gr.Warning("Введите имя пользователя!")
270
+ return gr.update(), gr.update(), "Не авторизован", "", {}, None, gr.update(choices=[], value=None), gr.update(visible=False)
271
+
272
+ is_pro = check_if_pro(token)
273
+ history_data = load_history(username)
274
+ chat_list_choices = get_chat_list(username)
275
+
276
+ current_chat_id = None
277
+ if not history_data:
278
+ chat_id = datetime.now().strftime("%Y%m%d_%H%M%S")
279
+ history_data[chat_id] = {
280
+ "title": "Новый чат",
281
+ "created": datetime.now().isoformat(),
282
+ "messages": []
283
+ }
284
+ save_history(username, history_data)
285
+ current_chat_id = chat_id
286
+ chat_list_choices = get_chat_list(username)
287
+
288
+ pro_badge_html = '<div class="pro-badge">👑 PRO</div>'
289
+ display_name = f"👤 {username}"
290
+
291
+ return (
292
+ gr.update(visible=True),
293
+ gr.update(visible=False),
294
+ display_name,
295
+ username,
296
+ history_data,
297
+ current_chat_id,
298
+ gr.update(choices=chat_list_choices, value=None),
299
+ gr.update(visible=is_pro, value=pro_badge_html)
300
+ )
301
+
302
+ def handle_logout():
303
+ return (
304
+ gr.update(visible=False),
305
+ gr.update(visible=True),
306
+ "Не авторизован",
307
+ "",
308
+ {},
309
+ None,
310
+ gr.update(choices=[], value=None),
311
+ gr.update(visible=False)
312
+ )
313
+
314
+ login_btn.click(
315
+ fn=handle_login,
316
+ inputs=[username_input, token_input],
317
+ outputs=[main_interface, login_screen, user_info, username_state, history_state, current_chat_id_state, chat_list, pro_badge]
318
+ )
319
+
320
+ send_btn.click(
321
+ fn=generate_response,
322
+ inputs=[msg_input, chatbot, username_state, current_chat_id_state],
323
+ outputs=[chatbot, msg_input, current_chat_id_state, chat_list]
324
+ )
325
+
326
+ msg_input.submit(
327
+ fn=generate_response,
328
+ inputs=[msg_input, chatbot, username_state, current_chat_id_state],
329
+ outputs=[chatbot, msg_input, current_chat_id_state, chat_list]
330
+ )
331
+
332
+ new_chat_btn.click(
333
+ fn=new_chat,
334
+ inputs=[username_state],
335
+ outputs=[chatbot, current_chat_id_state, chat_list]
336
+ )
337
+
338
+ chat_list.change(
339
+ fn=load_chat,
340
+ inputs=[chat_list, username_state, current_chat_id_state],
341
+ outputs=[chatbot, current_chat_id_state]
342
+ )
343
+
344
+ delete_chat_btn.click(
345
+ fn=delete_chat,
346
+ inputs=[chat_list, username_state],
347
+ outputs=[chatbot, history_state, current_chat_id_state, chat_list]
348
+ )
349
+
350
+ logout_btn.click(
351
+ fn=handle_logout,
352
+ inputs=None,
353
+ outputs=[main_interface, login_screen, user_info, username_state, history_state, current_chat_id_state, chat_list, pro_badge]
354
+ )
355
+
356
+ demo.launch(server_name="0.0.0.0", server_port=7860, css=CUSTOM_CSS)