Files changed (1) hide show
  1. app.py +214 -91
app.py CHANGED
@@ -1,125 +1,248 @@
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:
43
- break
44
- articles.append({
45
- "id": row.get("id", i),
46
- "title": row.get("title", "Без названия"),
47
- "text": row.get("text", "")[:3000],
48
- "url": row.get("url", "")
49
- })
50
-
51
- return articles
52
-
53
- @st.cache_resource
54
  def load_embedder():
55
  """Загружает модель для эмбеддингов"""
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 gradio as gr
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
 
10
  # ===================================================================
11
+ # 1. НАСТРОЙКИ И ЗАГРУЗКА ДАННЫХ
12
  # ===================================================================
13
 
14
  MODEL_NAME = "BAAI/bge-large-en-v1.5"
15
  DATASET_NAME = "wikimedia/wikipedia"
16
+ LANGUAGE = "20231101.en"
17
+ ARTICLE_LIMIT = 1000
18
 
19
+ ADMIN_USER = "admin"
20
+ ADMIN_PASS = "hfpassword21"
 
 
 
21
 
22
+ # Файл для хранения логов
23
+ LOG_FILE = "query_logs.json"
24
 
25
+ # Загружаем логи или создаём новый файл
26
+ if os.path.exists(LOG_FILE):
27
+ with open(LOG_FILE, "r") as f:
28
+ query_logs = json.load(f)
29
+ else:
30
+ query_logs = []
31
 
32
+ @gr.cache_resource
33
  def load_wikipedia():
34
  """Загружает датасет Википедии"""
35
+ dataset = load_dataset(DATASET_NAME, LANGUAGE, split="train", streaming=True)
36
+ articles = []
37
+ for i, row in enumerate(dataset):
38
+ if i >= ARTICLE_LIMIT:
39
+ break
40
+ articles.append({
41
+ "id": row.get("id", i),
42
+ "title": row.get("title", "Без названия"),
43
+ "text": row.get("text", "")[:3000],
44
+ "url": row.get("url", "")
45
+ })
46
+ return articles
47
+
48
+ @gr.cache_resource
 
 
 
 
 
 
 
 
49
  def load_embedder():
50
  """Загружает модель для эмбеддингов"""
51
+ return SentenceTransformer(MODEL_NAME)
52
+
53
+ articles = load_wikipedia()
54
+ embedder = load_embedder()
55
+
56
+ # Создаём эмбеддинги статей заранее
57
+ texts = [f"{a['title']}\n\n{a['text']}" for a in articles]
58
+ embeddings = embedder.encode(texts, normalize_embeddings=True)
59
 
60
  # ===================================================================
61
+ # 2. ОСНОВНЫЕ ФУНКЦИИ
62
  # ===================================================================
63
 
64
+ def search_wikipedia(query, history):
65
+ """Ищет ответ на вопрос в Википедии и логирует запрос"""
66
+ if not query:
67
+ return "❓ Введите вопрос", history
68
+
69
+ start_time = time.time()
 
 
 
 
 
 
 
 
 
 
 
70
 
71
+ # Поиск
72
+ query_vector = embedder.encode([query], normalize_embeddings=True)[0]
73
+ scores = embeddings @ query_vector
74
  top_indices = np.argsort(-scores)[:3]
75
+
76
  results = []
77
  for idx in top_indices:
78
+ score = float(scores[int(idx)])
79
+ if score > 0.3:
80
+ article = articles[int(idx)]
81
+ results.append(
82
+ f"### 📄 {article['title']} (сходство: {score:.2f})\n"
83
+ f"{article['text'][:1000]}...\n"
84
+ f"🔗 [Читать на Википедии]({article['url']})\n"
85
+ )
86
+
87
+ response = "\n---\n".join(results) if results else "😕 Не нашёл подходящей статьи. Попробуй уточнить вопрос."
88
+
89
+ # Логируем запрос
90
+ log_entry = {
91
+ "timestamp": datetime.now().isoformat(),
92
+ "query": query,
93
+ "results_count": len(results),
94
+ "response_time": round(time.time() - start_time, 2)
95
+ }
96
+ query_logs.append(log_entry)
97
+
98
+ # Сохраняем логи в файл
99
+ with open(LOG_FILE, "w") as f:
100
+ json.dump(query_logs[-100:], f) # Храним последние 100 запросов
101
+
102
+ # Обновляем историю для админ-панели
103
+ history = query_logs[-20:] # Последние 20 запросов
104
+
105
+ return response, history
106
+
107
+ def login(username, password):
108
+ if username == ADMIN_USER and password == ADMIN_PASS:
109
+ return gr.update(visible=True), "✅ Доступ разрешён", gr.update(visible=True)
110
+ return gr.update(visible=False), "❌ Неверный логин или пароль", gr.update(visible=False)
111
+
112
+ def get_admin_stats():
113
+ """Собирает статистику для админ-панели"""
114
+ total_queries = len(query_logs)
115
 
