From 5ccb54bfc57801f8481c5ddc548839bae00c8c77 Mon Sep 17 00:00:00 2001 From: Anionex <1005128408@qq.com> Date: Mon, 10 Aug 2026 11:47:49 +0800 Subject: [PATCH] chore: update TreeAI project files --- .github/FUNDING.yml | 2 + .github/ISSUE_TEMPLATE/bug_report.yml | 76 ++++++ .github/ISSUE_TEMPLATE/config.yml | 8 + .github/ISSUE_TEMPLATE/feature_request.yml | 37 +++ .github/ISSUE_TEMPLATE/question.yml | 37 +++ .github/PULL_REQUEST_TEMPLATE.md | 19 ++ .github/workflows/ci.yml | 38 +++ .gitignore | 1 + CHANGELOG.md | 30 +++ CODE_OF_CONDUCT.md | 55 +++++ CONTRIBUTING.md | 57 +++++ FUNDING.md | 31 +++ LICENSE | 21 ++ README.md | 196 +++++++++++---- README_CN.md | 160 ++++++++++++ SECURITY.md | 32 +++ SUPPORT.md | 37 +++ assets/hero.png | Bin 0 -> 148877 bytes assets/social-preview.png | Bin 0 -> 117394 bytes assets/treeai-workspace.png | Bin 0 -> 130765 bytes index.html | 14 +- package-lock.json | 267 +-------------------- package.json | 25 +- public/favicon.svg | 9 + src/App.tsx | 4 +- src/components/ChatFlow.tsx | 38 +-- src/components/FileUploadButton.tsx | 15 +- src/components/ModelManager.tsx | 4 +- src/components/Sidebar.tsx | 8 +- src/components/nodes/ChatNode.tsx | 15 +- src/components/nodes/SystemNode.tsx | 17 +- src/context/DatabaseContext.tsx | 3 +- src/db/db.ts | 4 +- src/main.tsx | 4 +- src/services/apiService.ts | 22 +- src/stores/modelStore.ts | 20 +- src/types.ts | 3 +- src/utils/exportUtils.ts | 13 +- src/utils/fileUtils.ts | 51 +--- todo.md | 9 +- 40 files changed, 918 insertions(+), 464 deletions(-) create mode 100644 .github/FUNDING.yml create mode 100644 .github/ISSUE_TEMPLATE/bug_report.yml create mode 100644 .github/ISSUE_TEMPLATE/config.yml create mode 100644 .github/ISSUE_TEMPLATE/feature_request.yml create mode 100644 .github/ISSUE_TEMPLATE/question.yml create mode 100644 .github/PULL_REQUEST_TEMPLATE.md create mode 100644 .github/workflows/ci.yml create mode 100644 CHANGELOG.md create mode 100644 CODE_OF_CONDUCT.md create mode 100644 CONTRIBUTING.md create mode 100644 FUNDING.md create mode 100644 LICENSE create mode 100644 README_CN.md create mode 100644 SECURITY.md create mode 100644 SUPPORT.md create mode 100644 assets/hero.png create mode 100644 assets/social-preview.png create mode 100644 assets/treeai-workspace.png create mode 100644 public/favicon.svg diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..cbf6585 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,2 @@ +# TreeAI funding link shown in the repository Sponsor button. +custom: "https://ifdian.net/a/anionex" diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 0000000..02ec098 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,76 @@ +name: Bug report +description: Report a reproducible problem in TreeAI. +title: "[Bug] " +labels: [bug] +body: + - type: markdown + attributes: + value: | + Thanks for helping improve TreeAI. Remove API keys, Authorization headers, private conversations, and sensitive screenshots before submitting. + - type: dropdown + id: area + attributes: + label: Affected area + options: + - Conversation canvas or layout + - Chat node editing or streaming + - Model configuration or API compatibility + - Session persistence or browser storage + - TXT / Markdown import + - FreeMind export + - Documentation or setup + - Other + validations: + required: true + - type: input + id: environment + attributes: + label: Environment + description: Include operating system, browser version, and Node.js version if running locally. + placeholder: macOS 15, Chrome 140, Node.js 20.19 + validations: + required: true + - type: input + id: provider + attributes: + label: Provider and model + description: Name the provider and model only. Never include the API key. + placeholder: OpenAI-compatible provider, model-name + - type: textarea + id: steps + attributes: + label: Reproduction steps + placeholder: | + 1. Configure ... + 2. Create ... + 3. Observe ... + validations: + required: true + - type: textarea + id: expected + attributes: + label: Expected behavior + validations: + required: true + - type: textarea + id: actual + attributes: + label: Actual behavior + validations: + required: true + - type: textarea + id: logs + attributes: + label: Sanitized logs or screenshots + description: Remove credentials, request headers, private conversation content, and unrelated personal data. + - type: checkboxes + id: checks + attributes: + label: Pre-submission checks + options: + - label: I searched existing issues and pull requests for the same problem. + required: true + - label: I removed credentials and private data from everything included above. + required: true + - label: I reviewed the compatibility and support documentation. + required: true diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..50b09ab --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,8 @@ +blank_issues_enabled: false +contact_links: + - name: Support guide + url: https://github.com/Anionex/treeAI/blob/master/SUPPORT.md + about: Check setup, CORS, endpoint, streaming, storage, and file-import guidance first. + - name: Security policy + url: https://github.com/Anionex/treeAI/security/policy + about: Report suspected vulnerabilities privately instead of opening a public issue. diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml new file mode 100644 index 0000000..27671fb --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -0,0 +1,37 @@ +name: Feature request +description: Propose a focused improvement that fits TreeAI's scope. +title: "[Feature] " +labels: [enhancement] +body: + - type: textarea + id: problem + attributes: + label: Problem + description: What workflow is difficult or impossible today? + validations: + required: true + - type: textarea + id: outcome + attributes: + label: Desired outcome + description: Describe the user-visible result rather than only an implementation. + validations: + required: true + - type: textarea + id: proposal + attributes: + label: Proposed approach + description: Include interface, persistence, protocol, or compatibility implications when known. + - type: textarea + id: alternatives + attributes: + label: Alternatives considered + - type: checkboxes + id: scope + attributes: + label: Scope checks + options: + - label: I searched existing issues and pull requests for related proposals. + required: true + - label: I described any effect on credentials, local storage, or provider compatibility. + required: true diff --git a/.github/ISSUE_TEMPLATE/question.yml b/.github/ISSUE_TEMPLATE/question.yml new file mode 100644 index 0000000..9909ba1 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/question.yml @@ -0,0 +1,37 @@ +name: Setup or usage question +description: Ask a focused question after checking the support guide. +title: "[Question] " +labels: [question] +body: + - type: markdown + attributes: + value: | + Do not include API keys, Authorization headers, private conversation text, or sensitive screenshots. + - type: textarea + id: goal + attributes: + label: What are you trying to do? + validations: + required: true + - type: textarea + id: attempted + attributes: + label: What have you tried? + description: Include sanitized commands, browser/provider details, and the relevant support section. + validations: + required: true + - type: textarea + id: result + attributes: + label: What happened? + validations: + required: true + - type: checkboxes + id: checks + attributes: + label: Checks + options: + - label: I read SUPPORT.md and the README compatibility section. + required: true + - label: I removed credentials and private data. + required: true diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..f0695d8 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,19 @@ +## Summary + +- What user problem does this change solve? +- What approach did you take? + +## Verification + +- [ ] `npm ci` +- [ ] `npm run lint` +- [ ] `npm run build` +- [ ] I exercised the affected UI or API flow. +- [ ] I included a sanitized screenshot for visual changes. + +## Safety and Compatibility + +- [ ] No API keys, private conversations, Authorization headers, or sensitive browser data are included. +- [ ] Existing IndexedDB data remains compatible, or a migration is included and documented. +- [ ] README, limitations, and changelog are updated for user-visible behavior. +- [ ] The pull request is focused and contains no unrelated refactor. diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..4f1ec20 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,38 @@ +name: CI + +on: + push: + branches: [master] + pull_request: + branches: [master] + +permissions: + contents: read + +concurrency: + group: ci-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + verify: + runs-on: ubuntu-latest + timeout-minutes: 10 + + steps: + - name: Check out repository + uses: actions/checkout@v4 + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: 20 + cache: npm + + - name: Install dependencies + run: npm ci + + - name: Lint + run: npm run lint + + - name: Build + run: npm run build diff --git a/.gitignore b/.gitignore index 7ceb59f..5a1f241 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,7 @@ lerna-debug.log* node_modules dist dist-ssr +.playwright-cli *.local # Editor directories and files diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..d214860 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,30 @@ +# Changelog + +All user-visible changes to TreeAI are documented here. + +The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and the project uses semantic versioning for tagged releases. + +## [Unreleased] + +### Added + +- Bilingual English and Chinese project documentation. +- Repository-owned hero, product screenshot, and social-preview assets. +- MIT license, funding guide, security policy, support guide, contribution guide, issue forms, pull-request template, and CI workflow. +- Browser metadata and a TreeAI favicon. + +### Changed + +- Unified the visible product name as TreeAI. +- Documented the client-only data boundary, provider compatibility, credential storage, and current limitations. +- Limited file import to the TXT and Markdown formats that are actually implemented. +- Removed unused PDF and DOCX dependencies. +- Cleaned up TypeScript and React code so ESLint passes. + +## [0.1.0] - 2025-05-13 + +### Added + +- Initial public preview of the React Flow conversation-tree workspace. +- Local session and model persistence with Dexie and IndexedDB. +- OpenAI-compatible streaming chat requests and FreeMind export. diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..f1f4053 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,55 @@ +# TreeAI Code of Conduct + +## Our Pledge + +We as members, contributors, and maintainers pledge to make participation in the TreeAI community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socioeconomic status, nationality, personal appearance, race, caste, color, religion, or sexual identity and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community. + +## Our Standards + +Examples of behavior that contributes to a positive environment include: + +- demonstrating empathy and kindness toward other people; +- respecting differing opinions, viewpoints, and experiences; +- giving and gracefully accepting constructive feedback; +- accepting responsibility, apologizing to those affected by mistakes, and learning from the experience; +- focusing on what is best for the community and the project. + +Examples of unacceptable behavior include: + +- sexualized language or imagery and sexual attention or advances of any kind; +- trolling, insulting or derogatory comments, and personal or political attacks; +- public or private harassment; +- publishing another person's private information without explicit permission; +- sharing credentials, private conversations, or sensitive screenshots that do not belong to you; +- other conduct that could reasonably be considered inappropriate in a professional setting. + +## Enforcement Responsibilities + +Community maintainers are responsible for clarifying and enforcing standards of acceptable behavior. They may remove, edit, or reject comments, commits, code, issues, and other contributions that do not align with this Code of Conduct, and will communicate moderation reasons when appropriate. + +## Scope + +This Code of Conduct applies within all project spaces and when an individual officially represents the project in public spaces. + +## Reporting + +Report abusive, harassing, or otherwise unacceptable behavior privately to `davidyang042@gmail.com` with the subject `TreeAI conduct report`. Do not report conduct incidents through a public issue. + +Reports will be reviewed and investigated as promptly and fairly as practical. Maintainers will respect the privacy and security of reporters and affected participants whenever possible. + +## Enforcement Guidelines + +Maintainers may use the following consequences based on the impact and pattern of behavior: + +1. **Correction** - A private explanation of the violation and how to correct it. +2. **Warning** - A warning with consequences for continued behavior. +3. **Temporary restriction** - Time-limited restrictions on project participation or communication. +4. **Permanent ban** - Permanent removal from project spaces for sustained or severe violations. + +The response may skip steps when necessary to protect the community. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant](https://www.contributor-covenant.org/), version 2.1, permanently available at [https://www.contributor-covenant.org/version/2/1/code_of_conduct.html](https://www.contributor-covenant.org/version/2/1/code_of_conduct.html). The enforcement guidelines draw on Mozilla's code-of-conduct enforcement ladder. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..6e131c6 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,57 @@ +# Contributing to TreeAI + +Thanks for helping improve TreeAI. Focused bug fixes, compatibility work, tests, accessibility improvements, documentation, and carefully scoped features are welcome. + +By participating, you agree to follow the [Code of Conduct](CODE_OF_CONDUCT.md). + +## Before You Start + +1. Read [README.md](README.md), especially compatibility, data boundaries, and limitations. +2. Search existing issues and pull requests before starting duplicate work. +3. Open an issue before broad architecture changes, persistence migrations, protocol changes, or new collaboration features. +4. Never include API keys, private conversations, Authorization headers, or unsanitized browser storage in an issue or pull request. + +## Local Setup + +Use Node.js 18 or newer: + +```bash +git clone https://github.com/Anionex/treeAI.git +cd treeAI +npm ci +npm run dev +``` + +## Project Boundaries + +- TreeAI is currently a client-only application. Discuss any backend, account, telemetry, or sync proposal before implementing it. +- Keep the browser-to-provider request boundary explicit. Do not proxy, log, or persist credentials in a new location without a security review. +- The IndexedDB database is still named `TreeChatDatabase` for compatibility with existing local data. Do not rename it without a versioned migration. +- Keep OpenAI-compatible streaming behavior backward compatible unless a change is explicitly scoped as a protocol migration. +- Publish only source-confirmed features. Partially implemented work belongs in project status or limitations. +- Keep pull requests narrow; do not combine unrelated refactors with a fix. + +## Verification + +Run all checks before opening a pull request: + +```bash +npm ci +npm run lint +npm run build +``` + +For user-interface changes, also run the app, exercise the affected flow, and include a sanitized screenshot when visual behavior changes. + +## Pull Requests + +- Explain the user problem and the chosen solution. +- Link the relevant issue when one exists. +- Describe behavior changes and compatibility risks. +- Add or update tests when a practical test surface exists. +- Update README, changelog, screenshots, or limitations when user-facing behavior changes. +- Keep generated lockfile changes limited to dependency changes made by the pull request. + +## Reporting Security Issues + +Do not open a public issue for a suspected vulnerability. Follow [SECURITY.md](SECURITY.md). diff --git a/FUNDING.md b/FUNDING.md new file mode 100644 index 0000000..5f33c10 --- /dev/null +++ b/FUNDING.md @@ -0,0 +1,31 @@ +# Funding / 赞助 + +## English + +TreeAI is open source under the MIT License. If it helps you explore and compare LLM conversations, you can support continued development through [Afdian](https://ifdian.net/a/anionex). + +Sponsorship helps fund: + +- browser and provider compatibility testing; +- visual workflow, accessibility, and performance improvements; +- test coverage and long-term maintenance; +- documentation, examples, and reusable conversation-tree workflows. + +Financial support does not purchase roadmap control, privileged security handling, or private access to user data. Bugs, feature proposals, and contributions continue to follow the same public project process. + +Non-financial support also helps: star or share the repository, report reproducible issues, improve documentation, or contribute focused fixes. + +## 中文 + +TreeAI 以 MIT License 开源。如果它帮助你更清晰地探索和比较大模型对话,可以通过[爱发电](https://ifdian.net/a/anionex)支持持续开发。 + +赞助主要用于: + +- 浏览器与不同模型服务商的兼容性验证; +- 可视化工作流、无障碍与性能优化; +- 自动化测试和长期维护; +- 文档、示例与可复用的树状对话工作流。 + +赞助不会换取路线图控制权、安全例外或用户数据访问权。Bug、功能建议和代码贡献仍按项目公开流程处理。 + +非资金支持同样重要:Star 或分享仓库、提交可复现的问题、改进文档,以及贡献聚焦的修复。 diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..4e0a3c8 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025-2026 Anionex + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index 787964e..dba0c84 100644 --- a/README.md +++ b/README.md @@ -1,68 +1,160 @@ -# TreeChat - 树状结构对话应用 +
+
+
+
+
One prompt, multiple independent paths. Continue any branch without overwriting the others.
+ +## Why TreeAI + +| Capability | What it gives you | +|---|---| +| **Branch from any node** | Explore an alternative question, assumption, or answer while preserving the original conversation path. | +| **Tune each branch independently** | Select the model, temperature, and token budget at the node level instead of locking one configuration to the whole session. | +| **Keep the workspace local** | Store sessions and model profiles in browser IndexedDB with no TreeAI server in the middle. | +| **Bring in text context** | Add `.txt` and `.md` files as a new conversation node. | +| **Export the tree** | Download a FreeMind-compatible `.mm` file for archiving or further mind-map work. | + +## Quick Start + +### Prerequisites + +- Node.js 18 or newer +- npm +- An OpenAI-compatible API that supports streaming `POST /chat/completions` +- A provider that allows browser CORS requests from your TreeAI origin + +### Run locally + +```bash +git clone https://github.com/Anionex/treeAI.git +cd treeAI +npm ci npm run dev ``` -minimal theme(new): -
+
+
+
+
一个起点,多条独立路径;继续任意分支都不会覆盖其他思路。
+ +## 为什么使用 TreeAI + +| 能力 | 带来的价值 | +|---|---| +| **从任意节点分叉** | 探索另一种问题、假设或答案,同时保留原有对话路径。 | +| **每个分支独立调参** | 在节点级选择模型、温度和 token 上限,而不是让整段会话被一个配置锁死。 | +| **本地优先** | 会话和模型配置保存在浏览器 IndexedDB,中间没有 TreeAI 服务器。 | +| **导入文本上下文** | 把 `.txt` 和 `.md` 文件内容作为新的对话节点加入画布。 | +| **导出对话树** | 下载兼容 FreeMind 的 `.mm` 文件,用于归档或继续制作思维导图。 | + +## 快速开始 + +### 前置要求 + +- Node.js 18 或更高版本 +- npm +- 支持流式 `POST /chat/completions` 的 OpenAI-compatible API +- 服务商允许来自 TreeAI 页面来源的浏览器 CORS 请求 + +### 本地运行 + +```bash +git clone https://github.com/Anionex/treeAI.git +cd treeAI +npm ci +npm run dev +``` + +打开 Vite 输出的本地地址,通常是 `http://localhost:5173`。 + +### 开始第一段对话 + +1. 点击左下角齿轮,打开**模型管理**。 +2. 填写模型名称、API Base URL(例如 `https://api.openai.com/v1`)、API Key 和服务商的模型标识。 +3. 创建新会话并编辑系统提示词节点。 +4. 添加子节点,输入消息并发送。 +5. 点击任意节点上的 **+**,继续当前分支或创建并行路径。 + +> [!IMPORTANT] +> TreeAI 是纯前端应用,API Key 会以未加密形式保存在当前浏览器的 IndexedDB 中。请使用权限受限的 Key 或可信的本地代理,不要在共享或不可信设备上配置凭据。 + +## 工作原理 + +```mermaid +flowchart LR + UI["React + React Flow 对话画布"] --> Stores["会话与模型 Store"] + Stores <--> DB["Dexie / IndexedDB"] + UI --> API["流式聊天服务"] + API --> Provider["OpenAI-compatible API"] + Files["TXT / Markdown 文件"] --> UI + Stores --> Export["FreeMind .mm 导出"] +``` + +- **对话图结构:** 每个聊天节点保存 `parentId`,界面据此重建和渲染独立分支。 +- **上下文组装:** 发送某个节点时,TreeAI 会沿父节点向上回溯,只提交该分支上的消息链。 +- **流式输出:** 应用读取 Server-Sent Events,并把增量内容实时渲染到当前节点。 +- **本地持久化:** 会话、节点位置和模型配置通过 Dexie 保存在浏览器中。 + +## 兼容性与数据边界 + +TreeAI 当前面向 OpenAI 风格的流式 Chat Completions API。兼容服务商需要: + +- 提供 `POST {baseUrl}/chat/completions`; +- 接受 `Authorization: Bearer ...`; +- 返回 `data:` 形式的 Server-Sent Events,并提供 OpenAI 风格的 `choices[0].delta.content`,或 `apiService.ts` 已处理的少量兼容文本结构; +- 允许浏览器通过 CORS 直接请求。 + +TreeAI 会把配置的 API Key 直接发送给对应服务商。仓库不包含中转服务器、统计分析服务或云端账户系统。 + +## 项目状态与限制 + +TreeAI 仍处于早期阶段(`0.1.0`)。核心树状对话流程可以构建和运行,但目前存在以下明确限制: + +- 暂不支持思考模型或服务商特有的 reasoning stream; +- 文件文本提取目前只启用 `.txt` 和 `.md`; +- 模型凭据在本地保存,但没有加密; +- 暂无账户同步、多人协作和跨设备备份; +- 尚未加入自动化应用测试;CI 目前验证 ESLint 和生产构建; +- 生产 bundle 仍偏大,后续需要进行代码拆分。 + +当前工作可见 [todo.md](todo.md) 和 [GitHub Issues](https://github.com/Anionex/treeAI/issues)。 + +## 开发 + +| 命令 | 用途 | +|---|---| +| `npm run dev` | 启动 Vite 开发服务器。 | +| `npm run lint` | 对整个项目运行 ESLint。 | +| `npm run build` | 生成生产构建。 | +| `npm run preview` | 在本地预览生产构建。 | + +主要目录: + +```text +src/ +├── components/ # 画布、节点、侧栏、模型管理、文件上传 +├── context/ # 数据库加载边界 +├── db/ # Dexie / IndexedDB 持久化 +├── services/ # OpenAI-compatible 流式请求 +├── stores/ # 会话与模型状态 +└── utils/ # 文件提取、通知、思维导图导出 +``` + +提交 Pull Request 前请运行: + +```bash +npm ci +npm run lint +npm run build +``` + +## 社区 + +- 使用和配置帮助:[支持指南](SUPPORT.md) +- Bug 与功能建议:[Issue 表单](https://github.com/Anionex/treeAI/issues/new/choose) +- 参与贡献:[贡献指南](CONTRIBUTING.md) +- 安全问题:[安全策略](SECURITY.md) +- 社区规范:[行为准则](CODE_OF_CONDUCT.md) +- 用户可见变更:[更新日志](CHANGELOG.md) +- 赞助项目:[Funding 说明](FUNDING.md) + +## 关于 + +TreeAI 由 [Anionex](https://github.com/Anionex) 维护。如果它让你更清晰地探索大模型对话,欢迎 Star、分享、提交可复现的问题、贡献聚焦的改进,或通过 [Funding 说明](FUNDING.md) 支持持续开发。 + +本项目采用 [MIT License](LICENSE) 开源。 diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..1b67f79 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,32 @@ +# Security Policy + +## Supported Versions + +Security fixes target the latest code on the default branch and the latest tagged release, when one exists. Older snapshots may not receive backports. + +## Reporting a Vulnerability + +Email `davidyang042@gmail.com` with the subject `TreeAI security report`. + +Include: + +- the affected version or commit; +- a clear description of the issue and impact; +- minimal reproduction steps or a proof of concept; +- relevant browser, provider, and operating-system details; +- suggested remediation, if known. + +Do not include live API keys, private conversations, unrelated personal data, or destructive payloads. Do not open a public issue until the report has been reviewed and a coordinated disclosure plan has been agreed. + +## Security Model + +TreeAI is a client-only browser application: + +- model API keys are stored unencrypted in IndexedDB; +- requests are sent directly from the browser to the configured provider; +- TreeAI does not provide a relay server, account system, or cloud backup; +- browser extensions, injected scripts, shared profiles, and compromised origins may access local data. + +Use restricted credentials, prefer a trusted local proxy when possible, and do not configure TreeAI on shared or untrusted devices. + +Useful reports include credential exposure, cross-site scripting, unsafe export handling, storage-boundary bypasses, dependency vulnerabilities with a demonstrated path, and provider-request behavior that leaks secrets. diff --git a/SUPPORT.md b/SUPPORT.md new file mode 100644 index 0000000..da29061 --- /dev/null +++ b/SUPPORT.md @@ -0,0 +1,37 @@ +# TreeAI Support + +## Start Here + +1. Confirm Node.js 18 or newer with `node --version`. +2. Run `npm ci`, then `npm run lint` and `npm run build`. +3. Review [Compatibility and Data Boundaries](README.md#compatibility-and-data-boundaries). +4. Remove secrets and private conversation content before sharing logs or screenshots. + +## Common Problems + +### The browser blocks the API request + +TreeAI calls the configured provider directly. The provider must allow browser CORS requests from the TreeAI origin. If it does not, use a trusted local proxy that adds the required CORS headers. + +### The endpoint returns 404 + +Enter the API base URL, usually ending in `/v1`, rather than the full `/chat/completions` path. TreeAI appends `/chat/completions` itself. + +### No text appears while streaming + +TreeAI expects Server-Sent Events containing `data:` lines and an OpenAI-style `choices[0].delta.content` field. Provider-specific reasoning streams and unrelated event formats are not currently supported. + +### I need to clear local data + +Sessions and model profiles live in browser IndexedDB under `TreeChatDatabase`. Use the browser's site-data or developer-tools storage controls to remove it. This permanently deletes local TreeAI data for that origin. + +### File import fails + +Only plain-text `.txt` and Markdown `.md` files are currently supported. + +## Where to Ask + +- Reproducible bugs: use the [bug report form](https://github.com/Anionex/treeAI/issues/new/choose). +- Focused feature proposals: use the feature request form. +- Setup and usage questions: use the question form. +- Security concerns: follow [SECURITY.md](SECURITY.md) instead of opening a public issue. diff --git a/assets/hero.png b/assets/hero.png new file mode 100644 index 0000000000000000000000000000000000000000..9218ecd43d13930dc254d78c420a0f18a318fcc9 GIT binary patch literal 148877 zcmY(qbwE_#^FO?#bhjYgOD!Qt36e|q(z&33A|28tCDPrEbazO@5`vNv(kYDsf<71Y z{dvB>{bS?abLP&OGx3@^v*GHh^0-)3SRfDxS5ZMm69htk3IZX2!axSD$jfu)fRI2S zMHwk=kA;IwSMM)dw1ZtDyVIo)(IVQ~+MYRDN6|iv;v+Zf8Swuol+sH9F&jGOTU=VI z5SGO%gx)SJGj~Z&DLK1~*d2{D+_syRBQ~>`)Uvh}4~JPz&1;>$Xa~Oz0r$jy?jc=a zB$;L5v8QRVkf19U_Hf)0PaluuBwSjzCtag9jE#d}iKI_Na%He~fwA_?T$|J7**2+JfohV$lG1pZ>Rs& -#t4z`)=P&0yCVYfC-_K{vPb_s`R>Pm+?)p<_+XKJT%s3pbc_5p{&DJVLI2G_ z{Ua4D!kTWW`mqS}chLWtmNWIitKG~oz}c30{5qf9T-<#8Zqxr0I>#nb^=stkciaEJ z n|f6odA!L+B!qYel`k?bevS{V0Q^dCVd7V$hL z$=hf1{yTR9`R_6aLz4vf`A+} RLE83KQqsB#z@`5^kz*2yeolHf>ff3Okj+G-K;XbB zG=l$!Y1D@3oSeI*{cmbuIqIjlyP5xO<=uCXf7pLI&M}iDk3%i?f5j2@&Ey>UfeW#d z7p>T!YwRsUGk@4Oz{3AW(`$0-Q2{qw=pCj1$ofYVJCfOl-Cg?6v`bz~IPl)wQ3h3q zA* *Ut`j9(q)JLFtD`WuyW)qCFTQn>qiY*sl2Rc?1mIFrD;%%*}dN? zR180JR=@#sj8MSvW-UxJ=JpdH{Ca^C701Qefh#z#nVafb$aQ@V``svec~Sp~kRW0x zmT(c ;yI?CFvB9w0pbysLNI4T0qU+yyF{ycSH)xt;L=2=r~S>jN`v`5)tE zKRuZqoJ2zFd`P|UfqC)6hRjdr{@qj;LuDBbB6BZ|40uNYEJ(e+%cWm?^uN0t0(c%> zx%a-SgB@2dqYwPote&E}+iCqKWFj <_Z0P3%7!EK!q|M@pzoSEZJJoyzuV~ z*>0yx_}N;eQt_9wW+LxbA(Dke;EzuKzj#u$UdUZ%h5qM>4g(s8HJMBK2w&eB7hCs) z1)-9$bbp47MbKZrJW54_jc|N@ns2cxF+3;Wge6uYESwCeaP?6^e=ZfV-y%7;<-ffz z@^@R|2S)*fK*tQMLT Y@~Z}7otr?1^FS> zYa|qroDG(Vj)!<=F@b>Fga%R!3Nt(r)uJ}WrZ>A+xmMUmcesdI%ezJe?LG&8k+)ss zjknaPMB>ZkG4hU+cT3sQ3t+FuOceKZj#M7_w}T~mc#G5)sJn&ha&LeIJ#8g?`@%44 zj6bobnbPUn`H{RG?4KizsXS jlx8F`IsGr3p(ltnIsbOM>| zr+tHkH3T*3-TwF2PO5aLj;QXMf%?>~ PGtMSAiH{-OYA zn2r(>L-|)Kouv`zR jrTlafO#)<)zt)&%c|rir;WF7dxbi9C z%FA~#a0jh_Ho;bSom^p?U>wcO{lblNE2M;!h)h|UnUZ*V%YF*AkZYFdz1=)A0&BRI zx5NGkDCrDlCX(!rh7ArxBLi2P1?YgQo{i@wX=U_U3tR75R%G?hA4AK{0uyLd{u9fd z4ifg9(pQTp7vgxn*$UW;j#)#+`UZ3A4u^Y(Jo=cL%S_+S74C{Bb;8pL=ZmbVql5F{ zV*gzvov{W|JI0t?W9{0letkkOIad#lhH$u?jz{&n^pDAvGx&0{RhJ;eawZ}1bPaTk#)uc98sbFl!V!j=*8rG z6(}5;>maUfgkps-%O?Y~mwp$;(Mk;)9I8eO8wFP%P=m@|^Hpbb(|rst6fKFU?(TWY zKE|bWP)k6jUCQo%l_>j_liwlsbqJ)(Nf`&+39`w*B6ZB@+gM1O?3uS9gui+_V_+sy=ZY#HOaUuI6uj+y z5nwT8XXHr3lcCe79aT6cthJEjdB*ANL^?=lV=`b-o?LUe4x{nyq&unpYXdnO&owPz zNe!-&ap?-_5;a@Ke}u;eV7pfzROo{%EG83YF7e~Ey3%Z@&+xK24+$3mHz91Xl~3$N zLEPjw{&WgbXp;&-Knh={)(zCYW!FVI_>Zzn3+}>3j*amF10s&t(zV1x>rM4?$R~7c zFr=}2ihqQ|A1$d+MK~z!HJYx5FlRhE*%uH(8V5XZ$X5;Zzf)P@hZTLWF%`HPtI7TB zw?9h=m#V=iq)=_MifO=Gfb#_ QkQUmuiy2nuuPqnbeNORx21| z;G%&oMMkCs7A=r4LlB>=hfA1K16v5l%K2!B`ZDRvR$FK-P*K5Al!0 J0`ABbD07C84i7PyYhwsi-YXfG5?;4nr=GJbgNLNO0D;4Zo?`bO krcdm> ?9x56qSopX46L`R*OU9nX-8+RuU)I`NthN}V-M-C z&HvB#)T;`LQ@7ez!IW0|nnlVb+CqCQLO^U=aGWT=v1-H4m64DuCazYNaeVxTu4n$1 z9YOhMOqijWAnD024pfbOj}O(+eTw^YQvjswr6Y?Dm#XwAM7_4J-xW&6Dl`O%sX5P} zQ}G}nEB)_@%<3JG@pBug)~Uzx5Q0}x4Xne@b=T+)xA1LM-PY`R9oLwC%KWJ7=J3V= zm!X1t)#P;Qm_H _~{*ZTP{VytNWlavbmM2ytDXXAC9ELrx!N zPSa*cV-sZI6P6a3STfFS@;}`#2sa{wlaJR=#|OLg>kH&Uwx!v0o`ni0+2gKp!~gV) zEQIj-#g#eJ*MrUG>G=7GLMV0@l90)PuuV`+_c;d+ZSj{a1VhskdG;tm`AFNAcx!kp zRY&7=YlY7sNx_v&?3ViLs<6wH!@q%5-Ds@ppu&VNBZo6JrV_lX%TgXCtjNeX&VnX& z`Z|QTI?9-(VlENx^Wn0EV8OIIPm{w831$YULv?p;(Hc%KsoXY%`2}T!-gS}g6qJn5 zYa}r?s$YRi$-6!~6kde$&kO5bK-F5;T%=SAeK2t^*dyb}Y@}xR@=0LMwt<{^)5v7{ z_1(2{AD!|jU_`BEQfS~(i;V9??=Q$;1loY3Z+Os+T;|e70GW|>Zh~<5=N2%N)hV;< zdpg#-Ac!DIY!QJ4*!0D;%w-q?{{=kKy+rn2&Km7Ja+8sA8Mum!TSsZZacrzAT%jo6 zJvIDdT%Xn_gP$Hm3PDfm;~t~RR($cQXa?5UZ4sq5>AU*5MOsYi ?mn zaQ)U=>0Sr{gUfGEi&)u>B(@e}jj;-0yU$oj4cx(2Bq(K|R4kGF9g`%k>hiDwlWw}h z(9c$V-9UUm0-~cTKC-Ujo(zhl)G$u)^ {#FzO2Yy>i#4i!`Li{P^|j$HK@h8vBaG6u&y2xW^u9C9qieB z8$cvCcr>Wq8F~Ta68BnemdJ~Q@Vbal9)b-d{h;0**4%nm5sa3!8D&H$5 q?AHKnnFA&D!X2*kDc8$sRT*gf!DiTK?QG Kgxkn2Tz{F})R2UgN_g_`$aHb0&~RE*f~; z5jlyJoW&;IcLerxfSL^Unm+iBXtCZDD)>+~Gnw*A?v!(&yZ}Bv1%(a}C7tP|)ORyj zYJuevlxw LW?_iph+mnl6l+Vs(76jDU#YOfSE{!V% Fh!=<`8=8==o zE+XQUpV@?5C7Kz-NzS1od@&6KrBL-M Vq8x*eM#gM9)9RoKPeD6CeO7z-V*=(ZGtX1MU8BlMcw~DdMZa)G_o%Z^1@gIvc{{ z>R8_SE-@F4Oz{_r0nRRSV!!;_D|SkmvXsH@xF__i#MR|p4EKc40B$l&hRLe?BBCzd z*nlrF;ylF>X+yf95~P&-J7d?P66dz@FIH)O9o5Z}Quxz#2DYw8#fS)2nL|dL6Hk2?oO> zUt7OxOu2b%(^{f)U1jcKUQQ9cj?)RJ_&uj=((mF(acP0mi($BHJG{}M|Jjs4= @^wr8J z8hl(OtbP~6+G|5T86CzCt3U4wgP aa`4XFMvXz%on%4ow$Sq0?&4X7T;IhP;jzUD7P3Wfmznu01XaH;J&`wU?!RU-w?X YI7SafGu&Any|~1%j|JNbu28+=BaK!tbBD?@w=J(ymb3* zB#|3hSe>=mtlAsOEbeEL@3h%aXMO8%bBR4$rB|tL-kTxf9njp@hh{fJE$YtA&8NW< z52Q-_Q>_ +};5{S=A)Gx0GYL{w%{0!AsWL8v-=E{$VEjvqxu>`Tc?|G&JHn z9Y4cLqLOFtM%*5I-|knX|NI_5Y(?H=U$fN?PHKXgjrN5PPGoN<|GC^s|1p++hIsRy z_Ro3S(dDM@JF2ntC(~xVQb!%`qEA|(Az1saFE0cnI)8-`-keTl``W_F$hvN}$W1dk zoR}HJyw7f~Zqf#8YB+^nKJoqSZ7+>H=$IQ2P(k(hk%ZaP=Vm$8f8H0VioZTBxD3Mz z=@~?n=_Ya bgaUZeIkhlLRjaYdI37no #KQg@jli>t$y8 zoc+F)Oy==hKbxp?cEZw)zi4wm!)AZ+;!QM-^9S80p1X0?-Ujtx#r%ba@SWw}x0L$& zH)&erJXkAP9Zn%G8gHtWI=K>9^PUx};(GsFB29k`D4DQQg|3Ya@WrFQNac1gO2lI? zA(hcqAT4-fqt|A<{p4!f{ZY#Ktg!pV9_xxv@#J$WoU_|z$969r|4J);A#wh*>rBz? zKbPxWqHc2w?~8d%4<)TfB_BSPll0BVjE|2^bln|zH_UGnef#@O^fL%hT&Xtmg==)g z@%FDVt-~-k-k|M~iD>PSGTN7YRZy=wl#?W2iA`kWIR5h~{mzEy sR3$6m?steywbhq21a$T>5sO3oouc9B~}L8Q`^M!;8)FQe<#QH&fTeyU15nG*0E z!2?m>j4&%4OM8oFGR{dnYO0ESCVm$)6|Qm-p@JK>0$+a|kxTq;axRXq$L9%*AZ7EN zj}jii)&vh4cX;wU5XWx0Zk@t?S9M)nk_=R2-tB*(&-?YB_I Qbr%DXK;63AQsGu1eW`j?IJjaLHkRl;NvjWHUHTm}a!kUvV|N7a#GxL` zsbAobG@WaX5X0nj$SO$h+Hsn~K$3X3vP`HiXvYxrI!~Jo7budzvyNk8t_5ugCO>Kw z3sAkQ_Wcu1Ec2znq=U}SZ@q$hsaZ-!kV9vI*Jiv$C0(`B@aKLoCRRvbm}*4*qy2fm zwZ5}@flzak$S9^fXT2(5Ll1DiYZXePF|?k2lUh65mLbVbaQSm$$?$=SP|6w&%+Oj+ zT F1s$%s{o0EceTWaY8ZK!}d zx~Xv~y=0RxF-dNE+1CE@Yhi`qgu_Dp+N`Pg;$qm&Ovjr6oz`E+tD?lLMK;z%lW%%M zalbFMfsA*L))}b}SLW*;ekoU!nb6X*Twkv^8tj^_Sf4Iyoi5|KIvI@LpZR|C>nLEU z^<<@Tv3>qB4E+z*x4R=PZOu6RG-ok^3KPWztIc8>68kFUhbFtbFoxX^UWK;kd!$du z?<9UMT}K?j%1ZOFaqm+OypuGs#t7a+qEv29`ND9ke2Mv4H*DA5>H`HT7{c2FRZ)|y z;KyrqJTe)rv$6OEM)7X%2;X_(eKV|-9G4~O?(Tf&o3B*TxgEa#7%WgkE9nhgY6~xU z8P7ud`^axOD@*2lrP6qV8kv^XFM>Q*b+@1-#vi1Wj`{#K80|s4Y91g8b8k%|!j8xG z6ojR9bybV4%-#=)C@9FN%O{^lXNWwLfw#Al{XTv+NXF%jNI3a%Za-hW8hF%+jfX*( zXSqFbMrLSb#aINNKggi=EjX)S-Ey=)*s1FD%1V-xsrvE#*ALDrk|NG496YB~<)gI5 zR@$o}U5Vvo=OX17+HH+(>4vb^#~be@Y*)(Xa~_Eo0k*JD2%P*h4%MFJlqRmtdQHVH zs1(o$kXISAF;3N@?n1*durBV3;GhkGBAA7HPc*J!IM*=lCI@7s!oI+qa$gI5-pR!i zPXsaSX7A1v?4PLi-tGH_lL+B=39kgxx<8kW3{DyxZH=$ET86(p5)Wfz=6X4Bb7lPJ zXV-7bft%XrlhcMxSh@jomzVM6+ys+$x7RH#t*4?Z_3jRpLktIKQw6V#r#$ga{nBU3 zY<%P88f;_7kt1i@z1!Qp+;#|dw&h!SVK+_fEi-j}G7@IGzpTeDA;?*e9vK@nu`pEV zzB!s?G`~HoulMSBbM_| 0W3051e)wov6 r}G W-+T@80~ZrPeDo66vI>l9S^giP$bJ z^`_=_!b6IDzdrue{KRhbQ;8}K15{Pf^ZcB>%2Tk+)O3J%Y=4$Dk3X5GxTsh%ES%mL z1kRn54hoF=$-*YHtC^eM=kj8DZ+b{!3ZHx6;b%e(H`Vc|uHUtmdJvqm;dF-)D+uQ= z4GbSr`vqU%aNGZ4ORo|A)#I>lbYWU+n5iz8oa5@qL0 F|Mx`vj<=k*Dc>AipN()hg_X*qnNs8Zl*sru#BC2-Z@ zM}eY`OHf~$FNrF@h#)CBaKa3Gjb1O|);1mRRpfRy^g7^c__+_JEWe1HSVNh9VW?HD z?VXi@oS?Xo#aj6@o*I&h9s#W85wh1*Nidr@A!}A=Oy_b)&l=c+kOOLqpP5o$vY8M| z(?#oS)+4sxwD+a9unbx_KECX80BeaMFyVYLTVHxrA&rb8-*G-wl5BG4;@l1wdm8K< z eAunE Hni+V^cNPo*D*t zP|ie7c^UJMA34aA4>B@PidDsx6bK%&iK{7n_1VB69JZ svByTRfh%)T~Mqq^`f!7Ux1gjb9Ek z-%F_2m8W%ZSb@Ofv^1l$MT^JB-E>NWApqvgb#kQqlK+7A;c^ w7JsP10wh?#E9SEu%$pbNyF68@v46Y$ zese6-Ly0}wJf(e}sF#;|kg%7xpi8caW93 7e6{E>5qyNp5?h z7j~Ws1{q9fb!%^-Mi!f4!SCjB*>W(f#BP5E$P=tq&t@x6ScCvLW_i9oDALjbh-+jM zai)JP9E}(mXJL@=$<-~pJ>AYe0?=96q*z$^m433cYS?+-+gl+)(?1i6QPq0@y4iO> zj3Gs;_E>qcj1h^?LP|!`hj{%F2{@(cXT7x3ZWEGpOT?1>TBH)rCeCQNk!*S>?kgt3 z%q%E=yE9*x-+2?K=08_!{;V^kUmo-L;&v+t|Jrp|TIQ{4_V0PNXW4+0oO!%bVr29| zPUbiji8wC4cp}|KOD}HP{1R}HZ@0$6*-oy`n!0{pTWD)18Qcm;_+GbrisI8Q*SjAz z)B2iWuTtOuA>3YzV8PZGKCop3aceW5(;(i{@%54(pi{yhbfo?(clDF{ULtBtQ9;|W ziJ-n*qOkKNOvRpLb+Mn2#$HHTG3iL`C$o`awv$Y&GD6*>Xc~9wCkZhb88I1PVN$4O zm)tu#zdt~RMe`Q#yRgg9>(>c9N9&vY*xX99Rjhf8Hsi gx`O(`kS$kI)E}8oKWXpsb}C z7MOou2&^-SM8^%j0DS0s- G@hY02^wf;z6ltgxj=o@$r$)thDI)ob!6k&-61&G_s0twvjf z2i2FmxX*BC@gpf1qUjkzg)L5BmQxVB!EW-*l$b{cDJ=T7^g_|1qN>5;pb!h@dF1`R z0$H@V53c!g0NN %aC}E^(*(cd0%T9cuA%3w}spE|vw`o>b zBaLuOX8bdmj(2M>W!(-(l_(ylWio7I1j#mHz~0%-k|kYz(i=d$lYxKGE;;^8ZrZ$? zCqoI-P@$FV1oFzF=*>KB)!R$UROJE3#SQ(sOPAn*=C&3e2`_=Jo9_qnrMtVjZiTYt zM}w#4=8`_YTQ*)$$>%gN9@C %d5puL^g4_jeK5{&(=Y;vK;yJATMLCpq3~{2zPLM zC_MgqxDof!awAvQVx7;MRDIcMoqRbv>#@DBEzU6Y7|mSumgWRpyu;Z-#De5+f_>4? zgYW-bFS!Fqc=!7*dorR!OpI!#Qv4!bjivnzFZ^a4z&QrtRy0zV-}b-Z bF2>*Tc ^P=elq<5$g!U?c%2^1A1NiJaLg3C?hC1;5oma8PDNdvRUAQU zd;C6)_}YK1-@O)b4*jvGBP0wfRY$$N7An&_O6KXhf%l+N_QrI4C%^={Nc SRB+Ljt5srSztvVr=F4k?Z#>K|MBq$aX7P;lAnm~R=cNTH zrt>_Il+Pg)>p;2ODEx__{NH-Hvp0VFRMseJ_f$tSf7;3Gt&9iyNz=bdj>Jl`DW4qm z(;;fn_YxI8ui5HUyy) j3@CpQ8EBV&Q@XE_LB30~Wep#QI0u`D(X&iw z@i>G71~L3gq2QMR%$g#UJ~Q5Y8M?&MPMhb!oF7S){j=h?9#aA &W%{%Y&6p?sANZ61D(X3XM(gp)1!@USPVgg)PEXPkS^@yvUyQ0r9oZu@4Be zegtY0mlIbjuc`T>jwRQGqs=K!R*?1DN`ji5wZBL$zI#!Ky^r@4b8Dy%jhWS3D6p9^ zOf;}LIfw4B=%5&uBuEQVq7M0(@FyoA^$DWZVp~deVqk>#ob*rl`(KrB&`ND0beiN| z8lsq`hOcu+e|yCj-6k9;svof)ecz7+qBE3kIeOw6M%Yuh9rAknPjd0&@Yy{y7n^g7 zws^Kq(akEsh*Q-(hYGw+6DS%uFb)mMEzinLV9a(`=)bRXi=l$Y*`6ex2fjc;JZ&X{ z(~myxvUq3NwEf`K1pknQ{tR6LyJ_C5cME8d-?&T7Da-V%1;CcT(TN@GZi9f1n?@`c zEB+}}TnrnpX~mQhv_z{hMQlEtU%mXiQ$Sh%xm-7cBG90rtLWv;kw)Xp+yrkX;#9aR zK%*f=(wUK(A|W#35&=b+zu}M+4l=W3j+iqexrY8EMNpOLQ4da+=@)ljJ-`(JYu%Sf zp&bnDxY&0cVj$3v8?N1UIErVZUMLk=_*jjL=_W$ks1pn~OYct+>RAhZ76X*S6?!#u zChmFV;f+pA6nRj={es{YS{|r)pe1=uI(V{LrBH4{s`*}^3a0 )yi`E1c7Kpn}X?`R8mdBGEV`cp?u8D9** zW4_3Zcz$}#dhJ*%e16x?34+!)ktb(IswX6 )bYUyp{Y9L-M$+T-w$;zYx6L^n{lID+lpT?%ogD$I|%KWatMKfs*Ba&$?6*@6^ zZlfP%wgUg`Lio3Z#w5xMZk=W!0ZJ;^r%m-)XLZ7?f2~k+ma|!L#{>x4tvztH{_TAyn!g24fMS}d;~D{8Xcfl0MYLR#J4?zrSFNWwqe?0u zOgh1dTnQ}a?cwk*mH2zKOTy~+Y+O^@RclM{`i#{s8bM%j-xHS8*SjMCF69|j9yq<= zD2taTx9)uKx8-UBXR->Myis1)0ijoSl#Y{{LI|-5HA8N!7|uoS_ Q_n#uZUgAw*(=U8V$s9l?Lc3?!bLaLz312Anmh0uI zKN(vk!99hTFSaH=JS{;VcKg?@7*VVXR{?%hksdt`fs{^t@(ZOy;8acXQQGi_N1H zzk)kP$E`mTQptNvXq8|QqeuR0(qrY%t!=I-y$K= SZ+EpuQ8#siqWES^b<51MrmNB(D7D`(vwIhx^H6IQmtehW*(LA`H zP(GtFrLUap4`(!^2Hz6im~4_w-`n-S%_4w1ZiuNPs9HK7r&RpP<=86qY>8v#Tzo~F z^ZI&N^k;r}3A7($P%zS w_|`wMc#0Lq zsu8w+*W&IUCuYNlg6&FAr0Yv2O+2}DmRxbhAP{C_yNk Rug;wK@0+ z7ssyf_r4las%8!Gmeol)QTHO}@gDvymnIxvr4C_4QgpPCJxj2m@9)~QJk9)tGgca9 zUwV#}@<-RUa4wdB?7k_RP0E?4&LY0QJC8cB@lLC9zND_dp3-9JKoyY-HK5i{bJvIO zS>E>j>syO>7oPNu;(1ZELZAgsEp8e?MHr5>8DBwm2|?iB-DoAx9X~drR-#`K{bWQ& z6Mdd9pwNlGQSdAcGBbhxw}rA>5yevfA+6MiHFRVS>W~^=+ejpBLm2y9E`;tcTUS!m z6szx5 VaIDOeA3E`bN2lTXKpsbN=zM zn9N}7<;6j%0dge}NERob|4A}QN#AFt0pZZL3Z&k`^=3o9CC!}tyW3C4ZIa*;utZ!t z@aS6#h9HGwSsGN%$B*^$C#%6_)3%hMvBOr7RGfh3yV2^wNQ>_*%7}XjZF >0%B<98k&BQ_g?PxNXf7U1G9HRJ{rx)r)@t*sm+S7&@7(yM!W zX{o8HiHVzATcK}lKskm;W}kCH;?S^c1DNA^gDhN%U|s{TsBf-pFJ$`(`H~9Q_A4qe z`0oFgpgx*Kl!{yg7BFFiOhT|eb1RrevCW7&Mhl;ejw(NX{8*}kXz;PCySpA6$^87h z2Od(+jstM^4I1EOE7XGAezf#Ox8h(aRUzg#3_!bAkBmnHi|uc-9r>&wB#^1IHxl~w zE^J!S%<>W-pgd8i$sRwf+~MKk?5w1s0@o)4B%_*F{EP(DKlD<4>J2ssamE#@=Gq(E zDMiNUaTz-v`RTdKNuxm#ilHLD0_V%Bf36Q|`^o(zBXWJTBbMyuL4QEa`Fb0F^fB%E z@iBqg5)m=6T88jsnH~>4d9HNsr{@CP+}s}a=-)yV^w8SQUw;f_z SljLg-whTIf*~^YNoEIbr5mm zGrvL}1lL~;57et#TU+~m`*US)x-62Gp9BldlnA7L9@u<>I|BVw&~5U{zcKo;79&9~ z(VWm;IW@#nicC22O@00vKKA{;Pe{6oRU*&m<5wc{+Ry$__PO}c4}Z|o)P(T!_xARl ze4zFx2LSPaUjP|ssPubXlx0)8!tKE7AR388sMqoi1H^Qppe?6UBdL_)_@9y%vYX*P z(=|}#>#LD{DNG+fjQW`)6ez%y88q0=RvNp1UzqOl^D`$Zowhw`N9xArLk@{T8v+pb z`YNjld&*BLCF4sKB(zq&tyKM8Z`4$2TXWAxAwos$O#d3HXB79j9vHK9T FwoF&|Du72nmG8M!C`OO zXXB3JAsmSuoSc#5)u^~ixkK40n;RR)cE{Z~PbW+C(6NP#y_!{#IzIUy4oohhQDOt+ zorL c+M1RgtYD_A7{#ys8qkP4ipkng%q^3qMdNvZQLe13JSo#eH617PuXXm=w z+Qr4iV{M%7NX@ECR=;<0jKVi-UZ@-83rR-Y$%xIhTsyh?z)&>sHl4t}N{YjZyk}O( z45n4aJPPm3+`qQgr;5jH^^-SFiS;dQ<&9dn)U|=4sWix*KZqeT )S9` z(&iHS(&8dh?>RDv`ejVNBQ 8RgK;q)!lSVld^gvSI-ma;ob#*{HU|!uP4btYKBZW%| zh%w=mmnT*B>0@IPyj3EPR?-Yk%)NK%>O@g>2O^|4=Rb!?q^OA{1nJifK >Z3q$iwxD#{>2OWt63Qr z#%2MdRZ*74`Y&S8Kx|`3)t6|%R+kK0yinvt pAuB1xxId#%dJ$;k?$uc{htP{G0=DJdz@z=o&=^r62$goiO1 z0XPF~j>NZb-vU4-Geke>uR|6Q5dk)okuk`e!rk57mlK2)pBw|%Z!zND4G5WokRk(-EQ zy%P1+FQr;QD0=o553c`XvBeoHWbJUN&7k5(Syd-gO>L#)O;b~oyREJ5*T$D++NHH# z7q&_8__(;Ksi~fx9=T}h;o9;!R2+X~1X7?l9SNt|yU8bnti&%eTRn1dVofLnPntO( zyAeP~7a&{zsTUFOvI;U#4(f^yQxNO~&mR*L^ViR# 1JGcr?r9*Q{I8`&*V@nX26kAo*Ny@c2H{829sF z WZ0($-~1V`0ZQ0au^@r6W4*VZr}O2%lfwy5Q%lLl3Y*VD>M^r47X<;qd#&>=*HQf zH6owiVb){m@*0o9XL3iZyg;be%U>m@c9#PqdQOr3$( YT4E0r>PkS zyh}? |1$!Cn4T6C6T3NH?FAO{^T++` z{Jh+#<@Dz2Y=63}SepY_a$a7ZLDQ=W&C+OE$*kDe)y>TiQBZYNXb2%%hWgahRg)%C zC@c6cOgZ3MrV%&R0vu+Dbg4QU4QAdl=s3>^<6W|+Hir%rI#OZS6c`j_X=&++U|*I1 z@ZC>9|1S1tC&tGQ=j&K`Ty-jqt~Q3_UH86(hli(%I2Qq$Xk@h5`xZZ2(r=u1w8eQl z0a~bACjRD!{pTW;O(#z|IXNg48ibA;iu-VDZ}0l%%9&yaBpPiS#r(~?Q7?olUfJ3$ zDL(a#WY(^nV&!4;ny(2p2Yl@tb(VG{BIUk8_3+)l2Gu%K$H+%d&=H};uf} WCv|mo1qBQoo2TXqP$&aKi_!Q_%&^r>*qUU-dbxQ)qSK*_BwZc{ zA*t@s!uWf ^mR~|UF3^%99jq<3c{3GeW?!Z6! z?aR`wGLE(t{atzesbWN*@=S+ey$QX=H#PjHFOG$U5?ce-n`_~S&Xa+M=o0!^A=0_( zfWD)mqE>@BpPK+tje0O7BqZE@?eOps0YUdsE?_NdsO21q5y_K)vOQ2i66W4!86c(m zmHvW{Y{mJsh4;JQ&A%MOYKw%kdI`YneNs)xIWFSM=`Hj-42={}P#|BjG7ItP2zCf= zFp96#Wdz~ohwY`eS8N{Yl>RgiT09P
MGemd`% zePt5-+yZp0?J(RhUr@qz!Ng+X@NLiH;L2j;h0 bar zez7yQ3*Eo^i@_sZT(RJ&z{g6=ER1Wy+<=8K@&| ->WrIq28&U3F|Lcg;Qhd)L|y-<)?WA@c|F=0Ikt1-5|&E z+yUqS-a<`H4L}0p)@8_+Dxjyk)0L>JisTIu*(93}_je08Pp%oz{{~A?>Q9@5VJjR) z&i42Cy}UpK;^)@_@Eu7?DAI8tkF$hVD-sw1fEG6W9IGf>Va&idRBKgWbAbwKpN8v+ zJ&ylphWOze9u$r(9y1G?2RV;l%U%ve4Zif9SiR5IGPkHn@0gd}+sx=7FeAVwzBC zm*Xu1P8(5gf8^}-`9pJ+VYD;fgZcb?#=Ya6lg~*n8Mw#{FK5-3+8X4Okw8!zh92a@ zcykOlO^`a~`zvH;1)%8)W`?A`rr%&4pU9kCwdlWf|4=$BptaIJ4mtmqU6r4e+gm{T zY;^kidt>SDhWeL7lUnZ|Ny_HmmtgPs0~iBEL{)tKQB-IH(@%lVPwS@J0e9(SFp+?R zlX;sH76UM%lg*GT7jMQeCY8g%S1($~gt#f!@eOn 2J{ zYJDAgwtjraxc?h(H%Zy7ePhMV!k7V9q}zQJC}*t*el}Ta1p)r23Pzk#p|S?L#0Be? zZFZ4HU}CR9)>L3g)3yyg5S}m$_$53SuOQy358n5&Jy;xLeg`!qglFOiIv4a&S6ZhZ zdrgZBZ={OEtv@^DIPo&74}!ZAdZTw!>yZsfRTi#qUW!ta7Y Di)9k?Iqtu6B{8;KeG{gbs}i{GEg($D1f3mfxw zmmR;}=j`5z3{(Q^A9&( DZk#g9-Ju>4(~gu(A8(Wq_jcE2 NJKYO+O~<>tbrW-)v?s3TXlGezBB zt#o$SOgu8t+-L$Y#7@uy{7b CI||f1gY O4kJMC=lD;Q;YP}9+YYUNB-iRu${#=cLyNh zQdCsDVF6-XGPm(UmtU7${_x1i695S{*v$ec)9w4h#pQ`*sJVru +{`CpI^(qzrOeN$(a0V1||W&vw+YzE_y^Q=D|x(zX}9*K0ZDT z4GjRxX_Za;{l406TJc3VE{>%?{&~2BaBw|bY+;m-7V|va`0@QKKpe<$fZ%l3bo7vh z#s|Qaz@vcXnDgDwT!xKez`1Z3w`r$0uv-pYw{AZU<^ePZU>}j!-=50Jq4)CMKzezB zOM~xe{f@sOb#XEnRGWP)P>jDkqY||J0z^IlNDA4{9RrL3_(vsOs0RFIwFiyiiTC9( zfTAuRdRSPj4aG77b%7HA2mm0JnS~|miR<3Ck3lyyzky43vz1HrFNk}2i&ZlLZ7s+- zIe7<6UM j!|T z0oVS;)>T{k%g3Mxcd*2L3=q0*IV?6aQ$+@Vwq2fQ*7o-HE-o(C)_}+ON=!_Al{DY! z17K|3vS~nC^FUS)g<|;%gXkEp^oYB-OSh8}ZoE&X8dP-(Yt89-y1wA|RyOBPK9{5v zWoBgjJcCOs7UlWkc|me=F%4&YT3YAMkq%j;?2qrdg-)o~cp&xm&jIriuG{B7Q@d;? z>4iH^Gd4!Tj*knr*=07i*Pn&xeg;g^h?~6Y$LwwA^ROWgaZtBo6ID<;3ZfPW)`gmw z WEWO4561Ml!3iOtp7s0~Kbz#;Pk?;dWCrL!g@ zcud-dct;c1wG8T?*SPFX1qTNMYO7riJDHIDvk_9!`0{I|LBj{FVpTx=&aSSv+u43` zad7|^CBf P`*5WTko%Tl(<^cB%hw%V7tz!rPTvUtaL)@?xAgvD9eO &6?R(V2WX@Rzb7|ORJg(9zdt%JH#c?f-Yr} z5$rH!8H%|dZEbDAzQjDU(d(1OAgiPEk?O%xpti= z+g`YouBV3<^73KgNv)loZ(qNbNt4}K?2?9%?Dgx{Z{NNZ7k~Dj8vz~+)N9F7Q5z66 zAMY9>5GYa(Jp;~2n8>5!W7vm04 FFsfhk h1-2SY07dF_zfl%lGt@Fi{$ zI=g?KkHJhYfuiXaIk(?z$)zh;CNQW&Ia(pKQs2IP6PJ+Kfn$5y(#B?9-w}{3KyaXi zq~iolCn}D>as=MvTUcLL%l+7JcxbPF8&2Kc*|~@t-@d(#Z&)}2Ry&YVNRXL%17;PB zt;tA%Ap}FPrY9iGZSW-)6A^Jf-D)x&DQIbLx3;kfZP5XY4~AlEd;56CmqqYww-=1m z?)oSX9i6xCu;qL!gly6o{R5dw`LJJMI$=GZ?Dk4$$Ym^SY;2%V=MXl-L{uHGWWaUa zNJ}GO`OC`>z~BoV&4r+#AfuxrO983wb^G2k*He2Pok#eFoRQw%-WIcsd&|9xKoX$; z9pI^Qb8}(KxbF2U@L4b6k&-U2ua75+1-67dHs*|kpU~3M4y3+&NJaHYclcn^t=e&K zr9z(uzqGUz0y)E@?Ri+COGCNZMn>u7 f((&7&8`35=h9XY|qWDiYLL#&&iGSFYWLC<6=!+mk2j z@Ch9Ho#3Mm!L`#$Mef3R8fDh>?Cg1R8S>yt)?v%Rq{3dJ4R1R;J@ux8IcbmJfS>A; z&@wS)z6qiAC1$zLuH*eG@n|i-EBS4VBX}o4iqQ9I{MJh{U?ylKf3gDI0u~F#3U0B; zpyx9s$Hc@0%osR6a4q1k&rf%{z}|sdP K?1uXdo)I`mJ zy9Kg=qDd}Y)(}hxxUz%oZ3dO1HCR#uNDWG2SXID@z?}p_+^L+^+0h{-B?SN!SPkl5 zQ&Ur5?+AL}Q@~JBQcy&Pg*Boy5@cZ6=!2<%d`MdxBR6;0EN`K5rPIL%nD!#$!Hazj z4o=AVFeflj8ay@Z%-a;aG6}*Sz=yzj%qm5|D<8njwzRZ3lcd2=gVxvuLUo`;>U{_R zUBIeX?1~ctivbG(OjRALSm6CzI5?1N*xBFDh9wC;MIPuB&Dzf}%5a--?iB1HSR|1D zOM2hX*a-J=0w6(9P!PQQ*3M2_dwZV&r=g)CQyW0LSY8Y2r%#o# e#A?B0)k$+ZGUo} 6GG@R&yI_NIKZ-g# zJMXRZ`_R$Rr5g38! QAtTjaq)EZN~mhF z1D7Kv0;@_(OADut6KemKB71P8xM5)TDHh8Qo~gJ~5{`tA+% zTZ(kt1+awahC5JT4(EX<2Al;p1&Aa-vj+zULawL0C}EPjcfb1LV~E}mEM^FO7|YME z;(m*qYn70kT)(RQXHQ+#(Xlqe50xu)Vf9UqH-yYHYodf4j@N2xwbh;#i%ML^cy4&R z4Om?DRM~Vexp?n@(gWvYZM~cNF0rDr@*I7F;Nd9_fI6jMFRvysN*8OmFZ9_7jBP2f zCzhqo?LXOIq+!(7`qBvS< !*nFhJnWS7v9e;ED {Aj%6(wnV0OXQ@4<0! z;09J5umx~U7p@Wft?k+n+k*%3dL5B~n%_!D%t{48o(Zt-?;ZmH%42{^b8>PZm? 2QwQC}(mtE#FhaQ$TLI Dlofpc6UkgsR|gSr0pOE>*q%q#K}pMnw*nVX#|oZVDPQka9jjC%XPN?X z;*SS5e9$! 3f>@Z_$FY#VsI@m)Q|+ShQWg`z?6U)14{wmDe;wmnac?`igN^5r0D2q*cDZ$ z+ik#m!J)3M4jwdHt=zUFlIt4o{Zl|ER4+Yck?I1htTG8Q))2*jeTOIN>47C5xS-Ym zI90jGWEkSZp&`YpCgc9}{v0h(+ 63x)GrFfp36E+AeqHQf=5HhbBcaeXjB)R(UZ*E>~Q#N!W- zC9r~!U9iYxEL85`OR0Ht72}%d0KudYq>0Wgk9e*@h&M0jJ)g9_?Ccw|IBf>*N`%|PrXpSOel;6DXfK#WVrw4}H1ol&s)etLAzR7SNV6~lR z%J^0Qv0;{Q$vB&nvkuMC&NWO_zSN+#dGocjBRytRhrN Y*X6%0TBpU(SjNpE$&ZD_ 2HQv9y6oKr*@C=;+KBw@JeC&zHoKbY}-N9eYG3?9n)v{RsIC z$jfxKv>d_e{hDuUXGh7Qhf|#;!!}EBH`e}$;fB *mf{FN$UvYup$sBJc(_b#X@g z&*|>Y3f)Z6&S5fXpj0+^o{;YBf^!qQqjT^Zq5zgvK2t`I!QGo4CAYB_>7qZE_bJnfDarJOw&PkOv1R`K>o*%P=vt(B $hj@sr7aA@_AMcr8)(E72yx| zFS=TBGj*h6NWY9HnbX>8`1oyk6~qaj=EG(F&fM6>^ji&z6OojyIjm}BA(xfj?oM`m zk>}vQ$|4vPu|ZJw 5~W{QF)N )-J@H;gB>_wj~hye`jbIj|tX2>(=Lv)mV zFpzN#m=}47&~F;gjWM1l{k`%o2j>oxS^WaXY4PW+fS@WFMLZn0t93M6Z@*T9zlC&w z$yE^%fXwiPGYMk$jGXdM{Ld}%gxl~9*!|$+X7Xz&2;Ki;C7!gAxade`5)0nR!@E57 z^|!u`*?7_5$J`fJoRpY$co&wZAEtEw{s-C{8n8kR=2}}?@R`gYxaH0Bo${d#i}QO} z+N=ZlU{$Tk2mD(zJsNIjp+P}4j7m6ls#VTMfNOCn1^MkZqAhS9PA>r X%) z3Ty>I@Unm+v2J?`?*YdN!NSe1oyD|s)1fNFWKU}9XiL`zH92PVCD%e@Ipg797CMvJ zXmpj1)$7~qr90u$(fp9J&vNR)ot?SE&dv@wAaKrJUQYJIK{pBitoCOOXDZc=6s8l_ z8k1VMY^bD4_y2N`DlqKL&SWY)VkczyDjmUTyAvkhoO+joNu#vt -1 @}c(8hgoawjm`0C~YST)0p;58}S5T J?~fwDm{Yo)Zh?=4lbZ6@ z!Zloha72R_0;qZ3ub@Hm3veA!3=iq)mw~U@-P(%aG7bw59|R{~0J{{fV@bgSjMQvX zAYv#d0o)jT)_aCfh@$6v>8o}+`5<-&%wjj+8VV<8%Q7VBT)N<8@$yYis(Q2qc|K@1 zPYG6^KT|p^(ODi@7V3N>AwfJax3BTRE@P z>F|`|1DEPwEhQlV5pJNrKcCaaw%ckk!8>zv&GY7sI(2dgqaDNwur%WQG1H` hTlb#r zb|<_9(|p0n0B`_ofRBe4^!+ Ed018Mj@UC#lo40RgYg7y3v*F~MjhBI^v;(a3 cl;&(o0l*;ub91(UE|+ua^c>~@Dcjf&v*qw*>jcZN|&ZuKHK{bu%F6@GzFX& zsinNYK3AtnBYLyFrgVCW*~Vvm|Fl+&Ks&a6WNYmXK}eJTd67%X3*YEO!H;GW6;4|- zT)quhx0l|Ce7Uwq%P84?Gp_re{2PP!H24iIXlx+lvMhz*(qO9Q6Bwza_eY0^hbJfc z`uZn_hp S3%nvah9^U=?kkpz& zq2j)OpIKgBo}HbYo`&)D2cQ92udTNpfS#Q-ZTw#mP*6~iko<&%A0D0zFa`j2^mKLo zfqohvcd7l1=^QCrsJy KDMA7Y=D2;4p|@%gV}*kB(r0aI&(pa&Qn_=nJna#=AS8bx3E(Bl41eE61#V zXpCx)bg^0D*V&8gAoCBq`9mC0*LW|c3`ebOO(QRaRJn=yr9AS9q>HWX%J$#Cx~of= z5gJ@ZiagZ6DDZ7ohFT}P@`xAH`^ncg9{fvxV66FuhljsPyn%zW1}rPsBuYW&p(+=r zFkL7VIs>Lx^wq2TPc7#FgkJ=iii#v{dH*DM40ms`uprrWTMY`8K_TI(1rG&rlhgj1 z!%}wwP>NTvu*ynHx3>BqtN~ M__NLsHo0P4lXEN zSc|ZwA=XOnF#uTxkV$|afp(KEG6C^Lr^OgxeY=gZqX{Pz1aBu`mtt78{=kyPg5O$M zNxu*srd{mSSN?EHB`{-9>DS%e4M6)ouXzo?YuGaYIt47}Al>>+IV+Y2ao>>=V~U?O zVpvJkfxnYkltMC5ukxezY A=2}iuMZ0AUvR0*`;%;f=B~G7 zvZ!C?euT!gP#Y;IwGxW-PM7sFSDgj@Q@|`BICTWZlsD&REFRJ@-J|LY;Py12t)s)n z#%6DSKdfE&wFq!Yz?4{)Mtjf9&cYf#D*Xr=rog~Jyx?)omlm-vs2%s$BI4pG-Ew^i z&S_|9j*gD--^t5^(g_Pu2dRHjPYVkREfQJ*0ks5~lCrXIJ5+aiz{ by&s(~>2n&Dq^12phz{Skm39)`d0}Y5dc+i&{ydj4& zp&W$U!g9*>_VR (?XjM8IU>?#O$vc zugBfmmhu+F3*Jzi(P{~%+w$POg;{$!!`@ X#b_@cuTgNH)_v|5`>s}wt0arOds7tP#h)fBd3*djJ>TTWS&IN1 zO4eJj5S?7b`0<_Tt4O_tDrEc-OCll_jt#4wKv*!fk&Wr# M^T-p(2DAAc -7%4I|sz9R}jKh$ssCe=F&zvnA z1KogOouM(7=l$UF2>j%5@WYhmvd#66ShxkWKg#vit5w+kKr@UMPy_?^fy0O|7^3TP z7O4CgBJ&~|!flh);@jw+S!ithcvV~OUvp&JpFE=rw+53WRFd8s=CCuY>;trTS`fQ~ zK3>crRF=jc&@bKFV{3?XD^_%F+}z$?8%c_(ZGCaYQQXmjEa%PzsTco~7jGCpOxKh> z6YB!p(#_L#U!}RDef}aV?96(s7(`}~9eg$h{VaJ-A&q;Vzg)4hw9@|g5to9$%5gmz zf{5=)7!i|_$A+kle&|~B@o!D;Y*v=E*gF?X)9kJ;Cp%=GA|;2DV);Pcxn`VtjK6l{ zwQJXk4Exu*gF_aI{R3RBPwR&VvLZi@0m6?kFR<8lAu1um+KT-LUNB%S?@q0x!vqqN zHYim))3nYMX=&AFgq ^$7vemoyl23k zURfh9)HYSR_DT|^fbBxhYzmR@EwUls-@RhW#H}tg!lnOlBk=H7FJ;{A$$fbt|NhHk zcWk;K>`N|Br(s)H=CMA)9eygGChNLe+bpE_Hh+nz*t~~fN6=3B$;k;*+aA3})!H$2 zS|F)Ba#$x tFnE@fWldY`7lUI^V&gKC(Ia zCpF{RFH|hAmE~yk@Z`bL#HVme$NeP(8FY67K~GO$JF2L3iudBp}8}mI0Q$Q8CH@ zY6(G$6I_aiR@41i>0RG{r3cc-3Q3Pc{Y%5gD_Fa%^B6NQZoTEc4FcfN(I=Gu*OSUp zUuDXEjjjxeRFv|#IelWA75mIVHJ^5`vXUQ)oD~gZs;kt9&-Qu~Tvi i_KXO#(pJSN{W9Mremkl(9jRz*zn_`b)kGPPsn>P9#{8Tzd$Oc1@_PA| zYrE|EAuqmUZd0q48YO35yJK5THy6UH2jCm1LL4VeV zM5F$1dN(-{1FZb~eKW2b6HXB5V)~i?&!|I}aY8#mdz3%KM5ZHiU@nYoijLST9j!To zD%*R7^2$N>5^>qkaCxy)BlvXXH{ss0(pT6)^KI`26U{<&&ue3KmIa^&PTtc}BM|?h zZsXqpPr K9VzAKUWb3rEG7MwG&J>1tpd*9@f92m5T=J zP2EXWqnAwxZFT}0*e{`7i#E9F=xUrljTWOMva6?c_JVar_g&!WVT0s#7JM_gK3{d| z|G78MTSlX0KCcp8_L*uwvazt>svJ;Ep^U3)>cpfK!)&&XWvEWCjgOC)*%>3|Z$CV- zmP=D*A}V(|K}Y0=v-bUGPs7u)-Q;{>0X&Kv9i`c-{MMGwKq@!aigkgLhn1C89KQ>Z zI(GraUYe5ZUht!5$31Bu!miltt@ZSbk9V{tLO5x&{?}}^s8YuRU%r=ik8SFIFAV+g z)~?^aElW37WET1Z@cO`0`mNGWnsX-bcpW1h6irG@+}-cdFRozWu9*6}7ut+Y(1$dR z{P>Cw6=VE3b&6K{MO-(-D(!Ay(A|I }StHr1P9WI<;T2gdXZLjsUUDFZ5^6r&areZ; z_^A2m^USJBGKLBek`*=2Qhxt_>fqw{)O-^d_AQnoq{4$AP#4A<;adL`1LS h!(XQJIW)Z%zbHs@sx6(w#XxQ3VDp$OiS!`!#I{=@RtOy-?`XkZ$gpv((5l z^!qxOXY`Xb8rxaq9g_qF1r;DB@%5KB2pM^v*MON`d;wWrI<-U0f(xg?pj^kOo>~S# z74pfzK0t&3Bq$jP$!igcrdq0dusfz?OG- YTHl$aC#AM6X8{1mw!DoG0ram%2F8-$M=kiLPu2VBX+(=&jKYo%9O z2r`!4ITzOStw%;i#uwr0aBN%whxXr-6bf?*OKl!rvAp{qPY(xpKb=32mJf4;SObYm z?ic*81`Y!w^y6s8($W$H4H}h>{Q%6Gn#5>BARh@)7% $V6kX#*5TLR)O^_b8G%1J^jXGB$=CS>sy_b5olxmRoBm?r=Slth-?T^| z1l4Q*35l|6n3!xhA;8Q)&P)IDjhi=>E9|$R3>@@sieaL@mp})U%8RWiBAaCS_2@D{ zG)qhI;27`xey{DiVuWS-{c78@fh37Rwr#)))4187iW4_Ax^9nm#nNbp(Nha(XUnB- zDj}h2V4)RgxtmAj!IE87iLvNrV^(?Ik5aO6BE=36zC-SoU{9R)u#Rk#bC7Pi>Bc z|MN+YA3n5N9jNRaSY}kHyZ+C1zzi39euD9zPgKQ!_fPn5&>jB*{^wQV^m-1!X%Nl4 z5E;N$3^Ka}Q*pb>jnKSqp@U4B$?9+^jhZi5Prh&QHU5B?{!Z9 (H6`lKI>FLayyj*IVbe@gKgb07WNc#wqx0O(0hc;( L{{eUwzLg6w)E8 zmC7_|WN~r$z11Djcl}=adq!;+26;H*!|0VP@0Zd#RL7OC5)DTx?1Vpl7cI~6kFu@Y zU%|l9hP4RIWuCiJ< `#WCjpyj< _FJ$~pt@Mtr`WkHB!$UAypAfV&DmN>#-3e~@ z0DX7-kKzEL9HQ>s-CaoP{nN65L4p#}5lA1u@+Zq>VhovHz7HltX3fFD;CDtwt23$p zE57ZlE#fChl@QXMZ6+dMF*JYn2zIXDr1Hq@N-b{Guyl6eB 1`;tO=QzUMNjlv>Wic +*V1+khdb@&`=Mz{A7c2wx&QKzdC% zIhq`M4jqOo`A%Ka_4PWwPD`LbEiaF9IhjvN2p+gQnq!zkfb|m={hY)lj2}3p*tk12 zEnJzf+S?U z&wW~2-%J1GP1GJtsP0m>-}<}vwf>R+tElha9go`^P*rjL*>zJ7#iUineBP%~oGcqS z^)F`la6GfQcYKoR5l- ptJVyT@y*HdgUu9;h4bCE)6e%-$Jy#K 42YwpMVE@XB~ zn{Rxbz hkGX81s=@k|jOS-ZZaS$H)`exua_h z1qKsz)W4!(G5*K9PxuB=wWBW4`ICiBA!rV9larE^b|MMfsmU_{D?sg-4!f4p>4(%F z0}x_4s5}8V#%F9Qx=Z#B4%vtL0}{w8;(KS!8+<@qsgxZSZ_ocRN$=Wr)2lrFykZ3r zl^3oy(?*J3M>?0Wyp3uK&b_h4rnp6ArXtt60OVt59X6+j|AsPWiGhxE_1hq3fpV54 z?JO@d7nfXuOief*uMnC4tBn5EdCM#LOjk&;H@*Uf{4^tKKiutq{^WF$oKmBVLbqu= z3>#OiU^85~2wPxU_a)D1@unNX*}1fN6=*6w$Akq1{+Cz>XY<8J%2^%?&VVjeiq=Wl zQ*kdA7#vR GsR$QF4*2>O@o`J_) zT&7K(XF8(w^%s*}z$Ywxd9={fVrt657YxG~#)bYJr=6}Fz&zi?oBD(?pVy#H3l-0G z)HNTiRZGRV;|JrSLU`*vYec_d&7I<`+&;{I@f#TsxBcOiyQo2zMro^qbs=$&>%r*$ zuei|aM& si^kqU@ehwS~T|Z9v1vEES$xw{Kfp+f{IK{w2G#x>ni-sqqZ? ztUFjR9K74~pRP;GVf?t ?(P8oGyW+vj@d}IH4X^@?JhEx3a36f}Zh_fqBQ5jnd;|Jv*@96N zs<7#Kls=eDxyOoj<3nB PHka#fo`@1SUMJ%{~vG&VPvB0?B z!{vgiY?OEnNq21T5z-Xt@&a$@_0C}4!JOux{pkr|xN|B8q2MbDRa%6=V`XUydDhTT#Ig1b^EzNlu)-meWF-dGuZ)4cI(0OJ+o&KKfkA*){ zKI3V?7yVlpw_~)(CT$-P<9t>py;}1}R9TbiRhE3)yye&ublf&rdq-EwRQi-|e$2=9 zxriJ!L*_)Oe=r-45c8oBNbd1o;ymol(zX`;x_4S~N1Ppp?GXWY5T5*w5MFSO#`s7@ zf+7D$4@^bPPp2x1{$z3>x|n$ssF}QSfpG6O5L}MU#GrF=xN%D|7YC=Xz7$*Qs~pKg zc=H8e S64c+$le1w#MySpRm+a|?_6cRA^EJ#}@v8AbjuD4w)gKi|s_XGw^F z_Z(zhEX|Aa%ww1NGm|hdY;$WEets9tsmoKV`TX-eYaOHU_dDWf%!`Qd-}T_Fd)$Ah zCCI{F g@|dC*u$jz57z#Fzu9C&Gz8LeMVn~yY5a*>^h7k0>H$^AzbQAhrR8Me` zNlh2xeu?t5aC~v4;U*7hp4iRTS@NqhGk+{pLGr1}EEdH}%|fM=q7L=<9>SkK-TH53 zH{8ll44B`5**QxGD#{86i&p!`GhE- QIN!p|wT>x<&^K6oYb`*~Ez+ z@V!yMI%3N*RQJ?^qHi~;1kwv|{GVTNpV>QQS^niwZyNtokGrND^S`$dKj&qsPx3he z10zYUk$vwJca=(p>5heP+2#M90KcBf*`nBu`Ml*r2?m{QbN5AI6h_StXxviYG6GgC zkr)HQ!;5b|-*O;vC!oT>tG{@69^zk!ckz8Vo?n=|e{?GRBl8RgYJ@J1|CuE@)~j;= z_?iD%BzP1Mnf+g9{O61R-jloL*Z;WX|1(4I^ZzqA7bgRt`}aGaiK+hm%j)&tx3*zC z@2Rn%AWLA)dh9+B{~n3bCYk#Dr(fhhMNSyvjTPTn>95(BF~3-c247n_>)ydiO!xov zpllJ+9>TS(IAjAGb$8N<{thm%MnaJHl=_zYs!YsnQtv m( zJDP0G$p7xj<6Ue5A&IDOBiiq5YzzvsG;Y*j7hjuOB*$_b@GXn~-w}Mioz_?(PE+C3 zl+W`(pYWgocLHl9ERnLb!?;$Sp#h_!X7#lqYf!d&GbS|r{P$5X3+_iL#yf2!UNsRh zzw4z(u%yRDkD5(a8!+AThxOm6%XH^lzZ@pYtmfnBaOk!0!K 4c?ok =;%%=W~tYQkEvH~(OFfO0mrU0+q zm}t@SovZph_osKOhnLx8K}mfyYMznD)Bo_NMlAo58uQpDbN@W5tsls?~ fiG)cncz_xxmW4F~T*^vZbA@pCN&3tu58A%6a$j#>ge! zD~XdaP?`3j;7Wuna_Gk7X@$$;C3Iqn*GU{lwdqhiw=z&GnX*VI>$)iQ1|&9 k$ScPmP`Qi>zZ*9Wq>-p0BJe%9TosE$~Q_dxtkCt!j=Pu2?Zo`MZV*yYJ@UPMY z&=UxCIPaRH KCp%4Vp2e1(NMPuc1VK-g1}N+!DUxx+oJ_ zbi=M63?MZmfgR@yi8EYWJ6zlY%jtxek>XRc#<(^G#G_v+xfaF)ruVjvHT{6b31vsF zY-yl#P&F24U}g?9+*~yh9d!%9ekuOQwcq>ZHl#<=*w%&}NEfddFUtU`LJkfNsJ4KX z1BVCBq7+r*Lvk`D@eotSV}6;jd0{~j9vj >LtN-zcYI%%YIy4q1{Er zc^xBO7D``FS`H0AJI a|ZCt0Wt<4u2g;3vNd5}c=q9zYJn?Wur%>QVf z&yF(w1vmmPX#N`5 oj5urcN*j!bWiTAd!vI3bjn+?6z6u# yEq)6E_yL`hlt>p96P zR81~H3bSg3vjE$cgSb(;aKh&PHWTsvkphb|SC{fI7GgcsPeO}NPOQ*EBD?#k3PO%3 ze|Q!tmHjn7^HV6^gk&; O1lx #dL1Mzl&0m*)<3+?Cnou~66oQ`vhlvO;?DOi7Jpb|1Q)m8_eSnrP%nLRUj z9=Mt3IIDFKTc<5_9-x7?>!;vo6`YJ)f RQVKbytl$wYQOwKh8@0R7 zYker&=g-&DLSN6S+iLF-e#>wy)$Aa9Jv~Y)CBEP2;2y8*O1p >)hg3ldVnbzDQ#j!mqk5W?yYhuBcZe#-9KxRj&<99bYAi#l4q?qBP2~P;wgxF zlFsk+c%l65t?Ij>a`nlX{`fjy5A+%a<}O)k#crj0yQ3#=%cB~-eO7zLDdOoC`pbml zCab6S#J!zkm4_M$?}Pa7V??Q9N=5i@N~3331&G#jt_sm7 -1$shF0eiQm++ Z*eDA`|ITs-1~9pov|ji+~35VQX!#9u0-MH`=_DR zL|GJqCf!4U6W?1T1NwG|kuFayJb4wAZQs5%GCKB;vLimv-$*=|u-|ceDH+pHu$*tV zF|hMl(lyo+em#E?$)udOU*0nN%+5r2r36S_4e2ABlx81x=pS?B&MzSZc>MSvJOW$cygby6>8|oa`ls{9{(QdSXMCw{T`WRQ&{O+;&NrIJ zDUwlwPJ^*^-*259{4M q;_chg`!-H3c{bx^1!sDHykKcZ zJU#aC9juBz?42d?*~*!Vv!Sfwo4M6vztml&8^NwlPj4tvVA|x$ae1Iq&FgRhT+aGP zss9Tn^fRkzva|iUjC(e-ozO$*_S)nQw#0g= k~EWdz&-+}Ygln-fjr*Cjs>&(oZ&Y0@FDU@8D z2Kix&S7N)WF6IL^`lC@4ddRI?1cBbs08sV#?P0uM@$f{2wdtSwI2tFb(Ke09C(2SPmU8?G!>oCJKQ54b_T$H| zn^8!v@+1A(#-Eg$U2!P^XC@=VI|^U9Aj1ZKyaJ=aaEy>ny1mnU$nM)u-d)|KKRZFB zfOIJ*(ygd`|GwAT;3VLR-qlPsr`y S+Aw)s?&5c+2=Y@TY0>+g@rU1%T!S87nDymleNz+2=yqBoGct^y zKX>P~ECTWdiG=EK$cj9cj)jIO!1?mKp;eRKhe8ny(D&dxjS5FVzTVXp=v|;}3b;U1 z^8r46sQ9kn11B+fiECKkR~q@R35qXjsLSi!o;n;;SC1tc^L=PwRx7pG?7s0O^i0&2 zag}G|RBr{zWW2GS9i~gnaxysHu~*4fM}w_eWvM; 9d zLk4bcv>U>opiLd?cXF~Dr&%kKqiSOyDQm0P$fi>T<>D~?*XBc040=aZBaYq0(Z*7S zWs|VPzzOp&2m9oa=FE|=g48*Q)p_=$>+1g5k(=ux4UPBD$NNgB-tT4`zJ0N%b7e=5 z#-+IFcIU9M PihkiK^plTqt?FpO=On^X}f0UJai6 zs;WN(%Iy>@ZL#s!hio=`17AgyiJFa91!^q84FaoOA~2cX7rSb`zHInQfJLJchT;tj z#j!PE)x90`*D+Q92)#+Ssl%W3NVL*v)QdA7lMyb55yS$LJxmv@0|euH AwRdOigre=y!s=oQ>568x(Uh03oINr7 z85?{wAvUR6V&AcLeb@pXztl~V<31Is$Ed3P{)xfb-4DgSqjtZ}c`Vm{9SKDi&xX`T zuACLSHm#2yES|E-rN;XD<~beg8=sGhi}2aqyKtvc8g|zXJ(y!gy^-a9e(eWBu{zTw z O6cXQ(H7+6ce= z;w`TCOc;aWk)97_MSgxh)H5q2OOk^Ak5v--1VQZ(h%`Z_t;!5lO@}@NkJII|$e{WM zDlCzpYKB@^qrzu`f@mm%dHBi?s! 72`D!A)B@4+JXC;!1Q809 zob2u6&pti$wC)j7BTrSv!Y?85eDnHsiTSiJENDt{KG Qq-@l<+{{+f~q0B7}8uNg-_Vnz`>lV2@=y@TZ2^uD#{yn_B zpq!mj4VPkf6{F|o<*>7(y+BbAR*Br5Lh)E@B0e L>by3}m7(Ax@p>$G zRYnKJe=P`Le6L>nLR`j6;mecvkBoMbZvi(ODz5v=aRHP&e72N#afpbR)pr^ y+ !?NIVId6TK}L y;a$h1Oeq$ZV?eyMoitiLhKN7*CUgNmWG!4L3ifoW$ zVtw{!N;tFFR$H*k?%a2u 1Yg{n9YWyMOD z_fceBye1?>X q)d)w&g(SvzZ3?FRHpkS+2I=Gd0}a>H zR?Qus RMi=yW zb;iPL+pna*(-ou@D#>S7q|o~ZaiaaJgT>e^ngYHOyl&G*v#)HDvdf3s6s!l;P|U|! z$59g&8kBvU)k A>@$q^nz4|0H-RJCd(C4 zgcg{lkugt3_bbmk&EIs5jX&e-6|B^9#dM!H=+(QaqpRy+Zx8S1`MNz}lcZj5D`gT5 zuR6H{RfI%D3yX_(=jbzX9y6$Y4i5Zvj~x2{EHBT`hyMHt)IgF99kdSkm6#a*L}g)h z72 9meqAdl;4D?WPfWZx zKMyv|1**68yJA6vy9Mb0Ha7Kt2Ndo6>r?m(N;57B$f4sxf4W?6Z!ZAnsZZW)7;A#* z4GKZOzhgT1;=!w1Yk$Oq5S%cfYZ5{b6#}k+th}4$l8$$Z=@(*3VZWUVAYRoS7pker z&DYgU+&>b3ZT{fAe@1t;Z^Z;kCO1{eo%bcaoQTL?x06eoV~;2<)(7)mb$oh;6m*j| zCwpeI@I5WDSvPOETI$GbyTo(P7y^C=`w}iQS^bU7w8}EODeOm&Xh|zlCo4=*p&2|! z^S9R{ks2I!O0TYcD~6Bwo6y!tfK8NS^Tq(H5Z+h^WIPhn~6eIE-a#&=wjDbPuNZ zwKgQ#W*G^cv1-Yj-~VkD$)Ww=`95pWDSTu^k*p?6t nOknX+wd<8}X zDIKwI8U CQuRpSv6jTO8Ru( 0BjC%&I qeMK)?DlncrNXvh z4ZP1GK4@Xat4wz=tz7ZygCR`Z5VHTHX_X*-0_VJP+p#r`I_FySI*>%XrQTtqcGNx$ zs$Mwlu28yyYsVuk9Vq{VoF(#nw!7AWg$gYAAI4H0i8;aie&hYYXw; Kz&$)U zB&>zcy)*kU*Juu^)$9a3J+OETRTU_NecfINivUVgt~?E_I&O*)T0A*ud`&sHS;nYg zJF$jZ8O&(WuuGh<_i GEmrm+O~nl?e|Dz?&MLC@2rv z;;6Rwul-_?uh#*^*z+h9)Ki^7kv$J$Vyvkt1$N zRG@kt%B!Hn8Is^oNWJ>6Bmx=&LIp3EUONfYdm*ZhJhe!& {q^k+8?7 fUA znBW~_xpkv|+YJ4DhrGFdbdcv}n*KMv@aWdb?14u5BeJ3q6~xV3<$>oG9i5$M{dp`` z-O6WNRc8wXUwD6r{FY^?iek@}_h&e5n$O^l*k*E?PkOz)^ IcTv z%hqAB#CrJImV&QMlmbil!bc#(j<>uJWW{sDvy*9Z`3&b4y#oT=Tmf1AD!();n*|-@ zC7cfKg;s17q4*!9*3GQGEeS|h$k<(fv^-lyBe)?%8>M@g^C{W&_thvQh?PK!7xML7 zbHX%1+bit;#%q<^?)PHr5YU v{a;yu_6n+ &T3vSEE9Q0JVhC$$oF zY;+zFI>@qC>Z;Y1!6F*TkZ5VKwYP`==W!GRcaDZ#%O2swKOT5*4Gl?WUQhp-U4oMw z?OzMo >V_F(M*_ynx%hKAj*~@R|I;LOFC9k`B zye+j~euns{dCSMAMFBbfH|@2H6xW)&>-bmC%KJ86-oS9Qs(9n|@F8$H2KcV_&&Cvc z-FU^e$o$$X=DRGR58#0@4mbYaA+CMoXk7%Xl%84rX4bbmSxZYzjp#2IAGbQP<3Ihf zedt~FM^Pv4P)oH()NV1G^^i2`)k{_>TdH3ht~zZkRa59(chpTC&S-DimeUhhWL9lK zb@U)3Ry%saobs#FCNdvvbl^0p>E ?NAAt&-8;Y%cB0y(^>2}t~v_=DH;&N zYn; roQa*27hIc6T39! (O9KZ_(5?; zM~AKwHz%hx)Zt%rynX%JU^ovbGhL{b1o3SMlr|mWUT*R?8q2uBmb3QO#N;$?=llpU z+X<*AEz8LX;T{!bEaG9VasY3?B{-Yf0%lYpdxF>D7`nmcZSI<_Y?XsFKY3q1*^d~R z2c78OyXq{RHg$4mMg1J%e)MQ_rBp4s%Esc|K)O;#U$JYI?jeR;#g*`fbk8vlHF+EY zP2c0|UtJl@dUIqrm2Jl!^DTJyA@w4UNk%d)IXCa-_HfJ$m%epGkE)tnmNF;1m3L8A zRJy8ao|UEd(y2#=sWKPaecb-`0I%JOt$6(n{3+CEnOq1+;|O~8?_I;eDfzm&?oT!V z;DRAjh{dR@QExOqHdTN{yf6hG4U>RaLd0Ve@ChZqz2pVUHJ*=x=wdIDJA3lCMI{j8 zL`(=xc&Fo+YixEKuQW;>cR>E~xwR$N_OOuGiMg6bMR}=%Rv4jPVxNUq8L?X86vHZe z5nOPhnhMWNqXg{eFS;OYX}DHH>&1zQ*yJJsw`t`Uuu|b=lgdP{R&3DPi;Ez>5(23| zR^vz53T@%o`N_JONOiT~tc|3$clT`iu{RG)`}4LS*XeE0)cNIe?OoCavBB~UksAbL zT `#$mTpBv6al3hL;>l}B_& fy z7f<1digJcVXMm8rmZ)%q>7A+uihnnVh!|oFyrye^eMtr-91y(VCBmZwmNtmC9M7+w zc^kMdGl4GnEm*ctssQ0v7_Nu;O6IU_@+rWQ%MvzE^zcO5ceBmmv~_iDE<_pP6A(nX z^RCd{xS=2e1FWDrllv?K*slA7x@YK2&_Lqi1g074Ag^E%9(^2ysqu^+3n Xh&-0ykVJ1NB};IxezEYjW1fVM{aC2Rm`(Gp?RiB*V2W4Vr7Xyq zN3ccIc+cSzDZD6Wo13#<4Ehqsb@Q{Q^y%Sx8xHKyB&cl?CqC1ToTxY@T=sSNC`eRh z9`(B >IckcW25znt(iA*%2 zuXo)0*D(o9Z;E+S+0LoIAUQ4=W7FN}d=$~Pd!XDkOZ>yVPw~avz&d@VdQQ3BByJMV zB*cBk^BKG5U4{%PkTX{wA3y=(qN!hFmZ-O6XjOy7u7jE(L?+Iq$jgD-cQc1EG4ZC< z+Y3*M1{Owbw`5=SO?##6BU4B(T(7Nme|?HEG{G_9G^8mZURkb=JiKX;KZh+rw^+6^ zhUDcmGBeA7mbfMsR=zE|wTU8 t;v2X$8L%Ec|;5D3X9}X#^ zw`Xad_3>Y#&V$r52Bs{bgO2NgK&k %6jWIjkuKYI0{cUYcbJARNH&KMEWn^pQ z#Fo3y;yhvwi@5Vtj>~nPQ*CaT9w9V4bnPS7bq>Gx@*Gj8;oG^|-`l|d*&N#%|Je9= zi-~Ey+3=EQTlG1^OnXteUxA_LP{oktl()ocO_0c+#F`y;jyjFT=FZN6K?fpHx`_#W zoHG fm_ynN&oPmR1Wjy%kf_#KCqg%uFeIcfn+l zkmLDw*k3nZNli_4IB}!xPh52HD6}8%kn6p++xwq@XZXaoQbacsZQA+uc gcdW>eQyQFQ5nxt #o|%RL#PXJgIGQ|gA^=kny*z{-YC z( AT3@8NfDI{X`3aCg-v+$g!oq Ht}YOYj>{UUo|C*}_};ZDA6iLw zzO*nTF c zI;m@6H5Xb(N2}SgL%CCt;~GzEi9&VDuSK%GTzj!#W>!UHP?s6?UE)GtFMPu3I+M$q zs8Rf3kwfz!#&!=sl?aWdOM9sHSpnf*M$z9~QLor2#pi!>-<=8NJ*g`EQcsFdZAr(m zeBp=@(ueUXYWwCE3LPmD=UV_L>)a|11t}{x1%BOo!hj@UcSp;vbm!hetNu>pi`f!I zvDw#i)klxF)bAK_WSbx2JH1lRqI{C8=Nc`P8os~sao^R$4f}~}iH`O1_AL41@u{i9 zt;7L=ahdG#nm^ucgZ^Z=d1`7;TVFZcLsHl6uZ~&okKU7Uob7) zy_9-)AZnrLp>_?_$Nh0v#Gb2e?(Kc tn6g8e=4+&pZj%3|_9jXP)$|A&ZNvGg-TQR%i34xqb2Uqw&5_oDKc?iL>_U zrp7H{x04jR30fh?-}Fzv6%<6*Bz0Bmv(btnP7XFied-=v1nuRDeUQhcQ%X6~OKmyB zhVH4v^=nttpToqNMYXqXF)T+%(1_SCHgzJUGj}%AK8KNtKq%o?Y~lP%8>iG18eEZ= zKVL-zsP&rFRg$PEDLwX1G%@?aLw!I;s+#Xl_L_OJ; Fn(~2^9q~$EErBnn($^ zozDTp#9;)q!mUU>S_ylN^0uqnaOxdSsc6F 2^Yh_o34fFKV+7aKt3VD$J@?tnMinOLZC=*R zy~EfObzS;lH^Fb+Tifr&%)RW!5Z6f6=Kr40N;R&`dK|7gVCLQClutvtVR-w3?+9}Q zIg9i!4@~C+%)0MFtT(jz19<|Fbpy}WWN{D;+AtdZ`6Ef=2_E)dI?BQwQ_4lC!Nj^Z zkbU~vGVpV&KQAgNb_L?37mkrba-N=W(sj$-qd$u-=ApFVXN%!v@{ej3H$JRzI@T|y zON*G58h@zTZf#T?`e?D&qABiaD^rhAqG0~O@ofIMG)MUbm+0%YV!5y=vfLLzna;K; zP>}ISsNl-;!Z;Ys-F>+-mL6CTl~gWvNGBxTGP(W|Pu%R+sjRbQZ@EEN3(=Xf#EG%e z >~KH4HcI zQax4f7*>HQ*pqWna}$B#x-Jl}^P4! ZjMYL&dlO+)(cPEuAkra!(tM~2M><%6?bDbWR&Ub-~? z3Hs*N{{DRj(me0oG 2c z&gWE~=f$C)tBE-!Yj2&iGRDH$-|{VIq MbY& zKu_TNF0azpor~YzdF*m%Vu-q*&G$yIdRMshoyZQB{zj#49zEvC)w!QyrDfL BHR zAU}VarSf3StWwNn^S-FYP$EvYjY=Ycc%tLhmcp%-(TdJI3FUBh5_0mjnVCO=$%IV? zn|B@f-6A?qE#JpveeO~IolO@M)Bp#LpCVtlI~aT03f%jx*kREBQ)Bu2;ndM_8Twgs zlJ{wrcNO%KXp#qg!A>D6OarUREZ;JD8|-YNVIq{*$UCX1tg5?v7w3WmSiDTjpu!t; z 1?@1Z;s;FvP35ohG3>-v--Pk*}`=# zFApb MUNdfKI>h6^r>V&c4@&2JQ5wB`jpGAR=>w2nHeV&qL^WF;O;#!D zJ`HJ3!|K XpVde{6?Uz5i}(*l^-)bW)T 2XIvoDPP(f)odi f7Sm zDcO==%c~JihJ?zOm~SI+lUL@VAGiK9r2wm~pXkT)`J$Kbs`o~GF%5YW`e^F8E=jB} zmd*Qkl&mfi`WG?Sk39B?zNSWS2{5aigLwQ0w{%{sH_C@3tC=o;k;^{V1D1Eys}Meh z)i YdvSizJms<} z*3za1l^tk>vfc1q@WKolE&0nuChSys=VWhX{Kpf0hWjV&c|PTE !eei+rMdnhO28XuQ6V~w}=`^(!ZswJ9pD1(gow2ZM0S$Xdr zy|l??hB&%yAWq`qWN6jaGxkjMeUg_q_Xcg?^_@NH(y;D)=w0(LJCcXgKw*%7t#V)F z1n-8| #onr6z3lpNMbNr}jF*QC9NEm3_#cYL9!>0x z{N*$VQh5kzDRd`6=_(eQ0d3RH+9+~0%hIMpMyjnHj9L29F1Z~JX5kXCHAl|gvz-vQ zSsogkXLYb9{3NXLY%ld9xY;nj_$qOUxFBXGN6?Vm-Jvg1?17<&&*@`(+YP#%l~Y}( zHm85C6gS=E)bC&-yug=k1T1};QrsO5E>|NJV?P=Rdz)^>&1{`4YKDxLa4~0KU@oce z2dgO8a&?%SPLYQYS_;q^yF2t!;7Oc0wh>7Dsc>60fNrcuuSPH4u|PkyOo04kq3m~v zkA@vjN;PbMEw5ac3&tc!_#s;Jn ?#vZNbk<^?I#D>1EI6J*FhePO9 z;oO-C27SDA7m*iuzJHimb75fS9rhu_WX(1Bb =}K3FB+yxKtsRLxOg1c2RQl_i2xiQi?`kE@ms&iW4y7;i;9 zf`*g1sdKv&;2v}iWE&vgt3+6QuBq81=o2Rqhw4*_W=Qauljg|p#w-dB1MB6x9wzx| zuNSa?eH?`2a4(Et##;~xe>lwLuAx&QIMl9r?7lj|395TeRrl* EDoa%BJoiEt*+QYkifOI(g21Q(go5N2j8ETX+S8`lxNab4K zr8C8{Rb;7%(~Ri&__2b_d%6$5PVup$t^frV)|P{Mg=p|cetvJL97T(-@1l&W{K#bK zY^JLosZ=-Rv3{x5G^Y6F1Kj%rvXs|l{07OSw+o8TIMl_LC1S*E+MC|5Ex=qh2Kvcv zspfM9LuesZ#ilDY4Dj>&00SQE`PqZocjeh*MXEk5o8#^(C5V XV{Wv8p3^U!=A=u$fm^u{bwJI_YZQDQdcJu5hiUGOQk?Ca?GgLJVjgTu78 zh&GYjb4Z|OJ4_%Z2F)NYmujhNY?m6WmBHL84uWYHAjwA NHt!-|I7S3f3bfQubkdvZ@9aJcbjpR_yW(@f zMG}$}0PO_G7Zs)+MHUu)nXOPNz@{@l6WttKKQ;Mk)7U%3H1{=7JwGX3g-~=3m#>;k z(Pz{8n2qF!V+8@f!$KC*Adzjsg1 zR!g@ZL;=L&k!%#qf3*Ld#i~PTcl*-*9Joye28m>C9|nFJ zq?#hhm8k~UK0o!(dbHo17ZySzQuVTZvR(5#h%oc( -O{Ck&pQz?U#ApWUlago8ntg|&ESq}J Z=DW-Y&TiPdDhfx{6 2B>Z zZ&adatAq`-I*pc !;e5Y4u(0!5GO59+C#jvk zar{9FuTOBfKEmViAWu=zCr3L9nE6y;ubB1ln;ivuhUs;0r5QVu#+>(8Gn;?U(ghCl z7Ca+cA={6;(A`R@3}7P~GM1Bjamv97y`LahVWam SK6A1vhkabP{v>^?&YjeNprarOTy!G-+%R|!tk zB3|It#~!TtW|c`ppftj=JUgS&ArZyi$!A}E7P80Q9{Xs 20{=+fJAwZcvF!ja#r?V$R7|7zf+ zNi)pu&PUy}CEex!V4?SO*FS$YKDxW*<4AWS#)kI0rpz-*W>O={P;MTRhc2N?7lo4w zyNYgc%Y!Qhtg+Pt^mpz+pr`t1#AP~K?mI&Y^pkI|kkw%YCevIKffID-cddIG|D`R` zr?j=D