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

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +106 -134
app.py CHANGED
@@ -4,13 +4,14 @@ import numpy as np
4
  import torch
5
  import torch.nn as nn
6
  import torch.nn.functional as F
7
- from transformers import AutoTokenizer, AutoModel, AutoModelForCausalLM, pipeline
8
  from sentence_transformers import SentenceTransformer
9
  import time
10
  from datetime import datetime
11
  import json
12
  import os
13
  import pickle
 
14
  import warnings
15
  warnings.filterwarnings('ignore')
16
 
@@ -23,21 +24,19 @@ print(f"CUDA доступна: {torch.cuda.is_available()}")
23
  if torch.cuda.is_available():
24
  print(f"GPU: {torch.cuda.get_device_name(0)}")
25
 
26
- # Используем русскую генеративную модель
27
- MODEL_NAME = "ai-forever/rugpt3small_based_on_gpt2" # Маленькая, но работает
28
- # Или более мощная: "ai-forever/rugpt3medium_based_on_gpt2" (больше, медленнее)
29
-
30
- EMBEDDING_MODEL = "all-MiniLM-L6-v2"
31
  SCIENCE_DATASET = "RafaelUI/ru_science"
32
- ARTICLE_LIMIT = 50 # Меньше для скорости
33
  MAX_LENGTH = 512
34
  TEMPERATURE = 0.7
35
  TOP_P = 0.9
36
 
 
37
  LOG_FILE = "query_logs.json"
38
  EMBEDDINGS_FILE = "science_embeddings.npy"
39
  ARTICLES_FILE = "science_articles.pkl"
40
- GPT_MODEL_PATH = "openairai_gpt_model"
41
 
42
  # Информация о создателях
43
  AI_NAME = "OpenAirAI"
@@ -49,19 +48,43 @@ CREATION_DATE = "2026"
49
 
50
  # Настройка страницы
51
  st.set_page_config(
52
- page_title=f"{AI_NAME} - Нейросетевой AI-ассистент",
53
  page_icon="🧠",
54
  layout="wide",
55
  initial_sidebar_state="expanded"
56
  )
57
 
58
  # ===================================================================
59
- # 2. НАСТОЯЩАЯ НЕЙРОСЕТЬ ДЛЯ ГЕНЕРАЦИИ
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
60
  # ===================================================================
61
 
62
  class NeuralChatbot:
63
- """Настоящая нейросеть для генерации ответов"""
64
-
65
  def __init__(self):
66
  self.device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
67
  self.tokenizer = None
@@ -69,20 +92,16 @@ class NeuralChatbot:
69
  self.generator = None
70
  self.is_loaded = False
71
 
72
- # Контекст для генерации
73
  self.system_prompt = f"""��ы - {AI_NAME}, научный AI-ассистент от компании {COMPANY_NAME}.
74
- Ты был создан в {CREATION_DATE} командой разработчиков {', '.join(CREATORS)}.
75
- Ты помогаешь людям с научными вопросами, анализируешь информацию и даёшь полезные ответы.
76
- Отвечай на русском языке, будь дружелюбным и профессиональным.
77
- Если не знаешь ответа - честно скажи об этом.
78
 
79
  Вот вопрос пользователя: """
80
 
81
  def load_model(self):
82
- """Загрузка генеративной нейросети"""
83
- with st.spinner("🧠 Загружаю нейросеть для генерации ответов..."):
84
  try:
85
- # Используем GPT для генерации
86
  from transformers import GPT2LMHeadModel, GPT2Tokenizer
87
 
88
  self.tokenizer = GPT2Tokenizer.from_pretrained(MODEL_NAME)
@@ -90,7 +109,6 @@ class NeuralChatbot:
90
  self.model.to(self.device)
91
  self.model.eval()
92
 
93
- # Создаем pipeline для генерации
94
  self.generator = pipeline(
95
  'text-generation',
96
  model=self.model,
@@ -104,24 +122,22 @@ class NeuralChatbot:
104
  )
105
 
106
  self.is_loaded = True
107
- st.success("✅ Нейросеть загружена!")
108
  return True
109
-
110
  except Exception as e:
111
- st.error(f" Ошибка загрузки нейросети: {e}")
112
- st.info("💡 Использую упрощенный режим")
113
  return False
