limloop commited on
Commit
cc146e8
·
1 Parent(s): 1dd15f5

рейтинговая система

Browse files
Files changed (7) hide show
  1. app.py +13 -7
  2. data_loader.py +10 -3
  3. database.py +15 -0
  4. locales/en.json +4 -1
  5. locales/ru.json +4 -1
  6. requirements.txt +4 -3
  7. ui_components.py +43 -13
app.py CHANGED
@@ -1,29 +1,35 @@
1
  import streamlit as st
2
- from streamlit_cookies_controller import CookieController
 
3
  from utils import apply_filters, apply_sort
4
  from locales import load_locale, lang_codes
5
  from data_loader import load_data, get_unique_mood, get_unique_tags
6
  from ui_components import render_sidebar, render_main_content, load_css
7
- import time
8
 
9
  DEFAULT_LANG = "en"
10
 
11
 
12
  # Настройки страницы
13
  st.set_page_config(layout="wide", page_icon="🧙", page_title="Fantasy Characters Explorer")
14
- cookie = CookieController()
15
 
16
  # Загрузка данных
 
17
  df = load_data()
18
 
19
- st.session_state.lang = cookie.get("lang")
20
  all_langs = lang_codes()
21
  if st.session_state.lang not in all_langs:
22
  st.session_state.lang = (st.context.locale.split("-", 1)[0] if st.context.locale else DEFAULT_LANG)
23
- cookie.set("lang", st.session_state.lang, secure=True, same_site=None)
24
 
25
  locale = load_locale(st.session_state.lang)
26
 
 
 
 
 
 
27
  # Загрузка стилей
28
  st.markdown(f"<style>{load_css()}</style>", unsafe_allow_html=True)
29
 
@@ -32,7 +38,7 @@ moods = [locale['filter_all']] + get_unique_mood(df)
32
  all_tags = get_unique_tags(df)
33
 
34
  # Рендеринг сайдбара с фильтрами
35
- selected_filters = render_sidebar(cookie, locale, all_langs, moods, all_tags)
36
 
37
  # Применение фильтров
38
  filtered_df = apply_filters(
@@ -48,4 +54,4 @@ sorted_df = apply_sort(locale, filtered_df, selected_filters['sort_option'])
48
 
49
 
50
  # Основной контент
51
- render_main_content(locale, sorted_df)
 
1
  import streamlit as st
2
+ from streamlit_local_storage import LocalStorage
3
+ from database import init_supabase
4
  from utils import apply_filters, apply_sort
5
  from locales import load_locale, lang_codes
6
  from data_loader import load_data, get_unique_mood, get_unique_tags
7
  from ui_components import render_sidebar, render_main_content, load_css
 
8
 
9
  DEFAULT_LANG = "en"
10
 
11
 
12
  # Настройки страницы
13
  st.set_page_config(layout="wide", page_icon="🧙", page_title="Fantasy Characters Explorer")
14
+ localS = LocalStorage()
15
 
16
  # Загрузка данных
17
+ database = init_supabase()
18
  df = load_data()
19
 
20
+ st.session_state.lang = localS.getItem("lang")
21
  all_langs = lang_codes()
22
  if st.session_state.lang not in all_langs:
23
  st.session_state.lang = (st.context.locale.split("-", 1)[0] if st.context.locale else DEFAULT_LANG)
24
+ localS.setItem("lang", st.session_state.lang)
25
 
26
  locale = load_locale(st.session_state.lang)
27
 
28
+ if 'rated' not in st.session_state:
29
+ st.session_state.rated = localS.getItem("rated")
30
+ if st.session_state.rated is None:
31
+ st.session_state.rated = []
32
+
33
  # Загрузка стилей
34
  st.markdown(f"<style>{load_css()}</style>", unsafe_allow_html=True)
35
 
 
38
  all_tags = get_unique_tags(df)
39
 
40
  # Рендеринг сайдбара с фильтрами
41
+ selected_filters = render_sidebar(localS, locale, all_langs, moods, all_tags)
42
 
43
  # Применение фильтров
44
  filtered_df = apply_filters(
 
54
 
55
 
56
  # Основной контент
57
+ render_main_content(locale, database, localS, sorted_df)
data_loader.py CHANGED
@@ -1,12 +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):
 
1
+ import os
2
+ import hashlib
3
  import pandas as pd
4
  import streamlit as st
5
 
6
+ def create_hash(row):
7
+ combined = str(row['name']) + str(row['short_story'])
8
+ return hashlib.sha256(combined.encode()).hexdigest()
9
+
10
  @st.cache_data
11
  def load_data():
12
  """Загрузка и кэширование набора данных"""
13
+ df = pd.read_json(os.getenv("DATASET_PATH"))
14
+ df['hash'] = df.apply(create_hash, axis=1)
15
+
16
+ return df
17
 
18
  @st.cache_data
19
  def get_unique_mood(_df):
database.py ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import streamlit as st
3
+ from supabase import create_client
4
+
5
+ @st.cache_resource
6
+ def init_supabase():
7
+ url = os.getenv("SUPABASE_URL")
8
+ key = os.getenv("SUPABASE_KEY")
9
+ return create_client(url, key)
10
+
11
+ def get_hash(database, table, hash):
12
+ return database.table(table).select("*").eq("hash", hash).execute()
13
+
14
+ def upsert_data(database, table, data):
15
+ return database.table(table).upsert(data).execute()
locales/en.json CHANGED
@@ -28,5 +28,8 @@
28
  "rpt_card_desc": "Appearance:",
29
  "rpt_card_story": "Personality:",
30
  "rpt_card_style": "Speech:",
31
- "rpt_card_msg": "First message:"
 
 
 
32
  }
 
