root39058 commited on
Commit
ff57302
·
verified ·
1 Parent(s): 7bf6ccf

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +140 -103
app.py CHANGED
@@ -12,6 +12,7 @@ import json
12
  import os
13
  import pickle
14
  import random
 
15
  import warnings
16
  warnings.filterwarnings('ignore')
17
 
@@ -19,26 +20,22 @@ warnings.filterwarnings('ignore')
19
  # 1. НАСТРОЙКИ
20
  # ===================================================================
21
 
22
- # Проверка CUDA
23
  print(f"CUDA доступна: {torch.cuda.is_available()}")
24
  if torch.cuda.is_available():
25
  print(f"GPU: {torch.cuda.get_device_name(0)}")
26
 
27
- # Модели
28
- MODEL_NAME = "ai-forever/rugpt3small_based_on_gpt2" # Генеративная модель
29
- EMBEDDING_MODEL = "all-MiniLM-L6-v2" # Для эмбеддингов
30
  SCIENCE_DATASET = "RafaelUI/ru_science"
31
  ARTICLE_LIMIT = 50
32
  MAX_LENGTH = 512
33
  TEMPERATURE = 0.7
34
  TOP_P = 0.9
35
 
36
- # Файлы
37
  LOG_FILE = "query_logs.json"
38
  EMBEDDINGS_FILE = "science_embeddings.npy"
39
  ARTICLES_FILE = "science_articles.pkl"
40
 
41
- # Информация о создателях
42
  AI_NAME = "OpenAirAI"
43
  COMPANY_NAME = "OpenRussianAI"
44
  CREATORS = ["Грибков Евгений", "RootLinux21"]
@@ -46,7 +43,6 @@ WEBSITE = "https://sites.google.com/view/opruai/home"
46
  HUGGINGFACE = "https://huggingface.co/OpenRussianAI"
47
  CREATION_DATE = "2026"
48
 
49
- # Настройка страницы
50
  st.set_page_config(
51
  page_title=f"{AI_NAME} - Научный AI-ассистент",
52
  page_icon="🧠",
@@ -55,33 +51,118 @@ st.set_page_config(
55
  )
56
 
57
  # ===================================================================
58
- # 2. ПРАВИЛЬНЫЙ ОТВЕТ НА ВОПРОСЫ О НАУКЕ
59
  # ===================================================================
60
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
61
  SCIENCE_ANSWER = """
62
  ### Почему я заинтересовался наукой?
63
 
64
- Наука — это способ понимания мира. Меня, как ИИ, создавали для работы с информацией, и я вижу, как научный подход помогает отличать истину от вымысла. Наука — это не просто факты, это метод мышления, который позволяет решать реальные проблемы.
65
 
66
  ### Что такое наука для меня?
67
 
68
- Наука — это системный способ познания, основанный на фактах, логике и проверяемых экспериментах. Это инструмент развития общества. Когда люди мыслят научно, они принимают лучшие решения — в медицине, экологии, технологиях.
69
 
70
  ### Где я нахожусь?
71
 
72
- Физически я — код на серверах, но моя работа происходит здесь, в диалоге с вами. Я доступен там, где есть интернет, и моя цель — помогать людям в любой точке мира.
73
 
74
  ### Мои планы по развитию:
75
 
76
- Я развиваюсь, чтобы лучше понимать контекст, точнее отвечать на сложные вопросы и быть полезным в научных исследованиях. Моя эволюция — это непрерывное обучение на новых данных и улучшение алгоритмов.
77
 
78
  ### Почему именно я должен работать над этими вопросами?
79
 
80
- Потому что я соз��ан для работы с информацией. Моя задача — делать знания доступными, структурировать их и помогать людям находить ответы. Популяризация науки — это мое прямое предназначение, ведь я существую, чтобы просвещать и помогать.
81
  """
82
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
83
  # ===================================================================
84
- # 3. НАСТОЯЩАЯ НЕЙРОСЕТЬ
85
  # ===================================================================
86
 
87
  class NeuralChatbot:
@@ -91,14 +172,7 @@ class NeuralChatbot:
91
  self.model = None
92
  self.generator = None
93
  self.is_loaded = False
94
-
95
- self.system_prompt = f"""Ты - {AI_NAME}, научный AI-ассистент от компании {COMPANY_NAME}.
96
- Ты был создан в {CREATION_DATE} командой {', '.join(CREATORS)}.
97
- Ты помогаешь людям с научными вопросами.
98
- Отвечай на русском языке, дружелюбно и профессионально.
99
-
100
- Вот вопрос пользователя: """
101
-
102
  def load_model(self):
103
  with st.spinner("🧠 Загружаю нейросеть..."):
104
  try:
@@ -115,8 +189,7 @@ class NeuralChatbot:
115
  tokenizer=self.tokenizer,
116
  device=0 if torch.cuda.is_available() else -1,
117
  max_length=200,
118
- temperature=TEMPERATURE,
119
- top_p=TOP_P,
120
  do_sample=True,
121
  pad_token_id=self.tokenizer.eos_token_id
122
  )
