From cccea658581f6e9b0e507c21b2dca4df46d4391f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20Ko=C5=BCdo=C5=84?= <102428159+Kacper-W-Kozdon@users.noreply.github.com> Date: Sat, 4 May 2024 12:15:51 +0200 Subject: [PATCH 01/69] Update .gitignore --- .gitignore | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index cbec326..6b305a9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ .env code/ -__pycache__/ \ No newline at end of file +__pycache__/ +.streamlit +.streamlit/secrets.toml From 364976825a64784e049a176e982637b9588bab98 Mon Sep 17 00:00:00 2001 From: Kacper-W-Kozdon Date: Sat, 4 May 2024 12:16:49 +0200 Subject: [PATCH 02/69] .gitignore --- .gitignore | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c503af0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +.env +code/ +__pycache__/ +.streamlit +.streamlit/secrets.toml \ No newline at end of file From a6521ee65d0a0baa2400940faca031c24110ab8a Mon Sep 17 00:00:00 2001 From: Kacper-W-Kozdon Date: Tue, 7 May 2024 13:39:48 +0200 Subject: [PATCH 03/69] minor leaderboard changes --- chatbot_arena.py | 435 +++++++++++++++++++++++++--------------- helpers.py | 140 ++++++++----- leaderboard.csv | 4 +- pages/1_leaderboards.py | 63 +++--- 4 files changed, 392 insertions(+), 250 deletions(-) diff --git a/chatbot_arena.py b/chatbot_arena.py index d6b1ad2..fe73883 100644 --- a/chatbot_arena.py +++ b/chatbot_arena.py @@ -1,8 +1,5 @@ import streamlit as st -from streamlit_gsheets import GSheetsConnection from unify import AsyncUnify -from unify import Unify -import os from unify.exceptions import UnifyError import asyncio import pandas as pd @@ -19,9 +16,7 @@ def init_session(mode: str = "keys") -> None: - - ''' - Initialize session states and databases. + """Initialize session states and databases. Parameters ---------- @@ -37,14 +32,25 @@ def init_session(mode: str = "keys") -> None: Returns ------- None - ''' + """ global all_models, data, json_data if mode == "keys": - keys = ["chat_input", "winner_selected", "api_key_provided", - "vote1", "vote2", "model1", "model2", "scores", - "authenticated", "new_models_selected", "detailed_leaderboards", - "detail", "new_source"] + keys = [ + "chat_input", + "winner_selected", + "api_key_provided", + "vote1", + "vote2", + "model1", + "model2", + "scores", + "authenticated", + "new_models_selected", + "detailed_leaderboards", + "detail", + "new_source", + ] for key in keys: if key not in st.session_state.keys(): st.session_state[key] = None @@ -56,16 +62,16 @@ def init_session(mode: str = "keys") -> None: st.session_state.chat_history2 = [] if "model1_selectbox" not in st.session_state.keys(): - st.session_state.placeholder_model1 = 'other' + st.session_state.placeholder_model1 = "other" if "model1_other" not in st.session_state.keys(): - st.session_state.placeholder_model1_other = 'model@provider' + st.session_state.placeholder_model1_other = "model@provider" if "model2_selectbox" not in st.session_state.keys(): - st.session_state.placeholder_model2 = 'other' + st.session_state.placeholder_model2 = "other" if "model2_other" not in st.session_state.keys(): - st.session_state.placeholder_model2_other = 'model@provider' + st.session_state.placeholder_model2_other = "model@provider" if "index_model1" not in st.session_state.keys(): - st.session_state.index_model1 = 0 + st.session_state.index_model1 = 0 if "index_model2" not in st.session_state.keys(): st.session_state.index_model2 = 0 if "value_model1_other" not in st.session_state.keys(): @@ -79,29 +85,34 @@ def init_session(mode: str = "keys") -> None: if "source" not in st.session_state.keys(): st.session_state.source = False + if "vote_counts" not in st.session_state: + st.session_state["vote_counts"] = { + model: {"Wins ⭐": 0, "Losses ❌": 0} for model in ["others"] + } + if mode == "offline": helpers.database.get_offline() # Load JSON data from file all_models = st.session_state.models - #model_options = [model.split("@")[0] for model in all_models] - data = pd.read_csv("leaderboard.csv") # This will raise an error if the file does not exist + # model_options = [model.split("@")[0] for model in all_models] + data = pd.read_csv( + "leaderboard.csv" + ) # This will raise an error if the file does not exist json_data = st.session_state.leaderboard - - if 'vote_counts' not in st.session_state: - st.session_state['vote_counts'] = json_data + st.session_state["vote_counts"] = json_data if mode == "online": helpers.database.get_online() all_models = list(st.session_state.models) json_data = st.session_state.leaderboard data = {model: 0 for model in json_data.index} + st.session_state["vote_counts"] = json_data def select_model(api_key: str = "", authenticated: bool = False) -> None: + """Select two models for the Unify API. The models are picked through + selectbox options. - ''' - Select two models for the Unify API. The models are picked through selectbox options. - Parameters ---------- api_key @@ -115,7 +126,7 @@ def select_model(api_key: str = "", authenticated: bool = False) -> None: Returns ------- None - ''' + """ global json_data, all_models @@ -123,48 +134,72 @@ def select_model(api_key: str = "", authenticated: bool = False) -> None: model1_other_disabled = True model2_other_disabled = True - st.selectbox("Select the first model's endpoint:", - all_models, - disabled=disabled, - index=st.session_state.index_model1, - on_change=lambda: (setattr(st.session_state, "chat_history1", []), - setattr(st.session_state, "chat_history2", []), - setattr(st.session_state, "winner_selected", False), - setattr(st.session_state, "new_models_selected", True)), - key="model1_selectbox") - if st.session_state.model1_selectbox == 'other': + st.selectbox( + "Select the first model's endpoint:", + all_models, + disabled=disabled, + index=st.session_state.index_model1, + on_change=lambda: ( + setattr(st.session_state, "chat_history1", []), + setattr(st.session_state, "chat_history2", []), + setattr(st.session_state, "winner_selected", False), + setattr(st.session_state, "new_models_selected", True), + ), + key="model1_selectbox", + ) + if st.session_state.model1_selectbox == "other": model1_other_disabled = False - st.text_input('If "other", provide your own model:', - placeholder="@", - disabled=model1_other_disabled, - value=st.session_state.value_model1_other, - on_change=lambda: (setattr(st.session_state, "chat_history1", []), - setattr(st.session_state, "chat_history2", []), - setattr(st.session_state, "winner_selected", False), - setattr(st.session_state, "new_models_selected", True)), - key='model1_other') - st.selectbox("Select the second model's endpoint:", - all_models, - disabled=disabled, - index=st.session_state.index_model2, - on_change=lambda: (setattr(st.session_state, "chat_history1", []), - setattr(st.session_state, "chat_history2", []), - setattr(st.session_state, "winner_selected", False), - setattr(st.session_state, "new_models_selected", True)), - key="model2_selectbox") - if st.session_state.model2_selectbox == 'other': + st.text_input( + 'If "other", provide your own model:', + placeholder="@", + disabled=model1_other_disabled, + value=st.session_state.value_model1_other, + on_change=lambda: ( + setattr(st.session_state, "chat_history1", []), + setattr(st.session_state, "chat_history2", []), + setattr(st.session_state, "winner_selected", False), + setattr(st.session_state, "new_models_selected", True), + ), + key="model1_other", + ) + st.selectbox( + "Select the second model's endpoint:", + all_models, + disabled=disabled, + index=st.session_state.index_model2, + on_change=lambda: ( + setattr(st.session_state, "chat_history1", []), + setattr(st.session_state, "chat_history2", []), + setattr(st.session_state, "winner_selected", False), + setattr(st.session_state, "new_models_selected", True), + ), + key="model2_selectbox", + ) + if st.session_state.model2_selectbox == "other": model2_other_disabled = False - st.text_input('If "other", provide your own model:', - placeholder="@", - disabled=model2_other_disabled, - value=st.session_state.value_model2_other, - on_change=lambda: (setattr(st.session_state, "chat_history1", []), - setattr(st.session_state, "chat_history2", []), - setattr(st.session_state, "winner_selected", False), - setattr(st.session_state, "new_models_selected", True)), - key='model2_other') - selected_model1 = st.session_state.model1_selectbox if st.session_state.model1_selectbox != "other" else st.session_state.model1_other - selected_model2 = st.session_state.model2_selectbox if st.session_state.model2_selectbox != "other" else st.session_state.model2_other + st.text_input( + 'If "other", provide your own model:', + placeholder="@", + disabled=model2_other_disabled, + value=st.session_state.value_model2_other, + on_change=lambda: ( + setattr(st.session_state, "chat_history1", []), + setattr(st.session_state, "chat_history2", []), + setattr(st.session_state, "winner_selected", False), + setattr(st.session_state, "new_models_selected", True), + ), + key="model2_other", + ) + selected_model1 = ( + st.session_state.model1_selectbox + if st.session_state.model1_selectbox != "other" + else st.session_state.model1_other + ) + selected_model2 = ( + st.session_state.model2_selectbox + if st.session_state.model2_selectbox != "other" + else st.session_state.model2_other + ) st.session_state.index_model1 = all_models.index(st.session_state.model1_selectbox) st.session_state.index_model2 = all_models.index(st.session_state.model2_selectbox) @@ -177,15 +212,13 @@ def select_model(api_key: str = "", authenticated: bool = False) -> None: random.shuffle(selected_models) if st.session_state.new_models_selected in [True, None]: - st.session_state['model1'] = selected_models.pop(0) - st.session_state['model2'] = selected_models.pop(0) + st.session_state["model1"] = selected_models.pop(0) + st.session_state["model2"] = selected_models.pop(0) st.session_state.new_models_selected = False def history(model: str = "model1", output: str = "") -> None: - - ''' - Assign to session states and manage the contents of the chat history. + """Assign to session states and manage the contents of the chat history. Parameters ---------- @@ -199,26 +232,28 @@ def history(model: str = "model1", output: str = "") -> None: Returns ------- None + """ - ''' - - if model == 'model1': - st.session_state['chat_history1'].append({"role": "assistant", "content": output}) - elif model == 'model2': - st.session_state['chat_history2'].append({"role": "assistant", "content": output}) + if model == "model1": + st.session_state["chat_history1"].append( + {"role": "assistant", "content": output} + ) + elif model == "model2": + st.session_state["chat_history2"].append( + {"role": "assistant", "content": output} + ) else: st.write("Please, enter the model1 or model2 in history function.") - if len(st.session_state['chat_history1']) >= 10: - st.session_state['chat_history1'].pop(0) - if len(st.session_state['chat_history2']) >= 10: - st.session_state['chat_history2'].pop(0) + if len(st.session_state["chat_history1"]) >= 10: + st.session_state["chat_history1"].pop(0) + if len(st.session_state["chat_history2"]) >= 10: + st.session_state["chat_history2"].pop(0) def input_api_key(api_key: str = " ") -> None: - ''' - Authorize the Unify API key provided by the user. If the key is recognised, - the app's UI will be unlocked. + """Authorize the Unify API key provided by the user. If the key is + recognised, the app's UI will be unlocked. Parameters ---------- @@ -229,17 +264,19 @@ def input_api_key(api_key: str = " ") -> None: Returns ------- None - - ''' + """ authorisation_url = "https://api.unify.ai/v0/get_credits" - r = requests.get(authorisation_url, headers={"accept": "application/json", "Authorization": f"Bearer {api_key}"}).json() + r = requests.get( + authorisation_url, + headers={"accept": "application/json", "Authorization": f"Bearer {api_key}"}, + ).json() if "id" in r.keys(): setattr(st.session_state, "api_key_provided", True) setattr(st.session_state, "authenticated", True) st.sidebar.write(f"Session user credits: {r['credits']}") - st.session_state['api_key'] = api_key + st.session_state["api_key"] = api_key elif "detail" in r.keys(): setattr(st.session_state, "api_key_provided", False) setattr(st.session_state, "authenticated", False) @@ -251,8 +288,7 @@ def input_api_key(api_key: str = " ") -> None: def print_history(contain: st.container) -> None: - ''' - Print the chat history in a streamlit split container. + """Print the chat history in a streamlit split container. Parameters ---------- @@ -262,25 +298,23 @@ def print_history(contain: st.container) -> None: Returns ------- None - - ''' + """ cont1, cont2 = contain for i in st.session_state["chat_history1"]: - if i['role']=="user": - cont1.write("🧑‍💻" +" "+ i["content"]) + if i["role"] == "user": + cont1.write("🧑‍💻" + " " + i["content"]) else: cont1.write(i["content"]) for i in st.session_state["chat_history2"]: - if i['role']=="user": - cont2.write("🧑‍💻" +" "+ i["content"]) + if i["role"] == "user": + cont2.write("🧑‍💻" + " " + i["content"]) else: - cont2.write(i["content"]) + cont2.write(i["content"]) def call_model(Endpoint: str) -> AsyncUnify: - ''' - Prepare the Unify model to which the prompts will be sent. + """Prepare the Unify model to which the prompts will be sent. Parameters ---------- @@ -291,19 +325,15 @@ def call_model(Endpoint: str) -> AsyncUnify: ------- async_unify the AsyncUnify object with the endpoint assigned to it. + """ - ''' - - async_unify = AsyncUnify( - api_key=st.session_state['api_key'], - endpoint=Endpoint) + async_unify = AsyncUnify(api_key=st.session_state["api_key"], endpoint=Endpoint) return async_unify async def main() -> None: - ''' - Main loop of the streamlit app. Contains all of the flow control of the app - and generates UI elements. + """Main loop of the streamlit app. Contains all of the flow control of the + app and generates UI elements. Parameters ---------- @@ -312,12 +342,11 @@ async def main() -> None: Returns ------- None - - ''' + """ global all_models, data - st.set_option('deprecation.showPyplotGlobalUse', False) + st.set_option("deprecation.showPyplotGlobalUse", False) init_session("keys") st.session_state.code_input = "" st.markdown( @@ -326,16 +355,21 @@ async def main() -> None: ⚔️ Unify Chatbot Arena: Benchmarking LLMs in the Wild 🚀 """, - unsafe_allow_html=True) + unsafe_allow_html=True, + ) st.sidebar.subheader("Unify API Key") - api_key = st.sidebar.text_input(" ", value=st.session_state.api_key, - placeholder="API key is required to proceed.", - type="password") + api_key = st.sidebar.text_input( + " ", + value=st.session_state.api_key, + placeholder="API key is required to proceed.", + type="password", + ) input_api_key(api_key) - st.session_state.source = st.sidebar.checkbox("Use online database (google sheets).", - value=st.session_state.source, - on_change=lambda: setattr(st.session_state, "new_source", - True)) + st.session_state.source = st.sidebar.checkbox( + "Use online database (google sheets).", + value=st.session_state.source, + on_change=lambda: setattr(st.session_state, "new_source", True), + ) source = "online" if st.session_state.source is True else "offline" if st.session_state.new_source in [True, None]: init_session(source) @@ -346,14 +380,30 @@ async def main() -> None: # Display chat UI with col11: if st.session_state.winner_selected is True: - st.markdown("Model 1: " + st.session_state['model1'] + "", unsafe_allow_html=True) + st.markdown( + "Model 1: " + + st.session_state["model1"] + + "", + unsafe_allow_html=True, + ) else: - st.markdown("Model 1: ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░", unsafe_allow_html=True) + st.markdown( + "Model 1: ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░", + unsafe_allow_html=True, + ) with col21: if st.session_state.winner_selected is True: - st.markdown("Model 2: " + st.session_state['model2'] + "", unsafe_allow_html=True) + st.markdown( + "Model 2: " + + st.session_state["model2"] + + "", + unsafe_allow_html=True, + ) else: - st.markdown("Model 2: ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░", unsafe_allow_html=True) + st.markdown( + "Model 2: ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░", + unsafe_allow_html=True, + ) col1, col2 = st.columns(2) with col1: cont1 = st.container(height=500) @@ -363,53 +413,77 @@ async def main() -> None: st.session_state["chat_history1"] = [] if "chat_history2" not in st.session_state: st.session_state["chat_history2"] = [] - if prompt := st.chat_input("Say something", disabled=False if st.session_state.api_key_provided is True else True, on_submit=lambda: setattr(st.session_state, "winner_selected", False)): + if prompt := st.chat_input( + "Say something", + disabled=False if st.session_state.api_key_provided is True else True, + on_submit=lambda: setattr(st.session_state, "winner_selected", False), + ): st.session_state["chat_input"] = prompt st.session_state.code_input = prompt - st.session_state['chat_history1'].append({"role": "user", "content": st.session_state["chat_input"]}) - st.session_state['chat_history2'].append({"role": "user", "content": st.session_state["chat_input"]}) - message1 = st.session_state['chat_history1'] - message2 = st.session_state['chat_history2'] + st.session_state["chat_history1"].append( + {"role": "user", "content": st.session_state["chat_input"]} + ) + st.session_state["chat_history2"].append( + {"role": "user", "content": st.session_state["chat_input"]} + ) + message1 = st.session_state["chat_history1"] + message2 = st.session_state["chat_history2"] print_history(contain=(cont1, cont2)) u1 = None u2 = None try: - u1 = call_model(st.session_state['model1']) - if st.session_state['model1'] not in all_models: + u1 = call_model(st.session_state["model1"]) + if st.session_state["model1"] not in all_models: with open("models.json", "w") as models_file_update: upd_models = [model for model in all_models] - upd_models[-1] = st.session_state['model1'] + upd_models[-1] = st.session_state["model1"] upd_models.append("other") upd_models_dict = {"models": tuple(upd_models)} json.dump(upd_models_dict, models_file_update) - if (model1_to_add := st.session_state['model1'][:st.session_state['model1'].find("@")]) not in data.keys(): - st.session_state['vote_counts'][f"{model1_to_add}"]["Wins ⭐"] = 0 - st.session_state['vote_counts'][f"{model1_to_add}"]["Losses ❌"] = 0 + if ( + model1_to_add := st.session_state["model1"][ + : st.session_state["model1"].find("@") + ] + ) not in data.keys(): + st.session_state["vote_counts"][f"{model1_to_add}"]["Wins ⭐"] = 0 + st.session_state["vote_counts"][f"{model1_to_add}"]["Losses ❌"] = 0 except UnifyError: setattr(st.session_state, "winner_selected", True) - if "@" not in st.session_state['model1']: - cont1.error("Model endpoints have to follow the '@' format") - cont2.error("Model endpoints have to follow the '@' format") + if "@" not in st.session_state["model1"]: + cont1.error( + "Model endpoints have to follow the '@' format" + ) + cont2.error( + "Model endpoints have to follow the '@' format" + ) else: cont1.error("One of the models is not currently supported.") cont2.error("One of the models is not currently supported.") try: - u2 = call_model(st.session_state['model2']) - if st.session_state['model2'] not in all_models: + u2 = call_model(st.session_state["model2"]) + if st.session_state["model2"] not in all_models: with open("models.json", "w") as models_file_update: upd_models = [model for model in all_models] - upd_models[-1] = st.session_state['model2'] + upd_models[-1] = st.session_state["model2"] upd_models.append("other") upd_models_dict = {"models": tuple(upd_models)} json.dump(upd_models_dict, models_file_update) - if (model2_to_add := st.session_state['model2'][:st.session_state['model2'].find("@")]) not in data.keys(): - st.session_state['vote_counts'][f"{model2_to_add}"]["Wins ⭐"] = 0 - st.session_state['vote_counts'][f"{model2_to_add}"]["Losses ❌"] = 0 + if ( + model2_to_add := st.session_state["model2"][ + : st.session_state["model2"].find("@") + ] + ) not in data.keys(): + st.session_state["vote_counts"][f"{model2_to_add}"]["Wins ⭐"] = 0 + st.session_state["vote_counts"][f"{model2_to_add}"]["Losses ❌"] = 0 except UnifyError: setattr(st.session_state, "winner_selected", True) - if "@" not in st.session_state['model2']: - cont1.error("Model endpoints have to follow the '@' format") - cont2.error("Model endpoints have to follow the '@' format") + if "@" not in st.session_state["model2"]: + cont1.error( + "Model endpoints have to follow the '@' format" + ) + cont2.error( + "Model endpoints have to follow the '@' format" + ) else: cont1.error("One of the models is not currently supported.") cont2.error("One of the models is not currently supported.") @@ -426,69 +500,96 @@ async def call(unify_obj, model, contain, message): if full_response == "": full_response = "" except UnifyError as error_message: - contain.error(f"The selected model and/or provider might not be available. Clearing the chat history.\n {error_message}", icon="🚨") + contain.error( + f"The selected model and/or provider might not be available. Clearing the chat history.\n {error_message}", + icon="🚨", + ) if model == "model1": st.session_state.chat_history1 = [] if model == "model2": st.session_state.chat_history2 = [] setattr(st.session_state, "winner_selected", False) except IndexError as error_message: - contain.error(f"There was an issue with the model's response:\n {error_message}", icon="🚨") + contain.error( + f"There was an issue with the model's response:\n {error_message}", + icon="🚨", + ) setattr(st.session_state, "winner_selected", False) finally: history(model=model, output=full_response) await asyncio.gather( - call(u1,model='model1', contain=cont1,message=message1), - call(u2,model='model2', contain=cont2,message=message2) + call(u1, model="model1", contain=cont1, message=message1), + call(u2, model="model2", contain=cont2, message=message2), ) c1, c2 = st.columns(2) # Display the vote buttons vote_disabled = True if st.session_state.winner_selected in [None, True] else False with c1: - left_button_clicked = st.button("👍 Vote First Model", disabled=vote_disabled, - on_click=lambda: setattr(st.session_state, "winner_selected", True)) + left_button_clicked = st.button( + "👍 Vote First Model", + disabled=vote_disabled, + on_click=lambda: setattr(st.session_state, "winner_selected", True), + ) if left_button_clicked: st.balloons() # Increase the vote count for the selected model by 1 when the button is clicked - model1 = st.session_state['model1'].split("@")[0] - model2 = st.session_state['model2'].split("@")[0] - st.session_state['vote_counts'][model1]["Wins ⭐"] += 1 - st.session_state['vote_counts'][st.session_state['model2'].split("@")[0]]["Losses ❌"] += 1 - if model1 not in st.session_state.detailed_leaderboards["scores"].keys() or model1 not in st.session_state.detailed_leaderboards["scores"].keys(): + model1 = st.session_state["model1"].split("@")[0] + model2 = st.session_state["model2"].split("@")[0] + st.session_state["vote_counts"][model1]["Wins ⭐"] += 1 + st.session_state["vote_counts"][st.session_state["model2"].split("@")[0]][ + "Losses ❌" + ] += 1 + if ( + model1 not in st.session_state.detailed_leaderboards["scores"].keys() + or model1 not in st.session_state.detailed_leaderboards["scores"].keys() + ): st.session_state.detailed_leaderboards["scores"].at[model1, model2] = 0 st.session_state.detailed_leaderboards["scores"].at[model1, model2] += 1 print_history(contain=(cont1, cont2)) try: - st.session_state.code_input = st.session_state["chat_history1"][-2]['content'] + st.session_state.code_input = st.session_state["chat_history1"][-2][ + "content" + ] except IndexError: st.session_state.code_input = " " with c2: - right_button_clicked = st.button("👍 Vote Second Model", disabled=vote_disabled, - on_click=lambda: setattr(st.session_state, "winner_selected", True)) + right_button_clicked = st.button( + "👍 Vote Second Model", + disabled=vote_disabled, + on_click=lambda: setattr(st.session_state, "winner_selected", True), + ) if right_button_clicked: st.balloons() # Increase the vote count for the selected model by 1 when the button is clicked - model1 = st.session_state['model1'].split("@")[0] - model2 = st.session_state['model2'].split("@")[0] - st.session_state['vote_counts'][model2]["Wins ⭐"] += 1 - st.session_state['vote_counts'][st.session_state['model1'].split("@")[0]]["Losses ❌"] += 1 - if model2 not in st.session_state.detailed_leaderboards["scores"].keys() or model1 not in st.session_state.detailed_leaderboards["scores"].keys(): + model1 = st.session_state["model1"].split("@")[0] + model2 = st.session_state["model2"].split("@")[0] + st.session_state["vote_counts"][model2]["Wins ⭐"] += 1 + st.session_state["vote_counts"][st.session_state["model1"].split("@")[0]][ + "Losses ❌" + ] += 1 + if ( + model2 not in st.session_state.detailed_leaderboards["scores"].keys() + or model1 not in st.session_state.detailed_leaderboards["scores"].keys() + ): st.session_state.detailed_leaderboards["scores"].at[model2, model1] = 0 st.session_state.detailed_leaderboards["scores"].at[model2, model1] += 1 - + print_history(contain=(cont1, cont2)) try: - st.session_state.code_input = st.session_state["chat_history2"][-2]['content'] + st.session_state.code_input = st.session_state["chat_history2"][-2][ + "content" + ] except IndexError: st.session_state.code_input = " " # Add custom CSS for the buttons history_button_clicked = st.button("Clear Histroy") if history_button_clicked: - st.session_state["chat_history1"] = [] - st.session_state["chat_history2"] = [] + st.session_state["chat_history1"] = [] + st.session_state["chat_history2"] = [] + if __name__ == "__main__": asyncio.run(main()) diff --git a/helpers.py b/helpers.py index 8853758..d3bbbb9 100644 --- a/helpers.py +++ b/helpers.py @@ -1,14 +1,8 @@ import streamlit as st from streamlit_gsheets import GSheetsConnection -from unify import AsyncUnify -from unify import Unify import os -from unify.exceptions import UnifyError -import asyncio import pandas as pd import json -import requests -import random leaderboard_worksheet_id = 0 detail_worksheet_id = 1113438455 @@ -18,10 +12,8 @@ class database: def get_offline() -> None: - - ''' - Static method. Assigns the local database's contents to - the corresponding session states. + """Static method. Assigns the local database's contents to the + corresponding session states. Parameters ---------- @@ -30,8 +22,7 @@ def get_offline() -> None: Returns ------- None - - ''' + """ keys = ["leaderboard", "detail", "models", "detailed_leaderboards"] for key in keys: @@ -50,44 +41,73 @@ def get_offline() -> None: models_df = pd.DataFrame(data) models_df.to_csv("models.csv") - all_models = tuple(data['models']) + all_models = tuple(data["models"]) if not os.path.exists("./leaderboard.csv"): - leaderboard = pd.DataFrame(["other", 0, 0], columns=["Model Name", "Wins ⭐", "Losses ❌"], index=["other"]) + leaderboard = pd.DataFrame( + ["other", 0, 0], + columns=["Model Name", "Wins ⭐", "Losses ❌"], + index=["other"], + ) leaderboard.to_csv("leaderboard.csv") - data = pd.read_csv("leaderboard.csv") # This will raise an error if the file does not exist - json_data = {model: {"Wins ⭐": wins, "Losses ❌": losses} for model, wins, losses in zip(data["Model Name"], data["Wins ⭐"], data["Losses ❌"])} - - if 'vote_counts' not in st.session_state: - st.session_state['vote_counts'] = json_data + data = pd.read_csv( + "leaderboard.csv" + ) # This will raise an error if the file does not exist + json_data = { + model: {"Wins ⭐": wins, "Losses ❌": losses} + for model, wins, losses in zip( + data["Model Name"], data["Wins ⭐"], data["Losses ❌"] + ) + } if not os.path.exists("./detail_leaderboards.json"): - with open("detail_leaderboards.json", "w") as out_file: - detail_leaderboards = {"scores": {winning_model: {losing_model: 0 for losing_model in json_data.keys()} for winning_model in json_data.keys()}} + with open("detail_leaderboards.json", "w") as out_file: + detail_leaderboards = { + "scores": { + winning_model: { + losing_model: 0 for losing_model in json_data.keys() + } + for winning_model in json_data.keys() + } + } json.dump(detail_leaderboards, out_file) if not os.path.exists("./detail_leaderboards.csv"): - detail_dataframe = pd.DataFrame(data={winning_model: {losing_model: 0 for losing_model in json_data.keys()} for winning_model in json_data.keys()}) + detail_dataframe = pd.DataFrame( + data={ + winning_model: { + losing_model: 0 for losing_model in json_data.keys() + } + for winning_model in json_data.keys() + } + ) detail_dataframe.index = list(json_data.keys()) - detail_dataframe.to_csv('detail_leaderboards.csv') + detail_dataframe.to_csv("detail_leaderboards.csv") if not os.path.exists("./detail_leaderboards.json"): - with open("detail_leaderboards.json", "w") as out_file: - detail_leaderboards = {"scores": {winning_model: {losing_model: 0 for losing_model in json_data.keys()} for winning_model in json_data.keys()}} + with open("detail_leaderboards.json", "w") as out_file: + detail_leaderboards = { + "scores": { + winning_model: { + losing_model: 0 for losing_model in json_data.keys() + } + for winning_model in json_data.keys() + } + } json.dump(detail_leaderboards, out_file) with open("detail_leaderboards.csv", "r") as in_file: - st.session_state.detailed_leaderboards = {"scores": pd.read_csv(in_file, index_col=0)} + st.session_state.detailed_leaderboards = { + "scores": pd.read_csv(in_file, index_col=0) + } st.session_state.leaderboard = json_data st.session_state.models = all_models def get_online(): - - ''' - Static method. Assigns the online database's contents to - the corresponding session states. + """Static method. Assigns the online database's contents to the + corresponding session states. Parameters ---------- @@ -96,8 +116,7 @@ def get_online(): Returns ------- None - - ''' + """ keys = ["leaderboard", "detail", "models"] for key in keys: @@ -107,20 +126,24 @@ def get_online(): conn = st.connection("gsheets", type=GSheetsConnection) gsheets_leaderboard = conn.read(worksheet="leaderboard") - gsheets_leaderboard.index = list(gsheets_leaderboard['Model Name']) + gsheets_leaderboard.index = list(gsheets_leaderboard["Model Name"]) gsheets_detail = conn.read(worksheet="detail_leaderboard") gsheets_models = conn.read(worksheet="models") - gsheets_detail.index = list(gsheets_leaderboard['Model Name']) + gsheets_detail.index = list(gsheets_leaderboard["Model Name"]) + + gsheets_leaderboard.dropna(axis=0, how="all", inplace=True) + gsheets_leaderboard.dropna(axis=1, how="all", inplace=True) + gsheets_detail.dropna(axis=0, how="all", inplace=True) + gsheets_detail.dropna(axis=1, how="all", inplace=True) + gsheets_models.dropna(axis=0, how="all", inplace=True) + gsheets_models.dropna(axis=1, how="all", inplace=True) st.session_state.leaderboard = gsheets_leaderboard st.session_state.detailed_leaderboards = {"scores": gsheets_detail} - st.session_state.models = gsheets_models['Models'] + st.session_state.models = gsheets_models["Models"] def save_offline(): - - ''' - Static method. Saves the session states - in the local database. + """Static method. Saves the session states in the local database. Parameters ---------- @@ -129,29 +152,31 @@ def save_offline(): Returns ------- None - - ''' + """ keys = ["leaderboard", "detail", "models"] for key in keys: if key not in st.session_state.keys(): st.session_state[key] = None - sorted_counts = sorted(st.session_state['vote_counts'].items(), key=lambda x: x[1]["Wins ⭐"] + x[1]["Losses ❌"], reverse=True) + sorted_counts = sorted( + st.session_state["vote_counts"].items(), + key=lambda x: x[1]["Wins ⭐"] + x[1]["Losses ❌"], + reverse=True, + ) for idx, votes in enumerate(sorted_counts): sorted_counts[idx] = (votes[0], votes[1]["Wins ⭐"], votes[1]["Losses ❌"]) - sorted_counts_df = pd.DataFrame(sorted_counts, columns=['Model Name', 'Wins ⭐', 'Losses ❌']) + sorted_counts_df = pd.DataFrame( + sorted_counts, columns=["Model Name", "Wins ⭐", "Losses ❌"] + ) - detail_leaderboards = st.session_state.detailed_leaderboards - with open("detail_leaderboards.json", "w") as out_file: - json.dump(detail_leaderboards, out_file) - sorted_counts_df.to_csv('leaderboard.csv', index=False) + detail_leaderboards = st.session_state.detailed_leaderboards["scores"] - def save_online(): + detail_leaderboards.to_csv("detail_leaderboards.csv", index=False) + sorted_counts_df.to_csv("leaderboard.csv", index=False) - ''' - Static method. Saves the session states - in the online database. + def save_online(): + """Static method. Saves the session states in the online database. Parameters ---------- @@ -160,19 +185,24 @@ def save_online(): Returns ------- None - - ''' + """ keys = ["leaderboard", "detail", "models"] for key in keys: if key not in st.session_state.keys(): st.session_state[key] = None - sorted_counts = sorted(st.session_state['vote_counts'].items(), key=lambda x: x[1]["Wins ⭐"] + x[1]["Losses ❌"], reverse=True) + sorted_counts = sorted( + st.session_state["vote_counts"].items(), + key=lambda x: x[1]["Wins ⭐"] + x[1]["Losses ❌"], + reverse=True, + ) for idx, votes in enumerate(sorted_counts): sorted_counts[idx] = (votes[0], votes[1]["Wins ⭐"], votes[1]["Losses ❌"]) - sorted_counts_df = pd.DataFrame(sorted_counts, columns=['Model Name', 'Wins ⭐', 'Losses ❌']) + sorted_counts_df = pd.DataFrame( + sorted_counts, columns=["Model Name", "Wins ⭐", "Losses ❌"] + ) detail_leaderboards = st.session_state.detailed_leaderboards["scores"] diff --git a/leaderboard.csv b/leaderboard.csv index c587915..68eb4e5 100644 --- a/leaderboard.csv +++ b/leaderboard.csv @@ -1,6 +1,6 @@ Model Name,Wins ⭐,Losses ❌ -mixtral-8x7b-instruct-v0.1,1,0 -llama-2-70b-chat,0,1 +mixtral-8x7b-instruct-v0.1,0,0 +llama-2-70b-chat,0,0 gpt-4-turbo,0,0 mistral-large,0,0 llama-2-7b-chat,0,0 diff --git a/pages/1_leaderboards.py b/pages/1_leaderboards.py index 1d14d27..cde4fa6 100644 --- a/pages/1_leaderboards.py +++ b/pages/1_leaderboards.py @@ -1,13 +1,5 @@ import streamlit as st -from unify import AsyncUnify -from unify import Unify -import os -from unify.exceptions import UnifyError -import asyncio import pandas as pd -import json -import requests -import random import helpers st.set_page_config( @@ -20,19 +12,34 @@ # Add custom CSS for the buttons st.markdown( -""" + """

LeaderBoard For LLMs 🚀

""", -unsafe_allow_html=True) + unsafe_allow_html=True, +) # Create a DataFrame with the sorted vote counts -sorted_counts = sorted(st.session_state['vote_counts'].items(), key=lambda x: x[1]["Wins ⭐"] + x[1]["Losses ❌"], reverse=True) -for idx, votes in enumerate(sorted_counts): - sorted_counts[idx] = (votes[0], votes[1]["Wins ⭐"], votes[1]["Losses ❌"]) -sorted_counts_df = pd.DataFrame(sorted_counts, columns=['Model Name', 'Wins ⭐', 'Losses ❌']) - -st.data_editor(sorted_counts_df, num_rows="dynamic", use_container_width=True) +if source == "offline": + sorted_counts = sorted( + st.session_state["vote_counts"].items(), + key=lambda x: x[1]["Wins ⭐"] + x[1]["Losses ❌"], + reverse=True, + ) + for idx, votes in enumerate(sorted_counts): + sorted_counts[idx] = (votes[0], votes[1]["Wins ⭐"], votes[1]["Losses ❌"]) +if source == "online": + vote_counts_df = pd.DataFrame(st.session_state.vote_counts) + sorted_counts = vote_counts_df[["Model Name", "Wins ⭐", "Losses ❌"]] + sorted_counts.sort_values(by=["Wins ⭐", "Losses ❌"], inplace=True) + sorted_counts.style.hide() +sorted_counts_df = pd.DataFrame( + sorted_counts, columns=["Model Name", "Wins ⭐", "Losses ❌"] +) +sorted_counts_df.style.hide() +st.data_editor( + sorted_counts_df, num_rows="dynamic", use_container_width=True, hide_index=True +) detail_leaderboards = st.session_state.detailed_leaderboards @@ -43,15 +50,19 @@ with c2: model2_detail = st.selectbox("Select model 2", model_selection) with st.container(border=True): - st.markdown(f"

{model1_detail} : {model2_detail}

", - unsafe_allow_html=True) - st.markdown(f"

{int(detail_leaderboards['scores'].at[model1_detail, model2_detail])}:{int(detail_leaderboards['scores'].at[model2_detail, model1_detail])}

", - unsafe_allow_html=True) + st.markdown( + f"

{model1_detail} : {model2_detail}

", + unsafe_allow_html=True, + ) + st.markdown( + f"

{int(detail_leaderboards['scores'].at[model1_detail, model2_detail])}:{int(detail_leaderboards['scores'].at[model2_detail, model1_detail])}

", + unsafe_allow_html=True, + ) with st.sidebar: - st.button("Save leaderboards", key="save") - if st.session_state.save: - if source == "offline": - helpers.database.save_offline() - if source == "online": - helpers.database.save_online() + st.button("Save leaderboards", key="save") + if st.session_state.save: + if source == "offline": + helpers.database.save_offline() + if source == "online": + helpers.database.save_online() From d3d0742fb4567cb91c9d5682b630d81db789023c Mon Sep 17 00:00:00 2001 From: Kacper-W-Kozdon Date: Tue, 7 May 2024 13:56:45 +0200 Subject: [PATCH 04/69] minor leaderboard changes --- helpers.py | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/helpers.py b/helpers.py index d3bbbb9..02dc422 100644 --- a/helpers.py +++ b/helpers.py @@ -118,7 +118,14 @@ def get_online(): None """ - keys = ["leaderboard", "detail", "models"] + keys = [ + "leaderboard", + "detail", + "models", + "online_leaderboard", + "online_detailed", + "online_models", + ] for key in keys: if key not in st.session_state.keys(): st.session_state[key] = None @@ -138,10 +145,19 @@ def get_online(): gsheets_models.dropna(axis=0, how="all", inplace=True) gsheets_models.dropna(axis=1, how="all", inplace=True) + st.session_state.online_leaderboard = gsheets_leaderboard + st.session_state.online_detailed = {"scores": gsheets_detail} + st.session_state.online_models = gsheets_models["Models"] + st.session_state.leaderboard = gsheets_leaderboard - st.session_state.detailed_leaderboards = {"scores": gsheets_detail} + st.session_state.detailed_leaderboard = {"scores": gsheets_detail} st.session_state.models = gsheets_models["Models"] + st.session_state.leaderboard.where( + gsheets_leaderboard[["Wins ⭐", "Losses ❌"]] == 0, 0, inplace=True + ) + st.session_state.detailed_leaderboard["scores"].where(gsheets_detail == 0, 0) + def save_offline(): """Static method. Saves the session states in the local database. From eacf6a5abdc7d729422f8fdd37b057c5c62f82b1 Mon Sep 17 00:00:00 2001 From: Kacper-W-Kozdon Date: Tue, 7 May 2024 13:59:58 +0200 Subject: [PATCH 05/69] minor leaderboard changes --- helpers.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/helpers.py b/helpers.py index 02dc422..8990b64 100644 --- a/helpers.py +++ b/helpers.py @@ -153,10 +153,14 @@ def get_online(): st.session_state.detailed_leaderboard = {"scores": gsheets_detail} st.session_state.models = gsheets_models["Models"] - st.session_state.leaderboard.where( - gsheets_leaderboard[["Wins ⭐", "Losses ❌"]] == 0, 0, inplace=True + st.session_state.leaderboard = st.session_state.leaderboard.where( + gsheets_leaderboard[["Wins ⭐", "Losses ❌"]] == 0, 0 + ) + st.session_state.detailed_leaderboard["scores"] = ( + st.session_state.detailed_leaderboard["scores"].where( + gsheets_detail == 0, 0 + ) ) - st.session_state.detailed_leaderboard["scores"].where(gsheets_detail == 0, 0) def save_offline(): """Static method. Saves the session states in the local database. From 759279afa2636fbc2b656e401d92999477639e41 Mon Sep 17 00:00:00 2001 From: Kacper-W-Kozdon Date: Tue, 7 May 2024 14:14:41 +0200 Subject: [PATCH 06/69] minor leaderboard changes --- helpers.py | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/helpers.py b/helpers.py index 8990b64..34d541a 100644 --- a/helpers.py +++ b/helpers.py @@ -105,11 +105,15 @@ def get_offline() -> None: st.session_state.leaderboard = json_data st.session_state.models = all_models - def get_online(): + def get_online(update: bool = False): """Static method. Assigns the online database's contents to the corresponding session states. Parameters + update + if True, the session states with the new votes will not get reset, only the full leaderboards + will be refreshed. Used to ensure that the online database will get correctly updated. + Default: False ---------- None @@ -149,18 +153,19 @@ def get_online(): st.session_state.online_detailed = {"scores": gsheets_detail} st.session_state.online_models = gsheets_models["Models"] - st.session_state.leaderboard = gsheets_leaderboard - st.session_state.detailed_leaderboard = {"scores": gsheets_detail} - st.session_state.models = gsheets_models["Models"] + if not update: + st.session_state.leaderboard = gsheets_leaderboard + st.session_state.detailed_leaderboard = {"scores": gsheets_detail} + st.session_state.models = gsheets_models["Models"] - st.session_state.leaderboard = st.session_state.leaderboard.where( - gsheets_leaderboard[["Wins ⭐", "Losses ❌"]] == 0, 0 - ) - st.session_state.detailed_leaderboard["scores"] = ( - st.session_state.detailed_leaderboard["scores"].where( - gsheets_detail == 0, 0 + st.session_state.leaderboard = st.session_state.leaderboard.where( + gsheets_leaderboard[["Wins ⭐", "Losses ❌"]] == 0, 0 + ) + st.session_state.detailed_leaderboard["scores"] = ( + st.session_state.detailed_leaderboard["scores"].where( + gsheets_detail == 0, 0 + ) ) - ) def save_offline(): """Static method. Saves the session states in the local database. From 3425a9ac6d29b437949b60858e42621ec79677df Mon Sep 17 00:00:00 2001 From: Kacper-W-Kozdon Date: Tue, 7 May 2024 14:33:09 +0200 Subject: [PATCH 07/69] minor leaderboard changes --- helpers.py | 4 ++++ pages/1_leaderboards.py | 14 ++++++++++++-- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/helpers.py b/helpers.py index 34d541a..ad72c79 100644 --- a/helpers.py +++ b/helpers.py @@ -11,6 +11,7 @@ class database: + @staticmethod def get_offline() -> None: """Static method. Assigns the local database's contents to the corresponding session states. @@ -105,6 +106,7 @@ def get_offline() -> None: st.session_state.leaderboard = json_data st.session_state.models = all_models + @staticmethod def get_online(update: bool = False): """Static method. Assigns the online database's contents to the corresponding session states. @@ -167,6 +169,7 @@ def get_online(update: bool = False): ) ) + @staticmethod def save_offline(): """Static method. Saves the session states in the local database. @@ -200,6 +203,7 @@ def save_offline(): detail_leaderboards.to_csv("detail_leaderboards.csv", index=False) sorted_counts_df.to_csv("leaderboard.csv", index=False) + @staticmethod def save_online(): """Static method. Saves the session states in the online database. diff --git a/pages/1_leaderboards.py b/pages/1_leaderboards.py index cde4fa6..f5770e8 100644 --- a/pages/1_leaderboards.py +++ b/pages/1_leaderboards.py @@ -28,22 +28,32 @@ ) for idx, votes in enumerate(sorted_counts): sorted_counts[idx] = (votes[0], votes[1]["Wins ⭐"], votes[1]["Losses ❌"]) + + detail_leaderboards = st.session_state.detailed_leaderboards + model_selection = list(detail_leaderboards["scores"].keys())[1:] + if source == "online": + helpers.database.get_online(True) + detail_leaderboards = st.session_state.detailed_leaderboards.add( + st.session_state.online_detailed, fill_value=0 + ) + model_selection = list(detail_leaderboards["scores"].keys())[1:] vote_counts_df = pd.DataFrame(st.session_state.vote_counts) + vote_counts_df.add(st.session_state.online_leaderboard, fill_value=0) sorted_counts = vote_counts_df[["Model Name", "Wins ⭐", "Losses ❌"]] sorted_counts.sort_values(by=["Wins ⭐", "Losses ❌"], inplace=True) sorted_counts.style.hide() + sorted_counts_df = pd.DataFrame( sorted_counts, columns=["Model Name", "Wins ⭐", "Losses ❌"] ) sorted_counts_df.style.hide() + st.data_editor( sorted_counts_df, num_rows="dynamic", use_container_width=True, hide_index=True ) -detail_leaderboards = st.session_state.detailed_leaderboards -model_selection = list(detail_leaderboards["scores"].keys())[1:] c1, c2 = st.columns(2) with c1: model1_detail = st.selectbox("Select model 1", model_selection) From 750d136871c8085df543e43bd72d6af62431c04c Mon Sep 17 00:00:00 2001 From: Kacper-W-Kozdon Date: Tue, 7 May 2024 18:57:48 +0200 Subject: [PATCH 08/69] minor leaderboard changes --- chatbot_arena.py | 2 ++ helpers.py | 19 +++++++++---------- pages/1_leaderboards.py | 4 +++- 3 files changed, 14 insertions(+), 11 deletions(-) diff --git a/chatbot_arena.py b/chatbot_arena.py index fe73883..0af6fe3 100644 --- a/chatbot_arena.py +++ b/chatbot_arena.py @@ -438,6 +438,7 @@ async def main() -> None: upd_models = [model for model in all_models] upd_models[-1] = st.session_state["model1"] upd_models.append("other") + st.session_state_models = upd_models upd_models_dict = {"models": tuple(upd_models)} json.dump(upd_models_dict, models_file_update) if ( @@ -466,6 +467,7 @@ async def main() -> None: upd_models = [model for model in all_models] upd_models[-1] = st.session_state["model2"] upd_models.append("other") + st.session_state.models = upd_models upd_models_dict = {"models": tuple(upd_models)} json.dump(upd_models_dict, models_file_update) if ( diff --git a/helpers.py b/helpers.py index ad72c79..22a3d53 100644 --- a/helpers.py +++ b/helpers.py @@ -221,19 +221,18 @@ def save_online(): if key not in st.session_state.keys(): st.session_state[key] = None - sorted_counts = sorted( - st.session_state["vote_counts"].items(), - key=lambda x: x[1]["Wins ⭐"] + x[1]["Losses ❌"], - reverse=True, - ) - for idx, votes in enumerate(sorted_counts): - sorted_counts[idx] = (votes[0], votes[1]["Wins ⭐"], votes[1]["Losses ❌"]) + database.get_online(True) - sorted_counts_df = pd.DataFrame( - sorted_counts, columns=["Model Name", "Wins ⭐", "Losses ❌"] + vote_counts_df = pd.DataFrame(st.session_state.vote_counts) + vote_counts_df = vote_counts_df.add( + st.session_state.online_leaderboard, fill_value=0 ) + sorted_counts_df = vote_counts_df[["Model Name", "Wins ⭐", "Losses ❌"]] + sorted_counts_df.sort_values(by=["Wins ⭐", "Losses ❌"], inplace=True) - detail_leaderboards = st.session_state.detailed_leaderboards["scores"] + detail_leaderboards = st.session_state.detailed_leaderboards["scores"].add( + st.session_state.online_detailed["scores"] + ) models = st.session_state.models diff --git a/pages/1_leaderboards.py b/pages/1_leaderboards.py index f5770e8..8859ba3 100644 --- a/pages/1_leaderboards.py +++ b/pages/1_leaderboards.py @@ -39,7 +39,9 @@ ) model_selection = list(detail_leaderboards["scores"].keys())[1:] vote_counts_df = pd.DataFrame(st.session_state.vote_counts) - vote_counts_df.add(st.session_state.online_leaderboard, fill_value=0) + vote_counts_df = vote_counts_df.add( + st.session_state.online_leaderboard, fill_value=0 + ) sorted_counts = vote_counts_df[["Model Name", "Wins ⭐", "Losses ❌"]] sorted_counts.sort_values(by=["Wins ⭐", "Losses ❌"], inplace=True) sorted_counts.style.hide() From 2b4b465338dd37d47724d0e7cdd55986afedc911 Mon Sep 17 00:00:00 2001 From: Kacper-W-Kozdon Date: Tue, 7 May 2024 19:05:30 +0200 Subject: [PATCH 09/69] minor leaderboard changes --- chatbot_arena.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/chatbot_arena.py b/chatbot_arena.py index 0af6fe3..095441d 100644 --- a/chatbot_arena.py +++ b/chatbot_arena.py @@ -539,6 +539,10 @@ async def call(unify_obj, model, contain, message): # Increase the vote count for the selected model by 1 when the button is clicked model1 = st.session_state["model1"].split("@")[0] model2 = st.session_state["model2"].split("@")[0] + if model1 not in st.session_state.vote_counts.keys(): + st.session_state.vote_counts[model1] = {"Wins ⭐": 0, "Losses ❌": 0} + if model2 not in st.session_state.vote_counts.keys(): + st.session_state.vote_counts[model2] = {"Wins ⭐": 0, "Losses ❌": 0} st.session_state["vote_counts"][model1]["Wins ⭐"] += 1 st.session_state["vote_counts"][st.session_state["model2"].split("@")[0]][ "Losses ❌" @@ -568,6 +572,10 @@ async def call(unify_obj, model, contain, message): # Increase the vote count for the selected model by 1 when the button is clicked model1 = st.session_state["model1"].split("@")[0] model2 = st.session_state["model2"].split("@")[0] + if model1 not in st.session_state.vote_counts.keys(): + st.session_state.vote_counts[model1] = {"Wins ⭐": 0, "Losses ❌": 0} + if model2 not in st.session_state.vote_counts.keys(): + st.session_state.vote_counts[model2] = {"Wins ⭐": 0, "Losses ❌": 0} st.session_state["vote_counts"][model2]["Wins ⭐"] += 1 st.session_state["vote_counts"][st.session_state["model1"].split("@")[0]][ "Losses ❌" From d6acd57abb776c445a25f5c347d5465f7b20933d Mon Sep 17 00:00:00 2001 From: Kacper-W-Kozdon Date: Tue, 7 May 2024 19:16:45 +0200 Subject: [PATCH 10/69] minor leaderboard changes --- chatbot_arena.py | 24 +++++++++--------------- 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/chatbot_arena.py b/chatbot_arena.py index 095441d..9108bcd 100644 --- a/chatbot_arena.py +++ b/chatbot_arena.py @@ -438,7 +438,7 @@ async def main() -> None: upd_models = [model for model in all_models] upd_models[-1] = st.session_state["model1"] upd_models.append("other") - st.session_state_models = upd_models + st.session_state.models = upd_models upd_models_dict = {"models": tuple(upd_models)} json.dump(upd_models_dict, models_file_update) if ( @@ -539,13 +539,10 @@ async def call(unify_obj, model, contain, message): # Increase the vote count for the selected model by 1 when the button is clicked model1 = st.session_state["model1"].split("@")[0] model2 = st.session_state["model2"].split("@")[0] - if model1 not in st.session_state.vote_counts.keys(): - st.session_state.vote_counts[model1] = {"Wins ⭐": 0, "Losses ❌": 0} - if model2 not in st.session_state.vote_counts.keys(): - st.session_state.vote_counts[model2] = {"Wins ⭐": 0, "Losses ❌": 0} - st.session_state["vote_counts"][model1]["Wins ⭐"] += 1 - st.session_state["vote_counts"][st.session_state["model2"].split("@")[0]][ - "Losses ❌" + + st.session_state["vote_counts"].at[model1, "Wins ⭐"] += 1 + st.session_state["vote_counts"].at[ + st.session_state["model2"].split("@")[0], "Losses ❌" ] += 1 if ( model1 not in st.session_state.detailed_leaderboards["scores"].keys() @@ -572,13 +569,10 @@ async def call(unify_obj, model, contain, message): # Increase the vote count for the selected model by 1 when the button is clicked model1 = st.session_state["model1"].split("@")[0] model2 = st.session_state["model2"].split("@")[0] - if model1 not in st.session_state.vote_counts.keys(): - st.session_state.vote_counts[model1] = {"Wins ⭐": 0, "Losses ❌": 0} - if model2 not in st.session_state.vote_counts.keys(): - st.session_state.vote_counts[model2] = {"Wins ⭐": 0, "Losses ❌": 0} - st.session_state["vote_counts"][model2]["Wins ⭐"] += 1 - st.session_state["vote_counts"][st.session_state["model1"].split("@")[0]][ - "Losses ❌" + + st.session_state["vote_counts"].at[model2, "Wins ⭐"] += 1 + st.session_state["vote_counts"].at[ + st.session_state["model1"].split("@")[0], "Losses ❌" ] += 1 if ( model2 not in st.session_state.detailed_leaderboards["scores"].keys() From 5fe54a7ccb2431d1ef0ce13573df38d402e9e3a0 Mon Sep 17 00:00:00 2001 From: Kacper-W-Kozdon Date: Tue, 7 May 2024 19:19:49 +0200 Subject: [PATCH 11/69] minor leaderboard changes --- pages/1_leaderboards.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pages/1_leaderboards.py b/pages/1_leaderboards.py index 8859ba3..b1c7d87 100644 --- a/pages/1_leaderboards.py +++ b/pages/1_leaderboards.py @@ -34,8 +34,8 @@ if source == "online": helpers.database.get_online(True) - detail_leaderboards = st.session_state.detailed_leaderboards.add( - st.session_state.online_detailed, fill_value=0 + detail_leaderboards = st.session_state.detailed_leaderboards["scores"].add( + st.session_state.online_detailed["scores"], fill_value=0 ) model_selection = list(detail_leaderboards["scores"].keys())[1:] vote_counts_df = pd.DataFrame(st.session_state.vote_counts) From cfc37e2d90d67e2266420d5e3dd3c2786b0ef582 Mon Sep 17 00:00:00 2001 From: Kacper-W-Kozdon Date: Tue, 7 May 2024 19:33:25 +0200 Subject: [PATCH 12/69] minor leaderboard changes --- helpers.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/helpers.py b/helpers.py index 22a3d53..32a0761 100644 --- a/helpers.py +++ b/helpers.py @@ -151,13 +151,15 @@ def get_online(update: bool = False): gsheets_models.dropna(axis=0, how="all", inplace=True) gsheets_models.dropna(axis=1, how="all", inplace=True) - st.session_state.online_leaderboard = gsheets_leaderboard - st.session_state.online_detailed = {"scores": gsheets_detail} + st.session_state.online_leaderboard = gsheets_leaderboard.convert_dtypes() + st.session_state.online_detailed = {"scores": gsheets_detail.convert_dtypes()} st.session_state.online_models = gsheets_models["Models"] if not update: - st.session_state.leaderboard = gsheets_leaderboard - st.session_state.detailed_leaderboard = {"scores": gsheets_detail} + st.session_state.leaderboard = gsheets_leaderboard.convert_dtypes() + st.session_state.detailed_leaderboard = { + "scores": gsheets_detail.convert_dtypes() + } st.session_state.models = gsheets_models["Models"] st.session_state.leaderboard = st.session_state.leaderboard.where( From eaedfb81dfecbfb2acd550cfced2af7028a00b83 Mon Sep 17 00:00:00 2001 From: Kacper-W-Kozdon Date: Tue, 7 May 2024 19:36:29 +0200 Subject: [PATCH 13/69] minor leaderboard changes --- helpers.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/helpers.py b/helpers.py index 32a0761..138e888 100644 --- a/helpers.py +++ b/helpers.py @@ -156,10 +156,8 @@ def get_online(update: bool = False): st.session_state.online_models = gsheets_models["Models"] if not update: - st.session_state.leaderboard = gsheets_leaderboard.convert_dtypes() - st.session_state.detailed_leaderboard = { - "scores": gsheets_detail.convert_dtypes() - } + st.session_state.leaderboard = gsheets_leaderboard + st.session_state.detailed_leaderboard = {"scores": gsheets_detail} st.session_state.models = gsheets_models["Models"] st.session_state.leaderboard = st.session_state.leaderboard.where( From 3688d1ca6f9051dd95ae1aebbd2016c2acc5c516 Mon Sep 17 00:00:00 2001 From: Kacper-W-Kozdon Date: Tue, 7 May 2024 19:39:31 +0200 Subject: [PATCH 14/69] minor leaderboard changes --- helpers.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/helpers.py b/helpers.py index 138e888..32a0761 100644 --- a/helpers.py +++ b/helpers.py @@ -156,8 +156,10 @@ def get_online(update: bool = False): st.session_state.online_models = gsheets_models["Models"] if not update: - st.session_state.leaderboard = gsheets_leaderboard - st.session_state.detailed_leaderboard = {"scores": gsheets_detail} + st.session_state.leaderboard = gsheets_leaderboard.convert_dtypes() + st.session_state.detailed_leaderboard = { + "scores": gsheets_detail.convert_dtypes() + } st.session_state.models = gsheets_models["Models"] st.session_state.leaderboard = st.session_state.leaderboard.where( From 9d836e762a7519c48f042b3a0c9b0d54807890a9 Mon Sep 17 00:00:00 2001 From: Kacper-W-Kozdon Date: Tue, 7 May 2024 19:49:11 +0200 Subject: [PATCH 15/69] minor leaderboard changes --- helpers.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/helpers.py b/helpers.py index 32a0761..0eafa62 100644 --- a/helpers.py +++ b/helpers.py @@ -156,21 +156,26 @@ def get_online(update: bool = False): st.session_state.online_models = gsheets_models["Models"] if not update: - st.session_state.leaderboard = gsheets_leaderboard.convert_dtypes() - st.session_state.detailed_leaderboard = { - "scores": gsheets_detail.convert_dtypes() - } + st.session_state.leaderboard = gsheets_leaderboard + st.session_state.detailed_leaderboard = {"scores": gsheets_detail} st.session_state.models = gsheets_models["Models"] st.session_state.leaderboard = st.session_state.leaderboard.where( gsheets_leaderboard[["Wins ⭐", "Losses ❌"]] == 0, 0 ) + + st.session_state.leaderboard = st.session_state.leaderboard.convert_dtypes() + st.session_state.detailed_leaderboard["scores"] = ( st.session_state.detailed_leaderboard["scores"].where( gsheets_detail == 0, 0 ) ) + st.session_state.detailed_leaderboard["scores"] = ( + st.session_state.detailed_leaderboard["scores"].convert_dtypes() + ) + @staticmethod def save_offline(): """Static method. Saves the session states in the local database. From 5e0557ec1f8207a743f70a3830dfcb5b2bcad294 Mon Sep 17 00:00:00 2001 From: Kacper-W-Kozdon Date: Tue, 7 May 2024 21:19:20 +0200 Subject: [PATCH 16/69] minor leaderboard changes --- chatbot_arena.py | 10 ++++++++-- helpers.py | 15 ++++++++++----- pages/1_leaderboards.py | 13 ++++++++----- 3 files changed, 26 insertions(+), 12 deletions(-) diff --git a/chatbot_arena.py b/chatbot_arena.py index 9108bcd..c8ee3aa 100644 --- a/chatbot_arena.py +++ b/chatbot_arena.py @@ -446,8 +446,11 @@ async def main() -> None: : st.session_state["model1"].find("@") ] ) not in data.keys(): - st.session_state["vote_counts"][f"{model1_to_add}"]["Wins ⭐"] = 0 - st.session_state["vote_counts"][f"{model1_to_add}"]["Losses ❌"] = 0 + st.session_state["vote_counts"].at[f"{model1_to_add}", "Wins ⭐"] = 0 + st.session_state["vote_counts"].at[f"{model1_to_add}", "Losses ❌"] = 0 + st.session_state["vote_counts"].at[ + f"{model1_to_add}", "Model Name" + ] = f"{model1_to_add}" except UnifyError: setattr(st.session_state, "winner_selected", True) if "@" not in st.session_state["model1"]: @@ -477,6 +480,9 @@ async def main() -> None: ) not in data.keys(): st.session_state["vote_counts"][f"{model2_to_add}"]["Wins ⭐"] = 0 st.session_state["vote_counts"][f"{model2_to_add}"]["Losses ❌"] = 0 + st.session_state["vote_counts"].at[ + f"{model2_to_add}", "Model Name" + ] = f"{model2_to_add}" except UnifyError: setattr(st.session_state, "winner_selected", True) if "@" not in st.session_state["model2"]: diff --git a/helpers.py b/helpers.py index 0eafa62..d4feeb3 100644 --- a/helpers.py +++ b/helpers.py @@ -151,6 +151,9 @@ def get_online(update: bool = False): gsheets_models.dropna(axis=0, how="all", inplace=True) gsheets_models.dropna(axis=1, how="all", inplace=True) + if "Unnamed: 0" in list(gsheets_detail.columns): + gsheets_detail.drop("Unnamed: 0", axis=1, inplace=True) + st.session_state.online_leaderboard = gsheets_leaderboard.convert_dtypes() st.session_state.online_detailed = {"scores": gsheets_detail.convert_dtypes()} st.session_state.online_models = gsheets_models["Models"] @@ -160,8 +163,10 @@ def get_online(update: bool = False): st.session_state.detailed_leaderboard = {"scores": gsheets_detail} st.session_state.models = gsheets_models["Models"] - st.session_state.leaderboard = st.session_state.leaderboard.where( - gsheets_leaderboard[["Wins ⭐", "Losses ❌"]] == 0, 0 + st.session_state.leaderboard[["Wins ⭐", "Losses ❌"]] = ( + st.session_state.leaderboard[["Wins ⭐", "Losses ❌"]].where( + gsheets_leaderboard[["Wins ⭐", "Losses ❌"]] == 0, 0 + ) ) st.session_state.leaderboard = st.session_state.leaderboard.convert_dtypes() @@ -231,9 +236,9 @@ def save_online(): database.get_online(True) vote_counts_df = pd.DataFrame(st.session_state.vote_counts) - vote_counts_df = vote_counts_df.add( - st.session_state.online_leaderboard, fill_value=0 - ) + vote_counts_df[["Wins ⭐", "Losses ❌"]] = vote_counts_df[ + ["Wins ⭐", "Losses ❌"] + ].add(st.session_state.online_leaderboard[["Wins ⭐", "Losses ❌"]], fill_value=0) sorted_counts_df = vote_counts_df[["Model Name", "Wins ⭐", "Losses ❌"]] sorted_counts_df.sort_values(by=["Wins ⭐", "Losses ❌"], inplace=True) diff --git a/pages/1_leaderboards.py b/pages/1_leaderboards.py index b1c7d87..8e8a7bf 100644 --- a/pages/1_leaderboards.py +++ b/pages/1_leaderboards.py @@ -37,14 +37,17 @@ detail_leaderboards = st.session_state.detailed_leaderboards["scores"].add( st.session_state.online_detailed["scores"], fill_value=0 ) - model_selection = list(detail_leaderboards["scores"].keys())[1:] + + model_selection = list(detail_leaderboards.keys()) + detail_leaderboards = {"scores": detail_leaderboards} + vote_counts_df = pd.DataFrame(st.session_state.vote_counts) - vote_counts_df = vote_counts_df.add( - st.session_state.online_leaderboard, fill_value=0 - ) + vote_counts_df[["Wins ⭐", "Losses ❌"]] = vote_counts_df[ + ["Wins ⭐", "Losses ❌"] + ].add(st.session_state.online_leaderboard[["Wins ⭐", "Losses ❌"]], fill_value=0) sorted_counts = vote_counts_df[["Model Name", "Wins ⭐", "Losses ❌"]] sorted_counts.sort_values(by=["Wins ⭐", "Losses ❌"], inplace=True) - sorted_counts.style.hide() + sorted_counts.index = range(sorted_counts.shape[0]) sorted_counts_df = pd.DataFrame( sorted_counts, columns=["Model Name", "Wins ⭐", "Losses ❌"] From 6c11a2044c4306944466979c5a7896fc1623ee0a Mon Sep 17 00:00:00 2001 From: Kacper-W-Kozdon Date: Wed, 8 May 2024 11:26:33 +0200 Subject: [PATCH 17/69] minor leaderboard changes --- chatbot_arena.py | 432 +++++++++++++++++++++++++--------------- leaderboard.csv | 4 +- pages/1_leaderboards.py | 108 +++++++--- 3 files changed, 346 insertions(+), 198 deletions(-) diff --git a/chatbot_arena.py b/chatbot_arena.py index d6b1ad2..d365a6b 100644 --- a/chatbot_arena.py +++ b/chatbot_arena.py @@ -1,8 +1,5 @@ import streamlit as st -from streamlit_gsheets import GSheetsConnection from unify import AsyncUnify -from unify import Unify -import os from unify.exceptions import UnifyError import asyncio import pandas as pd @@ -19,9 +16,7 @@ def init_session(mode: str = "keys") -> None: - - ''' - Initialize session states and databases. + """Initialize session states and databases. Parameters ---------- @@ -37,14 +32,25 @@ def init_session(mode: str = "keys") -> None: Returns ------- None - ''' + """ global all_models, data, json_data if mode == "keys": - keys = ["chat_input", "winner_selected", "api_key_provided", - "vote1", "vote2", "model1", "model2", "scores", - "authenticated", "new_models_selected", "detailed_leaderboards", - "detail", "new_source"] + keys = [ + "chat_input", + "winner_selected", + "api_key_provided", + "vote1", + "vote2", + "model1", + "model2", + "scores", + "authenticated", + "new_models_selected", + "detailed_leaderboards", + "detail", + "new_source", + ] for key in keys: if key not in st.session_state.keys(): st.session_state[key] = None @@ -56,16 +62,16 @@ def init_session(mode: str = "keys") -> None: st.session_state.chat_history2 = [] if "model1_selectbox" not in st.session_state.keys(): - st.session_state.placeholder_model1 = 'other' + st.session_state.placeholder_model1 = "other" if "model1_other" not in st.session_state.keys(): - st.session_state.placeholder_model1_other = 'model@provider' + st.session_state.placeholder_model1_other = "model@provider" if "model2_selectbox" not in st.session_state.keys(): - st.session_state.placeholder_model2 = 'other' + st.session_state.placeholder_model2 = "other" if "model2_other" not in st.session_state.keys(): - st.session_state.placeholder_model2_other = 'model@provider' + st.session_state.placeholder_model2_other = "model@provider" if "index_model1" not in st.session_state.keys(): - st.session_state.index_model1 = 0 + st.session_state.index_model1 = 0 if "index_model2" not in st.session_state.keys(): st.session_state.index_model2 = 0 if "value_model1_other" not in st.session_state.keys(): @@ -79,16 +85,21 @@ def init_session(mode: str = "keys") -> None: if "source" not in st.session_state.keys(): st.session_state.source = False + if "enable_detail" not in st.session_state.keys(): + st.session_state.enable_detail = False + if mode == "offline": helpers.database.get_offline() # Load JSON data from file all_models = st.session_state.models - #model_options = [model.split("@")[0] for model in all_models] - data = pd.read_csv("leaderboard.csv") # This will raise an error if the file does not exist + # model_options = [model.split("@")[0] for model in all_models] + data = pd.read_csv( + "leaderboard.csv" + ) # This will raise an error if the file does not exist json_data = st.session_state.leaderboard - if 'vote_counts' not in st.session_state: - st.session_state['vote_counts'] = json_data + if "vote_counts" not in st.session_state: + st.session_state["vote_counts"] = json_data if mode == "online": helpers.database.get_online() @@ -98,10 +109,9 @@ def init_session(mode: str = "keys") -> None: def select_model(api_key: str = "", authenticated: bool = False) -> None: + """Select two models for the Unify API. The models are picked through + selectbox options. - ''' - Select two models for the Unify API. The models are picked through selectbox options. - Parameters ---------- api_key @@ -115,7 +125,7 @@ def select_model(api_key: str = "", authenticated: bool = False) -> None: Returns ------- None - ''' + """ global json_data, all_models @@ -123,48 +133,72 @@ def select_model(api_key: str = "", authenticated: bool = False) -> None: model1_other_disabled = True model2_other_disabled = True - st.selectbox("Select the first model's endpoint:", - all_models, - disabled=disabled, - index=st.session_state.index_model1, - on_change=lambda: (setattr(st.session_state, "chat_history1", []), - setattr(st.session_state, "chat_history2", []), - setattr(st.session_state, "winner_selected", False), - setattr(st.session_state, "new_models_selected", True)), - key="model1_selectbox") - if st.session_state.model1_selectbox == 'other': + st.selectbox( + "Select the first model's endpoint:", + all_models, + disabled=disabled, + index=st.session_state.index_model1, + on_change=lambda: ( + setattr(st.session_state, "chat_history1", []), + setattr(st.session_state, "chat_history2", []), + setattr(st.session_state, "winner_selected", False), + setattr(st.session_state, "new_models_selected", True), + ), + key="model1_selectbox", + ) + if st.session_state.model1_selectbox == "other": model1_other_disabled = False - st.text_input('If "other", provide your own model:', - placeholder="@", - disabled=model1_other_disabled, - value=st.session_state.value_model1_other, - on_change=lambda: (setattr(st.session_state, "chat_history1", []), - setattr(st.session_state, "chat_history2", []), - setattr(st.session_state, "winner_selected", False), - setattr(st.session_state, "new_models_selected", True)), - key='model1_other') - st.selectbox("Select the second model's endpoint:", - all_models, - disabled=disabled, - index=st.session_state.index_model2, - on_change=lambda: (setattr(st.session_state, "chat_history1", []), - setattr(st.session_state, "chat_history2", []), - setattr(st.session_state, "winner_selected", False), - setattr(st.session_state, "new_models_selected", True)), - key="model2_selectbox") - if st.session_state.model2_selectbox == 'other': + st.text_input( + 'If "other", provide your own model:', + placeholder="@", + disabled=model1_other_disabled, + value=st.session_state.value_model1_other, + on_change=lambda: ( + setattr(st.session_state, "chat_history1", []), + setattr(st.session_state, "chat_history2", []), + setattr(st.session_state, "winner_selected", False), + setattr(st.session_state, "new_models_selected", True), + ), + key="model1_other", + ) + st.selectbox( + "Select the second model's endpoint:", + all_models, + disabled=disabled, + index=st.session_state.index_model2, + on_change=lambda: ( + setattr(st.session_state, "chat_history1", []), + setattr(st.session_state, "chat_history2", []), + setattr(st.session_state, "winner_selected", False), + setattr(st.session_state, "new_models_selected", True), + ), + key="model2_selectbox", + ) + if st.session_state.model2_selectbox == "other": model2_other_disabled = False - st.text_input('If "other", provide your own model:', - placeholder="@", - disabled=model2_other_disabled, - value=st.session_state.value_model2_other, - on_change=lambda: (setattr(st.session_state, "chat_history1", []), - setattr(st.session_state, "chat_history2", []), - setattr(st.session_state, "winner_selected", False), - setattr(st.session_state, "new_models_selected", True)), - key='model2_other') - selected_model1 = st.session_state.model1_selectbox if st.session_state.model1_selectbox != "other" else st.session_state.model1_other - selected_model2 = st.session_state.model2_selectbox if st.session_state.model2_selectbox != "other" else st.session_state.model2_other + st.text_input( + 'If "other", provide your own model:', + placeholder="@", + disabled=model2_other_disabled, + value=st.session_state.value_model2_other, + on_change=lambda: ( + setattr(st.session_state, "chat_history1", []), + setattr(st.session_state, "chat_history2", []), + setattr(st.session_state, "winner_selected", False), + setattr(st.session_state, "new_models_selected", True), + ), + key="model2_other", + ) + selected_model1 = ( + st.session_state.model1_selectbox + if st.session_state.model1_selectbox != "other" + else st.session_state.model1_other + ) + selected_model2 = ( + st.session_state.model2_selectbox + if st.session_state.model2_selectbox != "other" + else st.session_state.model2_other + ) st.session_state.index_model1 = all_models.index(st.session_state.model1_selectbox) st.session_state.index_model2 = all_models.index(st.session_state.model2_selectbox) @@ -177,15 +211,13 @@ def select_model(api_key: str = "", authenticated: bool = False) -> None: random.shuffle(selected_models) if st.session_state.new_models_selected in [True, None]: - st.session_state['model1'] = selected_models.pop(0) - st.session_state['model2'] = selected_models.pop(0) + st.session_state["model1"] = selected_models.pop(0) + st.session_state["model2"] = selected_models.pop(0) st.session_state.new_models_selected = False def history(model: str = "model1", output: str = "") -> None: - - ''' - Assign to session states and manage the contents of the chat history. + """Assign to session states and manage the contents of the chat history. Parameters ---------- @@ -199,26 +231,28 @@ def history(model: str = "model1", output: str = "") -> None: Returns ------- None + """ - ''' - - if model == 'model1': - st.session_state['chat_history1'].append({"role": "assistant", "content": output}) - elif model == 'model2': - st.session_state['chat_history2'].append({"role": "assistant", "content": output}) + if model == "model1": + st.session_state["chat_history1"].append( + {"role": "assistant", "content": output} + ) + elif model == "model2": + st.session_state["chat_history2"].append( + {"role": "assistant", "content": output} + ) else: st.write("Please, enter the model1 or model2 in history function.") - if len(st.session_state['chat_history1']) >= 10: - st.session_state['chat_history1'].pop(0) - if len(st.session_state['chat_history2']) >= 10: - st.session_state['chat_history2'].pop(0) + if len(st.session_state["chat_history1"]) >= 10: + st.session_state["chat_history1"].pop(0) + if len(st.session_state["chat_history2"]) >= 10: + st.session_state["chat_history2"].pop(0) def input_api_key(api_key: str = " ") -> None: - ''' - Authorize the Unify API key provided by the user. If the key is recognised, - the app's UI will be unlocked. + """Authorize the Unify API key provided by the user. If the key is + recognised, the app's UI will be unlocked. Parameters ---------- @@ -229,17 +263,19 @@ def input_api_key(api_key: str = " ") -> None: Returns ------- None - - ''' + """ authorisation_url = "https://api.unify.ai/v0/get_credits" - r = requests.get(authorisation_url, headers={"accept": "application/json", "Authorization": f"Bearer {api_key}"}).json() + r = requests.get( + authorisation_url, + headers={"accept": "application/json", "Authorization": f"Bearer {api_key}"}, + ).json() if "id" in r.keys(): setattr(st.session_state, "api_key_provided", True) setattr(st.session_state, "authenticated", True) st.sidebar.write(f"Session user credits: {r['credits']}") - st.session_state['api_key'] = api_key + st.session_state["api_key"] = api_key elif "detail" in r.keys(): setattr(st.session_state, "api_key_provided", False) setattr(st.session_state, "authenticated", False) @@ -251,8 +287,7 @@ def input_api_key(api_key: str = " ") -> None: def print_history(contain: st.container) -> None: - ''' - Print the chat history in a streamlit split container. + """Print the chat history in a streamlit split container. Parameters ---------- @@ -262,25 +297,23 @@ def print_history(contain: st.container) -> None: Returns ------- None - - ''' + """ cont1, cont2 = contain for i in st.session_state["chat_history1"]: - if i['role']=="user": - cont1.write("🧑‍💻" +" "+ i["content"]) + if i["role"] == "user": + cont1.write("🧑‍💻" + " " + i["content"]) else: cont1.write(i["content"]) for i in st.session_state["chat_history2"]: - if i['role']=="user": - cont2.write("🧑‍💻" +" "+ i["content"]) + if i["role"] == "user": + cont2.write("🧑‍💻" + " " + i["content"]) else: - cont2.write(i["content"]) + cont2.write(i["content"]) def call_model(Endpoint: str) -> AsyncUnify: - ''' - Prepare the Unify model to which the prompts will be sent. + """Prepare the Unify model to which the prompts will be sent. Parameters ---------- @@ -291,19 +324,15 @@ def call_model(Endpoint: str) -> AsyncUnify: ------- async_unify the AsyncUnify object with the endpoint assigned to it. + """ - ''' - - async_unify = AsyncUnify( - api_key=st.session_state['api_key'], - endpoint=Endpoint) + async_unify = AsyncUnify(api_key=st.session_state["api_key"], endpoint=Endpoint) return async_unify async def main() -> None: - ''' - Main loop of the streamlit app. Contains all of the flow control of the app - and generates UI elements. + """Main loop of the streamlit app. Contains all of the flow control of the + app and generates UI elements. Parameters ---------- @@ -312,12 +341,11 @@ async def main() -> None: Returns ------- None - - ''' + """ global all_models, data - st.set_option('deprecation.showPyplotGlobalUse', False) + st.set_option("deprecation.showPyplotGlobalUse", False) init_session("keys") st.session_state.code_input = "" st.markdown( @@ -326,16 +354,21 @@ async def main() -> None: ⚔️ Unify Chatbot Arena: Benchmarking LLMs in the Wild 🚀 """, - unsafe_allow_html=True) + unsafe_allow_html=True, + ) st.sidebar.subheader("Unify API Key") - api_key = st.sidebar.text_input(" ", value=st.session_state.api_key, - placeholder="API key is required to proceed.", - type="password") + api_key = st.sidebar.text_input( + " ", + value=st.session_state.api_key, + placeholder="API key is required to proceed.", + type="password", + ) input_api_key(api_key) - st.session_state.source = st.sidebar.checkbox("Use online database (google sheets).", - value=st.session_state.source, - on_change=lambda: setattr(st.session_state, "new_source", - True)) + st.session_state.source = st.sidebar.checkbox( + "Use online database (google sheets).", + value=st.session_state.source, + on_change=lambda: setattr(st.session_state, "new_source", True), + ) source = "online" if st.session_state.source is True else "offline" if st.session_state.new_source in [True, None]: init_session(source) @@ -346,14 +379,30 @@ async def main() -> None: # Display chat UI with col11: if st.session_state.winner_selected is True: - st.markdown("Model 1: " + st.session_state['model1'] + "", unsafe_allow_html=True) + st.markdown( + "Model 1: " + + st.session_state["model1"] + + "", + unsafe_allow_html=True, + ) else: - st.markdown("Model 1: ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░", unsafe_allow_html=True) + st.markdown( + "Model 1: ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░", + unsafe_allow_html=True, + ) with col21: if st.session_state.winner_selected is True: - st.markdown("Model 2: " + st.session_state['model2'] + "", unsafe_allow_html=True) + st.markdown( + "Model 2: " + + st.session_state["model2"] + + "", + unsafe_allow_html=True, + ) else: - st.markdown("Model 2: ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░", unsafe_allow_html=True) + st.markdown( + "Model 2: ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░", + unsafe_allow_html=True, + ) col1, col2 = st.columns(2) with col1: cont1 = st.container(height=500) @@ -363,53 +412,77 @@ async def main() -> None: st.session_state["chat_history1"] = [] if "chat_history2" not in st.session_state: st.session_state["chat_history2"] = [] - if prompt := st.chat_input("Say something", disabled=False if st.session_state.api_key_provided is True else True, on_submit=lambda: setattr(st.session_state, "winner_selected", False)): + if prompt := st.chat_input( + "Say something", + disabled=False if st.session_state.api_key_provided is True else True, + on_submit=lambda: setattr(st.session_state, "winner_selected", False), + ): st.session_state["chat_input"] = prompt st.session_state.code_input = prompt - st.session_state['chat_history1'].append({"role": "user", "content": st.session_state["chat_input"]}) - st.session_state['chat_history2'].append({"role": "user", "content": st.session_state["chat_input"]}) - message1 = st.session_state['chat_history1'] - message2 = st.session_state['chat_history2'] + st.session_state["chat_history1"].append( + {"role": "user", "content": st.session_state["chat_input"]} + ) + st.session_state["chat_history2"].append( + {"role": "user", "content": st.session_state["chat_input"]} + ) + message1 = st.session_state["chat_history1"] + message2 = st.session_state["chat_history2"] print_history(contain=(cont1, cont2)) u1 = None u2 = None try: - u1 = call_model(st.session_state['model1']) - if st.session_state['model1'] not in all_models: + u1 = call_model(st.session_state["model1"]) + if st.session_state["model1"] not in all_models: with open("models.json", "w") as models_file_update: upd_models = [model for model in all_models] - upd_models[-1] = st.session_state['model1'] + upd_models[-1] = st.session_state["model1"] upd_models.append("other") upd_models_dict = {"models": tuple(upd_models)} json.dump(upd_models_dict, models_file_update) - if (model1_to_add := st.session_state['model1'][:st.session_state['model1'].find("@")]) not in data.keys(): - st.session_state['vote_counts'][f"{model1_to_add}"]["Wins ⭐"] = 0 - st.session_state['vote_counts'][f"{model1_to_add}"]["Losses ❌"] = 0 + if ( + model1_to_add := st.session_state["model1"][ + : st.session_state["model1"].find("@") + ] + ) not in data.keys(): + st.session_state["vote_counts"][f"{model1_to_add}"]["Wins ⭐"] = 0 + st.session_state["vote_counts"][f"{model1_to_add}"]["Losses ❌"] = 0 except UnifyError: setattr(st.session_state, "winner_selected", True) - if "@" not in st.session_state['model1']: - cont1.error("Model endpoints have to follow the '@' format") - cont2.error("Model endpoints have to follow the '@' format") + if "@" not in st.session_state["model1"]: + cont1.error( + "Model endpoints have to follow the '@' format" + ) + cont2.error( + "Model endpoints have to follow the '@' format" + ) else: cont1.error("One of the models is not currently supported.") cont2.error("One of the models is not currently supported.") try: - u2 = call_model(st.session_state['model2']) - if st.session_state['model2'] not in all_models: + u2 = call_model(st.session_state["model2"]) + if st.session_state["model2"] not in all_models: with open("models.json", "w") as models_file_update: upd_models = [model for model in all_models] - upd_models[-1] = st.session_state['model2'] + upd_models[-1] = st.session_state["model2"] upd_models.append("other") upd_models_dict = {"models": tuple(upd_models)} json.dump(upd_models_dict, models_file_update) - if (model2_to_add := st.session_state['model2'][:st.session_state['model2'].find("@")]) not in data.keys(): - st.session_state['vote_counts'][f"{model2_to_add}"]["Wins ⭐"] = 0 - st.session_state['vote_counts'][f"{model2_to_add}"]["Losses ❌"] = 0 + if ( + model2_to_add := st.session_state["model2"][ + : st.session_state["model2"].find("@") + ] + ) not in data.keys(): + st.session_state["vote_counts"][f"{model2_to_add}"]["Wins ⭐"] = 0 + st.session_state["vote_counts"][f"{model2_to_add}"]["Losses ❌"] = 0 except UnifyError: setattr(st.session_state, "winner_selected", True) - if "@" not in st.session_state['model2']: - cont1.error("Model endpoints have to follow the '@' format") - cont2.error("Model endpoints have to follow the '@' format") + if "@" not in st.session_state["model2"]: + cont1.error( + "Model endpoints have to follow the '@' format" + ) + cont2.error( + "Model endpoints have to follow the '@' format" + ) else: cont1.error("One of the models is not currently supported.") cont2.error("One of the models is not currently supported.") @@ -426,69 +499,96 @@ async def call(unify_obj, model, contain, message): if full_response == "": full_response = "" except UnifyError as error_message: - contain.error(f"The selected model and/or provider might not be available. Clearing the chat history.\n {error_message}", icon="🚨") + contain.error( + f"The selected model and/or provider might not be available. Clearing the chat history.\n {error_message}", + icon="🚨", + ) if model == "model1": st.session_state.chat_history1 = [] if model == "model2": st.session_state.chat_history2 = [] setattr(st.session_state, "winner_selected", False) except IndexError as error_message: - contain.error(f"There was an issue with the model's response:\n {error_message}", icon="🚨") + contain.error( + f"There was an issue with the model's response:\n {error_message}", + icon="🚨", + ) setattr(st.session_state, "winner_selected", False) finally: history(model=model, output=full_response) await asyncio.gather( - call(u1,model='model1', contain=cont1,message=message1), - call(u2,model='model2', contain=cont2,message=message2) + call(u1, model="model1", contain=cont1, message=message1), + call(u2, model="model2", contain=cont2, message=message2), ) c1, c2 = st.columns(2) # Display the vote buttons vote_disabled = True if st.session_state.winner_selected in [None, True] else False with c1: - left_button_clicked = st.button("👍 Vote First Model", disabled=vote_disabled, - on_click=lambda: setattr(st.session_state, "winner_selected", True)) + left_button_clicked = st.button( + "👍 Vote First Model", + disabled=vote_disabled, + on_click=lambda: setattr(st.session_state, "winner_selected", True), + ) if left_button_clicked: st.balloons() # Increase the vote count for the selected model by 1 when the button is clicked - model1 = st.session_state['model1'].split("@")[0] - model2 = st.session_state['model2'].split("@")[0] - st.session_state['vote_counts'][model1]["Wins ⭐"] += 1 - st.session_state['vote_counts'][st.session_state['model2'].split("@")[0]]["Losses ❌"] += 1 - if model1 not in st.session_state.detailed_leaderboards["scores"].keys() or model1 not in st.session_state.detailed_leaderboards["scores"].keys(): + model1 = st.session_state["model1"].split("@")[0] + model2 = st.session_state["model2"].split("@")[0] + st.session_state["vote_counts"][model1]["Wins ⭐"] += 1 + st.session_state["vote_counts"][st.session_state["model2"].split("@")[0]][ + "Losses ❌" + ] += 1 + if ( + model1 not in st.session_state.detailed_leaderboards["scores"].keys() + or model1 not in st.session_state.detailed_leaderboards["scores"].keys() + ): st.session_state.detailed_leaderboards["scores"].at[model1, model2] = 0 st.session_state.detailed_leaderboards["scores"].at[model1, model2] += 1 print_history(contain=(cont1, cont2)) try: - st.session_state.code_input = st.session_state["chat_history1"][-2]['content'] + st.session_state.code_input = st.session_state["chat_history1"][-2][ + "content" + ] except IndexError: st.session_state.code_input = " " with c2: - right_button_clicked = st.button("👍 Vote Second Model", disabled=vote_disabled, - on_click=lambda: setattr(st.session_state, "winner_selected", True)) + right_button_clicked = st.button( + "👍 Vote Second Model", + disabled=vote_disabled, + on_click=lambda: setattr(st.session_state, "winner_selected", True), + ) if right_button_clicked: st.balloons() # Increase the vote count for the selected model by 1 when the button is clicked - model1 = st.session_state['model1'].split("@")[0] - model2 = st.session_state['model2'].split("@")[0] - st.session_state['vote_counts'][model2]["Wins ⭐"] += 1 - st.session_state['vote_counts'][st.session_state['model1'].split("@")[0]]["Losses ❌"] += 1 - if model2 not in st.session_state.detailed_leaderboards["scores"].keys() or model1 not in st.session_state.detailed_leaderboards["scores"].keys(): + model1 = st.session_state["model1"].split("@")[0] + model2 = st.session_state["model2"].split("@")[0] + st.session_state["vote_counts"][model2]["Wins ⭐"] += 1 + st.session_state["vote_counts"][st.session_state["model1"].split("@")[0]][ + "Losses ❌" + ] += 1 + if ( + model2 not in st.session_state.detailed_leaderboards["scores"].keys() + or model1 not in st.session_state.detailed_leaderboards["scores"].keys() + ): st.session_state.detailed_leaderboards["scores"].at[model2, model1] = 0 st.session_state.detailed_leaderboards["scores"].at[model2, model1] += 1 - + print_history(contain=(cont1, cont2)) try: - st.session_state.code_input = st.session_state["chat_history2"][-2]['content'] + st.session_state.code_input = st.session_state["chat_history2"][-2][ + "content" + ] except IndexError: st.session_state.code_input = " " # Add custom CSS for the buttons history_button_clicked = st.button("Clear Histroy") if history_button_clicked: - st.session_state["chat_history1"] = [] - st.session_state["chat_history2"] = [] + st.session_state["chat_history1"] = [] + st.session_state["chat_history2"] = [] + if __name__ == "__main__": asyncio.run(main()) diff --git a/leaderboard.csv b/leaderboard.csv index c587915..68eb4e5 100644 --- a/leaderboard.csv +++ b/leaderboard.csv @@ -1,6 +1,6 @@ Model Name,Wins ⭐,Losses ❌ -mixtral-8x7b-instruct-v0.1,1,0 -llama-2-70b-chat,0,1 +mixtral-8x7b-instruct-v0.1,0,0 +llama-2-70b-chat,0,0 gpt-4-turbo,0,0 mistral-large,0,0 llama-2-7b-chat,0,0 diff --git a/pages/1_leaderboards.py b/pages/1_leaderboards.py index 1d14d27..5a76c48 100644 --- a/pages/1_leaderboards.py +++ b/pages/1_leaderboards.py @@ -1,13 +1,5 @@ import streamlit as st -from unify import AsyncUnify -from unify import Unify -import os -from unify.exceptions import UnifyError -import asyncio import pandas as pd -import json -import requests -import random import helpers st.set_page_config( @@ -20,38 +12,94 @@ # Add custom CSS for the buttons st.markdown( -""" + """

LeaderBoard For LLMs 🚀

""", -unsafe_allow_html=True) + unsafe_allow_html=True, +) # Create a DataFrame with the sorted vote counts -sorted_counts = sorted(st.session_state['vote_counts'].items(), key=lambda x: x[1]["Wins ⭐"] + x[1]["Losses ❌"], reverse=True) + +with st.sidebar: + st.session_state.enable_detail = st.checkbox( + "Enable detailed view", value=st.session_state.enable_detail + ) + +sorted_counts = sorted( + st.session_state["vote_counts"].items(), + key=lambda x: x[1]["Wins ⭐"] + x[1]["Losses ❌"], + reverse=True, +) for idx, votes in enumerate(sorted_counts): sorted_counts[idx] = (votes[0], votes[1]["Wins ⭐"], votes[1]["Losses ❌"]) -sorted_counts_df = pd.DataFrame(sorted_counts, columns=['Model Name', 'Wins ⭐', 'Losses ❌']) - -st.data_editor(sorted_counts_df, num_rows="dynamic", use_container_width=True) +sorted_counts_df = pd.DataFrame( + sorted_counts, columns=["Model Name", "Wins ⭐", "Losses ❌"] +) +sorted_counts_detail = sorted_counts_df.assign(Compare=False) +sorted_counts_detail = sorted_counts_detail[ + ["Compare", "Model Name", "Wins ⭐", "Losses ❌"] +] detail_leaderboards = st.session_state.detailed_leaderboards model_selection = list(detail_leaderboards["scores"].keys())[1:] -c1, c2 = st.columns(2) -with c1: - model1_detail = st.selectbox("Select model 1", model_selection) -with c2: - model2_detail = st.selectbox("Select model 2", model_selection) -with st.container(border=True): - st.markdown(f"

{model1_detail} : {model2_detail}

", - unsafe_allow_html=True) - st.markdown(f"

{int(detail_leaderboards['scores'].at[model1_detail, model2_detail])}:{int(detail_leaderboards['scores'].at[model2_detail, model1_detail])}

", - unsafe_allow_html=True) + +if st.session_state.enable_detail: + select_for_comparison = st.data_editor( + sorted_counts_detail, num_rows="dynamic", use_container_width=True + ) + models_to_compare = select_for_comparison.loc[ + select_for_comparison["Compare"] is True + ] + + model_names = models_to_compare["Model Name"] + + view_detail = detail_leaderboards["scores"].loc[model_names, model_names] + + with st.container(border=True): + + if not model_names.empty: + st.markdown( + "

Detailed leaderboards:

", + unsafe_allow_html=True, + ) + st.markdown( + "
The values represent the number of wins of the row model against the column model
", + unsafe_allow_html=True, + ) + st.markdown( + "
(row, column) -> #row_wins
", + unsafe_allow_html=True, + ) + st.write(view_detail) + else: + st.markdown( + "

Select the models to compare.

", + unsafe_allow_html=True, + ) +else: + st.data_editor(sorted_counts_df, num_rows="dynamic", use_container_width=True) + + c1, c2 = st.columns(2) + with c1: + model1_detail = st.selectbox("Select model 1", model_selection) + with c2: + model2_detail = st.selectbox("Select model 2", model_selection) + with st.container(border=True): + st.markdown( + f"

{model1_detail} : {model2_detail}

", + unsafe_allow_html=True, + ) + st.markdown( + f"

{int(detail_leaderboards['scores'].at[model1_detail, model2_detail])}:{int(detail_leaderboards['scores'].at[model2_detail, model1_detail])}

", + unsafe_allow_html=True, + ) with st.sidebar: - st.button("Save leaderboards", key="save") - if st.session_state.save: - if source == "offline": - helpers.database.save_offline() - if source == "online": - helpers.database.save_online() + st.button("Save leaderboards", key="save") + if st.session_state.save: + if source == "offline": + helpers.database.save_offline() + if source == "online": + helpers.database.save_online() From 84e75d86b0229aa576a7263f6df70f1413be794d Mon Sep 17 00:00:00 2001 From: Kacper-W-Kozdon Date: Wed, 8 May 2024 12:05:21 +0200 Subject: [PATCH 18/69] main test merge --- pages/1_leaderboards.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/pages/1_leaderboards.py b/pages/1_leaderboards.py index b47fbae..5de9f5d 100644 --- a/pages/1_leaderboards.py +++ b/pages/1_leaderboards.py @@ -71,9 +71,7 @@ select_for_comparison = st.data_editor( sorted_counts_detail, num_rows="dynamic", use_container_width=True ) - models_to_compare = select_for_comparison.loc[ - select_for_comparison["Compare"] is True - ] + models_to_compare = select_for_comparison.loc[select_for_comparison["Compare"]] model_names = models_to_compare["Model Name"] From 2b7cc9a0832c42b368699389bd942d7ac26ba512 Mon Sep 17 00:00:00 2001 From: Kacper-W-Kozdon Date: Thu, 9 May 2024 01:46:26 +0200 Subject: [PATCH 19/69] reset temp leaderboards on save --- pages/1_leaderboards.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/pages/1_leaderboards.py b/pages/1_leaderboards.py index dd371c5..f42ae39 100644 --- a/pages/1_leaderboards.py +++ b/pages/1_leaderboards.py @@ -150,3 +150,14 @@ except Exception as e: st.write("Could not upload the results.") st.write(e) + st.session_state.leaderboard[["Wins ⭐", "Losses ❌"]] = ( + st.session_state.leaderboard[["Wins ⭐", "Losses ❌"]].where( + st.session_state.leaderboard[["Wins ⭐", "Losses ❌"]] == 0, 0 + ) + ) + + st.session_state.detailed_leaderboard["scores"] = ( + st.session_state.detailed_leaderboard["scores"].where( + st.session_state.detailed_leaderboard["scores"] == 0, 0 + ) + ) From d6b3cebf286da2a1f5f22e5776ff9d7437de49ee Mon Sep 17 00:00:00 2001 From: Kacper-W-Kozdon Date: Fri, 10 May 2024 18:14:50 +0200 Subject: [PATCH 20/69] save added to the main page, fixed session state keys --- chatbot_arena.py | 22 ++++++++++++++++++++++ helpers.py | 4 ++-- pages/1_leaderboards.py | 12 ++++++------ 3 files changed, 30 insertions(+), 8 deletions(-) diff --git a/chatbot_arena.py b/chatbot_arena.py index 559b8e4..d087ec4 100644 --- a/chatbot_arena.py +++ b/chatbot_arena.py @@ -516,6 +516,28 @@ async def call(unify_obj, model, contain, message): st.session_state["chat_history1"] = [] st.session_state["chat_history2"] = [] + with st.sidebar: + st.button("Save leaderboards", key="save") + if st.session_state.save: + + helpers.database.save_offline() + try: + helpers.database.save_online() + except Exception as e: + st.write("Could not upload the results.") + st.write(e) + st.session_state.leaderboard[["Wins ⭐", "Losses ❌"]] = ( + st.session_state.leaderboard[["Wins ⭐", "Losses ❌"]].where( + st.session_state.leaderboard[["Wins ⭐", "Losses ❌"]] == 0, 0 + ) + ) + + st.session_state.detailed_leaderboards["scores"] = ( + st.session_state.detailed_leaderboards["scores"].where( + st.session_state.detailed_leaderboards["scores"] == 0, 0 + ) + ) + if __name__ == "__main__": asyncio.run(main()) diff --git a/helpers.py b/helpers.py index 84878a9..498be66 100644 --- a/helpers.py +++ b/helpers.py @@ -107,7 +107,7 @@ def get_offline(update: bool = False) -> None: if not update: st.session_state.leaderboard = pd.DataFrame(json_data) - st.session_state.detailed_leaderboard = st.session_state.offline_detailed + st.session_state.detailed_leaderboards = st.session_state.offline_detailed st.session_state.models = st.session_state.offline_models st.session_state.leaderboard[["Wins ⭐", "Losses ❌"]] = ( @@ -176,7 +176,7 @@ def get_online(update: bool = False): if not update: st.session_state.leaderboard = gsheets_leaderboard - st.session_state.detailed_leaderboard = {"scores": gsheets_detail} + st.session_state.detailed_leaderboards = {"scores": gsheets_detail} st.session_state.models = gsheets_models["Models"] st.session_state.leaderboard[["Wins ⭐", "Losses ❌"]] = ( diff --git a/pages/1_leaderboards.py b/pages/1_leaderboards.py index f42ae39..bdd2972 100644 --- a/pages/1_leaderboards.py +++ b/pages/1_leaderboards.py @@ -30,7 +30,7 @@ sorted_counts.sort_values(by=["Wins ⭐", "Losses ❌"], inplace=True) sorted_counts.index = range(sorted_counts.shape[0]) - detail_leaderboards = st.session_state.detailed_leaderboard["scores"].add( + detail_leaderboards = st.session_state.detailed_leaderboards["scores"].add( st.session_state.offline_detailed["scores"], fill_value=0 ) @@ -39,7 +39,7 @@ if source == "online": helpers.database.get_online(True) - detail_leaderboards = st.session_state.detailed_leaderboard["scores"].add( + detail_leaderboards = st.session_state.detailed_leaderboards["scores"].add( st.session_state.online_detailed["scores"], fill_value=0 ) @@ -74,7 +74,7 @@ ["Compare", "Model Name", "Wins ⭐", "Losses ❌"] ] -detail_leaderboards = st.session_state.detailed_leaderboard +detail_leaderboards = st.session_state.detailed_leaderboards model_selection = list(detail_leaderboards["scores"].keys())[1:] if st.session_state.enable_detail: @@ -156,8 +156,8 @@ ) ) - st.session_state.detailed_leaderboard["scores"] = ( - st.session_state.detailed_leaderboard["scores"].where( - st.session_state.detailed_leaderboard["scores"] == 0, 0 + st.session_state.detailed_leaderboards["scores"] = ( + st.session_state.detailed_leaderboards["scores"].where( + st.session_state.detailed_leaderboards["scores"] == 0, 0 ) ) From a341c1a22def2bf6b21a8b3effca4ebce8f26e97 Mon Sep 17 00:00:00 2001 From: Kacper-W-Kozdon Date: Fri, 10 May 2024 18:40:41 +0200 Subject: [PATCH 21/69] names --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 6b305a9..6d354a3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ .env code/ __pycache__/ +.mypy_cache/ +.pytest_cache/ .streamlit -.streamlit/secrets.toml From 676405283b11b5951d737c9df0af1fba7fcb419c Mon Sep 17 00:00:00 2001 From: Kacper-W-Kozdon Date: Fri, 10 May 2024 18:42:26 +0200 Subject: [PATCH 22/69] names --- helpers.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/helpers.py b/helpers.py index 498be66..90d489b 100644 --- a/helpers.py +++ b/helpers.py @@ -116,9 +116,9 @@ def get_offline(update: bool = False) -> None: ) ) - st.session_state.detailed_leaderboard["scores"] = ( - st.session_state.detailed_leaderboard["scores"].where( - st.session_state.detailed_leaderboard["scores"] == 0, 0 + st.session_state.detailed_leaderboards["scores"] = ( + st.session_state.detailed_leaderboards["scores"].where( + st.session_state.detailed_leaderboards["scores"] == 0, 0 ) ) @@ -187,14 +187,14 @@ def get_online(update: bool = False): st.session_state.leaderboard = st.session_state.leaderboard.convert_dtypes() - st.session_state.detailed_leaderboard["scores"] = ( - st.session_state.detailed_leaderboard["scores"].where( + st.session_state.detailed_leaderboards["scores"] = ( + st.session_state.detailed_leaderboards["scores"].where( gsheets_detail == 0, 0 ) ) - st.session_state.detailed_leaderboard["scores"] = ( - st.session_state.detailed_leaderboard["scores"].convert_dtypes() + st.session_state.detailed_leaderboards["scores"] = ( + st.session_state.detailed_leaderboards["scores"].convert_dtypes() ) @staticmethod @@ -230,7 +230,7 @@ def save_offline(): st.session_state.offline_detailed["scores"] ) - models = st.session_state.models + models = pd.DataFrame(st.session_state.models) detail_leaderboards = st.session_state.detailed_leaderboards["scores"] @@ -269,7 +269,7 @@ def save_online(): st.session_state.online_detailed["scores"] ) - models = st.session_state.models + models = pd.DataFrame(st.session_state.models) with st.echo(): # Create GSheets connection From 809c429c999f8e037608938569e0cd58b7b3cdd4 Mon Sep 17 00:00:00 2001 From: Kacper-W-Kozdon Date: Sat, 11 May 2024 14:10:00 +0200 Subject: [PATCH 23/69] Added light/dark mode switch --- chatbot_arena.py | 4 +++ helpers.py | 55 +++++++++++++++++++++++++++++++++++++++++ pages/1_leaderboards.py | 6 ++++- pages/2_import_model.py | 26 +++++++++---------- 4 files changed, 75 insertions(+), 16 deletions(-) diff --git a/chatbot_arena.py b/chatbot_arena.py index d087ec4..0ae17b5 100644 --- a/chatbot_arena.py +++ b/chatbot_arena.py @@ -260,6 +260,10 @@ async def main() -> None: source = "offline" helpers.init_session(source) + _, theme_col = st.column([7, 1]) + with theme_col: + helpers.change_theme_button() + all_models = list(st.session_state.models) json_data = st.session_state.leaderboard diff --git a/helpers.py b/helpers.py index 90d489b..944e9de 100644 --- a/helpers.py +++ b/helpers.py @@ -374,6 +374,30 @@ def init_session(mode: str = "keys") -> None: if "enable_detail" not in st.session_state.keys(): st.session_state.enable_detail = False + if ( + "themes" not in st.session_state + ): # Source of the themes solution: https://discuss.streamlit.io/t/changing-the-streamlit-theme-with-a-toggle-button-solution/56842/2 + st.session_state.themes = { + "current_theme": "light", + "refreshed": True, + "light": { + "theme.base": "dark", + "theme.backgroundColor": "black", + "theme.primaryColor": "#c98bdb", + "theme.secondaryBackgroundColor": "#5591f5", + "theme.textColor": "white", + "button_face": "🌜", + }, + "dark": { + "theme.base": "light", + "theme.backgroundColor": "white", + "theme.primaryColor": "#5591f5", + "theme.secondaryBackgroundColor": "#82E1D7", + "theme.textColor": "#0a1464", + "button_face": "🌞", + }, + } + if mode == "offline": database.get_offline(st.session_state.new_source is True) # Load JSON data from file @@ -395,3 +419,34 @@ def init_session(mode: str = "keys") -> None: json_data = st.session_state.leaderboard data = {model: 0 for model in json_data.index} st.session_state["vote_counts"] = json_data + + +def ChangeTheme(): + previous_theme = st.session_state.themes["current_theme"] + tdict = ( + st.session_state.themes["light"] + if st.session_state.themes["current_theme"] == "light" + else st.session_state.themes["dark"] + ) + for vkey, vval in tdict.items(): + if vkey.startswith("theme"): + st._config.set_option(vkey, vval) + + st.session_state.themes["refreshed"] = False + if previous_theme == "dark": + st.session_state.themes["current_theme"] = "light" + elif previous_theme == "light": + st.session_state.themes["current_theme"] = "dark" + + +def change_theme_button(): + btn_face = ( + st.session_state.themes["light"]["button_face"] + if st.session_state.themes["current_theme"] == "light" + else st.session_state.themes["dark"]["button_face"] + ) + st.button(btn_face, on_click=ChangeTheme) + + if st.session_state.themes["refreshed"] is False: + st.session_state.themes["refreshed"] = True + st.rerun() diff --git a/pages/1_leaderboards.py b/pages/1_leaderboards.py index bdd2972..c36c241 100644 --- a/pages/1_leaderboards.py +++ b/pages/1_leaderboards.py @@ -10,6 +10,10 @@ source = "online" if st.session_state.source is True else "offline" +_, theme_col = st.column([7, 1]) +with theme_col: + helpers.change_theme_button() + # Add custom CSS for the buttons st.markdown( """ @@ -141,7 +145,7 @@ helpers.database.get_offline(True) st.session_state.new_source = False with st.sidebar: - st.button("Save leaderboards", key="save") + st.button("Save leaderboards", key="save2") if st.session_state.save: helpers.database.save_offline() diff --git a/pages/2_import_model.py b/pages/2_import_model.py index 1187f50..c679dbc 100644 --- a/pages/2_import_model.py +++ b/pages/2_import_model.py @@ -1,13 +1,5 @@ import streamlit as st -from unify import AsyncUnify -from unify import Unify -import os -from unify.exceptions import UnifyError -import asyncio -import pandas as pd -import json -import requests -import random +import helpers st.set_page_config( page_title="Import Model", @@ -15,8 +7,12 @@ layout="wide", ) +_, theme_col = st.column([7, 1]) +with theme_col: + helpers.change_theme_button() + code_input = st.session_state.code_input -api1 = f''' +api1 = f""" # if you like the {st.session_state.model1} model, then you can add this to your code: import os from unify import Unify @@ -26,8 +22,8 @@ endpoint="{st.session_state['model1']}" ) response = unify.generate(user_prompt="{code_input}") - ''' -api2 = f''' + """ +api2 = f""" # if you like the {st.session_state.model2} model, then you can add this to your code: import os from unify import Unify @@ -37,6 +33,6 @@ endpoint="{st.session_state['model2']}" ) response = unify.generate(user_prompt="{code_input}") - ''' -st.code(api1, language='python') -st.code(api2, language='python') + """ +st.code(api1, language="python") +st.code(api2, language="python") From f31b4dfa6227e8351c8c32bfc861cf25f8514aa9 Mon Sep 17 00:00:00 2001 From: Kacper-W-Kozdon Date: Sat, 11 May 2024 14:11:46 +0200 Subject: [PATCH 24/69] Added light/dark mode switch --- chatbot_arena.py | 2 +- pages/1_leaderboards.py | 2 +- pages/2_import_model.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/chatbot_arena.py b/chatbot_arena.py index 0ae17b5..8f240e3 100644 --- a/chatbot_arena.py +++ b/chatbot_arena.py @@ -260,7 +260,7 @@ async def main() -> None: source = "offline" helpers.init_session(source) - _, theme_col = st.column([7, 1]) + _, theme_col = st.columns([7, 1]) with theme_col: helpers.change_theme_button() diff --git a/pages/1_leaderboards.py b/pages/1_leaderboards.py index c36c241..65e1766 100644 --- a/pages/1_leaderboards.py +++ b/pages/1_leaderboards.py @@ -10,7 +10,7 @@ source = "online" if st.session_state.source is True else "offline" -_, theme_col = st.column([7, 1]) +_, theme_col = st.columns([7, 1]) with theme_col: helpers.change_theme_button() diff --git a/pages/2_import_model.py b/pages/2_import_model.py index c679dbc..6d1ca9b 100644 --- a/pages/2_import_model.py +++ b/pages/2_import_model.py @@ -7,7 +7,7 @@ layout="wide", ) -_, theme_col = st.column([7, 1]) +_, theme_col = st.columns([7, 1]) with theme_col: helpers.change_theme_button() From e27d1ab250b04e30f8c9e1cf95644ed214164835 Mon Sep 17 00:00:00 2001 From: Kacper-W-Kozdon Date: Sat, 11 May 2024 14:13:57 +0200 Subject: [PATCH 25/69] working light/dark switch --- helpers.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/helpers.py b/helpers.py index 944e9de..ffbfbb7 100644 --- a/helpers.py +++ b/helpers.py @@ -382,18 +382,18 @@ def init_session(mode: str = "keys") -> None: "refreshed": True, "light": { "theme.base": "dark", - "theme.backgroundColor": "black", - "theme.primaryColor": "#c98bdb", - "theme.secondaryBackgroundColor": "#5591f5", - "theme.textColor": "white", + # "theme.backgroundColor": "black", + # "theme.primaryColor": "#c98bdb", + # "theme.secondaryBackgroundColor": "#5591f5", + # "theme.textColor": "white", "button_face": "🌜", }, "dark": { "theme.base": "light", - "theme.backgroundColor": "white", - "theme.primaryColor": "#5591f5", - "theme.secondaryBackgroundColor": "#82E1D7", - "theme.textColor": "#0a1464", + # "theme.backgroundColor": "white", + # "theme.primaryColor": "#5591f5", + # "theme.secondaryBackgroundColor": "#82E1D7", + # "theme.textColor": "#0a1464", "button_face": "🌞", }, } From 607cd93202a224476956d56856dd1506efd44adb Mon Sep 17 00:00:00 2001 From: Kacper-W-Kozdon Date: Sat, 11 May 2024 14:14:29 +0200 Subject: [PATCH 26/69] working light/dark switch --- pages/1_leaderboards.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pages/1_leaderboards.py b/pages/1_leaderboards.py index 65e1766..8e5bd66 100644 --- a/pages/1_leaderboards.py +++ b/pages/1_leaderboards.py @@ -146,7 +146,7 @@ st.session_state.new_source = False with st.sidebar: st.button("Save leaderboards", key="save2") - if st.session_state.save: + if st.session_state.save2: helpers.database.save_offline() try: From c3dd56be693098ba4d797eb87c5c908c929268c0 Mon Sep 17 00:00:00 2001 From: Kacper-W-Kozdon Date: Sat, 11 May 2024 14:59:19 +0200 Subject: [PATCH 27/69] Added extra voting options --- chatbot_arena.py | 122 ++++--------------- helpers.py | 256 ++++++++++++++++++++++++++++++++++++++-- pages/1_leaderboards.py | 23 +--- pages/2_import_model.py | 2 +- 4 files changed, 274 insertions(+), 129 deletions(-) diff --git a/chatbot_arena.py b/chatbot_arena.py index 8f240e3..497c9a6 100644 --- a/chatbot_arena.py +++ b/chatbot_arena.py @@ -193,32 +193,6 @@ def input_api_key(api_key: str = " ") -> None: st.sidebar.write(f"{r['error']}") -def print_history(contain: st.container) -> None: - """Print the chat history in a streamlit split container. - - Parameters - ---------- - contain - streamlit container to print the chat history into. - - Returns - ------- - None - """ - - cont1, cont2 = contain - for i in st.session_state["chat_history1"]: - if i["role"] == "user": - cont1.write("🧑‍💻" + " " + i["content"]) - else: - cont1.write(i["content"]) - for i in st.session_state["chat_history2"]: - if i["role"] == "user": - cont2.write("🧑‍💻" + " " + i["content"]) - else: - cont2.write(i["content"]) - - def call_model(Endpoint: str) -> AsyncUnify: """Prepare the Unify model to which the prompts will be sent. @@ -262,7 +236,7 @@ async def main() -> None: _, theme_col = st.columns([7, 1]) with theme_col: - helpers.change_theme_button() + helpers.Buttons.change_theme_button() all_models = list(st.session_state.models) json_data = st.session_state.leaderboard @@ -347,7 +321,7 @@ async def main() -> None: ) message1 = st.session_state["chat_history1"] message2 = st.session_state["chat_history2"] - print_history(contain=(cont1, cont2)) + helpers.print_history(contain=(cont1, cont2)) u1 = None u2 = None try: @@ -451,69 +425,44 @@ async def call(unify_obj, model, contain, message): call(u2, model="model2", contain=cont2, message=message2), ) - c1, c2 = st.columns(2) + c1, c2, c3, c4 = st.columns([3, 1, 3, 1]) # Display the vote buttons vote_disabled = True if st.session_state.winner_selected in [None, True] else False with c1: left_button_clicked = st.button( - "👍 Vote First Model", + "👍 Vote 1st Model", disabled=vote_disabled, on_click=lambda: setattr(st.session_state, "winner_selected", True), ) if left_button_clicked: - st.balloons() - # Increase the vote count for the selected model by 1 when the button is clicked - model1 = st.session_state["model1"].split("@")[0] - model2 = st.session_state["model2"].split("@")[0] - - st.session_state["vote_counts"].at[model1, "Wins ⭐"] += 1 - st.session_state["vote_counts"].at[ - st.session_state["model2"].split("@")[0], "Losses ❌" - ] += 1 - if ( - model1 not in st.session_state.detailed_leaderboards["scores"].keys() - or model1 not in st.session_state.detailed_leaderboards["scores"].keys() - ): - st.session_state.detailed_leaderboards["scores"].at[model1, model2] = 0 - st.session_state.detailed_leaderboards["scores"].at[model1, model2] += 1 + helpers.Buttons.left_button_clicked(cont1, cont2) - print_history(contain=(cont1, cont2)) - try: - st.session_state.code_input = st.session_state["chat_history1"][-2][ - "content" - ] - except IndexError: - st.session_state.code_input = " " with c2: + tie_button_clicked = st.button( + "👔 Vote Tie (1:1)", + disabled=vote_disabled, + on_click=lambda: setattr(st.session_state, "winner_selected", True), + ) + if tie_button_clicked: + helpers.Buttons.tie_button(cont1, cont2) + + with c3: + no_win_button_clicked = st.button( + "❌ No Winners (0:0)", + disabled=vote_disabled, + on_click=lambda: setattr(st.session_state, "winner_selected", True), + ) + if no_win_button_clicked: + helpers.Buttons.no_win_button(cont1, cont2) + + with c4: right_button_clicked = st.button( - "👍 Vote Second Model", + "👍 Vote 2nd Model", disabled=vote_disabled, on_click=lambda: setattr(st.session_state, "winner_selected", True), ) if right_button_clicked: - st.balloons() - # Increase the vote count for the selected model by 1 when the button is clicked - model1 = st.session_state["model1"].split("@")[0] - model2 = st.session_state["model2"].split("@")[0] - - st.session_state["vote_counts"].at[model2, "Wins ⭐"] += 1 - st.session_state["vote_counts"].at[ - st.session_state["model1"].split("@")[0], "Losses ❌" - ] += 1 - if ( - model2 not in st.session_state.detailed_leaderboards["scores"].keys() - or model1 not in st.session_state.detailed_leaderboards["scores"].keys() - ): - st.session_state.detailed_leaderboards["scores"].at[model2, model1] = 0 - st.session_state.detailed_leaderboards["scores"].at[model2, model1] += 1 - - print_history(contain=(cont1, cont2)) - try: - st.session_state.code_input = st.session_state["chat_history2"][-2][ - "content" - ] - except IndexError: - st.session_state.code_input = " " + helpers.Buttons.right_button_clicked(cont1, cont2) # Add custom CSS for the buttons history_button_clicked = st.button("Clear Histroy") if history_button_clicked: @@ -521,26 +470,7 @@ async def call(unify_obj, model, contain, message): st.session_state["chat_history2"] = [] with st.sidebar: - st.button("Save leaderboards", key="save") - if st.session_state.save: - - helpers.database.save_offline() - try: - helpers.database.save_online() - except Exception as e: - st.write("Could not upload the results.") - st.write(e) - st.session_state.leaderboard[["Wins ⭐", "Losses ❌"]] = ( - st.session_state.leaderboard[["Wins ⭐", "Losses ❌"]].where( - st.session_state.leaderboard[["Wins ⭐", "Losses ❌"]] == 0, 0 - ) - ) - - st.session_state.detailed_leaderboards["scores"] = ( - st.session_state.detailed_leaderboards["scores"].where( - st.session_state.detailed_leaderboards["scores"] == 0, 0 - ) - ) + helpers.Buttons.save_button() if __name__ == "__main__": diff --git a/helpers.py b/helpers.py index ffbfbb7..d1dc34b 100644 --- a/helpers.py +++ b/helpers.py @@ -128,11 +128,12 @@ def get_online(update: bool = False): corresponding session states. Parameters + ---------- update if True, the session states with the new votes will not get reset, only the full leaderboards will be refreshed. Used to ensure that the online database will get correctly updated. Default: False - ---------- + None Returns @@ -439,14 +440,247 @@ def ChangeTheme(): st.session_state.themes["current_theme"] = "dark" -def change_theme_button(): - btn_face = ( - st.session_state.themes["light"]["button_face"] - if st.session_state.themes["current_theme"] == "light" - else st.session_state.themes["dark"]["button_face"] - ) - st.button(btn_face, on_click=ChangeTheme) +class Buttons: + def change_theme_button() -> None: + """The button to switch between the light and dark modes. + + Parameters + ---------- + None + + Returns + ------- + None + """ + btn_face = ( + st.session_state.themes["light"]["button_face"] + if st.session_state.themes["current_theme"] == "light" + else st.session_state.themes["dark"]["button_face"] + ) + st.button(btn_face, on_click=ChangeTheme) + + if st.session_state.themes["refreshed"] is False: + st.session_state.themes["refreshed"] = True + st.rerun() + + def left_button_clicked( + cont1: st.container = None, cont2: st.container = None + ) -> None: + """The button to select the model on the left side as the winning + model. + + Parameters + ---------- + cont1 + The chat window containing the chat history with the left-side model. + cont2 + The chat window containing the chat history with the right-side model. + + Returns + ------- + None + """ + assert cont1 is not None + assert cont2 is not None + st.balloons() + # Increase the vote count for the selected model by 1 when the button is clicked + model1 = st.session_state["model1"].split("@")[0] + model2 = st.session_state["model2"].split("@")[0] + + st.session_state["vote_counts"].at[model1, "Wins ⭐"] += 1 + st.session_state["vote_counts"].at[ + st.session_state["model2"].split("@")[0], "Losses ❌" + ] += 1 + if ( + model1 not in st.session_state.detailed_leaderboards["scores"].keys() + or model1 not in st.session_state.detailed_leaderboards["scores"].keys() + ): + st.session_state.detailed_leaderboards["scores"].at[model1, model2] = 0 + st.session_state.detailed_leaderboards["scores"].at[model1, model2] += 1 + + print_history(contain=(cont1, cont2)) + try: + st.session_state.code_input = st.session_state["chat_history1"][-2][ + "content" + ] + except IndexError: + st.session_state.code_input = " " + + def right_button_clicked( + cont1: st.container = None, cont2: st.container = None + ) -> None: + """The button to select the model on the right side as the winning + model. + + Parameters + ---------- + cont1 + The chat window containing the chat history with the left-side model. + cont2 + The chat window containing the chat history with the right-side model. + + Returns + ------- + None + """ + assert cont1 is not None + assert cont2 is not None + st.balloons() + # Increase the vote count for the selected model by 1 when the button is clicked + model1 = st.session_state["model1"].split("@")[0] + model2 = st.session_state["model2"].split("@")[0] + + st.session_state["vote_counts"].at[model2, "Wins ⭐"] += 1 + st.session_state["vote_counts"].at[ + st.session_state["model1"].split("@")[0], "Losses ❌" + ] += 1 + if ( + model2 not in st.session_state.detailed_leaderboards["scores"].keys() + or model1 not in st.session_state.detailed_leaderboards["scores"].keys() + ): + st.session_state.detailed_leaderboards["scores"].at[model2, model1] = 0 + st.session_state.detailed_leaderboards["scores"].at[model2, model1] += 1 + + print_history(contain=(cont1, cont2)) + try: + st.session_state.code_input = st.session_state["chat_history2"][-2][ + "content" + ] + except IndexError: + st.session_state.code_input = " " + + def tie_button(cont1: st.container = None, cont2: st.container = None) -> None: + """The button to declare a tie. + + Parameters + ---------- + cont1 + The chat window containing the chat history with the left-side model. + cont2 + The chat window containing the chat history with the right-side model. + + Returns + ------- + None + """ + assert cont1 is not None + assert cont2 is not None + st.balloons() + # Increase the vote count for the selected model by 1 when the button is clicked + model1 = st.session_state["model1"].split("@")[0] + model2 = st.session_state["model2"].split("@")[0] + + st.session_state["vote_counts"].at[model2, "Wins ⭐"] += 1 + st.session_state["vote_counts"].at[model1, "Wins ⭐"] += 1 + if ( + model2 not in st.session_state.detailed_leaderboards["scores"].keys() + or model1 not in st.session_state.detailed_leaderboards["scores"].keys() + ): + st.session_state.detailed_leaderboards["scores"].at[model2, model1] = 0 + st.session_state.detailed_leaderboards["scores"].at[model1, model2] = 0 + st.session_state.detailed_leaderboards["scores"].at[model2, model1] += 1 + st.session_state.detailed_leaderboards["scores"].at[model1, model2] += 1 + + print_history(contain=(cont1, cont2)) + try: + st.session_state.code_input = st.session_state["chat_history2"][-2][ + "content" + ] + except IndexError: + st.session_state.code_input = " " + + def no_win_button(cont1: st.container = None, cont2: st.container = None) -> None: + """The button to declare that both models provided bad answers. + + Parameters + ---------- + cont1 + The chat window containing the chat history with the left-side model. + cont2 + The chat window containing the chat history with the right-side model. + + Returns + ------- + None + """ + assert cont1 is not None + assert cont2 is not None + st.balloons() + # Increase the vote count for the selected model by 1 when the button is clicked + model1 = st.session_state["model1"].split("@")[0] + model2 = st.session_state["model2"].split("@")[0] + + st.session_state["vote_counts"].at[model2, "Losses ❌"] += 1 + st.session_state["vote_counts"].at[model1, "Losses ❌"] += 1 + if ( + model2 not in st.session_state.detailed_leaderboards["scores"].keys() + or model1 not in st.session_state.detailed_leaderboards["scores"].keys() + ): + st.session_state.detailed_leaderboards["scores"].at[model2, model1] = 0 + st.session_state.detailed_leaderboards["scores"].at[model1, model2] = 0 + + print_history(contain=(cont1, cont2)) + try: + st.session_state.code_input = st.session_state["chat_history2"][-2][ + "content" + ] + except IndexError: + st.session_state.code_input = " " + + def save_button() -> None: + """Update the leaderboards with the results from the session. + + Parameters + ---------- + None + + Returns + ------- + None + """ + save = st.button("Save leaderboards") + if save: + + database.save_offline() + try: + database.save_online() + except Exception as e: + st.write("Could not upload the results.") + st.write(e) + st.session_state.leaderboard[["Wins ⭐", "Losses ❌"]] = ( + st.session_state.leaderboard[["Wins ⭐", "Losses ❌"]].where( + st.session_state.leaderboard[["Wins ⭐", "Losses ❌"]] == 0, 0 + ) + ) + + st.session_state.detailed_leaderboards["scores"] = ( + st.session_state.detailed_leaderboards["scores"].where( + st.session_state.detailed_leaderboards["scores"] == 0, 0 + ) + ) + + +def print_history(contain: tuple[st.container]) -> None: + """Print the chat history in a streamlit split container. + + Parameters + ---------- + contain + streamlit container to print the chat history into. + + Returns + ------- + None + """ - if st.session_state.themes["refreshed"] is False: - st.session_state.themes["refreshed"] = True - st.rerun() + cont1, cont2 = contain + for i in st.session_state["chat_history1"]: + if i["role"] == "user": + cont1.write("🧑‍💻" + " " + i["content"]) + else: + cont1.write(i["content"]) + for i in st.session_state["chat_history2"]: + if i["role"] == "user": + cont2.write("🧑‍💻" + " " + i["content"]) + else: + cont2.write(i["content"]) diff --git a/pages/1_leaderboards.py b/pages/1_leaderboards.py index 8e5bd66..4c0dca2 100644 --- a/pages/1_leaderboards.py +++ b/pages/1_leaderboards.py @@ -12,7 +12,7 @@ _, theme_col = st.columns([7, 1]) with theme_col: - helpers.change_theme_button() + helpers.Buttons.change_theme_button() # Add custom CSS for the buttons st.markdown( @@ -145,23 +145,4 @@ helpers.database.get_offline(True) st.session_state.new_source = False with st.sidebar: - st.button("Save leaderboards", key="save2") - if st.session_state.save2: - - helpers.database.save_offline() - try: - helpers.database.save_online() - except Exception as e: - st.write("Could not upload the results.") - st.write(e) - st.session_state.leaderboard[["Wins ⭐", "Losses ❌"]] = ( - st.session_state.leaderboard[["Wins ⭐", "Losses ❌"]].where( - st.session_state.leaderboard[["Wins ⭐", "Losses ❌"]] == 0, 0 - ) - ) - - st.session_state.detailed_leaderboards["scores"] = ( - st.session_state.detailed_leaderboards["scores"].where( - st.session_state.detailed_leaderboards["scores"] == 0, 0 - ) - ) + helpers.Buttons.save_button() diff --git a/pages/2_import_model.py b/pages/2_import_model.py index 6d1ca9b..8b9129d 100644 --- a/pages/2_import_model.py +++ b/pages/2_import_model.py @@ -9,7 +9,7 @@ _, theme_col = st.columns([7, 1]) with theme_col: - helpers.change_theme_button() + helpers.Buttons.change_theme_button() code_input = st.session_state.code_input api1 = f""" From aab291bc5f74171c97b4c3212cb0c19b1dd6b991 Mon Sep 17 00:00:00 2001 From: Kacper-W-Kozdon Date: Sat, 11 May 2024 15:52:42 +0200 Subject: [PATCH 28/69] Added hyperlink to google sheets --- pages/1_leaderboards.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pages/1_leaderboards.py b/pages/1_leaderboards.py index 4c0dca2..04b5b6f 100644 --- a/pages/1_leaderboards.py +++ b/pages/1_leaderboards.py @@ -130,7 +130,7 @@ unsafe_allow_html=True, ) enable_global = st.sidebar.checkbox( - "Enable global leaderboards", + "Enable [global leaderboards](https://docs.google.com/spreadsheets/d/10QrEik70RYY_LM8RW8GGq-vZWK2e1dka6agRGtKZPHU/edit?usp=sharing)", value=st.session_state.source, on_change=lambda: ( setattr(st.session_state, "new_source", True), From 63fbc8e9e13dccc4c7a7da2c3f4d2d9151ae184d Mon Sep 17 00:00:00 2001 From: Kacper-W-Kozdon Date: Sat, 11 May 2024 15:59:04 +0200 Subject: [PATCH 29/69] Minor save fix --- helpers.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/helpers.py b/helpers.py index d1dc34b..0f9a061 100644 --- a/helpers.py +++ b/helpers.py @@ -224,7 +224,9 @@ def save_offline(): ].add( st.session_state.offline_leaderboard[["Wins ⭐", "Losses ❌"]], fill_value=0 ) - sorted_counts_df = vote_counts_df[["Model Name", "Wins ⭐", "Losses ❌"]] + sorted_counts_df = vote_counts_df[["Wins ⭐", "Losses ❌"]] + sorted_counts_df["Model Name"] = sorted_counts_df.index + sorted_counts_df.sort_values(by=["Wins ⭐", "Losses ❌"], inplace=True) detail_leaderboards = st.session_state.detailed_leaderboards["scores"].add( @@ -263,7 +265,9 @@ def save_online(): vote_counts_df[["Wins ⭐", "Losses ❌"]] = vote_counts_df[ ["Wins ⭐", "Losses ❌"] ].add(st.session_state.online_leaderboard[["Wins ⭐", "Losses ❌"]], fill_value=0) - sorted_counts_df = vote_counts_df[["Model Name", "Wins ⭐", "Losses ❌"]] + sorted_counts_df = vote_counts_df[["Wins ⭐", "Losses ❌"]] + sorted_counts_df["Model Name"] = sorted_counts_df.index + sorted_counts_df.sort_values(by=["Wins ⭐", "Losses ❌"], inplace=True) detail_leaderboards = st.session_state.detailed_leaderboards["scores"].add( From 8567b429f977fe5f3a94377f2e90f85b8ddf4071 Mon Sep 17 00:00:00 2001 From: Kacper-W-Kozdon Date: Sat, 11 May 2024 16:43:52 +0200 Subject: [PATCH 30/69] added st.rerun() in model selection to increase responsiveness to changes --- chatbot_arena.py | 9 +++++++-- helpers.py | 1 + 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/chatbot_arena.py b/chatbot_arena.py index 497c9a6..750dded 100644 --- a/chatbot_arena.py +++ b/chatbot_arena.py @@ -53,6 +53,8 @@ def select_model(api_key: str = "", authenticated: bool = False) -> None: ), key="model1_selectbox", ) + st.session_state.index_model1 = all_models.index(st.session_state.model1_selectbox) + if st.session_state.model1_selectbox == "other": model1_other_disabled = False st.text_input( @@ -81,6 +83,8 @@ def select_model(api_key: str = "", authenticated: bool = False) -> None: ), key="model2_selectbox", ) + st.session_state.index_model2 = all_models.index(st.session_state.model2_selectbox) + if st.session_state.model2_selectbox == "other": model2_other_disabled = False st.text_input( @@ -107,8 +111,8 @@ def select_model(api_key: str = "", authenticated: bool = False) -> None: else st.session_state.model2_other ) - st.session_state.index_model1 = all_models.index(st.session_state.model1_selectbox) - st.session_state.index_model2 = all_models.index(st.session_state.model2_selectbox) + # st.session_state.index_model1 = all_models.index(st.session_state.model1_selectbox) + # st.session_state.index_model2 = all_models.index(st.session_state.model2_selectbox) if st.session_state.model1_selectbox == "other": st.session_state.value_model1_other = selected_model1 if st.session_state.model2_selectbox == "other": @@ -121,6 +125,7 @@ def select_model(api_key: str = "", authenticated: bool = False) -> None: st.session_state["model1"] = selected_models.pop(0) st.session_state["model2"] = selected_models.pop(0) st.session_state.new_models_selected = False + st.rerun() def history(model: str = "model1", output: str = "") -> None: diff --git a/helpers.py b/helpers.py index 0f9a061..7388adf 100644 --- a/helpers.py +++ b/helpers.py @@ -360,6 +360,7 @@ def init_session(mode: str = "keys") -> None: st.session_state.index_model1 = 0 if "index_model2" not in st.session_state.keys(): st.session_state.index_model2 = 0 + if "value_model1_other" not in st.session_state.keys(): st.session_state.value_model1_other = "" if "value_model2_other" not in st.session_state.keys(): From e24c652d4e53fc9472d4072afc923830412523a0 Mon Sep 17 00:00:00 2001 From: Kacper-W-Kozdon Date: Sat, 11 May 2024 16:50:23 +0200 Subject: [PATCH 31/69] Minor fixes in save --- helpers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/helpers.py b/helpers.py index 7388adf..27732de 100644 --- a/helpers.py +++ b/helpers.py @@ -274,7 +274,7 @@ def save_online(): st.session_state.online_detailed["scores"] ) - models = pd.DataFrame(st.session_state.models) + models = pd.DataFrame({"Models": st.session_state.models}) with st.echo(): # Create GSheets connection From 2752c7b0057b8228910f8bec3482ce4e2fb4a8df Mon Sep 17 00:00:00 2001 From: Kacper-W-Kozdon Date: Sat, 11 May 2024 16:55:34 +0200 Subject: [PATCH 32/69] Minor fixes in save --- helpers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/helpers.py b/helpers.py index 27732de..71b8ee9 100644 --- a/helpers.py +++ b/helpers.py @@ -233,7 +233,7 @@ def save_offline(): st.session_state.offline_detailed["scores"] ) - models = pd.DataFrame(st.session_state.models) + models = pd.DataFrame({"Models": st.session_state.models}) detail_leaderboards = st.session_state.detailed_leaderboards["scores"] From 882e3b0f9817e57e4093e418b5446ea59c477073 Mon Sep 17 00:00:00 2001 From: Kacper-W-Kozdon Date: Sat, 11 May 2024 17:05:16 +0200 Subject: [PATCH 33/69] Minor fixes in save --- chatbot_arena.py | 31 +++++++++++++++++++++++++------ helpers.py | 1 + 2 files changed, 26 insertions(+), 6 deletions(-) diff --git a/chatbot_arena.py b/chatbot_arena.py index 750dded..ca9ae0c 100644 --- a/chatbot_arena.py +++ b/chatbot_arena.py @@ -314,7 +314,10 @@ async def main() -> None: if prompt := st.chat_input( "Say something", disabled=False if st.session_state.api_key_provided is True else True, - on_submit=lambda: setattr(st.session_state, "winner_selected", False), + on_submit=lambda: ( + setattr(st.session_state, "winner_selected", False), + setattr(st.session_state, "prompt_provided", True), + ), ): st.session_state["chat_input"] = prompt st.session_state.code_input = prompt @@ -432,12 +435,19 @@ async def call(unify_obj, model, contain, message): c1, c2, c3, c4 = st.columns([3, 1, 3, 1]) # Display the vote buttons - vote_disabled = True if st.session_state.winner_selected in [None, True] else False + vote_disabled = ( + True + if all([st.session_state.winner_selected, st.session_state.prompt_provided]) + else False + ) with c1: left_button_clicked = st.button( "👍 Vote 1st Model", disabled=vote_disabled, - on_click=lambda: setattr(st.session_state, "winner_selected", True), + on_click=lambda: ( + setattr(st.session_state, "winner_selected", True), + setattr(st.session_state, "prompt_provided", False), + ), ) if left_button_clicked: helpers.Buttons.left_button_clicked(cont1, cont2) @@ -446,7 +456,10 @@ async def call(unify_obj, model, contain, message): tie_button_clicked = st.button( "👔 Vote Tie (1:1)", disabled=vote_disabled, - on_click=lambda: setattr(st.session_state, "winner_selected", True), + on_click=lambda: ( + setattr(st.session_state, "winner_selected", True), + setattr(st.session_state, "prompt_provided", False), + ), ) if tie_button_clicked: helpers.Buttons.tie_button(cont1, cont2) @@ -455,7 +468,10 @@ async def call(unify_obj, model, contain, message): no_win_button_clicked = st.button( "❌ No Winners (0:0)", disabled=vote_disabled, - on_click=lambda: setattr(st.session_state, "winner_selected", True), + on_click=lambda: ( + setattr(st.session_state, "winner_selected", True), + setattr(st.session_state, "prompt_provided", False), + ), ) if no_win_button_clicked: helpers.Buttons.no_win_button(cont1, cont2) @@ -464,7 +480,10 @@ async def call(unify_obj, model, contain, message): right_button_clicked = st.button( "👍 Vote 2nd Model", disabled=vote_disabled, - on_click=lambda: setattr(st.session_state, "winner_selected", True), + on_click=lambda: ( + setattr(st.session_state, "winner_selected", True), + setattr(st.session_state, "prompt_provided", False), + ), ) if right_button_clicked: helpers.Buttons.right_button_clicked(cont1, cont2) diff --git a/helpers.py b/helpers.py index 71b8ee9..240ab8e 100644 --- a/helpers.py +++ b/helpers.py @@ -336,6 +336,7 @@ def init_session(mode: str = "keys") -> None: "detailed_leaderboards", "detail", "new_source", + "prompt_provided", ] for key in keys: if key not in st.session_state.keys(): From 712e33c8f4e60835dce64bf81967db12aacd692d Mon Sep 17 00:00:00 2001 From: Kacper-W-Kozdon Date: Sat, 11 May 2024 17:07:51 +0200 Subject: [PATCH 34/69] Minor fixes in save --- chatbot_arena.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chatbot_arena.py b/chatbot_arena.py index ca9ae0c..6c25e40 100644 --- a/chatbot_arena.py +++ b/chatbot_arena.py @@ -437,7 +437,7 @@ async def call(unify_obj, model, contain, message): # Display the vote buttons vote_disabled = ( True - if all([st.session_state.winner_selected, st.session_state.prompt_provided]) + if all([st.session_state.winner_selected, not st.session_state.prompt_provided]) else False ) with c1: From 6b011ab076df868ede4dd53bfe735eb85edcf22b Mon Sep 17 00:00:00 2001 From: Kacper-W-Kozdon Date: Sat, 11 May 2024 17:39:53 +0200 Subject: [PATCH 35/69] FIXED ALL OF THE KNOWN SAVE ISSUES --- chatbot_arena.py | 5 ++++- helpers.py | 14 ++++++++++---- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/chatbot_arena.py b/chatbot_arena.py index 6c25e40..9ba8bf4 100644 --- a/chatbot_arena.py +++ b/chatbot_arena.py @@ -437,7 +437,10 @@ async def call(unify_obj, model, contain, message): # Display the vote buttons vote_disabled = ( True - if all([st.session_state.winner_selected, not st.session_state.prompt_provided]) + if all([ + st.session_state.winner_selected, + st.session_state.prompt_provided is not True, + ]) else False ) with c1: diff --git a/helpers.py b/helpers.py index 240ab8e..df76f0b 100644 --- a/helpers.py +++ b/helpers.py @@ -77,12 +77,12 @@ def get_offline(update: bool = False) -> None: detail_dataframe = pd.DataFrame( data={ winning_model: { - losing_model: 0 for losing_model in json_data.keys() + losing_model: 0 for losing_model in json_data["Model Name"] } - for winning_model in json_data.keys() + for winning_model in json_data["Model Name"] } ) - detail_dataframe.index = list(json_data.keys()) + detail_dataframe.index = list(json_data["Model Name"]) detail_dataframe.to_csv("detail_leaderboards.csv") if not os.path.exists("./detail_leaderboards.json"): @@ -236,8 +236,9 @@ def save_offline(): models = pd.DataFrame({"Models": st.session_state.models}) detail_leaderboards = st.session_state.detailed_leaderboards["scores"] + detail_leaderboards.index = detail_leaderboards.columns - detail_leaderboards.to_csv("detail_leaderboards.csv", index=False) + detail_leaderboards.to_csv("detail_leaderboards.csv", index=True) sorted_counts_df.to_csv("leaderboard.csv", index=False) models.to_csv("models.csv", index=False) @@ -273,6 +274,11 @@ def save_online(): detail_leaderboards = st.session_state.detailed_leaderboards["scores"].add( st.session_state.online_detailed["scores"] ) + detail_leaderboards.index = detail_leaderboards.columns + try: + detail_leaderboards.insert(0, "", detail_leaderboards.index) + except ValueError: + pass models = pd.DataFrame({"Models": st.session_state.models}) From 5123ed035ef77532769e998a24ad683ebd5fd38c Mon Sep 17 00:00:00 2001 From: Kacper-W-Kozdon Date: Sat, 11 May 2024 18:20:39 +0200 Subject: [PATCH 36/69] control flow changes in voting --- chatbot_arena.py | 49 ++++++++++++++++++++++-------------------------- helpers.py | 9 +++++++-- 2 files changed, 29 insertions(+), 29 deletions(-) diff --git a/chatbot_arena.py b/chatbot_arena.py index 9ba8bf4..91319e6 100644 --- a/chatbot_arena.py +++ b/chatbot_arena.py @@ -40,19 +40,21 @@ def select_model(api_key: str = "", authenticated: bool = False) -> None: model1_other_disabled = True model2_other_disabled = True - st.selectbox( + st.session_state.model1_selectbox = st.selectbox( "Select the first model's endpoint:", - all_models, + options=all_models + ["I'm feeling lucky"], disabled=disabled, index=st.session_state.index_model1, on_change=lambda: ( setattr(st.session_state, "chat_history1", []), setattr(st.session_state, "chat_history2", []), - setattr(st.session_state, "winner_selected", False), + setattr(st.session_state, "winner_selected", True), + setattr(st.session_state, "prompt_provided", True), setattr(st.session_state, "new_models_selected", True), ), - key="model1_selectbox", ) + if st.session_state.model1_selectbox == "I'm feeling lucky": + st.session_state.model1_selectbox = random.choice(all_models[:-1]) st.session_state.index_model1 = all_models.index(st.session_state.model1_selectbox) if st.session_state.model1_selectbox == "other": @@ -65,24 +67,27 @@ def select_model(api_key: str = "", authenticated: bool = False) -> None: on_change=lambda: ( setattr(st.session_state, "chat_history1", []), setattr(st.session_state, "chat_history2", []), - setattr(st.session_state, "winner_selected", False), + setattr(st.session_state, "winner_selected", True), + setattr(st.session_state, "prompt_provided", True), setattr(st.session_state, "new_models_selected", True), ), key="model1_other", ) - st.selectbox( + st.session_state.model2_selectbox = st.selectbox( "Select the second model's endpoint:", - all_models, + options=all_models + ["I'm feeling lucky"], disabled=disabled, index=st.session_state.index_model2, on_change=lambda: ( setattr(st.session_state, "chat_history1", []), setattr(st.session_state, "chat_history2", []), - setattr(st.session_state, "winner_selected", False), + setattr(st.session_state, "winner_selected", True), + setattr(st.session_state, "prompt_provided", True), setattr(st.session_state, "new_models_selected", True), ), - key="model2_selectbox", ) + if st.session_state.model2_selectbox == "I'm feeling lucky": + st.session_state.model2_selectbox = random.choice(all_models[:-1]) st.session_state.index_model2 = all_models.index(st.session_state.model2_selectbox) if st.session_state.model2_selectbox == "other": @@ -95,7 +100,8 @@ def select_model(api_key: str = "", authenticated: bool = False) -> None: on_change=lambda: ( setattr(st.session_state, "chat_history1", []), setattr(st.session_state, "chat_history2", []), - setattr(st.session_state, "winner_selected", False), + setattr(st.session_state, "winner_selected", True), + setattr(st.session_state, "prompt_provided", True), setattr(st.session_state, "new_models_selected", True), ), key="model2_other", @@ -435,11 +441,12 @@ async def call(unify_obj, model, contain, message): c1, c2, c3, c4 = st.columns([3, 1, 3, 1]) # Display the vote buttons + vote_disabled = ( True if all([ st.session_state.winner_selected, - st.session_state.prompt_provided is not True, + st.session_state.prompt_provided, ]) else False ) @@ -447,10 +454,7 @@ async def call(unify_obj, model, contain, message): left_button_clicked = st.button( "👍 Vote 1st Model", disabled=vote_disabled, - on_click=lambda: ( - setattr(st.session_state, "winner_selected", True), - setattr(st.session_state, "prompt_provided", False), - ), + on_click=lambda: (setattr(st.session_state, "winner_selected", True),), ) if left_button_clicked: helpers.Buttons.left_button_clicked(cont1, cont2) @@ -459,10 +463,7 @@ async def call(unify_obj, model, contain, message): tie_button_clicked = st.button( "👔 Vote Tie (1:1)", disabled=vote_disabled, - on_click=lambda: ( - setattr(st.session_state, "winner_selected", True), - setattr(st.session_state, "prompt_provided", False), - ), + on_click=lambda: (setattr(st.session_state, "winner_selected", True),), ) if tie_button_clicked: helpers.Buttons.tie_button(cont1, cont2) @@ -471,10 +472,7 @@ async def call(unify_obj, model, contain, message): no_win_button_clicked = st.button( "❌ No Winners (0:0)", disabled=vote_disabled, - on_click=lambda: ( - setattr(st.session_state, "winner_selected", True), - setattr(st.session_state, "prompt_provided", False), - ), + on_click=lambda: (setattr(st.session_state, "winner_selected", True),), ) if no_win_button_clicked: helpers.Buttons.no_win_button(cont1, cont2) @@ -483,10 +481,7 @@ async def call(unify_obj, model, contain, message): right_button_clicked = st.button( "👍 Vote 2nd Model", disabled=vote_disabled, - on_click=lambda: ( - setattr(st.session_state, "winner_selected", True), - setattr(st.session_state, "prompt_provided", False), - ), + on_click=lambda: (setattr(st.session_state, "winner_selected", True),), ) if right_button_clicked: helpers.Buttons.right_button_clicked(cont1, cont2) diff --git a/helpers.py b/helpers.py index df76f0b..d43cde7 100644 --- a/helpers.py +++ b/helpers.py @@ -330,7 +330,6 @@ def init_session(mode: str = "keys") -> None: if mode == "keys": keys = [ "chat_input", - "winner_selected", "api_key_provided", "vote1", "vote2", @@ -342,7 +341,6 @@ def init_session(mode: str = "keys") -> None: "detailed_leaderboards", "detail", "new_source", - "prompt_provided", ] for key in keys: if key not in st.session_state.keys(): @@ -356,10 +354,12 @@ def init_session(mode: str = "keys") -> None: if "model1_selectbox" not in st.session_state.keys(): st.session_state.placeholder_model1 = "other" + st.session_state.model1_selectbox = None if "model1_other" not in st.session_state.keys(): st.session_state.placeholder_model1_other = "model@provider" if "model2_selectbox" not in st.session_state.keys(): st.session_state.placeholder_model2 = "other" + st.session_state.model2_selectbox = None if "model2_other" not in st.session_state.keys(): st.session_state.placeholder_model2_other = "model@provider" @@ -387,6 +387,11 @@ def init_session(mode: str = "keys") -> None: if "enable_detail" not in st.session_state.keys(): st.session_state.enable_detail = False + if "winner_selected" not in st.session_state.keys(): + st.session_state.winner_selected = True + if "prompt_provided" not in st.session_state.keys(): + st.session_state.prompt_provided = True + if ( "themes" not in st.session_state ): # Source of the themes solution: https://discuss.streamlit.io/t/changing-the-streamlit-theme-with-a-toggle-button-solution/56842/2 From 15dc05904ae1e3908f66e2105998234e6b1c2ec0 Mon Sep 17 00:00:00 2001 From: Kacper-W-Kozdon Date: Sat, 11 May 2024 23:33:57 +0200 Subject: [PATCH 37/69] control flow changes in voting --- helpers.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/helpers.py b/helpers.py index d43cde7..77c49e0 100644 --- a/helpers.py +++ b/helpers.py @@ -458,6 +458,8 @@ def ChangeTheme(): class Buttons: + + @staticmethod def change_theme_button() -> None: """The button to switch between the light and dark modes. @@ -480,6 +482,7 @@ def change_theme_button() -> None: st.session_state.themes["refreshed"] = True st.rerun() + @staticmethod def left_button_clicked( cont1: st.container = None, cont2: st.container = None ) -> None: @@ -523,6 +526,7 @@ def left_button_clicked( except IndexError: st.session_state.code_input = " " + @staticmethod def right_button_clicked( cont1: st.container = None, cont2: st.container = None ) -> None: @@ -566,6 +570,7 @@ def right_button_clicked( except IndexError: st.session_state.code_input = " " + @staticmethod def tie_button(cont1: st.container = None, cont2: st.container = None) -> None: """The button to declare a tie. @@ -606,6 +611,7 @@ def tie_button(cont1: st.container = None, cont2: st.container = None) -> None: except IndexError: st.session_state.code_input = " " + @staticmethod def no_win_button(cont1: st.container = None, cont2: st.container = None) -> None: """The button to declare that both models provided bad answers. @@ -644,6 +650,7 @@ def no_win_button(cont1: st.container = None, cont2: st.container = None) -> Non except IndexError: st.session_state.code_input = " " + @staticmethod def save_button() -> None: """Update the leaderboards with the results from the session. From 6666bc85f6ef44be26f28467a8a3051a7e079ddd Mon Sep 17 00:00:00 2001 From: Kacper-W-Kozdon Date: Sun, 12 May 2024 00:26:32 +0200 Subject: [PATCH 38/69] Minor fixes in save offline --- helpers.py | 4 +--- pages/1_leaderboards.py | 1 + 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/helpers.py b/helpers.py index 77c49e0..d576e0d 100644 --- a/helpers.py +++ b/helpers.py @@ -232,12 +232,10 @@ def save_offline(): detail_leaderboards = st.session_state.detailed_leaderboards["scores"].add( st.session_state.offline_detailed["scores"] ) + detail_leaderboards.index = detail_leaderboards.columns models = pd.DataFrame({"Models": st.session_state.models}) - detail_leaderboards = st.session_state.detailed_leaderboards["scores"] - detail_leaderboards.index = detail_leaderboards.columns - detail_leaderboards.to_csv("detail_leaderboards.csv", index=True) sorted_counts_df.to_csv("leaderboard.csv", index=False) models.to_csv("models.csv", index=False) diff --git a/pages/1_leaderboards.py b/pages/1_leaderboards.py index 04b5b6f..454c859 100644 --- a/pages/1_leaderboards.py +++ b/pages/1_leaderboards.py @@ -144,5 +144,6 @@ if source == "offline": helpers.database.get_offline(True) st.session_state.new_source = False + st.rerun() with st.sidebar: helpers.Buttons.save_button() From 463f558e8b93dc6a3262dbfc7d14cb7bb278c519 Mon Sep 17 00:00:00 2001 From: Kacper-W-Kozdon Date: Sun, 12 May 2024 00:55:47 +0200 Subject: [PATCH 39/69] Minor fixes in save offline --- chatbot_arena.py | 4 ++-- helpers.py | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/chatbot_arena.py b/chatbot_arena.py index 91319e6..5a9db3b 100644 --- a/chatbot_arena.py +++ b/chatbot_arena.py @@ -283,7 +283,7 @@ async def main() -> None: col11, col21 = st.columns(2) # Display chat UI with col11: - if st.session_state.winner_selected is True: + if all([st.session_state.winner_selected, st.session_state.prompt_provided]): st.markdown( "Model 1: " + st.session_state["model1"] @@ -296,7 +296,7 @@ async def main() -> None: unsafe_allow_html=True, ) with col21: - if st.session_state.winner_selected is True: + if all([st.session_state.winner_selected, st.session_state.prompt_provided]): st.markdown( "Model 2: " + st.session_state["model2"] diff --git a/helpers.py b/helpers.py index d576e0d..e1afc8b 100644 --- a/helpers.py +++ b/helpers.py @@ -257,7 +257,6 @@ def save_online(): for key in keys: if key not in st.session_state.keys(): st.session_state[key] = None - database.get_online(True) vote_counts_df = pd.DataFrame(st.session_state.vote_counts) From 690087cdaa250ac0c81e3a3ca1cbba392c63d4bf Mon Sep 17 00:00:00 2001 From: Kacper-W-Kozdon Date: Sun, 12 May 2024 01:16:41 +0200 Subject: [PATCH 40/69] Minor fixes in save offline --- helpers.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/helpers.py b/helpers.py index e1afc8b..1fb351a 100644 --- a/helpers.py +++ b/helpers.py @@ -301,7 +301,7 @@ def save_online(): data=models, ) st.cache_data.clear() - st.experimental_rerun() + st.rerun() def init_session(mode: str = "keys") -> None: @@ -421,12 +421,12 @@ def init_session(mode: str = "keys") -> None: data = pd.read_csv( "leaderboard.csv" ) # This will raise an error if the file does not exist + st.session_state.leaderboards.set_index("Model Name", inplace=True) json_data = st.session_state.leaderboard st.session_state["vote_counts"] = pd.DataFrame( json_data, columns=["Model Name", "Wins ⭐", "Losses ❌"] ) - st.session_state["vote_counts"].set_index("Model Name", inplace=True) if mode == "online": database.get_online(st.session_state.new_source is True) From f7a1c7bba98142a21c4e962902c613d0a0de8157 Mon Sep 17 00:00:00 2001 From: Kacper-W-Kozdon Date: Sun, 12 May 2024 01:20:57 +0200 Subject: [PATCH 41/69] Minor fixes in save offline --- helpers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/helpers.py b/helpers.py index 1fb351a..6726c11 100644 --- a/helpers.py +++ b/helpers.py @@ -421,7 +421,7 @@ def init_session(mode: str = "keys") -> None: data = pd.read_csv( "leaderboard.csv" ) # This will raise an error if the file does not exist - st.session_state.leaderboards.set_index("Model Name", inplace=True) + st.session_state.leaderboard.set_index("Model Name", inplace=True) json_data = st.session_state.leaderboard st.session_state["vote_counts"] = pd.DataFrame( From 0cb7fcd0f162f784e07aa85b2598b4b2ffe88cbe Mon Sep 17 00:00:00 2001 From: Kacper-W-Kozdon Date: Sun, 12 May 2024 03:34:07 +0200 Subject: [PATCH 42/69] Minor fixes in save offline --- chatbot_arena.py | 9 ++++----- helpers.py | 49 ++++++++++++++++++++++++++++++++---------------- 2 files changed, 37 insertions(+), 21 deletions(-) diff --git a/chatbot_arena.py b/chatbot_arena.py index 5a9db3b..1ac8e01 100644 --- a/chatbot_arena.py +++ b/chatbot_arena.py @@ -49,7 +49,7 @@ def select_model(api_key: str = "", authenticated: bool = False) -> None: setattr(st.session_state, "chat_history1", []), setattr(st.session_state, "chat_history2", []), setattr(st.session_state, "winner_selected", True), - setattr(st.session_state, "prompt_provided", True), + setattr(st.session_state, "prompt_provided", False), setattr(st.session_state, "new_models_selected", True), ), ) @@ -68,7 +68,7 @@ def select_model(api_key: str = "", authenticated: bool = False) -> None: setattr(st.session_state, "chat_history1", []), setattr(st.session_state, "chat_history2", []), setattr(st.session_state, "winner_selected", True), - setattr(st.session_state, "prompt_provided", True), + setattr(st.session_state, "prompt_provided", False), setattr(st.session_state, "new_models_selected", True), ), key="model1_other", @@ -82,7 +82,7 @@ def select_model(api_key: str = "", authenticated: bool = False) -> None: setattr(st.session_state, "chat_history1", []), setattr(st.session_state, "chat_history2", []), setattr(st.session_state, "winner_selected", True), - setattr(st.session_state, "prompt_provided", True), + setattr(st.session_state, "prompt_provided", False), setattr(st.session_state, "new_models_selected", True), ), ) @@ -101,7 +101,7 @@ def select_model(api_key: str = "", authenticated: bool = False) -> None: setattr(st.session_state, "chat_history1", []), setattr(st.session_state, "chat_history2", []), setattr(st.session_state, "winner_selected", True), - setattr(st.session_state, "prompt_provided", True), + setattr(st.session_state, "prompt_provided", False), setattr(st.session_state, "new_models_selected", True), ), key="model2_other", @@ -446,7 +446,6 @@ async def call(unify_obj, model, contain, message): True if all([ st.session_state.winner_selected, - st.session_state.prompt_provided, ]) else False ) diff --git a/helpers.py b/helpers.py index 6726c11..e97ce5a 100644 --- a/helpers.py +++ b/helpers.py @@ -43,14 +43,14 @@ def get_offline(update: bool = False) -> None: models_df.to_csv("models.csv") all_models = tuple(data["models"]) - + model_names = list(set([model.split("@")[0] for model in all_models])) if not os.path.exists("./leaderboard.csv"): - leaderboard = pd.DataFrame( - ["other", 0, 0], - columns=["Model Name", "Wins ⭐", "Losses ❌"], - index=["other"], - ) - leaderboard.to_csv("leaderboard.csv") + leaderboard = pd.DataFrame({ + "Model Name": [model for model in model_names], + "Wins ⭐": [0 for model in model_names], + "Losses ❌": [0 for model in model_names], + }) + leaderboard.to_csv("leaderboard.csv", index=False) data = pd.read_csv( "leaderboard.csv" @@ -215,10 +215,8 @@ def save_offline(): for key in keys: if key not in st.session_state.keys(): st.session_state[key] = None - database.get_offline(True) - - vote_counts_df = pd.DataFrame(st.session_state.vote_counts) + vote_counts_df = st.session_state.vote_counts vote_counts_df[["Wins ⭐", "Losses ❌"]] = vote_counts_df[ ["Wins ⭐", "Losses ❌"] ].add( @@ -268,6 +266,12 @@ def save_online(): sorted_counts_df.sort_values(by=["Wins ⭐", "Losses ❌"], inplace=True) + st.session_state.detailed_leaderboards["scores"].index = ( + st.session_state.detailed_leaderboards["scores"].columns + ) + st.session_state.online_detailed["scores"].index = ( + st.session_state.online_detailed["scores"].columns + ) detail_leaderboards = st.session_state.detailed_leaderboards["scores"].add( st.session_state.online_detailed["scores"] ) @@ -338,6 +342,7 @@ def init_session(mode: str = "keys") -> None: "detailed_leaderboards", "detail", "new_source", + "saved", ] for key in keys: if key not in st.session_state.keys(): @@ -377,9 +382,9 @@ def init_session(mode: str = "keys") -> None: st.session_state.source = False if "vote_counts" not in st.session_state: - st.session_state["vote_counts"] = { - model: {"Wins ⭐": 0, "Losses ❌": 0} for model in ["others"] - } + st.session_state.vote_counts = pd.DataFrame( + {"Model Name": ["other"], "Wins ⭐": [0], "Losses ❌": [0]} + ) if "enable_detail" not in st.session_state.keys(): st.session_state.enable_detail = False @@ -387,7 +392,7 @@ def init_session(mode: str = "keys") -> None: if "winner_selected" not in st.session_state.keys(): st.session_state.winner_selected = True if "prompt_provided" not in st.session_state.keys(): - st.session_state.prompt_provided = True + st.session_state.prompt_provided = False if ( "themes" not in st.session_state @@ -659,15 +664,27 @@ def save_button() -> None: ------- None """ - save = st.button("Save leaderboards") - if save: + st.button( + "Save leaderboards", + on_click=lambda: setattr(st.session_state, "saved", True), + ) + st.write(st.session_state.vote_counts) + if st.session_state.saved: + st.session_state.saved = False database.save_offline() try: database.save_online() except Exception as e: st.write("Could not upload the results.") st.write(e) + + st.session_state.vote_counts[["Wins ⭐", "Losses ❌"]] = ( + st.session_state.vote_counts[["Wins ⭐", "Losses ❌"]].where( + st.session_state.vote_counts[["Wins ⭐", "Losses ❌"]] == 0, 0 + ) + ) + st.session_state.leaderboard[["Wins ⭐", "Losses ❌"]] = ( st.session_state.leaderboard[["Wins ⭐", "Losses ❌"]].where( st.session_state.leaderboard[["Wins ⭐", "Losses ❌"]] == 0, 0 From 1c15b51c829c8fe858fc02575ac3b1abe5e6922b Mon Sep 17 00:00:00 2001 From: Kacper-W-Kozdon Date: Sun, 12 May 2024 03:52:55 +0200 Subject: [PATCH 43/69] Minor fixes in save offline --- helpers.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/helpers.py b/helpers.py index e97ce5a..3b21983 100644 --- a/helpers.py +++ b/helpers.py @@ -426,7 +426,7 @@ def init_session(mode: str = "keys") -> None: data = pd.read_csv( "leaderboard.csv" ) # This will raise an error if the file does not exist - st.session_state.leaderboard.set_index("Model Name", inplace=True) + st.session_state.leaderboard.set_index("Model Name", inplace=True, drop=False) json_data = st.session_state.leaderboard st.session_state["vote_counts"] = pd.DataFrame( @@ -668,7 +668,7 @@ def save_button() -> None: "Save leaderboards", on_click=lambda: setattr(st.session_state, "saved", True), ) - st.write(st.session_state.vote_counts) + # st.write(st.session_state.vote_counts) if st.session_state.saved: st.session_state.saved = False From 272b017495b7270fed05e10eea98e60d7174bf61 Mon Sep 17 00:00:00 2001 From: Kacper-W-Kozdon Date: Sun, 12 May 2024 03:57:22 +0200 Subject: [PATCH 44/69] Minor fixes in save offline --- chatbot_arena.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/chatbot_arena.py b/chatbot_arena.py index 1ac8e01..27dc561 100644 --- a/chatbot_arena.py +++ b/chatbot_arena.py @@ -352,7 +352,7 @@ async def main() -> None: model1_to_add := st.session_state["model1"][ : st.session_state["model1"].find("@") ] - ) not in data.keys(): + ) not in data["Model Name"]: st.session_state["vote_counts"].at[f"{model1_to_add}", "Wins ⭐"] = 0 st.session_state["vote_counts"].at[f"{model1_to_add}", "Losses ❌"] = 0 st.session_state["vote_counts"].at[ @@ -384,7 +384,7 @@ async def main() -> None: model2_to_add := st.session_state["model2"][ : st.session_state["model2"].find("@") ] - ) not in data.keys(): + ) not in data["Model Name"]: st.session_state["vote_counts"].at[f"{model2_to_add}", "Wins ⭐"] = 0 st.session_state["vote_counts"].at[f"{model2_to_add}", "Losses ❌"] = 0 From ec156eb528b36ac6988a34e813ade99cfa576b62 Mon Sep 17 00:00:00 2001 From: Kacper-W-Kozdon Date: Sun, 12 May 2024 10:24:55 +0200 Subject: [PATCH 45/69] Minor fixes in save offline --- helpers.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/helpers.py b/helpers.py index 3b21983..a9692e0 100644 --- a/helpers.py +++ b/helpers.py @@ -385,6 +385,9 @@ def init_session(mode: str = "keys") -> None: st.session_state.vote_counts = pd.DataFrame( {"Model Name": ["other"], "Wins ⭐": [0], "Losses ❌": [0]} ) + st.session_state.vote_counts.set_index( + "Model Name", inplace=True, drop=False + ) if "enable_detail" not in st.session_state.keys(): st.session_state.enable_detail = False @@ -429,16 +432,16 @@ def init_session(mode: str = "keys") -> None: st.session_state.leaderboard.set_index("Model Name", inplace=True, drop=False) json_data = st.session_state.leaderboard - st.session_state["vote_counts"] = pd.DataFrame( - json_data, columns=["Model Name", "Wins ⭐", "Losses ❌"] - ) + # st.session_state["vote_counts"] = pd.DataFrame( + # json_data, columns=["Model Name", "Wins ⭐", "Losses ❌"] + # ) if mode == "online": database.get_online(st.session_state.new_source is True) all_models = list(st.session_state.models) json_data = st.session_state.leaderboard data = {model: 0 for model in json_data.index} - st.session_state["vote_counts"] = json_data + # st.session_state["vote_counts"] = json_data def ChangeTheme(): From 06b21db948b01060eeb7c0f755032ce84cd55933 Mon Sep 17 00:00:00 2001 From: Kacper-W-Kozdon Date: Sun, 12 May 2024 10:43:37 +0200 Subject: [PATCH 46/69] Minor fixes in save offline --- helpers.py | 1 + pages/1_leaderboards.py | 12 ++++++++++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/helpers.py b/helpers.py index a9692e0..c9fbf8d 100644 --- a/helpers.py +++ b/helpers.py @@ -224,6 +224,7 @@ def save_offline(): ) sorted_counts_df = vote_counts_df[["Wins ⭐", "Losses ❌"]] sorted_counts_df["Model Name"] = sorted_counts_df.index + sorted_counts_df = sorted_counts_df[["Model Name", "Wins ⭐", "Losses ❌"]] sorted_counts_df.sort_values(by=["Wins ⭐", "Losses ❌"], inplace=True) diff --git a/pages/1_leaderboards.py b/pages/1_leaderboards.py index 454c859..b941a2a 100644 --- a/pages/1_leaderboards.py +++ b/pages/1_leaderboards.py @@ -29,7 +29,11 @@ vote_counts_df["Model Name"] = vote_counts_df.index vote_counts_df[["Wins ⭐", "Losses ❌"]] = vote_counts_df[ ["Wins ⭐", "Losses ❌"] - ].add(st.session_state.offline_leaderboard[["Wins ⭐", "Losses ❌"]], fill_value=0) + ].add( + st.session_state.offline_leaderboard[["Wins ⭐", "Losses ❌"]], + fill_value=0, + axis="index", + ) sorted_counts = vote_counts_df[["Model Name", "Wins ⭐", "Losses ❌"]] sorted_counts.sort_values(by=["Wins ⭐", "Losses ❌"], inplace=True) sorted_counts.index = range(sorted_counts.shape[0]) @@ -53,7 +57,11 @@ vote_counts_df = pd.DataFrame(st.session_state.vote_counts) vote_counts_df[["Wins ⭐", "Losses ❌"]] = vote_counts_df[ ["Wins ⭐", "Losses ❌"] - ].add(st.session_state.online_leaderboard[["Wins ⭐", "Losses ❌"]], fill_value=0) + ].add( + st.session_state.online_leaderboard[["Wins ⭐", "Losses ❌"]], + fill_value=0, + axis="index", + ) vote_counts_df["Model Name"] = vote_counts_df.index sorted_counts = vote_counts_df[["Model Name", "Wins ⭐", "Losses ❌"]] sorted_counts.sort_values(by=["Wins ⭐", "Losses ❌"], inplace=True) From 920d6654365984a5318635284c55fd8da3ccc286 Mon Sep 17 00:00:00 2001 From: Kacper-W-Kozdon Date: Sun, 12 May 2024 10:51:34 +0200 Subject: [PATCH 47/69] Minor fixes in save offline --- pages/1_leaderboards.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/pages/1_leaderboards.py b/pages/1_leaderboards.py index b941a2a..0723b24 100644 --- a/pages/1_leaderboards.py +++ b/pages/1_leaderboards.py @@ -32,7 +32,6 @@ ].add( st.session_state.offline_leaderboard[["Wins ⭐", "Losses ❌"]], fill_value=0, - axis="index", ) sorted_counts = vote_counts_df[["Model Name", "Wins ⭐", "Losses ❌"]] sorted_counts.sort_values(by=["Wins ⭐", "Losses ❌"], inplace=True) @@ -60,7 +59,6 @@ ].add( st.session_state.online_leaderboard[["Wins ⭐", "Losses ❌"]], fill_value=0, - axis="index", ) vote_counts_df["Model Name"] = vote_counts_df.index sorted_counts = vote_counts_df[["Model Name", "Wins ⭐", "Losses ❌"]] From f7af268087f79e6cea62d6f4fcbcc8ca702d8bfa Mon Sep 17 00:00:00 2001 From: Kacper-W-Kozdon Date: Sun, 12 May 2024 11:09:40 +0200 Subject: [PATCH 48/69] Minor fixes in save offline --- helpers.py | 21 +++++++++++++-------- pages/1_leaderboards.py | 17 ++++++++--------- 2 files changed, 21 insertions(+), 17 deletions(-) diff --git a/helpers.py b/helpers.py index c9fbf8d..c6fcc76 100644 --- a/helpers.py +++ b/helpers.py @@ -103,6 +103,9 @@ def get_offline(update: bool = False) -> None: } st.session_state.offline_leaderboard = pd.DataFrame(json_data) + st.session_state.offline_leaderboard.set_index( + "Model Name", inplace=True, drop=False + ) st.session_state.offline_models = all_models if not update: @@ -175,6 +178,10 @@ def get_online(update: bool = False): st.session_state.online_detailed = {"scores": gsheets_detail.convert_dtypes()} st.session_state.online_models = gsheets_models["Models"] + st.session_state.online_leaderboard.set_index( + "Model Name", inplace=True, drop=False + ) + if not update: st.session_state.leaderboard = gsheets_leaderboard st.session_state.detailed_leaderboards = {"scores": gsheets_detail} @@ -217,12 +224,10 @@ def save_offline(): st.session_state[key] = None database.get_offline(True) vote_counts_df = st.session_state.vote_counts - vote_counts_df[["Wins ⭐", "Losses ❌"]] = vote_counts_df[ - ["Wins ⭐", "Losses ❌"] - ].add( + vote_counts_df_added = vote_counts_df[["Wins ⭐", "Losses ❌"]].add( st.session_state.offline_leaderboard[["Wins ⭐", "Losses ❌"]], fill_value=0 ) - sorted_counts_df = vote_counts_df[["Wins ⭐", "Losses ❌"]] + sorted_counts_df = vote_counts_df_added[["Wins ⭐", "Losses ❌"]] sorted_counts_df["Model Name"] = sorted_counts_df.index sorted_counts_df = sorted_counts_df[["Model Name", "Wins ⭐", "Losses ❌"]] @@ -259,10 +264,10 @@ def save_online(): database.get_online(True) vote_counts_df = pd.DataFrame(st.session_state.vote_counts) - vote_counts_df[["Wins ⭐", "Losses ❌"]] = vote_counts_df[ - ["Wins ⭐", "Losses ❌"] - ].add(st.session_state.online_leaderboard[["Wins ⭐", "Losses ❌"]], fill_value=0) - sorted_counts_df = vote_counts_df[["Wins ⭐", "Losses ❌"]] + vote_counts_df_added = vote_counts_df[["Wins ⭐", "Losses ❌"]].add( + st.session_state.online_leaderboard[["Wins ⭐", "Losses ❌"]], fill_value=0 + ) + sorted_counts_df = vote_counts_df_added[["Wins ⭐", "Losses ❌"]] sorted_counts_df["Model Name"] = sorted_counts_df.index sorted_counts_df.sort_values(by=["Wins ⭐", "Losses ❌"], inplace=True) diff --git a/pages/1_leaderboards.py b/pages/1_leaderboards.py index 0723b24..11aeffe 100644 --- a/pages/1_leaderboards.py +++ b/pages/1_leaderboards.py @@ -25,15 +25,16 @@ ) # Create a DataFrame with the sorted vote counts if source == "offline": + st.write(st.session_state.offline_leaderboard) + st.write(st.session_state.vote_counts) vote_counts_df = pd.DataFrame(st.session_state.vote_counts) vote_counts_df["Model Name"] = vote_counts_df.index - vote_counts_df[["Wins ⭐", "Losses ❌"]] = vote_counts_df[ - ["Wins ⭐", "Losses ❌"] - ].add( + vote_counts_df_added = vote_counts_df[["Wins ⭐", "Losses ❌"]].add( st.session_state.offline_leaderboard[["Wins ⭐", "Losses ❌"]], fill_value=0, ) - sorted_counts = vote_counts_df[["Model Name", "Wins ⭐", "Losses ❌"]] + sorted_counts = vote_counts_df_added[["Model Name", "Wins ⭐", "Losses ❌"]] + st.write(sorted_counts) sorted_counts.sort_values(by=["Wins ⭐", "Losses ❌"], inplace=True) sorted_counts.index = range(sorted_counts.shape[0]) @@ -54,14 +55,12 @@ detail_leaderboards = {"scores": detail_leaderboards} vote_counts_df = pd.DataFrame(st.session_state.vote_counts) - vote_counts_df[["Wins ⭐", "Losses ❌"]] = vote_counts_df[ - ["Wins ⭐", "Losses ❌"] - ].add( + vote_counts_df_added = vote_counts_df[["Wins ⭐", "Losses ❌"]].add( st.session_state.online_leaderboard[["Wins ⭐", "Losses ❌"]], fill_value=0, ) - vote_counts_df["Model Name"] = vote_counts_df.index - sorted_counts = vote_counts_df[["Model Name", "Wins ⭐", "Losses ❌"]] + vote_counts_df_added["Model Name"] = vote_counts_df_added.index + sorted_counts = vote_counts_df_added[["Model Name", "Wins ⭐", "Losses ❌"]] sorted_counts.sort_values(by=["Wins ⭐", "Losses ❌"], inplace=True) sorted_counts.index = range(sorted_counts.shape[0]) From b8e9248028ededcdf9d7aefb107e7f814e0800e4 Mon Sep 17 00:00:00 2001 From: Kacper-W-Kozdon Date: Sun, 12 May 2024 11:11:34 +0200 Subject: [PATCH 49/69] Minor fixes in save offline --- pages/1_leaderboards.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pages/1_leaderboards.py b/pages/1_leaderboards.py index 11aeffe..dd7f36e 100644 --- a/pages/1_leaderboards.py +++ b/pages/1_leaderboards.py @@ -33,7 +33,7 @@ st.session_state.offline_leaderboard[["Wins ⭐", "Losses ❌"]], fill_value=0, ) - sorted_counts = vote_counts_df_added[["Model Name", "Wins ⭐", "Losses ❌"]] + sorted_counts = vote_counts_df_added[["Wins ⭐", "Losses ❌"]] st.write(sorted_counts) sorted_counts.sort_values(by=["Wins ⭐", "Losses ❌"], inplace=True) sorted_counts.index = range(sorted_counts.shape[0]) @@ -60,7 +60,7 @@ fill_value=0, ) vote_counts_df_added["Model Name"] = vote_counts_df_added.index - sorted_counts = vote_counts_df_added[["Model Name", "Wins ⭐", "Losses ❌"]] + sorted_counts = vote_counts_df_added[["Wins ⭐", "Losses ❌"]] sorted_counts.sort_values(by=["Wins ⭐", "Losses ❌"], inplace=True) sorted_counts.index = range(sorted_counts.shape[0]) From c395244b48cf9615102dbd25af8fabecd2431f0e Mon Sep 17 00:00:00 2001 From: Kacper-W-Kozdon Date: Sun, 12 May 2024 11:13:54 +0200 Subject: [PATCH 50/69] Minor fixes in save offline --- pages/1_leaderboards.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pages/1_leaderboards.py b/pages/1_leaderboards.py index dd7f36e..d9abc82 100644 --- a/pages/1_leaderboards.py +++ b/pages/1_leaderboards.py @@ -33,7 +33,8 @@ st.session_state.offline_leaderboard[["Wins ⭐", "Losses ❌"]], fill_value=0, ) - sorted_counts = vote_counts_df_added[["Wins ⭐", "Losses ❌"]] + vote_counts_df_added["Model Name"] = vote_counts_df_added.index + sorted_counts = vote_counts_df_added[["Model Name", "Wins ⭐", "Losses ❌"]] st.write(sorted_counts) sorted_counts.sort_values(by=["Wins ⭐", "Losses ❌"], inplace=True) sorted_counts.index = range(sorted_counts.shape[0]) @@ -60,7 +61,7 @@ fill_value=0, ) vote_counts_df_added["Model Name"] = vote_counts_df_added.index - sorted_counts = vote_counts_df_added[["Wins ⭐", "Losses ❌"]] + sorted_counts = vote_counts_df_added[["Model Name", "Wins ⭐", "Losses ❌"]] sorted_counts.sort_values(by=["Wins ⭐", "Losses ❌"], inplace=True) sorted_counts.index = range(sorted_counts.shape[0]) From 2fbd160de8d3366c6d1b29e996312a127370a839 Mon Sep 17 00:00:00 2001 From: Kacper-W-Kozdon Date: Sun, 12 May 2024 11:17:10 +0200 Subject: [PATCH 51/69] Minor fixes in save offline --- pages/1_leaderboards.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pages/1_leaderboards.py b/pages/1_leaderboards.py index d9abc82..453bb66 100644 --- a/pages/1_leaderboards.py +++ b/pages/1_leaderboards.py @@ -56,6 +56,7 @@ detail_leaderboards = {"scores": detail_leaderboards} vote_counts_df = pd.DataFrame(st.session_state.vote_counts) + vote_counts_df["Model Name"] = vote_counts_df.index vote_counts_df_added = vote_counts_df[["Wins ⭐", "Losses ❌"]].add( st.session_state.online_leaderboard[["Wins ⭐", "Losses ❌"]], fill_value=0, From c0c2fb1a0f02d2fba8ce4b520c10fe474cd7730a Mon Sep 17 00:00:00 2001 From: Kacper-W-Kozdon Date: Sun, 12 May 2024 11:21:55 +0200 Subject: [PATCH 52/69] Minor fixes in save offline --- pages/1_leaderboards.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/pages/1_leaderboards.py b/pages/1_leaderboards.py index 453bb66..81a7ff9 100644 --- a/pages/1_leaderboards.py +++ b/pages/1_leaderboards.py @@ -25,8 +25,7 @@ ) # Create a DataFrame with the sorted vote counts if source == "offline": - st.write(st.session_state.offline_leaderboard) - st.write(st.session_state.vote_counts) + vote_counts_df = pd.DataFrame(st.session_state.vote_counts) vote_counts_df["Model Name"] = vote_counts_df.index vote_counts_df_added = vote_counts_df[["Wins ⭐", "Losses ❌"]].add( @@ -35,7 +34,6 @@ ) vote_counts_df_added["Model Name"] = vote_counts_df_added.index sorted_counts = vote_counts_df_added[["Model Name", "Wins ⭐", "Losses ❌"]] - st.write(sorted_counts) sorted_counts.sort_values(by=["Wins ⭐", "Losses ❌"], inplace=True) sorted_counts.index = range(sorted_counts.shape[0]) @@ -57,6 +55,7 @@ vote_counts_df = pd.DataFrame(st.session_state.vote_counts) vote_counts_df["Model Name"] = vote_counts_df.index + vote_counts_df_added = vote_counts_df[["Wins ⭐", "Losses ❌"]].add( st.session_state.online_leaderboard[["Wins ⭐", "Losses ❌"]], fill_value=0, From 456fe3d2e9ddf1c9eb0e70d7b5fef6e48c33c59a Mon Sep 17 00:00:00 2001 From: Kacper-W-Kozdon Date: Sun, 12 May 2024 11:27:22 +0200 Subject: [PATCH 53/69] Minor fixes in save offline --- helpers.py | 1 + 1 file changed, 1 insertion(+) diff --git a/helpers.py b/helpers.py index c6fcc76..8cdae2f 100644 --- a/helpers.py +++ b/helpers.py @@ -269,6 +269,7 @@ def save_online(): ) sorted_counts_df = vote_counts_df_added[["Wins ⭐", "Losses ❌"]] sorted_counts_df["Model Name"] = sorted_counts_df.index + sorted_counts_df = sorted_counts_df[["Model Name", "Wins ⭐", "Losses ❌"]] sorted_counts_df.sort_values(by=["Wins ⭐", "Losses ❌"], inplace=True) From 25bc860cacc8bfde4d0d3bd7487caaea053eaeb0 Mon Sep 17 00:00:00 2001 From: Kacper-W-Kozdon Date: Sun, 12 May 2024 11:33:24 +0200 Subject: [PATCH 54/69] Minor fixes in save offline --- helpers.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/helpers.py b/helpers.py index 8cdae2f..cc6b812 100644 --- a/helpers.py +++ b/helpers.py @@ -568,8 +568,8 @@ def right_button_clicked( st.session_state["model1"].split("@")[0], "Losses ❌" ] += 1 if ( - model2 not in st.session_state.detailed_leaderboards["scores"].keys() - or model1 not in st.session_state.detailed_leaderboards["scores"].keys() + model2 not in st.session_state.detailed_leaderboards["scores"].index + or model1 not in st.session_state.detailed_leaderboards["scores"].index ): st.session_state.detailed_leaderboards["scores"].at[model2, model1] = 0 st.session_state.detailed_leaderboards["scores"].at[model2, model1] += 1 @@ -607,8 +607,8 @@ def tie_button(cont1: st.container = None, cont2: st.container = None) -> None: st.session_state["vote_counts"].at[model2, "Wins ⭐"] += 1 st.session_state["vote_counts"].at[model1, "Wins ⭐"] += 1 if ( - model2 not in st.session_state.detailed_leaderboards["scores"].keys() - or model1 not in st.session_state.detailed_leaderboards["scores"].keys() + model2 not in st.session_state.detailed_leaderboards["scores"].index + or model1 not in st.session_state.detailed_leaderboards["scores"].index ): st.session_state.detailed_leaderboards["scores"].at[model2, model1] = 0 st.session_state.detailed_leaderboards["scores"].at[model1, model2] = 0 @@ -648,8 +648,8 @@ def no_win_button(cont1: st.container = None, cont2: st.container = None) -> Non st.session_state["vote_counts"].at[model2, "Losses ❌"] += 1 st.session_state["vote_counts"].at[model1, "Losses ❌"] += 1 if ( - model2 not in st.session_state.detailed_leaderboards["scores"].keys() - or model1 not in st.session_state.detailed_leaderboards["scores"].keys() + model2 not in st.session_state.detailed_leaderboards["scores"].index + or model1 not in st.session_state.detailed_leaderboards["scores"].index ): st.session_state.detailed_leaderboards["scores"].at[model2, model1] = 0 st.session_state.detailed_leaderboards["scores"].at[model1, model2] = 0 From 446c5259752aab05bac75ec872d5e8ae748166e2 Mon Sep 17 00:00:00 2001 From: Kacper-W-Kozdon Date: Sun, 12 May 2024 11:53:21 +0200 Subject: [PATCH 55/69] Minor fixes in save offline --- helpers.py | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/helpers.py b/helpers.py index cc6b812..103e50d 100644 --- a/helpers.py +++ b/helpers.py @@ -689,22 +689,20 @@ def save_button() -> None: st.write("Could not upload the results.") st.write(e) - st.session_state.vote_counts[["Wins ⭐", "Losses ❌"]] = ( - st.session_state.vote_counts[["Wins ⭐", "Losses ❌"]].where( - st.session_state.vote_counts[["Wins ⭐", "Losses ❌"]] == 0, 0 - ) + st.session_state.vote_counts.loc[["Wins ⭐", "Losses ❌"]].where( + st.session_state.vote_counts[["Wins ⭐", "Losses ❌"]] == 0, + 0, + inplace=True, ) - st.session_state.leaderboard[["Wins ⭐", "Losses ❌"]] = ( - st.session_state.leaderboard[["Wins ⭐", "Losses ❌"]].where( - st.session_state.leaderboard[["Wins ⭐", "Losses ❌"]] == 0, 0 - ) + st.session_state.leaderboard.loc[["Wins ⭐", "Losses ❌"]].where( + st.session_state.leaderboard[["Wins ⭐", "Losses ❌"]] == 0, + 0, + inplace=True, ) - st.session_state.detailed_leaderboards["scores"] = ( - st.session_state.detailed_leaderboards["scores"].where( - st.session_state.detailed_leaderboards["scores"] == 0, 0 - ) + st.session_state.detailed_leaderboards["scores"].where( + st.session_state.detailed_leaderboards["scores"] == 0, 0, inplace=True ) From 364d7174581111cec42c6c70fc71482a726478ed Mon Sep 17 00:00:00 2001 From: Kacper-W-Kozdon Date: Sun, 12 May 2024 11:56:47 +0200 Subject: [PATCH 56/69] Minor fixes in save offline --- leaderboard.csv | 50 +++++++-------- models.csv | 162 ++++++++++++++++++++++++------------------------ 2 files changed, 106 insertions(+), 106 deletions(-) diff --git a/leaderboard.csv b/leaderboard.csv index 68eb4e5..ce2c175 100644 --- a/leaderboard.csv +++ b/leaderboard.csv @@ -1,26 +1,26 @@ Model Name,Wins ⭐,Losses ❌ -mixtral-8x7b-instruct-v0.1,0,0 -llama-2-70b-chat,0,0 -gpt-4-turbo,0,0 -mistral-large,0,0 -llama-2-7b-chat,0,0 -gemma-2b-it,0,0 -mistral-7b-instruct-v0.1,0,0 -gemma-7b-it,0,0 -llama-2-13b-chat,0,0 -codellama-13b-instruct,0,0 -yi-34b-chat,0,0 -gpt-3.5-turbo,0,0 -deepseek-coder-33b-instruct,0,0 -llama-3-70b-chat,0,0 -mistral-medium,0,0 -mixtral-8x22b-instruct-v0.1,0,0 -other,0,0 -codellama-34b-instruct,0,0 -llama-3-8b-chat,0,0 -pplx-7b-chat,0,0 -mistral-7b-instruct-v0.2,0,0 -mistral-small,0,0 -gpt-4,0,0 -pplx-70b-chat,0,0 -codellama-7b-instruct,0,0 +codellama-13b-instruct,0.0,0.0 +codellama-34b-instruct,0.0,0.0 +codellama-7b-instruct,0.0,0.0 +deepseek-coder-33b-instruct,0.0,0.0 +gemma-2b-it,0.0,0.0 +gemma-7b-it,0.0,0.0 +gpt-3.5-turbo,0.0,0.0 +gpt-4,0.0,0.0 +gpt-4-turbo,0.0,0.0 +llama-2-13b-chat,0.0,0.0 +llama-2-70b-chat,0.0,0.0 +llama-2-7b-chat,0.0,0.0 +llama-3-70b-chat,0.0,0.0 +llama-3-8b-chat,0.0,0.0 +mistral-7b-instruct-v0.1,0.0,0.0 +mistral-7b-instruct-v0.2,0.0,0.0 +mistral-large,0.0,0.0 +mistral-medium,0.0,0.0 +mistral-small,0.0,0.0 +mixtral-8x22b-instruct-v0.1,0.0,0.0 +other,0.0,0.0 +pplx-70b-chat,0.0,0.0 +pplx-7b-chat,0.0,0.0 +yi-34b-chat,0.0,0.0 +mixtral-8x7b-instruct-v0.1,0.0,0.0 diff --git a/models.csv b/models.csv index 0d934ed..46b4744 100644 --- a/models.csv +++ b/models.csv @@ -1,81 +1,81 @@ -,models -0,mixtral-8x7b-instruct-v0.1@together-ai -1,mixtral-8x7b-instruct-v0.1@octoai -2,mixtral-8x7b-instruct-v0.1@replicate -3,mixtral-8x7b-instruct-v0.1@mistral-ai -4,mixtral-8x7b-instruct-v0.1@perplexity-ai -5,mixtral-8x7b-instruct-v0.1@anyscale -6,mixtral-8x7b-instruct-v0.1@fireworks-ai -7,mixtral-8x7b-instruct-v0.1@lepton-ai -8,mixtral-8x7b-instruct-v0.1@deepinfra -9,mixtral-8x7b-instruct-v0.1@aws-bedrock -10,llama-2-70b-chat@aws-bedrock -11,llama-2-70b-chat@together-ai -12,llama-2-70b-chat@octoai -13,llama-2-70b-chat@replicate -14,llama-2-70b-chat@perplexity-ai -15,llama-2-70b-chat@anyscale -16,llama-2-70b-chat@fireworks-ai -17,llama-2-70b-chat@lepton-ai -18,llama-2-70b-chat@deepinfra -19,llama-2-13b-chat@aws-bedrock -20,llama-2-13b-chat@together-ai -21,llama-2-13b-chat@octoai -22,llama-2-13b-chat@replicate -23,llama-2-13b-chat@anyscale -24,llama-2-13b-chat@fireworks-ai -25,llama-2-13b-chat@lepton-ai -26,llama-2-13b-chat@deepinfra -27,gemma-7b-it@anyscale -28,gemma-7b-it@together-ai -29,gemma-7b-it@fireworks-ai -30,gemma-7b-it@lepton-ai -31,gemma-7b-it@deepinfra -32,llama-3-8b-chat@together-ai -33,llama-3-8b-chat@fireworks-ai -34,llama-2-7b-chat@anyscale -35,llama-2-7b-chat@together-ai -36,llama-2-7b-chat@replicate -37,llama-2-7b-chat@fireworks-ai -38,llama-2-7b-chat@lepton-ai -39,llama-2-7b-chat@deepinfra -40,mistral-7b-instruct-v0.2@perplexity-ai -41,mistral-7b-instruct-v0.2@together-ai -42,mistral-7b-instruct-v0.2@mistral-ai -43,mistral-7b-instruct-v0.2@replicate -44,mistral-7b-instruct-v0.2@aws-bedrock -45,mistral-7b-instruct-v0.2@octoai -46,mistral-7b-instruct-v0.2@fireworks-ai -47,codellama-34b-instruct@anyscale -48,codellama-34b-instruct@perplexity-ai -49,codellama-34b-instruct@together-ai -50,codellama-34b-instruct@octoai -51,codellama-34b-instruct@fireworks-ai -52,codellama-34b-instruct@deepinfra -53,mistral-7b-instruct-v0.1@anyscale -54,mistral-7b-instruct-v0.1@together-ai -55,mistral-7b-instruct-v0.1@fireworks-ai -56,mistral-7b-instruct-v0.1@deepinfra -57,mixtral-8x22b-instruct-v0.1@mistral-ai -58,mixtral-8x22b-instruct-v0.1@together-ai -59,mixtral-8x22b-instruct-v0.1@fireworks-ai -60,mixtral-8x22b-instruct-v0.1@deepinfra -61,codellama-13b-instruct@together-ai -62,codellama-13b-instruct@octoai -63,codellama-13b-instruct@firewz7b-instruct@together-ai -64,codellama-7b-instruct@octoai -65,yi-34b-chat@together-ai -66,yi-34b-chat@deepinfra -67,llama-3-70b-chat@together-ai -68,llama-3-70b-chat@fireworks-ai -69,pplx-7b-chat@perplexity-ai -70,mistral-medium@mistral-ai -71,gpt-4@openai -72,pplx-70b-chat@perplexity-ai -73,gpt-3.5-turbo@openai -74,deepseek-coder-33b-instruct@together-ai -75,gemma-2b-it@together-ai -76,gpt-4-turbo@openai -77,mistral-small@mistral-ai -78,mistral-large@mistral-ai -79,other +Models +mixtral-8x7b-instruct-v0.1@together-ai +mixtral-8x7b-instruct-v0.1@octoai +mixtral-8x7b-instruct-v0.1@replicate +mixtral-8x7b-instruct-v0.1@mistral-ai +mixtral-8x7b-instruct-v0.1@perplexity-ai +mixtral-8x7b-instruct-v0.1@anyscale +mixtral-8x7b-instruct-v0.1@fireworks-ai +mixtral-8x7b-instruct-v0.1@lepton-ai +mixtral-8x7b-instruct-v0.1@deepinfra +mixtral-8x7b-instruct-v0.1@aws-bedrock +llama-2-70b-chat@aws-bedrock +llama-2-70b-chat@together-ai +llama-2-70b-chat@octoai +llama-2-70b-chat@replicate +llama-2-70b-chat@perplexity-ai +llama-2-70b-chat@anyscale +llama-2-70b-chat@fireworks-ai +llama-2-70b-chat@lepton-ai +llama-2-70b-chat@deepinfra +llama-2-13b-chat@aws-bedrock +llama-2-13b-chat@together-ai +llama-2-13b-chat@octoai +llama-2-13b-chat@replicate +llama-2-13b-chat@anyscale +llama-2-13b-chat@fireworks-ai +llama-2-13b-chat@lepton-ai +llama-2-13b-chat@deepinfra +gemma-7b-it@anyscale +gemma-7b-it@together-ai +gemma-7b-it@fireworks-ai +gemma-7b-it@lepton-ai +gemma-7b-it@deepinfra +llama-3-8b-chat@together-ai +llama-3-8b-chat@fireworks-ai +llama-2-7b-chat@anyscale +llama-2-7b-chat@together-ai +llama-2-7b-chat@replicate +llama-2-7b-chat@fireworks-ai +llama-2-7b-chat@lepton-ai +llama-2-7b-chat@deepinfra +mistral-7b-instruct-v0.2@perplexity-ai +mistral-7b-instruct-v0.2@together-ai +mistral-7b-instruct-v0.2@mistral-ai +mistral-7b-instruct-v0.2@replicate +mistral-7b-instruct-v0.2@aws-bedrock +mistral-7b-instruct-v0.2@octoai +mistral-7b-instruct-v0.2@fireworks-ai +codellama-34b-instruct@anyscale +codellama-34b-instruct@perplexity-ai +codellama-34b-instruct@together-ai +codellama-34b-instruct@octoai +codellama-34b-instruct@fireworks-ai +codellama-34b-instruct@deepinfra +mistral-7b-instruct-v0.1@anyscale +mistral-7b-instruct-v0.1@together-ai +mistral-7b-instruct-v0.1@fireworks-ai +mistral-7b-instruct-v0.1@deepinfra +mixtral-8x22b-instruct-v0.1@mistral-ai +mixtral-8x22b-instruct-v0.1@together-ai +mixtral-8x22b-instruct-v0.1@fireworks-ai +mixtral-8x22b-instruct-v0.1@deepinfra +codellama-13b-instruct@together-ai +codellama-13b-instruct@octoai +codellama-13b-instruct@firewz7b-instruct@together-ai +codellama-7b-instruct@octoai +yi-34b-chat@together-ai +yi-34b-chat@deepinfra +llama-3-70b-chat@together-ai +llama-3-70b-chat@fireworks-ai +pplx-7b-chat@perplexity-ai +mistral-medium@mistral-ai +gpt-4@openai +pplx-70b-chat@perplexity-ai +gpt-3.5-turbo@openai +deepseek-coder-33b-instruct@together-ai +gemma-2b-it@together-ai +gpt-4-turbo@openai +mistral-small@mistral-ai +mistral-large@mistral-ai +other From def71e72686688a69f46b9a7ef26cf628c50eb2c Mon Sep 17 00:00:00 2001 From: Kacper-W-Kozdon Date: Sun, 12 May 2024 11:56:52 +0200 Subject: [PATCH 57/69] Minor fixes in save offline --- detail_leaderboards.csv | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/detail_leaderboards.csv b/detail_leaderboards.csv index fdc6450..51ed795 100644 --- a/detail_leaderboards.csv +++ b/detail_leaderboards.csv @@ -14,7 +14,7 @@ gpt-3.5-turbo,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 deepseek-coder-33b-instruct,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 llama-3-70b-chat,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 mistral-medium,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 -mixtral-8x22b-instruct-v0.1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +mixtral-8x22b-instruct-v0.1,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 other,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 codellama-34b-instruct,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 llama-3-8b-chat,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 From 9c5d296ad81fd5a61a22fdfd6dbf210aec9862ac Mon Sep 17 00:00:00 2001 From: Kacper-W-Kozdon Date: Sun, 12 May 2024 16:55:07 +0200 Subject: [PATCH 58/69] leaderboards display --- helpers.py | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/helpers.py b/helpers.py index 103e50d..d475b37 100644 --- a/helpers.py +++ b/helpers.py @@ -689,18 +689,33 @@ def save_button() -> None: st.write("Could not upload the results.") st.write(e) - st.session_state.vote_counts.loc[["Wins ⭐", "Losses ❌"]].where( - st.session_state.vote_counts[["Wins ⭐", "Losses ❌"]] == 0, + vote_counts_temp = st.session_state.vote_counts.loc[ + ["Wins ⭐", "Losses ❌"] + ] + leaderboard_temp = st.session_state.leaderboard.loc[ + ["Wins ⭐", "Losses ❌"] + ] + + vote_counts_temp.where( + vote_counts_temp in [0, "0"], 0, inplace=True, ) - st.session_state.leaderboard.loc[["Wins ⭐", "Losses ❌"]].where( - st.session_state.leaderboard[["Wins ⭐", "Losses ❌"]] == 0, + st.session_state.vote_counts.loc[["Wins ⭐", "Losses ❌"]] = ( + vote_counts_temp + ) + + leaderboard_temp.where( + leaderboard_temp in [0, "0"], 0, inplace=True, ) + st.session_state.leaderboard.loc[["Wins ⭐", "Losses ❌"]] = ( + leaderboard_temp + ) + st.session_state.detailed_leaderboards["scores"].where( st.session_state.detailed_leaderboards["scores"] == 0, 0, inplace=True ) From 2c71cb5350a5dcc6211d85013f9f11dbf9ab7fa4 Mon Sep 17 00:00:00 2001 From: Kacper-W-Kozdon Date: Sun, 12 May 2024 17:33:38 +0200 Subject: [PATCH 59/69] detailed save online fix --- chatbot_arena.py | 1 + helpers.py | 34 +++------------------------------- 2 files changed, 4 insertions(+), 31 deletions(-) diff --git a/chatbot_arena.py b/chatbot_arena.py index 27dc561..a13e4f5 100644 --- a/chatbot_arena.py +++ b/chatbot_arena.py @@ -492,6 +492,7 @@ async def call(unify_obj, model, contain, message): with st.sidebar: helpers.Buttons.save_button() + st.write(st.session_state.vote_counts) if __name__ == "__main__": diff --git a/helpers.py b/helpers.py index d475b37..111e2cb 100644 --- a/helpers.py +++ b/helpers.py @@ -688,37 +688,9 @@ def save_button() -> None: except Exception as e: st.write("Could not upload the results.") st.write(e) - - vote_counts_temp = st.session_state.vote_counts.loc[ - ["Wins ⭐", "Losses ❌"] - ] - leaderboard_temp = st.session_state.leaderboard.loc[ - ["Wins ⭐", "Losses ❌"] - ] - - vote_counts_temp.where( - vote_counts_temp in [0, "0"], - 0, - inplace=True, - ) - - st.session_state.vote_counts.loc[["Wins ⭐", "Losses ❌"]] = ( - vote_counts_temp - ) - - leaderboard_temp.where( - leaderboard_temp in [0, "0"], - 0, - inplace=True, - ) - - st.session_state.leaderboard.loc[["Wins ⭐", "Losses ❌"]] = ( - leaderboard_temp - ) - - st.session_state.detailed_leaderboards["scores"].where( - st.session_state.detailed_leaderboards["scores"] == 0, 0, inplace=True - ) + finally: + for key in st.session_state.keys(): + del st.session_state[key] def print_history(contain: tuple[st.container]) -> None: From f7bd0809c96d8396f720d40d88cdf9b65fc58420 Mon Sep 17 00:00:00 2001 From: Kacper-W-Kozdon Date: Sun, 12 May 2024 17:53:02 +0200 Subject: [PATCH 60/69] removed 'scores' --- chatbot_arena.py | 3 +-- helpers.py | 8 +++----- pages/1_leaderboards.py | 4 ++-- 3 files changed, 6 insertions(+), 9 deletions(-) diff --git a/chatbot_arena.py b/chatbot_arena.py index a13e4f5..ca58eae 100644 --- a/chatbot_arena.py +++ b/chatbot_arena.py @@ -489,10 +489,9 @@ async def call(unify_obj, model, contain, message): if history_button_clicked: st.session_state["chat_history1"] = [] st.session_state["chat_history2"] = [] - + st.write(st.session_state.detailed_leaderboards) with st.sidebar: helpers.Buttons.save_button() - st.write(st.session_state.vote_counts) if __name__ == "__main__": diff --git a/helpers.py b/helpers.py index 111e2cb..b2c4b50 100644 --- a/helpers.py +++ b/helpers.py @@ -98,9 +98,7 @@ def get_offline(update: bool = False) -> None: json.dump(detail_leaderboards, out_file) with open("detail_leaderboards.csv", "r") as in_file: - st.session_state.offline_detailed = { - "scores": pd.read_csv(in_file, index_col=0) - } + st.session_state.offline_detailed = pd.read_csv(in_file, index_col=0) st.session_state.offline_leaderboard = pd.DataFrame(json_data) st.session_state.offline_leaderboard.set_index( @@ -175,7 +173,7 @@ def get_online(update: bool = False): gsheets_detail.drop("Unnamed: 0", axis=1, inplace=True) st.session_state.online_leaderboard = gsheets_leaderboard.convert_dtypes() - st.session_state.online_detailed = {"scores": gsheets_detail.convert_dtypes()} + st.session_state.online_detailed = gsheets_detail.convert_dtypes() st.session_state.online_models = gsheets_models["Models"] st.session_state.online_leaderboard.set_index( @@ -184,7 +182,7 @@ def get_online(update: bool = False): if not update: st.session_state.leaderboard = gsheets_leaderboard - st.session_state.detailed_leaderboards = {"scores": gsheets_detail} + st.session_state.detailed_leaderboards = gsheets_detail st.session_state.models = gsheets_models["Models"] st.session_state.leaderboard[["Wins ⭐", "Losses ❌"]] = ( diff --git a/pages/1_leaderboards.py b/pages/1_leaderboards.py index 81a7ff9..7c42393 100644 --- a/pages/1_leaderboards.py +++ b/pages/1_leaderboards.py @@ -42,7 +42,7 @@ ) model_selection = list(detail_leaderboards.keys()) - detail_leaderboards = {"scores": detail_leaderboards} + detail_leaderboards = detail_leaderboards if source == "online": helpers.database.get_online(True) @@ -51,7 +51,7 @@ ) model_selection = list(detail_leaderboards.keys()) - detail_leaderboards = {"scores": detail_leaderboards} + detail_leaderboards = detail_leaderboards vote_counts_df = pd.DataFrame(st.session_state.vote_counts) vote_counts_df["Model Name"] = vote_counts_df.index From e6eaf96d1046b3c0861c1f69c76a512a363a3e81 Mon Sep 17 00:00:00 2001 From: Kacper-W-Kozdon Date: Sun, 12 May 2024 18:03:45 +0200 Subject: [PATCH 61/69] removed 'scores' --- helpers.py | 92 +++++++++++++++-------------------------- pages/1_leaderboards.py | 12 +++--- 2 files changed, 39 insertions(+), 65 deletions(-) diff --git a/helpers.py b/helpers.py index b2c4b50..c56bcb0 100644 --- a/helpers.py +++ b/helpers.py @@ -61,18 +61,6 @@ def get_offline(update: bool = False) -> None: "Losses ❌": [losses for losses in data["Losses ❌"]], } - if not os.path.exists("./detail_leaderboards.json"): - with open("detail_leaderboards.json", "w") as out_file: - detail_leaderboards = { - "scores": { - winning_model: { - losing_model: 0 for losing_model in json_data.keys() - } - for winning_model in json_data.keys() - } - } - json.dump(detail_leaderboards, out_file) - if not os.path.exists("./detail_leaderboards.csv"): detail_dataframe = pd.DataFrame( data={ @@ -85,18 +73,6 @@ def get_offline(update: bool = False) -> None: detail_dataframe.index = list(json_data["Model Name"]) detail_dataframe.to_csv("detail_leaderboards.csv") - if not os.path.exists("./detail_leaderboards.json"): - with open("detail_leaderboards.json", "w") as out_file: - detail_leaderboards = { - "scores": { - winning_model: { - losing_model: 0 for losing_model in json_data.keys() - } - for winning_model in json_data.keys() - } - } - json.dump(detail_leaderboards, out_file) - with open("detail_leaderboards.csv", "r") as in_file: st.session_state.offline_detailed = pd.read_csv(in_file, index_col=0) @@ -117,9 +93,9 @@ def get_offline(update: bool = False) -> None: ) ) - st.session_state.detailed_leaderboards["scores"] = ( - st.session_state.detailed_leaderboards["scores"].where( - st.session_state.detailed_leaderboards["scores"] == 0, 0 + st.session_state.detailed_leaderboards = ( + st.session_state.detailed_leaderboards.where( + st.session_state.detailed_leaderboards == 0, 0 ) ) @@ -193,14 +169,12 @@ def get_online(update: bool = False): st.session_state.leaderboard = st.session_state.leaderboard.convert_dtypes() - st.session_state.detailed_leaderboards["scores"] = ( - st.session_state.detailed_leaderboards["scores"].where( - gsheets_detail == 0, 0 - ) + st.session_state.detailed_leaderboards = ( + st.session_state.detailed_leaderboards.where(gsheets_detail == 0, 0) ) - st.session_state.detailed_leaderboards["scores"] = ( - st.session_state.detailed_leaderboards["scores"].convert_dtypes() + st.session_state.detailed_leaderboards = ( + st.session_state.detailed_leaderboards.convert_dtypes() ) @staticmethod @@ -231,8 +205,8 @@ def save_offline(): sorted_counts_df.sort_values(by=["Wins ⭐", "Losses ❌"], inplace=True) - detail_leaderboards = st.session_state.detailed_leaderboards["scores"].add( - st.session_state.offline_detailed["scores"] + detail_leaderboards = st.session_state.detailed_leaderboards.add( + st.session_state.offline_detailed ) detail_leaderboards.index = detail_leaderboards.columns @@ -271,14 +245,14 @@ def save_online(): sorted_counts_df.sort_values(by=["Wins ⭐", "Losses ❌"], inplace=True) - st.session_state.detailed_leaderboards["scores"].index = ( - st.session_state.detailed_leaderboards["scores"].columns + st.session_state.detailed_leaderboards.index = ( + st.session_state.detailed_leaderboards.columns ) - st.session_state.online_detailed["scores"].index = ( - st.session_state.online_detailed["scores"].columns + st.session_state.online_detailed.index = ( + st.session_state.online_detailed.columns ) - detail_leaderboards = st.session_state.detailed_leaderboards["scores"].add( - st.session_state.online_detailed["scores"] + detail_leaderboards = st.session_state.detailed_leaderboards.add( + st.session_state.online_detailed ) detail_leaderboards.index = detail_leaderboards.columns try: @@ -522,11 +496,11 @@ def left_button_clicked( st.session_state["model2"].split("@")[0], "Losses ❌" ] += 1 if ( - model1 not in st.session_state.detailed_leaderboards["scores"].keys() - or model1 not in st.session_state.detailed_leaderboards["scores"].keys() + model1 not in st.session_state.detailed_leaderboards.keys() + or model1 not in st.session_state.detailed_leaderboards.keys() ): - st.session_state.detailed_leaderboards["scores"].at[model1, model2] = 0 - st.session_state.detailed_leaderboards["scores"].at[model1, model2] += 1 + st.session_state.detailed_leaderboards.at[model1, model2] = 0 + st.session_state.detailed_leaderboards.at[model1, model2] += 1 print_history(contain=(cont1, cont2)) try: @@ -566,11 +540,11 @@ def right_button_clicked( st.session_state["model1"].split("@")[0], "Losses ❌" ] += 1 if ( - model2 not in st.session_state.detailed_leaderboards["scores"].index - or model1 not in st.session_state.detailed_leaderboards["scores"].index + model2 not in st.session_state.detailed_leaderboards.index + or model1 not in st.session_state.detailed_leaderboards.index ): - st.session_state.detailed_leaderboards["scores"].at[model2, model1] = 0 - st.session_state.detailed_leaderboards["scores"].at[model2, model1] += 1 + st.session_state.detailed_leaderboards.at[model2, model1] = 0 + st.session_state.detailed_leaderboards.at[model2, model1] += 1 print_history(contain=(cont1, cont2)) try: @@ -605,13 +579,13 @@ def tie_button(cont1: st.container = None, cont2: st.container = None) -> None: st.session_state["vote_counts"].at[model2, "Wins ⭐"] += 1 st.session_state["vote_counts"].at[model1, "Wins ⭐"] += 1 if ( - model2 not in st.session_state.detailed_leaderboards["scores"].index - or model1 not in st.session_state.detailed_leaderboards["scores"].index + model2 not in st.session_state.detailed_leaderboards.index + or model1 not in st.session_state.detailed_leaderboards.index ): - st.session_state.detailed_leaderboards["scores"].at[model2, model1] = 0 - st.session_state.detailed_leaderboards["scores"].at[model1, model2] = 0 - st.session_state.detailed_leaderboards["scores"].at[model2, model1] += 1 - st.session_state.detailed_leaderboards["scores"].at[model1, model2] += 1 + st.session_state.detailed_leaderboards.at[model2, model1] = 0 + st.session_state.detailed_leaderboards.at[model1, model2] = 0 + st.session_state.detailed_leaderboards.at[model2, model1] += 1 + st.session_state.detailed_leaderboards.at[model1, model2] += 1 print_history(contain=(cont1, cont2)) try: @@ -646,11 +620,11 @@ def no_win_button(cont1: st.container = None, cont2: st.container = None) -> Non st.session_state["vote_counts"].at[model2, "Losses ❌"] += 1 st.session_state["vote_counts"].at[model1, "Losses ❌"] += 1 if ( - model2 not in st.session_state.detailed_leaderboards["scores"].index - or model1 not in st.session_state.detailed_leaderboards["scores"].index + model2 not in st.session_state.detailed_leaderboards.index + or model1 not in st.session_state.detailed_leaderboards.index ): - st.session_state.detailed_leaderboards["scores"].at[model2, model1] = 0 - st.session_state.detailed_leaderboards["scores"].at[model1, model2] = 0 + st.session_state.detailed_leaderboards.at[model2, model1] = 0 + st.session_state.detailed_leaderboards.at[model1, model2] = 0 print_history(contain=(cont1, cont2)) try: diff --git a/pages/1_leaderboards.py b/pages/1_leaderboards.py index 7c42393..d928daa 100644 --- a/pages/1_leaderboards.py +++ b/pages/1_leaderboards.py @@ -37,8 +37,8 @@ sorted_counts.sort_values(by=["Wins ⭐", "Losses ❌"], inplace=True) sorted_counts.index = range(sorted_counts.shape[0]) - detail_leaderboards = st.session_state.detailed_leaderboards["scores"].add( - st.session_state.offline_detailed["scores"], fill_value=0 + detail_leaderboards = st.session_state.detailed_leaderboards.add( + st.session_state.offline_detailed, fill_value=0 ) model_selection = list(detail_leaderboards.keys()) @@ -46,8 +46,8 @@ if source == "online": helpers.database.get_online(True) - detail_leaderboards = st.session_state.detailed_leaderboards["scores"].add( - st.session_state.online_detailed["scores"], fill_value=0 + detail_leaderboards = st.session_state.detailed_leaderboards.add( + st.session_state.online_detailed, fill_value=0 ) model_selection = list(detail_leaderboards.keys()) @@ -85,7 +85,7 @@ ] detail_leaderboards = st.session_state.detailed_leaderboards -model_selection = list(detail_leaderboards["scores"].keys())[1:] +model_selection = list(detail_leaderboards.keys())[1:] if st.session_state.enable_detail: select_for_comparison = st.data_editor( @@ -95,7 +95,7 @@ model_names = models_to_compare["Model Name"] - view_detail = detail_leaderboards["scores"].loc[model_names, model_names] + view_detail = detail_leaderboards.loc[model_names, model_names] with st.container(border=True): From 5d88d183bab848c40e3c577d3550d8773bc2b804 Mon Sep 17 00:00:00 2001 From: Kacper-W-Kozdon Date: Sun, 12 May 2024 18:49:41 +0200 Subject: [PATCH 62/69] fully fixed detailed view --- detail_leaderboards.csv | 52 ++++++++++++++++++++--------------------- leaderboard.csv | 8 +++---- pages/1_leaderboards.py | 3 +-- 3 files changed, 31 insertions(+), 32 deletions(-) diff --git a/detail_leaderboards.csv b/detail_leaderboards.csv index 51ed795..1cb1bd6 100644 --- a/detail_leaderboards.csv +++ b/detail_leaderboards.csv @@ -1,26 +1,26 @@ -,mixtral-8x7b-instruct-v0.1,llama-2-70b-chat,gpt-4-turbo,mistral-large,llama-2-7b-chat,gemma-2b-it,mistral-7b-instruct-v0.1,gemma-7b-it,llama-2-13b-chat,codellama-13b-instruct,yi-34b-chat,gpt-3.5-turbo,deepseek-coder-33b-instruct,llama-3-70b-chat,mistral-medium,mixtral-8x22b-instruct-v0.1,other,codellama-34b-instruct,llama-3-8b-chat,pplx-7b-chat,mistral-7b-instruct-v0.2,mistral-small,gpt-4,pplx-70b-chat,codellama-7b-instruct -mixtral-8x7b-instruct-v0.1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 -llama-2-70b-chat,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 -gpt-4-turbo,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 -mistral-large,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 -llama-2-7b-chat,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 -gemma-2b-it,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 -mistral-7b-instruct-v0.1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 -gemma-7b-it,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 -llama-2-13b-chat,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 -codellama-13b-instruct,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 -yi-34b-chat,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 -gpt-3.5-turbo,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 -deepseek-coder-33b-instruct,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 -llama-3-70b-chat,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 -mistral-medium,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 -mixtral-8x22b-instruct-v0.1,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 -other,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 -codellama-34b-instruct,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 -llama-3-8b-chat,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 -pplx-7b-chat,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 -mistral-7b-instruct-v0.2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 -mistral-small,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 -gpt-4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 -pplx-70b-chat,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 -codellama-7b-instruct,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +,codellama-13b-instruct,codellama-34b-instruct,codellama-7b-instruct,deepseek-coder-33b-instruct,gemma-2b-it,gemma-7b-it,gpt-3.5-turbo,gpt-4,gpt-4-turbo,llama-2-13b-chat,llama-2-70b-chat,llama-2-7b-chat,llama-3-70b-chat,llama-3-8b-chat,mistral-7b-instruct-v0.1,mistral-7b-instruct-v0.2,mistral-large,mistral-medium,mistral-small,mixtral-8x22b-instruct-v0.1,mixtral-8x7b-instruct-v0.1,other,pplx-70b-chat,pplx-7b-chat,yi-34b-chat +codellama-13b-instruct,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0 +codellama-34b-instruct,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0 +codellama-7b-instruct,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0 +deepseek-coder-33b-instruct,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0 +gemma-2b-it,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0 +gemma-7b-it,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0 +gpt-3.5-turbo,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0 +gpt-4,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0 +gpt-4-turbo,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,3.0,0.0,0.0,0.0,0.0 +llama-2-13b-chat,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0 +llama-2-70b-chat,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0 +llama-2-7b-chat,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0 +llama-3-70b-chat,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0 +llama-3-8b-chat,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0 +mistral-7b-instruct-v0.1,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0 +mistral-7b-instruct-v0.2,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0 +mistral-large,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0 +mistral-medium,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0 +mistral-small,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0 +mixtral-8x22b-instruct-v0.1,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0 +mixtral-8x7b-instruct-v0.1,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0 +other,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0 +pplx-70b-chat,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0 +pplx-7b-chat,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0 +yi-34b-chat,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0 diff --git a/leaderboard.csv b/leaderboard.csv index ce2c175..e2409a0 100644 --- a/leaderboard.csv +++ b/leaderboard.csv @@ -5,9 +5,6 @@ codellama-7b-instruct,0.0,0.0 deepseek-coder-33b-instruct,0.0,0.0 gemma-2b-it,0.0,0.0 gemma-7b-it,0.0,0.0 -gpt-3.5-turbo,0.0,0.0 -gpt-4,0.0,0.0 -gpt-4-turbo,0.0,0.0 llama-2-13b-chat,0.0,0.0 llama-2-70b-chat,0.0,0.0 llama-2-7b-chat,0.0,0.0 @@ -23,4 +20,7 @@ other,0.0,0.0 pplx-70b-chat,0.0,0.0 pplx-7b-chat,0.0,0.0 yi-34b-chat,0.0,0.0 -mixtral-8x7b-instruct-v0.1,0.0,0.0 +gpt-4,0.0,1.0 +gpt-3.5-turbo,1.0,3.0 +mixtral-8x7b-instruct-v0.1,6.0,14.0 +gpt-4-turbo,16.0,5.0 diff --git a/pages/1_leaderboards.py b/pages/1_leaderboards.py index d928daa..beadcb3 100644 --- a/pages/1_leaderboards.py +++ b/pages/1_leaderboards.py @@ -84,7 +84,6 @@ ["Compare", "Model Name", "Wins ⭐", "Losses ❌"] ] -detail_leaderboards = st.session_state.detailed_leaderboards model_selection = list(detail_leaderboards.keys())[1:] if st.session_state.enable_detail: @@ -132,7 +131,7 @@ unsafe_allow_html=True, ) st.markdown( - f"

{int(detail_leaderboards['scores'].at[model1_detail, model2_detail])}:{int(detail_leaderboards['scores'].at[model2_detail, model1_detail])}

", + f"

{int(detail_leaderboards.at[model1_detail, model2_detail])}:{int(detail_leaderboards.at[model2_detail, model1_detail])}

", unsafe_allow_html=True, ) enable_global = st.sidebar.checkbox( From 8913af545f3731c21e4ab33ccec9f5045c6cf3d5 Mon Sep 17 00:00:00 2001 From: Kacper-W-Kozdon Date: Sun, 12 May 2024 18:50:02 +0200 Subject: [PATCH 63/69] fully fixed detailed view --- helpers.py | 40 +++++++++++++--------------------------- 1 file changed, 13 insertions(+), 27 deletions(-) diff --git a/helpers.py b/helpers.py index c56bcb0..f10b2c4 100644 --- a/helpers.py +++ b/helpers.py @@ -84,7 +84,6 @@ def get_offline(update: bool = False) -> None: if not update: st.session_state.leaderboard = pd.DataFrame(json_data) - st.session_state.detailed_leaderboards = st.session_state.offline_detailed st.session_state.models = st.session_state.offline_models st.session_state.leaderboard[["Wins ⭐", "Losses ❌"]] = ( @@ -93,12 +92,6 @@ def get_offline(update: bool = False) -> None: ) ) - st.session_state.detailed_leaderboards = ( - st.session_state.detailed_leaderboards.where( - st.session_state.detailed_leaderboards == 0, 0 - ) - ) - @staticmethod def get_online(update: bool = False): """Static method. Assigns the online database's contents to the @@ -158,7 +151,6 @@ def get_online(update: bool = False): if not update: st.session_state.leaderboard = gsheets_leaderboard - st.session_state.detailed_leaderboards = gsheets_detail st.session_state.models = gsheets_models["Models"] st.session_state.leaderboard[["Wins ⭐", "Losses ❌"]] = ( @@ -169,14 +161,6 @@ def get_online(update: bool = False): st.session_state.leaderboard = st.session_state.leaderboard.convert_dtypes() - st.session_state.detailed_leaderboards = ( - st.session_state.detailed_leaderboards.where(gsheets_detail == 0, 0) - ) - - st.session_state.detailed_leaderboards = ( - st.session_state.detailed_leaderboards.convert_dtypes() - ) - @staticmethod def save_offline(): """Static method. Saves the session states in the local database. @@ -206,7 +190,7 @@ def save_offline(): sorted_counts_df.sort_values(by=["Wins ⭐", "Losses ❌"], inplace=True) detail_leaderboards = st.session_state.detailed_leaderboards.add( - st.session_state.offline_detailed + st.session_state.offline_detailed, fill_value=0 ) detail_leaderboards.index = detail_leaderboards.columns @@ -245,14 +229,8 @@ def save_online(): sorted_counts_df.sort_values(by=["Wins ⭐", "Losses ❌"], inplace=True) - st.session_state.detailed_leaderboards.index = ( - st.session_state.detailed_leaderboards.columns - ) - st.session_state.online_detailed.index = ( - st.session_state.online_detailed.columns - ) detail_leaderboards = st.session_state.detailed_leaderboards.add( - st.session_state.online_detailed + st.session_state.offline_detailed, fill_value=0 ) detail_leaderboards.index = detail_leaderboards.columns try: @@ -318,7 +296,6 @@ def init_session(mode: str = "keys") -> None: "scores", "authenticated", "new_models_selected", - "detailed_leaderboards", "detail", "new_source", "saved", @@ -368,6 +345,15 @@ def init_session(mode: str = "keys") -> None: "Model Name", inplace=True, drop=False ) + if "detailed_leaderboards" not in st.session_state: + st.session_state.detailed_leaderboards = pd.DataFrame({"other": [0]}) + st.session_state.detailed_leaderboards.insert( + 0, "", st.session_state.detailed_leaderboards.columns + ) + st.session_state.detailed_leaderboards.set_index( + "", inplace=True, drop=True + ) + if "enable_detail" not in st.session_state.keys(): st.session_state.enable_detail = False @@ -650,9 +636,9 @@ def save_button() -> None: "Save leaderboards", on_click=lambda: setattr(st.session_state, "saved", True), ) - # st.write(st.session_state.vote_counts) - if st.session_state.saved: + if st.session_state.saved: + st.write(st.session_state.detailed_leaderboards) st.session_state.saved = False database.save_offline() try: From bd9032c3713f45c19f1b8110bfb605e4b0f30368 Mon Sep 17 00:00:00 2001 From: Kacper-W-Kozdon Date: Sun, 12 May 2024 18:53:16 +0200 Subject: [PATCH 64/69] fully fixed detailed view --- chatbot_arena.py | 1 - helpers.py | 1 - 2 files changed, 2 deletions(-) diff --git a/chatbot_arena.py b/chatbot_arena.py index ca58eae..df1f7d1 100644 --- a/chatbot_arena.py +++ b/chatbot_arena.py @@ -489,7 +489,6 @@ async def call(unify_obj, model, contain, message): if history_button_clicked: st.session_state["chat_history1"] = [] st.session_state["chat_history2"] = [] - st.write(st.session_state.detailed_leaderboards) with st.sidebar: helpers.Buttons.save_button() diff --git a/helpers.py b/helpers.py index f10b2c4..b793e0e 100644 --- a/helpers.py +++ b/helpers.py @@ -638,7 +638,6 @@ def save_button() -> None: ) if st.session_state.saved: - st.write(st.session_state.detailed_leaderboards) st.session_state.saved = False database.save_offline() try: From c7f4929e5e276384defd16119973a7538ccb5eb9 Mon Sep 17 00:00:00 2001 From: Kacper-W-Kozdon Date: Sun, 12 May 2024 20:07:25 +0200 Subject: [PATCH 65/69] added Demo.mp4 --- Demo.mp4 | Bin 0 -> 30341930 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 Demo.mp4 diff --git a/Demo.mp4 b/Demo.mp4 new file mode 100644 index 0000000000000000000000000000000000000000..2140649c5a4df2be81f7a2afa9ce60f6bf1bab8f GIT binary patch literal 30341930 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

!ECl-@`<+MJfTZetTx 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%0D4hdDm_&oi^sSJd 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%0DAA3FiEh~;De$q0D&6D1;Psi(*C{=t`fxyOr#HG z)lg;Km%Ea@6sd4C5tsoLm>pUB?mxI}8?oADJMXb1IEcr`C(roUBhA_>ofz=ifC*Gq z1;Cpk%>uLS1p_PXnJxZ&Y^wyOmA1DD_)5hyj({c$5Ks^&KxzFmIs@RXUh1J11xbSW zSxGvh{v_Z?ty|;VV=pehlJ=+gr3|gBR1NF8uo-Mjbid#~{VD_u!&`u?iI1lV)~Md>xKMyAq&wGHrbOudp4g{NWg|9bDT z?xY0#zokkYP?~K1E%Wl;+js?+Li_6lS@3nWwWNvkT+gWM3wKRmBdmB^$1A@M0ege? z)){T|V37td%i;Z-uRqG&@SMAfv!_vE1}79`+pK8ux?YGWB5m=aQau`Au}0T#sA42E zF)L*d)RF4hO8h;H;XWH=BlAh&#jEf4f%`wH1%V_YRg4;=@8AEiXKiFwV-Ch3Xzvls zfEK33`{I?y<(HZaFh$YU{>RZLB%}n;(*5PQoB3>9pHa(UW?%6 zCAXo+Vq$Q6nIFFF)fh;sh`aeKTcFvWqhTz~-U-iYxdgW#^{rg-*MZM@1pY?dt2KQv zN}Vq`*koBz2v02~Gp=`J+1qQFS=dZ)_qFqE_TURaywBE^r?S<0!FMpQ+3ck;N)XP1 zkJ{c3^CEVy<$>oRxyZL>c47i$iHq2}W-TfVD|#iL)~?HinWz~%?=_cUa4awUc;~D6>G$T|@2+kK zw3T<%`_YoeAn9I0EEMdvRB)!5UW!&}I^$ru;gHWQop&8D5m8n~mf~00iTS>8I59F6 zY=Krv_7n<#TM~1{pL~?{as0!ko^mkz6xkE;IwGN^i-wWlI6virB)(9!SiRtCw9s;D>8{ z*cE95cyhOno}u7li7jbOY^i32^WmLXT#`$eAxJFQ$Sa z1sZG@$pS#M_Uam*awNgqmw8U(HrUGY=b=?y9c8NV2MNyro~ew92|JhM7XjNxTTym8 zOJbh1orIaop)FS9+e;q~jt8XWD93Px3|TBgTu+MkJ{vFD2&BIAwy2}3o(e{&XSGhe z3eXr_80YiigPdX-f>6d|okqnZSQ48|Qc`>i?niLlnW1GuCXx4V*aDo~1DIJBun?); z-rx&kd}_oF=h`oF1egVekY7Aa9>e6V!3^)+H1{2n{kdhTR{D1D<51gAx)7c4lMIH# zF|20d5w(s_8$Z&g-6s5#CPA^Z&gY;I6mDnomd5+Mn%X{nuDjnp?-S4NfIz6nzdYA78Ko#PZCf8`{N$K^2iQ}D21mH8^jh+F#13p(VcozrOGHy!SoNInO=!k9+TPc$j2n z_S$Q&z4p$`>@{nBR&X@GMt7Xh-LP4(T0DGa^=nvjoy)9HgbL}&M@;e*4KIAC)|VA_ znt1aLW>hK;bStmvd6XqpDarHOty^RF#^~eT4q)rxae}h-C6 z`c-_?;k43z^C;SkZt{!fX+cbaKf1I^lL5=3U$Iy?T$JEhdi{Faxfpa0jNtx*LNm3r zK_9&xCo{#dYr=QwoSkQ@EOE?J-=6L2K6Ky5@tOG7-Qv?h^TSq)#U=BL&o zGbiHKb|^TVmE*(|E$VE7=9OGmLbAPRwRWA&R)+?PrLIoNqFTF`#X|poS-0ig0#!Pf`6jqK`V4C ziLY&0`mQP%EERWs@lFI+CgFqluHBWJ{mOC1e#f>f-J6*5m$$lDo?N3P27G>Q%QdOB zh0AJqllnChr19V_QzHwrJ@!@LbE~9dVKa+Y^08c^E}m*FeAOe}8}CnRzds(sb*^#X z?W4Sg*u{wQeOZGx_u>$~ic*Wm9<}jTMC#xNcJ_rWJ26zZbci>*9JiZ(?Kn!nQgUEp zQh3RnE-PN{+I>Yz&rhYp1H;!0JR#D!N9sesON3m>BQ&pxqg`M9f$&q>RPk8;cb)h4 zNj&to8>>fWFr{#c~^QZd5rGnI-JG8~g$Hw$>VtY%mGwVBHtP6o% z6-&hNpCE1(uF++zCUB7_m&8`wSG@^lG3R(VpOZm(qr)PHIBMZYvv;mZD`a87i|&2Y z0peMWHSw2hmC=~4CR^X_!GQav^dnZe@XUezkz8ywDrYFL;`Ac9YhsD**Yqw`W>rH+ zN}Vk~JmladXFrKQr}u;t?YV1pObI5Vs^t)aAZW?sId7p!&W%^QdfC%5iEzO%zpuv* z!XnsPJ;BpYVo`U0HL`ZG7(%hn$pV4pHvYz{=NGd?Y+R3PldU<)UWhh)oSL=0|CC}b z2aXR94rZdr@TuIhQ`#*xwF+BUxY~~#WE1Dx&a!3yy!#R9j-;#PcB05yFutKllzTd3 zYQC9W7$)8d;Ud#_hQ5VEh>WnS3mXJ^ip{H+oyb`~g*q{o6MNu=59+qD%L^fNl)+E- zPY()=H4lVnZ(9vzJNhlzYpQiB=N4=yYa6bO7>5m!}R{bab*x$ z2}0`)9d>+mYocCv3AN|@v_|$2J#Lzhj&b6~-=+4#(;Y2)CQxOior+hcch<0XtE8La z_ue{)A-pt15%&&;K!m&zt0 z$5dpk)s~X+RpXxv`5i`09z+PGht3rUER=+hjeUEd2lCyB1>jDG!0TpCwJ#*cx!dNd zU9qP09DV?0HQwan$9s5)GK(gxSl;_5$j8Ss;RbUf#^Q`)NZSvd^QSom=QpoT6p~&CF z)}DvV>l=R){a=`m9=5uNj139mwe*(?MRP{^X;*N z)oR~oO59f-zeC0;iSGls@!;xxUrr0ltnxRW(5DMVR^jgR>GlQLDN#l2-grfgw11DE zP=P zWuzz5ZbT3rq5>b65O+w`j!cnv`v+I8*pK|m{0_BFr(_M->|b7p*FM0}{@$%1o@A<4 zBLm|g4Q!${6Dsq5^3BADK4gr3$0KW%$)MG_-via7G@ zke5Lw=lmy5GWYXn$}eUO4=6-#gvaC%7cYzC6Y7K~QQ!lCn%C!@)42R4R@#w*7ino2 zf*Wk+I+^i|{e?z%N%aKdxOsKPC>$(+tzgs0(0@i6#w3;}Qb|4jviAN~twc7rpl&Tm zr?k~EVyw94%ySrlCz6b7aXmlqo;*3Afu1esxbyBn^^RtBw=aAxB>xK;=EbS1;Iy5> z58nFrOj6lK<;pV}ro`$Ubn-wOk`&PhxA48RSj#vMN254Om|N?9P{9*A1^)5$1Jg2Z$oFd3 z1BzAYxJ?>Fp8%Z3DA+4dF!517Q{AFl-hpty0naWWkBlCV3t@3XV=r4k8W%{R-6Ib( z?u1H8?w+ckg2<$}54f!jp~K&b-!1>q=Ki)Z>Bq;0kZz*-zj5e$^mm88&=LkIt;q*F z0k^8&ok-j&KkG;3C871oUfXsT>o{#PRoMaXxYPkI1u-iaE1s}?x?9ad}n05*);w*pQ=k?2Jnv(EazNgAje6{ms?L& z-te>!@-|I447nRclKi4HU5a{+3tnR3PFI(7330D<(0)&jJ!CA}e<35>eUpY)!%fOt z_+r+_cE`egBzi_LEjlgAoar#)BmuBP7Y(tnXUBHggt?~mSJ_7>gz%>)8ygD3 zvw!I>eYbYs+iKzfpLkaJ@C{$|W2ljSVPS^4XZsx63&7K;#BO<0?NmoHCgM04yi+*GI z9O22|iei@VAAW-|A-wuZMe zWW_9ey%Tg9*@(t|eL)-O@8A%&N2_A~F{6IYsrWXG&bv&Vzy%+z8BocVP(lV_w7%EA zyg!CKcr;g3qxa@l2BB;sBPv@U`nw(#Jmq7>Y8T)2(DuXHfC$9(By)ej7Ga~ zG+5H%7a5lHK)N-mGUw}KyF1g5hT(xiWor2`W01}jYrORR&hGL0Va%jt)rSLicssed zeI-IMib=F1Du80}nEGz=3Qel|nZfhdwOC>$3y-0S*VNQx3-DWOq;m;XpuJXJ^JkwE zXzgxvMVMz0YWDQ%n6?{=UVNy+ay{f@Wi*v+rSJ_KpbhXx>LR`Oczv-;G#&8$S zbYtZk0XcJmB6W&+>r+4)!55Y}P2SBTgICH_P5}|5{ibc}r=7k>F?`8a^iHk~URRh4 zu+nb6==?!@&}D{f_-o<7jDrIg@uSv%4Y;kZj8^?KFz)EK?l^zxaE*Q#9CwWD-%RM9 zJ?PGNhZ%NWtpwis_MHLVlU0oZ&i&1shj80vo=nQi^W2&5OymVV5T zNz+7ZXetBJjZW-v}9;I9b&i&t$-a4MWu2#c4L z>Tdu?FN;~_-*Sk`pe(0veu^rv~9*I)wZ1K6la$w_CATi7|I&W_$VfT>>2DB?Ky0=u7yUsj(X%WLoU7ctELF4k_YdjAlodd}k!Div8S0%S z`vA_P6P29y5xL6X1|X$L>~7>d^Y0RHuF70->@XtxZ=6m%>_pVQm^vStjaZXw^z9f5 zp4mBx44!E)mRxtawd#Vb5nHR59$Q}z%toZTAnVqL#9pai?%talB8hm8XciFf1Ci%J z|DX6sDjsk1Zu;7`d}QkX?eBy9I915hMOd|q41bBtCyr$J`l+X3H(WG^Mv|jO5~UT-gN1yhC_Dax3Sw4oU6=yZJy~@PE2og z=qa;@f3giGBnKdIOU6!>885f1#dm((bHrNQ=Y3J*~)8Hs;@&L{j>F}&2LUo2(epY^>E z(2Kg!Bfm5=Uc6bg^|P_-ZlaF1TcCO?FlQc@GmdF#IjY}d;u zX%?BbH@*elW>Ky^So|fWY)+_Tigqakw&L0V2)W-z_rZ7nwtKea1pwy9Uqamd!!ziQ zT!WO5W!&bc-pn6f-6fq~&IvOH;kS zF7-MthAY#1k9@$T;h8Xl{K=HFZ^^j(#=TZoH5hpRc$jMEpScHsm;EozsUrACQ>LhJ zH0w9mEO%&1+bW+`xeQYnHPHv9R)}2iMBTNzL8NqpLuLjeCS$$9Ut2+Kt zdSkOZ)9@;vbVhj#7C<;~jNGMONDX+?;m9Dg3?;w(%gL_!6{m_g^?}3?1 zmMRKI4D!fpAdF41ddtL$pBKKxg$;ELaOLcv9MYcZN>5SE%*#&);%ZobmHN#_ZWC6S z%eZw(a9n^O^x~ag%eR4bN76V=eWU034Wz>0%e|cT_bA`E)(6kaOPCex>FFzN6J-P! zeLg5vhj}RA#d-C=g*N75DlcWXGqo-nht3rFLDpt!00_ci^d`kBvw-c&2}|Y1fV1NC zpOWzUeJ#sRt9LhPbovK|jifaTgif@zOho?JcihI28ix2ONf@l;-XwhZxP57Ff+=bh zbx>Prz9IIEp3kDUQqc)nQMON$m7O?MXu1TNXF|PXKmK?%no`ahTiuUNsdf!Yn_D7ITu-~olZpwAEnny_ zY(%sXBal6Mcpe|H+0N^Sk7UKR65g<#Q$w7bW*@!p^=`V&B9^ zFjA@zi=TA$ZmYl;y8L*?x7I1Y&O<9-u-zHH7blc=tMz4_hsq8q=troMm6+Gr=rNsP zD68`Xh0licn&S9mRnGen+cJgK{q{?ZegX^aPbb5Blqoptgx1Ku=Te zJwvG%n)Jt$UsEr8Hz+10Ch>L3$dRHM3_8j7WKSVA)Y-N+B3*(*j*JU zt*Yw^j+h`8NL=zx@(O&!J1}Qp6J(Momp%dfIzyr;gex}+LE$ea1McR4Z_*y*zX9=5 z-u_`fF_j-dIk9_2(QVnt^C3&mMq~xdiFkJ#7E>%qA{byLIP&aTF*(!W6!l_GK_XY# z<0Mpe=H8slp}tmTCwK7z6;t~f%AL%IGv?gk3~JY#QgQEC6cwB%d|Y1h8TW8dK~x_w zWJyQ65*BM!_&)}%CwYeP!nY2>mew$l_fKKDja2H+v!h2#0k01QNVqK3)@0?JE$vd1 zqQ^@=IPqCx&%&oh^lHc6%{N{3`4%5O>F+N-4H=cU6O33SlN#5Xi$CY)to@{v&UXz0 zfEt{YB4Um$?0VTIXGU=*wU5W!g?Th^i!9*mx8;3$FF#)vwy&K6d*8L8R3JgWcWfjV zOk|B9oSv^i&9pK$_{h_~*_1{uwJ^7w8%;<(4;7Nz@+Wn4J;M&!AnKcoa$@{2>E@5M zyZNqrOmnMlb+@@t(wpa8Wv2}}TTwWa8fa)6U8SgjBwGcK)g4g#m-^8Rz13nbq2Lusjhm?LTDH52EXFf{EI>T1tB3Olh3IfkE`9DS%WcSYv>!yPc~$) z=$)?CBzo^W*zmVZcpj?NVBbh_@TMBM#4y6oo{4+w$}7V>XtDI}!fPY*wDJu@A5(N^ z5L5X1&TD~}<9@w=PZ!XFh@99lUEkcYOvA z;gs^6$02#D1M zv!u}z6X^~i^70*UTxt|)ZLf-)h54Q%^`*KVn;}-g`)fHynY1^oKc`9WA^4A z2;6UiuaLiSc@Ac>8OhB4{GhObhld|L7pM?Nr2yapn-btc;gL{9a z)sAurvYFdwZjL2Z+UX@b4+)`#Qw=&l^N$x zqymo5WgJB0!*PSB59u=YkVJF*fd7c|lXdIQs_9@2PjnC4Hy4>DW}| zY4&@qQ_dCup44H^M{omI%AFIz_Ze}CcPXMowAJ#M-$GwIPuw4&dAim{W5U6uF0N#@n@4;aQp#Y-Ip3(~HUXVr{yK>st3`T@ z0Ei0HcoeqyQzOMxO(|8OJpN1){(v~;g^ZUgPaGvrsTEc}ZvH4mAt)K@PR4H9#N0DF zDSQm$xAcqLjqu!O@pnJAab4P^v81v2NAZn_@kZ=M>B21_R;T|p5E*PuB3s>{WoD0GmmcvVSwlCJKSZ|CFXsKYL|UJ#U6HPERrjF{;+ZMKbd<#`Dk+Ut6;8vjE+NF41I`Z_e09q&FCDWD$BB976>pbx2ex+Xv zP2YMGK;06X4`)2)JG7K|1kx27RD4hQNpG7@Bbydp@SF6|nDIWt*Acgl5jSjk0uhS@ zd)Orh#5s1w-#E69&}F3Wy%D{6X*{yQf1Zynl~)$WLokWA$3~pCCsu>~(<-vc>)HP;uQru#RP_w# zj6=3A+)>_eqM3wKRQTQ6vLtRiqYUgKe3V!VBwp>?rXX+21tb02hQeBZ#ZO4jlHvx5 z(CzMAEYZKlGleRWBZgh4K}k~q-$-JVk^w33zvXb2@YuIdd97a}#2`(HM6jLiob5XND2Nsa7;7 z00%NRRIZWLP%;zVa{a^iT;yHgXN#6@F*h=Z>aA#86Iq@m2Hs7`i66PgOeCqKo`(?F zQUH@6Nw_IleoLO1sNZn7pT80z+Qs@FBXV;P3eX zL%2leN{1BbiJ!_$WTDzhdT{E+1a~CvhT35yJ&jKJ5Q+WB49b11A3?l!7_be*lo=zB zqTpzn9!ScQfuOU0LwcJ{W?urm-)Ar|JQ~@e;f4TA=TuUM6V3WBm*)$AT%K3ys|6=x z^BZ4;X`;j{GD)2-(vc^lSGKwo50P0{+~w13M}fGt-(vK8?#{sgW3Jt>inX!Z=eaBp0C z=6M7U04SjJZv_BISeN2a9|C{?o61q-UlI=ew=~dSMdJJ2Ys9O+Da`txijm(^LE^W6 zx9ntu{G-Q=-3$dXZuLk>BVFlaz?c6>UxP!7&ZB?kfL~?zpV3js7&$Qc)6sJUjemr8 zm#^5(aQ~-f{=;xq{`bZiebfWK;{Pz9z6CHOG4%J~%5P-^kg>m&@0O)N{;NMDZY%#7 z=!)IUKjZ-ol{tU3VjK<#{htlwW&N6b`iB$qV87KbAY!!iXhgcw+{wRZ5(L23ef!VRv|0Gu zX#Qu@|DWW>;l`CmVu#UX_t!0Wl4EOq{lFi#U%VHgVPmd$dkqdZ$rnHC0_Iky+O54~on~Z16iRCqh$wY^jAS#MCTNLZx+6U=J5-(-tike%%#~eO@ zghbGW=MC(X1g2JjyB^0NXQ5WIZd?~eQk!VVUCov@Z}XuUbljpm;AVMHj}8{EIZvY* z90t-F;)^c6jFy&YwxXiUK2aD~4c+?Lyl}IbN5;=XNzUR2p^k2`@%lB&6+hYtVi);f zqSudbaBDBhEwFCM;ndmC5zoXY_cx;-hru{b1A^S=JSnH33iX#hBBYb= zzr79}4?&^_Z)VT6 z;lMZW0#-7-lBnsNXxj&bnE^n-{iVi0G&g_{Y*Js&i389=bD$ZzhmnL~0NUv618V?M z^er*0JN~OM5IjPr?~d;zhV@As!<#?g0MJpn#DA7=_5&>fprF57{O=GV{tcT$DgULe z{J-Hm^gn6$NG1Q$$p5S-|K#}Z1p6QK^ZzMT|5L#Kg5&={)_>Ra|8~3oIpOe~uaA^m zm3Li82Vr#{w{PEm|H1P%s~@RMO*xa*vGWRIWf?*BZO+-ady*1a6$aqq+Kl$G&?pr#KIzla1X9 zvqvMi0C;dK(kYggwnuwMFX25yk2j+g)M-yaRs#GG^i z{(}B@vizAKfB*j9$?|8O9DVKrq&A6+gbIQ{cTQ+#gsDQ3B~GfHcfoZ5 zsK?qV1BDHTV906=#>&4k_P2%+>)j^Rjk@WvMThXlrdoZ+ej#VqC$h6bjU$3D4ffF$ zHZq!O_p2%UugDiN2NxCDAhMs%hXmaW;eWQdlr}WP)UDl9-j`GHlIwQ2p}|h@=WCp5 z(2JWSDZq8uDQ{t4_VB1ftEhs+)vd$yB3Zlt5udwUKO!+ZzAyyIv|(OXSi@w=666!Y z|1|cC%iFjHsgQ8DY>R~gl1I}u=3wW#w#Khr2Ac%mp3S+ug&lw=v1T41o)3xOI^@Cs z^wJ>U9=tQ7Z*43$gi8R*(4v5JuSVSV6~A7Xk>8_)G6P+ZsV~KkZKgyc`T*!6Q?34! zG_?#%iP;bP5Bt93tYtV=UXoO;D=*<%@0UCYl+OJn`Al=a zl?|neMfZRmiw}+B$9Z1KW$eZ_J4&i_3|HWHadSl%1YC+yka*Q{ zllsC&Y2A%rKtj4;Pi(doyILuJLQ;@^1=+d-Kv8G^M!C+VtVQ74pLb~WyjnpXVOHlk zTdW8+e*NrNlnC@MJ@)zRdVlv>8t{^~nL~+U<+m5M$7W-Y$QvsqCR5Hz^~IlnsnWk?mi@sRx5|b{S>l05bsSH`KHP z*syFpn}Ex>xvoh+;*ePcs?_Nd3wyUY4>@ehl$_l#WV2O7&VJ8BK0Gz|Hm=cTGqF3% z_gfrbx06)p9|98a{2uK;U%Tg)D?X<`Rl40>`o(SCRKI^67~O3(c${Yson|dX0-3R^-9Wt`aWv6G{8wQ9-G}-;)CZ87 zsm};M7~;0F)dFw^7&{e}77VFH-1yv$klOyNrZSfW*j2MXWRSa+t=!0}3ofPm8wG`p z8(rytol^yHi)?xxq%U@==*0KtjZsf0wBFp=~A{| z`QYP!a7!D)y)ExbuP^OZR(2^aY*dAqWUL@n=NAXv-8Q=JX8@L_KP3xpnaFHEf47RA zIelY!V8-Su``QlSJIf`{<%a8bXRyu~1z8E}Z0VXwgCqFj!Ihr-P zt7mbJIAZ_p9fU3jcex3ZL&JsG$qnMii14KuD=}q#{-pRw#_l#1b8_3p2`22uaY;Ed zjDZfz+ZewhYKnKWR7`39@4P>v+@CvZ+_{we@GX_h8!$$BiL6?AFA=lUZGqhGmVEvk zFufV=ofgBU&)uYyRv}ZJ>nq+fwv%uwu>O@1!|^mC)slLg_c=&F#KA`}Fk z)46y7UJiPqrhDu9fp52lKG|iXuXF@;I@?-TcQP~k@ur1s2r?UnOCYGLE$`TW=zzf6 za3UNr0qVSOP1-ataWkuWD11*I?pzN7$ouR{$qn((z4Bg9SNPNqP#2XQY(O%DOvUTx z8EL+*jCwcjb<#q-4fpXog43E*X0SS0naCy)H-`FHn=rvm$ZMJkYBpT5;}d3w zR=)P8`e^~1^TVvqs~74!oVf?_6UtJ?PRv)jV;yKm+i4X$5}jl{-TN|43QpG*nC2D zjw;!l*5JCjEtbq+Be12U^-aJg?3kZHdoyIG?#|7ifPfPYD%`{M341>y5^+E3lXH2O z^<}j5OVp*PQ;$ysjmJf*I0&6-_M*iyW89BvjAPUE8IEh_o`_*hrGG$(tn)Za+tz6% zl_wTeEKgDy?vZL@ZVQlHmh};*WY(S+rGm8a6WMGPwYgwu8EM0L2pxVcm8u25=>4uT zZCob6N!Oj<>KSHWb&kcdqX7wG-jwLg-Q*-gnEFx!654ljT({6;{1br*Y{y|5YVhfnhi-7}6zpUMW~T z8|8P~(d11#@g3vgTJ?qWEalgrQu6m&~f|2{rfQI1R5dpQ$F!kGijKsy*h& z1M4(BlXrDIl||8`pmTMC*ke!la1NxGG{bUc$~=xwM{dk0R|FQyc$LK#$>YmI*W3NH zf$!-Oz7;jzwkz$Rz4xLE;z+0_W-^^HvXG=MQ^$iCqDu{M++Rcy@_HpV7 zv}KCM;3EBK&Rz|gU=nOnRejjcx=hV@Lp<_fbJp}@p0=0gE;M(GSCqMy!5U#{jLH`C z%&Hcz#F=aihoT=w?=(`!%~K^zgL4wh{T;L^>;`-dTDjz>eSbtmMt=h3K7{zeYMj|h z*ZP7e%d%*kn#h#iO0y}q1^ePSP8+q&(lqCu)Sy&?)JEk5CI~W7jtex7^aKp-QA_Q6 z3WBJ?pM-pEndiT9Ar)jS%|4Y-zA=2EO8=JeSPZpnPt;rPeGfS^CWbA$C^J25z?0Ww z0`7|-iZY(OgdVFD7{cr4)i2kXG^XtA;xDh|N>ORqDrUU%FJ)Aeh{eREfr{Cz{Dj_} ziW51Q4vzVDF@wW~_LdXl^zpD!CGL5%gN2%9PLZEcBwtpau`nJgD1 zti9<Sl5od8pP24jp2zWg@k~#(uYWGOq?;4wmQ6?JO3*g{l!7sjMI5ANe6#< zy*+X$5ycntic9u-iY8Wa04J3PZ5uv?akFAp=ZTE3mW||+Su;{%UFy$%GHGXD(0Y+c z9-Q*piXVz@Ymez|BM}znh49JGWY}H&7V1*VdiL2Ac|%@%sLZ1bh3P_HVQ#B^SM_vb zE#n!AqSF}^yIB|VO1zw(Z<3{p_W17ImK>J`OVxuJ%=zgkY^ev>6M@-c^l&`kQ5 zsCaaLnln>sMiD-62CiKq;GdQ~MsP3F9v`Qren@E4D9UROknQm)W+~MyhI%MvCPYbS z7C`_{Lc$cQhL&V6jcN#lYp-JfSue ztQcscq0e%B-eW29yS%W_6nH5qIPDG>le3xx|CIvWmj@JK?x&>VJ82-fdHcQ8^S1(0 z8FtTH@X=)IuzP29^|>|^v~3;QPNw{!%;+WMvlybz;^iKNF3466sLSp9D$I-w{CJS`&43g3 z{-CDz=E9!9=0Sx)$(w_sY~MF45lo)hW{&UQWwpgmx!=4hcJr#b!fl0fraJe$qXq5E zN`Z$mLWBo zgy##Mu77W=?wPMHld+atxYbLXDB}j%-#u2>*A(SA!m1UorN1*;gHcGhqxcF%g`{C7 z7-kBPQ-yCAkvKL#-;&_E&*32SElxy}_T}UCU5TE2a{L`fjc;6ua383_TXg|OOuXG~ zv@xwX%05CdY1~Vm&zoA~myI~DkeS!Ko+Y|W^t09%Lfm&@yChwzd#nf;ZigLKW}k>8 z4zq;8EO;QU2z2f#KI+`y7n2&S_`23QuBdhO$uIZ48O0~BM1Ir!1q~Wort0M13sAx^ z@<5(IvfBc&SD@CRpG=MJq zvMpfDNNBnez@CVTF&ouRkb$!wr*4HT+e3w(vESM z)8Wcp?L^_OdQM{YVUnnvL?BmG2tRNX-LR79LacGN`pf#@`1{DAAsl|RQ2$M8bw~=V zn*Z$)aPqK>d5;J@6CBl_4>{uY`>1*?$KN9I*M*sJ2HFxuJ}g`mDQ2I z$r%4f;(v+}1qIS%&9cm%A(N$r6WON1OtVO|auAs4dRZ2QLR^sl7E(u}iSPkL@5c!) zVfuL$GfMaZ+RL=E%uZ0Y1U=-^kYcf7_OmU`_!OMm~q{Z9yvtdcSg^Wr#*TI+mUdG?id zo2F}+X9&~Tlj@*H6k2tH{ihundvkKC(|s;N^gPW@b0i+WC96xc^01o_oY7$9>kbM9 zlN6UZnYEpYW6$fOKs8`_yE%4-0u6ZxUN?Njpoq*t+Zlhlnl<8%>$L3X!fUgs*&C)(KD z;T11F<;Y5?l%&W?+Q{lJ;d#l{1bPXgSr97=r%0l8q)8^!eW47L`d}Azng5aXb6z6L!e}b`6oo~*TAi1$&wO#z>t|=PpT2}@ypDQE zL1B!%fO4Pqq{g$}6SAgoZ8=-!XOdPwB>*i^!bVN;9*@6i%f%ru7So<=I0PZXauap) z=zP^sjKg-F2&Tdb3kr0UXl%i8n#eHOJTHF~>vY(eJIohpPo~IbNDR|L=9xoIekssq z2Rmy>JcwX7uU3vwctu-}Q%-|7V~F59N_Wv8taM(TV$+Tk`w8b{3)>v>AHVd=ii`7Z z1K%>&&Oy(z@MpW4jpyEb{aj0%TfkJXBfZ(w&F?fd3bL`SFsrW6`iM6qbeNx$i-C_K zT0^XHq!2v|!AP>>k>8`j}g96QGpdvb`ehG5P z+pl1lJIRX0!7(2xb1fa?nl{P@N*iruPpU*HQpgxSfj+$iX4!@K?qa@(f*peQ3liVTO%zl3qcrpfr$aKR=Rg&nEo~H{_|vFIOp5 z95!-nWiD8-oR#P`zaNPHDzkR8OasFKx=SlVISI0)kIdj=D4{53cGoh_i|m;(I=YJE?hU&#hWly0CHXe&9B9@&=qbLF_7@2aSGlm34)m)^Ja5bD zV4AYgjGR^Z7w!ZNe=RcxB5v-9ze|(3#t<^;O!7KMQ74PmIh*DbS-wio;zX$Ihki0- z1O?vp6Ef$byNaJ3EMtG#Hcvk*KOMHT@ls0A%&lEu8XOQPKzpT8SMM7F{iWUK4U0jK z*)kEc&wRok0!4Onw>`JZkE7WXd*u@K73kC#SIp*-y>AU@H4aMVr@!K$m=aze#+gC zSw#$EN>~Ooi12WQP9Z|II_g=hvqpK!Fo=tlrCb(G6NOn{?|sh2c8!v{$6cY|_$^C| z80tR!f!Skn3S{*anO~J8kTaKup$L~aQKn@5voFDFaDMWaU z9hQVk^x!YgbNL$*t&{sBHDA>9RB#(kzkfwdZfmy+s>Xgc`*<2`M^nK(K%qh@@x7zn zEXM+zpKGhALL((GQCo4E|L45aBrnoJE)x$sMGDV~!(toFo)6&7|as7p`9h{k=< zJOOG)X5tT-2iVeSh= zQr3}7C3YI5_{4wi+h(- z=x``PqurU@NUuePeZ@R4$;gkCUV=6PS0w`Y?NGGb`y3jP&K%c+tnY&cH{){WVH)y3 zPMyyY@acs4o=iW64dS!ME4P%TLs5eQ;I|y^?Cg(Habfd}APNsNUHY~sgm2$_49GA$ zmSzF#*%bZ*AL%R}1rgK~Y`uOXfjHqN<}XUobBZz~gI46{%^vNCr_~bq`wo+hq zwmYR4Z#Yi#)~<-FiGiDv0b(Z&8@Ew|GS&H%<@7KK^Rzfv!@ zSgAnw<&&QJ_v}&=uf3$K)cQ~j3aJy&(YEC(+OOLx*u{O1mg2eT9Ql8!d-HfGqyG)t=^>s8J(^PJ~AXJ*cG&gb*~yg%+@}e*2a(S@#FPFYAAOD_Dy{J1}?wN-}P* z4Qp>Nov%j$%D7tB+u{zDP;1VU=Rgt3K7-I-Gxf+7(@+nq*v&S4%gL93;u}BFXg&)B z_Ni^E=AC@3j{|>`t1v~+TLx)KKSo*U+^-?pw2v!K=6~?_(E9H9{-=d}$v6Jv+NcY@ zlMg;G4Ri{O^NW-W)iY|h-v8OEc>jl0^fvkz=ez2-Veec1|D~`3Vu{82jPE^R zYb(!Jmqt8%wyF^u0BHwcQdJOl8Dz;msiN zo`<|+dP-AKuwZpiDMlToQwjAp?%%=?cCtyy^zW@yNwWbR5jg)d;x!?ZoGH|1Eqxn7 z0|hJ7o#TE!l>KxeGoH5+Jr4sbw(4E9ko)Rk8g9JC*H+7@wThZg1b&&vz}C(b2C?$- z{i_{AW6mkrS;^Rb?c1Ajx3}YVoi*?f_{`QJleK-6W@)xEUW(v2kjGiI8Q8`CePEv& z(JfXT5+>O{8fKn8MG!Ib?~KJ+a!#s^u;vxD@(X7?jW>Jm;POi|V7eF<(sZIY{^ZT; zk4tNmnK1eAmt^)%uJ~pWdS1-f@99Kd&izw zJfczmqZWiLzr&`r!kV}8h5rQLF9eLoA*pz9%vPFI#hO3?=P+0ct<+e6#Dmm~X;^K{ zV7!1*htbSer)3PJR&>=`gBoc}dfm;*Y9#C@!#s^*=7q1}JtiPr#Y=U9tr*vi?Ot-; zf{=;hammTXuhk0nIt;fpIOv+{HVRW(4vh&;w}hgMc-z>4wm~>ft)3%CN>i^zRY;x=q8R-+)Jm$QRlohjoz&r8LpXeFUawyTcf7lXm^rNn_=UjqoUom{J^yv0yTd&=|rc=IDW=PpRB)!XnI) z`x`4n)}Y*9#x0=Aaosb_s!L-GF`mqhs2^P;pLdu3uoQAlorCmH-6VI&WG$S zu*l4<j;4Cmnx?9jIu)PiCUV!C}fY(g!&U^>y zvzt1@HzB&V|Ea;jiK|Ctx>l}@p;Xwf67a!b!c>T-m{ZwLy0pN<;|_9oN_0JZp*7%* z37ta>n4l_RU~QpbH%fQ%I~6j5gJGpg7rJ920QtAf(j9?Y0d zuSyT(tPKg1J;kM)y>VHuVfdSH#hzYoE0Bw)1biw9DNA8(9?_)CVZA9RlZ=8AM+iTA z7Rt75lyJZckQ-S3I{2DRS2Gk(cx%7FyvCyIyM8jraVG_x&=pZI1#mMSjUb-8XQ{Ho z^6d^Yl)40r*xj}4J)Pm2de_}>Qq6Ab7#^*lwyBQ%A};zy(RbyMten*-wkLa&g0r!J zB?6(QSdbCi4HgyJ_1YqI;c+&!<7}X{Y?|LPzQ()IjVq?Cq8Qzwe=IV z6gPT|z*0i5C5g5AZJ`wn4p)Bm#vy=%Q+4~~_i|@7NO=ES!^Nz`C=cBlMa74)E#IZO z$cFF2KW$;vZ9U|+Yg_vdChj#}#m+HS1U3SWJ9(fVZD<$;f)k^KC(iHOZ%6$i>2b(; zHOdIRtL^$OnD;2Oz&<#1cM%4WFH7EC^GDc<+OUu(!-?DqR}=|*hZ&w%FQo_l-W(S| z+$*wGP7+9dHVX0BQ!DL}j9tCTbdt=}OxmIwte+b?HaT&cZnTBSL)mymzocm@oI88V z$K<@A1WRZxPJ6mWAI(xct_HmrBze^B*cGRP&7>KW^KRVpbo|F@xgNpSD>AubHk#(} z?!&h)755}Y5gIgTlMOmz(n93VP~4~UQ-v?1*!>w;?P9B`(?gPug`sKzSL1JOONfE6 zis2$hPNG$>WD}XV9{Kr9y`|MHmH!B zr20eYMobHWL1rssU7_u^R;x{ingBH2J6#y=Z2o13n9*aB?L_B8+S{9L<#ac%$jYAG zT?-uK7skS)u!hqE@%PRcLiQgE9(0i5i*hNSr(Tr^P}uUdA(^NnLH;>(JzSpwdP+e~ zGgOi;c*HC3s^a4{P)0_;JFKMX_Mot-IJ;M^{lcWli`$w61&Fuoob5ZFr0Z26~22l5VR>+ogkZ?MGqWq-||dq@!)+ zWYDsA>_xH6A0NRus_#D^x5f! z$58$8==b3u`R3FwSfaa2-g#5W>q#*!QRWSK4KKD@F9+(Dz?6v~Ys*b4()CzPX4emf zaqw)GwiT8ICf~afx58@0D>&_uS`Owd^Ks9VKN$7v`7B4Q#b~hy|*i0o1 z9_Ie`(bOizjlFXR)631gAvKJa7J4ZN96wd+cM$@`QESyt`2Yd`Q zw*+BK$(80plM_+1s&8Y#!4R#T^g_CkwxG!y-W#|r8<#C9QY3ZdJUQX5yku-|__tT( zk^UF7HMGbH;Pbw@C5aJdW&H`yf-|d$j1Z}Tw%J$TC_!y5~D3H8>BH1q!NvGYp|!DY&MFH zyt5m2$4yB@E7|1sH`(m;UuqS`3mp(}q9H1-viw@4cxgbVHE4|1ngircBDbL?k+RVi z=<0YkP(KPj`I+qRVJbG}TKp?aZu2X2{ZxbuR>>DKH1=f`F>okqo^&Y6dU zHC!<4oo%lvAKqt|w-8Ufc%B)ce_*#8)@xLQW$HSGzKd4Q6+L5pF2lL&nd!nlU7SPS zU=HOJ80PA8p~nV+-Usai2f^eQS0kJ-(m3KnyTMv&oQ%aHS)8>ha$JHGDNv)SOODgMaI#=jtodJ#&a6l2&GZmh7EAJlknC=f>2iHVVH zVPV2A&oBpzk#d4{dj#fKTVvBGG8tKq@I&(%dX_ku|9bA|WFr$LND2NcSBYpo@c0-H@qXX?LRjoo|M zV|M9COZfbjYIY?fBiss(Cw&uM0TW|_WQu&Vn{tj74ylk_|EP_u*a7MBgx&j)^McYB zs=*5Hv!{h~?h8OF=9rOyzatI@Vca(J(n9u5sw$iSdn8M+ws?b+^(ZK9G1ur}0%mWZ~G zX<&z|$9P*@(arn7wm;#*ta-)xdIZV44nFZ136sgM*6oPWJ4^lv365w8pwDaCP zIaJ|K4&P3W4rb@7=CK+m%}?0$hhyM#F=&NtZP%Z|FO_x88l2K=#nx9&o1=fnzZWdN z0st>_r@>BGjricrn+`}RUo}L}s5bf-b2MQSFIz00lVak`szYuE)74a;K+k)Ze~@F^ z7j%u!gFp}Sp^kTjKY-wJpnEjTshon>FavYQiLtpmiQ)8nf}x>^G)uYtHHS`+R0Z5v zdlnWb&N;UgDmn~NRgN9k!SY%Xs1#gRw}VqRtgZ7hGx`p8dl8l^(P?@jX-)u)IWxPw ziFay85jfd+*bpfXbA9J0A+S#htRkaM%>jWveia@8YX@7CyTO%^HDiMLWziyG3Q@7+ zh^aflw!2H!DI8f36H_fu{Ui8jDqM!{$(pJneX|KS6&EN$yadc-Lou0Okw}skdW3!l>9uYDXWPVtAf5Qy$6Q1J@XVNju0@EHq8F(L)aV z7a2m%FSVvlh!J6JcDOy>Uecv|2yzu1TS>)&t|dV?V=AZ_B-}93YzKm)Sl%YNDu$H+PH89{iItnn#>wER5XKGU>&1PYI z4_98`(6>jrkIJl0jIN#VNmvUwXd31`x?-?2y3ACQ0XCXiJTW?;^xgIBfN#^v=P$=> z@ADTsM)Ov`!M%eDtvh=!la91IFY;Uc-wU3)yAE@%ZubhT{&~opBa#rl1iVz!F`N7I z+o(Npxiq7iqupq2ol!pb8{jp}{)*oupY>to?(llr#K7+9=d|icbyeW%g;fAB9X8W@ z0-%WiJHONS{empOgsA>@NuynSdilR@Qy%jBa#?4B3VV4?qV(brKt?$GYNq;U>HO+N z;KmvQ&IZHW%Z7tVqISaziV2E^gL>;2tOk9AuP(+PP$ zf&SQUpunq%8^uiZRCW2>2FzQQvr!Z;zrfh9UuHh3v2mvPU(^5Z6f3g{$0<%AaIoLC z)!ibt(ciZAX+|uyo?1=#!2Y$4+NH0uwlH>GSR=($uSOB@!l%J_Qv)apH=*j(-{IIU z+G0KBa@97q;hnKL2u+X0r}jb^#^^SPp|VjU{duIKD;e956*kpPP%kC}+ubWqQPU^J zWXbtx3C!{>ilxBy$&bV$7(ITw^DtE!ZW$#f7zBeLBTr6nzvMn+3u3B3HHOfzVL-u~XG90&WQlvk@7d{EQksP4``M9a$&=4~>AD z+>tSC3h@ugE#r8lF6@UgtF)_9|HiHevIfdhSX4{VN|8y&pmwZ zHyz}zMje?8)-f@|hk?wE*}1BFMXUOkuK*ODjRXzOgOI-&932iSGNGu~Eeo39JI{dI-ol z0$5D{uKt%U(uT-xlm_1XdzXK?BAl}uOr*aVBBlTB(tlm}Pqj7U!4Ck{=x@LN^JZ4# zf87_|=lb_zdEmZ(kE9pwQHTHUpR)ttCjEQwO32^4Qh~PDF9Nb;WTpU$_TTSlGAWn| z%*S_+nRx=F={5~yAV3uiuyWuJSOu<10)sEp+x4V||4H?ruGwp(1Xk@%w>PSb%MZDB zbuRCqTcOBH{Dl@vT6B{r+9h=RCPQsfu3g`|PNO46fSQ<}I&R0Wy+b{g1B`ch4&5>1 z5nbB&hBvb*vV%^3GGRZ{s!5%kOKR`E_6ccqdJ`xstv)7MaAt1*rt%35s$wh*B!?G! zyz+i3sFJo5Z$-_pGo^G0N*23Md`Zw-Xo=bWLy(0341(6fFWpl_Rke*L5r9nu1r0r5 z7}6F;#Ko5s*1RidKX~U{O4ri9nH7<6D5!%gFg60N=&h0c+6U|`5xw~0#P9>@yumJb zUZK3oV4qSJr*g>W2|M>5wknn5@r5O^-9i{m9?c$)|3yC-#yM$})SU+2)>Lax0-*%B zQLEw0OEsw5+sp(s6)&M}QHPw#MpZrDo-37uaiu@D&<=kr7m=BLV~X4h(s~6% zZ!#6?0|=?nBv% zstMpf&+jBFHe9n= z{`{R4;=hkgRP4QXZ%+~L&1|+_)Ga9n)S%&0NX_ej4;8|8K3hp3`TQ#h%2#oo4-@-! z)}$Epgv{p$Mt`)L+o~$)*5H2n@^0XetsRMp+~&_VR5*xZ+8J(?fIm*D6L_kdvp=#$BVcz zNt;>ewgXR@ALhm39K~4fRqAoHfZTcfT1F6%yA*ij@3bP$0Jd>jpI^io3Fk7`;y-KY zQA-ni2i?II=St3w0spAgEUJ77m2pz8-8*Ogq;ooN798^uX;`qsUscdeU0XH!ZE~zH z%_?0}fYsG^AL^~C@frS1ncjik63e8i+(t_sd8aHCed;Pj2|a%F>#=D?YjZ;xstCC^ z4`l>aj#c`hm`bvO-L|<^+$NI& z{uAZ08HIjuTq|F!w9`M@i z&Xx5i99FCY#DdjG-=$<$m);fti%G|pBkUrjA_=>ZQ~fJSDx>I~5ceq0&{#zlUzjt0 zg8)*21VuSbDq?t=DZ(R&Se9jx$a!TR?R5-dP=ss-r)0>q@Yo@13iPiJ! z!5whFEU1oEqD`O=JpV%qNN1gWa-so<@x#6KEa0Shqg2L-NQbBM?^mdgKVaDP0A-{` zFYsisR5?-W0|oI=Z)N6@9N_VouRvWpZO8tQ)btSC17V%fpS*GduMI=oGZ!j*Gb z74EdHd*sS)b&Y?LO%~pVkDg{)Q(>kTbt#YY*eq{xXOZ2N#fCLm1aFy}_A$QM%OZBl zkH&I}zG5jV2Y-f$NQkkez>sWY9=a9wF%1l+e?w^ciWqqQ$K+rb~UGxqF3D%Q14@27? z1^7g)ErYG8SyF1E&4M)qg1V2?xU!vqfs^*^aGyOw4skA1A--Bz zm+Y-T_zn6@%2l3BP!W)1;$FG4_Lbfe5NEKJ4c4NEzaoA>BQ)Wj8QacGJT%> z=yNy=t*FF<1lfYFaD{Wwet{Nn0$36uSa?3d)9KEhovTsN{(YvpEfsV%2%|6KZdbrF zc^UmcO#7)1n#V+9d`zy7u{w8bgCO^-!>93UCt#j>a(+vZ=V_qm_E^#7t1lrga?bmjF@D7j%w#r zn&Qp#VA(jEj=St-F9iek@`OUZ7D)I*N*iAauuNRLpXL=ya({RjFg$=^VSxrlNE&m# zK*aIh^JyMWa^*ek3j43azg@BCiWiK15kb(Tcd z-e?eDsPIMm)mcCiIZ!o%Z&puJTTdmfBJK?1KLHxnLGJ$lzWCQU?~lxX#!9_e-3gx! z`$oEG?S${YuOa?<$~tq~D+OgI-c>7q|2F$`6W>bjx5_c=^&A7RhR6zF`~lPN=?XsW zPDAN@D_{kF%y=$T{uy=q&$o=*e{vG_r(tl2sg48fj3$E9NLcBz#uopfLo8K$Ku}Ku zmK^jE&B-J`mD?>^IiJd=1(2nBF=FKx?LBxYV`?J!TfaQVUcT^1b#(c?wYx()K;;^- z2m_y>3MEIrx|i83NluKBPt@P(jzgRkq5C~LqY)wdwKFGiwBuOY(WkS5H~O)b zi3%@#xLwAmETQ0Fskw36{PK%>-ad zor3&1^F6Kt$fN4E?`=3^9(nD6nUb3VcM443b4n3Q+dFq$G9>u5tpYrl-LTL6IJy1t zV+LV{rl*=sRRGiVFK^Eh)jkZ)!TeamGKA99cPo59f@zSlRJm_|EQe-LCQ>aH{gAdV zK=08#805kDF)({mc3i;Do3fdnf`$bvZPe~!qhfDLDkmF!7boul^}&2GKb-}6pYO+u zlrVj7RNX4^LiaJHYbnsy`3YM-8aaU=`ik8dSoz)CC1ko=OLnXz=YognW^}7#pD4f{ z8(~c+f&0HdpKZ~r6Wb3g4MsPld{|83PrF5g;xaH7$O=WX$mk5-o9%@v6obLlMDbBA z!o)mYXj#odGQ~$b?h=Tf&BpE^e;(|(IPoIu>7EtKksk&PdD*64u$&jeUO0aH!<~Bh zvS4M0y$pg~$wC)Pc_xRxPn5eHu=_|()U;%>-U9djs+BEzCZRTeo%R7ijW4VopuQ923Jx_PTfYm6HYP9P}=ItJoyyyzOfM|GcC#p7xf&8G?^;P-Q zo^8SCL^S<7Ac@wXc#LYd*!}f7O8>h1S!lLgR&I+HgF4FaU)WD}ZX=oitk!-;OY)v& zVfd<+xB3M9pgWj*@sS#oXm1t&wSN0;#(}V|Zm9o)sBPP{6sq@vQH{P{;4eOjk0i|& zi`iNY4YOi@C?+lSmQ+1H@%K;ZXkGl?*WU-Woc5g|Yc=sLVLqdP)WGO!Vb9o_U>FM~ zJMpnJFD|Mhdf@aOerdsCC)c@e%OWpOHc4Hu?9Y}k34yo_(UbYfBe;aU}f=Vx5s~Q3vraC}{U>A@qzg{I~ z2=IsE2Zw~#)97dCt^>}Pk+KlreW(Jb(}CHrVp`%_5uqE%sNZB>a1uCcWgZi-O9v1X z=yNTRA8k6oxiU9g58THmRAD2P{-2|?;YsaZnFCZJIHmsRbrk`F!S#w8ssgAVB7emP z^nv?sNDLUv%f+t0K&^aQKO_4CZJxW{!xGGT3w3Jv;Tz}(qm{WqfM&gF#5Rv{`C{~& zKGq>w)>2Eln6}seScxlY0V??G#w$v)4}SlLDU)|W1JvR2Jf)g*X6&{PNT@5M&;Aoj zl1r{Th;iH`kc{onawD%UV?nNdF{+kF5~Xi*&dw2^shQfVlYHuZ*-9Pad)z1_oxEJK zR{g3kPT&bhlo3J(GK-%jq|)3q^joAK41bYw%={q`Z9AW=SH5%x$&0#2so(m3lE=%L zaBLR%G;YN?kIN)pKX`P!x!mBlaf|+#?F*UB>GB{Mymy2yg-w~*l4zy*WC~Hv*v7hz zDaH$>5s(g_1QXR3%fc2IS0OUc@spw&Di(7~!+v7h9uM)x1)^_4x`{U_hCAG3TT8$R z`v_VA^Rjl&9j3xS4q7p8Sf@XfM6F7B4Hl9LD!n?Kq_hhW6LW9tz|T0=d@#|bYdl@@ z4jp%S8KG&loTR@1TN%E9LzHdKj!U>A>L2wh%}YipmaUj+ZlkDeX?Wo$)PL0?Asi;` z*(zEaohUOXBiQCZs@*F0SEyy~^YVoa$k1+>+qsUWh1;6%|Ft9T5rDx}=>6657~aX; zp|>IOVr7F0S{Dh~sG+isCw7P}z>)x(WypbP!-D^4nEb`u$T@RrlQY#Dk|2Z9z*T;0 z1vCx%81)o;d33q;ua?W-H#aakvff-G@HCsv0dg&hS5=00+EfCvBUVOh7dB{4j{pN1 z;)>b|OcVYm@#YnL&JE8?XGAt&n(4%x|8U3Jm(>+1-|0+mtqsrxe!HGqw{G^_v{{q7 z3|LQF0bv^1%O&nVn6%3BkaJ;H^$UM3ByR17;q!vl@{uXLVc@ucWzor_>$3!4TwKZ> z-3GLkSLcDUAQGLe=~Kyr-{xUB))~KXpep*H5YeO_X&yZ&*$3cQy>N zoy?H>zisUL3liCH+UmgW^XjWJhRc0vVZsfXy^Tg=dod` zg;F=)4zK_BB24+V4d>v7*>?jS@A98M{TBTFpW1d#dc~vvy0oW=&k5-g2^(#oW`%Yl|h59lo4geVif`?i07F<1po-5TBirUKvT) z62Jzoz|5toRS(z=ZutmEy=2am&o}Dewhu#KGGlrQ zT%*^*kG6rBP2S)8!`2c$uB#xSj(yB(yqr-1F?)X62Rl#!dGPtkB5wCOvR-*G8#n|U3gmWnSM*M+;&r@b@ zJx@sleyZp10H{GcdTre^u$CGWieW_dGk|DJyg|^@ZZyj-=cOu#nyks5! zhE|gdwH2~_;*rDzzcZ2I27N!JM;Q~s=(sU_{OJnB!H=YtEgolfMRhH(Ku)meqZb6& zti~uzq(|hJYk`{`Zg1+#4Va2MwL=wIB9E=4Lp;)JN$x!lnXbp~O|;#q&ZEnx9&n15 zOX*q-7f}el7lNlR8R)~>CyfXVwi&l z(bAx-XBq)+s~~1!5Tr9_54Rd8kdy?+l`esFB@PG_Vz;oi(GEpSP?vOm#dz-@ZHXdn z0ziH3{cMRN>?h=xZQsxPSR=<2jRoH?Fg|&fK$Q<|s+}gl(N2=^J_e{T+CkK=Q2WEy zXECZ4n`fqu7%1O^-RAM)2*@;6h>Y7pW*howi{giOi+2n?`PtKAcHODq&s*$`DC!_j z>1bi5XeEb_>p^OQ5F+>|x1^9Ajn0Q9r6(5hj+c{A&y0xYWq{rP91FJ$QS0}L(I%?M z<=C4uc8=wjla)9(l7(kJaqeBxd!YfxOX<`M8wzDmZrSH(v z=#t#U()^Si_(Sam>mx~|n@WGfUn&1^$9k)=X~O$D`JN+FNIR|JIo{@%ne`02D`ImK zyz}H>y=S%YVF`axo)@d4@i;7AR`KJ7rGC3{TBe9qWCWfrnlvNUW-yT`PON1vXlv-4dVD!U^2v4t^|EC}f>0Cb47$6eP4 zGhs*jt93YSz&&MY;9v^ea+%2qyWp8ZvrH`#ehgxEV8od$--GX>Po)AZ0ZF__OFOh! zhufW#dI?FgKX|O|#;p{ZJ{>&oMt$KXo_|J;Q~6yr<3OH-OvcKN{owi=EFsN_j(VrM zXSzW=e@yh3YlB)KF#QscY-7rH+!xs+Ymumb{!GGP^mmF#L`gH)C)J+(3(mQ5946H@aS}VL!rD(A^lZhdPjwW4SGy zadR3#Up9n1?U&UI*YJP;X&34MF&sXohw_2rFCi*&axaUR_4iD)5>sf}iI!yF+qm!T z+WN@^OC~BvAO|Tx$)TaZ+K_3&v_s9U&E>boxKoKO`bTLKeK6FEr~rBdFMuDgmDG*Tv zqOxa2$x=Hk!vf(uy(ZP)O|+xe$U1dH_SwGK&CiIU)q-vSjX71?M^;PS1D&@zxR1QbA_p3AUTuTq zJ-7`*FyvtSqLI#_Hm76-5zj?=CmGN}kc0r5S!2`&qmXYv(#S)6W`AdQX7qjS7OkdO zdbD(|&*PpO9_mJ*J*n2ViuKuyOU*(L;Dq0mw!_QuU4r6@X2p;-DY|l7iucygykSZp zDLM(*)SR;2o0#4rntaq&=u~*P?1=|sUQm@^%99>c?iWu`tL>AIir_TIM3E=rIe#Mh ze;ll0X4jeAXt3@3nc$p9Lbop?E*E7`S@2I65iU?b&%-VS=OG7|7kpoTNac%LbF+TA zPWPqL4cz=L6e+ziD6A%|?dN8~X)q{Q!B?b5bx|7mSzcK9kC;|}I?tEkx2hFy4(Xy6iJHV+ z5uO#SizaL1r>1Wr4s=*A#1IsM*Brl}rty*7lvQuVmWTz~R1OSUt z0I(<(0*g}N=rut3uWY8CvzG9uRHd{vXQTdlp?|w<=|vY4IH+I!u3x{F@GOjPwJYow z$4e!B%!n#*%onspw;kB#dOc)tUij>)pryRx9c#w{p?A$E=mrqsh1_61&63o)?4t`` z_VJou)$b0#o*>HYryFwZhd#F^%g@*wL!DRSt+?Iz{p5yGtBi&A&CMs;BhjUgDc`y8 z_gR{yca1ZJfe2e~&)+#^eWkp&KDqou=Yb?7c%oib207RCN!NQ#y!T37!J0|r=23E^ z9D{e=eE8R3-}oLvTR)b-$N9}b4Uw{HyTQ+vd%i(53>`{gOb<3Or%>?845Q~$SvgG<8T zL57Lonz7Jph%9-t-lbFYGt2KlU=qoEGT(QbhpS4{B@fd>tV7U+I}(cnh591{QT@Bv z052(zjuQ-(hgTHWPW#3nMfDt`2Nv$rO^s^Yl*a{G(bCYUH@rZBWLZ|oe1Z_D!zs#K z4@bv=3O9>rOfW1vgLB^@m!XOioZVzQd*)QUcb zZ}z;9Cxp~1&x0A_==dQ~7Z+#BW;=Verx}FT-#r6#({_o>@!oW%V)#l<`QFv%^GB#? z(#1NdGeCISU$_h+c>JVr+gO;OAV9T!rR&^n`>K6S_UD!u zmrahm@lL@1_^@t}t#$!^&Mp8X9!z#wjr0fHgyR6J>#Eni8s13ADZLT`Fh&kOKOQx~ zBn~*Tmp{+zalQg#m4Mgh60YmAs@3m2V8;d@zF`7DFBx>f$2Uy4zIUyZ?AXgMKDN62 zfGJA6D$TjRylT{Xkxl=ygDIl-;dP2x{dFUL$H6%(_~zFa69A>gi%9R~so6K{j1E!Y zE>=p8!vlaW;UplziQElTtoCVl1_&nyfh6Vh__@-e)vLU3R@&#E?W=5fG(!DtaAYrXPGk_fN`YkUM)kL^}NJyC4i+^Cs(f@ zue4^Mi^?=2fdvI)w8o5BB|lJlFfeI*txYzs-yHPNGcZ(oc1Su6Ob?+SnSgs5GETH# z1+{Eh5TkI$2nXBZ{ESqH&lb5tttR@)bd{tjQ1m|X(XCKN1PciPX2em*$Pt>&@IsM_ z#?@cYUP*aDJv(*FP*5?xDsyDPq+p?mZMIGJA`X#G#U0(|SV8{MjK_$9eEL>{f-fq{ zWfGq_RsJ}lsCrg9(n%CN7d#w>ty;K>eHzWe&RXBYfw<7E$9pvqaZbo)H7j4$bSD)@}}-IYX4&>tYzN+bDmu<4-ybTE(h>22a7iCU#XCA-Sy^ z!|u?lIIYPC8^O#vnT=2;Fai;NwQ7In3y{l@UP2cu7`E&5;d;SkWND^SJRl4zXnOoJ z038IC&s~y=06d!PWgFCZ&<4lH=%dlIuy$-X02^EZYD)EDlfYj-8m&o9wNJgIWFg+H zR<&q7Hw#jFM~L01+6v_zHL45n?rN3#`K^9Qj{E#iyvy+-@9)pT3SXWW)vpTxlE}JR zBNLY7z7O~S^$M)wvrou*e_y|Pz-O;_Rm#on@Qf{(dzbya;01@J=0I#axav9*W?A8PRn_MpS0Z4>V zI+nhGH$Sa(wcmCj5%BVdnp*)^J;ZzcU4Seiv;H(@W^N34Ju}4Q>a=|0x(w~ggJ7@t z_>iR&xz7Jnb7Cw^;_gdn+sAFpp_LpZ6@6~n0eC%eO>YAkDE*%c;CTmnvyP0!t@q)7 zD2}rmflhw-CyV>P6vv03ECMshKZ)7@r6`lxfJ%06e9r%ee*Nq7-|p#{{SW>6PiKqQ zy9a#p7JAi}G5$=yP`uV&IJ!A-*WY*!PVQS`bXddZtA)zg;-!&@kK>LeSrpL%2FUO0 z&epQWkUxXRPuYHdix^E1U36ULKSs3fS{_yJTMVjFX<7cmPAr+VIz|8fG{NRAWKD(I z7qCr*YOSIW{LKM$H~Rk?p?jov$9vzAAP+-HkPiP+PImp-bs#K}ETXj5B4+hWQj-|h z=D=B*`-$;X>~H@3Ja~3lbn3)9Xhv$NSD;udem{EvG&rSNeSN`TcrBGT4#?&Xmu88j zQWvXzVBadf@kN0*=Fs<7I)oRgBIY)u=@}h+hwH3&^1@g3r@?GVlHh2*Lg8TGL~Fu< z@`N?{ia$SKvBiq06GQwvCSqwV$7{tW@LnJpGAKoKx82|pFuhtbeF_N7?9z;D=D>63 zT1qg8OF}q$hp2fDj45RStU{oB3c6!h5ibGtnXYvTo#?hKF_`47e~)n5 z;jTm6Di4Z4W!U7%m`6y?yNwSSjz-<+e(K9Ry+JrEoFLZ-R!*tb zFBET365m0_WUsG<8$n^94@~_X{4&3*z|>?8+oMdi`p9TdVb(rn>Nv+e=U9<%g@tm zZMr4`ZX?KmbKe3&hbkC5EuJ@4*1i_fwpe2up-UAcCPWg|uA3`vyD;QQoQsgoy? zqmC$YZm#2hQMuY;{=-n=D2+R)X)9{;4V}SF=Sa`^iy-?p`DMHF3@0 zEyq4D$9=x?N@-hk4)|D;P`qAC#6=Zxqsen;?+BBWHH;cZbZ&Usg(}jAR z5Kmdtnp`LpYcmK;I$<}JiQyOmcGmZHyDB5rLjB10uU*J`);{ho>nBZ3}ekf9|3 z0;6Nmx}ey$3CoEXA+MHuPd7cy;k_ZcpbTN}3oC9^2{imk(}sy^G~=9uN12!ULU?bG zwmk}TFNV_VjtWyGsa4Mf-B!3$pZA*F-`ubdB)ThKx)$%!?IvAxJue6HGW0=1sK@p6h&os_7s#J#)R+oBF0uIS zYtOu!Iu&&G9s8lLpvF>~Lil5GS@&;ut?2wNHY&-KY@*OBgTmLbL@)%9^% zFK=@~2xID{#yjcB#H4Ll3?WVVBfg~A;LA^G z2@}H_XUyzWMH|0Og^|JcPU6!dwnf77;jy~+&wNmjZkO(5p>FBRY1GIQaA`0K(l@(; zmZOQ4R-Q1(dYGrsN$Ya*zWJwC-v`K%))-(0v(jQ8(93Xgg0;IHCcfAvfG1G+~nO3$UyamMhYWL<|^RUr;IZGuADQ{Tl_-toebfYF8su&q#B-BYz%x8r{eDm<0% z3SOiqCK{I2Fzyv(=N2W8v_XHv&cs1qgRhN)gxTH;b{8hM4XPh&6e;L>*3;9(uI!}r zSle8Atr&+#>MjrS@J!>(tjNs2afBIh}ZP=1UsmJHt$gc;fM~eVV3Tu$oP) z?T7{0S*Oo??+yR;Wjc=^wceFt*r;Qp-MAJK?3Ju>P2D)Vs@airtz+VHX}fPT`^?MC z8r$=LoWb^~WhrpUIqsE^@&`%B2UA2wKYMuJ_`!T{A-=aWfDWMaXfvp9StKTsvRtb2vHm{ES~t6$4y@yR;?<*-3ZQGH#1Q^%i&pM}uK6kO2SnK4TR z_^jV;wv~NT4>p7QO%|SP67U-pS8@ZiH7_|S&OlDdYAHKfFivb$hp5EfGN1BZYx#ryA>S3F`t)Oq0Utn^W{~403zw8m4+*8%)kPrGc5rUh zBDpo;_yxp0>;WoGjv%Eq`3Tk5AXTFoBy6R z3(-{pl(|OTzt!ykM$sm~j0ZfV|Lm2JV0~qWblG}za4Y*)%0|{j`5HG_;p+w5UR!== z5FW7Xd1r&yx1A_EQ<{OF!CwYy)6^c!ZDe0;yvo3TU2l*O-&vSRh~WQ|BAK{YS^$Vm zgqnk-ywNC_mFFQ}Ugk{1x(j3JxZ%nCuZ002^1AvS4<wI4-pS+amTK z&<$(*KiGTscqaeZYu9!?w>_Va!~MXH2dOrJ z%aj*hK5u@sv{IV92@E|w9)vs{yA8fo&=$j+D&7nx?O?y=>&%!z<=+iYqIfxxC*#GN z3vXxfq@Xv+d*8nsz0&VsX!RWIB;We^DO2hYQn_qUa_cAWuek|$;Lo7F#d0tE76?~q z)?8+fct80p-7q6L4vwZ)VS$%)VgxKBI8a5WDCe?x5^Zpg<Cn5GcF>jg z1?ZEpMVhkd{b46D*t*{IzT$6J`^;x2@oXV$*|sF4%+Gw;KP3os!x()KDjiB&j`G*X z_&={-zBARe=f^UY=f%%1@Stx5loJ%`zt8W#&*)I-f4#&LkonU4w>^IAXFFNyXTBV- zey?Rsf1*gg{f$%aJ^wq1ZwqV>jzGRxTue;fAWMeF!Y->^w!D#arvQ?1>(4}see&S_@sCM1GVM9T zG!M(~dyTW+neq}a2E8t3mxU>t33Y~RhUE=D46njPAByg8{_aj%t)cR){C1OEpgo$p zV9pMM6c|<8UMf!MnS25}rl6djk$nH?sS_5M(ltepb#N3z{al8@MBlL{+Mv&hXw;t8 zD^5CaL%5=~S@K4C*;cJx801Msl{1A{g(G6+k}m|laJAB##+0E`*ML_DzzVVXjGcsN zhX65Xucc{UYxVy1?JUbPXe|{C-94m0iqLJ;eRpDN@CdN??hQ1!ejRFc62(|Rhc4Av zz}bs@a5mUKA~Cxvwkuf6mKTN`joQeQPCTX^52#@|9iakJxmk$mnlIZS*?|Fgl zlqv#@y^g6?O7Q)NFu+r`M97jGGH{XWDK0sm^vHEaq-1lDBzMfGbF&IjI&^z#Wp~oA zu26A}oa<^U&*g5_SZiyejU_nO5qpnb0Fg+aK>4iO1NP9ve#xMH2k5NBDr6Z{b`cFK zUW|=c@*HB5pI1=C$xpk8S>UcyIPvThP69r_0{>DsTA2kJvrOSY4X!C1=zZeqJ6ib~ zlrMW7T0xmtIJbNhWAJc~&++AhXyJg>I?S%jA2JBUvMmgleJ-yQpa;(1-yYac{x3kta#wlzXS<9k z`EB{1UH>xz{~3Y*7b8$6n~yYQHF+$sP0apdGxtC3<^FyDKQ?p!cQ$kQK_NW}2 zb^dX)T}P7sv2!_o2qwc9x&@=gOb4n?JookLP^$>rbDKRP^_M+A;QBDDi*V<$rRvUEt(EEsE{$ zYX5B-PQ>;uT6XY8!Kaw_{PBA^eP+7Kui??RrNp;8cAwh27X%T5viWFo6WMTBdpQY)KP7(^Je)*|LLg`}7|B3_6uNLPwFW*)^9Z-HEoQ~)y zBIcZl=vZK|+TS#ag16e9o_PThwf}SxcYI}0=ods;!cPMse!9JOrkXI-au5zBJwCbic-nQk5}XRw%r%yfnj zyGrr+i&gAVl~p;RgL{TL6vs%c$y#OC{@#nZ;^L)rnt;{%o^Ylm#g)BUJDB5g9uvpk zhInm0*^t7YgSvzid@aviG&$Y)s!`in_D+m$ofs~)WQ|rxr+L)-O0iIMi>bkB^d*0w zb}0S+M*vmx$S$vOlrp%*gSbTkf!uMB#zWdch4{;p4#=;7lG5WqjZaU@196|I(m@#; zTmm#bb($lqd~r1qP_S+>D*2VIvrs<{m^T`%N`2@x#NwV45mWrk8EO!rp==a7f73*3 zecJJ6LXZil5qgRp7lp6T7VvR%uwjn!P^No)25n^v9y7-tatLmAi?pfV8N#0z*lxTOmq4n=Z{=iBH_?Q3rR-=(_wWM zb|@A0hj;As;~WmsS9>dvD`S>$@t>pV8Kp`Txt7Z`E*`Gik-$_x4VitX88&=iz3A(m z&oG9mmB(3`9e|yM+>Lvy>%+sJjGh~`K&J5An)>isodzT$!E+U(#qsl{d}E^448|sD z236(ti?bT%`uyP6jdwvYc(u#vZGZw-74mg#J$KM5SEn*TK^WGZjqRy=;r0ereS+q% z3py$-gh>Wk=TIRFu2~Uw4G!w;tI?k!@vq!xkZ(6|4xmN)<0M~rCc*k3koQ_o)aOIl zRP`|vBC>)$y@hY(>Ut7lAb~4aD!MuCOSwMAQ0A;vh_}{*vR)hH0>w{46d+@vZe0_s zDs^v}*H#wP(5C(Zx|x-w*{Al(bfGPbJH2o3d(S3~!aK5e&%Rc1^A;;nxb^+gdmJUR$M!0}fE zHvp(eAQ*1U23aD3xPOU3n6Xn&iv<#k$B`wymJNNuxNl}alquIg7U?u`fZ=ZCY?WQP}zioM>CbA zL%j#joK^H#lkRr_e(^**$tgV-*VQw7G)=UnOhe29Yp#9>Xk%|cAR8Pg+z zYZ+Q4#mt<6Ys0$vI6j#I3lN{mXnw8zRyW_3uoknA?5~*oUO9n+n5@-wgC~Zzq;0^= z!3gSnjejyqNE=c0!>&PLh@svicJhisXeQ^7pc;x91Py66K-(Dml%Eb$U|X2;z@1^` zis%%Ge$FD|Ta=wnrgH1qTQiReL0Ca|TtuH9v8Un))wJ$AFZQG4#Gj7JA3SRl>dl6! zvH5cYmekv<4&un4sXw{k3~Zk~Q5+4i-%L~fXto-0p&5X=v_gW67Xi5bac|d#Ce@>= z2ztf1Bp|+AZ?Gz>Bj5P#S$3xaU(S$Y;u)dsG$M2~C?-zH0CEX{jiVBv4cTf3tl}Hx z8|(HEX!p1~JvSw&XW^^4P=|!{rW$B`ZYd5*;oPj+i}K6>mA;)9HO#32#UGR~-5k24 zy;&W~#8f@}fh%UZAwUaL%@%}SFe_W%2%9XRL>%(+6sU;wx#%Z$O09&kr5=KnbBo!1 z7WxPf`~C^zAkqo6*;w(cUx~182U80s(fIPY6ZRY0donsu|2l|s4JieVJf?SJFd}rT z0K-gc@#y2ur)Yen@mHpkv#`UFkcW$WcTJ}51F{c$0aOh<%5bdvr(?sucEW3m(=3pd zjCmSm3<)@P^xX|q@|T0d(Y^12-GAGgJ>Nl}0?3}nBN3oAh-Z=h%82GH*J9?H;}Gtu zr1}zx70s}CKWBD--Gd^e6Z`^34*h;L`Wj@JVM|=CNe@s%B*$kioj3X{&f|^=gM`R54XcIOiS+s?E?E(-WRfHj;p_ z-uEYMVY&Jn6x^G5wVrL-TEyhBkNDEb8Z!4qe(HmDKn$+R>;Q&9g{xadDZG32=R0F> zF`*3SEb32qx5*n3U}os}hm>D0$7X>Yy@70EL!t>dP??v$Gy$(i_>T_I>Ux&MVVgG0 z?DW!sj>s-Thk;WyM;>euul=WOn=tyk>yN=I{<8;e8LJ8YXZ;)Q zIwnrmTE&0XF7d78O(ncuHyi~yfTVdtTPw`eAg&DCV#C&Esg{4++|)(wS07$4{54=v zWOLQ$>~Q?=8Z)35T_Y`5v9Lz2NObkR$Q5Y9RpM|Q=H6W;&Qjl~TuHH5Def}F ziD_YRYZazeSmfx1Pei68zM4!}zBWZQNHEtkJJGH(ApecKv#f9?*e>1S@ z#mvnIgHsq;*p(hlE3nZb=a+cJn1|G2vC~muY`Hh*hCtn=efY!+jRmzZo7cdqqPLqS z`XGZ3y)p{2iz`zW_L&tiR=zlMQ32o!ut~Fgy}j}a_~D?Vg19+7(z!((D9{>3o~Lb) zd8hBF0MmR?a9@2yiQ=!BYySKPdY4r`?ZbK}Ek-YMz1};0b1p>Q53yX>De$cXR8NqR3o1 zHxGCRw2v<{_`?L`+G{~7!MX7Wwk4u5#aHh3)YjRFxs9>x{55LZzER?6(h!^(fn-Nb z@7RHOUyw#O$v9Ax(5oFt)#`jvurq|PJ*CHn-4LHZT94PdKmZ&dRi3gr?!HDMg9q`} z{0;8|s%pHVBV@U!8gGMZyfhej=Yn0%HAf5{QHHCP3`cuwWQj9wL^dMj%Xx32Y zobU3M7S#)ng3-3R|E##5U2opa#pc|c$;GSw=A!*(yW>w$Sbv)RX`enF9mR=_@6q`$ zV8*06L|((0GY6Xr=E$0x#};Eo9}%1VHZy0tSbw_Tfy(-YuJv}!`Il2}7E|yO&MBTB z&boRd7E#q3Qo|)y({4%ve#chS6ckzrjD5I7ZQ?fljOw9ngsmqe3RevAjlQ>CS9_Dh zabh~2Q5jGWi9R$y+uZuF$>x*P`nb(>;S7q^x_GDw%41scWo_rTQssk`n09aFg6oG5 zslrlYF!cbdIf&g%A zb!ypeLOE$C9c(TJcC0T0MadytfMGhQqZvf&l+N?*TDzr3Z|s7UFtTn&Z=6bvweyq= z>_7=H$i-9$%e3_ij^VKclQ+i|x=OD;obPD8F%l!=8!MiDTXo8wb_|tR>-o zaWIVztf9kJpizuN@`4tH*i^^$R+HUy<-KzpzNr=pvj1$6)o@g9%tTiV%o{3^xj5m@ zLDt3{A1nC+{;+OeQnOgE8W`wIXE+hI{i0DK%Up*}u?H2|3yb9E>;5%z%>|d&2wCl& zug>xajiSIO`Zuqq?x^1sg0qxi+YT+OUhnQ99*^THW00l5#Qj~5s0tmz-6!(fj}CZxz-PYw%2?{ zmVCioJiY<0rso-E!Ih~jo(}YB_8g-r-?NA^R$7{uq}f{x%25ZUfv!eM@h|kge^mlx z6hMXf`|=pXt`9PqD=x`rt%CSHbL#R--ha=f8I|)n#}l&BI3r7EV5=vjnW=g^ zPD7&CY|naQnko(t{2?ww-gSJCYL~g*Q&zNZRUjt(Jo#!;$m>y(2*uPw@8UhjzU>1hmbXd_b4k#8G z&d?*gH^LC26gsiqL{g)5{-z#*L)tej$Ro5geFkf?fsa$9P)isdh!ebYAz7F00ZXIF zjB9=B+EYE*0BM{X{w50}Io8TzxUp>{H%X)2Ajo}k4^6ugKe){>+9ph^B3-AwJ^kigfK`pe$yEQJfV=M0DUIX3RojdhcL#AHP8m?y)iTYbYA`Qb!VBB8) zOds4xtj#7?g@_>GAK0mj!!tQ4gZ zWI|YXoS`;jailW+2Yk_Dy&{b)cR6@l(t*^$#DWCbI}}((nC!Gn2wW8>(I_Eolc&!> z=MS0a5r~*MRgHOPF3#Z|pSkN0HDy67YSUvv?!xEeP$_0X{znuzX$4y;sxzD>8x~<^ z7&B5j=EZT>2`_1A>mtknufv72sic{`+(;}49$8wzkV~K&LJhZPVI|hw!)|~=rp_91 ztBKFDUh*u-R0>j$s(Xj4^wo zH9$0lLuR#3$!y<62Z7%(*B<6f0-f>Na?anHvB{ioEmWlO^*|}NeTrVblMQ+3LrUyT zeN0uI=Requsm*cF%Id0?so^(QM%e2IS=j`i2NBz%=w3J}Vh$S6M!3XDsmSFz1#5^; zaR`QyJv-XcWvCT36v1Aw`M!m>wY)7UGv!0$PK9QF)5=z9_eXk2ozogixRSmOhQ!h; zryYY0j&YH5$os0Yt4uHBTNli`&jq;F>t5_`F*kM81U zYhOYHIGhf>%vUXkRDTj!(vkTFi$o@KJSWStwZ88ZEBV}iLJx;ta4WpRz7!fXSxsXg zX}_b8=@9aU{NwueB{RfyE|#>0X(LLPAA|(FC;0uO<(jDTTDh()ASZ#1>+_?+E3 zpQ_+Z()4<+?i^~3ogyvTUqd^>*=e#)>#ac<(}IlCBYfh4Hm70S-kX&X+n5mUF^L^tCVd073eSpfN@`Ov5&^pNq9ENGI*B&6GW~u$k?4d zyf?+EwU>e2Oo8Rrr}@Z5tbDN*6wQ@bZyh=7Q#`=$*yCErZom$c;mK@|=uVBedukd> z9SM+q8Hs8+!*OYMED(%-2t&6iU{n3Z9MR#bwiCY|!b;|&^?O^BFT4@LesTCX;&A-; zi;m8i;xjiAIvw9Y3HX%A)0tibY@}6~3Y@c&bRRLas9mxG*?}fmQAUx~L1HJ81Kn(+ zOdOERrp2q@-u@8Ii4DJyr~#dGv~sI*%iT-5t*cCJ?tdQ^JJ@Q6dp^2FsV zLr7~$r#w<=4k|<~*MQG`z_iSrvtE$N$TZ8+$rmjq&VoSK{ZPIvBt5gXqCS=#16A1a zQy<&hnCS9545pvtsodfdVb0~Gh)>eE!dTG%8f&932lM8Jp$M$>bO@n_aJC1Sv1Z!h zwoytK{jDO{#C7W7j4a25M^?zkp6Jiu+@8OdVGE%E?^e@G8T*d9eZ;0$dD<$sHY655 zAsCVsAkqb*R<;X$Le5JL@(3Z^+-y99o_x{9wXw3>bV*Ki-ONHVDG<%y72Ur8iJ{mc z3nDHzpW6~7>I(y8;UMFmyi!wuIeuxhtVVN-W;dADDYCs$`r};60(NJs5Z>9&P=L31 z-CP@yb&icOr)Gj-O!!Kn6^+*n~9`@uxE z<-&uTS(VB^?)kdd=Z#&XEmyRU@G6$UGolcXd3!ukZ+w+T#yHO*jxuyhV|H5p!scH^ zlS^H@Usn3fN_I{2ejgipG`s8@wbO5QsXl*phWOETtwTImr+@nDjA)9acDit*+fgkn zl-tY)G~Mp&Rjh3&I`XOWdEt(**?}jq1&I|qP`Kj*hTqHn>6)*he8Z1dEQo7k3#Rss zhb+F^(Q)?BpEsSEBTbHa434O1M~ZmWQNCjem#(MAa4Ue_S(-!ODObF`B7W4!uA6u9 zx|WmvPa%7ofnAg*uLeEjF#P)PN&=aG z0Ts0$Y&p!JUK>1dA%y@6j<+REqQDrrR~0$paCae!VTZGW5%Z`r5gqsEHk7XwjjzJG zxajN8KqvEZHOHAds|j3x<+-UcHC4x?~Fyh=841kk%wwFr^u%#wlu%Ld#MR7`a#zTnd>dZQlz`GUiWHoTs{%D^N= z#?kdDpL=(*^ceni?=su|R8_4Y#3hP?o1DiMi8?kp1Ty@xZrW?Hv0$Yg@8`bB8ON@v z_ir#J?n|QpYMQsWGq2s{%v8^AqNRQPhsN;GE@C?ie>3$WMYk(pLQ{vRv48@RzHS9TFzMu%)=#nGxmy8F^&HW5nwT@7d7uQ<*v!TgG#* zdkhy>Z8zqi2&x$?2=20%5Rl)!_z8)g5JPgv7*QqivCUW=04`Edqy_o>jzl;0a|AfZ zdSk>xPn(Dj#fiM%@oIOvwjv{dkB>|*l}hD$70FiY$o=_d_Mjp41s8E*^Po!unXG%9 zno_?*;Pg;}pQtpk(qu!qZ)*~`tdt;mPu$ggTS+smq{ohTU4Kf{xQ2nlzpp`NcXmrO z^UZW=C)yd?m^#{crdJPTp`RUx7!EqOk(emN5V${QWWDqc+*3aaR*uhJ3?oBnDPiUj zrgbYo%eC}^lwu4p1$*%UH9T@j?I3)Mi@8PE##GOE8S}x_RrIPQb@r6Z4o+Jbh9phr z%we+4T?B~0CB6y1bsjXOHZjto=0c1DA14qpwA^Stq5&IH_h-fdn2?utZ2{dnV+ScC zki$@~H5xOLA1O5C3`uCF%v6F8+qX1(KLtWf|7p!QR$|;d2jlC)4{Tp4)#BPmxE8-M zP=KWkLaOjb=;Bi-p*)xg(KaYTW?)fKx!sslC^>#@;R+O+{-&5zdAqQyeGS-$tL8aK zc(Xa59u?LvI`3gQ>@a`BnmQ)0*9V!%bT|_(o*n52CpA#t*?-q;^9ocpzL*y}xc^&h zkon%+2Of^vH}fBS&k>z@8-asVl4_x%Kqi@@OoI@+os!olE`3x36QI7rM_i29oDhec zPKMS&7DT5q!B|ilAPPL5=YiUhO$`(Uy3VP^%!$pr&6%6k9u9BW!>>0dMasv&%G@=; zcS<(G2mnj)Pmo9g;G&is+xp8~=sW*0q9 zdF@bz8EdW(4}#!FX737AAH9k-M@;9-olY$9{st}2ol(&(cYF}9mO*PK{It3*=e={+ zD*0{2&BUZI7~4WY79#QuUjR4M>2pXHVZFZ&)4Xbq!x5eNu!c9HA<-yM89+ud$r+g9AkQk8Q1UjJfU7ftpeA|YR8U20z zjRg^-gmaRj+kDMjsLf9DIMD@7gzho$3%WAf72-Ae zg^^j``m}5Lf}W!PdSkzH8qv^c$ALP8JATb}#0iG&zwov3_cJ-IeU- zp*?2gFfwH&J)WZE>RjfiEJWD=KDOrwpbNGCCW@vTiZG(I=%JaY7Ne^>V+i9blJ<^) zep5nq+7p_b4HOh-d-9u#uv6F!M_SD!klb`$X39iCk9%sm>iS1)aMI$(Ho>@h=@1o# zpV$r-CkbP8mA(K2*BG!l&Wx}+Us)Q5Me`m2uyc}u9AeX9Y?kH3J0WE{Tol~KyH0Jk z;h2N?2yo(&I$U@rZsaslMh51`@~EQ~yQGCLo=LRl6qYBb4%Rf!P}(8X8m%;x7S?}} zP^QA?$2wU5WApm*?%=aUh4-ORR`9LBqxRu-bo<%s)aj3S38iQGiw|m}90Lb$=d$?3 z^v680oY(|>gT_C3tO{(`RtQ(F16kv}0G)6q_|xpcUxsr0N ztPgJo{hjp~VW_QzH11|{mPvvUNn*8wq?hYL`{-c9mU)B+BX?S^x(%RvxfaSt(qDWX zbt1!eGz@HMZ7BpGbm^h<|H zpI)v^xKKQFxmgRczm1UUpTcX_7z8pqGwLjkKquM)T}j3kI+4(+i>Qt_hV?6aG+XKu zJT&(WWfD|mWIx!0qSORdvpVv8UBVF{3SGBK28?RjNTR5@KU%9X;#&J3pzNC+Y#E;c zt|`S#`^4!WLs(DTN|Jm5KPC_l3W12!F^iI~EB1c6j7P(h}6Kr(qF^>-W7fH;acG-@PuOg!v=Efa;(Z=3Y~)RU_yWp4Jd--? zVu@$-sQ=$)~Q86Hf_DWWK( zFAh7=d6Q)Yf|Nn!#Gj|*oQCpiKJEH$DGDXb&S57$WgbRn^M(pcV()ThHhEhH@V)H^5hw}XJRL>| zlP@)h-p5Dbpvkwff5J0+yMKFmqK6clNmg2n)lmH>FcLjs@&?mZ2bS-;7h&lWiB0>V z638`#j;Ne_3+QQ9Wf9mo1s(Y&z2*nOX7N_dxefwPBs&J?@ee79YAQDP?&U%VB%E@_ zB+cX5m9HY9@4&8u#S06TybxR~^5VJ+q)CxZgcxw$xZVU4TV?Ei^j-GX{{HnaFTqR6lHEzW6@^^uv z?ntF|q}~y39LTBePaH7Z5q^D*xz0%0KZzsb&SMRzik(%G?`4xX^)qgOqTGeCpoNiq z!f^&pWX5!2X7TiS&xSIDn2_}`n9!jNXAHygGl|s>hR7TDo_Vk^E|sUa$xO@QO@zwu z9C^yRpOrt}@8rbyC-J7`<7cc8nA5sRT6zdND1N~!mFNh2!T&J7VHGG$f@feYjq~0X zEEI={F+{)-ma-=A955T>2)HrI}7_INkf5TdPMn`ykn<8u4%#PfUrPP9 zROl=RM)SEu!Oo{W^D!`2KcaoE1FTVFL`9(JEzlEh?p1(46(UybakOpwCB7)>zo$V6^QqDnR37u3=WLEwp;_34mBdLp1M` zjlY=Q5M_RCKPboxt2(BY2}_CO4^rN0mP|vTsgt$1Q%X8Tj~I?4NxAWlV|TyeGykli zCZJD7tzYkG0xiih*aIFO6a4C}!FW~l`z;nKD~_2_wcg2C@+085gzsgt!cSho)A;I% zaU+hv0k*aCr7?oks9SnwGx;L`-9or(@KvDB8$QnvDBSZv37j72Z|~J#k?60-%(a4& z)~h9w458VZlCCMx<#WhcYLM7dry(Rq8lGXR1tBZ!B`mI7D?04XRm^BiY0Ael6cgg& z|A4u^Y&xBJWVXa0maaEhV?Mc2!oIJV`6(Y%5}mB^P@pv&w z+7OG=Rf>&dtcOA|y(6l5DCYf2mdk{5C}F(k!;2FMgi<)FmHTN9rGHa^dANuI;V5yL z?!utTN2@(lk5p>8#sK`pH=RF1${zCE`d|~kvuTF1z?EPgLm?^uWwe0wQxpT1JGHa) z$LHF))bzKS{O@$1qtocOh%JvHYnUhFtvBz6o$mx>TV4dbYbrK4N!6;`RK-=Kow~>N zMm7_0OmCS)8Mb~AXhh^$q!78<5|rZMtJ5|&L7W-lcS~-X{!jFRLR-0;y_H|P$vPH2 zG2#$RnOW-%p(|@aTnW;>OfV!=g{Y-{BT0BEXY&4>&Z67e3FwdFPgZvu5RQWh=j98Zd6m)H@)EN7gCD6%*xU$-2K>yileH?HLu+Ac6ht!UJo z?g|_q*>osKnncFD^MZY)fSSqUM@h3(uuuvt!1kq6{rWT8gYSF}8Yk|CpAsCIIyk?c zRaf!CN^cz1+&8w!o5YQYx89V#A%3$Q5Tz%+Ui0I7>pr!0wEfKoR#QHU#hdhj6Q|{# zie;9@ei8}$oBRd~s16sb#j`Hbwe}!Wi&C|C|9ouune)bxN> z$XhSM_PGXb$=@`T4(v5b{s`>9$vy%V9rco{&AO*jwdH$}vDkd0A$FB5>=dn`Qqfbs z1p3OYE-c0nkD1e@Wkn)sQFcBibkOruAK2a6=9*Z{Xk392G-?SfR^wepu4IbWF95N9lqdv5&a&eSEvMm4Ulj$E}ob>gvmQXUgc@Gr+oueno` z&t4h}m&$z>bh;LpDD9NW3uj9NEV)k5Zs=UV3|JIusymYS5dd}fTIaufmYUGQJleq| ziAL-qUO(SOETTHZE{bR8|4j3`8bHsko-Wb(!=7 z_y70h|L=Kkn9?Xme;GUV7j25ItX^PC^r_TJp>mB}?B?7QqLB6wzc?C!Aex@pI3JhUF~TUMj9N}j%~a;} z&ezvW%_!hNCdTNp0uu3E=#MeKS?;cw(Mp5L>By^jIo+c0hO@U9h8jOocMf$v=P}Mq%~pgH@gd+1WeWE%nno@!uXjGKv}5esqEqmTI#A>epKR+tmd|TBGgp~9 zhjCwP?Y`C_vT?qlWSX*Z8f5wA&FcZ(`V2=v{Mm*1 zv2~TL)4ZSI4N`|V(`75aE@D2|kow%wMRY#jwfDAUH)ZMZ*$?+v;GX4q|C^yPLv}!TrqdSXuGr;=VAfM2Fefms!Qs!HPbU6@0P;XF5?#y;D4~9X~FU zwA%1Qdv9n;Txu+R3^z8RD*hxbd@1cZB3X$l0um+kc|c} zi_{ zVbT!wtV(BOKxAPogOjADs`#|oD!VEG! z1|gvg8>CK6yi%lW>K?0}e=)WMo$4#OY+I-?CDR+qHh$Oeqvz9|D;%$T8|uf)}#TLfdimTxKQv$;hmxyWaljrpuH-g z6lZidM(u=mPUtmfRV8J_3O5?m212N^*|A$b_3q~dnUXciM`a`K`Xje}>w7lTFLjj% zY=CoIn!L^ar43h#81`Fa05}2w2;~4=fdCQUuVRi*0L-NN_>4g7qxvuZupuL_`k*(p zT};!`?wvFW@yoe){#DGcyT%jAZ%Bcw*Vrg&8q9~^HZOtwIi+v0bR}7@Oure?@cbC_ zjZHVnIZw63+9ROquZjbj+{Dw(OjQ_q8fWqY8JuxiyUEHBmVD{Zy zBW-aao`5{Vgk_+A!#{~bCtL>!ddXSAUKY%)HA{wBW-Y)9X9D0#rvc8#ew5vh!`U?1 zK%M{Dsj-+lxx5`QBAUux19C0 zJ3#$iw{5$!F1kXx}rfg4A$?0#F(hgvLd? z6}-6Kvwh=haF@T|`E&08k^GvgcM7*%dz*09O!fL!018;Y(g#)m4YuLaWh(;)G_PK! zm<}A0ik%S7$i*|X>|@8P=$^SYKt1@PW`GgApH>`^gs>F>I5X`swLz41RdV3|i}hd4 z-a51d+sJV51mvi}0VKHh3Ln>fug;Tp80x&1<7^dryq`cT9`v$Forr7>{~?XwoJeHC zEuF8ywmz%faY$8}N&N;dNCx^2JbOOyu~y!RpjTsLRJcZ7?!@Iv8X^5E_3i6@CchpM zoP1Tw^q*Hdl>1!d5@h6x^CE+aj_oA?`g+F+E25}m1!H-HN_j;>JXw+_wYSnqnPCG2 z_?pLx&{O2P(bZUaK$k60)a*M;(qLn+?dF@;2COp!-)=4dN3!kJC-hI;g9_ zGOpQNY*mJGw_iUgbL~amQZOOQnGg8FAV%y>I3@nB-qWvUtfUVLRv?C z2TdvQHM+1Q%j@Sk%|}W2*t;@(c92X!c&H~~71G@S6*My)4{U3`J7*N`z52?M#kOj$ z#uaA({Lp~ikfoaGONt93rQgiFs?dmw8Bt4#KroE6I;kca=dw&VTb_oBRDX!}4KYS( zN@CaS&hriJyAi682$l027Tk`#fbJdcib_de`sW=DB3xAF`>Vn`QXl<`sr1Q;Vhald z1q{@|S820uI+jDq#iP5K$7G`8Oe-2d9NF~E_~i70v|`%B!z(i_L6%ae$}&r-kQfB! zyNW;-d0k8OrQ=JthjwmXjSwAWFU|Hw5#gE-2!_z_AUWwJ;;0*&j7`76eCbG-nC{pCa!2zC4^z&sH1YYboc^%1A^7~hjL^Y%ugRtYNXArwPt2=?zOawks}WKySJEag8t_k_lXLBh))$vbCL7s-!>T1;n7!FM z_oc;EHhs+qq+Pg(h{B5w95_|Cf7_UEmBhM=X#rNk3G}o_NJ~KQ&0_N#q4J5K$g5V5cgJC)|G=-B0V(^<AvsjaA9O>79A z!qIRgxiN|fk%-&!h_9+fwPpvdlx;9T%4Zk=01e^nH`NRosH0_?*v~mGo?4?}{B>&pR@niE%1D z6NBzsx3p?d?b`n{_<^>5SbC9DIVF{ zNA~^I=Znu3pRWkp#oB+CHtSAUaq${{gFse2z3MW4kh2qvGGxG+dIJz5vZ%}z{WJz= zeO<^uVa>Tr6UcAKE5428a~c4uxEiHqtIBW9eezkiT5`PbpYuO{W4JQj&oPS;ncL(; zpNOGXyZzkGKR6rC>W3M_xrpq7fbKi1`koD%9vf7FaI>bA_aIq|$vYtlS?k9q+n@M2 zZMaNB%4r?)7#n$g6%N60?OYUOH`dC@Zw#=zUMv&(aT47iG=K@n0N{vk{{2G#PRagz2Hy3p1SMeI zXUW%X>s5`~4^g&U9_-cbb-Da3^|g||fN+_agwy?$t$NEqIwvO%Aek-^?yIT#`R$Po zE=3+a?X=q<9M+Pk9C48i5J-T=PK-l}{<`Ck_tjbZIa%Q1Qp`@x=Ur=-II}hy+~98A zyM3odBRGDtoeG1vew9@d0O>g@9*hscH${*K^wm5nS<-@f4R}If#5L&V=lSg;-F4k9z4oa zFE0lX&XJumdMCULQ8Pvsb70>oIJP+=czRydEM= zA7^@psW`G@H0IZ#+-jUselo4p3r3?}Dqdlw-??Md9L^#vFijH}M8qQOQ-=4`r+iHF zvkSQ|-H!GsKKZ?x2r}1lLFQU)`F-V!I!TiaMa4H<6hPXVBW9VkHm@<>*9LNK;aXOa z9lL3B$c7qX^SNI+J)+MRWWS2QUlf9FYq`gYMD0Z4U!vOEWulrd0#vkaoxwhEvr^I0 zix4XBVb>DVL6#cBbWXlj%0bXBd<8pDKIj8PRthC3Fo1&Vjw;u?xWxadoT(^?EkEU` zU)Z=!3MN*R+?|!jG0vhdT8^K!n?O3+4Dw)ff4Ks1AHkQUUFGU{UJneArdI|oQV?^= zo>C$Qo^~vNueVAkJ?yMJ1&rJKjWaGsYmoHmSLl~wneKC`@vI>QLvn(p-i|Df!thboEr_oDJ4m0Pk z{l{C9GO?6ewG_%VZM1q+kXd}7xo-(*)MD7Gd6>Vi&Tbo$WS6g<-h8_1Jwr7Rrku@d=6lt?t_4k}=`2IhY-$Fg}~(tZ@o z*Qda2e!1oMtc$p=aTEBn_DjE{8U_M{P5XzVTWg zgIp<2n)W*V<^Qnv?(t0j|NsAUn;B*%=bW}mQqm|Q8gr-=l}d65lMX^CLQb2rl0!$L zGNhx!6e?jusD#nMkW-SC$()zN_I>E}dB5MU_xtz$UA~v^|DQj)*w{wTJ@+yY>l-p)6is?b{KpAQ?*H=Ev?! zGnYA#o!A};wX;a0gNKG>(ze;|c{k4FBSVjw7e3O*p|gG+EL7IGibIn{8kHo(6zVST z-_!6mc^VEx{1 zEI%DuKc&IW395q~Huj|YWsDk~lHe_m`ZDQ z44NI#Hgm-22!9>9H^1aMAf~805m}}^c~NJ!9-9v6q19A#rmbf9seEe+RZP>iA-P-? z-&0!$S!4&8`tt@<hJ*hM1aJ@_En4`^yg8J-PTE2i95IPWzo+CM{+i} z8o)~i5YWRc1_|1^K}2`b7K`3}Sxy#D6+zKTogaMVH-U64daoXuNF z87)k=dHrow6y8$dX#XJ7XhXtLa4--ZVvtNYbbka-TE0wl3bvXJMHOQ$@fVr z6k*uj`A;@6Z}H0vWjvu8R#d#)59g(p{JAy=Z5a0LTZu$S7wz~+Ne^gz5-1P0Bo=$R z&$T!H?Xq%ls%m}x-Ie9&b_$rOE7y6Fe;;?DOw^=YE+bRcALpUm7=x8K2iKU}(xMVC z*3F_=Cu&q?+@k#&fP=H2e10jTG2FG7*-0iyo&@M2w{zAEuvTm*5tRGbh0}U;Nn)G( zFjDsJGX~f!pa{ZRg>T_lYBW#U6pmt&_+mI1VgMf@f;FtiVc?HtviNE%`~n`R>{`0i zKnfEV4?bGgkDSJ`aD6vo@_1v|yC9gNbKLGoy@QwdB?g%x!PZw#jV)mdCD>zfyJuRp zMa)zCc}3Y0(KPk?a7;B}(A8^QuI3|M(~LnE*aZ>f9fbRJkEJ=fuHCxCQ2gh%B4UsW zD1)4d0459;r7pnMJcj+~Sk$5GBj~0z-5ce3Bgb}!6DBA1z>+1QMv2?H_U?@?X`fo_ zyCQd$XAQm^?5mroNl3#}^A{9V=ueZ^@_UvLZ2L@Q)Tfx~OzcoZoq{sX{A5AeW6XVr zM|gq?U0)t+S$dFe@)%ieGf`d*RmX78e1d-Hw)<9TziT1|du4Sgef|p(EaxgFXsJch zOj9WIMLiDcgL{{*8=&N!-dBg12Sq8pS+zM$-8Brxb(i$wY>+j$Aw&4X?LE0&s0y$> zvbj-iIm75G^|<8bHr=)l;2aRhj<1GW0SYi1k|ppJt4(6N_+He z_Ush+Tfky>J0fyRPpBorr(6nUqSV|!qmACDcS(PW)+&xC*){TFo7b%nzq(bmvWc!< z#Aq%d^xV%&xX~fltFFfJjy!TeJj!UFG)5!IS~A7Ie(g;57u7e^!s1813JJKY=375K z@-s}(%r1eMK15Tg^Zq@@Zb;7NtZ-2b?p>dGoe*2!srF*gn5@i?ihK6`i%tTFmKAWIxk*Wr#f#R5! zzV}L+#qF-Bw=p{a?M976?5CNCa@X#dMGwRl&}NC$Pw~RWd34rIUkqyP=DU}o&p7ku zJV~=~Rpmr#Vm8sVxsflr&;#(3&$X-O0kpAzW}ne}eDc8bRX`E4ackwvxC?gjoIjLc z%U)$=byIy^jGa__fxs{;;Q^l7(3AVK_E^_uzF*>}c$>V81R~U5%(PYTNUOhN;JO5# z>ZC0o!H1Uc=Gv(p(p(Eg8H)6O@yjd1Kfsge!(pJ``EhwT^A- z&`mppaa2su_eHUOG7I%xVIws~DmNSh{4^t*c2`OOl5uUC!OHYKuGKj~~C)jB=gjQ4u*%9X3IDl${?$r>}3V*OHFBa3ir!GY%2-M4s{y7&6F_@W{m4Bhyv>^zF5&f?7@ijYHt~MK4UNx5pu! zHZQ*&KAvn;wn7o*56buTJ4CJy1>@6eJz1!dT*sQN4S>t*;>^$QjWW!qYgd{P4b1UsQngWrt`O&Z z-yarlg9g|eZ+df6&ZL&JSK3mhoyHjQja17*&_GPnXKFPg<&`MYkGi0u?yY;X=`>1U z=yCIgF(%E|OY$o`o_%}4y}maEMnChp$zzTEr!|);jjd}@>6Vszp%DlZ{rOYhMXiT5 z5Nl1uRtDi6$hHPj?O!R)fZ|h&R zG!xjm;+(}Z*k4SRxCC|lWiZ0K*4hWGW|1PL;`bVx2>Ctw@NfI5zu9z!Vvm%#Xo^aH z;48qIq$2^Kq+K0%H@i1G0paf_O%mbTh)qKSe+Fwy{Pj) zfZs= z^MGAVN99&WvpOt4f#%s)?5!c@_%Mk=wsQFb09W-Ds2t?H=tCLym9-~^eC?&;sIO3# zC#kk9WrT3L#!xpN(IZ(Whj8~81@v1r!V>WK)LixC`E%_C|3cq}3ItobnDDbOj;}JB zrT#&#z8jPxe}FfVTWn`;P?aCkcH{xe)?uAi!aijt8v{O5=H%e94=S+7hy?0~`!2{1 zP-taZ=9rLu0g$Dy!59kDpIs4`E@&wZA~ifl0>+h9DAhE+YxqZCb0f1QNJE-e)=P{TZH*-<)@U&!qpHepS-DSZ8tv)T}>TY7|wmTZ%wo9$N11R zoh=7feCt1Y9kmGJ*?v{?TD_ICIyh#1!Fp=_#bNU^^;=3uhm}B|gV~_^XeW_!y%XfO zt;4vNbT7`#gW%I1km0st=}=xEpmM?%jw%PFT+Df|!CX2^Qt>>~CDP zb9Sb`4GqkKDJ}V(D=3j6ox{(*jo-})H|uNtL$0+iX7We>gIl`|!5y^J)Yl{$R4(C$ z!XJRB4oi=D+iKe7r$-Zi)wfUW(dGZgdmMM*V#_dBWW=n(s=lK~0q(1>DSA zPP%UDvx)}lw__A(TdxWe!(h|lqZ~>-FsJ_qod$YO;Y)1Xgq&gl{}SNQ0Qr#=)lfuO z+*0%48rGwx2>A0!U9|7Xe4Rhf&sP1GWK=f7GAepU{@^8jeT}m7Ea9MF+J6g5$>e;G@bWjAt9N^i zDao{ioiRABn_(wS-*pfpZLF~bn0`E-Ndm?-JN zVE~!E2auTwfXuX-Xo{D66Z^HUE+NcAclC7jLPFWrMr@psd%nLj;L9RYVAEVqv0pRu_KQ2P&ji*dKSl<2E?B&bD4U$@ z_+4E>q-=XKsGj(Y9U)2qdazTvH(jO~l7VtG6+|a!;r7L}u8JCcI*SHen0MkIcI>)@ zvD;Zx{ZiU>ML>>Km+;aohtHw>c|vkHj2lAsSpXe0OCqr}l~fQ3r^8i`rpe^doeL0& z0ID2xHf6qn6v|J46g#toBD#uv6jg{6JQ?;V5GGbGLt*po^)8^Q`*cqs9@qr8s;sp9 zpqAs{mNV$8vY85bp&q-{3RX@d99-c$sGPw_wP=D18%wM5ZOzRXSU|A!jU%`lG$cOe zy$4P*Z0UY))cMkdpgSS9iABdd@}Jo-C71C3tP)G&oB3+gA6H_n_(^+V~nsQn}s2Cte@=Q zQ^5A`3t5=kjp!0GpbxWITiT=l26_>xb{!n$;}GUz7^h}y>@4ctG3J0e)$f(9*TWiT z!~p3Vi+-eK|Eb|;K&W@~<@hY4um&h_-+RYocD5aoJeMWBMrH(|>bB3k16JJ0tYL$Z zl3|0X&g-4UN;D#J8Sio&0lyi<{`s09uewVHW3Uz_oSb@j^Z&%TCdH1B%-k{U^FqAm zK>isIJLP%f^oRLhYq>J4AHQqeRMH8V*&+;xlj*-3LauQ83}g*oKj!}rvU|JhZ# z(k#!Ysb2ZeKBk*`Ek_nPm~2MYp_C-(i?)%CmAQ(GhWHOZD`)dBlo`8iqH-1%Z*D@m zg|$cVy?ZOcx=)mR`EARtB3J;H?-mb`En!_SV%3dU5ZZEESEe-6>{G(cWPW&&RBYGG zU$jp^I#SdkXv{nHA^l*Z=Av@mb2-IsnsIYA%-yh%q z)~v1q_X^=j;WDsXB9lV7{VO{+r$ouq0i5P#jdkw;%q5QO zGyNzun8{VfVTvqVqt2!cW~nH;=YDUm%0lxjbu+B11l$^AlybT>swhLScjMxW|L+Sw zEo}*lGwVxc-hR(D`4KsNzMm`zd*d@xZ(0lDtLHe2Te!-9c@TvOm52W_C<-w_QU(12 z&Su>P{IhGUXm_a~l=~09=L3$&D}oOg06S;J6*FijWhSDS&LUJde^3Q`ZAFl0jk%jX zz$YT37jVg(h_7&g${5Y3i;~?AIuBMV994O?R3aHHeIEIa`m3rQ2fzrhX~!ZU+%;QN za0$;pWq1fBxcWiHN6Wx8=tQEStE{De==R8 z{#}~Bn71;HczrEP6C67v%Cg_vTG1+_t_+)Rsdck#*1i5C+>*R)J=Fg4$2Uce;-EVz z4Ds2^cg3(zU73)s%$^m87!oYo`O!VKl)F#n-F&APX9VrPPXUfGFA z!NP-sb4Jg!*)wXw$^ngV-(c9_k4kd7u(48c>f`rx&@#Aep)?hOuh!t8Zz~Qwy6cZ1 z`QaMn@4bP4Je4uqN`R>H^RkH&D5!~%aT&m2iLbg}6Lz~(h~95;sQj@zv$R%okE8xb z9g+GCLZkK-Vqjo&?o051v9`T7{UA!+uyZ?+V~e7zmoK;{E~V|H+DjJtF1Xk3tKWAP z_E_%t;jyMT6PW%(4E20I*q3~VH%81$)mXX%W=i)Oie|9*y+(%n3WW#(xD@j*TF8XpW1w`n8G}J@x zQ{ke&N8kAXv=b<+|Kr&sCF@Jp5zi?DzzRq!X0BXX$%fAnPXK8&fI2>B2*Lmckn#8X zWt}|jDZogn(&V!+UXP}cgUu54?q_sy<%7xFdPy3_zSAz6@QEsr2cdpI!mzaGM$BR? zPDD*vYAX__o*jeP3u0`(hfkY`HjbV)AIf)H){u$7RID2S%qeW)m0K~_IY0H<1w*hy*i+Y!<;oo%j!;ynOs{{&Ti z2Ti9>1(o-KuDD^UQxCia?|lH(q&z>I3Yw900M}$_9-&t$1DGcAEa>U9UCRLlX)pDm z!P0#K2xt88cwOg-ZdJhPH0}nPCR!*tuPitP#I>zn zN9>#Gw{##t9C5T~LgCiszFm9J&IT|_cb{cua&ZPIE5d*ggLoXA%)ndG=7a${T1>bL z=!1J5xXdd(Rqp<8kDwgYW^V=QS5LXimz_mcry+?V6@+wk781iEOQb^C7{atTZx>f& zDqw`ZRzUOp`f{h2*^pn;y~JnJ;O}T;VOc9uA}f4n^Jd)>XHkrsS#%Y^z{l{OlM7vr zIcqqB=v32`ttZckwt0k`kr*l#FZAehC(~Rn*;^|L)N^V27oy9&est0pjbLewB^I5` zxz^dG>mGxl?is<*DpAN6Rv9}^4EE5XK6s6m+_Yw-&~Zl4p^@O~KUC3s_1tM+EKQMD z`1MG;rZ4By{W9^JqBWogc%dXuu(AXA3x1DIjt}11M0l{cB`fQlPX-0!rCw7fP;MXf zI0t^96Y&c4{)X^AgI>Ym{_#_QA?kbi=ifAF_TAA*+ykt1fdWL3$!Vg_${SQQRQ(ELKGWx|nbfW{46C`CWbF^Ka#k6qJGmeBPw?a8E#mkFo-J1JD} zKDNnrRIsizoWF#81EJV8gsu^3T9w`tT<)e$n_(^nb2h4$qsD6jxfX%wJYn`LvlV00 zF!2z&SsT#1>fF7{9978dwuxwj3n6A7; zEnOBCd7>S^9K1|dcK}=Af0!OFBfc;a;*4@4*4JN`9i0Q4>Qq}_NpAS)j#7$>Wersy zwQJOWH>kJCzQ~_7^$RDw-{r&?Zou~9r;Rny%K*>Bs-+uq95*o?FSU8+I~5N#Bu?|Y zmHU`WEZlNzn+k~t*d5>br&hHF-4kVoTvzO=Wo!q=4-O+qpN8}xr_*ew3AQ_LjvS6(xK@*bIND>@ z8_li%vNMK8be)i&DXHQ*{odxtPni>#JYz4QC%S(Yb^*BLo%%D6{JJmFVQR20_DZYr zE=v6?96K_+8!Y%n-U&9lc4=BPp<(`>+g|UNn_hh-00B{h@a@S&ew)2$odrure>>RG ziY@_o_zV}E7g24b=_k*-E{xJ@MEcHfu+6{t-XV^dgqp-fg83Sv3F1=>2erg{60yJ3 zbg=adBqZb?HOOV#fZgUI*zc_9UqoQ7pDWc31MbBwVAgyP^FIL2v4McKa~9)Q8E+3S zeL4>~iy%|=arZt(Qm6Y@{pW%`!WG5B*S=t0BdTg}Y*$f9TRqXclV`-sFdzMEnrI;; z5D$Ra-?Fwn1~4|rzjW@-gl53NOhf>J=3h#88spC_u|3KS@Hf{0n)4mR87%(CL)H;b zDlY{t&nEwR%T#Y)xW|jTAMf^iU{6> zdvpq@gr<~7@78#_27+%~TK)+jVN!hVGW39dNDpk{|MgIy5=sHx_;y{Gis3qe_UzRekco?C_Bn}DtI|MutW z-;k=kZ|e=g#g3r408;)&#{#E1dqAK?1{lNlfUb5K8ep=uqGGQ{BrNPxsq@F;IYHMs&G0~lXV5^=6h%mPVZ!Q=n2YPka@G!pF-+cvB%e1V@%LwLEcV8ky!KOC z{j5+_@V7pof7}GeyIu{Je$)Nv{lwiMhX)e4DdoQ18Ac2SfnEJQiDb3^Eqd!uDoGtr^|@Sur&Xc+M6|t?$=VG zCV}dRrk-?lH~}nsO=BCT>XQ4Qn88a*VPgA4s|&HhfQx6?DnBTr57^S0q5fa8q9AGu zN$eyP^Ev^)!Gk__{>;7jhR|&Wx#!a7LPfqO?Yoy8?k*gFu4E>?3w$JM@i}ck@8clq zc-$v@{6Gz*xkVBld*b=WDBnC!@iOqCOi>Eh(wn>sz<*(GAZumyuTIUVf7^P zNYc`RsCXd$+U@(R{FzS|+Dv$qA8f3d^US= z-7K1HFdy`t76I*({CpeZv=ciQ)XC%(LS%U*SEgG*)lN zkBb{Z8~11Kk|eFcCT<`K*)^O%W<2cQ%i_--6*;75oSX9-h)=Ol3$32+3 znWPW_-}=GI;+oL;%V)@GMQ##sINkDO<$rXNiXGSjI#0g~B17#R{ep_!Z@?n=4@txu;%GyNZVEqvQUL8wRGh!RYV_D_ z6Bk4Ecy?UNiU5gR*4=gTrc8EYmDLj_Gz%e{9wQGve$2WNfqQNZ=_*~$@qi$_#Y4nL z)dsE6xSLa=O0}eoG|+J82r(hNChT+l$tzxYcva9oejpvH7Cjiy`5JyDX*J}ODTYSm zbfn?R(l1^bm|%XMcNN~36&PW0%1p=uk48$TS6nh=R6R%>fMV5a58FGR)7-gB4O+_T zbG`p|ROI`$O@Y(!t8s(BYY+M^P30_uc-eULpFpOhOF3-XhddIA93L9EJ{PV(lJg#nv7I2O-)K(t^it zwq;y^V2}zK5`$6EE`7+CZU}RRdIBBc%*@o#gQQ-!uz=vJtk>{VH|BKfWS>lx z`p0PYhtB#-q13}gvDLi1y8lHj+1pm^JrEjBBPRbS8H?~IpPfuw($H7=k|qYNN!o zg3q)1BQ`3M(m(L=lNZ;eLmR)+HgGgDg()Bx5_%~jiri~<-SP=+O!$`tsW$2-wL%=8 z2V^p8Hv7^_{-}@an8C{G59Q@!+Cxv3=9UyC9yO?`X^bycyOEN1jHOz0GE+E^|&Wu7Ga=5^ToW=M35^U zDkh!?;_8~v^&=&!75Td3S5m@|JA4|a!P@J*>Q^-p2Ad#T%I1cqu9z;&Qx5}yRR1qv z(m?&^&uvqe*L>?Io}D~0Q?EY|#*QS^f5v`&Z}_z)+ZV+T3xM4wzl5Xqf~4!q^$r_g zoQzPo1oe0gDl;rPa}{6ea`7#cbgTUvql9z!HywGmZOU}dLu>2+ZD6OzW|(j8#M6@> zu;*hLAE?$xIj4i2m8iP)vIbtT4Z*Pb$6OB~VRY_!>hmpDZ@f5RRq9V-sjz`OLFT7g zCd4~Hv7Op`G4Q!XzVSH>Y!fX@t>o&bxrzCrnRn^k@7>yxCWdHw=nVYN=g6X@n%*kX zmZRzDi@W1|!v$GZSG5$WlW*pJZi9Wx4*^7hJ zr&}#Hj_E}Tty8_y%es)q(OXM|w2hw&4)(5hc3b~}c(+N&5%zT@#k~HU!G|kt>5$k= zS{V()?sUsh;_r}jP=%t=gD0!boNc?S$zQe6&EfNrff?#X2h#du#4thpv#0WVejbh- z5IF~p2J7oko#c*R+>sldOI>{>F80t>pH=tA_-9HPXP1f`mu_e5>lTiOqJ06ot9Dre zrkToh8cd57m5PIdF7VB%7s;CVb-8TVWzB==s6!tIAj^+b8N1WC%3{=E$1QdDL=G@7 z%jk?2Avz8w!hdXsZBw=mkV~x78j(g`A)i!6>PqKEzw-9vS|5By6$5Nf$@XT9X6#DE z_qlW#C9i1izQBU@Mhn6z2);c5l0*yg-HLsj#k$HhLPMRaquUl|m1-NKceeDml%d=);T9apG)INUXtuAaVHr@iw`ga63T+=s@U zyKZ7FCH30#8XU|&s%x6{B)l+Gb4mYjEl7bM^|{Ed#4dURn-hQhmQpVHH5z{yo}>$B zG4U`DI8{#!A?>ow=VhctaJn<*x`8lwOZtc`QRpiq+cBoZjf$+-(bjrlBP!zOr^h%Vw+^b$9FO^E@+> zjMnPQ^5SdI?<8D09m#0W zO1kuQzi^577;(#5=CI;p!k%t3j$-3Nb?Nc_2zwLXj%G^a>eEL_)QzGLbs!W!utrsX ztCHXI=avK96?ydA(=QrFm?J2bd^j=$ zOca&gY+oaL0zDe%sk|ijdk>^LHe?a?(7ig@YOdz~M- zYA4q<6W16*bzH%cb%!T(Z$+Bz5Ox1W@f3Kb4m|UV??J!EOfivNb`tjLALVF!@-iSi z)B6Xa?Z{NDz1!h`KLy}Y1sA#F*^{j2d^OjQTX!(@WuQOC!}iCGTmj2Ab!X#)h}}ZI zf9>WJJ|w>eIVR>8AUAVH{)JSMw^G6<+1a$;FXleBG}YYqp@fCrS^Dt}iwsslnA z<(0d*G7VSm(Jr!ocCDOz_{DGI>(A7u4nlBfirN(Rc1n?*f7_Ge0xHj97u zFd$P?4F!_O_vb&~mXuly*2)jnO+Up`$gS8fk|nmDd}bsuS#GgrQhx24vkJRq>ffsO zZ&V6^J^Fdl=;yh%Bee0|aU48|I)KB^d91z{;*$B67j(LX{Pswk&K5jN{bvi5gxR5g z?t{n_L?v$+1M$H~V%`^M)i({PTR|Q5!gPNoDsDjPrn;l(C7f&(OjdeB{cDH!!A32C z`xgOP2gylozI8W;4>>Y>f@4JT7`OvWt_FmMK?vWPOd-YbwA5i~rNqe=<@{JB&y7ma z(p{n#9fariSBoNEJ0e6#5D9rv))x9aut!FoY$JxCZ?mdZl3_L?&0qN{DRlH1h*UF~ zhX~NzJUz^(XWU)LCkJl5y!!Y)hZYN12&^D(k9gPiUn;N|^u#F%6Ynh_GQ5}`n_fr; ztl7TT6JK3N2KZhW+2|r;i%X-_dn(2bAv17#6MH5!q*UhV)C*Z?NAS?4Gm%81s?`e2 zq11HW)H?Tb^Xn?K?h}^sNUP+G-(NX!59j(qLE>C<>z1Ilu+%@skM_e0aD^S zVHG{Yh9)Ouw?b6M+-Bht&C6;%RSu0fdVrs@Vk|(JBdPVnsLZI%l?Rz0lL+^T{ZO?f*} z5oizFaqb99AplI7sQ@mbVr|g*c#{U1$Dh%bx0~N*>p&7NZvrrIW}eW66h+FVU8h`l zK1v!+LV0v-WPJb?ZND#WQFTDKuoop+K1<0I6Q9b@R2zQm@%rdmFzbdSUGCQVFEUAn zt%=wR5M&37?lk&aP)LsKB2aI7GD6gAo%#5#XZM|hP;`T_Jb_AyeN9GVd%nf@K-|HD z(5%5R@&lz2Wj?<28Qt$BfuRFut#3?^GNe`8uA-;27ZS-2wF!;WSpWWIEgkQ5>fU6s zj#HFC?Z>QQZjqvQbm;!x)GKV>uf+F_f|_Q2$g+K|ayES$ms0v{Ece~o$4eCQnoIvb zq@NkKlV@(Sk{yg{ZSr(c%~wyZUr_9&{`eg2ER>p#YBiT^jfwV>(O&e={qaadmlCW7 z7mBDiP{>|>8{Ik^NAyZL*j?nU&*)3=d!I7mi`-(F#&nzz?Z^!xyx;1oZ(C);G2p6< zn&+`7iNsIWfMz99Tq?featwmx8eE1OIw52E0GQ>vX5AZEZ1olpAU7puXGeL;8>xIS zZI;M&O&fKKKF=#SvIb{Sl=y0}5|L}4uVz93c;EYVlP@Fx;gECRvFKaqimj>VN-;bw z$W?wVxR;rvFlEu;Eiuu7n4qee(@&gbaVad{X|qgHu>2YPev81A(BPKYQ_`~t^Rn+} zQ{X;}e%1ff#eEQI2AamonU6#2vfaCrYf&dtpbGfOod(=xr0+i@aepCwy8l4>s;$_6 zL4Ak&>#LsJ`dASpa!W*eS{AtjNVr5BNQ6H`pNM+Br*|sH&<%*--09Crr@=%zOmOMX zZg)usXbbr;3|5SmH`60#CP84<4?$S_ek=jwUlb3TIeJCva`}ySq5>8)#cJb$D3z__ zRkJ(Pfw^GHAfHlhCkX2um*<7%ueaRLi#j!BNyjJH(ZAO{P=;HrDn=FFt~he-VMki$ zc5xWAg3)xNj5+G}3WULsdm@rV-cP38{C(rHKgef9>JJJVW8ue}6fAb{eoI!eybchr zjS%<*fLbh-t#likG+?Xb`(jAYeZ`)17F208NSg36PVA&xx_)r@*%#AqUhwli3*Lyo zmx$SQ;--9HQa1vE!M~0iC;+d*KkpwaX#)B(VeaDFQn!zr!ehm}Vr^(4>zKl5=}aAA zAI_B|lVFj)?_=;?h`B? zcD=j;C#4N*rt83kDVPaKgL+f^I|hM_{Stjm{NU~$DV|u@i^y|C*wcwN|Bx?Ax%)^Ms2{hyLBU$Q9tdCN7@hFzz1t?K!QV1u53wS(*+X$gSbQAPFy zO1v!Nk~TBt?hz{?h49yOICqz>D?Msy+BB4@=izJqvUq2qTUMWl*E5EwPZbwVec6wg45$QZ8vLV zO!h7)B{UyMdN^tm5G%8nuTad=0k}g5@ty_&aZ`eGGXP5EGOUT|x| z@RKzwu79Jjbpy3iuJsBd_%I6#uh*Ar<|eX=HVF7Rse;6%MaqQkk6Ddl7tEe87P^n- zOV%9BVT|qg&?USV7fQ#&IAW`4+At$Ad;-c&2XO#80H+>du7xWwKWRC3(cjP62vGw} ztxHGe9NlqV^N;LxQL@(?Je+>&*m}RXNR@4XYCV0{SREYaML`4SNILSb-*gP`zxXlV za-;s-B6t$h;CrY{G7fK^j?Cgnn+D4k6mc$DssjW&9gH=0rfBl)W z^yvzHzfzFSB@`FB3%hGwMb{QNuiw;nIpyM{n%dQ=Ko`y7Ow2o}UD(R0=8XKQJ2+uY zqMS#*_K}=L>$vzMRyLCRg$>b6_4pG@YG)tlGXhqD3#N^sa0KZhV32pM4$WK_-7V&k zbWA>T*BxCEYq{ezKRfv?)&X$Sl>-nSokv34bW)IbE*x+7S(91$V7#Wkx=QIZinCL@ z<9YJ zxX@4foFlzWg88!Uag$^BHd%jqH@>)Zn%>I$wV=Y*PabG5^)J>Go;L^@+DT~<;07MY zL2wui9=q?N#~U#i1XDEoSKb(yQ2-^VImpFq;iBs!)hN4TCQN&w@-}BeOWP3v--o5# z{Xg!`YF|atvykF_dw35bvqV8kTlLhZlEGqCcG&m+1t-&p_Ax*6C;bBs6v&Ar%)hWcxOuhDNYw42nzY91QPB2`vCWC{XEhnQ+%!UPTo_eRGQi!Vh_uk=q?Izn?P4fQ02u+=ChChm6n097cu zex=n$sRe_jw1L>%Ss+F4TyS%?4Gq7`c!%HJ=Cn$9_o*f5=Sv)xrWZ6i&wqe~QAVM{-hULki3cH`6)A`zp1ak;+iC$nf?QLH(KW4x5cD@tvT z%W<{SbfMH$_-+uThGV!}ry23*X?NqY?rpZ22Hnc&R{Tbevf%7Epxh+lc>N;ET{91g zll_t#TXx-#xA0@KaAb}1B%-lmc zp}oBy35Fr24S1$M~MR%X0e3!KC15I^K!8ad^T3KjJ#w8&>jSAofEj+JA z(bA{KhUtyX`J!z&l~yki(0H)gdW5YF_kdE@dcKX3iRwQIj@r%6Qk>xyfh zDHeiF1cebC5ap+CId?Bsk=kFJ1zCQO)z08dt*q|qp zu6Jb9?bRX<_WGAm4Qmj$Ro5m@`F+9HW($`>59l2ub-pk=?VGxmY-j4e@(XURa{Mmd zH|*`N3#MOi&(@!vDB-l`FU{0H^7#&ySul zDK|>b5#z4B&QrC`3!@n7dMh)(v`+LH!_a!km-&y^4#M5ES?(8)e?}$s-8R^)dnfA4 z{y?0!;?y&_qYob2x|N^3I*0K4wSUZ;IJzZQ=+dcL-94UGSoos#&=5dp7 zW1Bepe2zU8<&+$ODH?8uZGqeR+lU=NZ*wZMTJc?XcJMjA&d^d`t7Gdh|F(nl2~IKF z@9MFpH3LCbmVJ9~1-|Jqe}|~OHZwnWKuSI)Q-D(Hi}w=Ke`PwbX-@Ew>1q>XT2)jZ z{75%=r~Qza>rU56;-OhF)lc*9UOrT61Zux^=_{OF7|zPH6|MI`{BCGg+)-)?;-kN= zDlet{=8xPb`~0|#WlARZZuLS6mw&l_h2NArnHFCkz{uOT>aqE)E_HF}b(ey@(VK3` zaeJ#rtW~8R%psL2(WFb}^ZrWwtI*y{2ZIkLtxD7UV%40GJ^0ZtH)p5vprQ6aVGvK& zU~XVCDn0K8JTgjG@d=2t2hkm_vSe_W{VvY8XK zx4PeKK6SKOvMza$XILY{aVJ)d_HMO`icBXX`>5doQ%=GF2T_Gn54!{8FV)qpKS_r6 z$fT_^E^;ulAGb2 zl~_wdYZbqZvG|Oi<-wgVDt<8+7S-Y$GZI|HHx&kG;6>jz+zx+9Qrmwhmwc>mXF`ug zNjdi7*7nfotE)Fz_aT*Uwu?HPTjeY`(0uyYl3X&xAuoOTZlrYjMHGE)){|GK_jPt< zMjFfn%)+lqN<7lP7CU(SQCzZqM=0@KmA~Z6haRCPoFtQeo3|3(1@&{9w!^}jCbm0M=q3h<9zmhMhCumN*X9BFS|lnI@wM!zvv~NlK2h72H zkDin35AFBG-o5%IIP#&yJaUYp!r55Xo}0_zEQ9YS1co=`9~k|2@cm-UJ3W5j{U;2X zTi7&?TqWm)wHyXnLjO*^Z~1T3d#`CNp9sJ14P4;)WYa_kq(>fCF=h1EH7-{}v~o8k zVS6!o(=TJnP`G057NWFz0!MOMOBL+S2-J^a@Iz(8&w{qwr5|p-YM2`>Oc|DT8l!ht z2AB_MA%Nc1Ip+3JXZ;80JftE*LG7@c2I@ zJbD}Z4+&qK9-(IhAp!KQ=-~cGbE^Uz?ti!3c4XsR_Tt?Kh2~r=OU1-}*>5|?j*z32 z4+_a4T=h%NQZd354vt|yCPzAa4J1a;Dk_M$tvb%al%GtO6(S1Q+=%OT1dk|*3sywG zrXYrhIu*3c=1170cL)rDbcUvuxTn4YgZjI!R^2o@2v~$q)^(Cjz9MUy1mM+#JF#=uiJd}{faiQsN5w{aC-D)=r z(es;ew&IBi23wbfn+X<8qdjUWSW;Y<6qB_6VoLh;W+Y97qGKOV0epa3NTyELudSyx|(q2h? zw3TjXJJDO+^U}7#pT)4esC}!Du)g`U-tP5<#x(`(X?NBK*GB};oE1R}b`}W-l29>E zbm*MdrQLz$A6FY+a%FTmCS;)0i?YKWFZvPo`jm8-?*H~3eKlXG6ejLBIP)asx5M+( z?~>OYZdPT5d1|VX2c>~HK&wD9qc*b%wo7TG<8@kDe?X9b%^gz07z*>O>g6{nRn%3@ zCy3VyJKHvAF$~x{uJ;xre2p%BdHA-twRq#}-tvsmXWOgxA*QrD``9@hudKp%oEx)~ z(on^VZeN;-IR8zQOg-k);IRx?UF}zY@l=D3 zSLB2;+E%g)uN{4$8GMysFzfKMJ=cEd4X$n*S9{&LiAwICYld1$<6p}4PS$Rx25&o! zUgD@Uc`T_eomYFgJJ^gzo0BG}QG5feMEwQXb3u3BE|KHLE}fH~Xy6_^?G>^q=6FY5Uk|r_mtcUDEs4r{||fb9?taN2mZge*@l@(Ipj1FDGB8e8j}u+ z&L@OTQp%~2G>2`3PNE|z44n^5=-{*=I!K1fbij&CR+i(i!|$a&$NTWT@9THCuHPT` zAFHWtSFybhulMuuc-S5`m_KhQImjC4=l$MjDtT;4DH7VKeiI){X#Ae#)e}?ha3bum zq;k^ukwVDmZ602`Aj%=HOWLrXqAenU))ABrR3MiT-z;v9DXAel;%$q2t|zW05{}mH zy(U!`#1(DY6kBOs(!S69NA<`NIme_%fyQpS1xcANfgXa>24W_-nhzIBPH$2~>`7v3 zFB>;Wc%C7Y(D=bh33#GGv4XI)72tQ6&oO+YAntU4s^6M~AgSIro2MMEAPCI7T}p;K zbNCxL>uI9_#LO{`hHu4|`u4EAg5@Ld!bYi0%_Kgay%V+g!E;bm=ID-&MZu#`y|-cD z+~l3M@>@PS>)40a#JI?ElJ=G5ul14(0x!e#?bV_QS)S42aN%RN}w5x zgBihYG%B96wy}YmsVmPMQ%-JzE-|dovi{i6O^WEDV;iw?DMcKxz>meAehHK&r1f6L z6$(Y#T}1P+)x-~xtcLgckgGlco9IkIyxWx-YmnvWyG5r zpDAi|E*NUBTC^tm9fllbzctY3vVvMU5{LlT*FGdl-WYEOuR#pPy-9s=*O2yiX~dVj z!1abr?xjE)VZFHZ@6re_^`Fv+RZA~CDN-zIlgW~Z2C#|(s4&mxH2C7bfyZQCO5?+OSIjx!$zj`XPvmS%_@IJkl?l8aQEuN*#wByJ3! zcj{BrqI%IiOdh$U0wJA~Fv6PQrI?BoZRg>*W!GfMhe6f2M@y$V zIbQ74nc7RHXP1AIHAEcWupu1@MB*Qzx`Gr7GSAlk`jrL9*H>E z%GXG$O5}hl-*(Ja#sM(QD>hxt1vTNfcgxLrcY;y%>#N(lS#K!!-DJ4P3modU5mxep z*=)oeS2N6X(a&ZH`nrST-72Tra+`o`Xna0Z*v!zn&sSZDUX7`pQq&x)3L`rv2)`9x zcN50Z`C7WxE9hRCo+kmsJZ8Is&Aqv(Tn#R@iPB3VM%dFPwG{5?)WG-}H7IjKC9AtQ z{iAms$Sk3FS58e{B)nKEP|x6XSpcT7>l!fjppL7O!yprjVvDyUVgZJ zk*wL*r?-m$3T>mkUEY>N3dW_don74QTLUf>LhVcv?{rNbf16~cXxB}r^8EZUU+t1N z6qpPIei@wy75V4^Ubz!VP&q4P$v#^D7GNoeuPuu2N+zVzJ8tvi|LT&+oOVfopX;A4 z36tMk68h6F2?Y-fiDQhZQwPQa8L-Y_bJ&sATys$vrLgK|XNYpfNrt_n%;+S22f)9o zwH}5(z>`TyA-h$^f{0$`Jc@?@fSXK#t9%oLf5SJ#-?ai_*N=gbdd>IfU`iVnKNd?O zX0oswNv0$*p+bF+IaoWyIHv~*?>*!Q!i|4ce#bI9Ysmrrb8<8NMceDf$-!Zg44#B` zQ|S|;PbKeb-B;lHGe#ZEYV`|RXQ;GCOKeJ}Ac&lFJ=QhR4vr(cy??Xt8=Y(As8gnJ zXaePw92o2qpOVh$T!$1HoOiqGON}DIS=jaG{$h`a^8--xVM+Lr!1a@)INifsCr~vS z_!aMsPpbnGiT8ls0`1=Z?Z0}9Kpk+rk}rGC;(56mTkV^*1{x(@6ONlCrF>C&Veng>w?>~I^KA_r?D@CTM*{b# zzRhVAQE0BgXo9|$tR-k1;O+>5FJXh0(iFalD__7Fa)dGmhEub!o4 z5%~Qk@J1L9h0SR8v<3CuRh=$gAZQ7aRCBpG9J zV_*ZMG7*(K5{M+eY-lbM&k|VmsA%OKKFCvMuUmj1Q(8owbzf9;VG=aSQHGkeEZ<#^ z);<*0$X%-a$hXQB(k>bkmrK{39B$90*4a2#R!AwsgA)UdU|Qc%mcg}c_O6hDAgg$^ z5!7`ZWpz0(Rl16ceB}@LCWDiw>`4wyU=Rr0=AiE>K78L+5u`PX~25 z%W7v)L$_n5Q@W9XOS@;qQbLuj7XpuAS049YHFVuR@4gP3IBJ63(#BA|FDcUID!>HX z%Ve{k_1x+5A+D7;_W(9+r|}t2FQM}w*~(r!rh_UR%oCXIPuU^kbtaCJxu=YZcE5r9 zMR%=!=Q6&R)UjCja4D^1>`el5`%Z((QOPa40SRNFurGaim1Ax~a5t>y2KvKY>L0cU zQ$6QjwupfLWQ(u?o6eANutgnhWK0)u|1d=alnJ|*`ChUnX&&(21RAXbJS2BEgm7SJF=V10*9809 zqgcv(ES<9ewg53e_5n5?{sIN4Y|KemcmU#P0U2BGHO5XP&quimVSHGYS`z=Nrj0*J zt{~M`Ki%PMY77p|-ysIWD>~Wn)A`!IU6e!d&n}C)d_k1Z?pBNVItplsQ_H3l5S;H% zFP@ZX^A|Y4cDR_(hO5rHYw)t^rR+CgaY*%*(bNodtm!JYmvZfGIM`7(=M{ZPo){2k znV!p&&Ze7h*1Jv%V>t95Sa{&IKrdg*&~@dtuAlwqDIaj?`78bA2#BC@*&ijipI^S~ zBE-KnoDMFH=0>rm%jcA4P=W)wxnCjlfutA|xcIZ(Cy9NhnRI6I3m?aOMK(GVB(|+v zqcUE!p^Vq81G^@rJIYIFIiWh^q_3z~rVK1v9Ksc)kHx2XkwjF8`hHLIe)9kkl^ip1 z>kf{0Ygbff?@ihu+G}Q=ail3;i$nO;g{|$g7VUK-*#VUGRnW8VL>aww40KMAQPKeX+9$h5!wk~LIlm0Brf>(Q9GF_GR0nZNFTf}4~mr@#mSO*(C zx@b5{I=f?FOrx*pO8fBJcBwfz1`jWaq4X5}$jZB;T$?w7|8YbqPzym0493;w&_+;A zm|t~SUm8h13hb3SX)D#qAu1NWiyxp}7!k)pG|hf%%eF8z^v@L@g#EazjUZZS+0Q=e z#Q)Wmb^W6&Yb-VxSWeuR?!t?A6Po_;fhrH6GK@2L*(pVi!-IcVA#$nD8PwzlV6&GS zYf7ASd_0~Tuh!K?>g!VKDl#70-&Yvd_wM&!Kpd*M8r&);|M+DJgvp1(aw?~qt0{e* zafKA=i};yi1p4#F)`>WSWgpEix2jFwof8px?F-l~9YrFN6FrjJ&zj};ziXo@7LA7DZ=rRj!|H* zw0tqR%LjE*Y`#Qc=y=e5(S`~bBeYt{M0$am7rkr0<#f$zjMQD7#$N!qFAaeNRy>Qu z#qmzF-wUjztv%10KgDRU(m39{wBlG&$uk4zX#ed_%Y6zn8o63ppCpMKhwg%OH-_Do zFW;M=#KdX3RneuCF%mEM{1}?l5o=&^nPvSy<#5{f5xA^*R)|K_a7V(%MyC^ zQ!G-5J-f;!MGYLUn13=rP)OR&IhR!tC2l4C)rnG@Phe9?RC5_#q3HynION{jUm2aR2IA1+Ac&C0mt(}`C` z8&D^pOLq0DjKBH3m4Xqg?C@i#><8)x&3~bO2)FYX$w`S|>?EajCNgZYX%ckr8u;30 z!4_O=-ju?0&Na`&22M7=TR9gg4kIsn=|qgQfpfI3bx<)LldzU{j%Z7Y$-MSKs+=yA z$;DTf?gY(Nhe2Xw{DYBn^UqNzw(lK*(}V4IZ;8BW8IsmTDZ6W8&HSbumrR-taw=84 zI1VFa#$JqcV5$MV*6*}QFvAuCjS=w0C10PKpN>SC1}+P`HuOEH4*<7&x^M&vTwew6FcD|%G)JR&S^y6z-3d{1NOLQU&{fhc*C z@?ABs^=>A0ERVJ3q_+!L+7`5Nz7;+@SJBk$LXkG{>uLi_kYZ)1-vl96#rB2~gh`Uq zY8-Qh$1lr+9qTZ6Fu#AXsEZirC@vj%VMe^UW%yTq6;F&AWP~4XVFX>?JH;eq#vJ2p zMc=8OQ(ZQG=A_fSCHNWDvZqloc)xfDpKX`zF+V&L-v(Pn?@YkCFdQ5Z78fhbMTh;T za78g*a8X!E;nRQ4t`a<06AL*q{8t*+b?ZMzS6kD<@`u}rfPq&AhXhm8wHjJwP4(DN!9bGK4 zcjX94E)I2+BByN2nb_yY(H>hkW$fEnqjP$g<5xqf!k;G|5*8+K_}kL`Wl8%R#^7IT zsD-l|f)72EC51%BZIrb_V4>$q{0Y---g?pz}xmR2&KLUd%&N!dLVT8_}lG2KF_#L!kvgV7kCXw(&cvK zu$UX?54TV0W8DA8=>Gxc|K-mA82!I9`u{e3y2RQ!>*y~?-{o)N)82mypY~cm1L4zW ze}+#j{|ujo|EKWj@NePM^nV>bJ^$~*r|176e7gDX;nVwCvS#o{eZYP>kKp?E@aY!M ze-}Po)YDg#0m7$tKf|Ygq4cxFVEy^=0DsZM*9a1j8kZGLiyVRVG2=)KI6;mrCQa8rq2apQN}jtn}D% z6(r%H9}wrKU%F!+P^dd*HYATtJtR_)22q=RWPOD)ITaO0n8I(x(_ei4@A=b_6s{Q! zZm_K7v42!00&~Qs6 zAN?{%1Bp8%g7ks+`jYCr_b!E)b!$%~K<@rLZ&si2G|A?9|2zaB3w-F#ImWj9-G?vB z4z?-F=$2*z2I=?iiMZ2E#e~e4Y&CnSCvdb=em6`I)tF1z^A{Zz~(DGjG ziV^AtQqn6SBGjRxMveTgF&#Sml@0kFuuY4bL47j>Rvtz$jZr7&evS6$4i=FsCMdJ6 z*m5H!39`#v@o1|o#lH34oKxU?d6r_HQ-K_PC@-PYZ}6)-4uQ!af!S zDb?gVrnK)X%t*GkA|#QxGr=+;&67LO`oX3gDPCBQJEha??o2XgUDC*b zqZTT#4<<&dBAkywgw#j9x;GM;*di4w`m}NTABEon}hZ(45Kn( z4ZTI1EUkI*pQj?8Asdu&z2aLfMsIl4DW}PD8p--{JlD?`S2TGkY0D-mflyx~S{;>s z`wI02>+v9e4kW)aYSq^r24}jANHnG9IhRS}-`)>aN5hbQ#}K3UeA#Ej*p~+sL$1@P zV=6H;HZJ1+T_>q%VoPh?ldqSZWLu8QXbRcM`nO%{-1|euQ^pF}6VI?Pxpxh#-d#*~ zAi}z6Fi$$FC_3tjRIkt-WX`;plQ)ETJ6=7ud{p!S1-~GEKmN#RLFCMCjV&>~hmSbdQH*6XbJQ_p01?1e z=V26kBcm>F=k`@vZu)L?ZBcO{iA>3aFuSQmw+0|9lhF|<#s)+Bgy9hNI|F4)d(dY(5d^J{*>_BTB8Txml;BiN0Xer?9m zvaoq2{@o4CZ8Jw}_dmy8HME_3%2I#Rx+LbNo9;AaM!K0RA;KPVS+Vc<_~OULCmyZU znX1Eaz?l4uZhb_EU1z(Ty73s8G=@K?wUkMkq!Y*HJ_xr_*T0}S`#YaUB%dIwLTH$3 z)7#R^LUnncrFE_K`ePfq8|Twt&pu`PUo?}I`mKIeYJ3cWBd6!4RRrhEpV%LRlSt1n zs0!H~({7$@UN0?p3l^cBZ*f>)qBBIB$l5Vgz!EhV~C|XFYKnXN{#yn>%}h-IHldKJ(vT^6P%b zyQKMn$9q_5R9d-;)hf zFu~3!?ZwCUA51F~?L71H0M3F|ST9q2*3`cC*5WdMP;DRs$T`!6Q>w*RHC9Xz?JVf} zcE^k+wP{LWI+BUAG9M7WQGZm9jDeA?^#?a|eTr~XFoX2vw5geIy{~a_4PdFhjrhqF`54YWT<#SPh7;hEEEf zUjyxx*Fw>@ot?LHgE$m+VdJVg>jT(iB8+L&?SnW#a2?Z!@I@<^{-{y&6_y>Xm;phP zQ?VTH5QVckH#O&?*dA<+aK-8i(&TWdZ2(i_+H6#k`hsQ*Y9pJ>O6^r-EV;{eJPYqb zUeWlN;vA72ubZyF*5I`u;@F|3hT=r&?3a7pom!&b!*eo2aVKyAx--I$a=c%=KVF`j zI$VPyk1uwe2?K6=3w0m(o1XP-H6_@wL?W^lCztdDz7e#H=)uA4s8h19$J!$04wdL` z+hP3ez-M0>?^gX}FQxU%j2HRqEFI=w8PB&K#^rojyytHBLs_y-G?uua?$ZnHlymKo zsw0PpF8vF&2fKKu4d}Z9SH*SXQr@~R5^pi^cDj{&(v?KTVsZ@ zV}j1^_8p{m&swSnBX&^rZ7)GZ!3ajBAe^!342!Tp+2!3dwglmz7pDU_W4&Xbklz*1OhEjaaJ8h`2#HEb7+0bg43>+I!#S z9Vc`M0Sephoql5{zTueHvv?U(3C&Aq$!sU{nk+_>&t*(vtwmQhCcg!~83v^=KU8K8BWW@2u-0Xzf&L2I8csX1|`Io(!qg_=2Rk5Z*V%XGWD zl&SO#kSEZgWYVL~Bk$*u`9ti4>+nIsCjWRDdDd-NXy}1Y@A`q44_-mjyBIGamU2*gvbieL3zzA}J<c6r#m#}*8njQQFk zIZ}mn%Jb{kvgG}la1&+Y8ZP;faV9EuLXdg+y{MQ zt)9W`;W*ayx?GoTHSQT}Ijgb@dnRc%WXN+setC1#8-!l-R8s?8Y3;#d>gU7pv}85IhNt_6HG_97d)VUN=~fLCO^POS z?w|IzbUx*gB9ra?;Q8E~RMnodC*Oi_`_tkajgQ9o@1kRV9wT4B_>OZPkNS$l8!iDg{f%hE=NHQ8oO{jA^cdtZaP%9%1&}`hPhw+Glbpa(Jg@oC?sln7*K(JrV8R$B%+Nyk{1VPnHH+{5)GF@<`klc%gHp?E?ND3I~Z1`pE;FEe!QyOms0xX zoiwKB$01< zj1Hr$1BY%B9lmh3{NgLxxur?*3FMoVAySaLL@|Yz1$((%w@jg#sa~@~u@_oz!O=x)X1j01f6Z}tk}IT?)=Kif%(-CJXDam^3?XW@viZRvTH>vLKk~ZNxJw6K{d5O`!BBQ z3f~sGqrH>*>=9i-XI8ad=7z4gjpWji7pbVJ1G{ytFH_mDD+8a5WbgDAtHDyZcnDf@4`|8QqHa^2B6z8O0;$A zi>x7QZ9IA;A*G>-ko&QF#*NSnUm5Ynv(+>)%|#BET&u4lzb)y>&91WV(g6rv6XJUG zC^37BY}YA6JCE}}^r#{WxYvw5>U+?6PM|#7jfvj;#u+m*~XrS$~NohF?UI zv$~=i`o^!w9&wiESSk*EV>v2EQQeiR?XlMy74X!v0kqU5UN4 zIkPri%5`7#-FUVXhb6*Z$-xt%BA(Bb#MI37^~tgo!L(M>;DZ;4DERe#qpq>`N`9Eo z+0pEaFJLJ%tW@9-Y9rjj!Aq!qUu(mi1EI9j(5q4(`klEcLzSDz!+XLU}96is}@L3o8sS=HS%Q&5a=Q&AA8ZP<(%wWa9Gs z;QhBN;h)*8MJXODQc5;jeofXnqm|RYe{GtKIg6~R^O+f_4m`x| zeqPr>TwuDg#PD7&DtE9KwlS1_;?%Iy=4|voEVqDNkajqu2UK|i?d51bkLP}?bLLp- zD$EOA<8V;B?-BXZ=T2d9j-+br@mE=Ajy`|xJ8Q6n2-eOX zg0hO(y6!R#ghd7mPVsIfroO5bGnzlL#)*p-Zcq)&mD2Y>H2t3Co67t39h+x{YpG_* zPMr$oN0?$>c-v{{C)RV%TM@E0?Fmq(TFenbukJso%-$*Ms(F>IIh08M$R-ybFxx?; z-oDqUTV-2x5tjD^OET;}Pqa{#_gg=E@Mws;!`9+k=rPjlZ&9EjNvpKXT+SXWA#tVh_ISVDD!SCN z-m6bj!1Q6_JVF3fN_im@k6w@+J-W~}>4`aXRHO%jurzv)P@kR1R+qD+Sj5A+mN4AeHSjF;1p+p1E}`#cEKUH8D=k=zpp(Rs(&&=LrV zSeKEBs8PJ+(fTkpLOd%S8aV}ZPpVbxK`5d!>heYL&E-MzRk5NCd!i3m?RQz2u}*g2 z)Byo>77jPc1N6ME6|Ax@>k#BeOmm&U?sMf_5A9GtaZPy zSVo}7r8U8j-io=G&RAu|E7_~Iis3^)(47iP_cvWui?0Fwqd)Ti5QR-sw;{jisx2kOdQcHr0_Mmuw#2wL1w#BN1=Nl zxhnMtb_=sIEU)01zKOKnZ$!a7mBk?>t)VmGJj~b7q_!^&{PITB_j^n--UaMG3;$68 zTt*`fYNm*C#?GxQ?VTJESt z!-H1oWY82CFVs!+Z!6?4YOI?r=QuO`l{Ft-9BWy?xat0Ot1C-5UPvE4w()IEFaZwM z5JQaKo(vLo@<9eD?VYp3$eP%&;x9OT8{ASFQx#mKT`7pS<&HT_p=xiNIe{cAL0djf z_F?^m|2!@t)DIZ@>sP->w$CKPyxbK^G2$@hPQ;u7413vUBh141MM$jsN@SXPmMgNm zb~#R($zt(vJ;Myw1A z;~Eug!*O&~8NN0PabiOOqS2q0U|nd_mSf97m(rxPUU@af;!`oc$iPl#a?hMBj1JlY*JOzKb&I$^Ig^_-+x+m#JMyY|AmL!wU> z8K0ilITz|uxibZur%K0xJAKjkopbHSJd@wXTp!%!e~dY!AuaQ%Q=9(k55S)n}!jBvK z3+XYSs_Eq7ywM%~qE7e3@jJe@<&LLmq3m`S(zocYpu-!O@i^jjEhd=f<>K<*&^-bx zz=ZPD)hP#X2!58tOIwnOsW_c0>a6FxKVN~)D@Pb9qH$#+3%*+55swg0YD!hkdu28s zi8}5is`%OC1@rsPH|Oa>*MGwh<+^Kr{iwt#&_|x_Al2Y4= z_PU3w=(pu+*B%GFNuYA=>PUh@zWlD8b2Y>vX$2+3NNqi~7f0jakB4L~mV@{AZ(60w z{iYB4vx{4AI2V6QgC><_Vxw=k{@~5CH__AQB^oJ zQW?U>swr63GHVb33DD%|FL3*c7I^5;2sDsWawL|c4-l{ZNGw|v^T5GQ4&U%{arc9S zUKMNq;grm!QC4sZ{Vz(|=pXOkM^4tfG{3L_-Hn+ePf(;=r@Qpxu9y`SFJ4b0X=if~ z6HA7~OKo?-DeJeIzUP%b*>^Mk1%5f3UF>;B{rzM4_*%1&g2hXS@wZUaj%3-{Pk#Pd z7t)`eUK~26NM7uJf5(b_xZ zmGw5{I^)vPQaLBK4o;~jJm)K2BDynIc9*7pMYyzr|B)@O*oyjt{-jRp#{Lg^iZUkY zb7Wjq7dpPdi=xe=CjFZh@M&6%!qH|ou^D&DH$Q(OI%scUE9iythmXZ!GPsMLgR-w~ z%Oot!Wv3k7!ZmXNk~gJOI}6O(k!>hr?yM96+J(GVWfG>3OS*n`KLEf0*R<#NlJ)L<)IlZn?XEX8r8exqC`adowLbs162QEvnTR}# zNSi>pCK$s>=Jq*4exp<7a$1mp5Kjlrw!q-1-72#d{1^G(lnMk>4aO%>G~5~la<3?H za`n5ykL^wiMx8h*(q}qE^{3L_!0L$m+RPIAqq|&Q8Z>7Ajp3@DX1EURr+a{ph$*6Y zs%3)WJp}4RY(eBrPKAxCX(UFb?VFtxN}syzezzQa;JElZ+94h*Alxdp&waPDA66PM zu&3jVE*r0`zIf@D;txHqJy#8%S3fqvO$_uO`WVBz^4i+^8i!gb*hOt#&*~+BrG~#` zf3N(%nEe%AhpoaFB$Ly$_1t@{=3Y{DFKt`5ro#Gi^+f-8i9{f1x6X~t#y{uB&%5R# zolzW&ci|J$PW1ZGTRz#9u~NV1uwAj(6#mzXdV-xpsttb+;jFd@TG&$y<62Ol; zEEnGw#c;h}Q^MsBzjFi4KXl7rQJxGtg0_oGV3^E^&OTWjZ@QfCvG6-vc8pPF{$zVX zfpffCsmqwzJ}NJJbXn$+23AsNnoBu$rvNRUob-l=H1UZ=DJFBvdZ?nDS7{k6V&ij; z-4O!9HVy&4cn&zNa|~)cG_|z&8@Jt=ELbsLVjegPO zZi6@5GOV9?NN6(UNhTp|6^qEib8dRS3={{P%11n)@pn4Dto2nZo+p1 zLm~)XrGh6*XEW0&()OpWMquh{$?Af=Jd(3+Cu?o)vAi+rb3l<5Qe=TU%rEBndXa3) zrYo|shMch&9IcrbPYeE{+8Cd~JH7lopb2j} zF&9I~d!CU1*5_hRu-t^CuT`E#|GD^UV-Ncd#=UZh)&ZQk9?GXR7*@zb12M1>jTA6z z!9sKO=y`2hUDa-_%w%bE@&1R&!CN(a6hUL%5VxFR=R=Ic%Dp+=POO%<%}&B=?pYu&;1D=}kNm#+uOh$C_9vuIM}8x+Cco1}t8*SV z0YW3f`%C30D2P&qvqtM?p&6z2-(oR3qXG?41P2=fh9v}AdP$@fr{o2RQ@0nG2keVO8NF6ZciyPU#gK8!TGOby=cpH1I~yI$0T)6bf~B_PWCU{d$A^S%Ie8IH6sRbY(Tv)FWuload~ zg=&y>#N$C>?3uo!v!dQz$6N7g=OF~~nUR;f*}kirP8EQM;fR4+s)%-AfSN>ewImCC zb(s#Rl)9je8Kf)zqCLs=?ffGnVa5q!m6t}X=1vvnbW-wlIoLpe?3|kuVW-rWlGz4` zq4hxk%DVX{l%*v((98_7ERsw*DNUh%8*<_mUihs}S?7beWh*OJI%Vm)uxFYU&4#s4 z#%uOZ!WK0GFoSqVlCZLuKl^AUiK*W3pNhT|-sw}jJr9I7kAX(0^J|R-i1J}DVhP@v zh$3PoHukbraJ*L6cApfnDYJ#sAI@3S_@h)RKpPRH;3QWlV=QhHV4VV4L*Q)wtU{{R zDpew3M}I5K7PuU6p98Gm;c*4E%1BnA=d|!4^ea(gw;dJDN$Y6A^9m z2-K^{(;9geBe;9WNzfAOB6xwO=Vq;8FvvAs^xW>^gPHicWxC&MP$Z>Xrh?zEYD{-( z=NB60+|L4|r!%k--yaVcfR3;8EbxqHK^s62)OQOhr@Gc{_;qr+Zv+W+DP>7F^5(yL zW8UrYc?!lXhk0_HLebp#tUrUk=*^bA!R2HEZ4^MFP6CP*oDtx^_nlNR zN-N?Rc4NgOVG7y!0svA2#bJI!$CPQ(M9}gQ`nkNETf@gv=NDx#57-{4qFjM&N#7<8 zi7x}>!Lmr$lvZR{G~AxqJ~8kL)Oo*HOG#wVkIe$yRVs-0egW}bf0D3|8FU4deP4oj zcb~P)mI+9LFRW%?e-da+{{7kD0}p7d=?*P;!Uj*xylk3z#M=l^am$bq_JJwiCaS_; zzrNE%g|i4TlOU+($#{Drm*}g*ylXwy=G&gUuz1zAZSqcUB zF>#m4#j0~#6_P&3;rHP_W>a@J)_zEUH^1MmSmPO-JlsBZwXbN5R#KO9P;q23Ug183 z&bgx2Ol1Jx4=H=TG-Em#B)3E!x86~nG5}&T?M6;Kudg)Uh#(oklj^etpy{7}b(4Fn z2Xh`{>Rj1>;yhcKbyTe z{9cXw|BA%=^gD^w1*Rwd4TY8UHwx>%=({$bgQXhWRJW@@3ky@gu_fbzhD`B2c`u~& zowh$w#`Dc6zT@+5I&6<#O*A6L1!I`zwN*%Gef8?6QnhE07Gm8C{bi-47;>d5{}5l> z(WYt20llH%O~N;-;*_qB+}%r&ELg8YzY|0eb%{p^@{+p>eS>iBX;CZv#*D|`2 zvqoyuE|`mRsnGM9mhjvZAbpf-5wFJ9SdfyVP;H3q?ku63euiL$uJ9hHO*$yvR7jB3 zIVQ8t0MunDOYJ#RM!~?JVqKql0a|dgtgomHB$J9jNBb|~z~4&dggv0nd)@k581Mni z0O{Up%F7_o@15>+|NRk?14U%67UF9ob9#RgnlKkH`Cff;8l}nv zN`&XEoV69$(Cj!}le*s+Wy*{y|ZAK^MZ@|_5Y2d1a zhDI(My)OyhsBMKHq14f1s#<+oV`58C79E%(DN_(W*D>O%M3FziYI0DfMDfmdn?icq zoz0T^ulxmE_5KcA?L>(7R#%w|i}p0{X$n`Oewu`Zw@y&-b)C(7-c-SoQhFYY#AZOT=;+zgD4VIYmRrc6DyEI zqx%l@k?p0^xn&eol-fBvp?7JDNeZ#={|H;1{XfH2G+6Jy zfvpxF`_Hh|7XVu&y8jMa9YQv)6ZkeoeG^n_jOfw}_WE2%V3Kf~AAh(@+2Hb+V^-8K z=*-bx&yG(Vx=J_NO<(^HtX26h*6Qy6Q`QRSl25KyXG(HsOVs}8>s|nTUCfVC>ni#b z!|_f(^