/
Pavz
/
Project_case3
Обзор
Документация
Войти
/
Pavz
/
Project_case3
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
main
api/app5.py
384 строки
12 KB
pavel
add all
05 дек 2025, 23:14
05 дек 2025, 23:14
5bfc6e9
Код
Авторство
О чём код?
import sys import os sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) import streamlit as st from FAISS_manager import * import math from get_all_keys import * from redis_manager import * from config import POSITIVE_FEEDBACK, NEGATIVE_FEEDBACK, NEUTRAL_FEEDBACK, SIZE_OF_EMBEDDING from utils.load_to_redis_from_sql import fill_redis from collect_feedback import collect_feedback from download import load_offers_from_db, load_categories_from_db from download_20_offer import download_20_offer from v_adapting_functions import VectorManager import threading @st.cache_data def get_offer_df(): return load_offers_from_db() @st.cache_data def get_categories_df(): return load_categories_from_db() df = get_offer_df() cat_df = get_categories_df() def background_fill_redis() -> None: """ Должны заполнять один раз при запуске сайта. Нужна параллельная загрузка. Возможно сделать так, чтобы данные сохранялись и при повторном посещении сайта после остановки служб streamlit """ fill_redis(db=0) fill_redis(db=1) st.session_state.redis_fill = True st.rerun() @st.cache_resource def create_vectorman(): return VectorManager() @st.cache_resource def create_redisman(db) -> RedisManager: """ Инициализируем redisman. Нужно вызватвь это ф-ю дважды для db=0, db=1 """ return RedisManager(db) @st.cache_resource def create_index(size=SIZE_OF_EMBEDDING): flat_index = faiss.IndexFlatIP(size) index = faiss.IndexIDMap(flat_index) return index def create_faissman() -> FAISSManager: """ * Изменить create_faissman так, чтобы она принимала только db и faiss_index. В таком случае я буду вызывать create_redisman прямо тут. Но смогу ли я в случае чего к нему обращатсья (и надо ли мне это будет?) * Использование st.session_state (Для временных объектов) Если RedisManager является объектом, который должен быть доступен всем, но не должен входить в хэш-ключ другой функции, его можно временно сохранить в st.session_state. Принимает редис менеджер от db=1, faiss_index """ redis_man1 = create_redisman(db=1) faiss_index = create_index() vectors_dict = redis_man1.get_vectors(get_all_keys()) bad_vectors = {id: vectors_dict[id] for id in vectors_dict.keys() if len(vectors_dict[id]) != 1024} for key in bad_vectors: vector = bad_vectors[key].squeeze() bad_vectors[key] = vector redis_man1.set_vectors(bad_vectors) new_faiss_man = FAISSManager(redis_man1.get_vectors(get_all_keys()), faiss_index) new_faiss_man.upload_start_vectors() return new_faiss_man vector_man = create_vectorman() redis_man0 = create_redisman(db=0) redis_man1 =create_redisman(db=1) faiss_index = create_index() if "redis_fill" not in st.session_state: st.session_state.redis_fill = False if "thread_started" not in st.session_state: st.session_state.thread_started = False if "faiss_man" not in st.session_state: st.session_state.faiss_man = False if "already_create" not in st.session_state: st.session_state.already_create = False # flag = 0 if not st.session_state.thread_started and not st.session_state.redis_fill : st.session_state.thread_started = True t = threading.Thread(target=background_fill_redis, daemon=True) t.start() print("заполнеяем redis") if not st.session_state.faiss_man and not st.session_state.already_create: st.session_state.faiss_man = create_faissman() st.session_state.already_create = True main_faiss_man = st.session_state.faiss_man def get_recommendations(current_faiss_manager, query_id, query_vector): query_vector = query_vector.reshape(1,-1) scores, ids = current_faiss_manager.search_vectors(query_id, query_vector, df, cat_df) scores, ids = scores[0].tolist(), ids[0].tolist() res_list = [] for i in range(len(scores)): res = {ids[i]: scores[i]} res_list.append(res) return res_list # ========================================== # 1. НАСТРОЙКИ И CSS (FIXED LAYOUT) # ========================================== st.set_page_config(layout="wide", page_title="Maxidom Shop", page_icon="🏗️") st.markdown(""" <style> .product-img { width: 100%; height: 180px; object-fit: contain; background-color: white; border-radius: 8px 8px 0 0; padding: 10px; border: 1px solid #f0f0f0; border-bottom: none; display: block; margin-left: auto; margin-right: auto; } div.row-widget.stButton > button { width: 100%; height: 140px !important; min-height: 140px !important; max-height: 140px !important; background-color: white; border: 1px solid #f0f0f0; border-top: none; border-radius: 0 0 8px 8px; padding: 12px; transition: all 0.2s; box-shadow: 0 2px 5px rgba(0,0,0,0.02); display: flex; flex-direction: column; justify-content: flex-start; align-items: flex-start; text-align: left; overflow: hidden; } div.row-widget.stButton > button:hover { border-color: #E30613; box-shadow: 0 5px 15px rgba(227, 6, 19, 0.1); transform: translateY(-2px); color: #E30613; } .match-badge { background-color: #E30613; color: white; padding: 4px 8px; border-radius: 12px; font-size: 12px; font-weight: bold; box-shadow: 0 2px 4px rgba(0,0,0,0.2); } .stSlider { padding-top: 0px !important; padding-bottom: 0px !important; margin-top: -10px; } </style> """, unsafe_allow_html=True) st.markdown(""" <style> div.stButton > button { width: 100%; background-color: white; border: 1px solid #ddd; border-radius: 8px; transition: all 0.2s ease; color: #333; box-shadow: 0 2px 5px rgba(0,0,0,0.05); } /* Hover — красная подсветка */ div.stButton > button:hover { background-color: #ffffff !important; border-color: #E30613 !important; color: #E30613 !important; box-shadow: 0 6px 16px rgba(227, 6, 19, 0.25) !important; transform: translateY(-2px); } </style> """, unsafe_allow_html=True) # ========================================== # 3. ЛОГИКА # ========================================== if 'page_number' not in st.session_state: st.session_state.page_number = 1 # ========================================== # 5. ИНТЕРФЕЙС # ========================================== if 'page' not in st.session_state: st.session_state.page = "catalog" if 'product_idx' not in st.session_state: st.session_state.product_idx = None # --- СТРАНИЦА 1: КАТАЛОГ --- if st.session_state.page == "catalog": col1, col2, col3 = st.columns(3) with col2: st.title("Каталог товаров") if df.empty: st.warning("База данных пуста.") else: # Поиск search_options = df['name'].tolist() selected_search = st.selectbox("Найти товар:", [""] + search_options[:5000]) if selected_search: idx = df[df['name'] == selected_search].index[0] st.session_state.product_idx = idx st.session_state.page = "details" st.rerun() st.write("---") items_per_page = 20 total_pages = math.ceil(len(df) / items_per_page) current_page = st.session_state.page_number start_idx = (current_page - 1) * items_per_page end_idx = start_idx + items_per_page page_df = df.iloc[start_idx:end_idx] rows = [page_df.iloc[i:i+4] for i in range(0, len(page_df), 4)] for row in rows: cols = st.columns(4) for i in range(len(row)): item = row.iloc[i] with cols[i]: html_card = f""" <div class="product-card"> <img src='{item['picture']}' class='product-img'> <div class='product-price'>{item['display_price']}</div> <div class='product-name'>{item['name'][:50]}{'...' if len(item['name']) > 50 else ''}</div> </div> """ st.markdown(html_card, unsafe_allow_html=True) # Кнопка для выбора товара if st.button("Выбрать", key=f"select_{item['id']}", use_container_width=True): st.session_state.product_idx = df[df['id'] == item['id']].index[0] st.session_state.page = "details" st.rerun() # Навигация st.write("---") c_prev, c_info, c_next = st.columns([1,2,1]) with c_prev: if current_page > 1: if st.button("Назад"): st.session_state.page_number -= 1 st.rerun() with c_info: st.markdown(f"<div style='text-align:center; padding-top:10px;'>Страница <b>{current_page}</b> из {total_pages}</div>", unsafe_allow_html=True) with c_next: if current_page < total_pages: if st.button("Вперед"): st.session_state.page_number += 1 st.rerun() # --- СТРАНИЦА 2: КАРТОЧКА --- elif st.session_state.page == "details": st.session_state.user_feedback = {} idx = st.session_state.product_idx p = df.iloc[idx] if st.button("Вернуться в каталог"): st.session_state.page = "catalog" st.rerun() st.divider() c1, c2 = st.columns([1,2]) with c1: st.image(p['picture'], use_container_width=True) with c2: st.caption(f"Артикул: {p['id']}") st.title(p['name']) st.markdown(f"<h1 style='color:#E30613; font-size:40px;'>{p['display_price']}</h1>", unsafe_allow_html=True) st.markdown(f"**Бренд:** {p['vendor']} | **Страна:** {p['country']}") with st.expander("Описание товара", expanded=True): st.write(p['desc_final']) st.write("---") st.subheader("Рекомендованные товары") recs = get_recommendations(main_faiss_man,p['id'], redis_man1.get_vectors([p['id']])[p['id']]) # заменил len(df) на 20 # Основной товар main_id = p['id'] df_20_offer = download_20_offer(recs, df) feedback_dict = collect_feedback(main_id, recs, df_20_offer) # Кнопка подтверждения if st.button("Подтвердить оценки"): for r_data in recs: r_id = str(list(r_data.keys())[0]) if r_id not in st.session_state.user_feedback: st.session_state.user_feedback[r_id] = NEUTRAL_FEEDBACK st.success("Оценки сохранены!") adapted_vector = vector_man.create_adapted_vector(feedback_dict) ad_vec_key = list(adapted_vector.keys())[0] ad_vec_emb = adapted_vector[ad_vec_key] main_faiss_man.replace_vector(ad_vec_emb, ad_vec_key) print() redis_man1.set_vectors(adapted_vector) st.rerun()