From f9b91d45c1561edc1f1128fe3fff227d231f8c5d Mon Sep 17 00:00:00 2001 From: liubiren Date: Tue, 28 Jul 2026 01:18:38 +0800 Subject: [PATCH] 1 --- agent/application/domain_models.py | 19 +- agent/application/pages/conversation.py | 213 ++++++++++++++--------- agent/application/states/auth.py | 4 +- agent/application/states/conversation.py | 57 ++++-- agent/database.db | Bin 90112 -> 151552 bytes 5 files changed, 190 insertions(+), 103 deletions(-) diff --git a/agent/application/domain_models.py b/agent/application/domain_models.py index 6d101cc..1200d28 100644 --- a/agent/application/domain_models.py +++ b/agent/application/domain_models.py @@ -3,7 +3,7 @@ 领域模型 """ from datetime import datetime -from typing import Dict +from typing import Dict, List from pydantic import BaseModel, Field from pydantic_ai._uuid import uuid7 @@ -57,3 +57,20 @@ class ConversationHistoryItem(BaseModel): id: str = Field(..., description="会话唯一标识") description: str = Field(..., description="会话描述") created_at: str = Field(..., description="会话创建时间") + + +class DialogItem(BaseModel): + """ + 对话项领域模型 + """ + + id: str = Field(..., description="对话唯一标识") + user_prompt: str = Field(..., description="用户提示词") + thoughts: List[Thought] = Field(default_factory=list, description="思考列表") + result_output: str = Field(default="", description="结果输出") + is_thinking: bool = Field( + default=False, description="正在思考,True 表示正在思考,False 表示未正在思考" + ) + is_expanded: bool = Field( + default=False, description="思考折叠面板展开状态,True 表示展开,False 表示折叠" + ) diff --git a/agent/application/pages/conversation.py b/agent/application/pages/conversation.py index 0d6d7c5..a57b242 100644 --- a/agent/application/pages/conversation.py +++ b/agent/application/pages/conversation.py @@ -7,33 +7,33 @@ from typing import Dict from application.domain_models import ( Conversation, - Dialog, + DialogItem, Thought, ConversationHistoryItem, ) from application.states import ConversationState, AuthState -def conversation_history_item( - conversation: ConversationHistoryItem, +def conversation_history_item_showing( + item: ConversationHistoryItem, ) -> rx.Component: """ - 会话历史项 + 会话历史项展示 :param conversation: 会话历史项 :return: Component """ # 高亮:若为当前会话或显示更多则高亮 - highlight: bool = ( - conversation.id == ConversationState.shown_more_conversation_id - ) | (conversation.id == ConversationState.conversation_id) + highlight: bool = (item.id == ConversationState.shown_more_conversation_id) | ( + item.id == ConversationState.conversation_id + ) return rx.list.item( rx.vstack( rx.hstack( # 对话描述 rx.text( - conversation.description, + item.description, flex=1, height="22px", line_height="22px", @@ -52,7 +52,7 @@ def conversation_history_item( rx.spacer(), # 创建时间 rx.text( - conversation.created_at, + item.created_at, line_height="20px", font_size="var(--prismui-font-size-1)", color="var(--prismui-color-8)", @@ -64,7 +64,7 @@ def conversation_history_item( margin_bottom="8px", ), # 点击事件:切换会话 - on_click=lambda: ConversationState.switch_conversation(conversation.id), + on_click=lambda: ConversationState.switch_conversation(item.id), width="100%", ), # 更多按钮:点击更多按钮显示气泡卡片,可删除会话 @@ -97,7 +97,7 @@ def conversation_history_item( "删除", # 点击事件:删除对话 on_click=lambda: ConversationState.delete_conversation( - conversation.id + item.id ), display="flex", align_items="center", @@ -130,7 +130,7 @@ def conversation_history_item( overflow="visible", ), on_open_change=lambda is_shown: ConversationState.set_shown_more_conversation_id( - conversation.id, is_shown + item.id, is_shown ), open_delay=0, ), @@ -138,7 +138,7 @@ def conversation_history_item( top="27px", right="16px", transform="translateY(-50%)", - z_index=99, + z_index="99", min_width="14px", cursor="pointer", opacity=rx.cond(highlight, "1", "0"), @@ -172,11 +172,11 @@ def conversation_history_item( ) -def conversation_history( +def conversation_history_items_showing( is_conversation_history_shown: bool, ) -> rx.Component: """ - 会话历史 + 会话历史列表展示 :param is_conversation_history_shown: 会话历史展示状态 :return: Component """ @@ -193,11 +193,10 @@ def conversation_history( align_items="center", justify_content="space-between", ), - # 会话历史列表 rx.auto_scroll( rx.foreach( ConversationState.conversation_history_items, - conversation_history_item, + conversation_history_item_showing, ), flex="1", align_items="stretch", @@ -227,9 +226,9 @@ def conversation_history( ) -def greeting() -> rx.Component: +def greeting_showing() -> rx.Component: """ - 欢迎 + 欢迎展示 :return: Component """ return rx.vstack( @@ -332,20 +331,19 @@ def greeting() -> rx.Component: ) -def thought(thought): +def thought_showing(item: Thought): """ - 思考卡片 - - :param thought: 思考实例 + 思考展示 + :param item: 思考实例 :return: Component """ return rx.vstack( rx.match( - thought[1].type, + item.type, ( "thinking", rx.text( - thought[1].content, + item.content, line_height="22px", font_size="var(--prismui-font-size-2)", color="var(--prismui-color-3)", @@ -358,16 +356,16 @@ def thought(thought): ) -def dialog(dialog: Dialog) -> rx.Component: +def dialog_item_showing(item: DialogItem) -> rx.Component: """ - 对话 - :param dialog: 对话实例 + 对话项展示 + :param item: 对话项实例 :return: Component """ # 推理状态 - is_thinking = dialog.is_thinking + is_thinking = item.is_thinking # 思考折叠面板展开状态 - is_expanded = dialog.is_expanded + is_expanded = item.is_expanded return rx.vstack( rx.hstack( @@ -375,13 +373,12 @@ def dialog(dialog: Dialog) -> rx.Component: rx.vstack( # 用户提示词 rx.text( - dialog.user_prompt, + item.user_prompt, max_width="600px", padding="12px 16px", background_color="var(--prismui-background-color-3)", border_radius="var(--prismui-border-radius-3)", line_height="1.5", - color="var(--prismui-color-text)", word_wrap="break-word", word_break="break-all", white_space="pre-line", @@ -442,14 +439,14 @@ def dialog(dialog: Dialog) -> rx.Component: ), cursor="pointer", # 点击事件,展开/折叠思考折叠面板 - on_click=lambda: ConversationState.toggle_collapse(dialog.id), + on_click=lambda: ConversationState.toggle_collapse(item.id), ), rx.box( rx.box( rx.auto_scroll( rx.foreach( - dialog.thoughts, - thought, + item.thoughts, + thought_showing, ), width="100%", margin_top="8px", @@ -470,20 +467,41 @@ def dialog(dialog: Dialog) -> rx.Component: ), # 结果输出 rx.markdown( - dialog.result_output, + item.result_output, component_map={ "p": lambda text: rx.text( text, - margin_bottom="8px", + margin="6px 0", line_height="1.5", word_wrap="break-word", word_break="break-all", white_space="pre-line", ), + "h1": lambda text: rx.text( + text, + margin="12px 0 6px", + line_height="1.5", + font_size="20px", + font_weight="var(--prismui-font-weight-3)", + word_wrap="break-word", + word_break="break-all", + white_space="pre-line", + ), "h2": lambda text: rx.text( text, - margin_bottom="8px", + margin="10px 0 6px", line_height="1.5", + font_size="18px", + font_weight="var(--prismui-font-weight-3)", + word_wrap="break-word", + word_break="break-all", + white_space="pre-line", + ), + "h3": lambda text: rx.text( + text, + margin="8px 0 6px", + line_height="1.5", + font_size="16px", font_weight="var(--prismui-font-weight-3)", word_wrap="break-word", word_break="break-all", @@ -495,54 +513,71 @@ def dialog(dialog: Dialog) -> rx.Component: font_weight="var(--prismui-font-weight-3)", ), "ul": lambda children: rx.vstack( - children, margin_bottom="8px", gap="4px" + children, + align_items="flex-start", + margin="6px 0", + style={"padding": "0 !important"}, ), - "li": lambda text: rx.text( - text, - display="list-item", - list_style_type="disc", - list_style_position="outside", - margin="0 0 4px 16px", - line_height="1.5", - font_size="var(--prismui-font-size-1)", - color="var(--prismui-color-1)", - word_wrap="break-word", - word_break="break-all", - white_space="pre-line", - style={}, + "ol": lambda children: rx.vstack( + children, + align_items="flex-start", + margin="6px 0", + style={"padding": "0 !important"}, + ), + "li": lambda text: rx.hstack( + rx.icon( + "dot", + flex_shrink="0", + width="21px", + height="21px", + color="var(--prismui-color-5)", + ), + rx.text( + text, + line_height="1.5", + word_wrap="break-word", + word_break="break-all", + white_space="pre-line", + ), + align_items="flex-start", + margin_bottom="6px", + ), + "hr": lambda _: rx.divider( + height="1px", + margin="12px 0", + background_color="var(--prismui-background-color-6)", ), - "hr": lambda _: rx.divider(margin="8px 0"), }, width="100%", - margin_top="8px", ), padding="0 16px", width="100%", - key=dialog.id, + key=item.id, ) -def dialogs_showing() -> rx.Component: +def dialog_items_showing() -> rx.Component: """ - 对话列表展示 + 对话项列表展示 :return: Component """ # 当前会话的对话列表 - dialogs = ConversationState.dialogs + dialog_items = ConversationState.dialog_items # 若对话列表为空则显示欢迎,否则显示对话列表 return rx.cond( - dialogs.length() == 0, - # 欢迎 - greeting(), + dialog_items.length() == 0, + # 欢迎展示 + greeting_showing(), rx.vstack( rx.auto_scroll( rx.foreach( - dialogs, - dialog, + dialog_items, + dialog_item_showing, ), width="100%", padding="0 12px", + padding_bottom="180px", ), flex="1", width="100%", @@ -556,7 +591,7 @@ def dialogs_showing() -> rx.Component: ) -def create_conversation() -> rx.Component: +def conversation_creating() -> rx.Component: """ 创建会话 :return: Component @@ -629,13 +664,15 @@ def create_conversation() -> rx.Component: on_open_change=ConversationState.toggle_conversation_creating, ), display="flex", - width="100%", - max_width="1200px", - height="39px", - padding="0 12px", justify_content="flex-end", align_items="center", gap="4px", + padding="0 12px", + margin_bottom="8px", + width="100%", + max_width="1200px", + height="39px", + background="transparent", ) @@ -645,6 +682,8 @@ def user_prompt_sending() -> rx.Component: :return: Component """ return rx.vstack( + # 创建对话 + conversation_creating(), rx.vstack( rx.text_area( # 值绑定用户提示词 @@ -654,7 +693,8 @@ def user_prompt_sending() -> rx.Component: placeholder="请输入您的问题,按Enter换行", vertical_align="middle", width="100%", - height="64px", + min_height="64px", + max_height="224px", padding="4px 0", background_color="var(--prismui-background-color-1)", border="none !important", @@ -665,6 +705,10 @@ def user_prompt_sending() -> rx.Component: "border": "none !important", "outline": "none !important", "box-shadow": "none !important", + "field-sizing": "content !important", + "min-height": "64px", + "max-height": "224px", + "overflow-y": "auto", }, }, ), @@ -731,6 +775,10 @@ def user_prompt_sending() -> rx.Component: font_size="var(--prismui-font-size-1)", color="var(--prismui-color-10)", ), + position="absolute", + bottom="0", + left="0", + z_index="99", display="flex", flex_direction="column", width="100%", @@ -756,12 +804,14 @@ def conversation_history_collapse_button( height="16px", color="var(--prismui-color-3)", ), + # 点击事件:切换会话历史折叠面板展开状态 + on_click=ConversationState.toggle_conversation_history_shown, position="absolute", top="calc(50% - 20px)", left=rx.cond( is_conversation_history_shown, "calc(clamp(240px, 25%, 380px) - 8px)", "0" ), - z_index=99, + z_index="99", width="16px", height="40px", background_color="var(--prismui-background-color-1)", @@ -773,8 +823,6 @@ def conversation_history_collapse_button( box_shadow="var(--prismui-box-shadow-4)", transition="all 0.3s ease-in-out", cursor="pointer", - # 点击事件:切换会话历史折叠面板展开状态 - on_click=ConversationState.toggle_conversation_history_shown, ) @@ -787,29 +835,26 @@ def conversation() -> rx.Component: return rx.box( rx.hstack( - # 会话历史 - conversation_history(is_conversation_history_shown), + # 会话历史列表展示 + conversation_history_items_showing(is_conversation_history_shown), rx.box( rx.vstack( # 对话列表展示 - dialogs_showing(), - # 创建对话 - create_conversation(), - # 用户提示词发送 - user_prompt_sending(), - display="flex", - flex_flow="column", + dialog_items_showing(), width="100%", height="100%", gap="8px", align_items="center", ), + # 用户提示词发送 + user_prompt_sending(), position="relative", display="flex", flex="1", width="0", height="100%", - background="linear-gradient(180deg, #fffffff2, #f8fafff2 99%)", + padding_bottom="120px", + background="var(--prismui-background-2)", ), position="relative", display="flex", @@ -821,11 +866,11 @@ def conversation() -> rx.Component: ), # 会话历史折叠面板按钮 conversation_history_collapse_button(is_conversation_history_shown), + # 挂载事件:恢复会话状态 + on_mount=AuthState.resume_conversation_state, position="relative", width="100%", height="100%", border_radius="var(--prismui-border-radius-4)", overflow="hidden", - # 挂载事件:恢复当前用户会话状态 - on_mount=AuthState.resume_conversation_state, ) diff --git a/agent/application/states/auth.py b/agent/application/states/auth.py index 27b4b46..f679624 100644 --- a/agent/application/states/auth.py +++ b/agent/application/states/auth.py @@ -190,9 +190,9 @@ class AuthState(rx.State): @rx.event async def resume_conversation_state(self): """ - 恢复当前用户会话状态 + 恢复会话状态 """ - # 恢复当前用户会话状态 + # 恢复会话状态 conversation_state = await self.get_state(ConversationState) await conversation_state.resume(user_id=self.user_id) diff --git a/agent/application/states/conversation.py b/agent/application/states/conversation.py index 0887554..d994713 100644 --- a/agent/application/states/conversation.py +++ b/agent/application/states/conversation.py @@ -26,7 +26,7 @@ import reflex as rx from application.states.database import DatabaseState from application.domain_models import ( Conversation, - Dialog, + DialogItem, Thought, ConversationHistoryItem, ) @@ -252,16 +252,29 @@ class ConversationState(rx.State): return conversation.is_running @rx.var - def dialogs(self) -> List[Dialog]: + def dialog_items(self) -> List[DialogItem]: """ - 当前会话的对话列表 - :return: 当前会话的对话列表 + 当前会话的对话项列表 + :return: 当前会话的对话项列表 """ # 当前会话 conversation = self.conversations.get(self.conversation_id) if not conversation: return [] - return list(conversation.dialogs.values()) + + items: List[DialogItem] = [] + for dialog in conversation.dialogs.values(): + items.append( + DialogItem( + id=dialog.id, + user_prompt=dialog.user_prompt, + thoughts=list(dialog.thoughts.values()), + result_output=dialog.result_output, + is_thinking=dialog.is_thinking, + is_expanded=dialog.is_expanded, + ) + ) + return items @rx.event async def handle_user_prompt(self) -> AsyncGenerator[None]: @@ -278,27 +291,26 @@ class ConversationState(rx.State): # 当前会话 conversation = self.conversations[self.conversation_id] - # 将当前会话的运行状态设置为正在运行 + # 将运行状态设置为正在运行 conversation.is_running = True - yield # 通知前端渲染 + yield - # 获取数据库状态 database_state = await self.get_state(DatabaseState) - # 获取消息历史 - message_history = await database_state.get_message_history( - conversation_id=self.conversation_id - ) - # 创建对话记录再添加对话实例 conversation.dialogs.update( await database_state.create_dialog_record( conversation_id=self.conversation_id, user_prompt=user_prompt ) ) - # 将最后一个会话作为当前会话 + # 将最后一个对话作为当前对话 dialog = next(reversed(conversation.dialogs.values())) + # 获取消息历史 + message_history = await database_state.get_message_history( + conversation_id=self.conversation_id + ) + # 初始化工具调用唯一标识和片段索引映射字典 tool_call_ids: Dict[str, int] = {} async with agent.run_stream_events( @@ -324,6 +336,7 @@ class ConversationState(rx.State): dialog.thoughts[index] = Thought( type="thinking", content=content ) + yield # 工具检索分片开始事件 case ToolSearchCallPart(tool_call_id=tool_call_id): @@ -334,6 +347,7 @@ class ConversationState(rx.State): type="tool_search", content="正在生成检索关键词", ) + yield # 能力加载分片开始事件 case LoadCapabilityCallPart(tool_call_id=tool_call_id): @@ -343,6 +357,7 @@ class ConversationState(rx.State): type="capability_load", content="正在生成加载参数", ) + yield # 工具调用分片开始事件 case ToolCallPart(tool_call_id=tool_call_id): @@ -352,10 +367,12 @@ class ConversationState(rx.State): type="tool_call", content="正在生成调用参数", ) + yield # 文本分片开始事件 case TextPart(content=content): dialog.result_output = content + yield # ========== 增量事件 ========== case PartDeltaEvent(index=index, delta=delta): @@ -365,12 +382,14 @@ class ConversationState(rx.State): content_delta=content_delta, ): dialog.thoughts[index].content += content_delta or "" + yield # 文本分片增量事件 case TextPartDelta( content_delta=content_delta, ): dialog.result_output += content_delta + yield # ========== 结束事件 ========== case PartEndEvent( @@ -385,6 +404,7 @@ class ConversationState(rx.State): if next_part_kind == "text": dialog.is_thinking = False dialog.is_expanded = False + yield # ========== 函数工具调用事件 ========== case FunctionToolCallEvent(tool_call_id=tool_call_id, part=part): @@ -394,18 +414,21 @@ class ConversationState(rx.State): # 工具检索 case "tool_search": dialog.thoughts[index].content = "正在检索" + yield # 能力加载 case "capability_load": dialog.thoughts[index].content = ( f"正在加载能力 {part.tool_name}" ) + yield # 工具调用 case "tool_call": dialog.thoughts[index].content = ( f"正在调用工具 {part.tool_name}" ) + yield # ========== 函数工具结果事件 ========== case FunctionToolResultEvent( @@ -419,14 +442,17 @@ class ConversationState(rx.State): dialog.thoughts[index].content = ( content if isinstance(content, str) else "" ) # 暂仅考虑文本内容 + yield # 能力加载 case "capability_load": dialog.thoughts[index].content = "已加载" + yield # 工具调用 case "tool_call": dialog.thoughts[index].content = f"已调用" + yield # ========== 智能体运行结果事件 ========== case AgentRunResultEvent(result=result): @@ -444,8 +470,7 @@ class ConversationState(rx.State): ) # 将当前会话的运行状态设置为运行完成 conversation.is_running = False - - yield + yield @rx.event def toggle_collapse(self, dialog_id: str) -> None: diff --git a/agent/database.db b/agent/database.db index b08185e0f24567d3c7ae349644ea54adb1fb697a..3076509dfea48e48a9576076ca4c174d6f593e26 100644 GIT binary patch literal 151552 zcmeIbYj9oHl^zJbMOYGA?zY+POkx;{V~N&<&imYhn$EP^t)!Kf>~333CLMa9`#4ub zi3DIgWIHNl08%8u2M9`hi7%0q_|k&_DbXSbkVIF_kEvvmnyUOr_58_HxpD5jsOrQ? zrzV-I%#ZojUgw;<@B5N4MM>Te4PTtQ&)#c)d+oK?UVH7ee)MP0_l@|Sxq+c=sgX{z z?YG)itZ4g#&d#>BwhaFFZTv6#c^Ds7M}Oe|S4f}heay6V@BN)eI{x3budaQ+t>cl7 zn~(gPNB`|3|M=m5_m!g$zxkEb?XRtWvHcGp`ajnF#Y1ncYhSkyh18EiKp~(IPzWdl zWCVJ@x^{#4trb7->(BZ>>-$;n=&(ODsu?vpBqV~xB8{;OP?MKD}M2rKYae#vKrdg z_hmc(@iRaC-XA~n!_M#j;Kk1GZ+iaur#jo$i>hmW^QS+2?uBQ5_)k0kP!CVDvhldV{ndsCw$1LAMFzj}$Jua5uV(RCZX{p}UoQ-bUve|U83 z2;(Au?kFQ&{-*PZ_Vt;8{#X5>;nYaqK!0zYo@Dz{TL)gQ_iexb)81|V@Nnv7f4F&f zE82UiGp-ykxE*UZ{K>cL!61{~tIL1=*7({-*3p~ePewe#)rMSB(Ww00qm>*Zf7x5{ zB5{xWediMu1)dy#vOiCkFz{BYTr$luVI)ES0~wrXNc?9n?oG_Y-Oq~`Y{ zTLwm7-ZC=W`Hy~tzR2v#c=isAjtq|0ez%%!-G>{Em)xyhUHTf%r2Iw{C;3uh{Xo1P4_UU;X5vGH$B=z5!2#IMn2otUsI?>Ki1< zQ1kl|(Vc(zgC9Kq>@(#Il^OC=fUFmLi$8))e(|{%o-JdzYIgeGLu)tu6TRN_ukKf` zUj1zSzgW9&!?WL6G5)vX0Zk2#WVWP2rUKEBcYV1&45s|Wx~g%MzwMP?6JscU2^n6h z^vTgR>o)vPX#cVL?WeYatK}NjHgBBXsnarj_2l-ItJZD!&UaSq_#U&3Tzy$vtIL^1 zE=PP)f7J?WsmBVh3JPm6f`m>@M)7|(*f-QFqX_z*>I{i_sy>+6^y!r!E7@ zeFg8xf36vSa>cq0-~H~2?dt`8sjdFDbYF&eWf=Tl@#o4i3@g4Q6vNkg`$xA$cs*en zPnPlj_kQsGAHDd)XP*21i=7oU^bWp~=`5QbQhn&=@2_0D;m^KXubY*X=@k!FLA82_ z)T>AEzZD(-sSW?sk3v8ppb$_9Cy)5Kssx1QY^a5(FMyvGUuN;duC8 z)^|*=9sN@K|1Zhls%|I*6aoqXg@8gpA)pXY2q**;0tx|zfI=Wdpkvjy*US5VI{#nY z@kCq4zv?*L@xS1M`cVid1QY@a0fm4sfA6dQ10u zEVswfx=r0on!2^J)?2RO=!Pf1<@Oj>kD2VYOviRLYelWM?4;pnru>!$@_ID9l{9VF zqVxaN9f`J%bjQE$NOTU z%!<1+0j#d|QZoy{s#-5KGXShKiM;jY-d|F|Dc9iA)pXY2q**; z0tx|zfI>hapb$_9Cha zpb$_9Cy)5Kst|5U8>Lf8^o+-nQc5iMEF)YW=tFRQZ3Us?;ZifI>ha zpb$_9Cn(_?<}U zu7Q@*UeeCxT*vcTX-d8msiP^}Q`EX0PqRD^O?_QzDw!~hY?gYJN_f6O{Yx5dI%`=r zZZ3+RH||y0U81L1;MV}*0$!~N=@nsE0wj;J13V&xfwr^%4G2XNIHhLj3#xf$I`nk&vcS5n(Qra zQZv9NdM1-ddtkW?jhb!wDL>Z|o7CwgnxtORzyR3Qe_ht3>82e3Yi7|TIMcN>UGsgz z^|j3MdTBb{w&&r7Et>p!S(ApJP9$A|8W58NYCI3?H)(j?X40{UeO7grH)*0F*D#ZbpZp$V-8Ua%S!Z}=!l4lc=6WJ! z`I$t<)otIla+$Q5zGrIe9^2`mVMYYBqz7u&#MBse284K!4!W02r0kr5DGLP5v~4-r z>^(PWi6-rC%XM52O|E>atVzdl2mqU;wU&iI@{FXDOnI5?z4wxvG`dX(eZl-$`OR1c z8hScmITaHtqYZ09qz)%^p+l(b1HvYs5(_o-1$4PD2*xS}Kvurt}0|qj$B8 zp@XtknyTm(HDz?eOW)MctG0h1H>Ib+nVyCf)-=$R5B9Zm+s;7FI$EwJ$zIkJVg=xz z+YPaa)f!C=$CE&}lUU4k7wn;1m;||W!ZUNg&#-;R7h{zqQE29L4f4?Q5@}a=5?RxA zOq59LS+nMp)X=pa!@~)_wau$|L^|jIjUBLfI>hapb$_9 zCyK{}6a$)ut7^Eo^L6{q2swTyY{S*w7~WRTTV5Sg@f*^Dz|s;(`tAZ4aYh1dpC*U?uws z3P$jtV13&b+|grr^h5)T)cSY`n;46J4>kTB(U~SDo^OEN{-3yD1H;VkqTs*gf{o27 z8&L2)F4))@@^uva=cR)6O&MQ9!9NoPn;0T~2L+$tf{o1x?H&Io^8fuSivRyNHU7KS zQT88dRX+*=g@8gpA)pXY2q**;0tx|zfI>hapb$_9+y?|!uXf1sd#6aoqXg@8gpA)pXY2q**;0t$gIF#_@7ag#d@()oYm%Ma4| zf8&DE`G4br()oYmg3|eabx&C)M{(HxN?fB0f zf6ae>iKoBnnnFM!pb$_9CTbZ&R&iP|(DCc=7hx;*Q(F_=&~&vBjGcuf4wKg%uBN+VqWs_V|y&Hf&r7 zM*eTta1oZnN+RTjXBb}AMSOr%t8D}qZok&0b@g<<);03;K_4HwMz-|zztY$La@SLx zU73OY5x;+g9&aACZ9lhpG?&vdn@3&SNNyf=4AaGPGppmX>)4w|Enm~_1Gi=SCMZmL8Pwod89cXgHd-__%>X5u4hq9EqAeF!-|@sX@f<$b z+*Vq&i06o%fwmAt13k4p4Nn}`LOlpK;?QRg--gIIIcm{Nfo2)kp##s^8BM<|)ylp%aB^9uZH_N$)a+ zwnZc8*f~(1#HD}y&ZUVqfhWQr;Io6v>r`h7Wju|>5nPP1mdD*OvxL7+?b^DFZ-FrS z#aKBR!iRf@8|u_w!$f(mfk)rbh$?yX!!h+tU})uNG#!&D!G(CxoJ1^&xEDqWwSgbd zI@f0BlBmr~WvNcxr5b!OnxYlQrBSnu6lz0xd}eB0gmpkG(zlMTVO)~w4EoN9;alpd zr2!e4e#XRek|@u3+Li@+@Gi#7G4!}CJEs#rq;sIZj8}c1Q1xj987uS%pJjeS;2YM@O5vsODRAo$j=20LTx?X=hzx^^K z!>?|Q2X`ixK72dad3|wq4iEFECWF1_{%UM{apuO-vFZG&JuIGrshP$33&HF($`nq# z@vB>BsKAM-{4vP)&b9dUw`jk=VKshzZ6$vF&I*eEzYZ|~I{va_rejyfk2|c6ui#Jh zqYzLCCy)5Kssx1QY@afiD;Wv9E{a!BnTY6xP;DBR;FEA%GuZ{K519 zBTRZ-sUYI}X=w^wh>!*e*uZiL#23|J$3fIcEwxrEm`Z5LG=fGtIfMn(%yh!E{B$On z)KZ3*ULzIs5GOE`L!3U1B6_B^1X|R5HwBwUMq4cvOd^0`7Lj9x$r8bQ6HY3dvTa17 z%v!lsalt0G&6VF-wP}4T21>R6|AMizB3B`x5Kssx1QY@a0fm4L3tr znVY!!l=lBk3YPZ&O$wIw|4j;(_Ww-^miGTm3YPZ&O$w4f!0L{_ChQ)M{Qv6!sU9l? z6aor?Dg@3xzjDo{O@HwfVQ*|AZ$g`7L&wa2_FC6qYG`D*s|TNA-^Z>BU(Bwjx<>l8 z`NJcrZG&AsU8S@%FX~#4?e%D8w`n3{lJ@5)N%i%@oidA(qr?7CVsL0++u%so>pyO# z77rO%sFuF|;gO-y%t+rrKZxskyt8=et>T`KH}@~j9$K6|5lroayVf($1$*Dm@7edO zTl)+1hw|^d5$r!&y!2k-%HiPP8-;80aM)UU_iZ?DZSLRPk1FmQ3U<#G&V7_0yHvdN zMlf-FbHCo*8SK2AKe{`(H5W{Lnm@Zc*uCq8)X*#0fuHtMy%)#xSKckm&7esB)JMO% zb#ifbvUqWP>D|-8`J=^|>-o12p#MX#K^obrFeNPI5_#zOD}Z>`*#-KV-~ZzJC}A(7PjAHwgWTg zKe^nwabw}*V~e*BZQS_u#{AJ!^hs1~cX#H;P8Y{^{MFcaaN|O-^G5#Ig<#>>;`~l# z-iYP*FO6+q+Bq5QnJj!bli&MxFm-n6^t<`f3-SEm@_+o|3(up?!j(Iseg_OM*gu%q z1)B2vcHskF38%}E>-p>Ba?R1K%g=tCKXa%wYtQWmA((RcYnaE}^`)^Biwnn4*V5R0 z@y?sU^#1&j>xJnt)JP2g*YhV9Zyyil;FWnAHG#83i8SQ@WE6r74~9njd;3U8NoO|} z5?jGJG~qd^49=G{TQfD^&@v{rd5|!ojf_Liq6I{KSo5>dpM=3yZU3i?bh~qrv16c%4GN1!Kp5 zIfgMki~0M@vC}ADTtL4M7iTWy-=5*}!7cF0OfYj3VBoD_b{5hve`s>?_9dTQEOY_;6n^aXNqH!s6Up!QRWLFgS?e`V_LSc;^o2%pbm!2RF`L zT-rVchg$A?e(E6PHuw{)3#z~eXfD6wlVIX13ZVP&!R78#PtRXpTsWQIKVCS0tN8JL zYGUu%#o4_mR@1c?prP8rfvfE4n}1`fI3u|96hJ$`^+&F~p9XL4Eza!8PrQk@f?G#| znOh;M^Op+qS3p&aTev=4dT(j>UYgre2Y~~KVcHnF;_eR$Z+x2n#dzV!&fw}bbOAMh zVFXtW`KjT7e%wmvjh1C_PT$M@sgcnka1XN*meyDMvi?wSe`*`C)WD$MpXx&u@fTSz zU*A?Nq2UYC;v8RlApVOQ>FHuHe;uCCCAd&0aY8h9VIOPuq2gg9`u(4=Rxvu9dKtZc z4NAk{=t%F#z$-o}4Z7`O)}}IB{NA4q^^N%P7h3cf7-!|D1EV8l73el8BJ?OVn(Z5q zi*mjExK1@y@5Ft1t*bP;5w)KE`sU5i~XV^L|t+Bux9)u3%%S=(1TSkYrf{V6{j0_IN3l^X7b z)Oz|={b{*6sb7uBGl{NTUw_~5mR?2xtqqS13_=8!Sa!%~ido1yS(1ZUab#K$U}GgA85b-d*uKBjaB&dyDP={|0Dl*TgN9Ie#eGK z|K+2vJ(|Fu>PI1<5Kssx1QY@a0fm49C1OP}m!rdC1>o zZB5+h@3ywazV>%pTVsd(yREIU*Z$qr*37m4d5kIh_O~*orrGf9g>nb1==hgy_@{mp z0tx|zfI>hapb$_9Cy)5cs?!V6RxU=}BS5X_7-=TWV-{YkKGpe)1D? zo1pXm6&-(x_hapb$_9Cy)5U4~Tj{n!h8o>Mi z|Jc^?A1iB6e=7tO0tx|zfI>hapb$_9Cz##YEt-RZp%jKmQm0|6kGC z@K60H1QY@a0fm4OXNcaq4* z&RNQB&=%!vw{i}Z@myp$=dA3U!yLKKP{#H>yoLPssKIdzw2tiPas6Sgc#EEwHg(qnRZKZDvQdj>iZZL48PveNlQXf~ri(1@wn=z0J^b17si%xBqvvEb!qzu{ zhUcZwqiQ694^i&t2=kO^(V+bH4)Vqk{hpwc-en4r#~wIvzWQX+A(V1yWVUBK!7upi zXwzOE@%p~zU?1hm+ZQuvA&b8UOBx>_gS*lZasRmz+rf3EE?g%9# zg?=%oF||m-N7PE+I=Y5&Nv1RCJGX^zQ3kjH8JT{@#B-7;&v+sPALzlm7%#`rXJ$bg&n%`^#!cqB zr0(M_NC$c=Yk@XI3?!;DCXKb2gG58u>#u(XQ7^^*f7PG2t@`to|EWUMsXrA0UpfS~ ztXjKi)9*j_h*+2{tzq1bRu*rB-9;Q<#6RY+?iAj5er)mP#A~k~e|#l0h3}rS4{s9{ zH3{p{N@Eof@LN|Igizrni-nWCfUwYF;mwLbQp=I3(`*PTXe&a%TM*|gu(<|S4Qiv-NpTwJ_bU=vW+iDtk;pKL@PB(2pNVB5hqz%-=i#bFu2~Hu@xgK8DEI%0wX8xAj zwGEG!5z(WhsnG%`RD+NPSA%Ncir@Semt^rDz9odct*7y>dK%40CNePmB!IYpc_%_5poxX zOQ;#L%E6<^W8)?r)SgVINF@`v@VB;Q00ka<;DY|6Z5nA;i~GjCWt9wy94MBR9NRUB z+oYa2b{g+8${}iD-xyySKOP|-%QDUr?K01?^6Us@m0PLD$2^vp51@qkhWiUbK&_0v z%vDfdfre|5;u`X?VF?bPk+XH0Z#*-Z5<8tHp2%b|TGf=W+Mi5kiJ#0=r1FVTWD3fZ zcq188#&ya3#mY3=CS(nR+RM1aBZ5P1Lnjr}C-sz-PHsEoN}isIwj462 zb4i(V8FAO4ImGjr)uV7GrL(BP_L4$@7K*;111aWlRuCB}H*KRN^N_83-~--lBy&l^ zG~#ZONjXy5+!W3Ju)MT|;3qD+2g+-VCLw#PFo0U1MoTG6<5qx;%QbWZ7~mJ zc@uJp#^Xr1iPo7XBvl$B8GT41DK*;2c|@zAN!~VvTq3qOPDUrr^=WiLG3a2Lyqr!? zj5OgZ^{Va$MUt7-+#{>!z#Y8LxIjrK3Wcf;zM-)((>y9dWuADGWBv!T)3mWZ|3=~9 zF2vZU+za#b!QOKedw>7V{JxJCZ||bO_{R?}&K>yGtrP9-k3Zgt)C9;oQ8;q};r6kh zBWFu{0!dydc}wBao57Wfl$GMZu3+k8e4zX+`;XFxy<_=PGleUAmfqbJoH>J_@`a;s z;&px(vGzG%LH^?QVBr^NKRS-20RTQMZJ%D8|CFBGI$WIoxNv!a3V(VL32KPa zE63=o!#9fyCz19b|H+Zyt^J(g0L2Q2&*Uc#=8x=LoZE+D#hGLIE7UrE7iT8XGqjE* z1e`e|L~0MdFWfp>xQx6E2N;6<;ThyOp!#kdCBj99kiz-7#XHD`Gx4WCAK5a{kGK9& z>ebYbGDCfXBjF!t;8(Zy;JxD29mSa=ONWmaXOO*N9LNJbu7;CpP?x3-F5Wp=m_CL+ zP^yp{R1#@Q@{`kHzMc8G;`FqjGB|k>Uvi=dDm6BbA|O{J+IfpBD{Q~DbYy|Ec_1SQ z@dvZc!iP&^XTUw60ws}$Czw6~7FfJ_wm5w*n0Sk7zHtGA691V$wzG)DAcyA*M=nt1 zGlz?Jc7Vx{Phx3o&(b+mK7o-(dIo&8ICm*HG)`0!FAoI8wXG$7Z(u9Qe9 z?sIYKRPpLHYIgiWaqdH;u0TKe&tUFGaCNG9{d(};heRy*VEhCUPZZyHbLqVU`4iWv zsU4qCB9Zzs}6j29N>i!;!24{(|j zyt+7hrMT}JAVbCsd_@@~<}UyF3uknsXV|#ln#d9#Vg@ZE}Xz~)JY)DTw$!y zOGX(_f_Ha>>?IPwi2CsM((w;S_Z`PfdLM5y3&i~|+&YBng9H15y(g(-m(S(*ok21f zuo4P$j;8#cY0L{wjM3hnNF>_Zk%H#%Wz6_s3e){G^TtMGp+O#}50GR8>SE4Qu*1P`Quj!xKn%3XA)3X4uj`VH*N4#vY#-G&;w5PLrKw$ zWWv=AE0NBmokZHP%$$|XWF3oAWjugnKNoI;S-@G~8sz)InntS@W+E7L91AM#bmIg+ zrPMxHZYiD5INs%Laa;q|X1t4%g^xbM)XRT#j4&^c80Pu1_Fxs4!23D7hMng;RIuWrzYS_B;uKQ&$(~_Z8+R zxd~K)xq`}z)9-U1#Y8;=6<}%n5VsXdn?oNg&Yi@xgt)@Iq2z7lsgOj+Zd^k;HBKpm zPLjAiH(i`QkBabTaPT7fzjRtG+hQi7;Ye~)9--jW1F%-pmWIfH0e7&9!YOUEt;6CWa_9YBQ)S)9Ftj7WH&;9NMKKYN4MV+I}% z8DNlJI5~}RMw%hC!O9RV(Gv${#S7Akb)Vv$odWVu*`Vhl@e@ACt0es?7AjGIv=vfY zc0&Wa1PakmND?Qmx_XUzF>^DRx)JQW5X@Z?xZDBO$bS^vxW>FgpzJ^&cSTr;^h;P9 ziqltvJCyj0lTHyR(5bE-Mu&r|P~!G4-8vNPy~Z5||CBOC3I2=$;`J!MhnTq_(lsH? zRd9gF#e9FjDVOe5lNNamYw6OP`=@v97Tt^UBsn?~xx2RtWikm6e zqSl5Qb_8TC9h=_1TP2&`XPItBCrkO*!U%T6|O-FM+k7?#W zo^cOp=6u&lA@=_I`VZDeAJchS_RbX%E(q#o6<*p9-0oU_V0c<74l` zJ^?yfb|#UJ_QGRV;o!kX`s_x+2b?&##z7$;Uvo)zkg&t!dh$+XpFRht)A%g;(8;bQ z;ZY;Jb#T}xjzw|oN)9R}xqRS=6i2Fj2+fWh)d$SBI6!2l0H%bGnd?!Q%aLEI?1I9* zko#EAXEu6^d?f3=6&~@{aj@Y-Vi{-lj$}84YRns>4H^imD$Lo%Kt7!1Ltg2SSaL|< zqjd~49aak;;CdLzjzsLu$YaTG)f?|p+wgmYQ4_9@I17P&m`!BZ48v%)aih$!u)4*D zWH2*Zra9$09pmf|rgxAHcKc-XAi@Ss^Wf779PGg8M#~`R&=>y?ApUs$#<*YD3MFUt5JK{rgAyc6=qA()Z8x ztW{5PnZw|{j9C=T4f5t?rwry`dG_)+%5GtiO9J`X$_@zZ_39;st3sW5%e>B<9q|S^ zA}~M5vz__4dTxWxrqtHsNv_S-lb9ivPtF}IDY*6OUd#2|?HsP2w_$A|{Ui$N-~{C zi)$>SNw_z%%Uapp38@bM_foQLSY$rq0wxi+X!;~)f~)JG1n$Fx~Ji}HpWt>mM~=>ec=F1=W#6ju@-c0 z+$b*)8#iJFXm5w>NB*5tw6bAY+P^!G6a8cF(^9c#5{tyr_V-XlF#S&P`sMuCC9aE( zlD5AW9>7WU(&?3S02?;Ir&e@gH#<#t2ci;CD&bfJqvq0yDfltKU#K_(M}#*)mDvBU z{-d_F7JjK8g@8gpA#j%noPB=fnoXPjf)(2)mmP)j+vF<5KYOif5QiPZT|M{&rbH8fnp)ZB#OzNl+GHZ1ODw`nGmo<@dus;~EzzWyvq zjt=`niNT?PZG$6Sum8A}T0BFysFuF|;gO-y%t+rrKZxsk9OrFs!M$>G|KcpX3gI`l z58IDto`bsr-1Z>4Lx05mM{zC(kE`I|8*oKn!4ASWyQhQmM~gGp z^KT!>A9$;qN+*ssuc5-vSftIdqFU(z9yuAQ#h0d2AfBJFy_xMYA zhyB9D)`kHG^rbmf92 z1@2z#i&mH(3nq_{9(!y$7&|VjzbFs$W30q-`QX-AaBzl=GI&ck48FmxvyhETV<+gz z!tuhT$>7vn{`d?Wqafs1!)1SZb_0uL8%y8dt72^T0NG>%Qfq|Q#MJnp;=0(aHi{h&@_*J$j*KGy6*#luGQH}lM}-Q{^^=(_#c zd1iR_avd|Uv^w3U>lwCAOKYft4f(@^(4okwIcAEw+%h_}6|A>qWMpu-=jo?M243;| zw~VH{p%rGJ7;a0YhAENV)354J%hknWgpeBb)kKhI5?#5z{=VTYz0BWeZFpp05G+`t zE~26k{nTA%6~8tvz5B<1J<3Hcw3 z`~Pdc+J=AXMg;Y|eytfe0?S6d^~&n8tR7wO zPU4tb69$QU+3!lV7>29%yK29?g#Fge&%=I~XOeFwT+{O@P@0p-d44jB=vz8YJux*t z`~5EO_KGv_VtY}2(8Ie8+L^`o%}&0vbnZ^*Y!F@*-%bt<*k>HQN;_TQhrkXETzPf1 z6}#tP*JSCKNZcU82@;p7I0(W{i0cHkXe%rx1*qZMdc3`+GpJKz#RUi=x{1dx#!d^U zVd>qsX?J+@3^sysi@d3&ZRPDDJrHcVKGe5n+=9Zo2U;ys(4&cXM0p14VTbKBe-PjI0wyoD>;s5y9a?H9&}vA1v|J$4p}pxhIKSTSs9BDnc!czc6LA?tB) z5+;JV3Wyop;@JMB)9-M#pcND2fp5<%KtxY#9fAr}@9!}vHE|CIp&^D9JN;kx#SB|t z=EV%%(LcNGzi#Nu+W)hvVRkz@Tx`TX`*XGbXYzz1b(j19TrTDLIxc2pGdeD2I4Law zN0DU0)Uz4H&9O5XM*~CEthI9g|3DW#-v6)N+J=AXMbx7))7SZ{eI7On*`^OT82nOHtYCKj4f*;K-HOvIvh zOef)`P1A+31yRy7m>Lht#Dd){@5`~{Z90Y7j6va64o=9n7PuoIMi4g7yg7$wE#l~) zW|#-$>rV*sRl58UnNrxWLGMOavg&QuV;cre=waH4DX$}LWVZ(&rqApC-xe-^ zHFxUu&;Rwb=Va|H+_BPD7U39NErBlT3EcHCd@JSWa+$l>|35&9%=`a!BW>$O*8cRq zD1E9@g@8gpA)pXY2$T>Q|L1GhZQ8WGHx8(fPH5>AWzq7}3EXPUC49rxkvYl11&&lJ z1_K;6Vj~b76p7Kvi+0gwEU$PBZ?xo{L6jg`OWP z`Ef9XPP|$_yu+L8*WfPr_fEPyzbSfVY4Vq?{6^>#gz?RlXt}rLq!vH8@a3mNJwe zQW!<|FNHFoTQ{4#a6hz^3k`pPXcUg_t(NZC;<+cT&(JH412D)}cB3oq72z&Q4M}nH ztH(gTqK@nEHG^yLHF+MndSsj2Qyz-wCU52abB0E`=if4sjqc+!Zk7ynoh#N|oBM6K zn7E}|qM?fIxMrOGB2fjWRG@eS3a&(khW69VvjV%i;<}_f)F$z3~ar;(8euo*DJx8Z3`Gx^<86+3cBvOB@vHnw()MrgUVwlPzelm%%FtURlD+XCK=O zm(I=MI-^MZ0l|;EAo#d(BP$6MK4v1;ASg2f?n!cyrb~Z2gew=Vr#`ep`f-U0{i7Z7 zt_~q0LX=1m5n2sD`pC|oKF5Apcn5brfk(~WL#qKL!RVkS0K@tJ z&b6&NS5nT@Cxw7Q;B$h&+nd+m(DdqeTiFA(vYV)LXxd=I()hU4?y-{HNe7u~mvdYi z)neL7bzG{BOFu`)rDAKIL+Wk|!I!nPmvH@LTJv+JnbGyn$#H3e$;$_&C=lM9uF``z zof(~*icBTp*<|P{jqAp^ynJaN9n6Xtd3;VOB2;XqfE+k8-P~27EQQ|myc?3h#G1(!bKZ8(B^)ze?^~!&v7a05nl=0gUY6y}1jjSEz>ZJ8)LtiBvZSP?1~^9p%^@7zH*% zg~!-a6S!&*olVAEXmyb?hbF{z^ZR;3n$X$Y&u89JZ_$Z*wk{eA#9@4A>G)2J6~Dvk zI`=15Sj##_*UnE}t^p&5RRlPp5-fP9rutv1FJx#C$upoha{>fW#u~|qA5=2?%OqCp zD9Wk~&x#-5h4^3J28af=`4&jbYTlhRpV>4+xh!z6JxlxR?SjM%`VWAZ%wHE)DI zo6~=1x&HqKxexU94~~xXrbe@U1HIxtJfacBUIqLZH`%lDcLSp%HQ$l0$(MpVM0M(} zvt0jwD0W^qg6BEf=hFY*?y-e`-d&#mr_w&60@_-_aFX!*6wGW++M$fbB7?PTi?22^bIly8HO!J+=Pd-rq-?q(4>X%75sDYa z(a5}48C2IXTwyw;T-qLz9FrN9%5#ex2Gi3;{&1W60vb@p)g1~XDq#=fE49nH*^@4r z_53922?J>hdN`bB$sowVU*%f(JcA9?Rp%8LyXBc)<)G7L`KwE(HWUb#`X~AkqOPTg zL&W(|XuK6h>Q)V)A(G4kqON*a$wvuf@TKJ2V*1GObemg(O8_H9Iyw{DUFAWPm_$Az zlZQekk{OjDK>X!+a7D~+anL0nbG2~D)%ZL_DCF_H;NzdB$4R6B{2GqVguZ@nd?w@p z9~Q+Ycz{XSQs>qMj#AGyy~*2BQpBDciRU9$p5^m!^OW znT!24n$1!Ubjx=^56@wijrArACjpSBMkt@tv_i1FmDe|(6wrdj0Sm1T+%4OpfYhN( z5z;%F%B5pqhL+PBga@Xj0%_xdEu#knGxcnC=4fYtc$ObSdBug#VX6ft9pH^vktT_ z)MT&(^HC7YCPVT?7I3_1mn&!#37t{i%>j(oGPkN{q)$D0k z<)Ns`{jm(N8_p|UDJ1s}A^*$dgrxn6<+PSWz_XA~C4t{qKMduGlmbD9l!t&dKuO~& z)7Hu~$-A5%mY=22=(4`2rAUuWnvicL$y=@6$?rtso%9<(aE}53`cdt5Gtz1*#Ti2# zu7pA&#{5+w_{x@QN#roc@|wwWKct<9&oTfGKW|k*EJ0SiHZ#3tOjSn_ZM+g!=AYrU zH|&eVNV0nq`=khVBE~H(L@~?CLR+p;9^4vxzq(bD6ABGV++1s}3jnEN%`Z7I@LZ7r zl=m1sS0oX>3|0(>Ds4khRVTJ#sJT5Z$*w5($||fd$@@-bL)pC|Ovxkd@VJb;*K9?W z)Z0^rJ(g7p?Ek-6XNQQiOBf~Iy)q8UW8R;lxl{MVzl)V~T zC8KVZYlbClwjO&j(}mNq$`Bq2e=;XGW?p$w zvRMQ671p-O?O1IVD)BscEmVx8RV;exo&+beGL)=Pt>ayrP|!G6Up;jXL_tB3c__q! z4fWMz%35~VBMCM*w8tAx)&qIVDz;G|vV2nEeL91v2ct||y;;Zn<786Ay^Sv1R5e~2 z{cuZPrK-uCDDwjme7}Z}%;jP#^DxvKwlX%9@fWEAWjA~l23(t1hXI7lVl6;dF=_%h zvWtLm5E%ozmU=+R6DQ=AdSe}Ne-(q!aiy#rNh59J`3^!Gy%go1(*5jQPx?WXp zCD$;#+|IpAk<#K*T5l`Hsbw9C-jmHoBITdFFz}2NGrOvLk+}EC^qKT9JGpD&`Lz%$ z()-x=#+P?W^tNg;s^F{Ywo71#i;*~QjoRmwg;)YugO+B2JXvKPESoiV!-?eOET9O* z6>(ijy_a~F;KGf!Xd7_9q(p_Fmx;lC87dWXi#&oM|M1;DF+!naXl~k*$0_U}&Ve9B zO7$6~QzOTr7kGz2?wJpC>ue8L*#4*AlCNwLmBTtqv z_qxrmapdWF9@g^E*e7x~%ML-ZK$k~?=cue7ET2O|F(nygg&Zj@Z;iCfGEo^G?@IfO zdPX zGQam){>mP@*&~8R=J)TxB?rE@&2KI3oB1+sKdf?(Xhy7C^~ zbrI=9_>J~<3~ALR2`QCDcy(PAkuMr;+(_5dH*TzVI|JXtJ9Fd4rZ+uM5SP|PMfL6g ziOcl3AB9+)s1BFb`6ijT%$`Uj+S}1hzKes-qMIDB2LRv-e}k(+^WOT~rVocu(N7eKdx_G zDAU4?(eo(M0ACg3dv?gn5hgvEy2vR-u z&fw}&ar!)G3&qP|Fx#Yh~k6q((M*T@8OHtF0$=R-buG$SE~1Fv_)>!=jiis!>Qiju`ixH zY_fAr^{~lI6Ae%K38_3zp|?uu)UU=k)qY#t(qUhhs<)SKysOaobr0JHeX7V(_^nvhcd}bCJGbCfD zB#-L2@hzXxDM(7_55Yvj6a-Rb$;o7t?uu>ZJDH8yk%DrTNn1KGEwkN79LA=?>g7OQ z0?LeV;Q?j2MDI-e{kYBA2LfE;?q({#vs|F%mK(b1$e6*O9C2~Q+zefGB*Ue2Bt#Y{ zHUZxC(M&ayMg~_3jw-D9(C_{>{@2xA83OXYAU7OZMp)`#qhr$>8K*?l zNX{$Ev5@#3;)04A=}|oj=)0zU2rH;(S0nZTf=nsf5fKHb0peXT*q|I%uxgtefRB^Z zz=Q;AR{reG{A6fz*@h zQ9)N>?kw;o=x*+V(GaNf{(y{H!WJrj#imwZ1j`uN*P<{#`8^A;lJS-TFCx2cXkOjF zr?B&q3|h!%2!M5P7c%fh^hL-b_YR_k$A-Wr5L1V)9L}GckXZ!Op%X-YonWOha1??Z z3;b=V`f;ctu2DV|ia8S3$pgmV_R~0+5F;6t;DSYjOT)D5JK&mp^->Qf%^f zq0DMBJLT!jrzX*e(-Kq?1cD7hK_F%=_X~w-a$)T_coCm5v+7I7SOn0#ZDAP;)5P)g z4o6%>L_|bCAXbvTEC&|n_H)s&<2-{waz)t*bV|Hirh;^Fd^<^I>=|2}zkp`L0TD|P zs$zJWph^^pr3lF2G!4J;Ce6R?BrqGI1WCh-bG)2V`Z}?q;t4Oy)$AiL#nd?G(U8{L z_*_n2e-L@Hw7iQB@}IHz;Le8pRb%{w=76q2_{N8e)f@%l7H=N{S;79D!NCvF{owow zEOJXbE(a4NiqSD%voSPG0fsH^cC1KnubSu*V%a6AoA#%0We@(LsJs#^&L1w!@A~qu z2jEI_D5lw3c4mDd(!=pxl#B&stMrgWC&o%HLCi^!KC424qLrrVHj#n~8mZfI*vaq= zwp=>$RFjsm2fJeNYzf8IfnCAW$FlSU?nY_GygjqIt2ss#&lB&N^d7xY7xA#56&0); zyzqMpVlOHRhrBDC=Q7gxp2j)a70_g&{r>u~>nKJ1I|rcsXGnL#LwQswPt6P}T%x!lJzt zO^#Cc1%=UjgdR&X;r9{j{1m)e>N!>Np zi@LZH&oLp~5V$T}S4te*3bTZBBkI$RXb$fuw1gWFu}PJZDK)yeKO8_=@e;#LV=LT` zP#1<8ZL~6RA`hW>tV&~1#Q{;2Ww1hBi7#bJskh5TQW0UWA)aAPlo>1ZS}T{uEEi~4 zBbkZ$&1xQX5?ciJR4QL3OQX*_;^5@f{Ej#Al$D^u$?N$8q!dCQ3){wSO9(91r^EO8 zJNXMS7a(!JkUQn2i$_V_j4WKd5O7ao(a{uIVl$z1g3X%D^=K4?g)hW8a-56AnQR(* zfT|2wTBs(WE8l%t7nzTuDIb|Bc{;OztwDkiR(HD^HlNhNNmxM7Q>!yKn~Wmn9_t^> z`@Cil7c>wy;mDIoLo6P#ViZ>fgo*z|_acEsK87XngC;^v;>;WevagT@C1V0_&_rFV z#6dKmNg^`Zb5&bwW!m9yLW-6_akTGFRK$Y z^^s>OP3QaI2L0JY|94H}sjf^av&HZI=}_N@F9+!7$92N?#{u~rC;1tM|F<=Fx$*y< z9^2`*G!qU2l-TK&zW!`i57K4~4-WJX`*<=qH1KL))*tHaPi^z@)xeQBpWNr??c`nLJQBdKkJsK(F? zC!x6s!+lY=dMqN3=&Bj1|8;nYaqKtH$NijO5;+~$v@ zvZ;|&S5N=w)~!!<0fZ647;p`4_3`obACv$8%C-Mj+e7~aztoRHKp~(IPzWdlzUT;? z?^uVtng9D^tt^#%$)NGIhB|pOVf?FilK~5GJpG8Es#{qvK;S^;wue9Jg{Wd7{#{3mq7xbX2YI6Z9K`1Hp7(NpwEl%_1> zW2cK_JN{~HJh*Wo*m)y=>_V_`Y;k^P@y;RKoX#KJje{WYo}7j?zxVB6>g>|#ck`zg z@KxdBc>c<}T>g(=eBpVtS-5gX)bDh6G97TNAb+=gyYPYLz=5|4dnU-Q?fSS}^9!k= zSF!^??a$ACoIi6YH0M3uSv}3Cvv%G z3Kqj}iTnR;H`?gm7wG4mf3WT$+(>=*wYN95qP(@T(l^lLUb~k&nS9YEyj>}-wHTUC z*BC`!4$G%t-_q3^Jf}-9azGS03Ju;x^cRjr!&g@1xG#K3#5P6RKT)zew2OR4iKD`V zsU5_19gdJ=huL{NTvn4?=UbXg8?Npk8cmoeL0l=4Yp!)kP{f^)ZwT?7LpgOxU9ePe zI>Oo)0co>{AxJ>Q_qy)#zR@T%5q={;6aNlbNzYI)TImjwd^1oEo>a|NGMmVulH`y` zVGUI`3^}K0bR(^lrZ5VLgjEM*NilL|60Mv^>BB_2Ij$n)VciE#)tBAM;yy{qNW!c+ zQWVSTc~Ra;F;7JVYMCCU0k8tfd%Cn04iD2KQ(HY<_=;HARw+bLSOdSy7t1_{l6rCq z7MWUkfcaawMLBj>SzF6Zwn-G?`WeeWj(3G8;BNfJXhs7c-d!w3VP!zWi#hQG9$|Is(A2tAUVVs;_sm^@<2x5;;D2)Q$|N- z;pHz=JZADTg;WiMmk2z|na&VQfS#BhF{uct$35Yw%wT+);$ra(7axtX--gSP^cJE7 z;yK?9l`hvtnH)vNo@A~vJ25YV;Sz1u;S4DgF7tt{SwwA?Vv~iFMw)_hwi^V+q`Q_P z^nr}CJo&=bCD|zRWjL_8`JHHPN?d^t+C+JKDemx9tV-D zh@{AguYrA}LO(`df?bpG#-pCT&9S-gEjogYt)PFd+CiowfEL_iaPbe<6GX|0VCqeB zX%qgA6(xh2TgBbiseaMlVB&oK=oRXeaNeMP2`^VR_kb9fei&Oo1b7J(_$8w)00PgI zbJGk9n&WrjcvI0L+RE>lTwIvO<_FZ;&ccv18Jx#$w+qFqmq9B0q(EnJ;hn<# z6^s;hv(9D@J;k?8_fG`Ku}_&T88K|@gPq_F^1en}vHK12;iY}dUBQ9*{MlKs9A1Qv zFCYzeUkN6!7O$NP7Cu}W1INAtKN{i?F*?lEB?N%^oqfT@?bNRDYXPZ^I4~MSHrvUY zH0CaHrh?0rV3=U45w{4qqABq4x#A4WBOii10ENoq5eH#~`9t}4PH{&F=Q`{}2rL|5 zoIS8Ow-1vV<5l?RVm!*UGZ(J&JdVNCUZ4%jPkwf1{`l2!K*3ILObJS^hTati4^Xwo zZ)0*57f!+!L)<9p!E&`ayL9Y2 zbMWHAxyAW;nS@Y#KJG?X# zFX}XDqsRsf42et0Z$7rz%903kp1j~2_`W^P_eqv{FdPm!7gY;5qdd|k5Dhs11^3hfG#Ot{Ixn{jxczpwx0-so@xK4^U}_oqfihv0|CPHD{@0Ap9a@CE6pd}K>s zKTddF?s^^aEo!8vi&VP%zw=?w-In)0bSEjEI-pQ(e#8v<%y&Q0u-(J{hlcRWu>lgy z_+{S#MI+sK4-^gRXSfNXCL!eS_bw>vKktKZh^{t5A7kaJxe*H2teR_5#luGHk^eLH z*%}>Ay^JO9wXVMY!O@Y}(cjaw<=p_VP5ZOE0AT3Ovi$$q&CTeB8PqZL&z=82lV{$8 x|G$?``I(e!C(MkYC9IrlCXflnNaS?G&Eh=6No8#?RLxrjRURl0a##4{{~xHVV`2aR delta 257 zcmZozz}awsb%Hc&5Ca2)*hB?;AVaS%a%0K@eJ*DHI0pVx{8RYjHVX;_@K27jUj`IQ zV&G5WKf^yAC>Fv$S=V1=a+!Z0Q2Y}E{~!ME{GWj0_xUFVu*$G7b22i62qtD`PEIbM z`Iw3}f65nf;9}(e4U}aN<^K)Te2{DN`CSsM)Bp4^a!r@r#mFTHloDYC>1X0!&%l3! ze?3suV*bhN&xdlLiafffG5Pv^&1D6Q0z44)sSNy^`BQ=FBl#!a|Eo8-^ojiDP5&bt PHckv+-X7)5xJ(8B0>Dhk