114
 
115
  def generate_response(self, query):
116
- """Генерация ответа с помощью нейросети"""
117
  if not self.is_loaded:
118
- return self.generate_fallback_response(query)
119
 
120
  try:
121
- # Формируем промпт
122
- prompt = self.system_prompt + query
 
123
 
124
- # Генерируем ответ
125
  response = self.generator(
126
  prompt,
127
  max_new_tokens=300,
@@ -131,49 +147,30 @@ class NeuralChatbot:
131
  repetition_penalty=1.2
132
  )[0]['generated_text']
133
 
134
- # Убираем промпт из ответа
135
  response = response.replace(prompt, "").strip()
136
-
137
- # Если ответ пустой, используем fallback
138
- if not response or len(response) < 10:
139
- return self.generate_fallback_response(query)
140
-
141
- return response
142
 
143
  except Exception as e:
144
- print(f"Ошибка генерации: {e}")
145
- return self.generate_fallback_response(query)
146
 
147
- def generate_fallback_response(self, query):
148
- """Резервный ответ, если нейросеть не работает"""
149
- responses = [
150
- f"Я {AI_NAME}, нейросетевой ассистент от {COMPANY_NAME}. "
151
- f"К сожалению, сейчас я не могу сгенерировать полный ответ. "
152
- f"Попробуйте переформулировать вопрос или спросить позже. 🧠",
153
-
154
- f"Прошу прощения, я {AI_NAME}, и моя нейросеть временно недоступна. "
155
- f"Но я помню, что создан в {CREATION_DATE} командой {', '.join(CREATORS)}. "
156
- f"Чем ещё могу помочь? 🤖",
157
-
158
- f"Привет! Я {AI_NAME}. Моя нейросеть обрабатывает ваш запрос. "
159
- f"Пожалуйста, подождите немного или задайте другой вопрос. 🚀"
160
- ]
161
- return random.choice(responses)
162
 
163
  # ===================================================================
164
- # 3. ЗАГРУЗКА НАУЧНЫХ СТАТЕЙ (ДЛЯ КОНТЕКСТА)
165
  # ===================================================================
166
 
167
  @st.cache_resource
168
  def load_science_articles():
169
- articles_file = ARTICLES_FILE
 
 
170
 
171
- if os.path.exists(articles_file):
172
- with st.spinner("📚 Загружаю научные статьи с диска..."):
173
- with open(articles_file, 'rb') as f:
174
- return pickle.load(f)
175
-
176
- with st.spinner("📚 Загружаю научные статьи (первый раз)..."):
177
  try:
178
  dataset = load_dataset(SCIENCE_DATASET, split="train", streaming=True)
179
  articles = []
@@ -189,45 +186,36 @@ def load_science_articles():
189
  "source": "ru_science"
190
  })
191
 
192
- with open(articles_file, 'wb') as f:
193
  pickle.dump(articles, f)
194
-
195
  return articles
196
- except Exception as e:
197
- st.warning(f"Не удалось загрузить статьи: {e}")
198
  return []
199
 
200
  @st.cache_resource
201
  def load_embedder():
202
- with st.spinner("🧠 Загружаю модель для эмбеддингов..."):
203
- return SentenceTransformer(EMBEDDING_MODEL)
204
 
205
  @st.cache_resource
206
  def create_embeddings(_articles, _embedder):
207
- embeddings_file = EMBEDDINGS_FILE
208
-
209
- if os.path.exists(embeddings_file):
210
- with st.spinner("📊 Загружаю эмбеддинги с диска..."):
211
- return np.load(embeddings_file)
212
 
213
  if not _articles:
214
  return np.array([])
215
 
216
- with st.spinner(f"🔢 Создаю эмбеддинги для {len(_articles)} статей..."):
217
- texts = [f"{a['title']}\n\n{a['text']}" for a in _articles]
218
- embeddings = _embedder.encode(
219
- texts,
220
- normalize_embeddings=True,
221
- show_progress_bar=True,
222
- batch_size=64,
223
- device='cuda' if torch.cuda.is_available() else 'cpu'
224
- )
225
-
226
- np.save(embeddings_file, embeddings)
227
- return embeddings
228
 
229
  def search_articles(query, _articles, _embeddings, _embedder):