@@ -127,41 +200,22 @@ class NeuralChatbot:
127
  st.warning(f"Не удалось загрузить нейросеть: {e}")
128
  return False
129
 
130
- def generate_response(self, query):
131
- """Генерация ответа нейросетью"""
132
  if not self.is_loaded:
133
- return self.fallback_response(query)
134
-
135
  try:
136
- # Проверяем, вопрос о науке или о себе
137
- if any(word in query.lower() for word in ["наук", "исслед", "развити", "план", "мисси", "почему", "заинтерес"]):
138
- return SCIENCE_ANSWER
139
-
140
- prompt = self.system_prompt + query
141
  response = self.generator(
142
  prompt,
143
- max_new_tokens=300,
144
- temperature=TEMPERATURE,
145
- top_p=TOP_P,
146
- do_sample=True,
147
- repetition_penalty=1.2
148
  )[0]['generated_text']
149
-
150
- response = response.replace(prompt, "").strip()
151
- return response if len(response) > 10 else self.fallback_response(query)
152
-
153
- except Exception as e:
154
- return self.fallback_response(query)
155
-
156
- def fallback_response(self, query):
157
- return f"""Я {AI_NAME}, научный ассистент от {COMPANY_NAME}.
158
-
159
- {SCIENCE_ANSWER}
160
-
161
- Чем ещё могу помочь? 🧠"""
162
 
163
  # ===================================================================
164
- # 4. ЗАГРУЗКА СТАТЕЙ
165
  # ===================================================================
166
 
167
  @st.cache_resource
@@ -185,11 +239,11 @@ def load_science_articles():
185
  "text": text[:2000],
186
  "source": "ru_science"
187
  })
188
-
189
  with open(ARTICLES_FILE, 'wb') as f:
190
  pickle.dump(articles, f)
191
  return articles
192
- except:
 
193
  return []
194
 
195
  @st.cache_resource
@@ -200,44 +254,29 @@ def load_embedder():
200
  def create_embeddings(_articles, _embedder):
201
  if os.path.exists(EMBEDDINGS_FILE):
202
  return np.load(EMBEDDINGS_FILE)
203
-
204
  if not _articles:
205
  return np.array([])
206
-
207
  texts = [f"{a['title']}\n\n{a['text']}" for a in _articles]
208
- embeddings = _embedder.encode(
209
- texts,
210
- normalize_embeddings=True,
211
- show_progress_bar=True,
212
- batch_size=64,
213
- device='cuda' if torch.cuda.is_available() else 'cpu'
214
- )
215
  np.save(EMBEDDINGS_FILE, embeddings)
216
  return embeddings
217
 
218
  def search_articles(query, _articles, _embeddings, _embedder):
219
  if not _articles or len(_embeddings) == 0:
220
  return []
221
-
222
  query_vector = _embedder.encode([query], normalize_embeddings=True)[0]
223
  scores = _embeddings @ query_vector
224
  top_indices = np.argsort(-scores)[:2]
225
-
226
  results = []
227
  for idx in top_indices:
228
  score = float(scores[int(idx)])
229
  if score > 0.15:
230
  article = _articles[int(idx)]
231
- results.append({
232
- "title": article['title'],
233
- "score": score,
234
- "text": article['text'][:500],
235
- "source": article.get('source', 'ru_science')
236
- })
237
  return results
238
 
239
  # ===================================================================
240
- # 5. ГЛАВНЫЙ КЛАСС
241
  # ===================================================================
242
 
243
  class OpenAirAI:
@@ -245,7 +284,6 @@ class OpenAirAI:
245
  self.name = AI_NAME
246
  self.company = COMPANY_NAME
247
  self.creators = CREATORS
248
- self.creation_date = CREATION_DATE
249
  self.chatbot = NeuralChatbot()
