Files changed (1) hide show
  1. app.py +214 -61
app.py CHANGED
@@ -1,42 +1,51 @@
1
  import streamlit as st
2
  from datasets import load_dataset
3
  import numpy as np
4
- import torch
5
  from sentence_transformers import SentenceTransformer
 
 
 
 
 
6
 
7
  # ===================================================================
8
- # 1. НАСТРОЙКИ
9
  # ===================================================================
10
 
11
  MODEL_NAME = "BAAI/bge-large-en-v1.5"
12
  DATASET_NAME = "wikimedia/wikipedia"
13
- LANGUAGE = "20231101.en" # можно заменить на "20231101.ru" для русского
14
- ARTICLE_LIMIT = 1000 # сколько статей загрузить (для экономии памяти)
15
 
 
 
 
 
 
 
16
  st.set_page_config(
17
  page_title="Wikipedia Assistant",
18
  page_icon="🌖",
19
- layout="wide"
 
20
  )
21
 
22
- st.title("🌖 Wikipedia Assistant")
23
- st.markdown("Задай вопрос — я найду ответ в Википедии!")
 
 
 
 
24
 
25
  # ===================================================================
26
- # 2. ЗАГРУЗКА ДАТАСЕТА
27
  # ===================================================================
28
 
29
  @st.cache_resource
30
  def load_wikipedia():
31
  """Загружает датасет Википедии"""
32
  with st.spinner("📚 Загружаю Википедию..."):
33
- dataset = load_dataset(
34
- DATASET_NAME,
35
- LANGUAGE,
36
- split="train",
37
- streaming=True
38
- )
39
-
40
  articles = []
41
  for i, row in enumerate(dataset):
42
  if i >= ARTICLE_LIMIT:
@@ -47,7 +56,6 @@ def load_wikipedia():
47
  "text": row.get("text", "")[:3000],
48
  "url": row.get("url", "")
49
  })
50
-
51
  return articles
52
 
53
  @st.cache_resource
@@ -56,70 +64,215 @@ def load_embedder():
56
  with st.spinner("🧠 Загружаю модель..."):
57
  return SentenceTransformer(MODEL_NAME)
58
 
 
 
 
 
 
 
 
 
 
 
 
 
59
  # ===================================================================
60
- # 3. ПОИСК
61
  # ===================================================================
62
 
63
- def search_wikipedia(query, articles, embedder):
64
  """Ищет ответ на вопрос в Википедии"""
65
- # Кодируем запрос
66
- query_vector = embedder.encode(
67
- [query],
68
- normalize_embeddings=True
69
- )[0]
70
-
71
- # Кодируем все статьи (если ещё не закодированы)
72
- if "embeddings" not in st.session_state:
73
- with st.spinner("🔢 Создаю эмбеддинги статей..."):
74
- texts = [f"{a['title']}\n\n{a['text']}" for a in articles]
75
- embeddings = embedder.encode(
76
- texts,
77
- normalize_embeddings=True
78
- )
79
- st.session_state.embeddings = embeddings
80
 
81
- # Ищем
82
- scores = st.session_state.embeddings @ query_vector
 
83
  top_indices = np.argsort(-scores)[:3]
84
-
85
  results = []
86
  for idx in top_indices:
87
- results.append({
88
- "article": articles[int(idx)],
89
- "score": float(scores[int(idx)])
90
- })
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
91
 
92
  return results
93
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
94
  # ===================================================================
95
  # 4. ИНТЕРФЕЙС
96
  # ===================================================================
97
 
98
- # Загружаем данные
99
- articles = load_wikipedia()
100
- embedder = load_embedder()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
101
 
 
102
  st.success(f"✅ Загружено {len(articles)} статей")
103
 
104
- # Поле ввода
105
- query = st.text_input("🔍 Что хочешь узнать?", placeholder="Например: Как измеряют расстояние до галактик?")
 
 
 
 
 
 
 
 
106
 
107
- if query:
 
 
 
108
  with st.spinner("🔎 Ищу ответ..."):
109
- results = search_wikipedia(query, articles, embedder)
110
 
111
- # Показываем результаты
112
- for i, result in enumerate(results, 1):
113
- score = result["score"]
114
- article = result["article"]
 
 
 
 
115
 
116
- # Только если релевантно
117
- if score > 0.3:
118
- with st.expander(f"#{i} {article['title']} (сходство: {score:.2f})"):
119
- st.write(article["text"][:2000] + "...")
120
- if article["url"]:
121
- st.link_button("🔗 Читать на Википедии", article["url"])
122
- else:
123
- st.warning("😕 Не нашёл подходящей статьи. Попробуй уточнить вопрос.")
124
- else:
125
- st.info("💡 Напиши вопрос, и я найду ответ в Википедии")
 
 
 
 
 
 
1
  import streamlit as st
2
  from datasets import load_dataset
3
  import numpy as np
 
4
  from sentence_transformers import SentenceTransformer
5
+ import time
6
+ from datetime import datetime
7
+ import json
8
+ import os
9
+ import pandas as pd
10
 
11
  # ===================================================================
12
+ # 1. НАСТРОЙКИ И ЗАГРУЗКА ДАННЫХ
13
  # ===================================================================
14
 
15
  MODEL_NAME = "BAAI/bge-large-en-v1.5"
16
  DATASET_NAME = "wikimedia/wikipedia"
17
+ LANGUAGE = "20231101.en"
18
+ ARTICLE_LIMIT = 1000
19
 
20
+ ADMIN_USER = "admin"
21
+ ADMIN_PASS = "hfpassword21"
22
+
23
+ LOG_FILE = "query_logs.json"
24
+
25
+ # Настройка страницы
26
  st.set_page_config(
27
  page_title="Wikipedia Assistant",
28
  page_icon="🌖",
29
+ layout="wide",
30
+ initial_sidebar_state="expanded"
31
  )
32
 
33
+ # Загружаем логи
34
+ if os.path.exists(LOG_FILE):
35
+ with open(LOG_FILE, "r") as f:
36
+ query_logs = json.load(f)
37
+ else:
38
+ query_logs = []
39
 
40
  # ===================================================================
41
+ # 2. ЗАГРУЗКА ДАННЫХ (с кэшированием)
42
  # ===================================================================
43
 
44
  @st.cache_resource
45
  def load_wikipedia():
46
  """Загружает датасет Википедии"""
47
  with st.spinner("📚 Загружаю Википедию..."):
48
+ dataset = load_dataset(DATASET_NAME, LANGUAGE, split="train", streaming=True)
 
 
 
 
 
 
49
  articles = []
50
  for i, row in enumerate(dataset):
51
  if i >= ARTICLE_LIMIT:
 
56
  "text": row.get("text", "")[:3000],
57
  "url": row.get("url", "")
58
  })
 
59
  return articles
60
 
61
  @st.cache_resource
 
64
  with st.spinner("🧠 Загружаю модель..."):
65
  return SentenceTransformer(MODEL_NAME)
66
 
67
+ @st.cache_resource
68
+ def create_embeddings(articles, embedder):
69
+ """Создаёт эмбеддинги статей"""
70
+ with st.spinner("🔢 Создаю эмбеддинги статей..."):
71
+ texts = [f"{a['title']}\n\n{a['text']}" for a in articles]
72
+ return embedder.encode(texts, normalize_embeddings=True)
73
+
74
+ # Загружаем всё
75
+ articles = load_wikipedia()
76
+ embedder = load_embedder()
77
+ embeddings = create_embeddings(articles, embedder)
78
+
79
  # ===================================================================
80
+ # 3. ФУНКЦИИ ПОИСКА И ЛОГИРОВАНИЯ
81
  # ===================================================================
82
 