230
- """Поиск релевантных статей для контекста"""
231
  if not _articles or len(_embeddings) == 0:
232
  return []
233
 
@@ -246,14 +234,13 @@ def search_articles(query, _articles, _embeddings, _embedder):
246
  "text": article['text'][:500],
247
  "source": article.get('source', 'ru_science')
248
  })
249
-
250
  return results
251
 
252
  # ===================================================================
253
- # 4. ГЛАВНЫЙ КЛАСС ДЛЯ НЕЙРОСЕТИ
254
  # ===================================================================
255
 
256
- class OpenAirAINeural:
257
  def __init__(self):
258
  self.name = AI_NAME
259
  self.company = COMPANY_NAME
@@ -263,47 +250,38 @@ class OpenAirAINeural:
263
  self.is_ready = False
264
 
265
  def initialize(self):
266
- """Инициализация нейросети"""
267
  self.is_ready = self.chatbot.load_model()
268
  return self.is_ready
269
 
270
  def generate_answer(self, query, articles=None):
271
- """Генерация ответа с использованием контекста"""
 
 
 
272
 
273
- # Если есть статьи, добавляем их в контекст
274
- if articles and len(articles) > 0:
275
- context = "\n\nВот релевантные научные статьи:\n"
276
- for i, article in enumerate(articles[:2], 1):
277
- context += f"{i}. {article['title']}\n{article['text'][:300]}...\n"
278
-
279
- # Добавляем контекст к вопросу
280
- enhanced_query = f"{query}\n\n{context}\n\nНа основе этих статей, ответь на вопрос:"
281
- else:
282
- enhanced_query = query
283
-
284
- return self.chatbot.generate_response(enhanced_query)
285
 
286
  # ===================================================================
287
- # 5. ИНТЕРФЕЙС
288
  # ===================================================================
289
 
290
- # Загрузка данных
291
  articles = load_science_articles()
292
  embedder = load_embedder()
293
  embeddings = create_embeddings(articles, embedder)
294
 
295
- # Инициализация нейросети
296
- if 'neural_ai' not in st.session_state:
297
- st.session_state.neural_ai = OpenAirAINeural()
298
- st.session_state.neural_ai.initialize()
299
 
300
- neural_ai = st.session_state.neural_ai
301
 
302
- # Инициализация истории чата
303
  if "messages" not in st.session_state:
304
  st.session_state.messages = []
305
- # Приветствие генерируется нейросетью
306
- greeting = neural_ai.generate_answer("Привет! Представься и расскажи о себе кратко.")
307
  st.session_state.messages.append({"role": "assistant", "content": greeting})
308
 
309
  # --- БОКОВАЯ ПАНЕЛЬ ---
@@ -312,10 +290,10 @@ with st.sidebar:
312
  st.title(f"🧠 {AI_NAME}")
313
 
314
  st.markdown(f"""
315
- **{neural_ai.name}** | {neural_ai.creation_date}
316
 
317
- **Компания:** {neural_ai.company}
318
- **Разработчики:** {', '.join(neural_ai.creators)}
319
 
320
  ---
321
  **🔗 Ссылки:**
@@ -324,28 +302,18 @@ with st.sidebar:
324
 
325
  ---
326
  **📊 Статистика:**
327
- - Модель: {MODEL_NAME.split('/')[-1]}
328
  - Статей: {len(articles)}
329
  - Сообщений: {len(st.session_state.messages)}
330
  """)
331
 
332
- if os.path.exists(EMBEDDINGS_FILE):
333
- size = os.path.getsize(EMBEDDINGS_FILE) / (1024 * 1024)
334
- st.caption(f"💾 Эмбеддинги: {size:.1f} MB")
335
-
336
  if st.button("🗑️ Очистить чат"):
337
  st.session_state.messages = []
338
- greeting = neural_ai.generate_answer("Привет! Представься и расскажи о себе кратко.")
339
  st.session_state.messages.append({"role": "assistant", "content": greeting})
340
  st.rerun()
341
-
342
- if st.button("🔄 Перезагрузить нейросеть"):
343
- st.session_state.neural_ai = OpenAirAINeural()
344
- st.session_state.neural_ai.initialize()
345
- st.rerun()
346
 
347
  # --- ОСНОВНАЯ ЧАСТЬ ---
348
- st.title(f"🧠 {AI_NAME} - Нейросетевой AI-ассистент")
349
  st.markdown(f"**{AI_NAME}** от **{COMPANY_NAME}** | Генеративная нейросеть")
350
 
351
  # Отображение сообщений
@@ -355,19 +323,23 @@ for message in st.session_state.messages:
355
 
356
  # Поле ввода
357
  if prompt := st.chat_input("Задайте вопрос..."):
358
- # Добавляем сообщение пользователя
359
  st.session_state.messages.append({"role": "user", "content": prompt})
360
  with st.chat_message("user"):
361
  st.markdown(prompt)
362
 
363
- # Генерация ответа нейросетью
364
  with st.chat_message("assistant"):
365
  with st.spinner("🧠 Нейросеть думает..."):
366
- # Ищем релевантные статьи для контекста
367
  articles_context = search_articles(prompt, articles, embeddings, embedder)
368
 
369
- # Генерируем ответ с использованием контекста
370
- response = neural_ai.generate_answer(prompt, articles_context)
 
 
 
 
 
 
371
 
372
  st.markdown(response)
373
  st.session_state.messages.append({"role": "assistant", "content": response})
@@ -376,4 +348,4 @@ if prompt := st.chat_input("Задайте вопрос..."):
376
 
377
  # --- ПОДВАЛ ---
378
  st.divider()
379
- st.caption(f"🧠 {AI_NAME} от {COMPANY_NAME} | Создан в {CREATION_DATE} | Генеративная нейросеть")
 
4
  import torch
5
  import torch.nn as nn
6
  import torch.nn.functional as F
7
+ from transformers import AutoTokenizer, AutoModel, pipeline
8
  from sentence_transformers import SentenceTransformer
9
  import time
10
  from datetime import datetime
11
  import json
12
  import os
13
  import pickle
14
+ import random
15
  import warnings
16
  warnings.filterwarnings('ignore')
17
 
 
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"
 
48
 
49
  # Настройка страницы
50
  st.set_page_config(
51
+ page_title=f"{AI_NAME} - Научный AI-ассистент",
52
  page_icon="🧠",
53
  layout="wide",
54
  initial_sidebar_state="expanded"
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:
 
 
88
  def __init__(self):
89
  self.device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
90
  self.tokenizer = 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:
 
105
  from transformers import GPT2LMHeadModel, GPT2Tokenizer
106
 
107
  self.tokenizer = GPT2Tokenizer.from_pretrained(MODEL_NAME)
 
109
  self.model.to(self.device)
110
  self.model.eval()
111
 
 
112
  self.generator = pipeline(
113
  'text-generation',
114
  model=self.model,
 
122
  )
123
 
124
  self.is_loaded = True
 
125
  return True
 
126
  except Exception as e:
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,
 
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
168
  def load_science_articles():
169
+ if os.path.exists(ARTICLES_FILE):
170
+ with open(ARTICLES_FILE, 'rb') as f:
171
+ return pickle.load(f)
172
 
173
+ with st.spinner("📚 Загружаю научные статьи..."):
 
 
 
 
 
174
  try:
175
  dataset = load_dataset(SCIENCE_DATASET, split="train", streaming=True)
176
  articles = []
 
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
196
  def load_embedder():
197
+ return SentenceTransformer(EMBEDDING_MODEL)
 
198
 
199
  @st.cache_resource
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
 
 
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:
244
  def __init__(self):
245
  self.name = AI_NAME
246
  self.company = COMPANY_NAME
 
250
  self.is_ready = False
251
 
252
  def initialize(self):
 
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
  # --- БОКОВАЯ ПАНЕЛЬ ---
 
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)}
297
 
298
  ---
299
  **🔗 Ссылки:**
 
302
 
303
  ---
304
  **📊 Статистика:**
 
305
  - Статей: {len(articles)}
306
  - Сообщений: {len(st.session_state.messages)}
307
  """)
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
  # Отображение сообщений
 
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})
 
348
 
349
  # --- ПОДВАЛ ---
350
  st.divider()
351
+ st.caption(f"🧠 {AI_NAME} от {COMPANY_NAME} | Создан в {CREATION_DATE}")