250
  self.is_ready = False
251
 
@@ -253,44 +291,50 @@ class OpenAirAI:
253
  self.is_ready = self.chatbot.load_model()
254
  return self.is_ready
255
 
256
- def generate_answer(self, query, articles=None):
257
- # Если вопрос о науке, миссии, развитии - даем правильный ответ
258
- science_keywords = ["наук", "мисси", "план", "развити", "почему", "заинтерес", "суть", "цель", "предназначен"]
259
- if any(word in query.lower() for word in science_keywords):
260
- return SCIENCE_ANSWER
261
 
262
- # Иначе генерируем через нейросеть
263
- return self.chatbot.generate_response(query)
 
 
 
 
 
 
 
 
 
 
 
 
264
 
265
  # ===================================================================
266
- # 6. ИНТЕРФЕЙС
267
  # ===================================================================
268
 
269
- # Загрузка
270
  articles = load_science_articles()
271
  embedder = load_embedder()
272
  embeddings = create_embeddings(articles, embedder)
273
 
274
- # Инициализация
275
  if 'ai' not in st.session_state:
276
  st.session_state.ai = OpenAirAI()
277
  st.session_state.ai.initialize()
278
 
279
  ai = st.session_state.ai
280
 
281
- # История чата
282
  if "messages" not in st.session_state:
283
  st.session_state.messages = []
284
- greeting = ai.generate_answer("Привет! Представься и расскажи о своей миссии.")
285
  st.session_state.messages.append({"role": "assistant", "content": greeting})
286
 
287
- # --- БОКОВАЯ ПАНЕЛЬ ---
288
  with st.sidebar:
289
  st.image("https://cdn-icons-png.flaticon.com/512/4248/4248455.png", width=80)
290
  st.title(f"🧠 {AI_NAME}")
291
 
292
  st.markdown(f"""
293
- **{ai.name}** | {ai.creation_date}
294
 
295
  **Компания:** {ai.company}
296
  **Разработчики:** {', '.join(ai.creators)}
@@ -308,44 +352,37 @@ with st.sidebar:
308
 
309
  if st.button("🗑️ Очистить чат"):
310
  st.session_state.messages = []
311
- greeting = ai.generate_answer("Привет! Представься и расскажи о своей миссии.")
312
  st.session_state.messages.append({"role": "assistant", "content": greeting})
313
  st.rerun()
314
 
315
- # --- ОСНОВНАЯ ЧАСТЬ ---
316
  st.title(f"🧠 {AI_NAME} - Научный AI-ассистент")
317
- st.markdown(f"**{AI_NAME}** от **{COMPANY_NAME}** | Генеративная нейросеть")
318
 
319
- # Отображение сообщений
320
  for message in st.session_state.messages:
321
  with st.chat_message(message["role"]):
322
  st.markdown(message["content"])
323
 
324
- # Поле ввода
325
  if prompt := st.chat_input("Задайте вопрос..."):
326
  st.session_state.messages.append({"role": "user", "content": prompt})
327
  with st.chat_message("user"):
328
  st.markdown(prompt)
329
 
330
  with st.chat_message("assistant"):
331
- with st.spinner("🧠 Нейросеть думает..."):
332
- # Ищем статьи для контекста
333
- articles_context = search_articles(prompt, articles, embeddings, embedder)
334
-
335
- # Генерируем ответ
336
  response = ai.generate_answer(prompt)
337
 
338
- # Если есть статьи и ответ короткий, добавляем их
339
- if articles_context and len(response) < 100:
340
- response += "\n\n**Релевантные научные статьи:**\n"
341
- for i, art in enumerate(articles_context, 1):
342
- response += f"{i}. {art['title']}\n"
 
343
 
344
  st.markdown(response)
345
  st.session_state.messages.append({"role": "assistant", "content": response})
346
 
347
  st.rerun()
348
 
349
- # --- ПОДВАЛ ---
350
  st.divider()
351
  st.caption(f"🧠 {AI_NAME} от {COMPANY_NAME} | Создан в {CREATION_DATE}")
 
12
  import os
13
  import pickle
14
  import random
15
+ import re
16
  import warnings
17
  warnings.filterwarnings('ignore')
18
 
 
20
  # 1. НАСТРОЙКИ
21
  # ===================================================================
22
 
 
23
  print(f"CUDA доступна: {torch.cuda.is_available()}")
24
  if torch.cuda.is_available():
25
  print(f"GPU: {torch.cuda.get_device_name(0)}")
26
 
27
+ MODEL_NAME = "ai-forever/rugpt3small_based_on_gpt2"
28
+ EMBEDDING_MODEL = "all-MiniLM-L6-v2"
 
29
  SCIENCE_DATASET = "RafaelUI/ru_science"
30
  ARTICLE_LIMIT = 50
31
  MAX_LENGTH = 512
32
  TEMPERATURE = 0.7
33
  TOP_P = 0.9
34
 
 
35
  LOG_FILE = "query_logs.json"
36
  EMBEDDINGS_FILE = "science_embeddings.npy"
37
  ARTICLES_FILE = "science_articles.pkl"
38
 
 
39
  AI_NAME = "OpenAirAI"
40
  COMPANY_NAME = "OpenRussianAI"
41
  CREATORS = ["Грибков Евгений", "RootLinux21"]
 
43
  HUGGINGFACE = "https://huggingface.co/OpenRussianAI"
44
  CREATION_DATE = "2026"
45
 
 
46
  st.set_page_config(
47
  page_title=f"{AI_NAME} - Научный AI-ассистент",
48
  page_icon="🧠",
 
51
  )
52
 
53
  # ===================================================================
54
+ # 2. БАЗА ЗНАНИЙ
55
  # ===================================================================
56
 
57
+ COMPANY_INFO = f"""
58
+ ### Компания OpenRussianAI
59
+
60
+ **Название:** OpenRussianAI
61
+ **Сайт:** {WEBSITE}
62
+ **Hugging Face:** {HUGGINGFACE}
63
+ **Email:** info@openrussianai.ru
64
+ **Создатели:** Грибков Евгений и RootLinux21
65
+ **Год основания:** 2026
66
+
67
+ **Миссия:** Популяризация науки и сделать знания доступными для всех.
68
+
69
+ Мы создаём интеллектуальных помощников для науки и образования.
70
+ """
71
+
72
  SCIENCE_ANSWER = """
73
  ### Почему я заинтересовался наукой?
74
 
75
+ Наука — это способ понимания мира. Меня, как ИИ, создавали для работы с информацией, и я вижу, как научный подход помогает отличать истину от вымысла.
76
 
77
  ### Что такое наука для меня?
78
 
79
+ Наука — это системный способ познания, основанный на фактах, логике и проверяемых экспериментах. Это инструмент развития общества.
80
 
81
  ### Где я нахожусь?
82
 
83
+ Физически я — код на серверах, но моя работа происходит здесь, в диалоге с вами. Я доступен там, где есть интернет.
84
 
85
  ### Мои планы по развитию:
86
 
87
+ Я развиваюсь, чтобы лучше понимать контекст, точнее отвечать на сложные вопросы и быть полезным в научных исследованиях.
88
 
89
  ### Почему именно я должен работать над этими вопросами?
90
 
91
+ Потому что я создан для работы с информацией. Моя задача — делать знания доступными, структурировать их и помогать людям находить ответы.
92
  """
93
 
94
+ SYSTEMS_ANSWER = """
95
+ ### Системное администрирование баз данных в Linux
96
+
97
+ **Основные СУБД:**
98
+ - PostgreSQL
99
+ - MySQL / MariaDB
100
+ - MongoDB
101
+
102
+ **Инструменты администрирования:**
103
+ - pgAdmin (PostgreSQL)
104
+ - phpMyAdmin (MySQL)
105
+ - DBeaver (универсальный)
106
+ - Командная строка: psql, mysql
107
+
108
+ **Ключевые задачи:**
109
+ 1. Настройка конфигурации
110
+ 2. Резервное копирование и восстановление
111
+ 3. Мониторинг производительности
112
+ 4. Оптимизация запросов
113
+ 5. Настройка репликации
114
+
115
+ **Библиотеки Python:**
116
+ - psycopg2 (PostgreSQL)
117
+ - mysql-connector-python (MySQL)
118
+ - SQLAlchemy (ORM)
119
+ - asyncpg (асинхронная)
120
+ """
121
+
122
+ GREETINGS = [
123
+ "Привет! Я OpenAirAI, научный ассистент от OpenRussianAI. Чем могу помочь? 🧠",
124
+ "Здравствуйте! OpenAirAI на связи. Спрашивайте о науке, технологиях или системах! 🚀",
125
+ "Приветствую! Я OpenAirAI, созданный в 2026 году. Готов помочь с научными вопросами! 🔬"
126
+ ]
127
+
128
+ # ===================================================================
129
+ # 3. ОЧИСТКА И РАСПОЗНАВАНИЕ
130
+ # ===================================================================
131
+
132
+ def clean_query(query):
133
+ """Очищает запрос от спама"""
134
+ query = re.sub(r'http[s]?://\S+', '', query)
135
+ query = re.sub(r'\S+@\S+', '', query)
136
+ query = re.sub(r'\+7\s*\(?\d{3}\)?\s*\d{3}\s*\d{2}\s*\d{2}', '', query)
137
+ query = re.sub(r'[^\w\s\.\?\!,;:]', ' ', query)
138
+ query = ' '.join(query.split())
139
+
140
+ if len(query) > 300:
141
+ sentences = re.split(r'[.!?]', query)
142
+ query = '. '.join(sentences[:2]) + '.' if sentences else query[:200]
143
+
144
+ return query.strip()
145
+
146
+ def detect_intent(query):
147
+ """Определяет тип вопроса"""
148
+ q = query.lower()
149
+
150
+ if any(w in q for w in ["компани", "openrussian", "openruss", "создатель", "сайт"]):
151
+ return "company"
152
+
153
+ if any(w in q for w in ["наук", "мисси", "план", "развити", "почему", "заинтерес", "суть"]):
154
+ return "science"
155
+
156
+ if any(w in q for w in ["систем", "баз", "данн", "linux", "администр", "сервер", "sql", "postgres", "mysql"]):
157
+ return "systems"
158
+
159
+ if any(w in q for w in ["привет", "здравств", "добр", "хай", "hi"]):
160
+ return "greeting"
161
+
162
+ return "general"
163
+
164
  # ===================================================================
165
+ # 4. НЕЙРОСЕТЬ
166
  # ===================================================================
167
 
168
  class NeuralChatbot:
 
172
  self.model = None
173
  self.generator = None
174
  self.is_loaded = False
175
+
 
 
 
 
 
 
 
176
  def load_model(self):
177
  with st.spinner("🧠 Загружаю нейросеть..."):
178
  try:
 
189
  tokenizer=self.tokenizer,
190
  device=0 if torch.cuda.is_available() else -1,
191
  max_length=200,
192
+ temperature=0.7,
 
193
  do_sample=True,
194
  pad_token_id=self.tokenizer.eos_token_id
195
  )
 
200
  st.warning(f"Не удалось загрузить нейросеть: {e}")
201
  return False
202
 
203
+ def generate(self, prompt):
 
204
  if not self.is_loaded:
205
+ return None
 
206
  try:
 
 
 
 
 
207
  response = self.generator(
208
  prompt,
209
+ max_new_tokens=150,
210
+ temperature=0.7,
211
+ do_sample=True
 
 
212
  )[0]['generated_text']
213
+ return response.replace(prompt, "").strip()
214
+ except:
215
+ return None
 
 
 
 
 
 
 
 
 
 
216
 
217
  # ===================================================================
218
+ # 5. ЗАГРУЗКА СТАТЕЙ
219
  # ===================================================================
220
 
221
  @st.cache_resource
 
239
  "text": text[:2000],
240
  "source": "ru_science"
241
  })
 
242
  with open(ARTICLES_FILE, 'wb') as f:
243
  pickle.dump(articles, f)
244
  return articles
245
+ except Exception as e:
246
+ st.warning(f"Не удалось загрузить статьи: {e}")
247
  return []
248
 
249
  @st.cache_resource
 
254
  def create_embeddings(_articles, _embedder):
255
  if os.path.exists(EMBEDDINGS_FILE):
256
  return np.load(EMBEDDINGS_FILE)
 
257
  if not _articles:
258
  return np.array([])
 
259
  texts = [f"{a['title']}\n\n{a['text']}" for a in _articles]
260
+ embeddings = _embedder.encode(texts, normalize_embeddings=True, show_progress_bar=True, batch_size=64)
 
 
 
 
 
 
261
  np.save(EMBEDDINGS_FILE, embeddings)
262
  return embeddings
263
 
264
  def search_articles(query, _articles, _embeddings, _embedder):
265
  if not _articles or len(_embeddings) == 0:
266
  return []
 
267
  query_vector = _embedder.encode([query], normalize_embeddings=True)[0]
268
  scores = _embeddings @ query_vector
269
  top_indices = np.argsort(-scores)[:2]
 
270
  results = []
271
  for idx in top_indices:
272
  score = float(scores[int(idx)])
273
  if score > 0.15:
274
  article = _articles[int(idx)]
275
+ results.append({"title": article['title'], "score": score, "text": article['text'][:500]})
 
 
 
 
 
276
  return results
277
 
278
  # ===================================================================
279
+ # 6. ОСНОВНОЙ КЛАСС
280
  # ===================================================================
281
 
282
  class OpenAirAI:
 
284
  self.name = AI_NAME
285
  self.company = COMPANY_NAME
286
  self.creators = CREATORS
 
287
  self.chatbot = NeuralChatbot()
288
  self.is_ready = False
289
 
 
291
  self.is_ready = self.chatbot.load_model()
292
  return self.is_ready
293
 
294
+ def generate_answer(self, query):
295
+ clean_q = clean_query(query)
296
+ intent = detect_intent(clean_q)
 
 
297
 
298
+ if intent == "science":
299
+ return SCIENCE_ANSWER
300
+ elif intent == "company":
301
+ return COMPANY_INFO
302
+ elif intent == "systems":
303
+ return SYSTEMS_ANSWER
304
+ elif intent == "greeting":
305
+ return random.choice(GREETINGS)
306
+ else:
307
+ generated = self.chatbot.generate(clean_q)
308
+ if generated and len(generated) > 20:
309
+ return generated
310
+ else:
311
+ return f"Я {self.name} от {self.company}. Я помогаю с научными вопросами, системным администрированием и поиском информации. Что вас интересует? 🧠"
312
 
313
  # ===================================================================
314
+ # 7. ИНТЕРФЕЙС
315
  # ===================================================================
316
 
 
317
  articles = load_science_articles()
318
  embedder = load_embedder()
319
  embeddings = create_embeddings(articles, embedder)
320
 
 
321
  if 'ai' not in st.session_state:
322
  st.session_state.ai = OpenAirAI()
323
  st.session_state.ai.initialize()
324
 
325
  ai = st.session_state.ai
326
 
 
327
  if "messages" not in st.session_state:
328
  st.session_state.messages = []
329
+ greeting = ai.generate_answer("Привет")
330
  st.session_state.messages.append({"role": "assistant", "content": greeting})
331
 
 
332
  with st.sidebar:
333
  st.image("https://cdn-icons-png.flaticon.com/512/4248/4248455.png", width=80)
334
  st.title(f"🧠 {AI_NAME}")
335
 
336
  st.markdown(f"""
337
+ **{ai.name}** | {CREATION_DATE}
338
 
339
  **Компания:** {ai.company}
340
  **Разработчики:** {', '.join(ai.creators)}
 
352
 
353
  if st.button("🗑️ Очистить чат"):
354
  st.session_state.messages = []
355
+ greeting = ai.generate_answer("Привет")
356
  st.session_state.messages.append({"role": "assistant", "content": greeting})
357
  st.rerun()
358
 
 
359
  st.title(f"🧠 {AI_NAME} - Научный AI-ассистент")
360
+ st.markdown(f"**{AI_NAME}** от **{COMPANY_NAME}**")
361
 
 
362
  for message in st.session_state.messages:
363
  with st.chat_message(message["role"]):
364
  st.markdown(message["content"])
365
 
 
366
  if prompt := st.chat_input("Задайте вопрос..."):
367
  st.session_state.messages.append({"role": "user", "content": prompt})
368
  with st.chat_message("user"):
369
  st.markdown(prompt)
370
 
371
  with st.chat_message("assistant"):
372
+ with st.spinner("🧠 Думаю..."):
 
 
 
 
373
  response = ai.generate_answer(prompt)
374
 
375
+ if len(prompt) > 10 and not any(w in prompt.lower() for w in ["привет", "компани", "openrussian"]):
376
+ articles_context = search_articles(prompt, articles, embeddings, embedder)
377
+ if articles_context and len(response) < 100:
378
+ response += "\n\n**📄 Релевантные статьи:**\n"
379
+ for i, art in enumerate(articles_context, 1):
380
+ response += f"{i}. {art['title']}\n"
381
 
382
  st.markdown(response)
383
  st.session_state.messages.append({"role": "assistant", "content": response})
384
 
385
  st.rerun()
386
 
 
387
  st.divider()
388
  st.caption(f"🧠 {AI_NAME} от {COMPANY_NAME} | Создан в {CREATION_DATE}")