83
+ def search_wikipedia(query):
84
  """Ищет ответ на вопрос в Википедии"""
85
+ if not query:
86
+ return None
87
+
88
+ start_time = time.time()
 
 
 
 
 
 
 
 
 
 
 
89
 
90
+ # Поиск
91
+ query_vector = embedder.encode([query], normalize_embeddings=True)[0]
92
+ scores = embeddings @ query_vector
93
  top_indices = np.argsort(-scores)[:3]
94
+
95
  results = []
96
  for idx in top_indices:
97
+ score = float(scores[int(idx)])
98
+ if score > 0.3:
99
+ article = articles[int(idx)]
100
+ results.append({
101
+ "title": article['title'],
102
+ "score": score,
103
+ "text": article['text'][:1000],
104
+ "url": article['url']
105
+ })
106
+
107
+ # Логируем запрос
108
+ log_entry = {
109
+ "timestamp": datetime.now().isoformat(),
110
+ "query": query,
111
+ "results_count": len(results),
112
+ "response_time": round(time.time() - start_time, 2)
113
+ }
114
+ query_logs.append(log_entry)
115
+
116
+ # Сохраняем логи
117
+ with open(LOG_FILE, "w") as f:
118
+ json.dump(query_logs[-100:], f)
119
 
120
  return results
121
 
122
+ def get_admin_stats():
123
+ """Собирает статистику для админ-панели"""
124
+ total_queries = len(query_logs)
125
+
126
+ if total_queries > 0:
127
+ avg_time = sum(q["response_time"] for q in query_logs) / total_queries
128
+ popular_queries = sorted(query_logs, key=lambda x: x["results_count"], reverse=True)[:5]
129
+ else:
130
+ avg_time = 0
131
+ popular_queries = []
132
+
133
+ return {
134
+ "total_queries": total_queries,
135
+ "avg_time": avg_time,
136
+ "popular_queries": popular_queries,
137
+ "articles_count": len(articles),
138
+ "model_name": MODEL_NAME
139
+ }
140
+
141
+ def clear_logs():
142
+ """Очищает логи"""
143
+ global query_logs
144
+ query_logs = []
145
+ with open(LOG_FILE, "w") as f:
146
+ json.dump(query_logs, f)
147
+ return True
148
+
149
  # ===================================================================
150
  # 4. ИНТЕРФЕЙС
151
  # ===================================================================
152
 