28
  "rpt_card_desc": "Appearance:",
29
  "rpt_card_story": "Personality:",
30
  "rpt_card_style": "Speech:",
31
+ "rpt_card_msg": "First message:",
32
+ "rating_text": "Rating: 👍 {} | 👎 {}",
33
+ "rating_like": "👍 Like",
34
+ "rating_dislike": "👎 Dislike"
35
  }
locales/ru.json CHANGED
@@ -28,5 +28,8 @@
28
  "rpt_card_desc": "Внешность:",
29
  "rpt_card_story": "Характер:",
30
  "rpt_card_style": "Речь:",
31
- "rpt_card_msg": "Первая реплика:"
 
 
 
32
  }
 
28
  "rpt_card_desc": "Внешность:",
29
  "rpt_card_story": "Характер:",
30
  "rpt_card_style": "Речь:",
31
+ "rpt_card_msg": "Первая реплика:",
32
+ "rating_text": "Рейтинг: 👍 {} | 👎 {}",
33
+ "rating_like": "👍 Нравится",
34
+ "rating_dislike": "👎 Не нравится"
35
  }
requirements.txt CHANGED
@@ -1,4 +1,5 @@
 
 
 
1
  streamlit
2
- streamlit_cookies_controller
3
- datasets
4
- pandas
 
1
+ pandas
2
+ hashlib
3
+ supabase
4
  streamlit
5
+ streamlit-local-storage
 
 
ui_components.py CHANGED
@@ -1,4 +1,6 @@
1
  import streamlit as st
 
 
2
 
3
  @st.cache_resource
4
  def load_css():
@@ -6,7 +8,7 @@ def load_css():
6
  with open("assets/styles.css") as f:
7
  return f.read()
8
 
9
- def render_sidebar(cookie, locale, all_langs, moods, all_tags):
10
  """Рендеринг боковой панели с фильтрами"""
11
  with st.sidebar:
12
  st.markdown(
@@ -18,7 +20,7 @@ def render_sidebar(cookie, locale, all_langs, moods, all_tags):
18
 
19
  def on_language_change():
20
  if st.session_state.lang_select != st.session_state.lang:
21
- cookie.set("lang", st.session_state.lang_select, secure=True, same_site=None)
22
 
23
  st.selectbox(
24
  locale['filter_lang'],
@@ -52,21 +54,21 @@ def render_sidebar(cookie, locale, all_langs, moods, all_tags):
52
  def generate_rpg_card(locale, row):
53
  """Генерация RPG-карточки персонажа"""
54
  return f"""
55
- **{locale['rpt_card_world']}** {row['world']}
56
 
57
- **{locale['rpt_card_name']}** {row['name']}
58
- **{locale['rpt_card_desc']}** {row['description']}
59
- **{locale['rpt_card_story']}** {row['short_story']}
60
- **{locale['rpt_card_style']}** {row['style']}
61
 
62
- **{locale['rpt_card_msg']}**
63
- > {row['first_message']}
64
  """
65
 
66
  # Кэшированный рендеринг карточек
67
  @st.cache_data
68
  def render_character_card(locale, row):
69
  with st.container():
 
70
  st.markdown(f"### {row['name']}")
71
  st.caption(f"*{row['short_story']}*")
72
 
@@ -84,10 +86,35 @@ def render_character_card(locale, row):
84
  with st.expander(locale['char_rpg_card'], expanded=False):
85
  rpg_card = generate_rpg_card(locale, row)
86
  st.code(rpg_card, language="markdown")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
87
 
88
- st.markdown("---")
 
 
 
 
 
 
89
 
90
- def render_main_content(locale, filtered_df):
91
  """Рендеринг основного контента с пагинацией"""
92
  st.title("🧙 Fantasy Characters Explorer")
93
 
@@ -99,7 +126,7 @@ def render_main_content(locale, filtered_df):
99
  st.session_state.page = 0
100
 
101
  # Настройки пагинации
102
- per_page = 10 # Персонажей на странице
103
  total_pages = max(1, (len(filtered_df) // per_page) + (1 if len(filtered_df) % per_page else 0))
104
  if st.session_state.page >= total_pages:
105
  st.session_state.page = 0
@@ -110,7 +137,7 @@ def render_main_content(locale, filtered_df):
110
  st.selectbox(
111
  locale['main_counter'],
112
  options=[5, 10, 20],
113
- index=1,
114
  key='per_page',
115
  on_change=lambda: st.session_state.update(page=0)
116
  )
@@ -136,6 +163,9 @@ def render_main_content(locale, filtered_df):
136
  for idx in range(start_idx, end_idx):
137
  row = filtered_df.iloc[idx]
138
  render_character_card(locale, row)
 
 
 
139
 
140
  # Кнопки навигации
141
  if total_pages > 1:
 
1
  import streamlit as st
2
+ from database import get_hash, upsert_data
3
+
4
 
5
  @st.cache_resource
6
  def load_css():
 
8
  with open("assets/styles.css") as f:
9
  return f.read()
10
 
11
+ def render_sidebar(localS, locale, all_langs, moods, all_tags):
12
  """Рендеринг боковой панели с фильтрами"""
13
  with st.sidebar:
14
  st.markdown(
 
20
 
21
  def on_language_change():
22
  if st.session_state.lang_select != st.session_state.lang:
23
+ localS.setItem("lang", st.session_state.lang_select)
24
 
25
  st.selectbox(
26
  locale['filter_lang'],
 
54
  def generate_rpg_card(locale, row):
55
  """Генерация RPG-карточки персонажа"""
56
  return f"""
57
+ {locale['rpt_card_world']} {row['world']}
58
 
59
+ {locale['rpt_card_name']} {row['name']}
60
+ {locale['rpt_card_desc']} {row['description']}
61
+ {locale['rpt_card_story']} {row['short_story']}
62
+ {locale['rpt_card_style']} {row['style']}
63
 
64
+ {locale['rpt_card_msg']} {row['first_message']}
 
65
  """
66
 
67
  # Кэшированный рендеринг карточек
68
  @st.cache_data
69
  def render_character_card(locale, row):
70
  with st.container():
71
+ st.markdown("---")
72
  st.markdown(f"### {row['name']}")
73
  st.caption(f"*{row['short_story']}*")
74
 
 
86
  with st.expander(locale['char_rpg_card'], expanded=False):
87
  rpg_card = generate_rpg_card(locale, row)
88
  st.code(rpg_card, language="markdown")
89
+
90
+ def render_character_rating(locale, database, localS, row):
91
+ current_hash = row['hash']
92
+
93
+ rating_data, _ = get_hash(database, "rating", current_hash)
94
+ rating_data = rating_data[1][0] if rating_data[1] else {"hash": current_hash, "likes": 0, "dislikes": 0}
95
+
96
+ # Проверяем, оценивали ли уже этот хэш
97
+ if current_hash in st.session_state.rated and (rating_data['likes'] != 0 or rating_data['dislikes']):
98
+ # Показываем текущий рейтинг
99
+ st.write(locale["rating_text"].format(rating_data['likes'], rating_data['dislikes']))
100
+ else:
101
+ # Показываем кнопки для оценки
102
+ if st.button(locale["rating_like"], key=f"like_{current_hash}"):
103
+ rating_data["likes"] += 1
104
+ upsert_data(database, "rating", rating_data)
105
+ st.session_state.rated.append(current_hash)
106
+ localS.setItem("rated", st.session_state.rated)
107
+ st.rerun()
108
 
109
+ if st.button(locale["rating_dislike"], key=f"dislike_{current_hash}"):
110
+ rating_data["dislikes"] += 1
111
+ upsert_data(database, "rating", rating_data)
112
+ st.session_state.rated.append(current_hash)
113
+ localS.setItem("rated", st.session_state.rated)
114
+ st.rerun()
115
+
116
 
117
+ def render_main_content(locale, database, localS, filtered_df):
118
  """Рендеринг основного контента с пагинацией"""
119
  st.title("🧙 Fantasy Characters Explorer")
120
 
 
126
  st.session_state.page = 0
127
 
128
  # Настройки пагинации
129
+ per_page = 5 # Персонажей на странице
130
  total_pages = max(1, (len(filtered_df) // per_page) + (1 if len(filtered_df) % per_page else 0))
131
  if st.session_state.page >= total_pages:
132
  st.session_state.page = 0
 
137
  st.selectbox(
138
  locale['main_counter'],
139
  options=[5, 10, 20],
140
+ index=0,
141
  key='per_page',
142
  on_change=lambda: st.session_state.update(page=0)
143
  )
 
163
  for idx in range(start_idx, end_idx):
164
  row = filtered_df.iloc[idx]
165
  render_character_card(locale, row)
166
+ render_character_rating(locale, database, localS, row)
167
+
168
+ st.markdown("---")
169
 
170
  # Кнопки навигации
171
  if total_pages > 1: