loim commited on
Commit
6e00079
·
1 Parent(s): 995a61e

оптимизация

Browse files
Files changed (5) hide show
  1. .gitignore +1 -0
  2. app.py +21 -99
  3. data_loader.py +19 -0
  4. ui_components.py +232 -0
  5. utils.py +40 -0
.gitignore ADDED
@@ -0,0 +1 @@
 
 
1
+ /__pycache__
app.py CHANGED
@@ -1,111 +1,33 @@
1
  import streamlit as st
2
- from datasets import load_dataset
3
- import pandas as pd
 
4
 
5
  # Настройки страницы
6
  st.set_page_config(layout="wide", page_icon="🧙", page_title="Fantasy Characters Explorer")
7
 
8
  # Загрузка данных
9
- @st.cache_data
10
- def load_data():
11
- dataset = load_dataset("loim/ru_fantasy_characters")
12
- return pd.DataFrame(dataset["train"])
13
-
14
  df = load_data()
15
 
16
- # Функция для генерации RPG-карточки
17
- def generate_rpg_card(row):
18
- return f"""
19
- **Мир:** {row['world']}
20
-
21
- **Имя:** {row['name']}
22
- **Внешность:** {row['description']}
23
- **Характер:** {row['short_story']}
24
- **Речь:** {row['style']}
25
-
26
- **Первая реплика:**
27
- > {row['first_message']}
28
- """
29
-
30
- # Стили
31
- def local_css(file_name):
32
- with open(file_name) as f:
33
- st.markdown(f"<style>{f.read()}</style>", unsafe_allow_html=True)
34
-
35
- local_css("assets/styles.css")
36
-
37
- # --- САЙДБАР С ФИЛЬТРАМИ ---
38
- with st.sidebar:
39
- st.title("🔮 Фильтры")
40
- search_query = st.text_input("🔍 Поиск по имени/описанию", "")
41
- sort_option = st.selectbox(
42
- "Сортировка",
43
- ["По имени (А→Я)", "По имени (Я→А)", "По абсурдности (↑)", "По абсурдности (↓)"]
44
- )
45
- absurdity_range = st.slider("Уровень абсурдности", 0, 10, (0, 10))
46
- moods = ["Все"] + sorted(df["mood"].unique().tolist())
47
- selected_mood = st.selectbox("Настроение", moods)
48
- all_tags = sorted({tag for tags in df["tags"] for tag in tags.split()})
49
- selected_tags = st.multiselect("Теги", all_tags)
50
-
51
- # --- ФИЛЬТРАЦИЯ И СОРТИРОВКА ---
52
- filtered_df = df[
53
- (df["absurdity"] >= absurdity_range[0]) &
54
- (df["absurdity"] <= absurdity_range[1])
55
- ]
56
-
57
- if selected_mood != "Все":
58
- filtered_df = filtered_df[filtered_df["mood"] == selected_mood]
59
-
60
- if selected_tags:
61
- filtered_df = filtered_df[filtered_df["tags"].str.contains("|".join(selected_tags))]
62
-
63
- if search_query:
64
- filtered_df = filtered_df[
65
- filtered_df["name"].str.contains(search_query, case=False) |
66
- filtered_df["description"].str.contains(search_query, case=False) |
67
- filtered_df["short_story"].str.contains(search_query, case=False)
68
- ]
69
 
70
- # Применение сортировки
71
- if "А→Я" in sort_option:
72
- filtered_df = filtered_df.sort_values("name")
73
- elif "Я→А" in sort_option:
74
- filtered_df = filtered_df.sort_values("name", ascending=False)
75
- elif "↑" in sort_option:
76
- filtered_df = filtered_df.sort_values("absurdity")
77
- else:
78
- filtered_df = filtered_df.sort_values("absurdity", ascending=False)
79
 
80
- # --- ОТОБРАЖЕНИЕ РЕЗУЛЬТАТОВ ---
81
- st.title("🧙 Fantasy Characters Explorer")
82
- st.markdown(f"**Найдено персонажей:** {len(filtered_df)}")
83
 
84
- for idx, row in filtered_df.iterrows():
85
- with st.container():
86
- # Заголовок карточки
87
- st.markdown(f"### {row['name']}")
88
- st.caption(f"*{row['short_story']}*")
89
-
90
- # Основные колонки
91
- col1, col2 = st.columns([1, 3])
92
-
93
- with col1:
94
- st.markdown(f"**🌍 Мир:** {row['world']}")
95
- st.markdown(f"**🎭 Настроение:** `{row['mood']}`")
96
- st.markdown(f"**🌀 Абсурдность:** `{row['absurdity']}/10`")
97
- st.markdown(f"**🏷️ Теги:** `{' '.join(row['tags'].split())}`")
98
-
99
- with col2:
100
- st.markdown(f"**📖 Описание:** {row['description']}")
101
- st.markdown(f"**💬 Стиль речи:** *{row['style']}*")
102
-
103
- # RPG-карточка
104
- with st.expander("📋 RPG-карточка", expanded=False):
105
- rpg_card = generate_rpg_card(row)
106
- st.code(rpg_card, language="markdown")
107
-
108
- st.markdown("---")
109
 
110
- if len(filtered_df) == 0:
111
- st.warning("Персонажи не найдены. Попробуйте изменить фильтры.")
 
1
  import streamlit as st
2
+ from data_loader import load_data, get_unique_mood, get_unique_tags
3
+ from utils import apply_filters
4
+ from ui_components import render_sidebar, render_main_content, load_css
5
 
6
  # Настройки страницы
7
  st.set_page_config(layout="wide", page_icon="🧙", page_title="Fantasy Characters Explorer")
8
 
9
  # Загрузка данных
 
 
 
 
 
10
  df = load_data()
11
 