153
+ # --- БОКОВАЯ ПАНЕЛЬ (АДМИНКА) ---
154
+ with st.sidebar:
155
+ st.image("https://cdn-icons-png.flaticon.com/512/4248/4248455.png", width=80)
156
+ st.title("👑 Админ-панель")
157
+
158
+ # Вход
159
+ if "logged_in" not in st.session_state:
160
+ st.session_state.logged_in = False
161
+
162
+ if not st.session_state.logged_in:
163
+ with st.form("login_form"):
164
+ username = st.text_input("👤 Логин", placeholder="admin")
165
+ password = st.text_input("🔑 Пароль", type="password", placeholder="hfpassword21")
166
+ submitted = st.form_submit_button("🔑 Войти")
167
+
168
+ if submitted:
169
+ if username == ADMIN_USER and password == ADMIN_PASS:
170
+ st.session_state.logged_in = True
171
+ st.success("✅ Доступ разрешён!")
172
+ st.rerun()
173
+ else:
174
+ st.error("❌ Неверный логин или пароль")
175
+ else:
176
+ st.success("✅ Вы вошли как администратор")
177
+
178
+ # Кнопка выхода
179
+ if st.button("🚪 Выйти"):
180
+ st.session_state.logged_in = False
181
+ st.rerun()
182
+
183
+ st.divider()
184
+
185
+ # Статистика
186
+ st.subheader("📊 Статистика")
187
+ stats = get_admin_stats()
188
+
189
+ col1, col2 = st.columns(2)
190
+ with col1:
191
+ st.metric("Всего запросов", stats["total_queries"])
192
+ st.metric("Загружено статей", stats["articles_count"])
193
+ with col2:
194
+ st.metric("Ср. время ответа", f"{stats['avg_time']:.2f}с")
195
+ st.metric("Модель", stats["model_name"].split("/")[-1])
196
+
197
+ # Популярные запросы
198
+ if stats["popular_queries"]:
199
+ st.subheader("🔥 Топ-5 запросов")
200
+ for i, q in enumerate(stats["popular_queries"], 1):
201
+ st.write(f"{i}. **{q['query']}** (найдено: {q['results_count']})")
202
+
203
+ st.divider()
204
+
205
+ # Логи
206
+ st.subheader("📋 Последние запросы")
207
+ if query_logs:
208
+ # Показываем последние 10 в виде таблицы
209
+ df = pd.DataFrame(query_logs[-10:])
210
+ df["timestamp"] = pd.to_datetime(df["timestamp"]).dt.strftime("%H:%M:%S")
211
+ st.dataframe(
212
+ df[["timestamp", "query", "results_count", "response_time"]],
213
+ column_config={
214
+ "timestamp": "Время",
215
+ "query": "Запрос",
216
+ "results_count": "Результатов",
217
+ "response_time": "Время (с)"
218
+ },
219
+ use_container_width=True,
220
+ hide_index=True
221
+ )
222
+
223
+ if st.button("🗑️ Очистить логи", type="secondary"):
224
+ clear_logs()
225
+ st.success("Логи очищены!")
226
+ st.rerun()
227
+ else:
228
+ st.info("📭 Логов пока нет")
229
+
230
+ # --- ОСНОВНАЯ ЧАСТЬ ---
231
+ st.title("🌖 Wikipedia Assistant")
232
+ st.markdown("Задай вопрос — я найду ответ в Википедии!")
233
 
234
+ # Информация о загрузке
235
  st.success(f"✅ Загружено {len(articles)} статей")
236
 
237
+ # Поиск
238
+ query = st.text_input(
239
+ "🔍 Что хочешь узнать?",
240
+ placeholder="Например: Как измеряют расстояние до галактик?",
241
+ key="query_input"
242
+ )
243
+
244
+ col1, col2 = st.columns([1, 5])
245
+ with col1:
246
+ search_clicked = st.button("🔎 Найти", type="primary", use_container_width=True)
247
 
248
+ # Выполняем поиск
249
+ if query and (search_clicked or query != st.session_state.get("last_query", "")):
250
+ st.session_state.last_query = query
251
+
252
  with st.spinner("🔎 Ищу ответ..."):
253
+ results = search_wikipedia(query)
254
 
255
+ if results:
256
+ for i, result in enumerate(results, 1):
257
+ with st.expander(f"#{i} {result['title']} (сходство: {result['score']:.2f})", expanded=i==1):
258
+ st.write(result['text'] + "...")
259
+ if result['url']:
260
+ st.link_button("🔗 Читать на Википедии", result['url'])
261
+ else:
262
+ st.warning("😕 Не нашёл подходящей статьи. Попробуй уточнить вопрос.")
263
 
264
+ elif not query:
265
+ st.info("💡 Напиши вопрос, и я найду ответ в Википедии")
266
+
267
+ # ===================================================================
268
+ # 5. ДОПОЛНИТЕЛЬНЫЕ ВОЗМОЖНОСТИ
269
+ # ===================================================================
270
+
271
+ # Подвал
272
+ st.divider()
273
+ st.caption("🌖 Wikipedia Assistant | Использует BAAI/bge-large-en-v1.5")
274
+
275
+ # Кнопка внизу для переключения темы
276
+ if st.button("🎨 Сменить тему"):
277
+ st.session_state.theme = "dark" if st.session_state.get("theme") != "dark" else "light"
278
+ st.rerun()