116
+ if total_queries > 0:
117
+ avg_time = sum(q["response_time"] for q in query_logs) / total_queries
118
+ popular_queries = sorted(query_logs, key=lambda x: x["results_count"], reverse=True)[:5]
119
+ else:
120
+ avg_time = 0
121
+ popular_queries = []
122
+
123
+ # Формируем отчёт
124
+ stats = f"""
125
+ ## 📊 Статистика
126
+ - **Всего запросов:** {total_queries}
127
+ - **Среднее время ответа:** {avg_time:.2f} сек.
128
+ - **Загружено статей:** {len(articles)}
129
+ - **Модель:** {MODEL_NAME}
130
+
131
+ ## 🔥 Популярные запросы
132
+ """
133
+ for i, q in enumerate(popular_queries, 1):
134
+ stats += f"{i}. {q['query']} (найдено: {q['results_count']})\n"
135
+
136
+ return stats
137
+
138
+ def get_recent_logs():
139
+ """Показывает последние 10 запросов"""
140
+ if not query_logs:
141
+ return "📭 Логов пока нет"
142
+
143
+ logs = "## 📋 Последние запросы\n\n"
144
+ for q in query_logs[-10:]:
145
+ logs += f"**{q['timestamp']}**\n"
146
+ logs += f"Вопрос: {q['query']}\n"
147
+ logs += f"Результатов: {q['results_count']}, Время: {q['response_time']}с\n\n"
148
+
149
+ return logs
150
+
151
+ def clear_logs():
152
+ """Очищает логи"""
153
+ global query_logs
154
+ query_logs = []
155
+ with open(LOG_FILE, "w") as f:
156
+ json.dump(query_logs, f)
157
+ return "✅ Логи очищены", "Логов пока нет"
158
 
159
  # ===================================================================
160
+ # 3. ИНТЕРФЕЙС GRADIO
161
  # ===================================================================
162
 
163
+ with gr.Blocks(theme=gr.themes.Soft(), title="Wikipedia Assistant") as demo:
164
+ gr.Markdown("# 🌍 Wikipedia Assistant")
165
+ gr.Markdown("Задай вопрос — я найду ответ в Википедии!")
166
+
167
+ # Состояние для истории запросов
168
+ history_state = gr.State([])
169
+
170
+ with gr.Row():
171
+ with gr.Column(scale=4):
172
+ query_input = gr.Textbox(
173
+ label="🔍 Что хочешь узнать?",
174
+ placeholder="Например: Как измеряют расстояние до галактик?",
175
+ lines=2
176
+ )
177
+ with gr.Row():
178
+ search_btn = gr.Button("🔎 Найти", variant="primary")
179
+ clear_btn = gr.Button("🗑️ Очистить", variant="secondary")
180
+ output = gr.Markdown("💡 Напиши вопрос и нажми 'Найти'")
181
 
182
+ with gr.Column(scale=1):
183
+ gr.Markdown("### 👤 Вход в админку")
184
+ username = gr.Textbox(label="Логин", placeholder="admin")
185
+ password = gr.Textbox(label="Пароль", type="password", placeholder="hfpassword21")
186
+ login_btn = gr.Button("🔑 Войти", variant="primary")
187
+ status = gr.Textbox(label="Статус", interactive=False)
188
 
189
+ # Админ-панель (скрыта по умолчанию)
190
+ with gr.Tab("👑 Админ-панель", visible=False) as admin_tab:
191
+ with gr.Row():
192
+ with gr.Column(scale=2):
193
+ stats_output = gr.Markdown("## 📊 Загрузка статистики...")
194
+ refresh_stats_btn = gr.Button("🔄 Обновить статистику")
195
+ with gr.Column(scale=3):
196
+ logs_output = gr.Markdown("📭 Загрузка логов...")
197
+ with gr.Row():
198
+ refresh_logs_btn = gr.Button("🔄 Обновить логи")
199
+ clear_logs_btn = gr.Button("🗑️ Очистить логи", variant="stop")
200
 
201
+ # ===================================================================
202
+ # 4. ОБРАБОТЧИКИ СОБЫТИЙ
203
+ # ===================================================================
204
+
205
+ # Поиск
206
+ search_btn.click(
207
+ search_wikipedia,
208
+ inputs=[query_input, history_state],
209
+ outputs=[output, history_state]
210
+ )
211
+ query_input.submit(
212
+ search_wikipedia,
213
+ inputs=[query_input, history_state],
214
+ outputs=[output, history_state]
215
+ )
216
 
217
+ # Очистка
218
+ clear_btn.click(lambda: ("", "💡 Напиши вопрос и нажми 'Найти'"), None, [query_input, output])
219
+
220
+ # Вход в админку
221
+ login_btn.click(
222
+ login,
223
+ [username, password],
224
+ [admin_tab, status, gr.update(visible=True)]
225
+ )
226
+
227
+ # Обновление статистики
228
+ refresh_stats_btn.click(get_admin_stats, None, stats_output)
229
+
230
+ # Обновление логов
231
+ refresh_logs_btn.click(get_recent_logs, None, logs_output)
232
+
233
+ # Очистка логов
234
+ clear_logs_btn.click(clear_logs, None, [logs_output, logs_output])
235
+
236
+ # Автообновление при входе
237
+ admin_tab.select(
238
+ lambda: (get_admin_stats(), get_recent_logs()),
239
+ None,
240
+ [stats_output, logs_output]
241
+ )
242
+
243
+ # ===================================================================
244
+ # 5. ЗАПУСК
245
+ # ===================================================================
246
+
247
+ if __name__ == "__main__":
248
+ demo.launch(share=True)