12
+ # Загрузка стилей
13
+ st.markdown(f"<style>{load_css()}</style>", unsafe_allow_html=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
14
 
15
+ # Получение уникальных значений для фильтров
16
+ moods = ["Все"] + get_unique_mood(df)
17
+ all_tags = get_unique_tags(df)
 
 
 
 
 
 
18
 
19
+ # Рендеринг сайдбара с фильтрами
20
+ selected_filters = render_sidebar(moods, all_tags)
 
21
 
22
+ # Применение фильтров
23
+ filtered_df = apply_filters(
24
+ df=df,
25
+ search_query=selected_filters["search_query"],
26
+ absurdity_range=selected_filters["absurdity_range"],
27
+ selected_mood=selected_filters["selected_mood"],
28
+ selected_tags=selected_filters["selected_tags"],
29
+ sort_option=selected_filters["sort_option"]
30
+ )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
31
 
32
+ # О��новной контент
33
+ render_main_content(filtered_df)
data_loader.py ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from datasets import load_dataset
2
+ import pandas as pd
3
+ import streamlit as st
4
+
5
+ @st.cache_data
6
+ def load_data():
7
+ """Загрузка и кэширование набора данных"""
8
+ dataset = load_dataset("loim/ru_fantasy_characters")
9
+ return pd.DataFrame(dataset["train"])
10
+
11
+ @st.cache_data
12
+ def get_unique_mood(_df):
13
+ """Получение уникальных значений настроения"""
14
+ return sorted(_df["mood"].unique().tolist())
15
+
16
+ @st.cache_data
17
+ def get_unique_tags(_df):
18
+ """Получение уникальных тегов"""
19
+ return sorted({tag for tags in _df["tags"] for tag in tags.split()})
ui_components.py ADDED
@@ -0,0 +1,232 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+
3
+ @st.cache_resource
4
+ def load_css():
5
+ """Загрузка CSS стилей"""
6
+ with open("assets/styles.css") as f:
7
+ return f.read()
8
+
9
+ def render_sidebar(moods, all_tags):
10
+ """Рендеринг боковой панели с фильтрами"""
11
+ with st.sidebar:
12
+ st.title("🔮 Фильтры")
13
+ search_query = st.text_input("🔍 Поиск по имени/описанию", "")
14
+ sort_option = st.selectbox(
15
+ "Сортировка",
16
+ ["По имени (А→Я)", "По имени (Я→А)", "По абсурдности (↑)", "По абсурдности (↓)"]
17
+ )
18
+ absurdity_range = st.slider("Уровень абсурдности", 0, 10, (0, 10))
19
+ selected_mood = st.selectbox("Настроение", moods)
20
+ selected_tags = st.multiselect("Теги", all_tags)
21
+
22
+ return {
23
+ "search_query": search_query,
24
+ "sort_option": sort_option,
25
+ "absurdity_range": absurdity_range,
26
+ "selected_mood": selected_mood,
27
+ "selected_tags": selected_tags
28
+ }
29
+
30
+ def generate_rpg_card(row):
31
+ """Генерация RPG-карточки персонажа"""
32
+ return f"""
33
+ **Мир:** {row['world']}
34
+
35
+ **Имя:** {row['name']}
36
+ **Внешность:** {row['description']}
37
+ **Характер:** {row['short_story']}
38
+ **Речь:** {row['style']}
39
+
40
+ **Первая реплика:**
41
+ > {row['first_message']}
42
+ """
43
+
44
+ def render_main_content(filtered_df):
45
+ """Рендеринг основного контента с пагинацией"""
46
+ st.title("🧙 Fantasy Characters Explorer")
47
+
48
+ if len(filtered_df) == 0:
49
+ st.warning("Персонажи не найдены. Попробуйте изменить фильтры.")
50
+ else:
51
+ # Инициализация состояния пагинации
52
+ if 'page' not in st.session_state:
53
+ st.session_state.page = 0
54
+
55
+ # Настройки пагинации
56
+ per_page = 10 # Персонажей на странице
57
+ total_pages = max(1, (len(filtered_df) // per_page) + (1 if len(filtered_df) % per_page else 0))
58
+
59
+ # Управление пагинацией в колонках
60
+ col1, col2, _ = st.columns([2, 3, 5])
61
+ with col1:
62
+ st.selectbox(
63
+ "Персонажей на странице",
64
+ options=[5, 10, 20],
65
+ index=1,
66
+ key='per_page',
67
+ on_change=lambda: st.session_state.update(page=0)
68
+ )
69
+ with col2:
70
+ st.number_input(
71
+ "Страница",
72
+ min_value=0,
73
+ max_value=total_pages-1,
74
+ value=st.session_state.page,
75
+ key='page_input',
76
+ format="%d",
77
+ on_change=lambda: setattr(st.session_state, 'page', st.session_state.page_input)
78
+ )
79
+
80
+ # Рассчет диапазона записей
81
+ start_idx = st.session_state.page * st.session_state.per_page
82
+ end_idx = min((st.session_state.page + 1) * st.session_state.per_page, len(filtered_df))
83
+
84
+ # Отображение статистики
85
+ st.markdown(f"**Найдено персонажей:** {len(filtered_df)} (Страница {st.session_state.page + 1}/{total_pages})")
86
+
87
+ # Кэшированный рендеринг карточек
88
+ @st.cache_data(max_entries=100)
89
+ def render_character_card(row):
90
+ with st.container():
91
+ st.markdown(f"### {row['name']}")
92
+ st.caption(f"*{row['short_story']}*")
93
+
94
+ col1, col2 = st.columns([1, 3])
95
+ with col1:
96
+ st.markdown(f"**🌍 Мир:** {row['world']}")
97
+ st.markdown(f"**🎭 Настроение:** `{row['mood']}`")
98
+ st.markdown(f"**🌀 Абсурдность:** `{row['absurdity']}/10`")
99
+ st.markdown(f"**🏷️ Теги:** `{' '.join(row['tags'].split())}`")
100
+
101
+ with col2:
102
+ st.markdown(f"**📖 Описание:** {row['description']}")
103
+ st.markdown(f"**💬 Стиль речи:** *{row['style']}*")
104
+
105
+ with st.expander("📋 RPG-карточка", expanded=False):
106
+ rpg_card = generate_rpg_card(row)
107
+ st.code(rpg_card, language="markdown")
108
+
109
+ st.markdown("---")
110
+
111
+ # Рендеринг только видимых карточек
112
+ for idx in range(start_idx, end_idx):
113
+ row = filtered_df.iloc[idx]
114
+ render_character_card(row)
115
+
116
+ # Кнопки навигации
117
+ if total_pages > 1:
118
+ cols = st.columns(4)
119
+ with cols[0]:
120
+ if st.button("⏪ Первая страница", disabled=(st.session_state.page == 0)):
121
+ st.session_state.page = 0
122
+ st.rerun()
123
+ with cols[1]:
124
+ if st.button("◀️ Назад", disabled=(st.session_state.page == 0)):
125
+ st.session_state.page -= 1
126
+ st.rerun()
127
+ with cols[2]:
128
+ if st.button("Вперед ▶️", disabled=(st.session_state.page >= total_pages-1)):
129
+ st.session_state.page += 1
130
+ st.rerun()
131
+ with cols[3]:
132
+ if st.button("Последняя ⏩", disabled=(st.session_state.page == total_pages-1)):
133
+ st.session_state.page = total_pages-1
134
+ st.rerun()
135
+
136
+ def render_main_content(filtered_df):
137
+ """Рендеринг основного контента с пагинацией"""
138
+ st.title("🧙 Fantasy Characters Explorer")
139
+
140
+ # Инициализация состояния пагинации
141
+ if 'page' not in st.session_state:
142
+ st.session_state.page = 0
143
+
144
+ # Настройки пагинации
145
+ per_page = st.session_state.get('per_page', 10)
146
+ total_items = len(filtered_df)
147
+ total_pages = max(1, (total_items + per_page - 1) // per_page)
148
+
149
+ # Корректировка номера страницы при изменении данных
150
+ if st.session_state.page >= total_pages:
151
+ st.session_state.page = max(0, total_pages - 1)
152
+
153
+ # Управление пагинацией в колонках
154
+ col1, col2, _ = st.columns([2, 3, 5])
155
+ with col1:
156
+ st.selectbox(
157
+ "Персонажей на странице",
158
+ options=[5, 10, 20],
159
+ index=1,
160
+ key='per_page',
161
+ on_change=lambda: st.session_state.update(page=0)
162
+ )
163
+ with col2:
164
+ st.number_input(
165
+ "Страница",
166
+ min_value=0,
167
+ max_value=max(total_pages-1, 0), # Защита от отрицательных значений
168
+ value=min(st.session_state.page, total_pages-1), # Ограничение значения
169
+ key='page_input',
170
+ format="%d",
171
+ on_change=lambda: setattr(st.session_state, 'page', st.session_state.page_input)
172
+ )
173
+
174
+ # Рассчет диапазона записей
175
+ start_idx = st.session_state.page * per_page
176
+ end_idx = min(start_idx + per_page, total_items)
177
+
178
+ # Отображение статистики
179
+ st.markdown(f"**Найдено персонажей:** {total_items} (Страница {st.session_state.page if total_items > 0 else 0}/{total_pages-1})")
180
+
181
+ # Кэшированный рендеринг карточек
182
+ @st.cache_data
183
+ def render_character_card(row):
184
+ with st.container():
185
+ st.markdown(f"### {row['name']}")
186
+ st.caption(f"*{row['short_story']}*")
187
+
188
+ col1, col2 = st.columns([1, 3])
189
+ with col1:
190
+ st.markdown(f"**🌍 Мир:** {row['world']}")
191
+ st.markdown(f"**🎭 Настроение:** `{row['mood']}`")
192
+ st.markdown(f"**🌀 Абсурдность:** `{row['absurdity']}/10`")
193
+ st.markdown(f"**🏷️ Теги:** `{' '.join(row['tags'].split())}`")
194
+
195
+ with col2:
196
+ st.markdown(f"**📖 Описание:** {row['description']}")
197
+ st.markdown(f"**💬 Стиль речи:** *{row['style']}*")
198
+
199
+ with st.expander("📋 RPG-карточка", expanded=False):
200
+ rpg_card = generate_rpg_card(row)
201
+ st.code(rpg_card, language="markdown")
202
+
203
+ st.markdown("---")
204
+
205
+ # Рендеринг только видимых карточек
206
+ if total_items > 0:
207
+ for idx in range(start_idx, end_idx):
208
+ row = filtered_df.iloc[idx]
209
+ render_character_card(row)
210
+ else:
211
+ st.warning("Персонажи не найдены. Попробуйте изменить фильтры.")
212
+ return # Прерываем выполнение чтобы избежать ошибок
213
+
214
+ # Кнопки навигации (только если есть результаты)
215
+ if total_pages > 1:
216
+ cols = st.columns(4)
217
+ with cols[0]:
218
+ if st.button("⏪ Первая страница", disabled=(st.session_state.page == 0)):
219
+ st.session_state.page = 0
220
+ st.rerun()
221
+ with cols[1]:
222
+ if st.button("◀️ Назад", disabled=(st.session_state.page == 0)):
223
+ st.session_state.page -= 1
224
+ st.rerun()
225
+ with cols[2]:
226
+ if st.button("Вперед ▶️", disabled=(st.session_state.page >= total_pages-1)):
227
+ st.session_state.page += 1
228
+ st.rerun()
229
+ with cols[3]:
230
+ if st.button("Пос��едняя ⏩", disabled=(st.session_state.page == total_pages-1)):
231
+ st.session_state.page = total_pages-1
232
+ st.rerun()
utils.py ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+
3
+ @st.cache_data
4
+ def get_search_mask(df, search_query):
5
+ return (
6
+ df["world"].str.contains(search_query, case=False, na=False) |
7
+ df["name"].str.contains(search_query, case=False, na=False) |
8
+ df["short_story"].str.contains(search_query, case=False, na=False) |
9
+ df["description"].str.contains(search_query, case=False, na=False) |
10
+ df["style"].str.contains(search_query, case=False, na=False) |
11
+ df["first_message"].str.contains(search_query, case=False, na=False)
12
+ )
13
+
14
+ @st.cache_data
15
+ def apply_filters(df, search_query, absurdity_range, selected_mood, selected_tags, sort_option):
16
+ """Применение фильтров и сортировки"""
17
+ filtered_df = df[
18
+ (df["absurdity"] >= absurdity_range[0]) &
19
+ (df["absurdity"] <= absurdity_range[1])
20
+ ]
21
+
22
+ if selected_mood != "Все":
23
+ filtered_df = filtered_df[filtered_df["mood"] == selected_mood]
24
+
25
+ if selected_tags:
26
+ filtered_df = filtered_df[filtered_df["tags"].str.contains("|".join(selected_tags))]
27
+
28
+ if search_query:
29
+ mask = get_search_mask(df, search_query)
30
+ filtered_df = filtered_df[mask]
31
+
32
+ # Сортировка
33
+ sort_columns = {
34
+ "По имени (А→Я)": ("name", True),
35
+ "По имени (Я→А)": ("name", False),
36
+ "По абсурдности (↑)": ("absurdity", True),
37
+ "По абсурдности (↓)": ("absurdity", False)
38
+ }
39
+ col, asc = sort_columns[sort_option]
40
+ return filtered_df.sort_values(col, ascending=asc)