From a0ed128bdea984ff61629cbcdcd284d10d7a1551 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=AB=98=E7=89=A9?= <253518756@qq.com> Date: Sat, 9 May 2026 17:15:23 +0800 Subject: [PATCH] 1 --- .hermes/shared-memory/decision-log.md | 16 + .hermes/shared-memory/pitfalls.md | 41 +- deploy/env/api-server.env.example | 8 +- ..._DISCOVER_DRAFT_TAB_REDESIGN_2026-05-05.md | 10 + ..._EXTERNAL_SERVICE_ENV_CONFIG_2026-05-07.md | 20 +- .../AUTH_LOGIN_OPTIONS_DESIGN_2026-04-21.md | 2 +- ..._RODIN_GEN2_MODEL_GENERATION_2026-05-08.md | 128 ++ ...AND_FRONTEND_RULES_ALIGNMENT_2026-04-29.md | 8 +- ...LT_AUTOSAVE_AND_TAG_GATE_FIX_2026-04-28.md | 6 + docs/technical/README.md | 7 +- ...E_AUTH_FAILURE_ISOLATION_FIX_2026-05-09.md | 18 +- ...RATION_GPT_IMAGE_2_MIGRATION_2026-05-02.md | 35 +- ...EDB_PUBLISH_SCCACHE_FALLBACK_2026-05-09.md | 53 + ..._SH_PUBLISH_403_IDENTITY_FIX_2026-04-26.md | 12 + ...NGINE_GPT_IMAGE_2_GENERATION_2026-05-09.md | 116 ++ media/social-media-group/wechat.png | Bin 159597 -> 88595 bytes packages/shared/src/contracts/hyper3d.ts | 75 ++ packages/shared/src/contracts/index.ts | 1 + packages/shared/src/index.ts | 1 + scripts/dev-rust-stack.sh | 49 +- scripts/dev-web-rust.mjs | 77 +- server-rs/crates/api-server/Cargo.toml | 2 +- server-rs/crates/api-server/src/app.rs | 37 + server-rs/crates/api-server/src/config.rs | 60 +- .../api-server/src/hyper3d_generation.rs | 1052 +++++++++++++++++ server-rs/crates/api-server/src/main.rs | 1 + .../api-server/src/openai_image_generation.rs | 221 +--- server-rs/crates/api-server/src/puzzle.rs | 30 +- server-rs/crates/api-server/src/state.rs | 2 +- .../crates/shared-contracts/src/hyper3d.rs | 169 +++ server-rs/crates/shared-contracts/src/lib.rs | 1 + .../PlatformEntryFlowShellImpl.tsx | 146 ++- .../puzzle-result/PuzzleResultView.test.tsx | 34 + .../puzzle-result/PuzzleResultView.tsx | 30 +- ...gEntryFlowShell.agent.interaction.test.tsx | 27 +- .../RpgEntryHomeView.recharge.test.tsx | 156 ++- src/components/rpg-entry/RpgEntryHomeView.tsx | 100 +- .../rpg-entry/useRpgEntryBootstrap.ts | 7 +- src/index.css | 49 + src/services/assetReadUrlService.test.ts | 44 +- src/services/assetReadUrlService.ts | 15 +- src/services/hyper3dModelGenerationService.ts | 72 ++ .../puzzle-runtime/puzzleRuntimeClient.ts | 16 + 43 files changed, 2573 insertions(+), 381 deletions(-) create mode 100644 docs/technical/HYPER3D_RODIN_GEN2_MODEL_GENERATION_2026-05-08.md create mode 100644 docs/technical/SPACETIMEDB_PUBLISH_SCCACHE_FALLBACK_2026-05-09.md create mode 100644 docs/technical/VECTOR_ENGINE_GPT_IMAGE_2_GENERATION_2026-05-09.md create mode 100644 packages/shared/src/contracts/hyper3d.ts create mode 100644 server-rs/crates/api-server/src/hyper3d_generation.rs create mode 100644 server-rs/crates/shared-contracts/src/hyper3d.rs create mode 100644 src/services/hyper3dModelGenerationService.ts diff --git a/.hermes/shared-memory/decision-log.md b/.hermes/shared-memory/decision-log.md index c475d66a..a1ce36ee 100644 --- a/.hermes/shared-memory/decision-log.md +++ b/.hermes/shared-memory/decision-log.md @@ -16,6 +16,22 @@ --- +## 2026-05-09 GPT-image-2 图片生成统一迁移到 VectorEngine + +- 背景:仓库内 RPG、拼图、方洞和本地模板脚本的 GPT-image-2 生图此前依赖 APIMart 图片网关;团队要求参考 VectorEngine Apifox `api-448710071`,后续不再使用 APIMart 执行 GPT-image-2 图片生成。 +- 决策:所有 GPT-image-2 生图请求统一走 VectorEngine `POST /v1/images/generations`,基础配置读取 `VECTOR_ENGINE_BASE_URL` / `VECTOR_ENGINE_API_KEY` / `VECTOR_ENGINE_IMAGE_REQUEST_TIMEOUT_MS`,上游模型使用 `gpt-image-2-all`,请求体不再携带 `official_fallback`,参考图字段改为 `image`。APIMart 只保留给创意 Agent 的 `gpt-5` Responses 文本/多模态链路。 +- 影响范围:`api-server` 共享图片 helper、拼图图片生成、角色主图、RPG 场景图、开局 CG 故事板、方洞视觉资产、生产环境示例、gpt-image-2 本地 skill 和相关技术文档。 +- 验证方式:执行 `npm run check:encoding`、`cargo test -p api-server openai_image --manifest-path server-rs/Cargo.toml`、`cargo test -p api-server puzzle --manifest-path server-rs/Cargo.toml`、`cargo test -p api-server custom_world_ai --manifest-path server-rs/Cargo.toml`、`cargo test -p api-server character_visual --manifest-path server-rs/Cargo.toml`,并用 `npm run api-server` + `/healthz` 做后端 smoke。 +- 关联文档:`docs/technical/VECTOR_ENGINE_GPT_IMAGE_2_GENERATION_2026-05-09.md`、`docs/technical/API_SERVER_EXTERNAL_SERVICE_ENV_CONFIG_2026-05-07.md`。 + +## 2026-05-08 Hyper3D Rodin Gen-2 只通过后端安全代理接入 + +- 背景:需要接入 Hyper3D Rodin Gen-2 的文生 3D 模型与图生 3D 模型,但供应商 API Key 不能进入前端、文档或 Git;本次只是外部副作用代理,不需要新增平台真相表。 +- 决策:Hyper3D 统一走 `api-server` 的 `/api/assets/hyper3d/*` 鉴权路由,配置只读取 `HYPER3D_BASE_URL` / `HYPER3D_API_KEY` / `HYPER3D_MODEL_REQUEST_TIMEOUT_MS` 及兼容 `RODIN_*` 变量;生成提交、状态查询和下载列表都由后端代理。首版不写 SpacetimeDB、不确认 `asset_object`,下载链接后续由调用方决定是否进入 OSS 资产链。 +- 影响范围:`api-server` 外部服务配置、Hyper3D route、`shared-contracts` / TS contract、前端 service、生产环境示例和外部服务环境变量文档。 +- 验证方式:执行 `cargo test -p api-server hyper3d --manifest-path server-rs/Cargo.toml`、`cargo test -p shared-contracts hyper3d --manifest-path server-rs/Cargo.toml`、`cargo check -p api-server --manifest-path server-rs/Cargo.toml`、`npm run typecheck` 和编码检查;真实 API smoke 只在本地私密环境配置 key 后手动执行。 +- 关联文档:`docs/technical/HYPER3D_RODIN_GEN2_MODEL_GENERATION_2026-05-08.md`、`docs/technical/API_SERVER_EXTERNAL_SERVICE_ENV_CONFIG_2026-05-07.md`。 + ## 2026-05-08 APIMart 接口统一携带 `official_fallback` - 背景:APIMart 的图片生成和 Responses 接口在仓库内分散于 `api-server`、`platform-llm` 和本地 skill 脚本,若只修单点,容易出现不同入口的上游请求体不一致。 diff --git a/.hermes/shared-memory/pitfalls.md b/.hermes/shared-memory/pitfalls.md index 0d7c2698..a19f550e 100644 --- a/.hermes/shared-memory/pitfalls.md +++ b/.hermes/shared-memory/pitfalls.md @@ -43,6 +43,14 @@ - 验证:提交前检查 `git diff -- .hermes`,确认没有密钥、会话记录或个人路径敏感信息。 - 关联:`.hermes/README.md`。 +## GPT-image-2 不再读 APIMart 图片配置 + +- 现象:配置了 `APIMART_BASE_URL` / `APIMART_API_KEY` 后,RPG、拼图或方洞的 GPT-image-2 生图仍返回缺配置,或请求体里还出现 `official_fallback` / `image_urls`。 +- 原因:2026-05-09 后 GPT-image-2 图片生成已切到 VectorEngine `gpt-image-2-all`,APIMart 只保留给创意 Agent 的 `gpt-5` Responses 文本/多模态链路。 +- 处理:为图片生成配置 `VECTOR_ENGINE_BASE_URL=https://api.vectorengine.ai`、`VECTOR_ENGINE_API_KEY`、`VECTOR_ENGINE_IMAGE_REQUEST_TIMEOUT_MS`;排查请求体时确认路径为 `/v1/images/generations`、模型为 `gpt-image-2-all`、参考图字段为 `image`。 +- 验证:运行 `cargo test -p api-server openai_image --manifest-path server-rs/Cargo.toml` 和相关玩法图片生成测试;真实联调只在本地私密环境放置 VectorEngine key。 +- 关联:`docs/technical/VECTOR_ENGINE_GPT_IMAGE_2_GENERATION_2026-05-09.md`、`server-rs/crates/api-server/src/openai_image_generation.rs`。 + ## 旧后端路线文档造成判断漂移 - 现象:开发时参考到 Express、Node、PostgreSQL 或 Go 方向旧文档,导致接口、数据真相或部署路径与当前主线不一致。 @@ -67,6 +75,14 @@ - 验证:本地 SpacetimeDB 可正常启动并 publish / 访问。 - 关联:`docs/technical/SPACETIMEDB_LOCAL_REPLICA_IDENTITY_MISMATCH_FIX_2026-04-30.md`。 +## 本地 SpacetimeDB publish 403 优先查 CLI root 身份 + +- 现象:`spacetime publish` 在 `Pre-publish check` 阶段返回 `403 Forbidden`,提示当前 identity 无权对目标 database identity 执行 `update database`。 +- 原因:裸 `spacetime` 命令使用了全局 CLI 登录态,但本地开发库权限绑定在 `server-rs/.spacetimedb/local` root-dir 的另一个身份上。 +- 处理:对比 `spacetime login show` 和 `spacetime --root-dir server-rs/.spacetimedb/local login show`;本地开发发布必须使用 `npm run dev:rust`,或显式追加 `--root-dir=server-rs/.spacetimedb/local`。 +- 验证:`spacetime --root-dir server-rs/.spacetimedb/local list --server http://127.0.0.1:3101` 能看到目标库;重新发布不再使用无权限的全局 identity。 +- 关联:`scripts/dev-rust-stack.sh`、`docs/technical/SPACETIMEDB_START_SH_PUBLISH_403_IDENTITY_FIX_2026-04-26.md`。 + ## Vite SPA fallback 吞掉 API 请求 - 现象:本地请求 `/api/profile/*` 等接口时返回 HTML,被前端当 JSON 解析报错。 @@ -114,6 +130,7 @@ - 本地启动脚本没有让 `.env.local` 覆盖 `.env`,`SMS_AUTH_ENABLED=true` 不生效,后端只返回 `["password"]`。 - Rust API 直连已返回 `["phone","password"]`,但 Vite 代理目标指向未监听端口,导致 3000 域名下的 `login-options` 返回 `500`,`AuthGate` 降级成 `["password"]`。 - 3000 端口被旧 `dev:web` 占用后,新的完整栈 Vite 自动漂移到 3001/3002;浏览器仍打开旧 3000 页面,旧页面继续代理到已经下线的端口。 + - 单独 `npm run dev:web` 启动瞬间另一个临时 API 端口可用,脚本若自动切过去,之后临时 API 停掉也会让 3000 继续代理到空端口。 - 处理:优先用 `npm run api-server`、`npm run dev:rust` 或 `npm run dev` 启动,这些入口应保持 shell 环境变量最高优先级,并允许 `.env.local` 覆盖 `.env`;完整栈启动时还要确保脚本计算出的 `RUST_SERVER_TARGET` 不被 `.env.local` 里的旧值覆盖。排查时先请求 3000 域名下的 `/api/auth/login-options`,再直连 Rust API 目标,并核对 `.env.local` 的 `SMS_AUTH_ENABLED` 与代理端口;若 3001/3002 才返回正确结果,说明当前 3000 是旧前端进程,应清理旧进程后重启。 - 验证:`http://127.0.0.1:3000/api/auth/login-options` 返回至少 `{"availableLoginMethods":["phone","password"]}` 后,登录弹窗会恢复短信登录页签和“获取验证码”按钮。 - 关联:`scripts/api-server-dev.mjs`、`scripts/api-server-maincloud.mjs`、`scripts/dev-rust-stack.sh`、`scripts/dev-web-rust.mjs`、`docs/technical/AUTH_LOGIN_OPTIONS_DESIGN_2026-04-21.md`。 @@ -139,8 +156,10 @@ - 现象:前端登录成功后进入推荐页,推荐页自动加载出一个作品,随后瞬间回到未登录;停留在其他页面或推荐页没加载出作品时不复现。 - 原因:推荐页 embedded 运行态会自动发起受保护写请求。若这些卡片级后台请求遇到 `401` 或 refresh 失败,默认请求层曾清空 access token 并广播全局 auth 事件,导致 `AuthGate` 重新 hydrate 成未登录态。 - 处理:推荐页自动运行态请求传 `skipRefresh: true`、`notifyAuthStateChange: false`、`clearAuthOnUnauthorized: false`,并等 `canReadProtectedData` 为 true 后再启动;用户主动点击的受保护动作仍保留默认鉴权失败处理。 -- 验证:`npm run test -- src/services/apiClient.test.ts` 和 `npm run test -- src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx -t "home recommendation starts embedded puzzle"`。 -- 关联:`src/services/apiClient.ts`、`src/components/platform-entry/PlatformEntryFlowShellImpl.tsx`、`docs/technical/RECOMMEND_RUNTIME_AUTH_FAILURE_ISOLATION_FIX_2026-05-09.md`。 +- 追加处理:generated 私有图片换签 `/api/assets/read-url` 也属于展示层后台请求;推荐页拼图运行态挂载后会立即解析封面图,若换签 401 触发全局鉴权事件,也会表现成“进入拼图作品后瞬间未登录”。资源换签失败只应让当前图片为空,不应清 token、广播 auth 事件或主动 refresh。 +- 追加处理:从推荐页点进公开拼图作品并启动完整运行态后,`startPuzzleRun`、通关自动 `submitPuzzleLeaderboard`、下一关 `advancePuzzleNextLevel` 和重开同样属于当前玩法局部同步;这些请求失败时只应留在拼图错误态,不应清 token 或广播 auth 事件。 +- 验证:`npm run test -- src/services/apiClient.test.ts src/services/assetReadUrlService.test.ts`、`npm run test -- src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx -t "home recommendation starts embedded puzzle"`、`npm run test -- src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx -t "formal puzzle runtime uses frontend move merge logic and backend leaderboard"`、`npm run test -- src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx -t "formal puzzle similar work keeps current run level progression"`。 +- 关联:`src/services/apiClient.ts`、`src/services/assetReadUrlService.ts`、`src/services/puzzle-runtime/puzzleRuntimeClient.ts`、`src/components/platform-entry/PlatformEntryFlowShellImpl.tsx`、`docs/technical/RECOMMEND_RUNTIME_AUTH_FAILURE_ISOLATION_FIX_2026-05-09.md`。 ## Rust 冷编译导致 api-server 健康检查误超时 @@ -235,11 +254,11 @@ ## Rust 构建不要让不可用的 sccache 阻断 rustc -- 现象:Cargo 报 `could not execute process sccache ... rustc.exe -vV (never executed)`,真实 `rustc -Vv` 可以执行,但构建在调用包装器时失败。 -- 原因:环境或 Jenkinsfile 设置了 `RUSTC_WRAPPER=sccache`,但当前 Windows/Linux agent 上没有可执行的 `sccache`,或 PATH 中的 `sccache` shim 损坏。 -- 处理:本地临时排障可执行 `Remove-Item Env:RUSTC_WRAPPER -ErrorAction SilentlyContinue` 后重跑 Cargo;生产流水线必须先实际执行 `sccache --version`,失败时移除 `RUSTC_WRAPPER` 并回退到直接 `rustc`。 -- 验证:`rustc -Vv` 能输出版本;`cargo` 不再尝试调用不可用的 `sccache`;Jenkins 日志出现“未找到可用 sccache,改用 rustc 直接构建”后仍继续真实构建。 -- 关联:`jenkins/Jenkinsfile.production-stdb-module-build`、`docs/technical/PRODUCTION_DEPLOYMENT_PLAN_2026-05-02.md`。 +- 现象:Cargo 报 `could not execute process sccache ... rustc.exe -vV (never executed)`,或 `sccache: caused by: Failed to send data to or receive data from server / Failed to read response header / failed to fill whole buffer`;真实 `rustc -Vv` 可以执行,但构建在调用包装器时失败。 +- 原因:环境、Jenkinsfile 或 `server-rs/.cargo/config.toml` 启用了 `sccache` wrapper,但当前 agent 没有可执行的 `sccache`、PATH 中 shim 损坏,或本地 sccache server/client 通道状态损坏。 +- 处理:本地临时排障可在 Git Bash 中执行 `RUSTC_WRAPPER= CARGO_BUILD_RUSTC_WRAPPER= cargo build ...`;`npm run dev:rust` 的 SpacetimeDB publish 已在命中 sccache 通信失败时自动清空 wrapper 重试一次;生产流水线必须先实际执行 `sccache --version`,失败时移除 `RUSTC_WRAPPER` 并回退到直接 `rustc`。 +- 验证:`rustc -Vv` 能输出版本;清空 wrapper 后 `cargo check --target=wasm32-unknown-unknown --release` 能通过;Jenkins 日志出现“未找到可用 sccache,改用 rustc 直接构建”后仍继续真实构建。 +- 关联:`scripts/dev-rust-stack.sh`、`jenkins/Jenkinsfile.production-stdb-module-build`、`docs/technical/SPACETIMEDB_PUBLISH_SCCACHE_FALLBACK_2026-05-09.md`、`docs/technical/PRODUCTION_DEPLOYMENT_PLAN_2026-05-02.md`。 ## 生产发布入口不要沿用旧 Jenkinsfile / 一体化脚本 @@ -256,3 +275,11 @@ - 处理:Admin 任务配置页不展示范围选择,保存时固定 `scopeKind: 'user'`;API 和领域构造层拒绝非 `User`。 - 验证:非 `user` scope 返回错误;相关测试覆盖 `Site` / `Module` / `Work` 被拒绝。 - 关联:`docs/technical/RUNTIME_PROFILE_TASK_SCOPE_2026-05-04.md`、`docs/technical/ANALYTICS_DATE_DIMENSION_IMPLEMENTATION_2026-05-04.md`。 + +## 拼图发布 409 不一定是接口故障 + +- 现象:拼图结果页点击发布后,控制台出现 `POST /api/runtime/puzzle/agent/sessions/{sessionId}/actions 409 (Conflict)`,用户只看到发布失败。 +- 原因:`publish_puzzle_work` 是资产操作发布入口,发布前会预扣 `1` 枚光点;余额不足时后端按业务冲突返回 `409 CONFLICT`,`details.message` 为 `光点余额不足`。 +- 处理:前端发布弹窗在用户点击发布后必须保留并展示后端业务错误,不能只把错误写到弹窗背后的页面 banner。 +- 验证:`PuzzleResultView` 单测覆盖发布弹窗内展示 `光点余额不足`。 +- 关联:`src/components/puzzle-result/PuzzleResultView.tsx`、`docs/technical/PUZZLE_RESULT_AUTOSAVE_AND_TAG_GATE_FIX_2026-04-28.md`、`docs/technical/ASSET_GENERATION_POINTS_CONSUMPTION_2026-04-27.md`。 diff --git a/deploy/env/api-server.env.example b/deploy/env/api-server.env.example index 943d9a48..d8821cd3 100644 --- a/deploy/env/api-server.env.example +++ b/deploy/env/api-server.env.example @@ -37,12 +37,16 @@ GENARRATIVE_CREATION_AGENT_LLM_WEB_SEARCH_ENABLED=false APIMART_BASE_URL= APIMART_API_KEY= -APIMART_IMAGE_REQUEST_TIMEOUT_MS=180000 -VECTOR_ENGINE_BASE_URL= +VECTOR_ENGINE_BASE_URL=https://api.vectorengine.ai VECTOR_ENGINE_API_KEY= +VECTOR_ENGINE_IMAGE_REQUEST_TIMEOUT_MS=180000 VECTOR_ENGINE_AUDIO_REQUEST_TIMEOUT_MS=180000 +HYPER3D_BASE_URL=https://api.hyper3d.com/api/v2 +HYPER3D_API_KEY= +HYPER3D_MODEL_REQUEST_TIMEOUT_MS=180000 + VOLCENGINE_SPEECH_API_KEY= VOLCENGINE_SPEECH_APP_ID= VOLCENGINE_SPEECH_ACCESS_KEY= diff --git a/docs/design/PLATFORM_MOBILE_RECOMMEND_DISCOVER_DRAFT_TAB_REDESIGN_2026-05-05.md b/docs/design/PLATFORM_MOBILE_RECOMMEND_DISCOVER_DRAFT_TAB_REDESIGN_2026-05-05.md index 61da363f..115d7c0c 100644 --- a/docs/design/PLATFORM_MOBILE_RECOMMEND_DISCOVER_DRAFT_TAB_REDESIGN_2026-05-05.md +++ b/docs/design/PLATFORM_MOBILE_RECOMMEND_DISCOVER_DRAFT_TAB_REDESIGN_2026-05-05.md @@ -100,3 +100,13 @@ 3. 发现 创作 Tab 必须位于中间,并使用原推荐 Tab 的星光图标,保持几何和视觉上的主行动入口。推荐 Tab 改用游戏手柄图标,避免与创作图标重复。 + +## 9. 2026-05-08 新用户默认发现与推荐门禁补充 + +未登录新用户首次进入平台时默认落在“发现”Tab,不再直接进入“推荐”Tab 的内嵌运行态。 + +- 未登录用户点击底部或侧边栏“推荐”Tab 时,页面可切到推荐封面预览态,同时打开登录弹窗。 +- 未登录状态下推荐页只展示当前推荐作品封面,不启动作品运行态,不展示推荐作品信息区。 +- 未登录用户点击推荐页封面时,再次打开同一个登录弹窗;登录成功后由既有受保护动作继续进入作品详情或玩法入口。 +- 未登录状态下点击“下一个”只切换下一张推荐封面,不触发登录弹窗,也不启动玩法。 +- 已登录用户继续沿用推荐页内嵌运行态、上下滑切换和底部“下一个”行为。 diff --git a/docs/technical/API_SERVER_EXTERNAL_SERVICE_ENV_CONFIG_2026-05-07.md b/docs/technical/API_SERVER_EXTERNAL_SERVICE_ENV_CONFIG_2026-05-07.md index f727a5e8..78f31f19 100644 --- a/docs/technical/API_SERVER_EXTERNAL_SERVICE_ENV_CONFIG_2026-05-07.md +++ b/docs/technical/API_SERVER_EXTERNAL_SERVICE_ENV_CONFIG_2026-05-07.md @@ -34,16 +34,21 @@ GENARRATIVE_LLM_BASE_URL= GENARRATIVE_LLM_API_KEY= GENARRATIVE_LLM_MODEL= -# APIMart / OpenAI 兼容图片网关 +# APIMart / OpenAI 兼容 Responses 文本网关 APIMART_BASE_URL= APIMART_API_KEY= -APIMART_IMAGE_REQUEST_TIMEOUT_MS=180000 -# VectorEngine / Suno / Vidu 音频生成网关 -VECTOR_ENGINE_BASE_URL= +# VectorEngine / GPT-image-2 / Suno / Vidu 生成网关 +VECTOR_ENGINE_BASE_URL=https://api.vectorengine.ai VECTOR_ENGINE_API_KEY= +VECTOR_ENGINE_IMAGE_REQUEST_TIMEOUT_MS=180000 VECTOR_ENGINE_AUDIO_REQUEST_TIMEOUT_MS=180000 +# Hyper3D Rodin Gen-2 3D 模型生成 +HYPER3D_BASE_URL=https://api.hyper3d.com/api/v2 +HYPER3D_API_KEY= +HYPER3D_MODEL_REQUEST_TIMEOUT_MS=180000 + # 火山引擎豆包语音 ASR / TTS VOLCENGINE_SPEECH_API_KEY= VOLCENGINE_SPEECH_APP_ID= @@ -84,6 +89,9 @@ ARK_CHARACTER_VIDEO_MODEL / DASHSCOPE_CHARACTER_VIDEO_MODEL VOLCENGINE_SPEECH_API_KEY / VOLCENGINE_API_KEY VOLCENGINE_SPEECH_APP_ID / VOLCENGINE_ACCESS_KEY_ID VOLCENGINE_SPEECH_ACCESS_KEY / VOLCENGINE_SECRET_ACCESS_KEY +HYPER3D_BASE_URL / RODIN_BASE_URL +HYPER3D_API_KEY / RODIN_API_KEY +HYPER3D_MODEL_REQUEST_TIMEOUT_MS / RODIN_MODEL_REQUEST_TIMEOUT_MS ``` ## 运行时行为 @@ -93,8 +101,10 @@ VOLCENGINE_SPEECH_ACCESS_KEY / VOLCENGINE_SECRET_ACCESS_KEY 3. 文本 LLM provider 为 `ark` 且未配置 `GENARRATIVE_LLM_BASE_URL` 时,仍回退到 Ark 公开基础 URL。 4. 角色视频 provider 复用 Ark 且未配置 `ARK_CHARACTER_VIDEO_BASE_URL` 时,仍回退到 Ark 公开基础 URL。 5. 具体模型名缺失时不在配置层伪造默认模型,调用到对应能力时由下游配置校验返回缺配置错误。 -6. VectorEngine 音频生成只读取 `VECTOR_ENGINE_BASE_URL` / `VECTOR_ENGINE_API_KEY`,不复用 `APIMART_*`、`GENARRATIVE_LLM_*` 或前端变量。 +6. VectorEngine 图片与音频生成只读取 `VECTOR_ENGINE_BASE_URL` / `VECTOR_ENGINE_API_KEY`,其中 GPT-image-2 图片生成额外读取 `VECTOR_ENGINE_IMAGE_REQUEST_TIMEOUT_MS`;不复用 `APIMART_*`、`GENARRATIVE_LLM_*` 或前端变量。 7. 火山引擎语音能力由 `platform-speech` 收口协议帧与上游鉴权,`api-server` 只暴露平台鉴权后的代理路由,不向前端返回任何密钥字段。 +8. Hyper3D Rodin Gen-2 使用公开默认 `https://api.hyper3d.com/api/v2`,API Key 只读取 `HYPER3D_API_KEY` / `RODIN_API_KEY`,不复用文本 LLM、图片或音频网关密钥。 +9. APIMart 当前只保留给创意 Agent 的 `gpt-5` Responses 文本/多模态理解链路;GPT-image-2 图片生成不得再读取 APIMart 配置。 ## 示例文件 diff --git a/docs/technical/AUTH_LOGIN_OPTIONS_DESIGN_2026-04-21.md b/docs/technical/AUTH_LOGIN_OPTIONS_DESIGN_2026-04-21.md index c90935f2..0995f68d 100644 --- a/docs/technical/AUTH_LOGIN_OPTIONS_DESIGN_2026-04-21.md +++ b/docs/technical/AUTH_LOGIN_OPTIONS_DESIGN_2026-04-21.md @@ -135,5 +135,5 @@ 2. 再请求当前 Rust API 目标,例如 `http://127.0.0.1:3100/api/auth/login-options` 或 `http://127.0.0.1:8082/api/auth/login-options`。 3. 若直连 API 成功而 3000 返回 `500`,检查 `RUST_SERVER_TARGET`、`GENARRATIVE_API_TARGET`、`GENARRATIVE_RUNTIME_SERVER_TARGET` 是否指向仍在监听的 API 端口。 4. `npm run dev` / `npm run dev:rust` 完整栈默认由脚本计算 API 端口;加载 `.env.local` 给后端使用后,脚本必须重新固定 `RUST_SERVER_TARGET`,避免 `.env.local` 中的旧代理目标覆盖本次启动的实际 API 端口。 -5. `npm run dev:web` 只启动前端,不会自动拉起 Rust API;如果单独使用它,脚本会先探测 `.env.local` / 当前环境里声明的目标,再回退到本机常见端口,最终只会接入一个真实可用的 `api-server`。 +5. `npm run dev:web` 只启动前端,不会自动拉起 Rust API;如果 `.env.local` / 当前环境已经显式声明 `GENARRATIVE_RUNTIME_SERVER_TARGET`、`RUST_SERVER_TARGET`、`GENARRATIVE_API_TARGET` 或 `GENARRATIVE_API_PORT`,脚本必须固定使用该目标。目标当下不可用时只打印警告,不自动切到另一个端口,避免前端进程长时间绑定到随后会停掉的临时 API。 6. 如果 `3000` 仍然返回 `500`,先确认浏览器是不是还开着旧的前端进程。当前脚本如果因为端口占用漂移到 `3001` / `3002`,应直接关掉旧进程后重启,而不是继续用旧的 3000 页面判断登录入口状态。 diff --git a/docs/technical/HYPER3D_RODIN_GEN2_MODEL_GENERATION_2026-05-08.md b/docs/technical/HYPER3D_RODIN_GEN2_MODEL_GENERATION_2026-05-08.md new file mode 100644 index 00000000..97b3c1b0 --- /dev/null +++ b/docs/technical/HYPER3D_RODIN_GEN2_MODEL_GENERATION_2026-05-08.md @@ -0,0 +1,128 @@ +# Hyper3D Rodin Gen-2 3D 模型生成接入方案 2026-05-08 + +## 1. 范围 + +本方案用于接入 Hyper3D Rodin Gen-2 的文生 3D 模型与图生 3D 模型能力。 + +本次只做后端安全代理与前端可复用 client,不新增 SpacetimeDB 表,不落正式资产对象,不把 Hyper3D API Key 下发到前端。生成结果仍由调用方在拿到下载链接后决定是否进入 OSS / `asset_object` 的正式资产链。 + +## 2. 参考接口 + +参考文档: + +- `https://developer.hyper3d.ai/api-specification/rodin-generation-gen2` +- `https://developer.hyper3d.ai/api-specification/check-status` +- `https://developer.hyper3d.ai/api-specification/download-results` + +上游接口: + +```text +POST https://api.hyper3d.com/api/v2/rodin +POST https://api.hyper3d.com/api/v2/status +POST https://api.hyper3d.com/api/v2/download +``` + +Rodin Gen-2 提交接口必须使用 `multipart/form-data`。文本生成时提交 `prompt`;图片生成时提交一个或多个 `images` 文件,可选 `prompt` 作为辅助描述。两种模式均固定提交 `tier=Gen-2`。 + +## 3. 环境变量 + +```text +HYPER3D_BASE_URL=https://api.hyper3d.com/api/v2 +HYPER3D_API_KEY= +HYPER3D_MODEL_REQUEST_TIMEOUT_MS=180000 +``` + +兼容变量: + +```text +RODIN_BASE_URL +RODIN_API_KEY +RODIN_MODEL_REQUEST_TIMEOUT_MS +``` + +说明: + +1. `HYPER3D_API_KEY` / `RODIN_API_KEY` 只允许写入本地或生产私密环境,不提交到 Git。 +2. 缺少 API Key 时,后端返回 `503 SERVICE_UNAVAILABLE`。 +3. `HYPER3D_BASE_URL` 默认使用公开 API 基础地址;如果团队后续改用代理网关,可通过环境变量覆盖。 + +## 4. 后端路由 + +新增 4 个鉴权路由: + +| 方法 | 路由 | 用途 | +| --- | --- | --- | +| `POST` | `/api/assets/hyper3d/text-to-model` | 提交 Rodin Gen-2 文生模型任务 | +| `POST` | `/api/assets/hyper3d/image-to-model` | 提交 Rodin Gen-2 图生模型任务 | +| `POST` | `/api/assets/hyper3d/status` | 使用 `subscriptionKey` 查询任务状态 | +| `POST` | `/api/assets/hyper3d/download` | 使用 `taskUuid` 获取模型下载列表 | + +文生模型请求最小体: + +```json +{ + "prompt": "一只低多边形宝箱,适合 RPG 游戏资产", + "geometryFileFormat": "glb", + "material": "PBR", + "quality": "medium", + "meshMode": "Quad", + "previewRender": true +} +``` + +图生模型请求最小体: + +```json +{ + "imageDataUrls": ["data:image/png;base64,..."], + "prompt": "保留主体轮廓,生成游戏可用 3D 模型", + "conditionMode": "concat", + "geometryFileFormat": "glb" +} +``` + +## 5. 约束 + +1. 图片只接受 `data:image/png|jpeg|webp;base64,...`,最多 5 张。 +2. 单张图片解码后不超过 10MB。 +3. `geometryFileFormat` 限定为 `glb/usdz/fbx/obj/stl`,默认 `glb`。 +4. `material` 限定为 `PBR/Shaded/All`,默认 `PBR`。 +5. `quality` 限定为 `high/medium/low/extra-low`,默认 `medium`。 +6. `meshMode` 限定为 `Quad/Raw`,默认 `Quad`。 +7. `addons` 首版只允许 `HighPack`。 +8. `bboxCondition` 必须为 3 个正数,按上游要求序列化为 JSON 字符串。 + +## 6. 返回语义 + +提交任务成功后返回: + +```json +{ + "ok": true, + "provider": "hyper3d-rodin", + "mode": "text-to-model", + "taskUuid": "task-uuid", + "subscriptionKey": "subscription-key", + "jobUuids": ["job-uuid"], + "message": "Submitted.", + "tier": "Gen-2" +} +``` + +状态查询会把上游 `Waiting / Generating / Done / Failed` 归一化为 `waiting / generating / done / failed / unknown`。下载接口只返回上游 `list.name` 与 `list.url`,不在后端转存文件。 + +## 7. 验收 + +建议执行: + +```bash +npm run check:encoding +npm run typecheck + +cd server-rs +cargo test -p shared-contracts hyper3d +cargo test -p api-server hyper3d +cargo check -p api-server +``` + +真实 API smoke 只在本地私密环境设置 `HYPER3D_API_KEY` 后执行。提交生成任务会消耗 Hyper3D Credit,默认验证不自动调用真实生成接口。 diff --git a/docs/technical/PUZZLE_IMAGE_AND_FRONTEND_RULES_ALIGNMENT_2026-04-29.md b/docs/technical/PUZZLE_IMAGE_AND_FRONTEND_RULES_ALIGNMENT_2026-04-29.md index c7da2561..229e834e 100644 --- a/docs/technical/PUZZLE_IMAGE_AND_FRONTEND_RULES_ALIGNMENT_2026-04-29.md +++ b/docs/technical/PUZZLE_IMAGE_AND_FRONTEND_RULES_ALIGNMENT_2026-04-29.md @@ -14,12 +14,12 @@ ### 1. 图片生成 -1. 拼图默认使用 APIMart `gpt-image-2` 生成图,外部请求尺寸固定为 `1:1`;`nanobanana2` 仍映射为 `gemini-3.1-flash-image-preview`。 +1. 拼图默认使用 VectorEngine `gpt-image-2-all` 生成图,外部请求尺寸固定为 `1024x1024`;前端历史 `nanobanana2` 选项只保留兼容展示,后端同样回落到 VectorEngine GPT-image-2-all,不再调用 APIMart 图片网关。 2. 历史 `original` 或空模型值只做兼容输入,不再进入 DashScope 原模型链路,统一按 `gpt-image-2` 路由。 3. 文生图和参考图生图共用同一个正方形尺寸口径,禁止一条链路仍生成竖屏或横版图。 4. 拼图图片提示词明确写入 `1:1 正方形画布`,继续保留适配 `3x3 / 4x4 / 5x5 / 6x6 / 7x7` 拼图切块、主体清晰、层次明确、无文字水印等约束。 5. 文生图正向 prompt 必须由后端压缩到 `500` 字符以内,优先保留玩家画面描述开头与固定拼图约束,避免上游把超长 prompt 判为“请求参数不合法”。 -6. APIMart 上游失败时,api-server 必须在错误 details 中保留业务 message、`upstreamStatus` 和截断后的 `rawExcerpt`,日志也要记录同样的摘要,避免生成进度页只能看到通用 HTTP 文案。 +6. VectorEngine 上游失败时,api-server 必须在错误 details 中保留业务 message、`upstreamStatus` 和截断后的 `rawExcerpt`,日志也要记录同样的摘要,避免生成进度页只能看到通用 HTTP 文案。 7. 图片生成仍由 `api-server` 执行。SpacetimeDB reducer 不做网络 I/O。 8. 光点预扣失败属于钱包或 SpacetimeDB 服务链路错误,不得映射成 `400 BAD_REQUEST`。除余额不足返回 `409 CONFLICT` 外,其余预扣异常统一按上游/服务错误暴露,避免生成页误提示“请求参数不合法”。 @@ -47,10 +47,10 @@ ## 验收 -1. 点击拼图草稿生成或重新生成画面时,后端请求 APIMart 的 `size` 为 `1:1`,默认模型为 `gpt-image-2`。 +1. 点击拼图草稿生成或重新生成画面时,后端请求 VectorEngine 的 `size` 为 `1024x1024`,上游模型为 `gpt-image-2-all`。 2. 图片提示词包含 `1:1 正方形拼图关卡`。 3. 图片提示词长度不超过 `500` 字符,超长画面描述会被截断,但适配 `3x3 / 4x4 / 5x5 / 6x6 / 7x7` 拼图切块、`避免文字、水印、边框和 UI 元素` 等玩法约束不能丢。 -4. APIMart 返回参数错误、任务失败或非 2xx 时,前端错误优先展示后端 details.message,后端日志能看到 `upstreamStatus` 和 `rawExcerpt`。 +4. VectorEngine 返回参数错误、任务失败或非 2xx 时,前端错误优先展示后端 details.message,后端日志能看到 `upstreamStatus` 和 `rawExcerpt`。 5. 正式拼图 run 中拖动拼块后,前端立即更新棋盘、合并块和通关状态,不再等待 `/drag`。 6. 移动端运行时棋盘为正方形,并尽量贴近屏幕两侧边缘。 7. 基础单块和合并块都能看到圆角,合并块的外凸角与内凹角都不是直角,且图片不会溢出圆角裁剪。 diff --git a/docs/technical/PUZZLE_RESULT_AUTOSAVE_AND_TAG_GATE_FIX_2026-04-28.md b/docs/technical/PUZZLE_RESULT_AUTOSAVE_AND_TAG_GATE_FIX_2026-04-28.md index 6671adec..57d806f3 100644 --- a/docs/technical/PUZZLE_RESULT_AUTOSAVE_AND_TAG_GATE_FIX_2026-04-28.md +++ b/docs/technical/PUZZLE_RESULT_AUTOSAVE_AND_TAG_GATE_FIX_2026-04-28.md @@ -56,3 +56,9 @@ 3. 标签少于 `3` 个时,发布弹窗明确提示“正式标签数量必须在 3 到 6 之间”。 4. 标签补到 `3~6` 个后,无需刷新页面即可通过前端发布校验。 5. 结果页顶部能看到轻量自动保存状态,不额外堆叠说明文案。 + +## 2026-05-09 发布失败提示补充 + +`publish_puzzle_work` 属于资产操作发布入口,按 `ASSET_GENERATION_POINTS_CONSUMPTION_2026-04-27.md` 会在发布 mutation 前预扣 `1` 枚光点。余额不足时后端返回 `409 CONFLICT`,响应 `details.message` 为 `光点余额不足`,这属于业务拒绝,不是拼图发布接口不可用。 + +结果页发布弹窗必须在用户点击发布后继续展示后端错误原因,不能只把错误写到弹窗背后的页面 banner。这样余额不足、SpacetimeDB 发布门禁或其他后端业务错误都会在当前独立发布面板中直接可见。 diff --git a/docs/technical/README.md b/docs/technical/README.md index 017c2654..41b74eff 100644 --- a/docs/technical/README.md +++ b/docs/technical/README.md @@ -5,8 +5,11 @@ ## 文档列表 - [RUST_WORKSPACE_DEPENDENCY_CONSOLIDATION_2026-05-07.md](./RUST_WORKSPACE_DEPENDENCY_CONSOLIDATION_2026-05-07.md):记录 `server-rs` Cargo 依赖集中配置口径,第三方版本和 workspace 内部 crate path 统一维护在根 `server-rs/Cargo.toml`,成员 crate 只保留 feature/optional 差异。 -- [RECOMMEND_RUNTIME_AUTH_FAILURE_ISOLATION_FIX_2026-05-09.md](./RECOMMEND_RUNTIME_AUTH_FAILURE_ISOLATION_FIX_2026-05-09.md):记录平台推荐页自动加载作品后局部运行态请求 `401` 不应扩散成全局登出的修复,覆盖请求层局部鉴权失败隔离、推荐页 embedded 运行态启动和回归测试。 +- [VECTOR_ENGINE_GPT_IMAGE_2_GENERATION_2026-05-09.md](./VECTOR_ENGINE_GPT_IMAGE_2_GENERATION_2026-05-09.md):记录 GPT-image-2 图片生成从 APIMart 迁移到 VectorEngine `gpt-image-2-all` 的接口、环境变量、尺寸映射、错误口径和验收命令。 +- [SPACETIMEDB_PUBLISH_SCCACHE_FALLBACK_2026-05-09.md](./SPACETIMEDB_PUBLISH_SCCACHE_FALLBACK_2026-05-09.md):记录本地 `spacetime publish` 被 sccache wrapper 通信异常阻断时的根因、`dev-rust-stack` 自动降级策略和手动排障命令。 +- [RECOMMEND_RUNTIME_AUTH_FAILURE_ISOLATION_FIX_2026-05-09.md](./RECOMMEND_RUNTIME_AUTH_FAILURE_ISOLATION_FIX_2026-05-09.md):记录平台推荐页自动加载作品、公开拼图作品完整运行态和展示层图片换签的局部请求 `401` 不应扩散成全局登出的修复,覆盖请求层局部鉴权失败隔离、推荐页 embedded 运行态启动、拼图开局/排行榜/下一关和回归测试。 - [AUTH_GATE_LOGIN_RACE_GUARD_FIX_2026-05-09.md](./AUTH_GATE_LOGIN_RACE_GUARD_FIX_2026-05-09.md):记录 `AuthGate` 登录成功后又被旧 hydrate 覆盖回未登录态的竞态根因、版本号保护修复与回归测试。 +- [HYPER3D_RODIN_GEN2_MODEL_GENERATION_2026-05-08.md](./HYPER3D_RODIN_GEN2_MODEL_GENERATION_2026-05-08.md):记录 Hyper3D Rodin Gen-2 文生 3D 模型、图生 3D 模型、状态查询和下载列表的后端代理、环境变量、请求约束与验收边界。 - [VOLCENGINE_SPEECH_STREAMING_INTEGRATION_2026-05-08.md](./VOLCENGINE_SPEECH_STREAMING_INTEGRATION_2026-05-08.md):记录火山引擎大模型 ASR 双向流式、TTS WebSocket 双向流式和 TTS HTTP SSE 单向流式的后端代理、环境变量、协议帧和验收边界。 - [VECTOR_ENGINE_AUDIO_GENERATION_SUNO_VIDU_2026-05-08.md](./VECTOR_ENGINE_AUDIO_GENERATION_SUNO_VIDU_2026-05-08.md):记录视觉小说结果页接入 VectorEngine Suno 文生背景音乐与 Vidu 文生音效的接口、环境变量、后端路由、OSS 资产回写和前端弹层交互边界。 - [PROFILE_FEEDBACK_BACKEND_INTEGRATION_2026-05-08.md](./PROFILE_FEEDBACK_BACKEND_INTEGRATION_2026-05-08.md):冻结“我的”页签帮助与反馈入口的后端接入方案,覆盖 `POST /api/profile/feedback`、`profile_feedback_submission`、凭证图片 Data URL 校验和前端预览/提交边界。 @@ -20,7 +23,7 @@ - [PRODUCTION_DEPLOYMENT_PLAN_2026-05-02.md](./PRODUCTION_DEPLOYMENT_PLAN_2026-05-02.md):冻结单机生产部署目标,从旧一体化启动脚本切到 Nginx、systemd 托管 SpacetimeDB 与 Rust `api-server`,并记录生产 Jenkins 流水线拆分计划和首批部署骨架。 - [PUZZLE_RUNTIME_FRONTEND_LOGIC_REHOME_2026-05-02.md](./PUZZLE_RUNTIME_FRONTEND_LOGIC_REHOME_2026-05-02.md):记录拼图正式平台入口移动、交换、合并、拆分和通关裁决收回前端即时运行态,排行榜、下一关和游玩记录继续由后端持久化处理。 - [RPG_FOUNDATION_DRAFT_ROLE_DOSSIER_TIMEOUT_FALLBACK_2026-05-02.md](./RPG_FOUNDATION_DRAFT_ROLE_DOSSIER_TIMEOUT_FALLBACK_2026-05-02.md):记录 `agent-foundation-*-dossier-batch-*` 无搜索 Responses 请求超时后的本地养成档案兜底,避免底稿主链被尾部角色润色阶段阻断。 -- [RPG_IMAGE_GENERATION_GPT_IMAGE_2_MIGRATION_2026-05-02.md](./RPG_IMAGE_GENERATION_GPT_IMAGE_2_MIGRATION_2026-05-02.md):记录 RPG 角色主图与场景幕背景图统一迁移到 APIMart OpenAI 兼容 `gpt-image-2` 生图入口的边界、配置和验收口径。 +- [RPG_IMAGE_GENERATION_GPT_IMAGE_2_MIGRATION_2026-05-02.md](./RPG_IMAGE_GENERATION_GPT_IMAGE_2_MIGRATION_2026-05-02.md):记录 RPG 角色主图与场景幕背景图统一迁移到 `gpt-image-2` 生图入口的边界、配置和验收口径;2026-05-09 起实际上游以 VectorEngine 迁移文档为准。 - [RPG_FOUNDATION_DRAFT_LANDMARK_SEED_BATCH_TIMEOUT_FIX_2026-05-02.md](./RPG_FOUNDATION_DRAFT_LANDMARK_SEED_BATCH_TIMEOUT_FIX_2026-05-02.md):记录 `agent-foundation-landmark-seed-batch-1` 无搜索 Responses 请求超时的根因,并将场景骨架批次收敛为单场景生成。 - [PROFILE_MAIN_ROUTE_VITE_PROXY_FIX_2026-05-02.md](./PROFILE_MAIN_ROUTE_VITE_PROXY_FIX_2026-05-02.md):记录“我的”和“存档”页面在本地把 `/api/profile/*` 请求落到 Vite SPA fallback、导致 HTML 被当 JSON 解析的根因,以及 `/api/profile` 代理补齐与回归测试。 - [SERVER_RS_DDD_WP_DEL_CLEANUP_2026-05-01.md](./SERVER_RS_DDD_WP_DEL_CLEANUP_2026-05-01.md):记录 `WP-DEL 删除旧层与命名收口`,物理删除旧 runtime story HTTP DTO、前端 `Rpg*` alias、旧 `/api/custom-world/*` 非 runtime 前缀、Puzzle `local-next-level` 入口和 `/generated-*` 资产直读代理;生成资产读取统一走 OSS read-url 链路。 diff --git a/docs/technical/RECOMMEND_RUNTIME_AUTH_FAILURE_ISOLATION_FIX_2026-05-09.md b/docs/technical/RECOMMEND_RUNTIME_AUTH_FAILURE_ISOLATION_FIX_2026-05-09.md index e353756d..89e09032 100644 --- a/docs/technical/RECOMMEND_RUNTIME_AUTH_FAILURE_ISOLATION_FIX_2026-05-09.md +++ b/docs/technical/RECOMMEND_RUNTIME_AUTH_FAILURE_ISOLATION_FIX_2026-05-09.md @@ -6,6 +6,8 @@ 登录成功进入平台推荐页后,推荐页会自动加载一个公开作品并启动嵌入式运行态。实际联调中出现过:作品刚加载出来,前端又瞬间回到未登录状态;停留在其他页面,或推荐页没有成功加载出作品时不会复现。 +后续复测又发现:登录成功后,从推荐页点进拼图公开作品详情并启动完整拼图运行态,也可能在开局或通关后瞬间退回未登录。两类现象的底层问题一致,都是玩法/展示层局部请求把 `401` 扩散成全局鉴权事件。 + ## 根因 推荐页首屏的作品运行态启动是后台自动副作用,不是用户主动点击的账号操作。它会触发多条受保护请求,例如: @@ -16,6 +18,10 @@ 这些请求一旦遇到本地代理错配、后端短暂不可用或 token 刷新失败,原请求层会按普通受保护请求处理 `401`,清空 access token 并广播全局鉴权变更。`AuthGate` 收到事件后重新 hydrate,于是当前用户界面被切回未登录态。 +推荐页里还有一类更隐蔽的触发点:`ResolvedAssetImage` / `useResolvedAssetReadUrl` 在挂载时会请求 `/api/assets/read-url` 给 generated 私有图片换签。它本质上也是展示层后台请求,若按普通受保护请求处理 `401`,同样会把一次图片换签失败放大成全局掉线。 + +公开拼图作品的完整运行态还会在用户进入作品后自动发起 `startPuzzleRun`,通关后自动 `submitPuzzleLeaderboard`,点击下一关时 `advancePuzzleNextLevel`。这些请求属于当前玩法的运行态同步,失败时应该落到当前拼图错误态;它们不能清空全局 access token,也不能触发 `AuthGate` 重新 hydrate。 + ## 修复 本次把推荐页自动运行态请求定义为“卡片级后台请求”: @@ -23,14 +29,18 @@ 1. `apiClient` 增加 `clearAuthOnUnauthorized` 选项,允许局部请求在 `401` 时不清空全局 token。 2. 推荐页嵌入式运行态请求统一传入 `skipRefresh: true`、`notifyAuthStateChange: false`、`clearAuthOnUnauthorized: false`。 3. 推荐页自动启动作品前必须满足 `canReadProtectedData`,避免 `AuthGate` 仍在恢复阶段就提前发起受保护写请求。 -4. 普通用户主动点击“启动”、Remix、发布、点赞等路径继续保留默认全局鉴权处理。 +4. generated 图片换签请求同样使用局部后台鉴权选项并跳过 refresh,失败只让当前图片为空,不触发全局登录态清理。 +5. 公开拼图作品进入完整运行态后,把本次 run 标记为 `isolated` 鉴权模式;开局、重开、排行榜提交和下一关推进都沿用局部鉴权选项。 +6. Remix、发布、点赞、账号设置、退出登录等真正账号动作继续保留默认全局鉴权处理。 ## 验证 -1. `npm run test -- src/services/apiClient.test.ts` +1. `npm run test -- src/services/apiClient.test.ts src/services/assetReadUrlService.test.ts` 2. `npm run test -- src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx -t "home recommendation starts embedded puzzle"` -3. `npm run typecheck` -4. `npm run check:encoding` +3. `npm run test -- src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx -t "formal puzzle runtime uses frontend move merge logic and backend leaderboard"` +4. `npm run test -- src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx -t "formal puzzle similar work keeps current run level progression"` +5. `npm run typecheck` +6. `npm run check:encoding` ## 关联文件 diff --git a/docs/technical/RPG_IMAGE_GENERATION_GPT_IMAGE_2_MIGRATION_2026-05-02.md b/docs/technical/RPG_IMAGE_GENERATION_GPT_IMAGE_2_MIGRATION_2026-05-02.md index 276adfcf..39d220f8 100644 --- a/docs/technical/RPG_IMAGE_GENERATION_GPT_IMAGE_2_MIGRATION_2026-05-02.md +++ b/docs/technical/RPG_IMAGE_GENERATION_GPT_IMAGE_2_MIGRATION_2026-05-02.md @@ -7,7 +7,7 @@ RPG 创作链路里有两类正式图片资产需要统一模型: 1. 角色主图候选生成。 2. 场景幕背景图生成。 -旧实现中角色主图默认使用 `wan2.7-image-pro`,场景图根据是否有参考图分别使用 DashScope 文生图与图生图模型。拼图链路已经接入 APIMart 的 OpenAI 兼容 `/images/generations`,并以 `gpt-image-2` 作为默认图片模型,因此本次 RPG 图片迁移复用同一类服务端配置与请求口径。 +旧实现中角色主图默认使用 `wan2.7-image-pro`,场景图根据是否有参考图分别使用 DashScope 文生图与图生图模型。拼图链路已经接入 GPT-image-2 图片生成,因此本次 RPG 图片迁移复用同一类服务端配置与请求口径。2026-05-09 起,GPT-image-2 图片生成上游统一迁移到 VectorEngine,具体接口以 `VECTOR_ENGINE_GPT_IMAGE_2_GENERATION_2026-05-09.md` 为准。 ## 落地范围 @@ -26,9 +26,9 @@ RPG 创作链路里有两类正式图片资产需要统一模型: 服务端使用: ```text -POST {APIMART_BASE_URL}/images/generations -Authorization: Bearer {APIMART_API_KEY} -model = gpt-image-2 +POST {VECTOR_ENGINE_BASE_URL}/v1/images/generations +Authorization: Bearer {VECTOR_ENGINE_API_KEY} +model = gpt-image-2-all ``` 请求体统一包含: @@ -36,16 +36,15 @@ model = gpt-image-2 1. `model` 2. `prompt` 3. `n` -4. `official_fallback = true` -5. `size` -6. 有参考图时增加 `image_urls` +4. `size` +5. 有参考图时增加 `image` 尺寸归一规则: -1. `1024*1024`、`1024x1024`、`1:1` -> `1:1` -2. `1280*720`、`1600*900`、`16:9` -> `16:9` +1. `1024*1024`、`1024x1024`、`1:1` -> `1024x1024` +2. `1280*720`、`1600*900`、`16:9` -> `1536x1024` -响应解析兼容同步 `data[].url`、`data[].b64_json` 与异步 `task_id` / `GET /tasks/{task_id}` 结构。 +响应解析同步 `data[].url` 与 `data[].b64_json`;VectorEngine GPT-image-2-all 当前不再使用 APIMart 异步 `task_id` / `GET /tasks/{task_id}` 结构。 ## 非范围 @@ -56,22 +55,22 @@ model = gpt-image-2 ## 配置 -本次复用已有 APIMart 配置: +本次复用 VectorEngine 图片配置: ```text -APIMART_BASE_URL=https://api.apimart.ai/v1 -APIMART_API_KEY=... -APIMART_IMAGE_REQUEST_TIMEOUT_MS=180000 +VECTOR_ENGINE_BASE_URL=https://api.vectorengine.ai +VECTOR_ENGINE_API_KEY=... +VECTOR_ENGINE_IMAGE_REQUEST_TIMEOUT_MS=180000 ``` -`APIMART_API_KEY` 缺失时,角色主图与场景图返回 `SERVICE_UNAVAILABLE`,`details.provider = "apimart"`。 +`VECTOR_ENGINE_API_KEY` 缺失时,角色主图与场景图返回 `SERVICE_UNAVAILABLE`,`details.provider = "vector-engine"`。 ## 验收 -1. 角色主图生成请求上游 `model` 为 `gpt-image-2`,且携带 `official_fallback = true`。 -2. 场景图生成请求上游 `model` 为 `gpt-image-2`,且携带 `official_fallback = true`。 +1. 角色主图生成请求上游 `model` 为 `gpt-image-2-all`,且不携带 `official_fallback`。 +2. 场景图生成请求上游 `model` 为 `gpt-image-2-all`,且不携带 `official_fallback`。 3. 旧前端或历史草稿传 `wan2.7-image-pro` 时不会回退旧模型。 -4. 场景参考图生成仍能把参考图 Data URL 放入 `image_urls`。 +4. 场景参考图生成仍能把参考图 Data URL 放入 `image`。 5. 角色主图生成后仍执行原有 PNG 透明背景处理与 OSS 写入。 6. `cargo test -p api-server character_visual --manifest-path server-rs/Cargo.toml` 通过。 7. `cargo test -p api-server custom_world_ai --manifest-path server-rs/Cargo.toml` 通过。 diff --git a/docs/technical/SPACETIMEDB_PUBLISH_SCCACHE_FALLBACK_2026-05-09.md b/docs/technical/SPACETIMEDB_PUBLISH_SCCACHE_FALLBACK_2026-05-09.md new file mode 100644 index 00000000..be26b7be --- /dev/null +++ b/docs/technical/SPACETIMEDB_PUBLISH_SCCACHE_FALLBACK_2026-05-09.md @@ -0,0 +1,53 @@ +# SpacetimeDB publish sccache 降级处理 + +## 背景 + +Windows 本地执行 `npm run dev:rust` 或 `spacetime publish` 时,`spacetime` 会在内部调用 Cargo 构建 `server-rs/crates/spacetime-module`。因为 `server-rs/.cargo/config.toml` 配置了 `rustc-wrapper = "sccache"`,即使当前 shell 没有设置 `RUSTC_WRAPPER`,Cargo 仍会先执行 `sccache rustc -vV`。 + +当本机 sccache server 状态损坏、client/server 通信异常或版本残留不一致时,可能出现: + +```text +sccache: error: failed to execute compile +sccache: caused by: Failed to send data to or receive data from server +sccache: caused by: Failed to read response header +sccache: caused by: failed to fill whole buffer +``` + +这类错误发生在 rustc wrapper 层,不能说明 SpacetimeDB module 代码本身编译失败。 + +## 本地开发处理 + +`scripts/dev-rust-stack.sh` 的 publish 阶段保留首次正常 `sccache` 构建;如果 stderr 命中 sccache 通信或 wrapper 失败特征,则自动在同一 `--root-dir`、同一发布参数下清空本次子进程的 `RUSTC_WRAPPER` 与 `CARGO_BUILD_RUSTC_WRAPPER` 后重试。 + +该处理只影响本次 publish 子进程,不修改 `server-rs/.cargo/config.toml`,也不删除本地 target 缓存。 + +## 手动排障命令 + +优先确认 rustc 本身可用: + +```bash +rustc -vV +``` + +如果只想绕过本次 Cargo 构建的 sccache wrapper,可在 Git Bash 中执行: + +```bash +cd server-rs/crates/spacetime-module +RUSTC_WRAPPER= CARGO_BUILD_RUSTC_WRAPPER= cargo build --target=wasm32-unknown-unknown --release +``` + +如果需要排查 sccache server 状态: + +```bash +sccache --show-stats +sccache --stop-server +sccache --start-server +``` + +`sccache --stop-server` 本身也可能因为 server 通道已损坏而失败;此时不应阻断本地开发 publish,先使用 wrapper 降级完成验证。 + +## 验证 + +1. `bash -n scripts/dev-rust-stack.sh` +2. `RUSTC_WRAPPER= CARGO_BUILD_RUSTC_WRAPPER= cargo check --target=wasm32-unknown-unknown --release` +3. 重新运行 `npm run dev:rust`,看到 sccache 通信失败时脚本应打印降级提示并继续真实构建。 diff --git a/docs/technical/SPACETIMEDB_START_SH_PUBLISH_403_IDENTITY_FIX_2026-04-26.md b/docs/technical/SPACETIMEDB_START_SH_PUBLISH_403_IDENTITY_FIX_2026-04-26.md index 50e8e788..785c6ace 100644 --- a/docs/technical/SPACETIMEDB_START_SH_PUBLISH_403_IDENTITY_FIX_2026-04-26.md +++ b/docs/technical/SPACETIMEDB_START_SH_PUBLISH_403_IDENTITY_FIX_2026-04-26.md @@ -39,6 +39,8 @@ spacetime --root-dir="${GENARRATIVE_SPACETIME_ROOT_DIR}" ... `spacetime start` 不再额外设置 `--data-dir`,启动前会先执行 Ubuntu 专用 `sync_ubuntu_spacetime_install`,优先从 `/usr/.local/share/spacetime/bin//spacetimedb-cli` 或 `$HOME/.local/share/spacetime/bin//spacetimedb-cli` 同步到 `.spacetimedb/bin/current/spacetimedb-cli`;当前线上 `spacetime` 入口为 `/usr/local/bin/spacetime`。启动参数、探活和 root-dir 占用判定都使用同一个 `.spacetimedb/`。这样可以把发布包与部署机全局 `~/.spacetime` 隔离,避免后续人工 `spacetime login` 影响本地发布包。但如果旧 `.spacetimedb/` 已经由另一个身份创建,仍需要按第 4 节处理。 +本地 `npm run dev:rust` / `scripts/dev-rust-stack.sh` 也必须遵循同一条规则:`server ping`、`start` 和 `publish` 都显式使用 `server-rs/.spacetimedb/local` 作为 `--root-dir`。不要让发布命令回退到全局 CLI 登录态,否则会出现本地 root 已有目标库权限,但裸 `spacetime publish` 仍使用另一个身份发起预检查并返回 403。 + ## 4. 排查与处理 先在执行 `start.sh` 的同一台机器、同一用户下确认身份: @@ -48,6 +50,16 @@ spacetime --root-dir ./.spacetimedb login show spacetime --root-dir ./.spacetimedb list --server http://127.0.0.1:3101 ``` +本地开发栈排查时使用仓库本地 root: + +```bash +spacetime login show +spacetime --root-dir server-rs/.spacetimedb/local login show +spacetime --root-dir server-rs/.spacetimedb/local list --server http://127.0.0.1:3101 +``` + +如果裸 `spacetime login show` 的身份与 `--root-dir server-rs/.spacetimedb/local login show` 不一致,而目标库只出现在本地 root 的 `list` 结果中,说明不能使用裸 `spacetime publish`。应通过 `npm run dev:rust` 或显式追加 `--root-dir=server-rs/.spacetimedb/local` 重新发布。 + 如果目标是本地部署库,且允许清空本地数据: ```bash diff --git a/docs/technical/VECTOR_ENGINE_GPT_IMAGE_2_GENERATION_2026-05-09.md b/docs/technical/VECTOR_ENGINE_GPT_IMAGE_2_GENERATION_2026-05-09.md new file mode 100644 index 00000000..20ef258a --- /dev/null +++ b/docs/technical/VECTOR_ENGINE_GPT_IMAGE_2_GENERATION_2026-05-09.md @@ -0,0 +1,116 @@ +# VectorEngine GPT-image-2 图片生成迁移 2026-05-09 + +## 背景 + +GPT-image-2 图片生成此前通过 APIMart OpenAI 兼容入口执行。为统一供应商网关,本次参考 VectorEngine Apifox 文档 `https://vectorengine.apifox.cn/api-448710071`,把仓库内所有 GPT-image-2 生图调用迁移到 VectorEngine,不再使用 APIMart 图片网关。 + +APIMart 仍只保留给创意 Agent 的 `gpt-5` Responses 文本/多模态理解链路;不要把该文本链路与 GPT-image-2 图片生成配置混用。 + +## 参考接口 + +VectorEngine 正式环境基础地址来自 Apifox 项目环境: + +```text +https://api.vectorengine.ai +``` + +GPT-image-2-all 生图接口: + +```text +POST /v1/images/generations +Content-Type: application/json +Accept: application/json +Authorization: Bearer {VECTOR_ENGINE_API_KEY} +``` + +请求体: + +```json +{ + "model": "gpt-image-2-all", + "size": "1024x1024", + "n": 1, + "prompt": "生成一只猫" +} +``` + +参考图场景可按文档字段追加: + +```json +{ + "image": ["data:image/png;base64,..."] +} +``` + +响应体按同步 OpenAI Images 结构读取: + +```json +{ + "created": 1776909189, + "data": [ + { + "revised_prompt": "", + "url": "https://pro.filesystem.site/cdn/20260423/example.webp" + } + ] +} +``` + +## 尺寸映射 + +VectorEngine 文档要求使用像素尺寸,不再使用 APIMart 的比例写法: + +| 旧输入 | VectorEngine 请求值 | 用途 | +| --- | --- | --- | +| `1:1`、`1024*1024`、`1024x1024` | `1024x1024` | 拼图、方洞局部贴图、角色主形象 | +| `16:9`、`1280*720`、`1600*900`、`1536x1024` | `1536x1024` | 场景图、封面图、方洞横版背景 | +| `1024x1536` | `1024x1536` | 竖版图 | +| `2048x1152` | `1536x1024` | 开局 CG 故事板首版降为文档明确支持的横版尺寸 | + +若调用方传入其它非空尺寸,后端先透传,方便后续跟随 VectorEngine 文档扩展;空值统一回落到 `1024x1024`。 + +## 后端落点 + +1. `server-rs/crates/api-server/src/openai_image_generation.rs` + - 保留当前共享 helper 文件名与函数名,减少 RPG、方洞等调用方改动面。 + - 内部配置改读 `VECTOR_ENGINE_BASE_URL` / `VECTOR_ENGINE_API_KEY` / `VECTOR_ENGINE_IMAGE_REQUEST_TIMEOUT_MS`。 + - 请求模型固定为 `gpt-image-2-all`,不再写 `official_fallback`。 + - 请求路径改为 `/v1/images/generations`,响应直接解析 `data[].url` / `data[].b64_json`,不再轮询 `/tasks/{task_id}`。 +2. `server-rs/crates/api-server/src/puzzle.rs` + - 拼图默认 `gpt-image-2` 前端值继续兼容,但上游请求模型统一映射到 `gpt-image-2-all`。 + - `nanobanana2` / `gemini-3.1-flash-image-preview` 不再走 APIMart;当前阶段统一回落到 VectorEngine GPT-image-2-all,避免保留旧图片网关。 + - 错误 `details.provider` 改为 `vector-engine`。 +3. `.codex/skills/gpt-image-2-apimart/` + - 目录名暂不强制迁移,避免本地插件索引漂移;Skill 文案与脚本行为改为 VectorEngine。 + +## 环境变量 + +```text +VECTOR_ENGINE_BASE_URL=https://api.vectorengine.ai +VECTOR_ENGINE_API_KEY= +VECTOR_ENGINE_IMAGE_REQUEST_TIMEOUT_MS=180000 +``` + +说明: + +1. GPT-image-2 图片生成不读取 `APIMART_BASE_URL`、`APIMART_API_KEY` 或 `APIMART_IMAGE_REQUEST_TIMEOUT_MS`。 +2. `VECTOR_ENGINE_BASE_URL` 仍允许部署环境覆盖,不在代码中绑定私有网关。 +3. `VECTOR_ENGINE_API_KEY` 只能进入本地或生产私密环境文件,不提交到 Git。 + +## 非范围 + +1. 不迁移创意 Agent 的 APIMart `gpt-5` Responses 链路。 +2. 不改变 SpacetimeDB 表结构、migration 或 bindings。 +3. 不改前端 UI 文案和模型选择控件展示。 +4. 不新增新的图片资产表或图片代理路由。 + +## 验收 + +1. 所有 GPT-image-2 生图请求都走 `POST {VECTOR_ENGINE_BASE_URL}/v1/images/generations`。 +2. 请求体 `model = gpt-image-2-all`,尺寸为 VectorEngine 支持的像素尺寸。 +3. 请求体不再包含 `official_fallback`。 +4. 参考图字段使用 `image`,不再使用 APIMart 的 `image_urls`。 +5. 缺少 `VECTOR_ENGINE_BASE_URL` 或 `VECTOR_ENGINE_API_KEY` 时返回 `503 SERVICE_UNAVAILABLE`,`details.provider = "vector-engine"`。 +6. 上游错误映射为 `502 UPSTREAM_ERROR`,保留 `upstreamStatus`、业务 message 和截断后的 raw excerpt。 +7. 运行 `npm run check:encoding`、`cargo test -p api-server openai_image --manifest-path server-rs/Cargo.toml`、`cargo test -p api-server puzzle --manifest-path server-rs/Cargo.toml`、`cargo test -p api-server custom_world_ai --manifest-path server-rs/Cargo.toml`、`cargo test -p api-server character_visual --manifest-path server-rs/Cargo.toml`。 +8. 后端改动后使用 `npm run api-server` 重启,并确认 `/healthz`。 diff --git a/media/social-media-group/wechat.png b/media/social-media-group/wechat.png index 666de68928c618b44d31ee551d74703e2d92f653..96874f4842b0a25fbb2fec9cd6ac000dbc2b564b 100644 GIT binary patch literal 88595 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 literal 159597 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

<8)@g#jcO&uE)FNupJK0n0qN#H4qf z$QE04Ow{!h)S3>0Wh6LBq^79wRYj>dBYu+PTZAqx%^V`!q4u0%c43S0NVVShgG9-q zwn_rV9`M0e((O|rut)NCXhHHzTfyAczenDr#Lm{vKX)4v&`dSQk*R^}scxJvit8Sa zZzefa^xgKgE+0c08`?nqd#jK)wiac9C6Nu4Y8;w6dP*iaG*VRfg&GcAr5^+w-isGl z{mx-tP0w6#kwGz???!$c?kA$k&x{aADC?B5!4GPqKtT#LbWJObnyl}i+aBAbn(-?< zJmXJjGU6M0D}stFbBL&tM=-I~`k^OtxdP2HSDy%Rl1ZkJgt_yMQ{Bvh@-!#5cw&e2 z1JC+@)~+VVFX*=;xgB7c*sh zFBtt5KamE0$@a5CRMwy4Ng2bz<}NdZ%qypT0`u5;{G^^l?x&py#x+)p-~@ppBWGFoYc*LLfK4|>R5cam(UFiGj5xo7m@`Hm=e>bm0N5PB zXxT+FPidG!yo{p-OrFz*A$YHLR?Yhv<%v^jj@wB?`)w%rADDX&DT#06{Oy7T0O)aG zuD0*vlbaZcr2oG_dZ3TM)Lv+7Pn8_V9D$JCZk5S{UvAU!;X^FePH0bIzB(HT#8mA2 zEZ8;xPf0^V1LzP@u_Al&Ddj_+FbL+@unfcnnj%a;AtD6+W%!M{+T$h@7|0qL8UjEU zAqyA@OxB<|%gW50`t9lCa|TUP-QxSD8Xjaw8Z_eq15U`}E@Z1>jDDRNL)| zp#7wf6Ifjn7rA1g$QWJGh#1!IrjxPU!wgWNprE1Bo$K}W8 zT3VuaC@Dps|D_&KS9>~uQ&Urh&|M5I1o)4njoLq_JI?S^_+csoM0HGK#MO+-5^5O2$uKgz=FxYQ__T)CH3J5B>`>Db=u$F^i zN?-#jSqn&r;6{VB=Oz5(Hi|x_!@O1~MOpm%Y}MjoDjfjs-k;&#`$fS-?QNR5Ixw*MyO~2_U!9x9H7yd&x(s3W>tFAx0yWE_d5IWO&z7&QJk{HkisVJdg z5X8uX+{*|2%z^a`=0i?`Vgdrkt8_PKUtsLX@#*@y1F}X;sv&JGC`OhU4b94Yi(D(V zf92MJ_CLu}2HdEEJvhG9$u}F=MQ_b!7hIl0ie^@pKadlGu2$*bPe+S6yFJ6h!@K^m z`0o)*n#?8$$u7I`1M|rjF5z_ULgWZ)CjLuDJN1nt^l`9|>Nw2VUFhJ~79spUF2D=Z zAvU7~*lb;2{5HK`*9D9XF|o_r;4#p}0Q6WG`)<@0J3>a?J0;0gAXUBtjqqq7TO%vd`H4g$^uv!LBD_vm}p!yh80bzw| z(Vvt>*n<#qm;l8EvK6?}{xZ)-0sm|uMZ+t*_p%%9Ulppx$hpX9UWLw`?Jie2tT!;{g35Bz*G6=jW!hs-ql&cWCvTa z-i+FAZf=6ENq%)~OcThc&GSGp#nb|baid57UT^$tVREE6NdAGU>~yZ8|~?$3eCPX9s&cb7bKUAnl8fBKm?z2 zdM(Zg6{Ag4uwT0-c29yd%b+K0O0q4h3Uc9-3y;y<5nuXGcjar%P-kWjOA1s)xrK?v zJhF}7=`mU)(4S158o_Ah^JR{(nffM#ymTBU@9@l}#@@<;Ej`QaeTAeiMTYMS!)2Sh zgTdQDiPYoZb+iRaLaqJMw(S3Gw|M?2`2_Omv0v*foz1_pmh6eMJT$IhA;%_(3(FSg{dG*1MI?(s%yM@IAc|-*=bO)%-D)qfoD5 zO%_1GXc;x=P@yxuCL}3DJwsU9w6#}zEfoV#)deTK!%2xq3 zaZi)C@7K6_D^N4D$v54+DqDHFF{!zKSet($X<-xo&~k0(@vTir0vl~N!O6Of>20|{ z*x`W294_wI3~MfdlmYg=m4lY(WkG~B$A?w3v?OkhtO{h&veT8Vro_tv>2a!S7^A^a zb{>T)m(dyp%Y+^7daoXI7ryB0T=`pJ%fv8%na7`g!oT~&ffsqT#AP(Iy5ez_G~bHh ze&3XiQ_isA;aCI$LuzN-N+Y^sRoMfA!0r7&YO>T4dG9A-*QiATBJWo}A*rkgCa;>@ zZCdzVibUS>*^T{z{-spNe!w?$N0e#m)VeR_jaoxP)mxcn(Sdu94Htfvt0Dy4otY=U zvPR2!rt?y6w>>+;b3RTQflKCa{({9&i=qi{fq>&Bj?7YYOXHO}@+%X)3g%C&+-C~@ zOk;5@6J9!9y;6v{Gi-eQV`}JtO_H2BD}{-8OkUz#Lj0Rx1|sz zR2ykHBuurDn&#e0H2G9dPq>sb+4NhBw;IJ`lb;-~MBG<%hwnY{3nZV+x~S^42?<5= zU?Pt6k&u7D=ZO6F1p%qjl|fqRG_jT2hArcExT6Ql3!XlX@E1u*;j_>AV>lMX{V8JU zYX`VEy8@W$nA$75`M*dolJ%EhZAZOsEWl!8k^Ruo-LJvhn7XUos8hn&ru@=nmO5#K z9{-Bdv#_jU11m1kcS(lys+DJwZbIBkCfi-3Ahyq91pi4ehl71_sjWLS2X*?8jDN%6dH0 zr8vrLoqF^orMYy;BY*qbRY4v7X`@;N1jyTsW*J%T~sPCbf0~x{ZK%L>FWcLgVJDhF3cx}OP-F=#rBsHPNVqw zSd1L@jP)^2_pf$-A+lefMPeZfcW`RkjRvSL2u;W~o5ku&eChMD$I-w|5-aCN=VlrW z6KIJuB29hSS`FqepFahMTRC9zk5ObQ$RH(CAdeO(!=>M7RhF`1+QUd1c@BohhGK0`0*7Hq34+yIH(C^#ne%6Cxi=5 z3J>X#n&Mx@vgXAJJ61CYqN8-!=W>UzMs+VKFo{p9xa1HG5J@VhNiY3t-}z{^#lZ~rpunl5Rm=sqXcC_W?QX_RPl32uV3KS-x4@MB`JcRS$6BQt_NLMRIf~d z6^R3obb-P#)eSn_0H4*i41^os1jf=CABKHw(;qykN&WazQlO_zWm0AGI!AVryDwdi|d82-aojl7f&M)Fq4-wO|Y4bb)AH2{`95wjeTe4voG!32-?Qi z_@1FImS)fDk|}6mn)pXJmi*5>W_Jq(A~9(v6vVyD-lu*WpWE^A=`mANM-Ri$km!DX zJnirw50qrh8a<$q{oqMY(1rV@>PdtRvLsafKqn3Y_fC&i^*cf>VUW>!`431!yQhx) zlt83~@OK!-Nz+d_Il$?_1n{V(y+&OPvK<5{a9o-%p=}PfSR&)72f+v*FYmkAEI@ux z!@5DP8QHrGL?1NDg(};6BOstaf+i#vhl26X)v?8mx0V+Eb8+pJa>XJM&kY>!N7}wM zZXt6{XxrK>z^maEZziCFPOkU%N^w8K_*pdk7)&@Dh3t4>1_`W=&Q7|pMk7v0E7qB7 zK6rB)JG8LjFlb$W+Y*6aB<1M?0#^4{GkAn9TN5rte>{jd7&dNS|q{A2;oIsj5W;vHa#^MM5ME^aFA zf}2(tXouE4S|PQS1CpJivkrN(ATjLsF|g8JJflodEYOB2uPmAfr{IykhcLBfnP)9F ztN>sI5Z7-FW<7yApD{*XLnbU-+$u=-1i8+T6)$2_-?|5(b@sx@>QZ>3cvndHOGZLq z(@dm1ybb!4jOwkQ&4Sp{07-lb-AcDj69@u=Tr&8h=MZ%(k)jN=beZl!%Oy^b#p#$S{S#Uh#X8sII{^i(0lAVX(w0(!1D%1m(fq^>Ry&r3Vr;UE1>8s+`;q};&s%`#cL z0J%=M7+pkq3*hOF_wVzGs3o-yf~w9{%r(}1QkRlvAG$H8QG{!)xzGedpr5C}@gPf6 zC8w}LuFy&tQ3fi`_jV5+X(SK1X)$wwa!CP7B2CQ_-2%48uHwvN;k6qM4uU1)BXg7{ zn%>Bp*paSotS8?%tVbbcR7lwQY3R)3H>d6-2a!BD-x(4?A0>JqEFzyW z9P;3YpGx9*E!>%YxrjZ6-KAH~nWWf5oFkmnyWPaceT_SoZcr7Wze-xe69FM66u&ON z?GXCJXlv{ZER*TBeRC2dUP$Fp?$DNv>uX=}wlXYI%X52dXSf=$@5z)TZ)?^_KRQO< zu4JA%Nw^{#lE>Yj{up;J#k8Vncs14{m?#Qn2Cd3w5ipq`sGt~G3QJmSyo?+(5% zJ7Lg*oPkHdeAUm{9sTS}ahxEo-+t|A-0h_d#oRQm1W?N`r)z zKm53`8T+znj!m_ZgOd3fY~z-Juu^WZzT#+H*@lPyyxsHU%1`pW#5&#IFEZ60L00&5kAJ^E{FS)aIY*Up!zA7~=l@d1 z_zT@AHw3m4?2#;QD4|`ZHGUS{Ea4I%x#=q*sU0tnzaQ2uEvl47Z9HL;>1-d^F(HBc zCDx3PDK`>P`n>>hla7={l+2%O?yGB zvesbr0_j8srRwl*{}*6Bc^BoGTpvC~wnD|t-5OpS3elXgWjDxWLh)a#4D!tkAOi{& z$bce{6TU{hq|F+I)he_bZiVjnMrx=2+gL&(HvPaF*&ctV&1Q}glOEc?!a??edd4D4 zciPZ>K^*9Rk#-emIwF;ugMD;s(FGiR|MJNTe{tIXtdLsiI_CTddCPr+^KokU>gcr-MTF$^#YnIq3K>cR zVyD9Z8d`TrX$!3RS4FQ}TwGv4PbB$K8lVd&Cr+Z62^r$p?iqV2#$?xheU9T_lSKe9 ziXamG`3O$2mh%NVi_qzVPYPv>P9oz8fypnBkoEh#UHLkvcW`eK=oS|j7Z7>;`7@3y zQ{=}Cb(ZFNNMYZ3_Hl4^?(%^DhdOlv(xyn-Fl4KPt_1MVLk{ME6X?v#^vb3qWEskv zp>%^W|Gtme{xyK01Q^K}#ohd3Cj9Hb8Tv~Gyfo>#P22i8C!i_9&H?!1?_;d7&$jQ9 zxxhvnutoKtCJgvTdDHlsyAtg(e zfF9uYNuE}rNR)wOzB$J3MN{)ozg+l=~2oEERZl2Ch9r_g2O5 zF$0@SoPB(Jv`*8>AK{@K%z`u#uvV2fHkc9>o2Q zl=t7W)pXTnGO(6Uz=A8JGre^Lj^IbUKV##49GQsjP*Ve66)mj5h_mO_^UD+G^Y@mg zAP6L(Ja*r!PHZcH9Eh4+xO8%z(qowry!RpGuFwv>uJ&2=#AJ(kEwDBL##c(B{qey& z&LZXob%G+ir~sg+>_@w7B+*HcQ63Z8^HWloF629Hoyi7vg9;eZ+aUhR7g@oB3z)@_ z8h?K9gjBQg>>0e~5LXT#@M9>P)2}wL@LeZ8%5Z$<>0R=)cJ@>8^;i84lHx!Bc_8>TGXqa8Wp*e>~yEA!9~q{zZZr!}Ly=Fn1^lC91=k9W^Bx_e-g_ ztcdWarJ8-?)%z><(MYDXyKX-sq&^sX;0ArT5b6@q2 zkk4(kcRKv}xnje39263nELgBRPjI{ckp~T(RrAyPPMFD&zm*Qb{-oh3RuhkLKQLT^ zfv&FiE`yw|`NLfP8Qmtry^8rQhx=FQmvks1WoK!x`xcw%#m*VM2pFT(e)pD!iLWOs zvi&BeA!*FU^GIvIMxi^qPWg>`-oKDGGnVMQn7R4IO#j>TXUS4fbck2w{65#kxTZ*r zkZqv(u<35>^Cy{8!^FDqzzC)oQK>}1YI*f)R!^Ce1ovA~FoCXYj+&&cHj-qE+IwJQd@~N3{CN(5e5|iCr+ymLZ zpazj@|3Sr-`@sr1NB5;8ny8bKf3ZcIUZgSJv)Ah+>l6G#Hr60IFR~oo!OMg)lyw}M zk59t6B>KDzt#=*k8&^R(`+?MZLYt=LD%QQmt`w5#4 zk@||?W{%;M(IkWy(Nia4ceolIznLr9e;Xk7k$#9gsVlIbhk6QPB=Dki zUd70Qq<(&O7CqZT)>+ZJ?LVIc-i5$H3)*dONOb^+AK_#i@fh6YVVoA#+r_?1Z}v8y z5kYV9_c6?aHV%C+?pbL?$uc0k(=O#cM$>JRqRhcCiS~;^F|%zl`4<=jDGg_5K3wUm z!!|I+fSSzGGQX@0ecYMs+)feFhuz$g`x*#d!@v#vZ)_V{8{UMo9+A?^prRB>zv7_h zY&pds$H&Eu=(YNPG@W-m*8SVY?Y;NjnAf{ZECbsMEzb>0Q(1Gk;UuTcYBM;;Zgk)gq&YTBfDn!lTRQ#V5Gb)Z7 z8Ykke-aQ{B>x(Du4V}Na?N}jzt!T9x-tvO8ny8m(cD>hQ15_;#!qjDl48vL~Wd(zU|Uf;sVIKPt#X6L`*^pscpNQw41 z0CE+9t_u^gU3hqSKrND>BdE{1Y>oW;(y-zlNu+*-LCz2+R{%+n177ein}QK8x_0PY zM$$oZX41JBB)2@gX}LIeDDjBt&}qIyAKngy7QY@;21O9htZdbkg>0y|TI~X|kIR46 z?H(Qqy*BjgBArVy3=Iz_v|3RyFuW>2YG63;auO~GpPhf=$^_wn>FH>K^iCU{YP&;U zsdp@VPchXdFX7C-b6lV^1v;CP_Vz=?yR2>XNCa)*RgJ`m5`@C3w!mCN4W32jDX5Ny zMe-h>W4ubiz4QkyQ3_S2R|l_GO$tVU;Q;Q}o0x{k`vO6T5{%5w5~fTyzFWrj`={pB z056D8;LNXI@^a!nd_5YY5L3XzD8tvoR3d0j?Oqh~-UjUp#_Gbeu_NXFWo<4tq)@NS z80AG^7eRgIvU@_uOi9@5bkMz|b9Q!pJsC;%#tG0+03N7bW0*z|UQkk^vKPKJESJHK z48ofP4yBtVBiT!#cgPW3|61koNEwMJK+aLJ$DUX6dX3OyuuxX0p^43W?43~e z*{IU+0|#32t)*rYK8lvx)?q;@RS2^U{0_3v&FSOCaCKywv-T*_W#l_!$BIzv5Y&Sq2hmuAUj0GvK0ATU6X;BN(?I}=Vl$p_HDKI(t|2-kpxt4i0WW8-nL4(wV ztwh4QdaIkseeR+a*k;vv(fwX)r|fF(DdH@$GN`TQgnVrr?zQ7Q zoJnf<)*|A46(5SREy{NyOF}?Fav~bGJj_q_g34L0)dQsLvKMb3tiAncWJF7FLy=W} zKkhiPp5PG=yX1lqD1TAC_>?>*cPW~OFH6)`6PEJT@iPu@f|H&A3tHta>3ajc3Z1W1 z{B)`gzWha-dBmO)1omS02%VzFPuDx+4a0oDv2=nVn_ zK#3QEhq$=oXH&wA-c1>#ki>z(!K>APtJRB(mxAd_9CweFfhGc1RUhESgLUbHG1v+K z&hW29=5iej>H4=?rpRx7f>bB(@%lqhkt=^Y}0(J84z%N+;rRs zKtU!uX&IRS_^X~yyq7}k2D)3H96@C96r4MdbdA0vgV>!jYCao>BBKQ>puTK*Nhn&K zuTibNcnPT!urNJr_Ls^SNSb{$`T>sE@amoey#uf-3VqHJd88z&Jy=SQ`p=JdEE|2v zXOcZo&3yo^f8*nUn-xR6Jy^AX`x*_g1)vLm`}$R#KzRlqxiB%2xMnJbCmKk`H}zZ7 z&6j3(Z}-bww97!p5wlLifV(l3E~HEy9X9|pe6dy#ACWqO`JaQ~##N$Hpu^+n*o`v- z@7Z6!zJi$vXc&;nmyJ4sNOTa_f75=v!NZ zYA$#pMjQde_Mew2E#Q=LP}7>#Ci14V35KR^HK+EPD2wMe-Wn)QR^EwwO+W57dphWy zprsgHUsCc(3$q+o66&7*2W-m9itS!>*9kSOfRmZ30o;Bd7X&UXFttD)vIQ4@S9~;- zRCVw?%@M<7^B)m};KjqZdqA6a9MZ3vSll}SiUvnAH$^lZKZQ*Cn>VuChW3ww+P;B8 z0|GOACoeDIw0<2z>p15(nkBs&Q7gA0`v(X2d6CvR=+lCJI?e|dPr%>-)lXhthHjG3 z+bNRb5>ULx@cHXk@w;~wD$}`m2a#$P$=40uMfeJ4Ql+cb5nRW*9turBavj8F%0ynh|RcR|bOiFL>dkv%srf?#|?J zsF|W8P~k{sjx~9VzLW!pbzo!x3LiXo$l~DTyfEBdh$eQ# z0Z1!e-x|&N_3+TM-h>)zD}q!)ChNS8zTB%Zchd>P8FV7o^#l-K$( z#c0QocQnrv382`bR=p)^X;IBvoFj;;v$-&$!%#zL(J523GdT>$CF~kvt(tDfu*${} z3(;V@E~sgrw2WjsMC#X~s1!H^bZZNwW-C2f!NGYr6m6loG>c8o(@PuA`39?Fk235Q zPmbVznC@nAQNJSbusVHh_8?`J2J??@d=u(t@_v$;Sv&*^s_n>(uO&vY=DvHH1{| z*@qNimLvEQ8Ld6D_)_=jk>0N6Vf!K~WV;T;yUOH zFD2x~$YNVwK7PW-PSdlQX&L!oRZ?+w?8{K}YwVq82zgFV)zDkvwwHFd1jIzzo9L?mZUy%j#rfKrsGU!&Y^vzx&Z~qZUF}M7 z)h5fOdAWZ`DE#|%_KFvNIUc092R>s&CDq$H0rGpYSXsQD3Bo(Pdy zhnv`>)61o2iJ5MDT~2pqNY}i;g1h5A%d?A}HQW?=U$C{4lkv`b^HCA`@(7|z$`0N8 zhE1F*r7R*2E02l&CyPUL-m6j4H7~tiI?CirURqB5{o*6o&(-gUB zyb{0yO6;^C>bGT)%(YKg8jR?B+_KHJ0oO2hpeEV8i^xHFCbQQd8T1H?F=Bidb$Y*g znAqX2`1pek90KOk^{oh5X_cw{#hDNyML1OEIpvG10#evRekTP?0060=zoWYaO?&l9B=?$FQCDNfdPWvNcR_&VXpwkmWC5? zBCJd?kFM2&d3NIOg#N0|As{^BV}OTP3eH0OX{a5E!)1~b(e>v1HB&QF#gf6n_20h1 z#dZY0K%0vxD_n);G#EPm{;Q(7DSLnF5Z$Z+;(CWRj%!CUB@Bik4g&gTwJWw0Fm8l6KKt9(~j zm?BaUTm#yzd=&RZ=@{tgi(aLmHAd&R0QVv|dereVILa3~qX%s8VUD7+_>jel^u!s+ zUedEL>Hr5VRP%SNa=Xok+$W|r(z!gL=rEYcdC*!I#OU3)Fpke5cd)_K>IyA|;3wqb zQZ3p8VFB2$%kGJ)z^SN*T!>so5*b?yMaxTE2@1{TK7 zPWMk>EBfxv$rb6gl0G4Q)C*7D`dG=O|21G4>dlpjrG~G2ghEN-7z+MWc)%VT1P#?z z11Ij3GSE?;B`rikaYhtHgj&FybJb0bXtxDDjqIRtLvC z3K~(*x+lbgL4Ps57FL4PsTxNzK=D01NGrNoKU;}dR8clw{tA6B3YSW(J?Gk*8mOjh zXlk`DFWSCO6}-rA1bVCd_Mkg*e1VL`J3=5L2#J zI6@d9t*)Y*@O<(cL7fprbYLl8s=9m<)7M%ktpXgfHdkoqMRz=--V@0>WCV?XJiNi6 zrj&iIxV#F9@C3Htu*n{<17j=L_I*;)v=iZJ2dBfYQhMPWu~ICj73I|+DQ0T5Wy6LL ze#%D|>deaMPE*1=vs2^x9pOb24`Rudbb6a?{9|oW`7JTYzaUwDI^yItFMGq?UDKn< z1~+Bu-fsjs^$%Gv2g9PtNeNDzr(@8QUOWkK{FHe%7~jmq^Q$?71=o?H!KsTg?0NC? z++dM70ZziItUDrRUe@=H+*U(|rOsI!Pi1^Rj?L^*oRGa&@O+;C+-^N3A>fA_ zs>sr_$NQ3=4P3zy404U!p~hiERI8jpJGVydeZ+WX!^r5Il@Tmj6He)6>?+&{6rOlH zT)}ez!~IarhzJiepNYK=6#m`1?~C;Hr&Fs_EDa`a&gNOT$L(ygSPB-`-PTo82k z*^M;F>SYIiMMKVg8en}^j8}WY*3n6r@%%?zOkBQ!h+&OBPo7pc?A7e-?dwg$hk#SQ z<<-33#|vp>o|6+ezFor+j1#WQ7j}*{B0@rka8}8LwN!O?&d}=halkcY?n9ZBwX}-P!3lzpXUk0{7 zX*Z~e{U53QMWe+&TY!deqpZA5Mt=PLXAisXt$|}3N>E@AhUHhq^Z_fOcB)+WLIF%i zv0YXIqQb(jzO%-6x95q#bBJ%Y=Fh~&?CqNs_ZH2bV`+A4`U{-Yph=!IGIUNGjwY~p z1w1OSO5uhB7Y)D5dP?xF0p}990#K6@KJfnfCTswI<;hEFF@&4BJ{Q@0%VvK#FM~wa zzl;#aGal>d=>cpvuqMH-1g3IHh5%SHR+@c*;FVq+^ru<|U5;_k zhhcR%LQ@c|*=~56a8v|H;n12^q3-GBl`NB0&6EW!$u6Gz`kuiLAAn2|%csB4XX^zg zKUSh3a9iEPNhSg=jhLxq_otWL{m!RnUtw(`-X#V=J=uBH_1SG?m+ZA=I3}(ar9nOq zE}Gz)X*yYmw!A)@7F} z$Daw&+;CB)t2MQ}hoG~(xVYFKieGN?^8z`xF3{7?y zGNnpNMQ^Tend>=lrNR-Fsc?8wp-N|+8Au-Lio#5yb64r>%F~S1vbyL{gjS^?fIO$1 zYV~=TfO82qnASjBHZoM2VPy#N(re3F4w=i3-aVr}}9BgZ*;Pwh8)R5LdQT$R6gD)rT7USCSs#xLk>?wq-b5S0Jigz}&oBc?18M@XxX5te z0(QFFP6&EcDiEMgC3m(5W<`%DaigMfu+3-`O?^HXXXe~I!Xwc2hvD&`oPr-FuUtTE zlE~8(RE2R5*Mn!H7^T6mf7)gsaiAi5`8HcH!}(j>*Ey01(>rK2>%Q}x^2!Do702Vb ztm@%;LYmu4WVL*BWAeqKJ5N}+0#)b^v^|@(qwy^EN6RW%d;G0;`V>VKKsf=cA()sh z5=AAxl%JNnSN)nW$H^9dUJl@Gcz{8fj}Ib1fZvw#I|G6i(|P70UN)@~M{f14=^*a3 z+X-gDNGK?7-KLeVRoNhQ2IWCA@v*TtxvxC!9-9wHsGAjsXJq})5#%4MeLdweGJ~A_ z6p6Ss@~DZuefP{bh_AhFRd>|Wv;9FRWVnJNd`VCpq zCS_wc#fwJ2o)*?GfvL$`?rvdc4x7ZCu>i|4!Y-|K7%A203t4YfHSJ>P?Q(C#I z?z)V&WuTW2QU29hPr+g=H7ESp@yKrDxTF==C7rVn%cN`oV`@`Na!9GpB7zQkQbaxu zz4URn;@1Z{@nrAk2b1h3a4+NxuYQ@VqK=LfwI{F-($4o`e6y%3c{u1Jv?XcEkpdvt zzigML!~v?B94SRiIgcc85}Ev61;;(CBOFTg`#7e{;C%`=viDOJh-CCaoW03PS2Jq9 z4eiJm<7>7_QfcZ%z}EGH8Dsj3OVzt~(A6TJ*RqOb!o^94llY`nG=W^2`CjC#`W3cI zO((sjtRzUhCSWAV$S|{nwfvQ2ZYIocJQfIX_JD3+NK|#@GBEuH zp<`7xceK?hn-AtqFR2aW=H61WYz<@d#7e?f7cgEr zfOP1V34TD5$hj+m@I}Z*!YPJ}ed%BH_xW7TR39j+JC0@Ovpj zfncUWIp*JwGCg?5!$aiROr6)(O|}`ldJuV>jBl0@Y(sO7Sz%Wd%>p>&I)q@SoRqU* zTEk2*DAEO%y=q0`a(=?Qp%SJ#HSpe48iIH^?jQuVIEHwz-Q1ki+O5A#-wGT{>XB(< zm*K)T4E}HT;SW*E?(S|l;Gi4eNsRzXdIV9L^&8!bkQ+{Y|AklKS~9@j^z!uM?`KB6 z-8!7leWvgVwWa#3gfpOU1X8umw%oYV*h1R0jEn$q0*|B3hfw*vgViC>R$;LtcL5_d zq<$k6htrCeJ>RYy>tpp8=ciAN*Sp}WZb}M7y~7uutBcbwe|-PK!4*OYUf&*ieU&CuqjOG@{NdY3pcoA2 zQ7*EzcOySCH93FIYgf+0>Ly7Ha@k4eUREs&#oxjF zw`LOr)4Hu7SS|}jus)gi8juyDm*g_SYXxzPIKLLD-wq|-XmMFE^MPsrd(GpwvYFHY z^3tgdH9oR&*V_J3)!3mwb=I0AuD(Vsb_6q(2l+?Y)ubb#T5ly{KNU=eofDxo4#lg5 za-5jD$4u2l52V!15{4?EBFXVHYyjZ_yB9wgZo!CIreAK%w_wv~L0FY$wbMq4DI+Od z19n`~O8DRrVIjSwOGe+k=UWZW^g9`=1y}>XDQb!KRS#@UIg))K4Z)Ha7=)$v0J~B0 zahjcC40vi4c7ib3vov8~1GB_gwgpN8=`W)SV6+P&2j!W29#{TevKv;ZIumi zVo=NLDhQqfYrc!8YacnH^QdwvBNk>?Rv&(?}sgm^yq+8P7RgKZkGpIDYoBka-wSaH7ce3}0 zj7dIb@4E#9Ye!k$9KNEwaE2e8s1_D%1k*!Ne_9E+mVT$?C&F5&=6FL!v$T&uYn39E znP(|qj_b^ZHbjbOCHC6x7hbiVDZB~fm3@IgNH&Q6zDn@a4h&^G;#u5(m>yNJ` z`Z7Isr;ynF6Ss|GRQ;_svby@Rx|L^e*6!e9F~}?vOUTJ+b9VMG6{jK0i*$Qe4_x)2%#}G4$Ok%JE63+L(E~eW?%E%i zV2uL>Fxp&JyYk+&72_KRr(j|Fazz-)HBhHp#s@u{{QUlaZo3(OceS`)5GC>L0&W%1 z$AL8c+@eJD<6WJt` z$XDOge4ddBvwi=m&vQ>o>Jo?p(7XZ>@R1RnFVpzbhheM?%%mj7Sh$fjsti({(NOrY zhsgA1uy@0WvCpGwXXvw<}%zWbP ziN9fHf3?LfMofa{UcvAWf*#a+l>Yj;?rB`5T!2iz36kdSBJLCUy2!Z#37RT`?_scf zw%NhyEP*7R@4s;)_^|0RWJ|qxWx>WaUbh6^ZD%V|RTK7t>2QgFy&Ayi-}f#I z3M_(c9M;Mci7cv}p;)oXAj1Jf!pP7N#@qWvNkk&SliNEx6E{HQPzlX(Z_bsEO-)y; zGS^TN%+o~(8&Np>{`2+gZ(BN2D_U~!_Wq*Jli*Dm_Wxdh<%3h$@&=qPr@@ZgZ+8y) zc@>#|k2k%v8Q<%Xkr5aIAWtp^9} zS_)vdHsnu>?p%Oq|BoN~g6U-6iBqURkpQN`3(4%GG@B|(OqaW<>d23R1Th?%VZGjH zAtNTX4xt>)RSRc1YyFRmNu?%%3fYNgbWdl>)Z zptOX}GVI7fBne(~&<(_{c#q}qA*glR^3~NF^&+r@A8$_Gb!BWNrHWLvQiFTs8X7Q; z;8}&36g%79ua&bqkk12U{N>BckG6!5f}!}>&yT*-)#`IY=lkE!pb`O=u#m^o(ofM~ znPFpypKW~M4Mc+N=-3DOFb3dZ5O=Qkce7$8n-=5Y*u=2|JFXqqvwDS&g8_ z?ao-+mYqX7b21_#;kgpQO=OfQHHns3Zj%Ii+`0ALK$-zEAjr~7!D8?V2nY~GcP@E$ z5a>2qKuZifr?Auq_q;oDDvTUcPOfY{dl9P#MLh}V4(Zrj57~INhx0}pa(h`k&)^w> zx{~^Ov3vJm$A2qsF5=tNmRHpTlqrCDzPH33adROQlm|5cjt?*iwtBSgy@k}MxA*b= z+l_@5SssTsj+50@poR>h_MDLZnM&m~;^NDBz@cN1)xXSy@}vrO-FAZMz@mn=O2zna zkuUIfIXIkb%B`eew-4zF@T|iw1YHbII;?aOdS0Nz#n)wjL7uDv=umLShS^OdgkGoK z2b!7#uCF#X-_32eXt>uyjBs`Mc~#AU?k7a~_(oeB%4Rbh?+Xr6)a8lrOePlq*mwk{ zOEwZ%DgB6~wfUBH(P9^pQgr(xRqiFU>ws7Q703@bWX{$Kpq7}-CWB-A`()TYKsE;) zKX2?IVw4~8Q7C+CwY&P@ck94Iv90#q#9uQrGq~bFGy*mq`DZLRv48)asTb`V0j`5b zL`;+!DdhnN5W&=05}sZUM*=9btjZoU$6uALdmnr;@aYP{B!pKls*|PiZig8c8Q3IY zE(7<%Ki@`euE2ToW`BCM08gk037J`O5W-F&QeV*Wr>x*W}!5INLPd%vUlIH^(57cxP%7zr= zi*uzvzp^7n3}{M#SVJ`?+3|d(=+yN$l0Ba{bA_9mZ!tG^I7bG#N&M$Y$g^ ztWFK^N06pG{eZqVY&W0lYL?E+?Jv4%ghx^Hzrt^r6`{)d1^6)y%^c3;o~bp@G6Hkv z;n%~XX&2VQig-T-Sv?(cGLB*gx{$^z6%`DeXQW z>H_;lz4uObuPqIPOxD-s$h)&QBrHYqWywBhb{>t;Q66d!r7h^4h=Q!`3m-*6x%pMc>h8=|ylu!ERSfo(0P(^-A-$N;#0Dapcva1Q5>-w1WYi%P z1tLV5Yekj3I9?j{A^tITsn$N?i)zi*eblEvR}!SZoU%OL{AHIz7*B@pnHoKAU)UD; zqsU(cD{&A!FED7{(64d;smqZ(-=0gwHm(Omz@VTYxLo7_Hwdl{uto)_-X|mVj@7X# zMnVHmC7$xgrH8|gpW**NFBuUQsQ91{%zk~&3!^>>Hgau72U5}xa5TxBE+y-h9fGOq zCav6X&>M}F`7=r1QzP%+b~YXDIjwRHIfb@AS-{my6QzlFnn(@bHP>}A=A6MZ%|n}L zSus=%tKc8cwZh0577cT`P%sBbwByP5hC=JHdh*?Eb=<~!wu;21<>C_VTk^Dt^V(Sm z(I(lZ^9nwm$-T-7WK#w612hB!sp^ht@`HtpP zOTe>pAF&f9L%b1;jf#gp*i)%u0X*c@TLC__RRG?qulIohC+O_{cYF-aE;3)7@@bEL z=VO&gqyhCAvb&MuO(++{D6_dVNdGt*hd6s5X;e1x5mT>e8Y>}ce5nM`=N)(f8a;}^u<9Cg(G0qJrF ze?)jD{XQBmkoynlBBLMT&sWJW-Z^djWV^>RF^q%sHGj(QNbb`QP;Irja!&*yZ7w2z zke?jc77t6HhKv1v2x~(DIWilOhDj&-SC?(>bGB2=`O1-}=%(^UMwFv^a7u?eA4RlP znP7r9fC!H^)2Eyu5Cqp%2-<_Xf=M5*bA|o9bpQB8RPr9WVy^z0fI^0e)vWVbMFzfs z8zB}2CnFp4v2LtYhUuI|y-m$u^6mzsaYJR3WM|aT+hP1x<`%;l`6UIe9#!0H=ed6S zS$66b7jJz^q0j=dfhm4OL`W1$1tW0qgZ!dk`jO7dUk}c39=&FlyRSh996=|GDkHC% zprQP29FAa%Dojr-$7`PrN_~W)n}BzUK5m)sAz8^-Bzj_JYTte0rDUhz{wonELg>-{ zGCdF^EWjO@hxA0-zyH>3^oRqR6IGB65u~U7BK7H@75)(!Axj{lith%Z8z}eCwPYiW zGG7HB254%BNWG?}=F#yn>ZqZcZWhhP6&3=WDtp`!JajM>fe?+%5S1$(qFoN8DOmd{ zF?ORuG+;{G6CEL6A={+O@1)p6-7`4 zDIUgg6bXxfXVAoficm76kfsT%va$;siCn9+$c}0?aH~*hFiJl|f*<-#AS@qg5z0LV zJ_exi;bBx7@)A7(lm$#LV9&?-5jq?;yQ3n7k13``Q8Fhc5pp|r6!V7 z79HM@LGHs&!iJJ_q&+B_h1)BMm)LO|4BPtku9WxhhZqw)!wyIdw}!f_XChvdw{2Sa zm1x5O!H4x|P(LiL^`bV)u5`gsh!l~j#j`J!szk&A-i6`fVsn0IqYE}$cz}f8kUNa( z>Ijk0Ke)_^ZR?&WzQHt~rM(Sv)s=`R-h~x`Y5`V3=*PZcj~r+PzR+$5Yl-Mpb{DJc zD0W71(J$3&X9`*n)L?HKCNCFf+U#73FowJVL)&fl=fNBtj4}VKbVE=MH zuTRF#qcjSXcBOcLl)QwU@_z|`IIY11HJ*gC59>`Zd&7AM^3+4CAoaSGyV8-0D~0GE zc5kyj3_EJpEP<>G+$i97q7tIZ%xVenTL>+CetNwhe{DegI+-xFE_BN27268C^%81` zoEA%hOt1*o@|RR^`SaS`3tpeZX?>KFJSg~pw<8Q{`aEt@c$9!1X>FCOn!qJqa}|Z- z4~U$Q7hirf_78-Uc~{s!H-61JQaL9M5|q#u`Ou4lrXDu53cJS!dPxE#p4X~qef0$c z6!NZzy^@-)+r6a3{#;AkaDRaN-tpn#uH&sgLCjQmB$H1>>8@aVZNi_%tq^}Z4)QIh zFQI6dMSuuP_G0P4XGYGL2z&}&->|=ac|6LO^OmTPtfB7ZG;H?3i}D}L=DV3SQ3<%? z9om07K8-*#9)VO!0cd{PtTC8t)4~~*Ap%nk40H4C;Sg9>Otu!wz|Mi!8a`h@;+sV* z#)6*RipsiEvB;7aiRY{|mxAK52K!pL<*wYieKH5;f6#iN-F;=nPCVNDx77`A2 zETzuPf!89S9WunLRE&d`F~#4C35I6(G_PM=eQw;y-tnL_w2Y-$M)|FIJ_kt6fSf^_l;+dSjSWJ3g3KE z`zoo$m^RDai1Et@DNSC_+?wsjXnYf(Lxy?DMHn@R4xWqBX-BMi-b?;@=+UW4h;^kq z{~Sp?9cAP&C?qbw|DgcY(g>L^i#O@kdTby{P~hy2i`R5}jSLeCtl44f4SmH4@qL!D z(_0L*v>BP14TwFKU(>?8W7 zE`C%&w42BY&ti(jyi3Z!{ZOYayH@G}E8GW8`^v%qalILrVu{96*)8?<|B?75Li0I2$ z<^Cg?VrWzU4!x$BV5JG2=rs!?Uew-XEKbt+_5FZpQ+`TM>y*CdOwSy%((q}`6w@>j zcEO)vI$!(}+;7?+QcdqF_*9|W2LI%nWN$n3bdA=#mJ|w;4Sg_-P~{z6_ms|YadjQu zIR>nXqk5+cV$)xq2E&iy4~@`YW4_wDI?zy|T+nm}CqZFSK1|-)%j*ax;lTOg48{pM zojUm_nlzyn;w6IV##d#fTW_nUc7r@6%mXjx?Jl|#OrtGfw%!+n$a&fZq(ci%wT+ zxz<*QxYOp^4yV`#pTfHw%lYH_mrv5N^`Uw?9DsQs2!{f(Gk1`O6j|hCO(ePLtdl@@ z_m@P|)z>*L%;l;jps3xKerdiX>HS|8JHMaxqgCi35sy1u8<2vb#Uv%QL~!L18TrSB zp5uC88idJnowvW6k5Bq50h%R*ANrSR7#U?OCiuBB2A(-NF^S5b$9`ujjA<>JvU9Nf z;3-FuGoyii3I$rggv{_UfufP`>TDEH%-PYX%3g#*3s4wH$@5cw^~bsODU=&atZ3IF zcRa>CCRE!5HZy1_Kkxy+325HQs*5YrN{V*b1zsx__jo%us~#tQhfX_kzny;5ZQNQ9&n(e>en)XM2cl-{?nEdZH`(eF?P zu&hP8w&<)B5ThB1d+O;)#=UY<*6krSB$P1R3?LUpU5|R31bYg`nO?zFmprD<1*#6eg$s?8Sxi)Bc^;hC! zPir`gy-@y2n0PDQ=ELOd#lS3*0&mVxz&>;=15FAzEB0Zqo52XN2FBOF2}_lA+M;Q! zLg}8PG7X~7mxSvww#jD3(d$%(h6zz<&kx$52-VQx1Jv|A7?&$(|pI zKiD6kVHW^fCy7=CR`ehcgX{w%(?$EoyVnsR*}$wb0DmBK==l>`Ne!Vqp(@j&DsenO0e}$VZwo6k-M+2I5b_UtKXWqMbQt6 zD6H&Yw*+LXyPu62*lPDc9eJ_S_~q+Y9!>=4MquK-zCo=r5dk#fpYP?30$XLNRWb%@ zv+2NG7yym#+7AK;4$4O%7utd;4cu2KPyx6yiDbuz-_Jx?j)6esaQd_EWB#}!49L;| zCx_=1UZ6fY>3aYwSRXBdZvh)Z@TdV86;P))A*BYNFaL)$Qot0r!~6TI3GBm-Q-U0vP^4un`wqQw$dfgR@qaddV2v=B@z83U6v? z?`dMYG%6W`5+f@C2yuXN$dL@KR?VjDQ`i{izD}ll1PdRKa+W+p<(Zk$hGQHYAxQ&;D2+Y`#dEBk;d^^-DccT0Z*|Lr zV8zfabL9q+n&;0A;_khxtOWTKf-a!nmIA$cwfXwI8SGtaG?Eim0+5Y@O$3Zk1bHP* z&k%aC%B<-RZlnO%fT*#NfI4@v0$V50)1cA>_6=KJ2nQfkytimPgEwWL!#`sou;D^# zEae>?&zAjNH-!vA8;6H=eeF-L2n}Ah$Z1)%qAYk-8W#Bb^N3d^YzLhz-|>wKJwn%( z;A*N=rMeq%PgRHdsaj$%^|DUz<{DS3Z4wj0(O)qMz5IiuUm(;1lziKC+Z(Y2szDr$1W3pwzhviq~@~_5Hcf( zlsNLyMZY8`WWT4GFdJpEowCjoFeLZ*1Jl-=&>otIFCuI@$(a9O{anTh4cbH5Xxw@(&VOFo)yXppiGR?Eoi)nGC3b^^8#ZUu0sqpLy<$ZN2oB}q!f5aT!rMJDkO2o6;8PiWi=v_+tk*_pr(K={%M0*CL1qmIH#kwg9anmt3 zqPu2MoCQV)<$^JzH+2N>h|zJ02v@(F5l@@dw_len+B>wgU}zbm>80dh>?ClUYA}uC zbvQJlW9Bi=qyK(iD^#8uwhswEvuIYE)MVogMs$62c)V!2Q|pw6ji0FC6ye^c*qea6 zBWYyE*_pV_e(4m6)w-J$6ASb`RF3YQH6CkxEVCA(0XyT+YDlu)&(H*s57pIHLNsu7 z@M|FLmS}sn^p5Tw#Sx>l>z!dBg{67K*im{sLMQ+7!_#8>M8g{QtHOPM`68x|!$tFn zw-f6qvOM`k(WjB(82PFKjWzI)n;kKeV;F9|oZ?HSTU`BIP8E`HrpT>CXY{xF^Qd2O zI}ss!2CA9dv$~Ma6y_IMS%gNjCj_}ObaMM6kAtZdXCGD3y&xs6XEXeLE6$k7*>_(I z>x5>hlsR00xve2$Un>_zg7|JINCdWA5tqk3(rBaxTC&Q0#aEO%gy6LdG=7_z30XO? zzWiWJv<#t-TM$l??H0PwD}3gjb1O@OESbJ*g{_;z+OeX$rDqoF!^4Vk?d$Mz+CeWe zR^lx_!Swx^AFRAYPf@aDclUbU5VrN)yH;`U!^0=X`Lyd-FU$$p zee~3h;S{mF%LC#T&qI$2bg^Fvn}3xo)~L$t&XF}YTOTIV+)I*fTFb1$zWsom{uQlO z_kDA_euoEwa@q5UK(`)8no8^}Oosbw??TS7R40`pP{23=Z#^W1Kz(XtcHoF1$7btW znOMujVQ+=kQD~{%0V^`75PQCjp6M;;pG>!4m>5r{XT>f~-Taj6S=(N1xZbF@rCr18 zbDvdz5F*OR&gNFWDHzNpD`lj|nb^#%yCiA+bo;3kCeRlii6MPQB>>AUzy4O zAq*Vc*xqJfWW;pO=*zO?W09#Jae%fu*yJviCz1yYI>sCQNePP$36pb5UxMkm?}pmhTOuSRP2x+4+>;~l2<5~_{X{uf0{TPWu--*$ zEzn$Cg_iLXz#Yu@{Ig#85c->6FBWXDs{ZKyJ*s=W!L)e$l`HYi8R$ zdS>JN;E3frB&LSj*#>-FB_G+80=}@gwGf+3_PBffU(Lz_}yGCgQLcnp5&k>VwY_>0=90fshY~s z6z}LxT|XzkkZ(57`l1|4zLQ(SBx zI-J#QLfXc{GLe|kSgXqETo!mx8oA$dS+VT~@X&mmA;iQB_cG6I8=T_ZYUV;=rDb5a z8j1IZ)Gzzm@8D(@D!gnl4?wnGS0k^3@Y-UIbQNi>{XEXF0}75K)j|nKsqkFBmd01ESmF8&g2gzlssa(P)iJDSf+`!v$0uVyxwCh%e2f@WgDHC z2rrp*ss*7Tq@?rU`y$-O;p|>tRW-EkIr>v3_@{Ze;sDA{+v6EyOi7O60E;>j*`TGZ z!Vw2RpNy^3QB#8l8(gNqPo+Y==!KEaNjSRp7T9bNvnP|2lSBSTH4~Nr4+YAZUIW<$ zYTFQr@B2=5-z7g$_H=hM%NRHU7=M}TsF8&aXhw54EsxMMw=z7e&6CPv^+5bFQVt+8 zpM5d=Kx9HRiBdZP;Vfun7@M$o5qpj1y}hx4@&?r(=2b9BnN>}|N{u6#*h*L{OZ=XY zQ0O1$7Df0)0XF*f#-XuThjiX-SOM}LrS}np?xwdY3ebFmn6U9KA zFuK2i6DAM8%+pofAOViRHl2}3q&pyqh8tPbU!|%yaea>pFFo-Y2AU{XX z%+g}3=R;TJ7K9&nw9PY=y7#H)>4+u^>Fv9YUdZ?Kidz)mj9pv`Q!Kb@?if`VwBeV$ zVX;WIsDeX}TmTE=8hvXgj3NjM4H#eUwwC1h`+~_fEDU|BzhvAoR(Vm&e?OV=Vh$7j zjO6hjv_#UFzf&Lthm znAUHNjj)&QK~&W?TVzjnkdo?^@C6{1p$(VLBL-0XV8{6d{&%%Kv5N6N zi8NwLU0a8xdk{Gv2P#3O4N`+I$y*kZ(L!tdyD@c#35a>he@IFqyXnd2 zt;e5S-}2Z)s+q#GEweM?aV*Pv9%?hc=_TQ$!JkeR#?MA4qd(cb6id8WYd2K%ZSQ$F z%{1SaU?+{U$i5dW{YecURlQuiq`DXGz(Lz`?qR9zu+G&1YpJ{xn=W0AjTh46@O+og zERXz%)gm1*m}c?884jl3ZOuUb9u-#Kn~* z|3iIFc;jWlb?Do)GIK2pMKE%BiZ5s2mKSdB=h?fx)SUcgOIXI}K{Wp-uRy+EEVx;Y z!Y0lf2M2l=N}ty{RVV3c2CE>=xM2LbFPrPz=Nnb=43bp&5R_$8Vl|GCy0jjpw=HB$w76q|!b!`D(vZ>1t^(H%LE&FPPa3Dko>L{s}nX7~20Wocp-F?eOeQ4fTL zFE?q7xyMAY9l`OBclXuisp;ectJBphxhM^cV_D3!rwD@|ESDE1$jiS|Ai|!>ohO!% zk0fQPF?fT0J4YKWHxW}=BvGu;YHPl>L(6t7%*G$Za^V$bbGFB=zRU!Pfgl8?9J$zl zJtfpYxhM^QBUb`bvhAN67g2lyRab>?QvDM(lbkF#p3r z4IcI81SNqYZHo6IZur}mL9&4o%o)T|DZ>-Lu!s7K`~|C6UxOF|L~Q;vICYtsng&l0 z3sV?N`bHAs;eovZK7s1pq-|CxW|X!Wd<=Dk9rWr&qrlee>cV)-qxYVLn*!YB4>?&A zvDBRyrF@0%-7A{-Th>P94Xly>sE<^;%W#zY&n6_Es5)nhy&MCLqGp58?0(bolF@4m z^a)t@Dvze1F$5cqI6yfGnV%T^2nI<+!UGO+%P6SH@mP7lM?w1MNrt5Jb)0smJW5~E zLcEOAupijCG)ra!t}mgIlo0-cUBSghU8GGwfA~N-4+s-*6%fyA0zCi>H}3&a5!U|6 z6?jv01eT!3p3h!4sgE{T{Th3$r>6(~tyO?8akKaRmS7F9#4O~xZd*3R{6yQ>iL9a( z{Q-Q$>-C%~cx$NboxuwC3i??Cv0&1?-nqVPzWL`7-U~nekEZjE=lb8@KeG2;Nmf=u zl90VgRuU2_WmG~EqU@O!lI$c@k`>C1P-aMFZ<4L3#P9Mszpp>exqWWud>Zff>-Bs- z#&zAV`c7KYcjz5AkbDXTWl*5?!|pR7lU(jsdvWZDv1T2ro;~#-p4XKWmyiN?EevG) zx0gfVf-QZoVbC}L$;-DZg?;lknGLuk#6*vo1qC>WbH;hO%zrM3QQr0h65{5~GPCEr z_ces3(2rua@d+abzuTELIluYy-emd)IlOdfAr+=L zE>(XQ-$LlP@1U1;Fm=CLw2~ZELVRjIR8cS&DD7;m(J5`(oIzOr4q1kmt961c{g9az$XXirbC5T7ZhbW@{f}@ z5u67`p#c{ zf}+e;yx$H**U5U5!$*aQ4Uhhz7#}w`MjGWyg-ui%?M9E7pSy>NVBUeSnW7@F!wx1j zSGll%9XuH%$4VJ&Dq2=iu?bA=t{kEAhP|^h_NDm7<6B|l0JsZ}G!UHlII)Pk-svkQ z;K}yeUX>d66((yCa5X(RuR;L_^Fns&^3I~}sq51*QyE2jKPv3X%d=-LPiC5DinMOamN)j*j6rZ4~*s z0rs0N`?7@A72@yFEtm1JGZp`;+lf3<0%v(ySyM1s4nOzrJUTtKHj(&N_)jJgN?e7Z z805f6-n&l_C-x6#I{cUhhKBzcZ|N~+s~?TD=5SN9xhAh*=D|l~I2$aq$dAE4>dXs8 zMx1dMOoV+-=}=@TS7$cyT18}kQ-p{QWd(q|C^Mcst$#=xaSJaHQiJEP_)!py55Fa& zoW&&XA+I;|PlGtUi%LI00wPVefBO}qO6ZS2WQZ9}HhyW}}>DUM5>W1v7T= ziF#A;8}3q|l9T-d!`P_l&GpMHeYZ9b%Xc^vC;CIQf=nPk(GaDm`e2s7c!| zA0b&*<+{N#_Si?MQ7vpfRNQ10TDiqex17jmwu; zLhF5kzXiC5zbbs)usQ1O`g^mUV)-FTWc&bF>L6txIPss)9)UpwO|j2!*@%tH`9t-a zW?*0P6*%U#y_fDvJUTEo0>PSsUvPRC#XiNfu)0D{Utbg3$z&qd=3NuKQqUf`MkO{D z=vD3Qh2mRZym*o66PzSdYko32DB5y%Lho9M3k{2u`fwmFJNTDhXjvus8yILbHL?gg5sQwUnuj{A{LuChy@hnO|H)`VxXPrrazDrXiVx;u=H! z3q?E^F+PTl2Sh1-LH^!YY(nuA8VTFUqy!&6xP3+?z(tk|(@|jCZ%q?E#Aud=mKH=H zq7rBOeV*?mfgDs&Xj8o^*L30V_ZBE48^)Gl+CXQM^pV(bj~@`!lrOkSPo8O9j}Pr; z6gS8dW3{z(PD(1q>V+odHGgn0QZv3tV1aZ_nta{%y`q zSBAoW*j&Rcg9NAc%XK}cgv@KUH+f&wY49CBc5KGu?Oez@cg$sm&F3?lv37+_CYR2f zNot;$o1YJSg=b97aSKQRNp_91)SB#nF55;eWXe!#s{}7mY^bi>2IK;#lYc|kB@}zk z)4BNTRS2OnIwAOVt88QF+#98Ze@*&cs~vDlBpNmXjB>YsiN(s3|I6%lfc+<< zlZ~7>Oqa+sG9sNMLW2*0GhY%@haEm6zsC-wrY$4&H5q;P!GM5$8G9ze5WdmxuslGR zGB$QT=_9Ut{DB}rY@UDwH29BU#iP2svaeU`ZC?REC>vjOD@Q@XPmG$5Zs*^~4hGzZ zl0I_1V!loE?+X67yaTT_GR9OC04-DA`Q3S%;1k-0xizq<7|4l@#`P+Edc}Qwd=wbN zAp$1kG|rP`iq)QsP#@#uxRtQPG{(^G6hUVIBQv087;!i`A?2gxBvbuyB+IJ_XyZtce0%ZG_OK_&j$1Z<%)t=BCO+6tw=v8a; zssm@&ajxi{&fyfr@|#w_Cx}#k{MDl;KIX!5YoS;`$Xe>s+oyt85}!}kK6(`DmHf=> zVe32nr}uT=C$0*-o)Rv;dZOrng_Xdk!Frm;nx)aDu~Tv*Yx6Y(su2mZ<}<1!{%ji_ z8w*N*e{-osuh5b%&*Lrp*)9ufJS^EH|nPOC*W!5Aq!mu@Rzv$z`B@wgc%;*m}se|-5%cJ(o z341b>@21bw+GVQvzK@>VdSjfn=J!1qBpn|F$`oAEO+| z{BBG{I%&b^M9 z;zPpY4GNVn8tmbKoGmOsxI24GuvYL(P0(+lk4I{%D^)*JJ_{2QWRYNv4na2F*Q>ah zCRU)QY={`Y#^+b=SY)2ZaX#{nRMWQm>e1dfc8_$JaySlgbt2ua%rZ)rNb6;{12!)h zQOu>Pv3`^)0po2ynLUbA4g=$GObHGYZd;&?litLl8uj5n86MHIVq=;x+rZEZ$cov==GN~ey7 zvvDGf&6A0WCssGympL63e9okjhL#O1VCe?_8!p93aif>-h$<=TUx6qNY{vHXohGM- zBL)tuDp9W^n;!Sf`QQ=30U?y(vx^*EQ{~M(uGSs0G3jA^+mXya4Epag_hhmEXaAl zGcfeS8G|jUCVmlz<;KsWQIm`$GjZnoS($vO$C+&0s#Ryr=$=NDs~_6a{e+(B>Xj>d zjt96j`7k6mJc`{P{i#fnda(I&$x%js_hXM7*P{0m?PC&5kvY&nK`+oHCQAmLS)f7p zDK(zE zs_9QV?Potw=IW=Ys+@58Ag@7O=S&>m$J%C*5OIehb5Hvp(@Sa0$5m)9o~rMi<`12x zBa}(yA*WuIr@ZD@K~@p2pV)sg#{}iQoie!(9i7^9G3vlyK8TuGSn#}kl%2G9Ju0Mt zU2Ezx>7%K~HVWGs$*lrb;kU*r8-++{PCt~jJCBjmlRqt?9BRhhBM3)}oGl68J`f9`BGAY-&bw7wv_g)Xd*d&vHjG{>j+`m<@~3Rgy6A2k z{BYOvdhQ&~ol2|d3&VC~e|>78PlSWVMYhZ44h~$DfoiqVZah@u3Mj;ef9Hk^JQYC+ zharf$m=+g{2zi3o#KvFXEHNo#EaV(|aPKPj0o67o#&F@6fkVz2si|D-?4uhBLk}ty zUX@}L4fzB3H=YW#*TN#>Awhs=2{23PvEw;a?1_5FKtgv4{0Wt?Z(T$XO|&LD49?UH zOds00up5T%6}<-C$#?Y&Zk5nLzzF+?1hv%f-@oB)h;21=WmZtj?K!EhZz~yLd8B+L zrnGy?%&DEtNGptW{pkJdEhXd+4IRR-qnpTUSorlS%u9fj;c$j_)aM!Hn zCCt0g74Ga(HIY|=$|se|@_8RV;;#3cEB)D3zbec>7{n(EIETJIOo z2K9`SlaqtP|Mu4L1X?&j@ZO5Cp0%*p4cWkq6dg|cj+WDpY(=Jw!u9umHOYDJc3 zhAQnelq!2^Lt9so7M$Jfe)DGK&>{{K)=sv6M-%l9iHWID0@?NQ4CW2FJunfGTqcT^ z2+a>#6IRGeOrc$tR!enN0>RI2jk5iab+&9Rb(6wLfovmII6AG*A zRNk>FI!4YHOdER9#l>irc$jbZL}>Hz@{(hw1S$Q4`{Oyl8)|pwS5tQsg>?RNEJIfU z&*bG2i&8$m^MUo7|F)r0EFZuq3-`{SDZni<-Tn6-#wdMPLG}RagFdLL(EB2tm?lO* zwebQOBq8CA%0m%__q$rMZ<@5p%Ox-mobN%olIHD*#19aIG3(9?wF<>?{9lsKNdtrN z^rj08C&m0RR(G^e&plVPwF!lplusg^xlKVa`ovaj+)jy9{06{bl z9z4+D7^fyb7^{~9qtovCLYNuSngR@p*u0!aSk#z$F)Q)@-{cVpmwYlEc?_mOs&|ma z7sM6iqogb&1MJzJw>x0Pdah1VnEt@%$fDpgjD^_R2iaTukt17yneL$5Nq@0Y!o-0t&(5rcOO$TcMCRE$@0KI@rV{p}-~I9}^?=L2y8!+ZBEqfs+L zX$pAK*M39Ocwtd`qo3l|bg$39l{E+HT|Qa{>kdLTN^oytgo8h+i!Alpi3+#t*JFx58HV>d-OM))xI*-g zDEJy#KcxDBOsVBdxC9%T%$}E-ilXtgJI3o#{Pu06^J%qVWu@%0(+494jCEQ43kIvg zOg@Ak#q|Bj0r-}d5{F`PtnzU1fAv0T9%&dgf=OXP}Y zm6dn~j|yA+iRXPD?(XY9f7a8{vC&jsYuyX19=y}1|83lk; zGC*alkvY}XiuB~b-&p5s@g96BMVV~Wytaae4NoRMRGDYeDz8PK@c4=h1i10(?%F;t zL@ccEaDN*ZKvblKL3?v+dn0UL_$WH6=^tBlx7kTys0q2p_!Lo~^I^6hUA(8CpDx|? z0DbxPZTy6PCsN1bbclTlA^4^Y#`8Ya~><0oF;{jBxa?*(qE`Dbd zuF}rvJSH#y$RL*?f#n(7?2#s(6`OBopeA<^7ZxMp8q(Gi4h~fxN3Wo(B}X?j?Pu82Iy+)a3)k@|RD%yl8H|C{$j4r)VNt+!aqQUsvb{@wg&Rv$3&}4mvoL0#Hv1QxaVv zi20vt{Cf4z2lciO)6z7>X14$RMOX132Mlf6Y*I@lL3Z!_Pd^^E2UA`KrBy4N*F+d< zHJPHGDKTeSK|vQk*5-zqyt&Fmpm2wY_s6GMA*mT= zgVM(>x#9;xJ5G3BAv%*(sLe~T-)DZ_r|mJh)MdwTkrB(wnWooW6w0j+24)qLX{=sS zr;huKE%Eon_XDQk?L_OiwH6yE(bHS+FD!n;b@0EqiCqYC7SZol`^MOurT?nSijE8ApY-`{_kSX~jo4^{jf{9Zt{U$Ri=m9aD%LCowUX zb0^tf>*XMg5Vc#~Vz^c|oXVgAG)GQwr_5@qcy-!IVcCL4dQ%Xi#5J$UpD+?(uB#(} z$L&A+KKg2S5OyoeuJMg0BBw0*YsxJPKoM}f(z34_CPN;2AU??-;9^trC-@OPut4g; zXd-0P;~{WEk7x0+k-J)~`le2_!&T8H-zJN@H-3E!%Io#N%TLAbqoi0PbXLKtR>0S^ zJN7=)6OxRmadno3P_2}xnmCCoQX=QI+6&C>CRh1Bs_(C#d&n1?lJ|tnwL1NXtbb?v zrZdmHhRfHLYVtkCU6jA)#+AojDijluoXA)@QXEoy!@$@(#)_QccG9+}-#b@r;k!S3 z!?c&PR|D&LiO))SD155QTShD@@;q?$0o-nA`s+j<2|MF?4;)qcf~SO;ai!tLgQo+z zr>-vv7+ky?)vtj21_C=~{pcAfYBXr=g_(E5D7+aS^e%V+G!=F49&unjBH0J^rUl)! z_J{_%%b&Y@_pUIrrHu`|-&pCRBuA9hZXlGkb#(K@tE?!ki;!Io7A%LUo%2jT%F7m+ z+R-f6mgS>PYe;^FDm_#rGEe=;g^KJ%1$8wgs%zK3ZXdUh5FTQ+4;?Wx(wYl%<&TkJ zx1o8ad)+s+SUaKfoJ)hNjb;38N?k*{7MVBPCGpyB5z$5$i1G39htIpR%ggb`6bNz4 zbIS`wJdiznX_iXbQdM^{gqE&`k9y(%v;YSwg@x8lSPs${Tnxz7Xt>=dJ=K>@mA4(P zQS;+dBbnN)ksaA!Q|N|U@mG_ChkD-O|An#M1&ndYtrAV~y|$%-LFg7MeXb)zrRS`20DRM`uAt zfH#-tB63DH@4IO-A9 zfA5EC>9ymEp|H!nKXCY*%4NRbfUvoqpM%dPp3%3Fq|>Tr%*}pcRfwWUXKam4mlmm~ zZ?s`B82!doK0}-m*q`H)c*>8yaai_i4%1^IKYAR~yU%5AZtj(`Zvgszkd(ERzB>em z94?4!*M@5E-o0|=`P;X^6HsL;=j!RWVSQ4C(tdXOWj)ldhvvIJM==kwK_8AKCYy2 zU`!`Ik%WTod)JGzV;|R(U(vBJtd4TrC%l^SBxKcu&tQx5!yOE?f?69`X88-+vWRP% zh4?f!D#>|uMUptz-j*%CWUiX$N;PST%PQmB{`#T)_h<@r_`8?foRSabHs>TgUn9V8 zX$g-35=sO-4>2-g#@?dO|Ej3xRXl+czFs+bdF&wLdLxw?jxWy7L-6RpK%}ycmHLMhOaf5m;fLT8<%>`i9+j|{gVq`oS!j3b7JX-)@cg3mO9a&xguZ71Di5iQnowd; zY$H}@V`HPUtt~cRo48G(c7%?J@VFhkJ)`cy-e;K)9n~B37hV z;H?wp6J1^O-h0xl1&N0reVkHDl8$NSXGc8lO*mC%*02=F3>~=o8m=pO`3O_{^zElT zbEu@6ZqXejCRuu8kh_BLM+O-uxO{j|q%lYru6%8xy8|C}cMMrxU1_0}VbXSmh*JvS!Bfm#-Y{X@yf1L(0BKYE3us28B_HOLez{0u* zapojz_`L4w^BK+CxKDTKSku#jZ-`cSyPM3lGCFVl`|}f!n&qTv0k$JXgUsV(p%*Z4 zxO+F_?XecM2Ho(c3v=$4h5fa_v36RpoiDQl2?gsL$LMunZTq^r2@jr%CZ(7f*g8|e z{b3%1j9o4MP>3eFc$}4pUWnvM){pnDhS3!oeOEHmA=gbCP{T(?mVSDBbs9IdB>P3= z&l)+F_PES;UFYSZ-J=r9|0A#O`~OUTT&7rGhSh=Q?ZVA4+MMWA#yOP0MN763|1nO^ zXFB^?|NQLHu=o5`ng_NuqiDXFzrH~7iEmUdvPtUj;qx*nEE0h7V#{Kio4c24vc|Ts zALt#(j0;2` zQIbpAsq_)rXGFHOICTrB#kK4Ih0(cvFc(kS&9kwzM6HvWz2dAbZJFD{K~1_snlaS{FZ;)$d6c71rU%_cP`KGbENyL(25ve~IIsDGtTuoK4%U;{L)W z&-;?~LzWcPYKa4#dM~T;aDUgLQUMQ1LyIA<&Lh|fly-F*nfmEXX#eggt=l6)Zq8dk`20`m=hH*~ z`3N;X<-KFxWP<^dBL}S|1YRAjT=fbYPQWda(vG5r;28bmO4;PziSp{|A?I?#lP78D z=q~=T3J-B|lw`*|$-o%<+@J2Ow4~%6kJ(PS^$!ozp3E~6CTPW2?u78Vz++k%>6@I& z+;~qWGg#T5Va_@EOPxO?K-@s(xLTV@%aO^M`O(ojpg-U=6(4Bkx4pK<@aB~(2b1+z z#dz!KR26@v?>FI6CHqGAke`d%Q(~5=Mf;P^^U*+JTG6UBx2Hshcw{^x9hpnFK_W3= z$kcZ2AP@=G6J6wZ>S3U^NO~vd%d6NuT?Z}SAa%=^yd_M?-N>iQLHZ$2)ltNVFAQCZ zA<9uabw+txb~W|JL`sG?7OhBKp$^(4{h9La#8ubl&6==Whi z-;+CuG+yVWa#a(mDK^U666^B4Iu!)m)yCFTW_We-Fu6y>+FlZn@?Xqu%Z` z90*izrXrc*S=VH(Y}JFyOfgT~ETSFc+=;T{sm!P!o)wrBX7GFVQG_kLSVrf$ri@56 z|6+dKkZ|~-#&n<02T5An3S^xnpqK?(uz# z0T58kQ*K%88In))ht+lHM_ZXodnJ!$-6kAoG>?>a8}o^?K>h)~>L0x`7F>4H2_G+w zMqHBOusAZRM?FH?d?JX@n3gb&^T*xgQBc9YM5p|l+M=3p9#fdmtu88EjN^_h{kupPHpc-8jH>aGo z>1gh~MHN+NPRaf}m;Fwxe^c6W2!u_mSmaphrS2fqeYhy?#4J>;C5 zEiYj3^ol~kBe9u^+Hs@4O;2Ll6oXuURiV9H4!3N}2kNaj+tt`pUSzgj+E!B({3Zi( zB#NagoGGvf-wi*|JFfOa@_mi*^79Lwdl7zY!uAW`qY~HMO zFk1i(h9yygdVQoi?hKq!kFMech8HlC`F`w#=QEh77Chdy`iwIrP`p#8;VRyNCJu`c z^jLMmms(DFdV=2VM`@OCipeA9!KfI3q*gk9JhK1KpZP+Yw;q61WqqYDbDEI5-B^)z z&Bf02-Ul4rQCh?$a)rEKMe=~Sqg|v?2&4P!nXW5EjA4OzQhfY#M~-?!5%uq0#XRpxZ~{juwLm+?J@WKD=7&n{pY=J(q#P&+cp+`6h$-D8um&b$KT3|0x9MP*Ty34j zRpQk}Y*>HiL~f7HL!RrB2d+;%IrWM1&l8N(veadR_wJ)5BTF0m5)|>?aQ?Ol!`SoI z>y<6nr?ykm%P)!7#L=lR?F>80t^=}!pYoAAktOx&8=I1{vYs%Yt+zkE+}uDqek|dd z#h>Scr&4MQQ0ijAhj}5FBX0tx=pmt@aiLGU9k9Uz(e2M4Z(-)gcS`JZV8_CsB>7JB z=i2-`IM=E~j@(8~eGq(Ej3AQ}kNZ@K93uWK-!za^E_~1P~yQ z$Y&6fUQ4T&;wGPlEhFj*rmj@OzzP~CsboLSQ(~;QJUk}#R2f6-C?LIdP}m#U1o{Pt zF5OqJz`>mc1~_i4<rX9GV;fOWTtJ1%3yk5g?R)|MpTDAYbsr>0<)+)6$zQIxC3`F-CZM}_K&&;GG4H|0js>te%6srl&xi6)v{-3>SZR}krl$O4jO<%Y1y96ibkMG~*|K}(mIb~R70Vsj0G-nzB z_jo6F6UPAT;avt?1!K6~r1M&M8vZqG zi9UP(N9+BEFABjWhKrXdSI?_rJ-@kG(e~7CBB+zR!W-zb9VEsfaL-GJi1ayH5DpF) z;vAIEiYH;Iv%G5y?Ioex$V$ku*&1iV8$jS8zmy69orty@4L6tboZN+4LtO*KUyjXspQh_ohs}db)qZX<&EGqE` z6ubJ-Vd>S_A(h;QMh-FUFSs#4N6d_==*{Bx`;X`)PYH|pKlKK>JkY`D*twK;(!baL zOK)rI?36fk2&WZ7-beqfrZGh}Z9dK-D@T+5FEO);KY_gbh>QR$4WU3?3SwSOtsz*% zCd5G+9kYmB{J?pvgfYoO+xoC+Eos;phZ$(c1fkhEqmFsPx#~)jXpE2$h;?dkbxgS& zV%qr4!e)(`^3b6}EA%|fW_g0Y zeXI}9*$67VvJx-H&KZwkO-*|H<=-|~@*&N6($buJ0Gl1mR6O|{nnI#rkbX?`mt^W632#d1Ml)V3A^jhe>AFEE0 z`3oaqmG^n3&#={mMmeV0c%SM;bx&D(@+x?i@m*+T$6eP`qrl^4;D)#w+I6oRn78+8-aY)VHvoGNcN9eegFyTa>3p9OEhqdNx2 zX67UdW@iWAIGOf-4As`wI^2BL;8nsq@d&xG)N0|U_eG_oJ4l05y_%TG>dRYm>uzyI zjdc;08Wj8A7NBr@qFo!Nzs{zT;Gm);uI85Y&wIQ``c0)I_4(XSml~I3-()_HIqMQ| z?1P?ceq7HOTZ3f6rpkg&P{wl0f&D_|_e5?Oy0QlFkD2OrpHuy0*{z731@1zu^nIww z?~BxsWSPa>8XMbVH0kiAe71p;G8migQ7&$F_TBpd&ieBIMIvF)p2IjE>0JJ{q+|^j zm{LZek+6c#CcPU1Q;3{YM)j=rB`}$HDZ!$43zltrdorS72Wj8}fZMaX-u;`~9y~@3}^_y9kNm=C~1Cx`pJ)dT$KFv)Kgq|caD2^{+*<%@75miAM&T4Xy zWLa|T0_}*z!@p{W`-;w~M);;}rUf&TafF63RVQ7nT|c6Bm!B`z)_g8;+02D_M|30o z4)^_nn}1$-Du&Z|zSFrjQ)|*zbEU|AgMET+{etFN^x&;6a#wc^+BG`9MME)N+w%d{ zo_sxymujq%6SWT*$#Nkjr>;)@jTn6cv-Y@2Vq)7J+mf83DmlQd$~vRTLM{peF01&#C+n@J06h^b-&I zE!xSVtHe)UdYWwhW~3~=G=5F&Y$;JNMZ;a5D{t&BQQl&mq&aCL{D7Y8+hAQz2A8}+ za?l+yfhSW+`8-+WgcNRno_BdJ9v3CqVb$PSl55`65I{@I;l+92OF)v9@mpqMg1MLo zvKVUStc%}UouzoA57OO#UKC}uRobB@7PU^D^(U$%gzYMcn~ijhsvBjf<8;x$g`$e; z>XSD2%OjqDPbKclVo3~Jb1)Tt`Qin5#C%^rEkM723p{uYR!sFKR%E?@LOOvTJ1GuR z35bb-XDBdB>6{^C>W2Q`T&#YTKGh8y798G-i+H&w@ieyDp^Nuc;7vpno0V=zL_>BX7tzD z;)FxO-cNQviGReIk&4B?D0Ss+4&0|^qr0uX%B>tTe8Xe*x+<}oO#9xiR6eO4Te`Zc zO-2`Pd{kL>Bwr{Cq9L;q|8&|=;qv>UN1Hq(>1z3WRiS)Q4{S**KYjR1!YDwT@wRJo zTH12@LnB#CWwrygPFBNsU@*hpsi8x5Pv4(0oaT&D7;d;E9D`H=m^GYpOe$R`NZZ5! z8&A3@o^&u#4;4AG=da4BWMI}|dj2t_AMZl(959Ag0S$t4%P-W~RLp;iJb?z$p;@xn z7h%6)Rq1xg#Rb;D^6czA)<#5H^jjA@Yk9q%#`v85W?Ur^mHColzD>8wbhA4MJlWlSTgnU9Nl*l+!o*kx$vX6T@5_DWa$TiWdrk1Nn-8-eOr*ePd z@oVH=_fw`*kB@ekifj@^@NE(werPI+K2EqiUc8R%LsvGN(B{)e4{vSV$TqVNct^l- zc+3T+6v7Hultxf_PPx?!i8P-hU>@K2D`8!Q=@3?}u%CbEmF8?m3d+C&7-Eppfs5+M zX-d9>74vRHQA$b*77m4l*O5sCY8W6PSo8Q@Ph^WF9U*`GN2gc#Nf4QFl3qmSsi+qh zY)`XOZ=5F|W{$WQODr!<*>c)xv4w$KPlvqM!f2UqwO#%4OM(mm&k~*Pt2y&8_*65d zPcC}0-#?fgWZ-vYvxAqOtD{|(@7t~DZB~s;+nw&DfM?=VEi-pt@;DQU9tvu=GZeW+ zN0g>&{EBd2$nhyds{uYU;S(bC2c#eCq$*LFrE#6g3OH0>*c41TWYxhXcdfyXKdxnP z5UK4CG>^yCxiY^EqFQsBq=-F7QY;Ps1(pF_cOXpPO@Ue&*UVI>;6ceobDg-%0{_+L zwkzZTkk-a#33A-q8$c%#9D0;uhLZJXG&CIuKX#T$L3NA|W~WKw4q=#m>3!|jLdTdZ zRP$#^6+~CQoA3rF{^ce&iC$MTC~iM#A3NaWe)xro+?A)gtBSQHSI1U9`Ymd;+mV0Q zci}aP+p~U7PLozSiOtOTw`_u^{kiiZ#C@@}FJk$`j~6+Jj~M^nIL1e0+>2QodTa8j z$fY0fehm!FyHy7LI@f6^bqd`XZd82hkkyvlbkJQE7}9EW7^()_8xssM;XI$M7^JIRU2U3x;`05#2;d!*0Gt~ zXLKoYkuE_bq3@joFrg}~YS-`qCzgvR^I&HH0U{1I4-dFQF}A_74Dc6JLZpGJqpO%U z_}M?!VK6$Xa~Ov^hN{=33au(v7vC;Xva4W9K3?NlhEBsJQd$fa#+ z74~P}-_ExSnl zhXki`whd2*o3?f{yhId*sfE^fc!-H+dI_+TMOKQDb&HAInMGLhgVusZY=G2##m%|DI)n>F! zog-GVR%g#nFa~_zfV~JJ!_7@ufn#bXd&MX;LV^*5(5u)Rz?6KBX`I9m3-gifg^}%_ zHaj=*h1|Mz=9dTM91DJ_1rH32FoBkl0ZhL;ocVFJxft-1c-$aA8EuqVy-PU(-7buV zn5WPG#QK7$m{GXc!9`D#HtIEBNOeVpEW*7&zTf`cdG~!ZE+gF7Xk|)DnD(8Eo!p%v z52&9_-R&uX6f)G-(!$6CCzh<7Tz;QZaFj1);rLEBdJLEW2m|(XoLC6$`bPa#5ub<) zbEsODmALjM;~+=iC5?*`>n|fU_#gtvF`-}|96U4f`Sa!SfzZ&ky_a} z;P8&OuwvE1l{sQ7Y13mbqB$BVJ*Nb*W)~@y+5ha?kpm;BPt|xDfvoD_xLr*9dD70mh6wf94AB{Co~RFkbVz>I>Au9P5Ao{pTnlv#rFD zgSQxED{6m4nK?O8`ZjXr=%vZ5TrJaj8@WB8AkA~!lx3if=up_slUAjK-yx&CZ0_#g z7FK0RddZVrdnBVpsUJxcwwVWM_uPRmvE^eaKJeFa+CdL`2WFJ|JZ80`BQ z^j1z2-aGrtY^>HziKSCKW1X{1 z6sF$#?is6HNY~xy{Z71xBKKR5)%qy+*k_R$t@*a3Rj*!|dc>u9c<-f7%GTD^yT3WI z1Sfq|qj^b4uF5Dec03Y35PDbF;#}jwmI%f5$q#xm)jWjl+UE3QbuwN-srkcyNR=Y_ z=uZItUGi0oOHa+2u{ak&=0nb*HCE62fGO)x=#cd4Tp={jBsQgg1uNt7Bg)r;_bZU9 z$4A+oxKBKBN|DztOq#A_jl!+9c?af_u^r zPzZ5#aUn<&k+{RBvg}>G2(fY=r-rbeT99rBqx@;c(gV^8fI*@;9;{{V)?ngGGdQNC zgubv4E?(~Q;euIh@2WA8H418BIhkvPB!(2ERiMQ>t~)OkPIcbIKp9=rVa zpLO9aDC<3UFm6fK*Lt;1LmG&*Mx>B<9F^NCW{Z2FVzDu)UmF)#8b_md0}Qksgk7PM z&6Q2ZumY0STi)K5IVON_z_7fDAVsQA(!}jpJB?zW1{$<#MZmA9S2yjJ4oly|CM8Dc z&nyIX0a4`BrwjW>?)+TkT-Nfe5Olo(n!MkY39*!&#B^g!G38AuElG}Y?|%9SU$j@|5%inky`d- zojkC&5CcQ&Xxnb*W-M1vWpr{kI1L1|xQ4@3?O)+DMRSM&sN#_$flt+ZEP{%7rG?E; zM68gg_VE;IMeb3Om4$3_6T)Z|w-<}ppBZ`fqUF2?Wny$RZt2?!>CkUVPyWF~wjx#V ziD$L--QTOb-$Rdc`Nw)H?>raom@(Nnn>?ONm0aQ%2P<4B;2sAg zq`i#n{G^}Xm%+i*l$6^TtV&2X=4(VICMPEYPKCH7=u|*T|M8Aa*~-S|OjT|3gcrVD z>)Q)u6JGZ_UXm-w%3}Jt34mZHf>4_N^ZXpuUms;*es#6))vMI+dp>_=^}dG|E2aH2 zdTtq+U+~Eff%7-BMnUtHMBU8An7qgFj@XpNLCc-?@cGlUq41GoJ3;eVR8P)ph$QhM zzIaas?0jJlL>Y);Qe!mI&;<(EO(d=s!h~IUsYow}bMIbwRo}mS(8O|v-_tZk_GY#M zo9Z>iPkzXWKzWt1y`D=($Bh6*r0wvI&u{$QSzOv)#8&k}5y*TH-9kBw*X_=7J(=6h zd^o9Pxlqbx58sYTxLDI1y%Oy?LqNE0W8^FByM#u0XH{aS;d;ZsBP_!3^%yrf@!vn1 z2xt&AMfGI+w@{z#_cau^u^T|`fFRb~9>_32D!99rGJ;{P%E~rGok34OQpwc}6sdTh zCaxFi-&%>_@V>@;g6(_X=&-;6V*+meq4g> zs9dU~)eF8#zjDFc+ZQ~z-sv9J5DGG-V2)c?wRGA#5fgtai=LjP<9%fPL;4G_xLic! zCMdx8TcPgp+Ybxj?hh)n#YHWN>)sY>^rkK!DCRvJYWzm&A6ysqB%;0fgufcQ51x0d zd`5YtV*$wXd(z`5-HOz76y`nzZsgw+!Y;5@y(K?HiyUWN<57Wi0-*tj=+BH7`-p zGu+l})l)d+L%n|N z*%bQOqU7rPqsEgW@AQNZvqm5OXI5jlcB?IifksRC;>@t5=r@+OQ>vv@TtRQN$S+Ma zz7iIDRuOWt_`xIRhF!Wm z@mT#~t*cz{v+ki$^57}L46?~odaoP~LXFF?IpJNpQ){!_Su1(gC{Kj>y>3s^-uSY| zeA2E8Ezu9A8*@a?cyIc(<_g(=HbMq+T4?W2d`U=Yq)~l27L$rbA_GUyx;~S;{~_==OsFk3mNPatiA}iIx*ewlC8~4PLSQ{n@B%RBI=kr$zh9 zj5MTOcb;E(km&5Rk~mUS-&{ZR~HYRbTS( z2>VOT?5fnML~&;hw+$XI>ncyM{r7F{gl`^I6Ek}bMcOiEDAt7=2cm6g`C8)K#5JwQ zWmo*wb6PCkCa}JI$V)Q*rd^bM6%1?R@{xYNZ8X=0EiIjM8y1?9t}u~*BcNp6dye{^o=J|s zV{7~=hwHA{$@A{*Wc&gjPVE!e)}uHVYWdtgidet*NKRn6np+!U%+6JL9!_b0JgRDY zelyb~Utf@^Z3KZOnO{~eMP=N6@8GF`oFE5*s{7_GSrf10Mkh@t)f&$SeWsB(x9U&7 zI&I#z(b|4@I4ukqmAO z!3mo1CpnuQlFhdHBhJhdC;3C`=1LU@<qSg#Sg zSo`?N)P#~(gq8wwl5qT@L4?c@b~7Yp4$8#ZK=WuomDc0LKaPv6sM&(Jn+hje zy{l#%;{Wd1F?`K`cJPzEF>!$K)12Aw!QtvBLJrDxNX6(-B=RNn=n%xXS>|65{qT`b zjkK=Y$&F**5vNjtw=p_;D=HTcwu%HSYJEI$Zerz(4*a5GMh4ctbAvN-txjc+^g$)( z+9iHLEQq3G@(<}Ftutr(;gZBy3b-Pk)HF7If}4!vP8znw5oOOL9)`7=yd=nBhlt)fo_?We+>L&f@c-|(>@1wI$YEKQ; zE;}JtTxEFjg_G|i{B=I5-HVF)H=(?JQJRy*s?~f0@zX@v$ZEh$3%P4JFil0{zVKhX z1jaBs_2V`wqj|88CRQ+*Nhf+{l21*#=S{i0hlh-|it@F`Y~g1h=|hiwcY7@Zp(suB zkVfNY<*S|4xwjrP86rM9G!DH^KLN7e<4?d7X1Sj+=sDcV^9AMW-qn5*5!Em_BlSwW z+)}aR$wO){tyJTJSfg0=g1JFwaQK0m2a8Ts+f&+CBa}Gw9pRI^rOn>q6Nr}G`nyzn z7l?=&<++DsM7w!4r*32<3;lgC~PLtjjK;4)!%v^s}jC+G&Ka{sgR3{AOV^jo#o zar{p0{txnnm0$u1#?PTF#((NxlLKA~`bR5qGHE?I9rv~AljxjO#tt$bbBXg+l#*Hn zWM@~mwST-1?&$bdAT*+AuRz9#XaFSD{e~!!`T8O__!-gMt^%6B?s-JiNyBT(7gt9+ z@r&#iy&a4BwS3bLP-p0qE*f>v`mH%41HkP6r)usdxFmd>G8NSf=e7G)fAR^u@gB!x zDQx&Cu4Un05o5iQz;IELojkR~vOthA++Ch#gl&b{&5D5jL}CA0h5L;f!$+1y<{B4? zMODX+o)wl88Z0X@jB;wS{#6mJ)$oc}8BwM9{ihHIK{(CxXP;Uh98a@GhNRp&QW;*m|OpZ2U-y=wusXg9RI-ljP_Q1~s;{$uRaoKb>FAK_V)7GMzL zes-+PTnq$m>qd(&L1=?mBSAx6WaKZQ)67E+m?&IK-vf>sCh|a~Q<5w`=U33gNQU+T zffWX`K*Lm#5W!#z`cGTo#TQ>VSp0deN^pxz9SPb9`M_I0?sZ1eI!=9&WU{}ZczU!0 ztL-yi2agLB%3d!zl+-WWKu`52JC;Fclb)dCQ%iH$YY%gJS&lqEI_;!EsZF-q(-KT5uY%*@JA0r>V!1$nnqq&fnOG$IPYu2!;(uC=pYD;t0RSMNvwbMiy0>LS_ zUhr!RAE?=ve~U3pW$$E_vVt1ug~fr1BE$6jV;Zs{73lBSAlby=1i+GR16ZHGRzFuXonOP z=v!_?6KgKz9Qk~PcwFx=Qbi2Bn^9|;{~A2c z5fTDJcc9rUy2%nQMB`%eHR>F>G ztkBuNf?*2YG7`_ zOj&>B!#inq3;>}c!rw^eGpH?`QNRQQoSJ6rz>FA_b;t{oUECle*xpuR6!bi-M2*<{ zuD(8!T%b#KnE*pWQwvA(d7fDp!l8}LoBHt&t-Zo0_cdyh7^LgvV58eJS2{kl?(O>@ zj3(>tKF9df%6B~RR$ii8;DsQv5PmR2Sg z6u#6zBSRaiUX=KH-1F5Q5F()s%GQnnc<8FC6p`dJ?CAd=O=lfd)z-Cfngd9ul$5l9 zNOyBU8Uz#sM5RPRK%2FwJkOzT6?0sz^Qq@BfHYxMm@9>|Ne*>%=oDa8ZD3aFppLFw1euDoi1eF1JQ zjBO_QEDbQe>bz0$;iPyA;&zx!KLFtA$}8H|v`Ga}hdtze5Av0MP>$_F^gH-V<9qUU z6`=Gua)-MPj2uF`Y(B8XlZPpQFikF73rI zz3~9oljT1_z-J(hn49NxC^GXkys8Dkfakv}uTYuU!Zck!rS}b z^ejq&Nn#FP8a@W7<=~2E{^%6HyS5V!EOkX=I=*sn$Qs^A)3Ai_I`CsWG_!i!eK6xX z8a}+i@?sxj?j5ZPphf@z0W&k*`q`ahCxYV8f{DQER>)9F4*n8^5DTRy{9XY&BUnDa zB!tBC!D=9v4gOzFzAW{pRgZLwVGo2Ys*fDEgGiAx;gN}n$-{?`6Sn(l|Ly(!A>hcu z?QRD69K`JcISuSqBSC2<*Q zL!o!_;BlW}zki?*N)M0VNj;~@`#uXWq(tjqeH4`=lx!Zq4o3#D<*;>azzVc?0N@7x z?0JYl?gpg}NNA0b1K@{%4LQDtA)xHE*>#*`ieTj?^eE9H)4+t&&PNYQWT*jzQuCke zhVo2S=dk7-ZMyJ}0o*(M{fTHE5l6oBf&bZR{L1JaAQ~FUyfqi;PCc)-ece6nH;|08 z_t+Us$BPg=cN4!u!%;~S6pUmea1ezni$%6C?STo{K1fgL*#&7MPUcAVhAqIm$+D>*6mXd#3oYSA_TKUMpCE}S# zu0uncj;(YJw(#Rqgwd^`hblNPG(vD+*ZJmDU1*{$e~GAT*ZulvjCq}HpMscw+O>w* zXGKydNPKr{U(NB#D|c_8{DkbJcK~-66%jP5PT!)v66hh+v*@A78}!V0K#3>t-ccj< z^R{#>jz#oa0t{$BmaDMkbnis@shHw6+^4}WIBoA4Q1}?s{3%eEJ56C>kYC>Fog0@E z7V7pY(zBR+@%wk>A(ok79hWFd&r`e5=GFGn)l*#Dog|+5^9K~~l^-cL;$WD$n)I~4 zi5@_z@eD6iOX>^>U4~by#1XQ|u60j=bZr4Y{dpoJ1Kx6(YswjBP^@m5Aa=3z3oO9? z7#Le4#&v9tj`U7KWA4^T7^{j&q!8oU&0z~mZlEb#vB7@igm5?n!%3!#C;AA`XG5^T5P<6$W{!# zNXPcq)upvgu4)b%bzQ2?y@Cm=Kaqy-nH=w5QbnH|5&3_%X(z;bxwTkwk zR-4@(!FV^2w7Q|2TtzH}A#Q^;@~`mpW-PsBUpDEDi}>@)y@%~L$tHv@`nJ0^OdiElWV{0V4G@0k|#)FM$?6C6ND46>ymZ*YidW9 zjPebAE!CoRoCbUNpy%XO8zB0J1-FoncUX}QlFc7Jy)FM&~dOp|e!)9sq<4`{Q zYf9g$0n;vdx+6(V;G>VPMQB^gRzVA);$>@Z54LDnPxLsSe||X2OC-WClp3i~$8d+5 zxRL=Jc(1={Z$;jMt}$8-N)ajDvj&rBT%L>`mCGeCUwOJG)R&8{Ik*au?GjA%b@J=LzVSyB=+h zdP4=rAAExM^G*=ew{iTB*F0n4Mt-g zw!E*PCxy{31OHbks@@cc1%OEokW1QA7|l!zr8deLePg7bKza6N-~~ltF4C*u%6OFpm94Z90(Df+WkJEix{Rw?ss^u3Q`6kiUY6p+w^S2kYyTgBkeyxmmbxydka` zh%|X9NCD;XnmN-5=!^DmKyct70=h}~jGPH1{+3n>wbLbqKT z$B2)XAh(JURtW8_;lrZ|+^>mH!v7CmyKkz`$<{aabP?;(QBgtmv-6uta-m zW8myJMDJTG4^H`wmVOejhfvBTbkBC%0N z4zbxVp86m(6SFi2qblek?x;338yq&=E_(=xOfxf&1=jWJ#}^!Y@k;eLn@xd^ZdLcH zOT5M~LWw!;y|Q`lhO;}22o1Yl)dme(L2V9-$2q9&^f-eo4CPQr*l$^F0K)}BAumkA z6wn$%T(yV@_?*eACu~o!tWHKWLM}?Fl7=?|qc;Umyj<~(gi_&URCJO}21U)-7G&=o z_QiHO(GqvxlJVMrq49vnd*Myy89%Y{*nP$FZ2i*M zU%G3~(DG2zmsq*M%_+_(lL4V=xc#N4#>3C@ENxyt-blG+!v9H3Hw%MX6jA6?%^|u% z#7^iwW<^AWY|scl=bMjXTQbAm%+{hD(28b!~)l;V();rp`VpokOE(A-eoTQz=Y z+riZq@C=!@z!WG)uO}ZNAM!LbGlnvpHr08+m6h%>PBf(jzI0&>qkrZwlHDo6G7RtK zh(_QuS&dRI6;Qx4pr|Ow>Pev5V`rE&=8c5KfsDF>GxWBb-Nj=Mm^y=RTIxs~E9Tw;_@XKLAq zBo$$JJ!mB{;Y;DeHC_e1K0QvLQs6$1XHr>skvoXg*o{vhrS&N?=FwfeQElotK7UK8 zlp{?*#KfxEjNMh&$`6iEj{iKRqJL?RGwK;fGl#r|=&p?;1uw-p(nZ-f?1}J4K@-b= z)B8uy9C~^x$=v2q9aAe`EC6{|kMd3hi37i6KFNVfi|RG)H*_|C>XG?-oe{nSD$Xg7 zA%IRzZJIF=3TV+J3Qq|UJ8)iks)2Bf0Ur^CA>NTJB*I56k-lBZ_zgkm--1i_{LyWl z%Su*Qq-=!(9-AHQpVlw-wUoN8A0j{OpH#|pS3Kjd{rp*;02Q@J5t!1RQs8f*gN_fF z)b07hWSJ#?O-;SvqneMwFy^-32oYZj7`6Gmv!g6t#vEC1$PIIK1twG~&EAk;i@<`1 z@YVkOS+{UlTYNSC_6N;ut!BQQLG~)zTxuUUhmG36TB^o6Y^_Hf(}|%bSL2`(w0t@T zRXFG%CFlcMP^JNsRu7lc%ONVaH?ecfOo%T@40$yblFxk6atEmp7_eKVFlWqfn+zVu z)O4UDkHJ3C)D)ek{^t-qMD|xwi&^t3WK6&rI#T1IrDb8CMLaP+4RPfMIDGc69Ye4A z_*ms3iy-A3fH!pQz_|;=HW>#D3#Pl+B62Oq&Gt}xvyk{%%XU#rC!FOmEt?kWndw2a z3W=1|-32%mH^9!fB}k2L=dB~fS4ZA}cJdSuUm~;=kBk^nArA+1yG1h&;y^&@8Dxqv zDF>;Sa^oMoJK?@&;H7EuIu~M+&(~2w8Q4F+gxFRXnYNw%+1bhEYK0i_Ft4FPP=j@t zyMaFf=J_u@YXOa&I(LDk&fQ|t^U~G90Zj6sJ-7ne$1Rs-t5rTYD*wl|t34pax$H(X zxNNTcL(gATt!%ZZg z@@s1pReyph3C7PvexV)a*YRx!ng1fjly%~-yw=j+4CHLX-U8>^YuG#ZvW^*Ig+VoQ z{;_&CB_#zcH*8WZunaQ&Nk>!jZAwWxqa z;r>c8r0 z-h-kOnnU0_kv^G6r(bP0iwS|gI&d3t6djzL4uG1!1WlYEe>r1mDJdyYQ8(!9E8GO@ zA7t=>pDmo?Tf2cHCrIra31d6XE$KK_@Fl=KD=a8@#M>@5 zH05`f`o9pY>l8mQfsx8HJg4R;#|l^g!w2LZLqZ$alMV-!O}$U^7hi(75tzT;y8*Wl z^aT{kE@^IYdTg02npyLtth_g)U*b;uJG@OxNw(2(^o8!Z!XvqtQnM$R-L`eoD{rc1 z?>8vM$p%*_j6}teV&Y*sY?9KF1W~Zh1gB{3JnT zc*myX5Ga&E$IKw(*sR(6o2;|>p9l3?>}M%=UNSt$`jvX+fTi}aq2aNegmR#TLt6Z; zoBIS3wz(BHjkW$ATJkglSoU|^2g;C}!5M`aIQ2!MYC`|)xs>Dfe*_Kpo%bNh9xZyvJqjU`S@`jHzz?TBaa%*G zN@;d5p2-evAI++TktHyEeNA1B_*GG_RIC2F2vdwOPZq7Qlma!z#r!lkkxeCLM$veG z`l^blln-}I9Es=)fO)?#>ZB(oT!nuwLS88>QnjPmt!CA1+%H#$-Ms;qrnX>0GA<@&&1DzgjIf8}cMe%(0A zB5GChZ(p>)Vq}O*eTT)N@F8?(@M9py_sZ;erWTB4zXysh9_&U5;UIf{w?wmaM=z}` z*#~)MW8mC*Rx|JMYI}dC*~`vYctP=lvv9UaS=1QuKxIqMBWO-qXX#~WE~o9aw7&oJ-%?V*8^8gv{X{&TCCnj8N}I2oxp!~`5=s!01q=Z{&wc)81}nw(UHttG;NC7Y>ECDCvpb<_s~8ks4{f_QKFGXvuieL zUu|9zDWHBmy7xiGmNlyxB^I@mP@Xh3Z23=EV(309v}Czsfig}P>8w^n`HtwvXlFb% ziM1vMSk*fpJdGO=5z->7V76{{O{ih(5GtyTgCcPu#06<%VPurrV&==VU&6CUo{qo? zXRSEa(!Xz}H1Ni8$ZgWe$cToZaWTCmy_ED6I`_p{DY%OTUb;Du=0zAlJTBA-FzZ0`lY#Ix9eX%p z&e(w?2oU_A+U0$ri2!O<_l7HwxqM*|pa+}ot6Ff#BM5ijZIS_^sJ%T9;PyK^AcC%U zH^|ok1VD7xFt}ItTpa?9dBzC6chM`z|J1O;D`ZqXfJ%JF!h7$nDWH+dFk&dS_gulm z>=)(gf4%Y>e~pHgHjh9VW>d?4IDS?JPL3|?vEX2b z#CcH1fsb4pH1Kcg7Co~gf5TuNag_s@W5`nOfz)I-bHtwHq1f%qSDv1<>l7Q#DQN=j z1CZ|pxadJNprxE$UV#P3s%p|opm#}YKvr9JZfeTolYW+2KB^c{_v=l{b04IcvF5zb ze4_@)uWRqV1f(yy`Xqi2(r~-b|2T!yW&2*?zA-liXb6sc=2z^9x*^cxwIom3*w(2h zbd82fnHq5@{rW7J4S$2QTq3hA0c}x25mi)IFtu;}0Um9QT(%6~M0X5L zP>~Ag7EUr{oOranWJJIOM=ZTSi!51~Z=+kWP`}h#mTV_lDm3s;kc-vheD7IrKe@I$ zEr=D-%zb|$;YToyPp%FZJnHh6zgUxCclo4gy?>~r$wi=PvHn59&?3gf%O1@!8<73WdbF(;P24nznA+u zk5o!*rCxRHIW4#9|LG(o6Rfp$#_z~8qO~MF9kc2S60o;Z=zU7GdzXx@;}#p)&jX1Y zEn%F?xPsKa^Q$kv5VXFqc^-0rp?8IUE6(iw7psu&{CH)~%@dj)SunR;%eg@?m9LXc zkX&yAjGRKS9|62$r(un)@$k?M+%%xj{a6u5feT(Ys%e;z?p~#jAVaNoRu4{tf`X2Y zTx1!u9uEPZ>-hZTbz+#9&EONU^lS2?kx}kDyOI_(Xo-S^UoPw+EQOhw%Av87oZ5Bq zqc+#*tW!^jSI{q2%++?ThSA_V5#?5r$JGEg?q546aM8~Rh^(%FCMr-(E#G}8oBaJ3 z)~dq^2C@wOap$VDf0f7iO^)efr%WMj6USaPeGa6Gy_4)cU%99Dbi`lUFenaUT7`Qf z&OZKA^YdoM{UQSr!GYhDU5@qbQFPzGGI2~61?3(&EVD*T=Lc4=@(Kdv zpX8CUX)v4tl?zzq!a_nr8v>&f*@_BD(qy>s8TCe{Na3J-O*QoLN-x{nd+TUd?Eya1 zhK9@Ld*n3C-0cvP1q)ekFK${)YXl6iD-C~Kr^9(?_8Syt-5zy-nE2TS4>%{D;NV^W zA4?6v1im{Lb8~Yr8XaK7)^ou+Kv?CVf)SVH4heAAN8Ma8W=cOSZmgkFonja~Umg}pdqF0en-Wj|_GJs9 z_rS~y=^z49A=?m4-nf&k3gvAbGS|{f~RbSuIqie zl1N-8(p)UAx0om&MJN7uz9qottk6g9H`)_T`CZ7yl$KfGQ%(Q4Q7o@!<>GP#N3;K; z;y}YK0B+7=5m!)K0IL!B!}2W8^M*GdZslLo)mD>3P!wn3aJrxLX}mNJRzUY6FyT)x z4*KEvEh#0{SXPfTa+Aib!hvF|O?7p>E_SrZkc8I=CK|9lLSJ4l-18rcLuYDA z3cQ~GKzDh_rNB%g_yFEonxSHAPs@5=f3NarMV;tE?n?~0btP{^*m$OD5wt20umMdw;X_!z&D(i zR}-3_O+M0)+vWl-K0lc(=-^ZyB=lK8$PXOT+XJM>dB>IyoCYZt<^K#PFHaQOz8y1&&hZGj&E2}w(r+p)n|ZuQ5_ zXJx-Vrt9eJY-}dMNRpoJ1x!uQK>%i4@*xLf@ z(?L-2xawBUJ|^vX+IMNYT-t4N+Xb^_mDCgspQz;f>C(+tyHN|~L^FQm56ToUiZZ-% ze<|ur1=|RA2&tSJtSh7cL{%m9-v`AV>6?;VL-4e?Gtnx)^05s4LX0+K?+Ky zOihulX66hY9_Rf`qBe3X{tK0;KheJesrT^NVkwaGkr(9(o_LNWZkVaAWl1l2Do-Xi z7Bq+AjcIZDocFkXMpLKWGXLWoj%mX{H*1+B`s1-vjHJYRe%Zc><6mpV5oX`lNSXbr z`I;>M9qsugB35~eG@U@j(aMpCrn73?jV`R4JQx$>zLbY=6fj7LnZDVe*>E7-Q)8u*b{sT7N_(Zhae%v+UtW-_4Gi%5YcBD6_PTftK;gXaRGPoV~}< zod{?NECdoeqHi1q+ZMl!#S?4L6R5Hc{-{`a-Be*clBVM?yJq`%7@>P!Z!mNr`sVTW z=u<~mg>Z)a)RLPYTlJTe+sp(NYtaWk^cv}zQtqHjIjGeHWQ09UN!6uGZ|*BP2o8uz z#M05-t!sD{Y#+(_Qbytp@=hP7jiKVlwcoUs`i2Je=!c$t+y|FL4;(Ps7TH4TvR{A7 zu#3TGv{SYED$|ojTNE?KzjA}%edVL?ky|sZAJ1OK`fL&menVvnZ*X+I^E4qS;wd9% z(Q`&;w+w$|`&xmJi&6iiuHacGg`(ajv9Pe!cgX{nwMq23WCc&Vj;9u!dwZ$y9`MMqrfZDjnJ2%6;O~>(CdOs0lz0 zJI~vETF($;lZZals+x31I+0QzD*N`m%5Ie{alHt*#V&|ZdWy?JU6dM*jlj6IR?2}p zGPq*(N?K<|nRqO(>{a)t5Z}Pd=4^b!!BN@jvdz|*J~Zj0SVyO= z+^`$d7q%moVGSmukZM+)AE!+zRiFN9GTmr?QH(&?m*M}`eIc|S;Si@a@w_jL>Em#g zSvf<}i`KpGMht%=1G+{ZxCowFmW^)yp1(9g_lPsWnimOc>L?yvR=m4H>UUeDQZQf(e`{uQIaK|-xNl_p%ACpA{pi1E^{afRr;A@j zgqo1g9@N&FnYUVIiJ@50b$GEzGRo4u@ZGWgWKP^VR{y?iwXt}1CdDIISuNr`I#(fk zqQS1)s{>6DqozRDaQnzztOQH(#yd3g#&+U$8wpCibE--f^{(oImu$4b_#;&%DxT&& zM9VU$a+rhMHCDDs`)M*&2lW&TA#Ie?rq01M1#&4Q3p8kQwjy`5oUBsHI0M{>W-G)D zw4T-3poJpt>T6oA-_7vM(;%j9zb(C|Q^{ES09#sUPNT#=*vE?7K+%?*X_Dm7*Y~v! zo9tn)tIOsaZJp1Hza@U+F9sd!74pnsx$~}2h}y?X#bYrWo4WeavRureJA2`<3+~}- zR*=m4ONuVI=6(LTrbx0eTtiueE76e77Qwibfzx8ft*)D46B09WY_HKjVi@ z1%#pjs_fWu;AR7|-0RniJb1mBPMBYzdLqLGX$O0B2xGL$Ur=L#coFS=(41aJARKdH zw9W49S$F8J*o_D9DX`(Shml?wkX;M1W08SnJT)_aVCX<*N&IIR1{R=|YnszcF!p^g z2h(Am#2~8XZP4Aq@EAQ=*rhNmvLsoB<%a(3Bgx?Zr2&e1U(`6z=~{wImXB%~Rc~Qo zfCi6e7XrjU$Cryni*3^71G_Zn%Vjyh{*3ZBzBK+(n;(-;Frg=qpZGoNQ?X=lT zU^*-1LT$Ttut)7(a>U)3$D#w_IK#uboCzm}$foVzzg=pLokK6YeE|9lI7wFtiJ$(N zfTU}h>tEodruvY?C0G$61z~k?A^`C#J6R2r$w&R%LH3vc-(QcTak^)$UtdQsQ}&ev zsw%v@Tc}|h`9|Mwf?4@o$s}z7rLXzSzw>Lyl}g1nQKE8p8 zi7bXBO zG0FHZMr&q#eBMmnP&uTH7!WUzI!XKY{L&-62SmRx(E;eR_c;yuzE+JHj0Ka`&_;S> z$j7(DN_1_ulNKzd7Z(>HqGNN@Mk-U85n+tdaAD&dHgO<%ggRJJS3UhE*AVj z__ryE=+BRkDfFYCHx#nQ?Z1SI7$*@Cq^Y}jwaCNQ3|Jx}LI}&aaSf5x`c0Yic2c)P zi|EGbE7L{k<=nPP@}Gq2I$NDt>j@?&=n2&u`lBYvHq1;F8I)9kIQU=v&pa>v)4%2- z476Tf`HkHk6W1`KkoZVDw(M4QC>a#{ck&kf2(d`Mr{)XXI5EmZ4Z5a&W$8R)8eCQD zUx^IA725NWE(`{!<+J|pkM+8=w|UfsS*5I^8h(o2^$r$)ebPsiv7}1#;NzX=++l8C zv%5uZ6S7s(JIG6@@sR#3E)Epym&(oDtq>#Z7p^bmXurR}glqOT>r={o9Vfc2J&n<2 zjm^xOsP9|Z@jqq5_*!Qk_d5oV$V_rJF8@R!@Rccy)*#ZXEKnrg>o`*x{z{Qb>Jzlb z_7(d#&!j=hO6j3Rs=Y)*1XWa-9bc*Y7v5r&8e|Oe!~E2kO;ud3*;V&kK5VM8rqye2`J)T6BHwAG?>xO{EGk%G0CGW1lDZycyk`r_iHGY?= zbTR9!?H)?cD-w71<;oNry#w+ul-gl>%X4x=^7n%%)K1AZjk*sC_SIvv${(89mhWGa z4->x=3q(_K|1f7Cdv+M(eJ7(U3q=as@xic1HtSKWoJ9;yPl)||-8@|u*Hp@#gPjF7 zSV5tyE4nq!1&0&xR%o}6HaiX_&E5(1L>Y-1(642MD?R#!c^O#cCsY}Q@vwL`v--=I zm@c$};Bb2tj5NCxBJF39A{h?Gq%}Bh&oP{`zI|rzMa5^x?05R{qO7$wG7z^ik5;QL zrm}s8bTBY9^+x+6?VXG|<&uQ?NdKMDrI#d=8w zbIg)>1I)(YGH5Y_>Grin;p0bGLfifp>omcV?9UyQ|yL+^kj|+>P0b!4Y+28 zYk%4~0RRV%d$jfBxme#OP$_1Jzp-<5rRty+m^1fmd;I%H29I1(i4vU1SmSFUm@YmpIrA z5qtAtJ$N={u_<3?baZU|h4~dNXsm2< z0Kg0DC;<(|SKpXtpq+>MDNIMoHg*Z_94$VmJ~9zClcTSq1}4%yY!2I&m} z4Ng^H%LhpVoZ5d5acD#AnYEdj8QhVTH38G!^V}|My~TSE*9`x*>Cq7Tc`8FF8)ON* zgy=jR2G8$I93a<*TSO2%16_MSDS%=FqE2LR(GXL_J${f{A@ZJm@Kb&LkH-|tKH_fs z&m`!TXFkAA3OyFy#uKK*@8oY4{nuIUNyPVnCO*FB%h+}_Gl|}RmIV8mx+CD(9-JvJ zI|jLUFSdtSKIlirF9FLNbeDiTXwsSa8Y%=$Nx|B34}{>xG~Ug)89tgnya8Dm5Da$)x##VB-J!5m!Ehg5g*R{H)3~|0J)sAs zwSF3;aRY!Lz@g(oBMb}gSOl?w4@@%vFz#39s|!+A*aKhu`gb0z3+%-aREVVSvj8Ol zD=VvzkdTP2=<{dSe^3lIbC6@`aRP)`Qb?#1oyzJC5hAc{0U#5^ad;ww)6=ws8@eT_ zV2`l)Yy}{c7tfzRH#cVjTisj5vI5y7s3f56zWa`T&>KF zT3I~?yVVbNXn5*G9Sc-?e7K-e$>?8$M?f?P?OXT(%ta5>aiQxcm)@Tzz^;0#aZW*e zrw{HfXmPqJA-$Gl4`3kJMo-Mj^Um&oc?5p;_xHiZ3KU^V>lk*sYH^CUKs)Unhq$0j zC|fczR>KsvV&SiYG$%IYZ2=fF7+eL;?+FXDefnZ!_iECbXtw#lENe4YmZbgsl*Hb;gxIvxedO`m?_ubftLqBJ9+WIa>VG zrf_Y^$zU_7q3jlihF5)E9dKvyX)l%a2pLzPca&}OHRTvH@e=NhMQm%~FO>vg;Aw;Y zCOSHr6q`jIZw}T$hHcarPobj@t=c*|LH`iP&rr|r+c?Z&#F|PohBtlv8my?df**Aw zt+~G_`X3vg$}k8`fogO8Iw3|yyRbbrK`30q(lqb)z~_9d+A-xZ>J9Z%64OeT_dR};k6yk~eurHM@$5tce` zLqOAz!sZj7>nq@%pp2yFgNoN#D5D6MXDyTt`rOcFqjwbEAPw%BoG?$EXjT7|G7=SJgAyByEN7x3}JC$>ej zfMewYcbs&`MJluh%2sO%^^Rv*Dy@Z&2yt#ORH9?DEn5_A-X{%{*0jVfiPN0t^DHGq zD!1$9*om?ZNAvay&3#uY8}D)glgB0oCF(h4xNu&8W_%=r);uKFanzI%sRQ}!ef}gNW$XvQ$S!q{(IdzV1 zmmHG4{sslz%|Cc6i-8QR3cBYX?tJc7B-7Bgi{0tMEE(hasX}e#q}gPnfDvx^#i4lk z9!s(S-ccAaNgjnOmtan-P0`G}j=rtP6RlSwB(s`*cT3b{i=;0Qd-2vE9v&>*64|Kz z>RR4j_EM&jTx-ncC0{@%p%MH3pyORzFqAIHl3UcfiUqx|ZPS%p!+6CyUc_4b>xUn< z#vw)Qj>=tJRh=cx@Rky!Qb{q3N#P%!s#2TG2Y&4@ga++##PtO7|FBmOo4nFL)cu`= zKq*z24s%LKVJ25oOSkM@&9x)4$+A+mpBx=5HWm6hci3A_GP=RSxBk`YeGS)`)Yi>U zsMtaUN<&Xv`$}XO9H2lc9ZgZqcR2NXo1?%qI5DyD`WtF3u-XR2UuPny-~~o zW8hT>x8%aYlTE6YaXEA(YrM^$EsAS?`~V5s9{IA75b`~&7WBuu&UN;Wh9f)6`5 z=(x~|nL@N!nglZtJF|?D&j*p1pYIx#^%Rd}sa;aOhN_L0mYoOzI21*uSX-g{y93L& zpdSOKWHMY-+kp*dIEJT-x!{r;Dp2(Rf8DqYfIR-gFw}s39#Um>o@zj44UHV5?Be2{ z{{m+q(-$W9aY>rf(8Us=?Tp2;{&Yv3+|Wzhi$iB2=+_6Hh)Rb^X={QI`xdW8g14Y= zg|j!rs{SzTb%Rkwrf;zJzvleOpxfmtug?*VfrgoSlIoJz_{hk!>M8Jh%+E7Oq7Zji8p2KasLyq+3H~2G z$+SC10xG$J?Pf%lp$s=wMTGO-&FGL$JI8a4GWeI%?aKALDl>t4kqFOK3FdEZ|w+QCc9p-Rp zEOt0_SWHBM4aIW$BDD1M^jH|wqkUQLTl7SEd?dGqe1DBjPL?pm##zIYn}UF`%9pl9 z)>kT?%mWb}D?9EtT7pM=JKx@ZvXqmAz9*}yP~cG6VJ_p*0>4LC03R5P=3N}z1_wK7v zG>{tzvze-zh!NnEnBn(IgjHeT$=$)^rXBaM8P(`~e*ZpRN#8s<7~oJw5kbVR)hbEW zlP0tpH>LuRg2Mj@?eC|Z&cz7mp}QD>JW4u`w&zZ>ZS zIy9nFJB&4Dqky@hqPwG%c~ zIarv!HT=mp2A#xCPj}w zFjGLURG`^G>m>1YXokf@pOHv!%oF`2O&dn%pZ$C4`8u*$>YpEuPe^pTtXImE$x^=U z09Gq3_<~WDr3j3frM;RNyAh0qPw|uqD|0b;Ny^Rw-&zc)BMdLXc&TtK)}z5d0P46` zqLoX__jUGSFr#_tiF#`)g*Ag;UW};wa3^857*9!=Mbb|& z!DwtGn3vTSCdY03z5?;g^0W#fc;8Cq zR6gS$bpL*xOL8;sC+wb`oml1LYWIE(4WSh|iiAKE35?UFG7GaPvmIIjKxh#A#lHj( zvf+$*AdGHEtGEq-#$cIiFcHw^DTtqn^dNeN8DQn(2y zkn{$=_V4h@Vw%jaeQuJ?!!bs#)_&#ET3k4?_g3misinPFq{i zURSB=mx7SboOe&JPwafyRqV#s88}KGZH-l8iO_tpgJ7Z*@0{n z^Gd42D!6cHq=BDD){*q+N4?1oG}ytsQTeosNJUJBq@)LI96Qss=yc&pbT?mG{!Ejd zfBgA%{j6TwKAbe68}<#nK{ZHh@mxL5hKv30z_?ZRdh%oe5+Mb{GacyzgJ8MU?)(xztgY+F43~If*dQEEiI*{G-2$X2X z#u(I6W9Bf0;!oaQUq`Z#09Q$m6B;;EU_z>Of+a)C}=7cJk62Z&~h9G%a2h6@G>fi=pij}ET2b_he zsn6~_YPfV(78R%dpY84O>p(AOkcmL(XH-*7jVt7e!tp=WJv%QRicu@L>rhI9GS9@s8#F!eDL7g&(cgG@ zOaaHkxD5XLn=ubNZE0y~(`lbQ1Y-%(fbI;MknrV*TsRbhdZrBgz+R9B0On-K(`IrQ zf+rBj1g*8#-(cjmdeh?!63C^%67_qs3iPpgIvjTNRZxx4Mgb1?@Ad@T_b>%m=XHul zpt?Jn1Gx7^6!XTImIXla5Klqm_4o344j>(YCY>|9NYPwcJ}E|QIWmhE|7m0Dy)Vz8 z6Q{f~vysDd77xdgWn-SiN0!s`Wk?Jgi=&EbM0ZMmBNxSLj0)XzDG&Jx>kAa5DZX!qds(BKM#OCH^za8N?pY_hx z)>a6^y#@%6%(H`r#LAWYtK$;jA>RI;ndOk|Vf>18ATZ zD%K%v5GKP-zh;u19)JyaX%9+;+S*#UIS|~`#L&;!(NHk}-{OABV_Ll*z+Ul$<|VvU z&-isJnjo(gu6E~`Enel@j0rad8!M~S)yWdb`2b8Jg?{1y9cSS3z-a_Vv?Exx&^x}) zrG~!M+X{Bua+vfPatBZGPT2m1{^Y;E_WugrjJ*V(Jb3H?fA%1-3_>oT$OX2F9^Trj zHVa9u^9IQeyXvV1V6DP!130@kJnfMO>+cjKITNC8NfU@afr&r>p_H#&xekAFM2C5yM*5;jNWs{2`pP@xxpy$z3Sn$TEN zB6*+?ZZR~Cp)2Z&2<(yryILi2r9AX!GbG${l25JqlZAwZw_3_{*=%67NKozj@gr16 z`&vcMc;FAE34VN3W@;p%UTOf}Wh!cNjiG!x=PWHl&~Ysx{GU;ch~Y*T!ZO+TuAh z-MerU0?p;5WwD+dzMx4yf-u)G4otr(x$yj;A3S220C9F3O6DSRUXO)=P)!EsNmR$>`xuO(hG=SHsX=rSwm)lgSiU*pslNLu5 z%l=M!TGO15V$Hbkj2|O*<9rn&?dBB81r$tL=66pW1`-|gHW!pb4@Eisb)|3SZG_YQL|3j|-Y9>E z_tIfjVC^>w4*urC;VYXD`{?@0cXnHEHKRBh>ODx(Pk!NjXqv`b$<;j?_%foi_)GaL z%WjF_MN>V^p~U-$0P$aGxcisBOM6BU^)v%W1&Id=S#H6c&%X_5FH*&uKMJiTLL-Rw zEmB7Rer$W!!|h*~;LRKHiKC@BV?Q%(n+M0U5M*6c+?owPT0C^`C7xN6r1SKRGb*Fy zF?Su8A;=EmgJS|FMbh1(mObFF2MOJrd$Rm!p)2glhMRBQ3WqM#n@fbUzPuat7cIZ~ zpB2FwGcuN{jS>mlK*e znX<~d(S@%=7k~Nx(RAMNSpV(&myx~6NLE4;%HDft?@h`qn~=Tt2;q`VRv96by(8IV zWbYN(`*-@>zpqD+`;X6^T(0qczs}=4j_2{Fb2!%et}P4k?;ll<*WKP@^)VlD_-6Dz zRw8n!W!MVV7bBL~vtRmXQ*3u2gQ{@aIc9(-)ck0vQ11SLS=l-rx-v4RcsGk{xhXtj z)=qcnlX7={RFBQohy8nZU{Y$k%j&_MU0-objp4ZRa)8+Ji3Ywk0-LCqj%jin`c}96 zV-+ih9*9^BbP;=4ny6>WEMsG{6zN1B#MMih@vx)5f0#?^8n-;3k*wqtq zmlV6K$VzWohA2@786Q38Yh&~b8-@=)2S`fX?U>2!7uzn?V(v;($8vAz)OwzL;a|+n z%<_8VEw7mC(cb-);QrGTniv-4A;cH2B0YufoD{0EJ8Y9&zmt$A;Ugla-NVl+r`N>0sED)e|mpf3WePf z<~4d~;;>=0rL+WAaeeZmL@sk$#~Rs6M<2ClK4TzW>*QmM&dnJk8+*&Vo}buYRLzxg z4-}A*SLm$#@t8d0XP*kP?>l2h_sLX?pRuzux#&TW@>#*wM3!Z(y-HiL5zLqvI2%y{ zn1}_vdY!*2e2+av2-qm`-M$vB{=VjJ=sILZJztMfFXX9S2(j=CbGCMylru?;#RsxsV*IlVix)k!Z;=>@#)L z(2PS{yqvOsi3`={8gQq$QH(ZBqCfl4_wQHXfapRaRetSVnkRimYS*VWyKX$ocg)EL zx*X@Drq4)-RQ;Kvq^qzbo14_eQ&1i{%5fY@lkkR^1b_G|^oFV&?;>pB)xcouwtI|+ zI073@qFYI25c?uRR-v7M>$)VIG*4FIsaaIFVFyJnE>r!pkpRc@=^?aZSv2~85)RGS zjbtqk-92jxmp|JC-!=@aB>jYqj!Kt;-_uWI+QVYas9qx4@+hbk-A+2St#kit0bXH# zz(E)}jcbVPTsP6eY{Kij30y}6207ZB_orH(-zfXr)T%zZ)c9v~CDb3WralxSAxd7W z=qqn@DS6jw+Dw!SU6yo4w{j-3mdJeWFe~(}&bqg*GpMuS=r;%R;B7{Qw-zx)$05m` z>e0Hj3x6A7oNZp?`C(l;_}p3!p+3*gHJb9HWulDZ!8&W9=@^US>lAzH_!z056vQ2` z!@*2n(&kwP#jq&6^ptOchU94FU~B!yj-3Q+4Qrb;Kz&KUQ0X)(b(u3KRkh88LA7(V zymAdGJ8Ll5JD`tbH`bjzxv_#{pfzm!9Y?cr$Fe69{!kHZA$GG}(GFQDy5n9M(+Riy zZ(m(95#0#-lxK|16NUU}o%Ss`Sudr;KPWS)#60k0M%Xa4Kyg>8*1%J}&)S5G^XBsi zZWF5woH#zDw~XZof`TH^zSzIKlSHkachpn0Y5qQ~9cLa$3mPh(kSFBj{;F(MY;c{7 zdcel>__b(~WzpI#iCp@2_5iv(ow@Ef4a%n0Z~N}}N7)mvZgUaw#$ev{XTo~^ucB{P zk`}SxK=||SgHMEEl#O}c#oX_|XcgT!Z|3C4<Jk%B7YglqP=jYzlPg{_IypAR z!^I`_7a5DB8fK)2XWMSyiNmE}VrNXC8{q4)gdY>rwHV|{oA-sI8&$H**=U#H%xj?? zQ&T06G`mm|0-(&n8nH1F(_m8Au3ODmtaVxHYBpIntC{*UHKl05H81wu$I~2FnYi?)USFYRd|%Ct&c=RW`>rWilt-_PwgRfEVkaS zZ@D8G1hXH|Z~(K>XA%4cPS)9B z_hw)d5rOQULeS&bC4ze9#y~{ZE>$)z2yCD14z)jn`>@1E%J6q^@@E%Mp0@4)KQ4TK z*J6tUQYq*ct1bd&YL2_*@893itZyX;XanmVNDhvqIn2eHmSlYb43X3KKd@U3{RBFt zWVR|7Wk=`jJ0<m|6wgJYO1+X4)%X5rqfM{w**@L-v31 z6v)&Z1P-RH6YFbhyQf|o@@1hxSC8}Vod|_*dmb?a8A@L*>RW00eFO6Ci%BX=P%F0_~u6gLcL1%LiI z?4X+e0*ZoOy^6BqO-RH3iBZoD)T(Kxjl0(WxDO3yo|vA%VgKgPSk2GDakGnx;rsM) z)GDG=z#LudGi!1$G@z(t2xU)v-1w+_uV9!TYCi@CTY^jW;@eF$ibjA~38gUw!+sib zHie@$U`PVR87``^8DcjOM}7 zQ3jtQ^1HWSW;>-UQTi7ycBM9Uw7l(T9qN7_;0{QSyr1C#7wjHhT(xQw{=({X98w9m3i znu4^*s|XD`<&v3ZkE>#I63zd4gGNSh_Kf}%p&-sxNvEfSy zgc;h7goaM0z6O7>#dA~>?F@Gb|8K(5>Y78?a?VRUgO3)AG|+YX^C)r~)*kk_4*76O z1k0vOJ)sxYarw65&1dIH?1HgvjU_Zd?^qe0$tB{rke`=h(};)>Na}7i3AX(3Pm!A< ze{Q5o6Y-;v%Iw42C1&~2lZcYqcXaP%3~5K7Jt?y^LUmrExU+Gwn>zTxjXPb}iy(r- z*UvI2(qrs}^X>aYPKk{=8dx5_C!vVc_iK3-7aF;5xEr2rP!q4ScvvV)2ZtPn*iFJT)?j`4XG07yHn(`(qOynS(cRJmD^=3X2SH9{K*R^%jwC=M5{^ zAT8@FjuJx>-+QW+7Az)ei>0=UznRGCmceHoV1^@pKJVg>-3s|CI3E zT@CMpMhleHjZG+UQ!kP#Ln^0x`FIj;5r1NO3K8P$S`GPV0CNW$Nw)hL-OGuj-j|7D z3zy?}@69fx2%Ea!!3rK5({>-t5=`jrUU#9-DKE(RNhm%j&cP1J)**CX=xzx=;^?38IuHxn1#J}@NX)-f3_ z%wc^gBBq}OShXK7+>=P1M73E+#e^cGTSi|yUWe*;iUDXJi$vp^Z~|E~;*T9V^3%UW zI$00zisg+&W+j&IuA#{If8hY;o$&miwgN5O0pv!cGS#AtokQS*nYAmO;r|gmEUjbr z3m9mqoQWa~N&Rx)Ig|hD;g;-1990=}pWgmJzpJ}|Vd~TTyCoMyVt|qceBzEI#7ZK2 z1Rq-LBjx_xc{tqwi4U*XA~V>=IDt|nv_%dY;sJ4}MdHi?%Q$!RN=X?RxCvei7N;ry zB_(eLy%sbe^I`#+Y9Eel5Fg9HCs(9V0CuOZU%ve7ATt4Vr$CiCtDK2Tk2w&s10X8Z zJIo_f9eAN*^ODtAcMqxLtJ8Sxwt%Uxc6V_O$-d!m%)q|kWwpLEE|0R|+JZ^qS8%j@ zLJVzdi?zqpNq8I*s9ufWGXqiWetzM~&;Tb$^EXX!Knu#cf`@a{ zzl3bsN&uzp(Ak~(41U5F$cp;#3d%eIHa}2!P)1T1XxtUx+D%Jv)5#1SNO085r?_iy zZTNS7>UWk-bJ_K78-CNpKgA68K$j%g(Ns=cEJU(5f)`OUQTET>;=kwxBANYARBqJZ zc?cal5(Vv)z=MVdcLf#qvf{7ee=(wF^qg}|d?-gkq@@u$0DS8^Xk9k0;TK;(L~<9p zO8@I+>F)mf#Z#~z;)|Rh5$Et*gU$+3?3zK#zHd5Sp~I_?t4K;p0&m<+@8~zz$TNAS z4p8P^eZ9$Zo^6zL&n$f2`=RqP4RI;1o%?$fSJxj5sW)L=XuSf&bjNeLSiC2N$;O`R zK+nQLMdnUhxF_c42DwY<~7B=dq#SCYMgE$&|P);UUdDp9R`S-a8m@eu-C~hpcDp2MiN&V-jkVlRe+b4 zo+GW#XRm|o=DR^-3P*h`ev`Y6@XYWR^P3v+{4>XayY6z93~f{pEHxKSPW)r%yPTxM zjAm%x(*7N4On?6T2~JHlE-7zFP{7ylE1pb#XNxk0X?R?~^*Tz(ygKAEbZ z187(f;)XOPE)Gt!y4UVp%T3Z1oTj#HeG`?Iz>R|cjb2pbwk+BRt*c-?JS#{><6GO-teDAh7!%}Sk^8uI<#gPIb}`U_ zX3q!Iw|@tjr=ARHwcGpbr}#`B%;IZCx2kUcTE95n%nI)jNL(viZM)b;&Q z;*8cbBjbDt^9O1pen?IKsL-80wIrjx_{>*9!5c$ixNZtH7c&i{w|9{`( zqp~iCO8wtA+#CG+@LqWH|Gwe=nQ*?0R2K7UH+D}UuO^16bS2v2(P3k@fr3kT630)z zXUVmXAJN|4HBigYEi-3vYpi`;yzmB_{P7b4$6Rg0vuqLU!Z3uc9t$dhafXpChv*{B zG3}fVkDAdUYSx%&P5Z+MJ@VhQ0Zfc^#lNLo==h(|e`cV(#B>q9r9gkC%@jhRR9onk zGl44-+G(37QYO-DnL@@j{(ZSf!ejllHOdIl;jLDRd0I=V{AVvo5Y8nYAI0il;$bn@ z5{rf-3e@-Wz36OO$JIWo_8|@z%O~B+Sb%|Z?iU`1Bf1k4;<44&*jxBGIR7Qu zh3`}8Aa~yuxG5D;@e-MZz1}OHy91LaE0VtO!5;_qt?g&SLWiF+eT)A8@Zj!2$t5FcU5q(Dobg4kP6U2<|u8rQ4*@GO!w2JM;vvHW)`N zUx=|sb}d3h13Au^-f5665`0*%D@D&M;X)T4c}F5vl9Z*HZ-8oSqOpZ#4n>Ph0H)}+ zdH2i1akbHRq9H~=G`ivWmyIjV-9wZc*r=#ZegvyfH17LP4_*K6KFfnTyYpwhgIyq|HI9KTy)MvgclC zXw^3%rlQ(X3_O|dvzK;4)T_?H6-bY6-1l-mTspT^HB>^uZw;>u6fAJq)ow(^+-yo= z3&(hdqr~Um1X@)^UH)IR70cuoL;4rM9%&|d&o|30z8 z?@O*o$PCn_Kfa%m+!}Oz^Cp?#zVs;?$;GrS1&;`w=18>dG*UlTS|3|-9YUpKGri{~Q$;+Qkwo_$%Nt12Ab?XDI^OeAAfp}V9g=8YtS zv(a8G`Swhz445`xKA$7^Q_Fh^-yxM%QK+p)Z?;P$!_58;#jqJpxHVIKNj!lw|D43$ zrq^_Cq7{#hxa$$`)YI2}Ve&S#SJs-=UBr@NLC$V|yd+BUXV^>qv5e%&AHCmO)6{qujEY^vC;$WJ(cpsU)nsEy0~lnLhF-$dTXwQ_1(Uv zp}V-#*~JF(yLRRitRKGJZZ|XSNE~|=nmkO5{`F&i$@*&6s>ef`nV6L=inW;Hyrswz zkz75`XVv`fTm_7Q0V23&+RP+!YQtVByE`7@cZ;VVDU=hU%x6E4_$8#dK%eRfF-BO5 zgLi0^XgX?T((Q6pQG+WCH&^GUV%f`zlH+1l9^Hol7UKK6`>z?A57t(>-=yiw#Jsvc z6DGc{ri35 z{%`^2q=t?8*%EOKYC$z@{q5?SVqKzZtz=_+)tr|z2Et|!HBs$|DA=(obo?uRoC?~@ zR&oiwk;Zz^FV;j7U8sj#FOoA5E3HJM^uDJWo$CQl2>H{7bY~`EuGd;>JQ+4ZdPSPB zii6vnjzgPTs!Dzyc_g$?@=82AZSoap9@I{m7{ZdDF*Pc^5xM|p zVvm1LlWwH)XV zfCvH5>gR$8#2j3e2r%Fhk*JUlz)T>n@R_+Q83|G0+|C^YJ6pLfE6_23y!JE|pgD;i z3q1&M0%;}6F)RSzhGQ8Igx#=2!Th?G>@WIk_y=bX89od=I&`5d zc88n+BxcGbxu*WYqubijjP;v*RE;QfATN|cvx0L0xM60U-3lVZs)Z%c z8Gd6`;^yb)2d)NhKU^f>=>s|kbOO1!9DpeWTCJL~q!_}|#`DbL_}ZJ|&<_~Oq)tfG z&4WrY$2_-wSD13FIq=nzGGEBN{rAG5nh}utm7sx*!autsC?&`ob_XvM?qubXptXRk z1(#OtDG=Y_SEh_E&dBx*na#prpoVnnB^)s!`vdspcL8|`Pz4TWM7TS{%(B04Qt9ZG zl9J+n{O1i2hr_TPZcmPnA2V5{|6>h< z(gkPn2H$HRo-|U*xVCv1;XzOn`vUykU&~HlQULhrrLD1G^3pKeJE0%<&&&+oN5GC9 z!Xh7NDe$(~a$T5%=Q^w(9uSmoz%_03>zskYsIjd+ie_Q9Dzjme&m|oDz^agw@;JZE zu+fX3C|ot^OQ)MepX&#Bkl)?G%bT?xUBwP56qqp8+XVc_ajRq?8(K?FVBd) z90E@}I6>1zyb;Ol1p1ZVB4Lek%G78nCCt+(LvI7`DgXkN8P@%Sp~?0u*q8R9iZ`Uq zEV3iV!XB$vC9&kwgojOmE~}lL-3)}{fXxG6^{9_X8J{r_&7RL3(<*CDDc9C_8#us= z2d`6nd_0^5KpRzia0hWGvwwVQN)YnVg#y(Kzj#U0C_=mGtBJwF+0 zTF9rtG2Ra?b$~BwBp|T`ChqLz&KtGo)KR}AREjeu4HzdK?ZPFE0A3zahMJD?JeHt=^RW^sZc z`-Q73=?2{&t>2Jr-`M!iealCJ6+TNT7~`N1SGgP3ZB-S6oyi>OveW5MzG6zPdJz|d zt>N$if-{GA3r&Nuy^0EcKn+yp!Xo1N^M#}BDHy!D9Ia-TouqRIVd{ct`9Xmp8)TkL zntaj)Xmc`doGlaS@Yi7GgtxHxdxG^hamnDzh9R9g_I@dwcBH|@A*pQLj6I9SH(V z@FT#KBUFu*2_D=!L)bXzm=j4&pX)T`^hCc>BCFcCVDQBYkOWuQH@9ts#iCrK7BPEW zTPW64F;PWEQHWoWCJPXeZ+=F4I%z6nIr9GL1&sg9IXDOE+;qVLslChaTY>=^;+Rn0 zwHweH0e)_HHU{>fQalC+_q`?!{OoG|n??r-xK!-SHpLD(+6)u|#u}8F->1;I7#wO^ zs&)}|1c+|`TmB1f&}C*}u>w@D8!?;y+ftwH>(^jt1D9N$@~@vi*VdmvnoaVG=i`$k z1z>DbrbKxi{$QE&_}t#rEmL+{B1!LB30X7nGNy1P;{Wu`5m4UP`x2+~QJ0nNzx>&3 ztz#frE#t8`rM~PeW!BkSIV(2KxqlXo75j zIv$rC_H5y-T3-k$7`rty%`c@4oa%?B@HzOO#Lk%TV|Cx=r@5;u*!`bpYOy4Fi8O%? zOi)m9bj}K0>=_K9IxM6YMe0R!W_0z|U0@4ynjphMtG$9-wPUvjNAoKlxQ4;Y-NOIy zC{zx2)sgY4VK2{&7Veix3fV;LL-Qx(aEAn*8bQBkeLLrYC`d4DY(ra^ig0XhvyM81 zrmiAQ8#6O75a};f!hMP_T^S5>nZ+6LOs$x%cKi*GR5!|MSsBJKL#JTPVX+9$0UVa6 z2dCS+nh;u-QHwM-*MxXbnEK*BL@_7Iij(sh$55%Yj|4}X=3%fg-(iK0qhU| z=LPr#Ek=;$pgkta06m>wUA+5FESh?)&v!*5VTZfwi*+L8&y?IC{x`g0~_|BGi9!V%eu4@-GnHi zXmCfYA{j!3K4`4@kwY00lhHj0{leCHw=qsQBJ|Xpgrxm3?ZP?tc~n~CS|@lOekHq8 zN3S~7fk+o25NWPl_l3aUo&6pZR_n7QeVO<~T`AE^|8{vjXwxS->{sN<6Iq+=K8wH( ziR561bLC2LzEDaFjA+AeOWKjeQTv~8eeQqpS=f1+G19DLR9Y2Lp1Q(`Fma*~o6_HY ze2g-;`h3o-{sK90LwMlTHAff;TSNL_^EWQB4XHi+yjKfP0$;J2k#o=O@M#8Yv-#isrN*2%aJY?teJ*?Xy#(F;mr!$ zv&So93aGO>%A;QPQ#serxQ`>yX4JzXtDU)OkjU%gXSBfJb$GG0w(L1F(U>tCg|Ff4 z&(%+6jZd{6N1bIc_!{|b>&(~|5}A+T3(9F8wa_A7Ov6}yP4-`Z$%~4K{0ps+f5q2K zG_$Hrt=jZuJlnrCj!^9rIU{yNe-eT)knNM5GsP0Zm*CXQC97MuA0K|vk&L*-)aO8J z`TE81uAp#zzXTJXu67uHd-A9sy)bP`w?B>b=()c0QG36~>=B8gh^D%^Yo}1W!MBCp zwzV{ent#g0SMM5leqCO7qKuo%c)DfG;1N>E4C6M-7XJXe&F7Vc@hUVe_K|q912H-7 zxS-v@bW4ZE5pUh=d7BG$RPSa>t7OF8fRa~-???rzeTG@H4)6{x0v#C0Fg$?MA z)?h1XAF>y*@X%mi*HOL@5qhj%$&~o6E%VcGTequE5W->UF8 zKgbJxoME3~Dk9F=7)Q_r3Rjm+28@XOVA-h{`9ScrR+B%`IZ}wulk<23oJ5L00#Jj7R6uxTT;S`oy$&p#KGq4`=>}^Q7^2Zad6K)poKO$z-nyS2@UxcG)h+JmM=mg z92VxxBn!=b7dwel+l+WtJZX^YT3G=&@PpozPUXk83yw4-AogbWx0sfz7x`^E77~)| z`T3D#3@GJDvrmzOKbve22UYM1dLulan?z zQ|1*ZGn~d{$>zp1Cqf#$4p>MhVo$?S zF%poP-~WJEEauf654TpZLL0J?iAzF)V;V(+uE%e4$wi=o0@}9Ap;C&AE@l4*B+oz? zs;(yGorPcVHID)K53~q;9wIBj?+z4TviHCmo}mZ7Dh>{+7$+{%oy!d3(KRcewgyYg zJERl{5SinnA}bdp5ZD6E$t}zx&0nl}-T`BJMN&32dmI9IAjUy7@Y`)!yPx17aTm&X z#&}D-742=`)jS6r;u~Pw0AGg7OdUX%NM=hhV0{nhsp+g{BiX$J@3%AR{Z(vP}nsOI_e6%X*G8)3^+`yQz#792WW{y$z&Qc*=o(KmOKak97C z-)3Io{;v61`+j_PFMHGU)Ksrp)HI-T_SNP*_@5Ru((L&GF^~c#eO` zF|>{+qoRaLE?&07JQ1BEF=$0{H?1U4B(n^dxv-&Fb`rk=Y%|PyaIyhn(l@dJGY-1; ze~^xx2Brz!HRPubCCOZIwhqe*;^Id}}Wa;<(ms^>DyTH7H zZfBTasA;*U!-MR=L;+f7Vpj=2U4Y|wLa0(dd?XeK8H!J1{zBEr`6akGvZiC@)CpGJ z3S-+mOyam#!F46oHiaApR8=V>wd+8*SdL+&1_Tl~`-L!bITD_zq0{FK7T;Au zO^VJZ5jBT%*y{exwkJc?8gNNXO(s<_(y`YO zrwsqxg+Pd*zmw8%Aq2vaDl^1;w%i1RDrW!QEkZMZPB#kvXjmf+ue&fNsP)RCLe~r*XhL)#p{u}_6CCXV=`xv^-zcx}UokyZ zIX^%5pfD4M%`P~t64QE^O!hr|rbm!AhM8t4?C91A$45^@2$>2qB4e&Jl zZ;x1?IQ-g#^U|mdS`e$t7b`B$s|$}}yhpH*m#A2DSzG7e;-T{S_cgq!wd`fhR1xJ1 zVd4AULR5H}f^TB%+!F8t0&I*aR z0JxO6)XPszT1PmS@Rp?7i&szoqL zI_P@3K&#)kKX$^UpRGUr9qspf2-a}R{)`9Z53nc+<&wrXVSqS+Qdm%bAjSKy7Zo|2 zAHWvnu3ROonEA3v(>4|lF}kAergH}qk4w*OZEtQ8cPyQ>`PD8p3Gaqw{dfUwwtMc= zb~PH=B4>{nu=1*VYNcK2Tw|dR2EJ68A%(T(6+h&c6U(>45vjxocwhEuLo5Fn@|9cp za|4n!Q^H(aK&d$yb=@$wwJm{YO>t`93S1s(9dlLVnHk}5>7S2xA`DEG*e)YBGA<)K zZiyR0O#rrJk~{;^?U?88N+Uzop8d|dmG*LDIQ$ibQ@2BCnW=(A>B?6d6vxe; z+Tv8iy({03S&gYgxr>sE*AKQ{^;w{~y%f&i6|q+3J`lj!X-mZm&Enp{`6~F%v(`4_ zK!iL}sG@L1eT-A4I>b;IZR6yT1BW|py7`Y*e;GD$D(l0bIidRudd^lkFYf6Fo*R`V zfNh}MT<$?Je^n<&q3N6|vIo%_I?Eh^D)!6J>}PDF0W02P&qD8@EScn>WsMG_)MJq^ zMSnc^FV-rY;X(Ei*QvllCGO#8?IIgtI8;Z!b8n5)Q#7a;E^^-cD}5$c|B^+oPx~=P zlwuu>p^9pJGr!th+idH0=37^qCInm@FRycXq;_FvkxVlbc@$+klf}(?tOE-Kp))m0J#vjDs4a zHt{szO$=q7h*SkHzJa^VhM)r+d<1bgOC9x%*u6yBybRc|0j$VH0N3QfL$1UScw5DP z3s?nBP*qt`lU(Na(BET?C+HQ^^w!d(ETFfzx^gG{X#TpWwcervS2yq$c6Mm1HN4Kd zo7B4uGp3WE<1d5H+G;B}8B7fUxu9EXXbi*B8_+wQogo=oB;it~u{+k*Kr{ql@Di%8 zir)lhJ~aZl57x~~12dgpAJXpdea#drmGvYMl6SSUFhBQqe*1LY&CLyRur{vNU!gOp zf@{g#kIjl;VZLfsmsX^3QHPy>tZ6sLj(Z$xbYea0wEFXNA7V?N=j;o4bvT!;FNj_h z>nR!6=a@Y}iY`zu0`%5;z7g&BcM>TQ=XZp&#Q|akb1B43!R-Knp?^5TDBYZp{AoU! z-E?Swiz_$EaX?Nl-{K;0xi*;VC8F~6Uiz(e&v*nz;y4&e67Od{P0_6w0ibz4fi#W#>Dyha&pfZ)Fu5_MR<5_TZoS<#_1p@FG9Al!I<4t z$kM_BvOVqJOz|K$0+g)GwR;i+_|kKfdK}d;xjY+h8uRjj92%67h|Aza?;+iL zZiUM@cat2u*p(#t`#_6mI3TYa?g5&)&nr%*X0MLmO$t4OdrNQ?hxc##GsgIi`>f_D zWV{ja&}eQ5UK~(ixu#ss<`7qbq9-UXk_-L59hlI~aKu4k3Va(N3}+)hc!_CB2*?Up zM{~m}drREzbtFI81}nq#_EZf>z?qxjL82FJqR`kuCW47_iZ07lhrpjgH%{P?9tlN^ zVuLs3#>{km;hfDnMD4!$xdtrHRCu!HX?n|Yo^{i3#=mhz!4t|og&Eog0dzPyKatz; z*7NVA%8d2TDBIsHnZGlLD1J}56lL|)wqZBKQ!Z$Dl$Nl`!Z@eJSBvQ5O}!dO((Jyc zio&*wt-;u!vMUI`)0$qGh2&LAN-ZGX%X|(Wk*F@U0ev1$jQ~Cf3=AwbsktIy+H3i3 zxbjxs@JE(bp>_!f!cAwRW$@;f8st9M1p$&db{xz)y~{VnWG(14Yor#yXSi{o7&ffb zo=D_&{{mX5FVxOi-(cN8JYxh!zH8C?H-)L#NHl=Y;X|k*a{2;lVl;*Yz`C{Eo4g0o z4%ENag8=QxzXL~Fc$arY%Usc48N1SQ)WR+fh;PB2>t8NT7Z(A5q&x*6|dqXrKG$P{X$6U^YN|*ZHgm9l&ydS(8dDM@vU@9@MWQG!F0w}<)o@ap`1k=GU#4cK9$ByX32dg!zyffc;Dnv zDl&^eg+=J;#<)f-aqPNMk%CL=$*y*j&q7gsC*vpoI`!2be~61(C<&vV()xd9J~&5I znLOGJ%V69anqjeiUO|~)^J251hFbpUQ`oHL;6*}<9?>tq1{UUaEZmP0352}w?^z5` zXvLPMM!%$`JDNyEX!LzInFo#b z@9-v%Xps$Kiz7Z+s(jMMjin(<_BwmPO0+Q&S@dx96K_Ugn>I=NS`>}Owe1{%;O?ks zVbOr0f-qTkh9`hKeR-9ogxixa`_x3!iwgO-5vO{tJ5~1yPx>BYBabiEEx*nG__t&3 zNyX|$O>_q4V6`2$eA!1^{b94(0-X{&U2#vH!(+alhWK0K1XJ=C)@1=w2|?PWJSB|6 z>xUZlR-PYUr$^>|)f+}{zCOcIo6Mxd6psB~hCddXElW?4!#HdEv(AYDxk>58&s^K$ zR{^NUWUdv`y#dbN&J6U?`nclj6vCY%ZD$S6HYzv*))!OWM3!fXzj7xkJPl`EClha{ z6Gj*IZ5$Rb4m#O>Wnb}7i5}PV<2t_PMLk+Bm#_S~t+9x5xGUUn@D1StKL@SdBSJjo zji{MT&wW*4Mf%s5>&$+K31W{4e2MPe(NZ6+IzKCj@iDOca;{;Xn4|knE{$Y`Oog9r z?CbTkbm`aqBkSV2A9e#_XQ{K>oEU6l6TS_)fi3&bYcBA5MdJ?@o{5Ql9Bf1HzN4ub zhwxIlluy6;v4adecbXObuXEn@S(IE$Fx%5CD}K^0X`4qPM-?6z8A08WMG`HJZ?$}c z2e7;3$l081YA06hkr^jGDrEcjzwnJdYVVWU67BK>Iy71|I2?I}W$raN2{1UD?N4sC zCSNJAQuChrg-#$v%FaBsNRuSb0XmdTh3=66omVbQ2;djqv}&cQnA)yVvt`TqmY{Y3 z={6QaRo|MXZB$(x?iTW1g>hxPG<@L1AOSb53^K*9czCD%;mxp3`|7FYt&u4#)HNNI ztYBiB0)8sInyJUdPJmx>Y%dC=9&5xPUT_hB~mDaywR zr;UU~xCryJusq=O?Y=CzKsJKAMWB=ds}%Y1{qWC)PI$Y>DRG@rw3@`RU6S1IFt&PZ zswHNN(RQ-Z> z5u17FRMS)p2zUQt|JP=@nF8`gTwDih+uPpI z=mHy`QPvcIU%=A%6V-2_atVr|ZK|2ZA zn#fI%*iC`a-gl48x1zjUOzesZ^WGndz5G!dVed08SJyiMs%7I~-wa8Kw9t;Ey#P2+ zogwV@y?}64Glg3ylf}|z{$Bz1KBSo6yv|~DXS16b9|tiHkOq)fwl2&=0y!>ZSNk*31E}d^ie&MD?w#CR0%= z#&?UX@Z7g>-hshUX;W&-_M3D16P9%4(9UVN>+)TX=*1Bnn=z_?-$l`RGe^ux84Xc) z*aZsdE5D8%IE$z92LD|3xG{hzNgSWpr2}Z1Yhy|Lqs?$ocU~|IyHEfQ_sS+%@>k#b z{>N_K%$Bad)hZegU8{oLX9vKfYA&)CM~V!70)AVe@p%W>$De{BZv@rvxu~B3Fw(^Q zmU@i^pWO4U{?Cd)fKd-i?s@~(b=-XQ9}q=5|0eU1<}>MzdCe4fsXgDm1@q93e?$wy!{4W~^kU7@e$@|q{qVYTG?@gM^rZ)u$JI16MqnEED0J#q zTzs+$#2R3F|L=(J*GI!=DYPVYmn;gjUHE_C@IP8>Tv!UHrI4bQ%rCZzRwC5QE7gBP z@TpoZOkdJn_^fFbY;;hynAp z{yLpzPdo|{4d8vh=PpzwS&WOx!6im*|Cvm)V7L#+#y%I&s~ynS}lZULa_@ zy5U#xf#D}U?OyR+dsfMfYKupY;suy^h(upfeU%qVOp7`(t=1oO{CayZa&DV-Bh@^i z?;T4L7RUNEa4Ig>ReS@Ew>CG3?Me4!tQZqO?pp<`b}=0?S}VJKV1dVzY`Xmee@fmi z8BJcS@jmNHYNAUSVoOD|-=tR_rO$S?)L(j~f0DW=a!VHfg|UIj#iPJ=-xaOfK(Q-f zOrerarusemA@S?GJs0iAL4F;NTaD}=e7f~YdYYI`TiyKR5DA{k`kc z0fW9mz6}*WHNulR;8$4<%ZjH zJcKf4W@cVI)l+GCbgc64*`hY4M7nYfSc$l~UH{HB0J6DuEa<(=^Uiq(T~<(%ZanT% zcyhw+Y1I42`4gGm*4EZGHa4$cLzNKSf#S}wQ{9#WU+axF4arXB0!|=7s22gKq3WDK zUxer*aP(n4j@(&Y6M=DyXCupIh%n+qfR{)nOtR|-paYD5m-_giJ!!&_4IYJ7-*;Sf z1HtV{WutCCBmCRc6;)L(fNWt0hYV1FXMg`Dp!_zzxxKbV4vkg6-ZY{R7E-8A&@412 zN@P0#a@zO2#uu)OPxw(hX7>Tr*K~c}6p8`3of}&@%x8VQ*L#n`B5uGb(5NU6M2mo6 z?y=}8l_Z4+>42i|tY496H;m4d}5Y!{0A6ZaA9{rGYU~ zy~}N*Eo*!eE_*lQ9<(m;@Psh_&?z+n=F2<65NbU zSUiK{PHf9`r*QEBRUK*65_{qB2%A1K#`hP2Ua;Z@S}ro293`z zq6_nT3_X}QF}&Zw+M~>*=~u2F?Zpz@lE8&SDV*6`x~R4-L??AB^T+i%GHedY><$hN z;1`BeQ@3!?i{6m7dEh;Ev$(L;zdbIs;tn+cAg4n9m#<%Mv(=Z}jxETVrAz(#c;tNE zos|TOrXye%k8{EI#l{fC~wF$ktu-ao)as z`5^TfFh^fh>KznA{~+K7zxiAWj4N_fi_sEFIMSOfJ_gg(AJjT~OvtmR&{Z1yg(L+jfT>X&seo&9P{hl{B3XdzQag zQOV&AdA4e{Ulktt3LAN{1h z3Zsr>doYc>RR+(q)ffR3Y+h3N`(H2~hA6Zrl(#3=VK*pi6@}9`FiO+T4utyO6(MU^ zdD@Tnd5_q~YV)D}@58%?Wz#E*JtY12vNBZ}e_!48MR&0(t?4L-Y==VdBT zy8MnPE&KD*xN4tN!wOStiuwaZ(L6NJ-wdMB}Um#w2Ac9z}uKUdRG@^;s|3b)TTe6Raz{+**$b;2$_! zG#c;?M#IcWe6S-|cAbJjT5yF)O?u=#9L{@2_#u&;U^&Wxsm++7gImDKNm@0$W=@q# z(109uK~sa&X!~Q-2r6l2i#%unrDjHUS~Tt@w@&QWo0z|lM7qr_E=}Szu)<#bpzL|s zyKx=d=tY!N#)H{hmesRxD*R~;7TsIdL?0`WN{Lrv($X|HzTt}$C>8X@c3Y7ypaWmP!R?UA1_c)Ss$$j~{MJSi^*)v`W4>Wx}K-N@~vPN^8Q!#hA;J zan7z~-pUYnxvTWz2c4?a6}sIQjM(?(L`N-HpWgqu|4f}aj`#_wF@JkLEpS-<*r$1Q zqj!nPldB`2o85OR*=#mPcMs=cpuWE}GZ+|sP_p-`+VK|tqqt@`eu_OU?kw}r7sPD6 zFIld(pyq~EhP97Jc{|jQHd>XI2O~F5^+Qlu<6-6R+;i$dXYpydob;Y>Z1S@obUz~J z_wG`@8<6zq6gu9-8fR9Di*bx5PuV4=S1fm{&2VB{OM71`W!_HrqT;!xOlv5*xYL|= zmftwNv(l2P19z!<5eXKW-P4G-&p#HTWFITj9KG@Br`a0_FDzJ^QQMKsISxocdpI$z zB`KPXU5hqr`0N`sM1W=u>EFxN8EXp5NCZn^ycyf)EWdW2XcfwLmS}tmT4FBf+udFC ztSB4d-49;k=GeNYwve0ldY{(#SPv6~Cs9S2rILjTFg; zS!i=4jcO$EvLB1&7|G8K(xwJ`Wk_(T+py`}kq)7x2q58JBA0o~8=q?-4-bP7_^@6g zCPLJ(aYT9gG_)NmhrsVdj)N?H#*q~`)C8nG*a_=&#M6$fyMV-ts=N(TjL2+vH%1bX z25*!^Hq2VU7Wj{ONHtbBNiL_KW*y>aTw@wd-@D8{MEz%1ekc8VWN?VJ8h)7HB)BXPaY(Rk%$x@;#+0oXU)`jS z;O=Gmu-*YS$8d`QSA!e_EgEq+s(v}F*5zN!WK$rO?wwzrH=7-)pyssPP3&oFM$Sk_ zb)#v;rx(rGFh!Ivd&D(myi++mn^aV)fe59)*c@!@52GURO~;c9-9$=ORalCWTQ-M4 zW`*Lab5D*p1;ZY|xr2=6&e#4!?=l$7J7(pr(*Fv=}Z2tKY~Avh3y0;-#wyD zkGKfg;<{nY7u-p?+h=@s$du8YaGnBo?;wuUzGu}pz!1Ys`Mp#=H;(?TxZE(9>_H1S z`9PAr#8E!55AkUpYO`Qom#htjo&F{4UxTH4YrrC~$UjsCSi$W;_gHl0a|UErf%HFi zDvSy7a#Co{o<8B60dNH6iUXB{_*Aj4Pt%le+mkng58aPBe%ZaeeIheradaB@bnC>o zPNQU5Q0X!A$WJt;p)HYYhq_0EK-Vl=71)*^IotTj#&8>DV;W8H{qF(Q+C@O=1Hy?O!+qSMGJD$l@+-0LQvkiavu)d1f+v?8S=A|QkuFE4LA+dgR2482paR#}h3&&a`sM$?Su8ER6pIE{T z3>pd}+b!~0TWpy}!F8vmciaDgoH6l6ildX$7Hmyc{`SdAKii2C!zuCr5GG`Vd|Szq zeKt)w4Qq%+tV{QevfGkck}QR+Bo!84;^0sF%GSb2t9l`;oEHS z2%0<4c0;fRgkqHYL&|g;+4$@%ba*2h2QZ?7#R?b*l>G=ETPVr`Zyn47O0m9pn9RIiXJF9%3R-@U;>@-(M0h`V(xL-N700=-!Qj7JGh>>|EYCTYv;+CE+E zXL{+onYa$0m}LA8F3xtK*YR*uoHT^}Os!D`G$<=ci?fX&kZx%?M_ZA>Lz^}c<{TRr z2UC|!ZE;Wm!B)oNd)`25yM>*L%aM=tf@?!Wv%1H5swTQzhvn`2G*3k_D`3L_2sKp` z7EE9aXnA(_7rJ)%0}(DGJ4e|-ANd3RdI&AQz`C z$^<~TEPR&ifIkFHcx5!;3jqX7XGW;yDMPKzM{|G$VTS~_2~>y$#@+l#L%I)wXJ`N+ z2|3$Ek8kG|EwS8jI~lM>S+TMoPLolnw^A^@IiY{lwTrE4c@>Lk`K)2%Y#$#);o!*u z=!~mQGB9QP`{SZB-E;*o;-O6jzr)7&@5JH`yzeb*ji7V)9iY+DXOC()fxZ=-;DDwn zkwEnueG((z2y&L zjBxQ*R9C|}AnMgJ`VMRK$B!e#TxGL?@B+AL#j^)|x7*7>o&^kKxU?WwXgaFHb$Lj6 zhOVg}T9+y{r8@R=(E9@y65a^x%@9cteE`ETNF=YTV>d>~&~#a6I1C~WqdjEnXhuMO zGs!Cbwax6UU4_@Yf&PH{84b~uHcWrR?N8ZV{>c;_kBD}3GAITVt>q3bk-QKs!$@;c zo~luo9iCXimXP#g8-#<3&bmtC#X0oH!a91av8-1JK|Jkn60Jq`lQ6W!NC0%@V0UrY ziI}E-DemItMq!m+Cw6|->e|nF3+fLU-4?sFx2|XQiU2MXToKTh@q=jrh^rtU`~F=M z@}@Z7NM(Y9q_V0C@Um?Z!B^0YQhNH*g1$5Yvj83p4r+$}8ShrFWEul2*3>j3JG&?Q zxD~BTR4!@lP_8SwK@+QC0Qw^W}M($k|Q1*{^pC z%7RGLmk&`p+5dQ=@l@+4VsEdHUPzp2K3d!jCHGWs8B_YFOw`d_G4vD3-x9HQPo<(H z^PM7(F7kvUAk37UGUJ*!u5qA|oqvhZ{c~*A4Oik!`Xihi;)|Frx;_RA{eG`ZnxJPb z)+pMtnhcTL+H9Id;TO88UH2&r3IA0auv2iyAgO<^=M~Ep|8e<*IUN4OPmcLxaREVh zd86Ys&e`nTw2HCwG0PIkToFHxfM`7wWt^XJkDGWRIi=|!;bz&&e%>v?w``8*%JZ)G ze+WsReL%goKa9^7|F*K0_{~P~MZR(X?WPrVfbGjKSjSgy6n1I)?o65Cf4^;RPrZxU z+toi>iz$n3d@uWx&$)RzGorSCfPPMKfLEqE@9dglmx8+DXwB;`)quYi%XL(u)2o=$ z3-b=77zxee5o`iq^`eY#=31wndrzFju*vnEA675`VdhVxaEo~ zQnP-No1kr|)%OqWnAV+mV5idJy?Lu*kmBDZhhJ#4G-Dt3@yrl~{B$nY?@bn4WNeHI z^}3}6j)O&ZHeE;Ylg0k<2(IssFIHxK!+h%^%&uC-otGQyJyi;rCwlAcqU%{p=%ICN z{pBjkakQ1t6p_f2cYB|I3}Gjubd?4B*RQ>}r)jI-C`6Udoy)G59-RhkUjyU*+H-Z1 z>i26tQ-3Vwzs{42Tlq@Mys4{Mby5&!!)--HUENk`$hw84@x1JrOqGI%aX{&*6Yor+ zY)O3>&0n4q3*UaRzBn}_$^IDnM9y!)7vVT`OjYG0R^&UcW(88ZtZ3Ynb{*+FrgZWQ zzbFy65L@dEOtcOGV_=ly>oZ6c|5_+2i1#{@r$JU;X9f|<&*xCYbyxKp{d~Y%OSXiTv zmfo|oYs+kFIDvYIAnE-|xq`N0-PD$QBRro&0t)Ux4ONf>0bo#WLX$w+a;H*b8~Y$;HOm0hg5pMk7j0OshXdGzvALT=XRJG7yt*r7BWVG$yiif4f>O92k{u97pm4046>fTkQ|ioZaKWMQn}zc z0uWk|G*2YGa~Nv1exm=#K8!FyN@Ac-LC*GR)*aK5C1Bzmr;f;&*<3L%2TN$Qbs)C0VlEe z7cVf*^!k*?Hl9LB{YJ0RRP_h%Cpa#1;I3`tKbx$zh8E*LBmsc1!KYhmgmD1BZ;$Kq ztHQh*4#`Zt{fn~Cpi+lb6evM{{D}FZJoF1XB~D6|I!9=_T(Ra!0=fL3oKm|cB}|{L zTR%ztdiw?w5dm4~q!u!={6B`A7c+D{BOoyitmSW-NQfr7!p>n(;N5gL6whyR%&W;F6rrMn9NCF0 zoyK3+_90r0f%IOCVdB*($_{^pbfuW?j)f^iE41g~otDi%Nm1}HfyxOQT>7`LsL{^%d#neq_T*T8_xb$Y=q#u2g2xu~$85>UEBbxI3i` zbaW6#BVj0NG|ZIfLrza-Kot_j1c@P!3}Dw1%&DKQmGucCq^z;JpMiQH$k($D zj>mA9WfGiry&i_?Ia*2q;)74lPu&{q^d!?HBv;(&SYEdbNO1D!oxK>@z&V2H>H1w2 zGYW~i!OcUchkt6_>4H$3kg12x-^ss=j;G#=(BulUbj_h|N(Qi9dY@Q2$!44PUFS-Q zZ!mHxIg;{rT`++C-jfoLQ0KBc{LauQeEH=OERt7h61Zc{-*qn7LC5fEA@YDls~eXA zc>i!|X>wr)RnParK<1G!c-=t}umH|Ps;^a`Y>m~-lZts#+Y11m2#`xk7sK?E-5mfA_1X<7SR*edtB+kFA*TdBmmL=mB(`*z^@ z5Hs0M8m5$b!3I?a+O)g|f>H=T^b6aLZ&{G0$FB#!rOU;t3wUomv;MrhRLOaV1#UaA z>#uVby@acMeLWcQeZ?In$Kc{mNa^}b-~r-%xR1h}XN}F;19EHN2T1<324yV3)wbZr z3$p~35=3M=lj5d`&=QNK!$k+G0JRk z%4)PR{2FU(6EzA6BSPP>yu%j~bq^e;Ybv+R{8Gf5tg+tN^6_JAR!m-K2l(2hnX(6H z5ID!|4l&f<8O=W0*xEL3ajd_Er5VgHHdL&%W4j|$1i+v!oCFPo{LC5_e5;pj zkQi!_A}7Q*K84Mn?T%a7;`3cOU~j=5=Go~+aanKAJdCK&n*Kc~_CdTiG=Mpi^ywLh zyiv*!?nT@qh0kUFnB;-_1Px&Xe12Jik-`LYxgPohQutfdY`ph4iQ_!&M^6Y+sk)pEFU?bIQ4NK&Kif=W&2>Teva#bNCism)!xHYu-P zqjM8XK0JXfILpKMO&K^;nc6@I{O|+j%)l>zAz0865QP@Vg63cId|o>AOFR$}5m`R; zJCfRcN~^-6@^ikgc<0DCvZKz2W5eRM6zUn?`$IS|kz~vc<~#c*aP$8Bkmm|$A%Rq? zD?lx3GO44fi7o==JY-Br_L9Mv1cx@}o^m$49rYVvpoiu;=H~vmB;27}CL06GVD~=c zBNYyobzk3;h0e&u-6R94n>V41Y0G^(wFO->BxWoxa}XnLNI-0J=K|<#UZs_P;Tp{y zx5uSq4LpGh7Geir>{LPn!651!kghW|_$yJh9=nTZvXis+_D~!DLRoMC}B#v z{wv~U45O6@edYblKY!duO27TmIi5Vm8LHcWp8wsy1_aQSAy5|byQ>}=8IcoppgIVp z9Slsu5fSm7FBlPSca$`OV!;`MKg<|`4M9S-uwDn@uA5sTo9ICf46Gq41N>R<<{&t? z6bZN#iXD|L^6Y^RaQ5H6eJiIh0^bYYaklFR7t)3qoLZ}%EF2oan~hA?T+5`jFUH=| z{?gm$CD45uPh)2=Up+j-ou+-+k{_XNuVn^G%D2kW5S0+3>YP7@NGF2+hWFvGaDtS&y zaAcijZanBhZC(i}yR~8Ai@4s3z(l5pas>*?Jvje(PrhK-=_NAukQ7<6++jt|iuE1=B;aK+$Ddt6VHB*tQp}Wz7AA=o|`&%y7 z2G-#ggD-~i9gzTq{LJhp4VaAl> z)-R*Qt{|Iw3R#%t|2akZVV2dc5>jC?)I>WVvZi%rJgYnQq8=f&@H%ubwce}xA-sRx zw?488KVRQA4zD3oF3t~(D|0AM^PONgzEignHHrN7c6T@2g4bUo^21YPLG*S!D^C~G zM~)_o>_dqKw#oYG6x(-yq;RDC2ZlAYMt>u;68vkrYff+#SI4HL>h<#M-Bpr_Wl8U< z$}KqJw(`F7k!7Exjuoyq)9^!mIO(O%bnDH>vmC#EP z1YUEMs#4bj?r4gIH!G>i89Xv?+|wOfbRD9i_`QFAu=>H!a8A`1?+w= zSjn{9NWeClATL?;AH(P0vbm6GCHbpfzhdkp%C2hU-uzoh{a&2wkFF`naAap!HMQ4R zi+)ZakrTcj;YmW7CrQ4ZTXK_%$5>A1Izomm!6vx)Irr;HLI$ltSE#TCi5+sRDj^vmm$VpRB%bsIsg-xnp_6Haul#Sel!dX9o~Pj& z`|o3tmbm&rmtArM?8J9<=K5Ip7eQxDT#e7CCAy~{q<4m-W@Q6P;#^(oxK8`l5i z1Gdl$htDS0Sg9K&d1z{k!5w#U@Mj=WQAm$X6ym^mV-I&!6A9b`6tt!O0nCm z+Z)GA0hXl%PSnj5kr8(_g0lc5y@m687~h=CIqPHA1+;~vQ5lkL>~YF$X(raLhdDYq4?L_|5 z_-&$dDant5I;de07W`u{!@YhlA%}#O*!?i?LX>bc)n|1uKp;0 zH0s1Y5Y8#!wc*22$3Uzb#?v;s(Ds~fE1{>Pm85N|u$$7VID`Xf75)6ZkLOcYVG zQX8hS*)ESB0j$W*d~RYw&4N=k|0Z=zEHv}gQ5?(?t?d@zVujzasNd8aVX+40{WfH@ zk0ymUA$n0Db&V#?8zBAseaY2A6-LlNAQkC)P1Ofy{IgXpjF$gMU5_Iq(G_PfhH5WX z6|gNBdY>1Z0KoKP8;?g!;Q2?7(Hzw>Aj|0T%=fulBdaHXM1pOu-8WgbE!0D550>X^ z*Ott5e5-U}oio@%|1zlU+xs8e@Kk|%1eqi+O}a{BasTfqVfdXCbqQn!#(17?POkgCDiA&k&n{nmWr?B9itG&tYM zAJ$Ac0jDWd6Sgkpb`CHUm${?fE$tp2sD!#r+Fx> zBnTzJE};!Hsh%b6;(Jv09`0WpgGLR2=0JeS^|+>@>q=tT+9oozc?c67v_3J!rz-6s z1lmL4wYHBr{m!7P8{+~tkf0ND2f0~klVT6l4q*Dm7IuNvHke^S=_N$=<#YEZAu9`$ zt&N4J5)1%k0;wcmSdmXOGjR|7WDZuq-9L8Ukxdjuy$pL74GzlDvO8TGaOhjiU8uAw zASD$zNZ<*96|s*y3tgz)0m3NnqltX^7<8~usY5_pg^u)F;jPnC|(QA8r5DI zBq_r`msKf9FX!AB{T$|4aeos9j&%rBPEq;7n`Xyy_~P{y(eg3EifO>AqOMY1%I>p$ z@!j)|)@2^t67e`vNlxsx0vs5wk?}TAvZ&q5m|5nhW)tUjxIe_MZgo9?ti}I$_3jlG zeP+?PM`uusF-yHG8c}?#r4~2n!ZYMndCRU?SWt^hcFmh_NB2ZUrsAP=)U_1FhO+6$ znLWYJkpqdkw(XY&)yvP_GqJ}_y^_70e{$RO;D3=b9Qisnbl*g(zs~1Dlij;68s&LZ zlyi>pk1^`v=}#mShCjYE6i@wn;_rVI^&g>{eJBR`j5(}wTgBY#ceKMJ>H0pPv zy>=onYJuw;r3}J#N5hf*ufJhp_t(c~hr^xxjoLjchrkCsS6~5dKljp`zBpb`T`4@t zgy#y*bM8CrThs;2?6)=hd{=t|S~{^$9kD##EL~6fs`ztov?>YzE_x76HE^#Q$p_@( zvp$zVC~EV3-2h_s;nTluz(4#a!21ijNi??E;;7^9^Y@g{=7KvzHk-_65QL2|%Yr=_ zh0z}vQ_>Q%HDlh;dLr`9l^>vh(t+o+34@@PG*!S$L8<1$yaU$#Fk%&LDk;Gu+${<^ zEPrU$=pJ#G-I(g5xg10!f-}9KDum{pgE?05YI$|*>3kR&Amn^yaX+SkaeZ&G8{{gx zU8fU3Byt7-T$AVK{{V2(Q{hj*P!3RqfIrK3KvLV6&ZtycdKNv`% zp@oiosOa5L;(w^*&b!ifm`yO(_;mj56LRwks9qtnT~b=QVq^nM$bn~YuwYCAIXE8lA+>pqZ3A zUm%0a#s$=$H-tU_Spv|#$Hx-G&h#4DuplH4xjfLhERZ5-Cjwb-HYX*)=z0Bn7LJ5;08_~_)Y3eJn0P5S*DlJQ1xuU0D};q>!Enx5j@F3-0j!^ zOMvvb^HpZRTKhx`Jlxjg@_=j_7y}BYLNhD-?_dN>iOYavcWC~q&fyLhkfFJ6_tGAe z4XP7s=qqdjT6}nJy-eSFWe>;W1tSa*JsM}wu0r6hLpQ3MglT?)-bKUHaO2R=!Enl0 zk*SoPxO^`O*tvZ+pg)m4UC;NPdzM0Mr+5>dY`{^m5NZKB3IJO4O9if9{|F99n4hia z1{G9+fB}B?kcm?EfV*&X+=Pz~yZgV8m>6N9MDrcKbwJO6&CUINrx=MF7eFR}am~eA z(8b#6;U;{W;CemaYe@rKd>HdN#_w3T>nR_;qz`B3G~4$x573z6$xt9y>dG!jy4_Ek z!IW2wbCp2Pe&2jDh{b1e6tA0MBRVJ6Q37vli>bpC>6YL-Lo`#lvLTtFx5((G#un+^ zMcC<5`n7;wJkmO0&HT@VmP;LyPjAA#KNqJ-Di&WT{neAN%w-+t6WO=f5if(qn4T!k zCETW&HOpZn@Ntyoo0ZD1-_i5Mr+eRa#}4 zXr3XoGukn2)o;v3<0e5Yv-p6{i zwqIY_Cs5*jHNsuRn!M?`tx%bejkJYkyGlPNE*=Ty1Gn}m<$4L3sNxb=dC?~>d&W28 zI(t}o7NQ(|KbLZu20C-IPVh$9d>&vAZIFJ!M=Us;R5GCIu_4rvFVC-2ou#AkB4kb@ zQ4Y>V_QUB4=}syvx7P96zjr&BCOQ(X)~-( zMpmgkN7Bp_skP}Kg@;=IP==K9qVgGY-+?-58lMPW$90Yklje9GsxCUIfe4BR^$GgO zLfrv24@1dIrs@|jO??=pQt8uN-41?9cCsDQnv-({c8FG<65P)zvT3_J#2b(5oEm&A zLfLt#pBeYnw!o~?XrUxOBDdB}Gt)&hV`Ptk@0;zpAZdDE5iTr0>%=V7MBdDdpe9uGDz4Wi5>St|#SNc~P128Nr@!#>eyBCF}kJ+sD(Wl@uv`Oe2P-GrSfa|&;I z8?I@(I+(MIy+GKC6;l#q%C}IIWiS1`>5VKgD_3qPN3qt2H(;kS-x->FV<`T|Jh`K( zuvxF`!_Zz7gZRSOm5$P^&i?LPd5(E5cEcs4&Tz;GdZ*qsl^X1dMD?i4OZ&cP!1i5} zbX*Nk&baZG_Y0;Aozz?xOin&P~KQYqEmrranqNc_C=U<$m?Vy|JKtb*K$*ju+_m)w= zQo5VmsEKRH^y3jW3b(K(ME-EUWNmaws-~rx8 zG?C6uhe8F9(D1ik8vga%TviX7uD?@oN+-1Gz(?}&_Jsp!=MPq?uZU-)Q7tC0dpXHACR zH7Kloe$VF=e$LLYL*%J8pPM}cqnR{KW8xJoCwP4B?DQzYiUPR!Af5yEgb(8lp|30M zFrE$y3KCAm@fo!2l@>|W1eG-S=)iPI6rl+IK9VpVeoz`3RgR-$G`u{4IuRdukiWO* zmPM+eQj1c~gYb0J9WbOSv$?^i%O3);Mc0v>We_(d3P231TDF_r|LuDCPhg77 z3ojaU{2{`@f{_5oDBuI`BIM9Je$uEHVGzyxG=8K7GL-W%N$9x&bTHQpnaW*Eq)aPP zuQfOUwV%M9KSYaC1`x~SBrTrNI`XC{i zkVS*|n(5omKb5}&W%nN}fC5fdQW}>iro|g7A<#@JvqNT5k!{5qh|3TIM^IAWcW)uG z&&ET7L7t%W-o-+-(Q9-HOc#nuXwOaQdt65Ldaxc}GQ5GK)}xOQDzT3tgrDoxXbSn} zG0gEjAI*0l>m%oVc6ctVTLzbkP*yM7+uH$Hgq@0kA1r`l4%S{3q8jLaL8uI=^$UI9 zs%n+K+uTxhpHGc60;?|2W}uaKaoHgYI#zl+eX|Ds)z8t*!^J}8+g?tPi^3G2l>Pjy ziS=G-kEPNQWC%bLMjeC3MQ=&_#kbuDRp~l~-jd9pmiwnOLqim#kwa9m@vkd3b{sUeG;+zDQ7AI<)ar_h8YhPD%FDkQ|LKj_4N z;Jj!j-NoF4Vm}dW5bMb~)m7pVK2;G(`R!CU6>OmGE+l6x$)SQgVKo>B*iLoG{$ zj*FYZP;IaCQhEC{Uzue!Et6g)=H=-j?TlKI=-!8^ci92e8$8r7V=%f^>QrxSA^rFj z3x|&}%S~mzNW%yQ11nU9RHE9xKy5bte&K+FEKzpgw&&oBd(lE=y>xn!Lmhr(o;T9I zr*V5Mrup%)oM=fji4Qn(lQTqfluqsD2`F@hm0ykA%Z@tubc%9uZ#@dXmSh?#ZE4_< zn5~C=mtnWjySRpGb)XbrJ^gd#YGT0XOykm6_5n$}0^##}v95Hv4SS@01W5XUt9D}!jKNU+Y7{~kqg#QmM%IuW z)Z4Pz(4)YR6Ep|f#o3VLR8ynt41P#w5a?LQz-knFQe_Y4MYqwU-laVt_JOvE0t|4l zJ9B#Sq-ez6oCE(r8^}V3evnMhB~fpql2SGwV~p6Nlbf2Hgnn%nsC>E%lYuc(_*&rGcG^<$ocU$UvGsir3QH!`mkOZyORuP{7S%zv1mo`sa# zk=mO+a*7(2bm*s7Lf?(Dr2k`6dkh0M#R_?@9cS9mEJq z2*Ry5YtLh%0~k%q%a7`RzqnrxWJGT27}VT&BNuuOP8pOqNrGN10`COG-etbeK+}c( zPHEhcSpI7GuVBNn9LWb5Y_PyVg#9cCAik))jFZZ&Dlgyc6QAn}g@X}zFrfQ?u>Ecu?yw)nzd07wojzzAQ4P&AlU!8B`bE@<_8D%dC?E)p75 zh?P2qY^iaEPuj(2e?NSJ2LrG%3-ud=E-%o~p94Hf(h`kA%9!q1pCyu~yS+AbcFwmX zB^w(W)PHW2+_^SvqMaoTZPgI1rQrG4|FX?N&5WKbq@_(yO)WI;xHf`G2E2P9E7qIfQaSgQ~JeP)79f%knjs7K&I^U(Z3zaXkh;c({P6^JGOZ=*z9% zlBM6Z);%yE*g86cEKk4{K}QEh(@kjGA$6goKsx^_3JEd}G%M~(aFqB`_ZeKQd$)I5 z&>4_@n^dSS^aH|GLbHaF#QxTQXW^r|emiIksvdrlm656Rea4m42w6ErF+mSMH-9K# zI|ult6Kq4F{RCnBV%04yf*(^;zL01o^w0DL1O*&{20?FIAtZ^4G;$zAGHlXFEIl(j zI|%H`Qsr*v+?m>IhkmzGUjQ)yoaRu%_Dn!S8UPFKGQd*arqY2q1K{v=*wd z-dDmS4s`4#MxdU6A22t6f^as_Cr4j@`|%_2P_1f>;7XaTY-zXXeIyK*9%SB(fqvi) z9rqFoEisHyq49?IShK+`&4b{y2l+Jgr!Xifcr;j`$#st}St{s4y3xS*X9opnx8dpl zIRgemB4LT0FBp$DaKL>GKVUkAWP9C13P~sS#HhI)xaG$yvvm|9QLT|uq2n$ep8Qn2 zeLZ>l#}7f%1{T81%{6~GppqA*y@(-*<@a~Ex1dR!+!M8{%6J{=E zrluSb6vlin-j9tL6qGDTfNOPNU|>mTzXrcW3Y0v%3uv5z*Z-v4#t$A);NpS68=^Kz zeiH;)fbL=1U*UryQY25Bg7Kkmv8f+$ugR?O(&3P~%5{9x2(-}TyFv)B+F7;YI5w?r z1WwuiB4FUV3hR83zDO_QnZE}x*_TJb-vj4ONHb_4Hg)O&{6!-pmY8fsN0@~rYp@cX zy4pA}2!J^lZa>M%S6lT?6yasp+$A$|pjH8?I7C>qv}Al+QC_2acN_HfulmdfRtsXm z!2wrQS*Bp|2(((=&hZSn52+|O@F1Bn>C0}1fHuhb^tTplN*T6eGo?)G zBx9OgmKE(s!3%9ks9!-SHNi7os>b=fP;XC<{fxG1EdlZZF7K%2PI`I1(H-68dUqji zSIu7zTsa%Zw4;xvr*{V#o`eM(muV$XN;KNWhZRI+x4qR1vH2V7!Vhn>||K z)Ob)pq9;J@dr`~MQOd@3b|^c5m6SlrRQ-e_Il-G^_`r`|G73y4ODS3;glrb`wT%k6&E< zw2EQ*=P2YDl~=E?r4+z|PxD1F(XaoEOTRSfs|;^a=-|HyvinIsw{Whunu>U}9dVqf zGn5&>=aE<{awTePv@kOLSyY&x`~Fw1Rub)X@zRKTwd?f8Ofvuc66`oa$=G^(?iu@*T%aw1~uDn*lKxwtmNJKB2kGQYGwWAOA*O@XXI3 zhsAEug6By(`d9Yd@wB>$k!}2^oNo3C-|pJaVckpLzt7?`lFs&Fba2rl$FC?R;Wb_@qn*S0T0AXr;`Sr?-}t>F z(^%4cgmQsY_J9ZC@?U_|GYpM|cEuZd_rFzbw(SWcwSG)sz^9;D!ZP zKo5X)Syx)l7-B7Vj(|cF(~V^bt}WOXaVZnMdK@Z<{}p861;3=;;_k8168~ZICT1r` zz~VA0d;lGz8u8ysaUoOv#J_Y;O2l#j*(3Ofpr#DkI0=STi(Fc<=MWO!XR}}DBq^GX z8ZGq`8fY+!%Ld3B;D3&7hG=nSw{&@MB+~tUed$`A@hlq47>?vt97y^+R+%@k38^Cx zp|{!)&kVTVLqEO0DcZ%7ih$tgAjN#`m-z?v3EnzHDM?-s6$3gDPmpcU zgqu;HB5IUunSUq!oIN5Xh~00T0WtIhG)a)Ri8mUd0g7~#a_HSCsR4(7o#dqAY<+JY z)#W~G*nY=Ce&_*n#7&Ct-0_^eOZ!F>j8hXNtcf+fOg zJ9rb8_g1x&KpdZcfWZM3hgZJOkTne8S=rKf6kifg)~`8C`ZvQR2^YHsJ#E5vi-8s{&As@Y)zIgumXmsgz4@G4A_(df$v*2NUA15Z?P2M%FDR zymW^p5|ZQ|36B40D06<3Mpk&$3|-tvFbiz8Vyu?a=M&?|h!Dku!r_Q4oD ziu)%{8s6UKA@DCm~;`A6)qi=ObxPVcq_n6g}*13 zobt6!vqw{=LiMI4WflG`xw4Oh0LY~Ry;B9t4h6p^gfW+S1guwzdA;AylO}^RG zVPb;IPQ-RGDz_}J31vb@jF<8a+*uJ#P86XXTcENRp5qJK z>t;&1hhwAc7qePiBf$NAthVn;Twrf#NOPL9P?0I&mIosoz;P1C{3auYQk0lXZTms_ z1qwztzh@d9$kknOMV%d(RVmcVzk9Sd-g4Mc=NJ!bAIknaM}0*fJ{VNd$W5B4qLi*3 z<+YI4#H#mt?WIc7YVbr{s&3qNsRL~@SoZXMZm?~Ga9lmT%X@LJVPGu`vHV+CViweo z^ka*WuqKCWm8fOxPMyJiJfAnJ`4o2FMt#&XXVC=;#7yx5puw)UV!)aN4zYlW&B)DklWAPftgVJr40QS4~8`;DlI=f58E z9TBGZdrPcO=3nkYXyLnydQmgu8wP9TULQ|MvPWh2sjAGQuIDn{?5$B|!({Oh&g7K^ zm1fN)!5Bi`t{3;J{|^hHoLIxm#q7vsih}i>o$7I>HmllQ^FGsirIGMt2)9s+eC&Q> ztV;F?fgU2^ZF)qo9hgoo=~itIb);!>$p%JIXN|Z2U~1Q2zJuQ;P&hW9My{{9fa>T5-R#?tNRB;0 z<3=_nvK!b$@e|R5-Dw{`(EIH`Ife0Ef(7IAi_iT5-40a0I_R9Q+Yp zrnmjxNapSGFWdU3=05u34-8fF6GI>Q_gD&PkzUB}FD6hR))&(O)LlrVa&b(yM;Bt? zgW#d=e71q$Lw#k|TGXeb2VB9`PV#W?zK@I~B^rm7vw;@>N1yp5r{pZ?fd1 z;3Ea^|9ouoUuO&$)B3mC`$StiT8FfmnHn2|lbrtwAyk0N%u{Ue++vCspB@8N zI{YG%ufDM{6>_*8uLaZ?SAu<=fJIjw+BxVxdF0d!tN{LQCqj?F!MJq{ttLQy`6OYH z#DQ_(C-d^*phw0LsX-mp*g(CO`Rw09(;Gk$hTZh=86jp-o&9_-k*T47y~~*;|>Wy4xGW3 zx#V3-ZXK#%CG`UtNxd52bKnJ;Ju~lx@s~q%nX@x^EU_YAD z+|Ur#w=<@aH-X;!SIM)?do`b^mz+O$XAh@k&%!8?KIPxI2`q3(;`$c|2KUzJX=S?z z40e?s{yzTyLLeaBp~c1H-!~t1m3g$Td2mo6elhrtjQwHTWQ?P+Pae+*bXKr5{t203 zqekEtA6Ogef=ncc=7NMURTbE_g3nn{K;Zkp0QSc;K}E>oI0=B~p{om^ACP>WOP^z5 zB#>DC{2?SwEGxGy`a+)!9%F)il)HkP6O3x%i~UD&tmDK1RhnbQ&nAJ~fKdhnl*6W3 z!j`rD#ms9ka$$G@SO|RI=;2Idr2-umu|N?WY2<J5N#?zO*Cr_LrO-*!;=6ZBH+lvz7)eC11>CIPMkYziJ7Ijt%DJKBl@a1w_Y8;do z@F)vmIs{Gm)xlkZKYkx=>Hu~ro+9B3XOn`_7}BiPrw-7&_*-t120UZ{A%Vb$=HH$Q zKs>UvT!3C3-fwHrrR*gB;d_;@{BN(3&+}d(1S22{2 zBk&Y{2W?ny&{Aa^fweVY0>M;p-NWR^%}AnYh>(MeAqXZ#>&kpi%9+}*mH&2aR~ep6hwz_L%iOO1>)sGgy_xmK4^MriJ@>>KRg9V0AP$^ zJ^z883^o!VK|#LN|3vf@w(l_A05-xg*a?*Bh_4`uN1&8L^B&1eZV-W=5-wVh0m#CH z>*U0D_={^R#Q9PNS}4=5)|UvT7tWXiBF zcL0?PM6ZM01*N_i$aUelLQhLO7>=4uMyFqk%9CG%q60J_0g#?3DNlfV@6hibJogJr zMs;9j0iz@IQ8`~?hT~i&>*wR+1ECU3@ts44APTUxl>=JqU@$>vO?vMz(G(t!fsWpzk%1aKKp^Nt`%hrlSG1bP<^xiRb09qohXOi!EsRW7 zH-+@mudS`~*`rqc5McNL*UM++YcjkR92!(i{*Ya+F8E7vpLI3*yi#m(i~fZV-y(xCz%gxUFwWh|$K%bJ4T z1udy4*E)Fy;it6301yML+%v)5?I>WC!c7PS72p?|E8n(-d7lV9BP^nrnddys9fnuo z|JFN#r9Fr?sRtQ z7x*eiX7@IQMkb|Pry9w9HuqGDcpzfo4)tIf?-a46v_^LBEAl7&2qSI6ME8t`r!=V) zEf*gEwr*+G_lEjGxT`uFc8o1Mku!GqB(Y9Z;dtwT5|d$m7iLJ`^IC#iG2BBXlS+9> zcq=aA?alV(C7ylKVHDm~+}AT~iCLSzBwqLZ%iFrrSUkcrQAepJr(eww^0L^?TGPh^vy& z7vb35Q5OZ}j+3C$=%})uU)+S3y9dk{68|nK4|Q?|L?|&Wbnvmi2>yC;JYsowNSU57 z$T?5JA@p5o7H1b0@7n8f`loav7;n@R{pVALFy?JI$Y>w8y7u?FD7vQ%YcxtMYclX! z_uYI#jG{IjTk#!;JiR;NOCujBPS4T9+#^2rHCCRXBKq~a&xg`lhE(}+7yMEoTNFd{ zzWi(UQgDs8r z_3q~UR!y%Yy^hP2+U7jDLbh2D3#1XMGJKkeZ(d8k)abO*(NtO zFsJ0cxH|BY9mIMIUGYLGQHP_~5izkb25yBs=~JGe4zr#l<$Bli9n{HgLzw+2Ex~<7 zeA=^gebw}~n=A~9!%>ZqNlr8x{uH)h+STfZ_ss(M2wp9}KFd*l72|K$tyKHmRjy1T zJ2V1izLtTH8pW`rD|f9E(f?_s zYL!d3_OB9-d!;S2y!qc$3A-qp{bAdl=Z5}I46dQL@C{FjBvQ3}JV&wRv%f@@?q`kR z6`2PkDWbo+jwF4+TP}`ju2klUB0|oVn+ai!)sC#UGzhV$_FapPHYH}NS`w#_zKakG zc}d9jMyuYINh5a*hlFi*6qS;=d$q`NUP0@KXnEsqo*@3E?zYNcGgVCIt*ql2No68b zknC-(VI27quK)?s;z7BtB@x!hwBHi7}A%FR8ePqY-O8rd5=n#rL-NtDJOC zgTPkpDuM7I6)J|#K4HcbmzO#QWaQVQ0rm1H9@1FmcJNt969^mdv~&x7zO`j4=8+Ge z+MW&uKc z0Yvo#-Xyuy@gSa;RAz&Il0;WwFd_TmSvbFu9T>n#JVTJSu%z>9F8w($OYt1c?HGS< zR0IKA>F5ktX4K(oq1`Q`m87l9dp_rZLA6E&oSZ+wOAj%b!5FSxzOA}5G+Int0pMd4 zu}Mbs=jDd;<^k5HDlhO3@3&5{AY<9y7z|D8|3hd*QTp9S|<2?DSck!%eAH5 zd};QOkk14_(b1_AziCLw77H4A;rW2Ui+P&4FA;17Nm8(J#*2pO6&m;Z=DULV9LZDK zCUi$|i}%7j7$EIW%(>f4Y|8r?0+fsycZ!Pc%ura>8-tz!YJ8c{uSuBCL&d_|VwHc2 zq*0VrNn1Qnz=4n@c;dxiim66izT;#|L`*MP9%g8Bo3kPDjj3v`yHc-{2g`Ssw=>OO z6DCIldt$lN4W*DGL|$EUFTmY>_-T$zQ&ao{n3_;7}_yXK2`{4E;tx+5~GfszZ zZFENYGoQuQM0Kpyi-XrVN#T};{+JO#I^jJMfkh2E|I+b3!R*AgKSROaJP?0T7iLOY zA|&!^$)J`H>6(8_cwY~UfU5b>dr-&VCSySHfTl#W%QWUi@6*Ni0JNH;FnfjSd zyXQ~?fK*;ykvtgpXxnv-ukw*|vZ4TeDw;4<{psQqYMI7c(IVGGI64wlgIYo00MxCj zii(o^{xGTRwt}VeV*8U1M}PkYuHBw{U}n~x%&R&KXW=e%C$^*Ybz^|-G41x8yHP8CscxvQ}2bFPIkv)b<5WfzhBxjm2y4URe@ zKzmp|&yueSqpl`<5CPH;%%gBhVI3;NgLsZ83I-z#ce08%i6l9f%v19iFj61>UWAqQ zeVh;y0rlhF6V>?+->Il$R}48GK5v)iAS&o3_(dBsLu2uojj(_^O^*B@0|5jN>moN3 zAG)KG5-4MI2m&-rGGS>=jp9^9cnVjI>3%H;g`O=q+AygHvPrhSw^g7EyRG@ehM$&A z%^zqi9Klxer99ed%+g=#jAOi&Ek~HFa!<&}38wkbfY@NwZzOx6=re16A^vJnz>N$I zVf@SMo12h>Qs|9h5il*M&+87j56&kzqF{p^6MlNpuGSKK6`l5&E3mwVT^SHizygJI z;)M14cEFZKO#3$Yr9nE2NF%n*Ucr;mA9&L{=aB`!nB{0FBhLG`Is3ZJ5omm1FajkY zm(v2uVxw9roU`ncWyiXbT%;!2+Ek=63nRO$z(!<_^ufqH*jtA)Uluhn!2}UsI*P8*y{a*M4(n_0k1TV1M8oL~s_cYsBCYtq@@ zRO>3k?(-OxI59vNPbs-(hvD>Fk1ftQg!isMHPngM;FJ|EObLB8S&mld;h-GQ4rmJ1G%(7 zS8UjDE#xW|=ewH2J%X;+2v=5%d1XlBNjEn|uS6>Z&s$_& zuBQ8Au&a~f#>bRU@|Llh<&@Wb6|35Qc*-B`V%Ekysbg-s%Mtj^~hwFJ|#i_=aAo-pwwOJ!qH_+7p zdIsV_R~OO#El@G7W_J!@O9e`cez9)x5Nzc$v$G2)X)kWEL2yo99!w$MUBH8KE(cm$ zPmPa9xX0ugY50Q`OoBO@zV^RP@V{W`ca9qcjeKT?>TClx&d}Eb8^Wj!ylq(5!1Dv} zdc+ypA)!m@KO4Ls%=9seJ^L`T`uqwJ)4oiXqF)T!As;{g0EWT;=ZFis3OtaEvZ}Bx z{$GhYS1uD*#Pcr>Dc!?xOooOcOYRCjgI8twpX~=!r)C-s5~a{qWja#O*CZ4jmwo$i zI3;i=g@j}a-R(evdlI}1z|H{w#JzW5LIIZr9OY%3K1nC=$N4o4AO{w<+gwbnIkEX= zN91MMge`Uu(JwA45+g5ZZpK}-76I`@?nh~-;AH9LacyUA(b^~&(1W+Z+#C=g(6YhM zr@0Eix6S2ccvC?iS_}m8q!Hs0Mrq=ROxW7O5gHo0>}4_1Qme*x(JesmSw?22ZlRGQ zuCtcmRR!#WVe)bi0Jfkbz<-0cr8f{-4#8~z0hdbMkyes@Z33f8PTZTV?qJ1O35R)^ zz5sPe%mD}oX9tOAGSbq;fgnP(&75w`V2fcZ50}&j;QB$eI{tK8x$R-_DL9ClgActx zy8+hK5ns3uR5_jZ0)WWEXh)vR2vwM;M^$G>?q_@MnMh2=D*G?}1a||@7kx|all9v> zxo2S;*1GPuM;KbVeWyC~*d}BXIJA$GXr;NTv&te#=@427u4&Ar6mAqfG7P~-UK!PK z;UjrIo>4RkQ&KXCs@LW~J}si@M^!KI#AkQ-zv=K#8=YP{_~tuk)!eF@`b~am{nPjZ z#eK`O22e6Yzj!OpKuQNDM~-Un@s*wl-ueD-E;R=Y4a7>rVEBXv8jN7`TPPxfy9X4t z3SN(OGnn~Y2b}zzz@L{|XKVzYZO*iw0h-YYY^rX95Dsw_j9uLhc-%P;f=Y8^qoC2C z!I$mr?eiyZSjxHleGz<2%Ew#npdp2x?3z`UJ^co;8*w20gY_sqJsrNp0Br$c%r;bK zSXo%i6)4iB-Tmhf=#U_O89Nl7FIwQ`EPa%b@C7Ujc!$z6GtVtw0#7_Zp#c8m8F+JJ z1LI0TNP}e*Sj-?>3m}Z|nl@He$ICAhK^IE?m!{bR+AOp6&-*+n7hyyNmL2E|NiVxW z^2_i8IyWN&oKj%%L3|!0df}LEr4DsK{cs(%K!OFD1m-^_JuGFxsMy}S#|K*kuLd8v z4cl;f<}zn5_sejB!v9fa)o>oI0Yi@6)%@8!sN{n(T9`xk+&tN+n+b(<(qwQ1=PIn< zU^=>Re7FOHPvj_}I6?jnG^$lqR!-M@E&yRTP1IU#+V9C2lq5di1yv5$+dFXbc%sfd z3{XHQD@{#dh9-|WHBi$=Rudfvcja2|;xq8*+L(sD^irKlh zaK<54^<#=_!w#4;p`oF!u6lofCr5~ffs|Z~&ne{re0l%@8hCQ#Td|OZwk{t5xCJP4 z2eTg|&sDybkiT$xXw5X1(`?0F=ohb;-h9w^iFFYs$sozX8-cqU{H*s>?FCsB?pqcX zT5M+VORnqB9-zucpgjlf^+2^*8x`?Hf4uw`jB@jf0%qNr+Z!o;OG3{`Ei9N@*j)V} z6D%bKvlKWApt}tVYe7&M%t-&F!#Fbtz7NLd=L@Dzfw2*GvYWe(OV9*ppNL zJbXiu-?;Aez=xEZ19MSdDgp(r{h$#uG*p+I z|3qo6Ycyt*Wb^z)h8U#BOd<1ROD!@3uZ@()9LFdOGToos4k#NDL@Cb6P*>$?M-q|a z&7w`7P?yFheHArXY)99l)nTU5^qh_MncA+oD~H6;X)cKT`Qq`#x7=BA`iq zo?UdaM7o6;oBCC;5jpKufFd5nvozEbQZp)YZM*-n-mt$Ls%Wl!2w=h7Gw>))I$lbB~&~-!rI) zNcb)Fk8(5H%c8oH^m}XMtEWNhqgRuAvJ{LD9*;7TJT>4jyjdSB=OiOx!i;~5KF!CZ zVDYZ|elR*nQ)9V4owgk>QFPeRX{pg(q4Y9dG~1w#XJN+&5!8hCcD}dLqTjjWWc4;G zgf8D<{6i1zzs7aiZDV}MJexKr1H3T{GCG_)HBaGNbsUX`Le7EzyxjQXDsKPxu{NFJ z@oo!Jaycqg#R!!W%JHvL8qb3*S;zDu#_Ib|ukzW2g>gt)n-sDJZDIIwBkryk;4_Tn zC8n<3_!hsSp3`_s%aG~iY#0&(e!e0drbgGucc!=ocV%rjW>-$O@*k`$*}faRq`)1e z`04O1CXdH$$oMuX*0({^q{47n^pAW(8Gf`nwZ4gAcu_i z>u77v7XgITeyp!$v_ICZKfFQ^Y;u9I+2BA=GfZnB|0bSVJFTB(CE~ru=#Jr5zl}ij zi;H%F#=XV%B9ypWAD`)O++IhYFomda5}_S6Z=aCj5x){j4osH59}rZ6*><)T(eIL3 z4A9n%3A|iu@&tEM!^9Ba!$s3N|F!mhRqd}w4~~w-Ok{7gi}YN%*)dK++|K2Q$$IJW zF8@g6mjVab36`SRs=%y#xIj?@#X3rn7;5!`dgQA~rD^%d=Pn`Az#U8^PZXl+@x-xB zy#`@P-_mi{+OARVh|c?F<%(>j0{3ItW)>^XerA!V#$Xwjhqp?X@q?dREtxijd`32A zJ5PM_DB*n+WbY6?E70@YiEG{bn}J_+_pEy7UAe0QJciYhR{C*vm>Z1tB7`Jmvo#TS zEbpt&5~n-sc`ZN9d|Q%eE{#)+YKo27C6nqqqidu#%hd2%8tZ@e#cM?*!wS`;+{7e|en{Y}y@)Wc*>bISC3Cfj ztGVkaI`kzk2^VTRaCYgpp;#2ny3BLuF={-Ds>Z@`TUMeH%dx-?+7>)nPYXSSv}GIK z$CaUzs5&fO=bzfKrAd+t^o{9qB_R|nFY8H+1U(GtsHmm8GI|Wd1xcNsYt_igXj_%n zZ9iDUVgw70H?BX%XOq?MtfaLkh-b9eYFIBN47+MJJWQu%FYMD}+g*=URbFv&K1QI+ zrpxE9m9mR+ha-ya40tOm+zYp1|Ky1AFY-9 zAdWOq5w6%u0ow=FrDdbm2mx+5fE{>Ebj{k9qrZuU2)t92S7JJEnk$q9uuJiI0C`JW9jFr-z_?}uo%4pYnC_*rMY!87f55aSvwFDW0?7#on|6&TvWUx7 zz7=vEErDVO#0-cs-@Zcd9Poiab9tr%CZaLxw!#uq?h)OIsi|7wwQl}gRwgEav@Zrv z1EyJ9XOv}SwZmYG3dnQuXF96n^VL+lK_MEHFA##d zHEGllI4Ct1s%rLvDhBAusxY4;t@0sm7!YK%R<)d$Z?nTy^UgL*ClzW^$wxF2pQ4wx_(S-KUGu@^0b}LuLC%oiWZNWOQ&cqHddpFW1ttexgUc-0AtsC(bBe2!m*hq% z5a%Y%UJcfD+vCR*ijq^CA?^F5>f$>WHdtg%r=-CCwfC0E9CzohV=H8Fvm7^1v;S6< zXB{gQ@ggⓈ$t3w@wr5lL+a!n`H(3vtjcatL-0S*45Cp9r&V~_Pl4rwR;G;K6U+o z4Z__Gd#vV3Qqj>J;>|Rp@5;ZP_M9c)EiEKSA8EvnjWcl^MVZOWu@2}-G2kqsYf1hZ z#>dUIs5iOL5F|5JSaiRpXV7ZjI}C=a_7Q*1)?u&!?Id_yL)p-g(c!V63Sd*GFsM~s%M&}LHP%y|6tQ&GH6NeG_+amYhtn~=BD9ZsrCR$VmOsU6dXPLIOByRBNy_Y>VCQ@tph;%y@> z93QjLn7d?Z&KVCygiyW!!lylS8zA9{@xx!xdF>pX+j{5wbL7kG-;JqG^gG+m2Q2!L zbE!0#X>Z#k2?b(UmI|)9#>o7MR@OF8+z{L&=_(azE_lXrKs4%itHwn@$ni^q(}<~O za4h8x@2sC$Qj%$PWG`zB^>n3vG=5S~cCU!vFHt8&V{-&1)" && + if output="$(spacetime --root-dir="${SPACETIME_ROOT_DIR}" server ping "${server}" 2>&1)" && [[ "${output}" == *"Server is online:"* ]]; then return 0 fi @@ -345,6 +345,48 @@ prepare_migration_bootstrap_secret() { echo "[dev:rust] 迁移引导密钥: ${MIGRATION_BOOTSTRAP_SECRET}" } +is_sccache_wrapper_failure_log() { + local log_file="$1" + + grep -Eiq \ + 'sccache: error|could not execute process.*sccache|Failed to send data to or receive data from server|Mismatch of client/server versions|Failed to read response header|failed to fill whole buffer' \ + "${log_file}" +} + +run_spacetime_publish_with_sccache_fallback() { + local root_dir="$1" + shift + + local publish_log + publish_log="$(mktemp -t genarrative-spacetime-publish.XXXXXX.log)" + + set +e + spacetime --root-dir="${root_dir}" "$@" 2> >(tee "${publish_log}" >&2) + local publish_status="$?" + set -e + + if [[ "${publish_status}" -eq 0 ]]; then + rm -f "${publish_log}" + return 0 + fi + + if ! is_sccache_wrapper_failure_log "${publish_log}"; then + rm -f "${publish_log}" + return "${publish_status}" + fi + + echo "[dev:rust] 检测到 sccache wrapper 通信异常,改用 rustc 直连重试 SpacetimeDB 发布。" + echo "[dev:rust] 这只影响本次 publish;项目级 sccache 配置不会被修改。" + + set +e + RUSTC_WRAPPER= CARGO_BUILD_RUSTC_WRAPPER= spacetime --root-dir="${root_dir}" "$@" + publish_status="$?" + set -e + + rm -f "${publish_log}" + return "${publish_status}" +} + SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" REPO_ROOT="$(cd -- "${SCRIPT_DIR}/.." && pwd)" SERVER_RS_DIR="${REPO_ROOT}/server-rs" @@ -564,6 +606,7 @@ if [[ "${SKIP_SPACETIME}" -ne 1 ]]; then # 当目标端口被占用时,SpacetimeDB 会询问是否使用最近的可用端口; # 这里直接发送回车接受默认建议,再从启动日志解析实际监听端口。 printf '\n' | spacetime \ + --root-dir="${SPACETIME_ROOT_DIR}" \ start \ --data-dir "${SPACETIME_DATA_DIR}" \ --listen-addr "${SPACETIME_HOST}:${SPACETIME_PORT}" \ @@ -601,7 +644,9 @@ if [[ "${SKIP_PUBLISH}" -ne 1 ]]; then cd "${SERVER_RS_DIR}" # spacetime publish 会在内部调用 Cargo;从 server-rs 目录执行,确保读取 # server-rs/.cargo/config.toml 中的 sccache/linker 配置,并复用同一套 target 缓存。 - spacetime "${PUBLISH_ARGS[@]}" + # Windows 本地 sccache server 偶发通信异常时,保留 root-dir/target 缓存路径, + # 仅对重试子进程清空 Cargo wrapper,避免可选缓存工具阻断真实构建。 + run_spacetime_publish_with_sccache_fallback "${SPACETIME_ROOT_DIR}" "${PUBLISH_ARGS[@]}" ) fi diff --git a/scripts/dev-web-rust.mjs b/scripts/dev-web-rust.mjs index 98d1ed10..681f61c8 100644 --- a/scripts/dev-web-rust.mjs +++ b/scripts/dev-web-rust.mjs @@ -37,14 +37,30 @@ loadEnvFile(resolve(repoRoot, '.env'), fileEnv); loadEnvFile(resolve(repoRoot, '.env.local'), fileEnv); loadEnvFile(resolve(repoRoot, '.env.secrets.local'), fileEnv); -function buildTargetCandidates() { +function resolveConfiguredTarget() { + if (fileEnv.GENARRATIVE_RUNTIME_SERVER_TARGET) { + return fileEnv.GENARRATIVE_RUNTIME_SERVER_TARGET; + } + + if (fileEnv.RUST_SERVER_TARGET) { + return fileEnv.RUST_SERVER_TARGET; + } + + if (fileEnv.GENARRATIVE_API_TARGET) { + return fileEnv.GENARRATIVE_API_TARGET; + } + + if (fileEnv.GENARRATIVE_API_PORT) { + return `http://127.0.0.1:${fileEnv.GENARRATIVE_API_PORT}`; + } + + return ''; +} + +function buildFallbackCandidates() { const candidates = [ - fileEnv.GENARRATIVE_RUNTIME_SERVER_TARGET, - fileEnv.RUST_SERVER_TARGET, - fileEnv.GENARRATIVE_API_TARGET, - `http://127.0.0.1:${fileEnv.GENARRATIVE_API_PORT || '3100'}`, - 'http://127.0.0.1:8082', 'http://127.0.0.1:3100', + 'http://127.0.0.1:8082', ].filter(Boolean); return Array.from(new Set(candidates)); @@ -70,39 +86,30 @@ async function isTargetReachable(target) { } async function resolveRuntimeTarget() { - const candidates = buildTargetCandidates(); - const reachableTargets = []; + const configuredTarget = resolveConfiguredTarget(); - for (const target of candidates) { - if (await isTargetReachable(target)) { - reachableTargets.push(target); - if ( - target === fileEnv.GENARRATIVE_RUNTIME_SERVER_TARGET || - target === fileEnv.RUST_SERVER_TARGET || - target === fileEnv.GENARRATIVE_API_TARGET - ) { - return { - target, - fallbackUsed: false, - }; - } - } - } - - if (reachableTargets.length > 0) { + if (configuredTarget) { return { - target: reachableTargets[0], - fallbackUsed: true, + target: configuredTarget, + fallbackUsed: false, + targetUnavailable: !(await isTargetReachable(configuredTarget)), }; } + for (const target of buildFallbackCandidates()) { + if (await isTargetReachable(target)) { + return { + target, + fallbackUsed: true, + targetUnavailable: false, + }; + } + } + return { - target: - fileEnv.GENARRATIVE_RUNTIME_SERVER_TARGET || - fileEnv.RUST_SERVER_TARGET || - fileEnv.GENARRATIVE_API_TARGET || - `http://127.0.0.1:${fileEnv.GENARRATIVE_API_PORT || '3100'}`, + target: 'http://127.0.0.1:3100', fallbackUsed: false, + targetUnavailable: true, }; } @@ -113,6 +120,12 @@ if (runtimeTarget.fallbackUsed) { ); } +if (runtimeTarget.targetUnavailable) { + console.warn( + `[dev:web] Rust target 当前不可用: ${runtimeTarget.target},请先启动 api-server。`, + ); +} + const mergedEnv = { ...fileEnv, RUST_SERVER_TARGET: runtimeTarget.target, diff --git a/server-rs/crates/api-server/Cargo.toml b/server-rs/crates/api-server/Cargo.toml index 6dc372f8..acaf6c2b 100644 --- a/server-rs/crates/api-server/Cargo.toml +++ b/server-rs/crates/api-server/Cargo.toml @@ -10,7 +10,7 @@ axum = { workspace = true, features = ["ws"] } base64 = { workspace = true } dotenvy = { workspace = true } image = { workspace = true, features = ["jpeg", "png", "webp"] } -reqwest = { workspace = true, features = ["json", "rustls-tls"] } +reqwest = { workspace = true, features = ["json", "multipart", "rustls-tls"] } webp = { workspace = true } module-ai = { workspace = true } module-assets = { workspace = true, features = ["server-service"] } diff --git a/server-rs/crates/api-server/src/app.rs b/server-rs/crates/api-server/src/app.rs index 0170c726..9ba8e7c5 100644 --- a/server-rs/crates/api-server/src/app.rs +++ b/server-rs/crates/api-server/src/app.rs @@ -74,6 +74,10 @@ use crate::{ }, error_middleware::normalize_error_response, health::health_check, + hyper3d_generation::{ + get_hyper3d_downloads, get_hyper3d_task_status, submit_hyper3d_image_to_model, + submit_hyper3d_text_to_model, + }, llm::proxy_llm_chat_completions, login_options::auth_login_options, logout::logout, @@ -166,6 +170,7 @@ use crate::{ const PUZZLE_REFERENCE_IMAGE_BODY_LIMIT_BYTES: usize = 12 * 1024 * 1024; const PROFILE_FEEDBACK_BODY_LIMIT_BYTES: usize = 6 * 1024 * 1024; +const HYPER3D_IMAGE_TO_MODEL_BODY_LIMIT_BYTES: usize = 56 * 1024 * 1024; // 统一由这里构造 Axum 路由树,后续再逐项挂接中间件与业务路由。 pub fn build_router(state: AppState) -> Router { @@ -550,6 +555,38 @@ pub fn build_router(state: AppState) -> Router { post(resolve_role_asset_workflow).put(put_role_asset_workflow), ) .route("/api/assets/read-url", get(get_asset_read_url)) + .route( + "/api/assets/hyper3d/text-to-model", + post(submit_hyper3d_text_to_model).route_layer(middleware::from_fn_with_state( + state.clone(), + require_bearer_auth, + )), + ) + .route( + "/api/assets/hyper3d/image-to-model", + post(submit_hyper3d_image_to_model) + .layer(DefaultBodyLimit::max( + HYPER3D_IMAGE_TO_MODEL_BODY_LIMIT_BYTES, + )) + .route_layer(middleware::from_fn_with_state( + state.clone(), + require_bearer_auth, + )), + ) + .route( + "/api/assets/hyper3d/status", + post(get_hyper3d_task_status).route_layer(middleware::from_fn_with_state( + state.clone(), + require_bearer_auth, + )), + ) + .route( + "/api/assets/hyper3d/download", + post(get_hyper3d_downloads).route_layer(middleware::from_fn_with_state( + state.clone(), + require_bearer_auth, + )), + ) .route( "/api/assets/history", get(get_asset_history).route_layer(middleware::from_fn_with_state( diff --git a/server-rs/crates/api-server/src/config.rs b/server-rs/crates/api-server/src/config.rs index 6bfeca45..d8e42168 100644 --- a/server-rs/crates/api-server/src/config.rs +++ b/server-rs/crates/api-server/src/config.rs @@ -96,10 +96,13 @@ pub struct AppConfig { pub dashscope_image_request_timeout_ms: u64, pub apimart_base_url: String, pub apimart_api_key: Option, - pub apimart_image_request_timeout_ms: u64, pub vector_engine_base_url: String, pub vector_engine_api_key: Option, + pub vector_engine_image_request_timeout_ms: u64, pub vector_engine_audio_request_timeout_ms: u64, + pub hyper3d_base_url: String, + pub hyper3d_api_key: Option, + pub hyper3d_model_request_timeout_ms: u64, pub volcengine_speech_api_key: Option, pub volcengine_speech_app_id: Option, pub volcengine_speech_access_key: Option, @@ -203,10 +206,13 @@ impl Default for AppConfig { dashscope_image_request_timeout_ms: 150_000, apimart_base_url: String::new(), apimart_api_key: None, - apimart_image_request_timeout_ms: 180_000, vector_engine_base_url: String::new(), vector_engine_api_key: None, + vector_engine_image_request_timeout_ms: 180_000, vector_engine_audio_request_timeout_ms: 180_000, + hyper3d_base_url: "https://api.hyper3d.com/api/v2".to_string(), + hyper3d_api_key: None, + hyper3d_model_request_timeout_ms: 180_000, volcengine_speech_api_key: None, volcengine_speech_app_id: None, volcengine_speech_access_key: None, @@ -567,12 +573,6 @@ impl AppConfig { config.apimart_api_key = read_first_non_empty_env(&["APIMART_API_KEY"]); - if let Some(apimart_image_request_timeout_ms) = - read_first_positive_u64_env(&["APIMART_IMAGE_REQUEST_TIMEOUT_MS"]) - { - config.apimart_image_request_timeout_ms = apimart_image_request_timeout_ms; - } - if let Some(vector_engine_base_url) = read_first_non_empty_env(&["VECTOR_ENGINE_BASE_URL"]) { config.vector_engine_base_url = vector_engine_base_url; @@ -580,12 +580,33 @@ impl AppConfig { config.vector_engine_api_key = read_first_non_empty_env(&["VECTOR_ENGINE_API_KEY"]); + if let Some(vector_engine_image_request_timeout_ms) = + read_first_positive_u64_env(&["VECTOR_ENGINE_IMAGE_REQUEST_TIMEOUT_MS"]) + { + config.vector_engine_image_request_timeout_ms = vector_engine_image_request_timeout_ms; + } + if let Some(vector_engine_audio_request_timeout_ms) = read_first_positive_u64_env(&["VECTOR_ENGINE_AUDIO_REQUEST_TIMEOUT_MS"]) { config.vector_engine_audio_request_timeout_ms = vector_engine_audio_request_timeout_ms; } + if let Some(hyper3d_base_url) = + read_first_non_empty_env(&["HYPER3D_BASE_URL", "RODIN_BASE_URL"]) + { + config.hyper3d_base_url = hyper3d_base_url; + } + + config.hyper3d_api_key = read_first_non_empty_env(&["HYPER3D_API_KEY", "RODIN_API_KEY"]); + + if let Some(hyper3d_model_request_timeout_ms) = read_first_positive_u64_env(&[ + "HYPER3D_MODEL_REQUEST_TIMEOUT_MS", + "RODIN_MODEL_REQUEST_TIMEOUT_MS", + ]) { + config.hyper3d_model_request_timeout_ms = hyper3d_model_request_timeout_ms; + } + config.volcengine_speech_api_key = read_first_non_empty_env(&["VOLCENGINE_SPEECH_API_KEY", "VOLCENGINE_API_KEY"]); config.volcengine_speech_app_id = @@ -910,6 +931,7 @@ mod tests { assert!(config.apimart_base_url.is_empty()); assert!(config.vector_engine_base_url.is_empty()); assert!(config.ark_character_video_base_url.is_empty()); + assert_eq!(config.hyper3d_base_url, "https://api.hyper3d.com/api/v2"); assert!(config.ark_character_video_model.is_empty()); assert!(config.dashscope_scene_image_model.is_empty()); assert!(config.dashscope_reference_image_model.is_empty()); @@ -938,6 +960,8 @@ mod tests { std::env::remove_var("GENARRATIVE_LLM_MODEL"); std::env::remove_var("APIMART_BASE_URL"); std::env::remove_var("VECTOR_ENGINE_BASE_URL"); + std::env::remove_var("VECTOR_ENGINE_IMAGE_REQUEST_TIMEOUT_MS"); + std::env::remove_var("HYPER3D_BASE_URL"); std::env::remove_var("DASHSCOPE_SCENE_IMAGE_MODEL"); std::env::remove_var("DASHSCOPE_REFERENCE_IMAGE_MODEL"); std::env::remove_var("DASHSCOPE_COVER_IMAGE_MODEL"); @@ -949,8 +973,10 @@ mod tests { "https://llm.internal.example/v1", ); std::env::set_var("GENARRATIVE_LLM_MODEL", "internal-text-model"); - std::env::set_var("APIMART_BASE_URL", "https://image.internal.example/v1"); - std::env::set_var("VECTOR_ENGINE_BASE_URL", "https://audio.internal.example"); + std::env::set_var("APIMART_BASE_URL", "https://responses.internal.example/v1"); + std::env::set_var("VECTOR_ENGINE_BASE_URL", "https://vector.internal.example"); + std::env::set_var("VECTOR_ENGINE_IMAGE_REQUEST_TIMEOUT_MS", "210000"); + std::env::set_var("HYPER3D_BASE_URL", "https://model.internal.example/api/v2"); std::env::set_var("DASHSCOPE_SCENE_IMAGE_MODEL", "scene-model"); std::env::set_var("DASHSCOPE_REFERENCE_IMAGE_MODEL", "reference-model"); std::env::set_var("DASHSCOPE_COVER_IMAGE_MODEL", "cover-model"); @@ -965,10 +991,18 @@ mod tests { assert_eq!(config.llm_provider, LlmProvider::OpenAiCompatible); assert_eq!(config.llm_base_url, "https://llm.internal.example/v1"); assert_eq!(config.llm_model, "internal-text-model"); - assert_eq!(config.apimart_base_url, "https://image.internal.example/v1"); + assert_eq!( + config.apimart_base_url, + "https://responses.internal.example/v1" + ); assert_eq!( config.vector_engine_base_url, - "https://audio.internal.example" + "https://vector.internal.example" + ); + assert_eq!(config.vector_engine_image_request_timeout_ms, 210_000); + assert_eq!( + config.hyper3d_base_url, + "https://model.internal.example/api/v2" ); assert_eq!(config.dashscope_scene_image_model, "scene-model"); assert_eq!(config.dashscope_reference_image_model, "reference-model"); @@ -985,6 +1019,8 @@ mod tests { std::env::remove_var("GENARRATIVE_LLM_MODEL"); std::env::remove_var("APIMART_BASE_URL"); std::env::remove_var("VECTOR_ENGINE_BASE_URL"); + std::env::remove_var("VECTOR_ENGINE_IMAGE_REQUEST_TIMEOUT_MS"); + std::env::remove_var("HYPER3D_BASE_URL"); std::env::remove_var("DASHSCOPE_SCENE_IMAGE_MODEL"); std::env::remove_var("DASHSCOPE_REFERENCE_IMAGE_MODEL"); std::env::remove_var("DASHSCOPE_COVER_IMAGE_MODEL"); diff --git a/server-rs/crates/api-server/src/hyper3d_generation.rs b/server-rs/crates/api-server/src/hyper3d_generation.rs new file mode 100644 index 00000000..3824f00c --- /dev/null +++ b/server-rs/crates/api-server/src/hyper3d_generation.rs @@ -0,0 +1,1052 @@ +use std::time::Duration; + +use axum::{ + Json, + extract::{State, rejection::JsonRejection}, + http::StatusCode, + response::Response, +}; +use base64::{Engine as _, engine::general_purpose::STANDARD as BASE64_STANDARD}; +use reqwest::{header, multipart}; +use serde_json::{Value, json}; +use shared_contracts::hyper3d as contract; + +use crate::{ + api_response::json_success_body, http_error::AppError, request_context::RequestContext, + state::AppState, +}; + +const HYPER3D_PROVIDER: &str = "hyper3d-rodin"; +const RODIN_GEN2_TIER: &str = "Gen-2"; +const DEFAULT_GEOMETRY_FILE_FORMAT: &str = "glb"; +const DEFAULT_MATERIAL: &str = "PBR"; +const DEFAULT_QUALITY: &str = "medium"; +const DEFAULT_MESH_MODE: &str = "Quad"; +const DEFAULT_CONDITION_MODE: &str = "concat"; +const MAX_PROMPT_CHARS: usize = 2_000; +const MAX_NEGATIVE_PROMPT_CHARS: usize = 1_000; +const MAX_IMAGE_COUNT: usize = 5; +const MAX_IMAGE_BYTES: usize = 10 * 1024 * 1024; + +#[derive(Clone, Debug)] +struct Hyper3dSettings { + base_url: String, + api_key: String, + request_timeout_ms: u64, +} + +#[derive(Clone, Debug)] +struct DecodedImageDataUrl { + bytes: Vec, + mime_type: String, + file_name: String, +} + +#[derive(Clone, Debug)] +struct SubmitOptions { + seed: Option, + geometry_file_format: String, + material: String, + quality: String, + mesh_mode: String, + addons: Vec, + bbox_condition: Option>, + preview_render: bool, +} + +pub async fn submit_hyper3d_text_to_model( + State(state): State, + axum::extract::Extension(request_context): axum::extract::Extension, + payload: Result, JsonRejection>, +) -> Result, Response> { + let Json(payload) = parse_json_payload(&request_context, payload)?; + submit_text_to_model(&state, payload) + .await + .map(|payload| json_success_body(Some(&request_context), payload)) + .map_err(|error| error.into_response_with_context(Some(&request_context))) +} + +pub async fn submit_hyper3d_image_to_model( + State(state): State, + axum::extract::Extension(request_context): axum::extract::Extension, + payload: Result, JsonRejection>, +) -> Result, Response> { + let Json(payload) = parse_json_payload(&request_context, payload)?; + submit_image_to_model(&state, payload) + .await + .map(|payload| json_success_body(Some(&request_context), payload)) + .map_err(|error| error.into_response_with_context(Some(&request_context))) +} + +pub async fn get_hyper3d_task_status( + State(state): State, + axum::extract::Extension(request_context): axum::extract::Extension, + payload: Result, JsonRejection>, +) -> Result, Response> { + let Json(payload) = parse_json_payload(&request_context, payload)?; + query_task_status(&state, payload) + .await + .map(|payload| json_success_body(Some(&request_context), payload)) + .map_err(|error| error.into_response_with_context(Some(&request_context))) +} + +pub async fn get_hyper3d_downloads( + State(state): State, + axum::extract::Extension(request_context): axum::extract::Extension, + payload: Result, JsonRejection>, +) -> Result, Response> { + let Json(payload) = parse_json_payload(&request_context, payload)?; + query_downloads(&state, payload) + .await + .map(|payload| json_success_body(Some(&request_context), payload)) + .map_err(|error| error.into_response_with_context(Some(&request_context))) +} + +async fn submit_text_to_model( + state: &AppState, + payload: contract::Hyper3dTextToModelRequest, +) -> Result { + let settings = require_hyper3d_settings(state)?; + let http_client = build_hyper3d_http_client(&settings)?; + let prompt = normalize_required_text(&payload.prompt, "prompt", MAX_PROMPT_CHARS)?; + let options = SubmitOptions::from_text_request(&payload)?; + let mut form = multipart::Form::new() + .text("tier", RODIN_GEN2_TIER.to_string()) + .text("prompt", prompt); + form = append_common_submit_fields(form, &options)?; + if let Some(negative_prompt) = normalize_optional_limited_text( + payload.negative_prompt.as_deref(), + MAX_NEGATIVE_PROMPT_CHARS, + )? { + form = form.text("negative_prompt", negative_prompt); + } + + let response = post_hyper3d_multipart( + &http_client, + &settings, + "/rodin", + form, + "提交 Hyper3D 文生模型任务失败", + ) + .await?; + + Ok(build_submit_response( + contract::Hyper3dGenerationMode::TextToModel, + response, + )?) +} + +async fn submit_image_to_model( + state: &AppState, + payload: contract::Hyper3dImageToModelRequest, +) -> Result { + let settings = require_hyper3d_settings(state)?; + let http_client = build_hyper3d_http_client(&settings)?; + let options = SubmitOptions::from_image_request(&payload)?; + let mut form = multipart::Form::new().text("tier", RODIN_GEN2_TIER.to_string()); + form = append_common_submit_fields(form, &options)?; + let condition_mode = normalize_enum( + payload.condition_mode.as_deref(), + DEFAULT_CONDITION_MODE, + &["concat", "fuse"], + "conditionMode", + )?; + form = form.text("condition_mode", condition_mode); + if let Some(prompt) = + normalize_optional_limited_text(payload.prompt.as_deref(), MAX_PROMPT_CHARS)? + { + form = form.text("prompt", prompt); + } + for image_url in payload + .image_urls + .iter() + .map(|value| value.trim()) + .filter(|value| !value.is_empty()) + { + form = form.text("image_urls", image_url.to_string()); + } + for image in decode_image_data_urls(&payload.image_data_urls)? { + let part = multipart::Part::bytes(image.bytes) + .file_name(image.file_name) + .mime_str(&image.mime_type) + .map_err(|error| { + AppError::from_status(StatusCode::BAD_REQUEST).with_details(json!({ + "provider": HYPER3D_PROVIDER, + "message": format!("构造图生模型图片字段失败:{error}"), + })) + })?; + form = form.part("images", part); + } + + if payload.image_data_urls.is_empty() && payload.image_urls.is_empty() { + return Err( + AppError::from_status(StatusCode::BAD_REQUEST).with_details(json!({ + "provider": HYPER3D_PROVIDER, + "field": "imageDataUrls", + "message": "图生模型至少需要一张参考图", + })), + ); + } + if payload.image_data_urls.len() + payload.image_urls.len() > MAX_IMAGE_COUNT { + return Err( + AppError::from_status(StatusCode::BAD_REQUEST).with_details(json!({ + "provider": HYPER3D_PROVIDER, + "field": "imageDataUrls", + "message": format!("图生模型最多支持 {} 张参考图", MAX_IMAGE_COUNT), + })), + ); + } + + let response = post_hyper3d_multipart( + &http_client, + &settings, + "/rodin", + form, + "提交 Hyper3D 图生模型任务失败", + ) + .await?; + + Ok(build_submit_response( + contract::Hyper3dGenerationMode::ImageToModel, + response, + )?) +} + +async fn query_task_status( + state: &AppState, + payload: contract::Hyper3dTaskStatusRequest, +) -> Result { + let settings = require_hyper3d_settings(state)?; + let http_client = build_hyper3d_http_client(&settings)?; + let subscription_key = + normalize_required_text(&payload.subscription_key, "subscriptionKey", 256)?; + let response = post_hyper3d_json( + &http_client, + &settings, + "/status", + json!({ "subscription_key": subscription_key }), + "查询 Hyper3D 模型任务状态失败", + ) + .await?; + + let jobs = extract_job_statuses(&response); + let status = normalize_task_status( + find_first_string_by_key(&response, "status") + .or_else(|| jobs.first().map(|job| job.status.clone())) + .as_deref() + .unwrap_or("unknown"), + ); + + Ok(contract::Hyper3dTaskStatusResponse { + ok: true, + provider: HYPER3D_PROVIDER.to_string(), + status, + jobs, + raw: response, + }) +} + +async fn query_downloads( + state: &AppState, + payload: contract::Hyper3dDownloadRequest, +) -> Result { + let settings = require_hyper3d_settings(state)?; + let http_client = build_hyper3d_http_client(&settings)?; + let task_uuid = normalize_required_text(&payload.task_uuid, "taskUuid", 256)?; + let response = post_hyper3d_json( + &http_client, + &settings, + "/download", + json!({ "task_uuid": task_uuid }), + "获取 Hyper3D 模型下载列表失败", + ) + .await?; + + Ok(contract::Hyper3dDownloadResponse { + ok: true, + provider: HYPER3D_PROVIDER.to_string(), + files: extract_download_files(&response), + raw: response, + }) +} + +impl SubmitOptions { + fn from_text_request(payload: &contract::Hyper3dTextToModelRequest) -> Result { + Self::new( + payload.seed, + payload.geometry_file_format.as_deref(), + payload.material.as_deref(), + payload.quality.as_deref(), + payload.mesh_mode.as_deref(), + payload.addons.clone(), + payload.bbox_condition.clone(), + payload.preview_render, + ) + } + + fn from_image_request( + payload: &contract::Hyper3dImageToModelRequest, + ) -> Result { + Self::new( + payload.seed, + payload.geometry_file_format.as_deref(), + payload.material.as_deref(), + payload.quality.as_deref(), + payload.mesh_mode.as_deref(), + payload.addons.clone(), + payload.bbox_condition.clone(), + payload.preview_render, + ) + } + + #[allow(clippy::too_many_arguments)] + fn new( + seed: Option, + geometry_file_format: Option<&str>, + material: Option<&str>, + quality: Option<&str>, + mesh_mode: Option<&str>, + addons: Vec, + bbox_condition: Option>, + preview_render: Option, + ) -> Result { + Ok(Self { + seed, + geometry_file_format: normalize_enum( + geometry_file_format, + DEFAULT_GEOMETRY_FILE_FORMAT, + &["glb", "usdz", "fbx", "obj", "stl"], + "geometryFileFormat", + )?, + material: normalize_enum( + material, + DEFAULT_MATERIAL, + &["PBR", "Shaded", "All"], + "material", + )?, + quality: normalize_enum( + quality, + DEFAULT_QUALITY, + &["high", "medium", "low", "extra-low"], + "quality", + )?, + mesh_mode: normalize_enum(mesh_mode, DEFAULT_MESH_MODE, &["Quad", "Raw"], "meshMode")?, + addons: normalize_addons(addons)?, + bbox_condition: normalize_bbox_condition(bbox_condition)?, + preview_render: preview_render.unwrap_or(true), + }) + } +} + +fn append_common_submit_fields( + mut form: multipart::Form, + options: &SubmitOptions, +) -> Result { + form = form + .text( + "geometry_file_format", + options.geometry_file_format.to_string(), + ) + .text("material", options.material.to_string()) + .text("quality", options.quality.to_string()) + .text("mesh_mode", options.mesh_mode.to_string()) + .text("preview_render", options.preview_render.to_string()); + if let Some(seed) = options.seed { + form = form.text("seed", seed.to_string()); + } + for addon in &options.addons { + form = form.text("addons", addon.to_string()); + } + if let Some(bbox_condition) = &options.bbox_condition { + form = form.text( + "bbox_condition", + serde_json::to_string(bbox_condition).map_err(|error| { + AppError::from_status(StatusCode::BAD_REQUEST).with_details(json!({ + "provider": HYPER3D_PROVIDER, + "field": "bboxCondition", + "message": format!("bboxCondition 序列化失败:{error}"), + })) + })?, + ); + } + + Ok(form) +} + +fn require_hyper3d_settings(state: &AppState) -> Result { + let base_url = state.config.hyper3d_base_url.trim().trim_end_matches('/'); + if base_url.is_empty() { + return Err( + AppError::from_status(StatusCode::SERVICE_UNAVAILABLE).with_details(json!({ + "provider": HYPER3D_PROVIDER, + "reason": "HYPER3D_BASE_URL 未配置", + })), + ); + } + + let api_key = state + .config + .hyper3d_api_key + .as_deref() + .map(str::trim) + .filter(|value| !value.is_empty()) + .ok_or_else(|| { + AppError::from_status(StatusCode::SERVICE_UNAVAILABLE).with_details(json!({ + "provider": HYPER3D_PROVIDER, + "reason": "HYPER3D_API_KEY 未配置", + })) + })?; + + Ok(Hyper3dSettings { + base_url: base_url.to_string(), + api_key: api_key.to_string(), + request_timeout_ms: state.config.hyper3d_model_request_timeout_ms.max(1), + }) +} + +fn build_hyper3d_http_client(settings: &Hyper3dSettings) -> Result { + reqwest::Client::builder() + .timeout(Duration::from_millis(settings.request_timeout_ms)) + .build() + .map_err(|error| { + AppError::from_status(StatusCode::INTERNAL_SERVER_ERROR).with_details(json!({ + "provider": HYPER3D_PROVIDER, + "message": format!("构造 Hyper3D HTTP 客户端失败:{error}"), + })) + }) +} + +async fn post_hyper3d_multipart( + http_client: &reqwest::Client, + settings: &Hyper3dSettings, + path: &str, + form: multipart::Form, + failure_context: &str, +) -> Result { + let response = http_client + .post(format!("{}{}", settings.base_url, path)) + .header( + header::AUTHORIZATION, + format!("Bearer {}", settings.api_key), + ) + .header(header::ACCEPT, "application/json") + .multipart(form) + .send() + .await + .map_err(|error| hyper3d_bad_gateway(format!("{failure_context}:{error}")))?; + parse_hyper3d_response(response, failure_context).await +} + +async fn post_hyper3d_json( + http_client: &reqwest::Client, + settings: &Hyper3dSettings, + path: &str, + body: Value, + failure_context: &str, +) -> Result { + let response = http_client + .post(format!("{}{}", settings.base_url, path)) + .header( + header::AUTHORIZATION, + format!("Bearer {}", settings.api_key), + ) + .header(header::ACCEPT, "application/json") + .header(header::CONTENT_TYPE, "application/json") + .json(&body) + .send() + .await + .map_err(|error| hyper3d_bad_gateway(format!("{failure_context}:{error}")))?; + parse_hyper3d_response(response, failure_context).await +} + +async fn parse_hyper3d_response( + response: reqwest::Response, + failure_context: &str, +) -> Result { + let status = response.status(); + let raw_text = response.text().await.map_err(|error| { + hyper3d_bad_gateway(format!("{failure_context}:读取上游响应失败:{error}")) + })?; + if !status.is_success() { + return Err( + AppError::from_status(StatusCode::BAD_GATEWAY).with_details(json!({ + "provider": HYPER3D_PROVIDER, + "message": parse_api_error_message(&raw_text, failure_context), + "status": status.as_u16(), + "rawExcerpt": truncate_raw(&raw_text), + })), + ); + } + + serde_json::from_str::(&raw_text).map_err(|error| { + AppError::from_status(StatusCode::BAD_GATEWAY).with_details(json!({ + "provider": HYPER3D_PROVIDER, + "message": format!("{failure_context}:解析上游 JSON 失败:{error}"), + "rawExcerpt": truncate_raw(&raw_text), + })) + }) +} + +fn build_submit_response( + mode: contract::Hyper3dGenerationMode, + response: Value, +) -> Result { + let task_uuid = find_root_string_by_keys(&response, &["uuid", "task_uuid", "taskUuid"]) + .or_else(|| find_first_string_by_keys(&response, &["task_uuid", "taskUuid"])) + .ok_or_else(|| hyper3d_bad_gateway("Hyper3D 已响应,但未返回任务 uuid"))?; + let subscription_key = + find_root_string_by_keys(&response, &["subscription_key", "subscriptionKey"]) + .or_else(|| { + find_first_string_by_keys(&response, &["subscription_key", "subscriptionKey"]) + }) + .ok_or_else(|| hyper3d_bad_gateway("Hyper3D 已响应,但未返回 subscription_key"))?; + let job_uuids = extract_job_uuids(&response); + let message = find_first_string_by_keys(&response, &["message", "detail"]); + + Ok(contract::Hyper3dTaskSubmitResponse { + ok: true, + provider: HYPER3D_PROVIDER.to_string(), + mode, + task_uuid, + subscription_key, + job_uuids, + message, + tier: RODIN_GEN2_TIER.to_string(), + }) +} + +fn extract_job_statuses(payload: &Value) -> Vec { + let Some(array) = find_first_array_by_keys(payload, &["jobs", "tasks"]) else { + return Vec::new(); + }; + + array + .iter() + .filter_map(|value| { + let status = find_first_string_by_keys(value, &["status", "state"]) + .map(|value| normalize_task_status(&value))?; + Some(contract::Hyper3dJobStatusPayload { + uuid: find_first_string_by_keys(value, &["uuid", "task_uuid", "taskUuid"]), + progress: find_first_f64_by_keys(value, &["progress", "percentage"]) + .map(|value| value as f32), + message: find_first_string_by_keys(value, &["message", "detail", "error"]), + status, + }) + }) + .collect() +} + +fn extract_job_uuids(payload: &Value) -> Vec { + let mut job_uuids = Vec::new(); + if let Some(jobs) = find_first_array_by_keys(payload, &["jobs"]) { + for job in jobs { + if let Some(uuid) = find_first_string_by_keys(job, &["uuid", "task_uuid", "taskUuid"]) + && !job_uuids.contains(&uuid) + { + job_uuids.push(uuid); + } + } + } + for uuid in collect_strings_by_keys(payload, &["job_uuids", "jobUuids", "uuids"]) { + if !job_uuids.contains(&uuid) { + job_uuids.push(uuid); + } + } + job_uuids +} + +fn extract_download_files(payload: &Value) -> Vec { + let mut files = Vec::new(); + collect_download_files(payload, &mut files); + let mut deduped = Vec::new(); + for file in files { + if !deduped + .iter() + .any(|entry: &contract::Hyper3dDownloadFilePayload| entry.url == file.url) + { + deduped.push(file); + } + } + deduped +} + +fn collect_download_files(value: &Value, output: &mut Vec) { + match value { + Value::Object(object) => { + let maybe_url = object + .get("url") + .or_else(|| object.get("download_url")) + .or_else(|| object.get("downloadUrl")) + .and_then(Value::as_str) + .map(str::trim) + .filter(|value| value.starts_with("http://") || value.starts_with("https://")); + if let Some(url) = maybe_url { + let name = object + .get("name") + .or_else(|| object.get("file_name")) + .or_else(|| object.get("filename")) + .and_then(Value::as_str) + .map(str::trim) + .filter(|value| !value.is_empty()) + .unwrap_or("model") + .to_string(); + output.push(contract::Hyper3dDownloadFilePayload { + name, + url: url.to_string(), + }); + } + for nested in object.values() { + collect_download_files(nested, output); + } + } + Value::Array(items) => { + for item in items { + collect_download_files(item, output); + } + } + _ => {} + } +} + +fn decode_image_data_urls(values: &[String]) -> Result, AppError> { + values + .iter() + .enumerate() + .map(|(index, value)| decode_image_data_url(value, index + 1)) + .collect() +} + +fn decode_image_data_url(value: &str, index: usize) -> Result { + let value = value.trim(); + let Some((metadata, encoded)) = value.split_once(',') else { + return Err(invalid_image_data_url("参考图必须是 data URL")); + }; + if !metadata.starts_with("data:image/") || !metadata.ends_with(";base64") { + return Err(invalid_image_data_url( + "参考图只支持 image/png、image/jpeg 或 image/webp 的 base64 data URL", + )); + } + let mime_type = metadata + .trim_start_matches("data:") + .trim_end_matches(";base64") + .to_string(); + let extension = match mime_type.as_str() { + "image/png" => "png", + "image/jpeg" | "image/jpg" => "jpg", + "image/webp" => "webp", + _ => { + return Err(invalid_image_data_url( + "参考图只支持 image/png、image/jpeg 或 image/webp", + )); + } + }; + let bytes = BASE64_STANDARD + .decode(encoded) + .map_err(|_| invalid_image_data_url("参考图 base64 解码失败"))?; + if bytes.is_empty() || bytes.len() > MAX_IMAGE_BYTES { + return Err(invalid_image_data_url("参考图为空或超过 10MB")); + } + + Ok(DecodedImageDataUrl { + bytes, + mime_type, + file_name: format!("reference-{index:02}.{extension}"), + }) +} + +fn invalid_image_data_url(message: &str) -> AppError { + AppError::from_status(StatusCode::BAD_REQUEST).with_details(json!({ + "provider": HYPER3D_PROVIDER, + "field": "imageDataUrls", + "message": message, + })) +} + +fn normalize_required_text( + value: &str, + field: &'static str, + max_chars: usize, +) -> Result { + let normalized = value.trim().to_string(); + if normalized.is_empty() { + return Err( + AppError::from_status(StatusCode::BAD_REQUEST).with_details(json!({ + "provider": HYPER3D_PROVIDER, + "field": field, + "message": format!("{field} 不能为空"), + })), + ); + } + if normalized.chars().count() > max_chars { + return Err( + AppError::from_status(StatusCode::BAD_REQUEST).with_details(json!({ + "provider": HYPER3D_PROVIDER, + "field": field, + "message": format!("{field} 超过 {} 字符", max_chars), + })), + ); + } + Ok(normalized) +} + +fn normalize_optional_limited_text( + value: Option<&str>, + max_chars: usize, +) -> Result, AppError> { + let Some(normalized) = value.map(str::trim).filter(|value| !value.is_empty()) else { + return Ok(None); + }; + if normalized.chars().count() > max_chars { + return Err( + AppError::from_status(StatusCode::BAD_REQUEST).with_details(json!({ + "provider": HYPER3D_PROVIDER, + "message": format!("文本超过 {} 字符", max_chars), + })), + ); + } + Ok(Some(normalized.to_string())) +} + +fn normalize_enum( + value: Option<&str>, + default_value: &str, + allowed_values: &[&str], + field: &'static str, +) -> Result { + let value = value + .map(str::trim) + .filter(|value| !value.is_empty()) + .unwrap_or(default_value); + if let Some(allowed) = allowed_values + .iter() + .find(|allowed| allowed.eq_ignore_ascii_case(value)) + { + return Ok((*allowed).to_string()); + } + + Err( + AppError::from_status(StatusCode::BAD_REQUEST).with_details(json!({ + "provider": HYPER3D_PROVIDER, + "field": field, + "message": format!("{} 取值非法", field), + "allowed": allowed_values, + })), + ) +} + +fn normalize_addons(values: Vec) -> Result, AppError> { + let mut addons = Vec::new(); + for value in values { + let value = value.trim(); + if value.is_empty() { + continue; + } + if value != "HighPack" { + return Err( + AppError::from_status(StatusCode::BAD_REQUEST).with_details(json!({ + "provider": HYPER3D_PROVIDER, + "field": "addons", + "message": "addons 首版只支持 HighPack", + })), + ); + } + if !addons.iter().any(|addon| addon == value) { + addons.push(value.to_string()); + } + } + Ok(addons) +} + +fn normalize_bbox_condition(value: Option>) -> Result>, AppError> { + let Some(value) = value else { + return Ok(None); + }; + if value.len() != 3 || value.iter().any(|item| !item.is_finite() || *item <= 0.0) { + return Err( + AppError::from_status(StatusCode::BAD_REQUEST).with_details(json!({ + "provider": HYPER3D_PROVIDER, + "field": "bboxCondition", + "message": "bboxCondition 必须包含 3 个正数", + })), + ); + } + Ok(Some(value)) +} + +fn normalize_task_status(status: &str) -> String { + match status.trim().to_ascii_lowercase().as_str() { + "waiting" | "pending" | "queued" => "waiting".to_string(), + "generating" | "running" | "processing" => "generating".to_string(), + "done" | "finished" | "completed" | "success" | "succeeded" => "done".to_string(), + "failed" | "error" | "canceled" | "cancelled" => "failed".to_string(), + _ => "unknown".to_string(), + } +} + +fn parse_api_error_message(raw_text: &str, fallback_message: &str) -> String { + if let Ok(parsed) = serde_json::from_str::(raw_text) { + for key in ["message", "detail", "error"] { + if let Some(message) = find_first_string_by_key(&parsed, key) + && !message.trim().is_empty() + { + return message; + } + } + } + raw_text + .trim() + .chars() + .take(240) + .collect::() + .trim() + .to_string() + .chars() + .next() + .map(|_| raw_text.trim().chars().take(240).collect()) + .unwrap_or_else(|| fallback_message.to_string()) +} + +fn find_first_array_by_keys<'a>(value: &'a Value, keys: &[&str]) -> Option<&'a Vec> { + match value { + Value::Object(object) => { + for (key, value) in object { + if keys.iter().any(|target| key.eq_ignore_ascii_case(target)) + && let Some(array) = value.as_array() + { + return Some(array); + } + if let Some(found) = find_first_array_by_keys(value, keys) { + return Some(found); + } + } + None + } + Value::Array(items) => items + .iter() + .find_map(|item| find_first_array_by_keys(item, keys)), + _ => None, + } +} + +fn find_first_string_by_keys(value: &Value, keys: &[&str]) -> Option { + keys.iter() + .find_map(|key| find_first_string_by_key(value, key)) +} + +fn find_root_string_by_keys(value: &Value, keys: &[&str]) -> Option { + let object = value.as_object()?; + for key in keys { + if let Some(text) = object + .iter() + .find(|(candidate, _)| candidate.eq_ignore_ascii_case(key)) + .and_then(|(_, value)| value.as_str()) + .map(str::trim) + .filter(|value| !value.is_empty()) + { + return Some(text.to_string()); + } + } + None +} + +fn find_first_string_by_key(value: &Value, target_key: &str) -> Option { + match value { + Value::Object(object) => { + for (key, value) in object { + if key.eq_ignore_ascii_case(target_key) + && let Some(text) = value.as_str() + { + return Some(text.trim().to_string()); + } + if let Some(found) = find_first_string_by_key(value, target_key) { + return Some(found); + } + } + None + } + Value::Array(items) => items + .iter() + .find_map(|item| find_first_string_by_key(item, target_key)), + _ => None, + } +} + +fn find_first_f64_by_keys(value: &Value, keys: &[&str]) -> Option { + match value { + Value::Object(object) => { + for (key, value) in object { + if keys.iter().any(|target| key.eq_ignore_ascii_case(target)) + && let Some(number) = value.as_f64() + { + return Some(number); + } + if let Some(found) = find_first_f64_by_keys(value, keys) { + return Some(found); + } + } + None + } + Value::Array(items) => items + .iter() + .find_map(|item| find_first_f64_by_keys(item, keys)), + _ => None, + } +} + +fn collect_strings_by_keys(value: &Value, keys: &[&str]) -> Vec { + let mut results = Vec::new(); + collect_strings(value, keys, &mut results); + let mut deduped = Vec::new(); + for result in results { + if !deduped.contains(&result) { + deduped.push(result); + } + } + deduped +} + +fn collect_strings(value: &Value, keys: &[&str], output: &mut Vec) { + match value { + Value::Object(object) => { + for (key, value) in object { + if keys.iter().any(|target| key.eq_ignore_ascii_case(target)) { + match value { + Value::String(text) if !text.trim().is_empty() => { + output.push(text.trim().to_string()); + } + Value::Array(items) => { + for item in items { + if let Some(text) = item.as_str().map(str::trim) + && !text.is_empty() + { + output.push(text.to_string()); + } + } + } + _ => {} + } + } + collect_strings(value, keys, output); + } + } + Value::Array(items) => { + for item in items { + collect_strings(item, keys, output); + } + } + _ => {} + } +} + +fn truncate_raw(raw_text: &str) -> String { + raw_text.chars().take(800).collect() +} + +fn hyper3d_bad_gateway(message: impl Into) -> AppError { + AppError::from_status(StatusCode::BAD_GATEWAY).with_details(json!({ + "provider": HYPER3D_PROVIDER, + "message": message.into(), + })) +} + +fn parse_json_payload( + request_context: &RequestContext, + payload: Result, JsonRejection>, +) -> Result, Response> { + payload.map_err(|rejection| { + AppError::from_status(StatusCode::BAD_REQUEST) + .with_message(format!("请求体 JSON 不合法:{rejection}")) + .into_response_with_context(Some(request_context)) + }) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn validates_and_defaults_submit_options() { + let payload = contract::Hyper3dTextToModelRequest { + prompt: "宝箱".to_string(), + negative_prompt: None, + seed: Some(7), + geometry_file_format: None, + material: None, + quality: None, + mesh_mode: None, + addons: vec!["HighPack".to_string()], + bbox_condition: Some(vec![1.0, 2.0, 3.0]), + preview_render: None, + }; + + let options = SubmitOptions::from_text_request(&payload).expect("options should build"); + + assert_eq!(options.geometry_file_format, "glb"); + assert_eq!(options.material, "PBR"); + assert_eq!(options.quality, "medium"); + assert_eq!(options.mesh_mode, "Quad"); + assert_eq!(options.addons, vec!["HighPack"]); + assert!(options.preview_render); + } + + #[test] + fn rejects_invalid_bbox_condition() { + let error = normalize_bbox_condition(Some(vec![1.0, 0.0, 3.0])) + .expect_err("invalid bbox should fail"); + + assert_eq!(error.status_code(), StatusCode::BAD_REQUEST); + } + + #[test] + fn decodes_png_data_url() { + let data_url = format!( + "data:image/png;base64,{}", + BASE64_STANDARD.encode(b"\x89PNG\r\n\x1A\nrest") + ); + + let image = decode_image_data_url(&data_url, 1).expect("image should decode"); + + assert_eq!(image.mime_type, "image/png"); + assert_eq!(image.file_name, "reference-01.png"); + assert!(!image.bytes.is_empty()); + } + + #[test] + fn extracts_submit_response_from_nested_payload() { + let response = build_submit_response( + contract::Hyper3dGenerationMode::TextToModel, + json!({ + "uuid": "task-1", + "subscription_key": "sub-1", + "jobs": [{ "uuid": "job-1" }], + "message": "submitted" + }), + ) + .expect("submit response should build"); + + assert_eq!(response.task_uuid, "task-1"); + assert_eq!(response.subscription_key, "sub-1"); + assert_eq!(response.job_uuids, vec!["job-1"]); + } + + #[test] + fn extracts_download_files_from_list() { + let files = extract_download_files(&json!({ + "list": [ + { "name": "model.glb", "url": "https://cdn.example/model.glb" }, + { "name": "preview.png", "url": "https://cdn.example/preview.png" } + ] + })); + + assert_eq!(files.len(), 2); + assert_eq!(files[0].name, "model.glb"); + } + + #[test] + fn normalizes_status_values() { + assert_eq!(normalize_task_status("Waiting"), "waiting"); + assert_eq!(normalize_task_status("Generating"), "generating"); + assert_eq!(normalize_task_status("Done"), "done"); + assert_eq!(normalize_task_status("Failed"), "failed"); + } +} diff --git a/server-rs/crates/api-server/src/main.rs b/server-rs/crates/api-server/src/main.rs index c27f4e19..9a0bc43e 100644 --- a/server-rs/crates/api-server/src/main.rs +++ b/server-rs/crates/api-server/src/main.rs @@ -36,6 +36,7 @@ mod custom_world_rpg_draft_prompts; mod error_middleware; mod health; mod http_error; +mod hyper3d_generation; mod llm; mod llm_model_routing; mod login_options; diff --git a/server-rs/crates/api-server/src/openai_image_generation.rs b/server-rs/crates/api-server/src/openai_image_generation.rs index fd55bdf9..040f8993 100644 --- a/server-rs/crates/api-server/src/openai_image_generation.rs +++ b/server-rs/crates/api-server/src/openai_image_generation.rs @@ -1,14 +1,15 @@ -use std::time::{Duration, Instant}; +use std::time::Duration; use axum::http::StatusCode; use base64::{Engine as _, engine::general_purpose::STANDARD as BASE64_STANDARD}; use reqwest::header; use serde_json::{Map, Value, json}; -use tokio::time::sleep; use crate::{http_error::AppError, state::AppState}; pub(crate) const GPT_IMAGE_2_MODEL: &str = "gpt-image-2"; +pub(crate) const VECTOR_ENGINE_GPT_IMAGE_2_MODEL: &str = "gpt-image-2-all"; +const VECTOR_ENGINE_PROVIDER: &str = "vector-engine"; #[derive(Clone, Debug)] pub(crate) struct OpenAiImageSettings { @@ -31,37 +32,41 @@ pub(crate) struct DownloadedOpenAiImage { pub extension: String, } -// 中文注释:RPG 图片资产与拼图一样走 APIMart 的 OpenAI 兼容图片入口,避免把密钥或供应商协议暴露到前端。 +// 中文注释:RPG、方洞等图片资产统一走 VectorEngine GPT-image-2-all,避免把密钥或供应商协议暴露到前端。 pub(crate) fn require_openai_image_settings( state: &AppState, ) -> Result { - let base_url = state.config.apimart_base_url.trim().trim_end_matches('/'); + let base_url = state + .config + .vector_engine_base_url + .trim() + .trim_end_matches('/'); if base_url.is_empty() { return Err( AppError::from_status(StatusCode::SERVICE_UNAVAILABLE).with_details(json!({ - "provider": "apimart", - "reason": "APIMART_BASE_URL 未配置", + "provider": VECTOR_ENGINE_PROVIDER, + "reason": "VECTOR_ENGINE_BASE_URL 未配置", })), ); } let api_key = state .config - .apimart_api_key + .vector_engine_api_key .as_deref() .map(str::trim) .filter(|value| !value.is_empty()) .ok_or_else(|| { AppError::from_status(StatusCode::SERVICE_UNAVAILABLE).with_details(json!({ - "provider": "apimart", - "reason": "APIMART_API_KEY 未配置", + "provider": VECTOR_ENGINE_PROVIDER, + "reason": "VECTOR_ENGINE_API_KEY 未配置", })) })?; Ok(OpenAiImageSettings { base_url: base_url.to_string(), api_key: api_key.to_string(), - request_timeout_ms: state.config.apimart_image_request_timeout_ms.max(1), + request_timeout_ms: state.config.vector_engine_image_request_timeout_ms.max(1), }) } @@ -73,8 +78,8 @@ pub(crate) fn build_openai_image_http_client( .build() .map_err(|error| { AppError::from_status(StatusCode::INTERNAL_SERVER_ERROR).with_details(json!({ - "provider": "apimart", - "message": format!("构造 APIMart 图片生成 HTTP 客户端失败:{error}"), + "provider": VECTOR_ENGINE_PROVIDER, + "message": format!("构造 VectorEngine 图片生成 HTTP 客户端失败:{error}"), })) }) } @@ -97,11 +102,12 @@ pub(crate) async fn create_openai_image_generation( reference_images, ); let response = http_client - .post(format!("{}/images/generations", settings.base_url)) + .post(vector_engine_images_generation_url(settings)) .header( header::AUTHORIZATION, format!("Bearer {}", settings.api_key), ) + .header(header::ACCEPT, "application/json") .header(header::CONTENT_TYPE, "application/json") .json(&request_body) .send() @@ -124,40 +130,29 @@ pub(crate) async fn create_openai_image_generation( } let response_json = parse_json_payload(response_text.as_str(), failure_context)?; + let generation_id = extract_generation_id(&response_json.payload) + .unwrap_or_else(|| format!("vector-engine-{}", current_utc_micros())); + let actual_prompt = find_first_string_by_key(&response_json.payload, "revised_prompt") + .or_else(|| find_first_string_by_key(&response_json.payload, "actual_prompt")); let image_urls = extract_image_urls(&response_json.payload); if !image_urls.is_empty() { - return download_images_from_urls( - http_client, - format!("apimart-{}", current_utc_micros()), - image_urls, - candidate_count, - ) - .await; + let mut generated = + download_images_from_urls(http_client, generation_id, image_urls, candidate_count) + .await?; + generated.actual_prompt = actual_prompt; + return Ok(generated); } let b64_images = extract_b64_images(&response_json.payload); if !b64_images.is_empty() { - return Ok(images_from_base64( - format!("apimart-{}", current_utc_micros()), - b64_images, - candidate_count, - )); + let mut generated = images_from_base64(generation_id, b64_images, candidate_count); + generated.actual_prompt = actual_prompt; + return Ok(generated); } - let task_id = extract_task_id(&response_json.payload).ok_or_else(|| { - AppError::from_status(StatusCode::BAD_GATEWAY).with_details(json!({ - "provider": "apimart", - "message": format!("{failure_context}:上游未返回 task_id 或图片"), - })) - })?; - - wait_openai_generated_images( - http_client, - settings, - task_id.as_str(), - candidate_count, - failure_context, - ) - .await + Err(AppError::from_status(StatusCode::BAD_GATEWAY).with_details(json!({ + "provider": VECTOR_ENGINE_PROVIDER, + "message": format!("{failure_context}:VectorEngine 未返回图片地址"), + }))) } pub(crate) fn build_openai_image_request_body( @@ -170,14 +165,13 @@ pub(crate) fn build_openai_image_request_body( let mut body = Map::from_iter([ ( "model".to_string(), - Value::String(GPT_IMAGE_2_MODEL.to_string()), + Value::String(VECTOR_ENGINE_GPT_IMAGE_2_MODEL.to_string()), ), ( "prompt".to_string(), Value::String(build_prompt_with_negative(prompt, negative_prompt)), ), ("n".to_string(), json!(candidate_count.clamp(1, 4))), - ("official_fallback".to_string(), Value::Bool(true)), ( "size".to_string(), Value::String(normalize_image_size(size)), @@ -185,7 +179,7 @@ pub(crate) fn build_openai_image_request_body( ]); if !reference_images.is_empty() { - body.insert("image_urls".to_string(), json!(reference_images)); + body.insert("image".to_string(), json!(reference_images)); } Value::Object(body) @@ -205,109 +199,16 @@ fn build_prompt_with_negative(prompt: &str, negative_prompt: Option<&str>) -> St fn normalize_image_size(size: &str) -> String { match size.trim() { - "1024*1024" | "1024x1024" | "1:1" => "1:1", - "1280*720" | "1280x720" | "1600*900" | "1600x900" | "16:9" => "16:9", + "1024*1024" | "1024x1024" | "1:1" => "1024x1024", + "1280*720" | "1280x720" | "1600*900" | "1600x900" | "16:9" + | "1536x1024" | "2048x1152" | "2k" => "1536x1024", + "1024*1536" | "1024x1536" | "9:16" => "1024x1536", value if !value.is_empty() => value, - _ => "1:1", + _ => "1024x1024", } .to_string() } -async fn wait_openai_generated_images( - http_client: &reqwest::Client, - settings: &OpenAiImageSettings, - task_id: &str, - candidate_count: u32, - failure_context: &str, -) -> Result { - let deadline = Instant::now() + Duration::from_millis(settings.request_timeout_ms); - sleep(Duration::from_secs(10)).await; - - while Instant::now() < deadline { - let poll_response = http_client - .get(format!("{}/tasks/{}", settings.base_url, task_id)) - .header( - header::AUTHORIZATION, - format!("Bearer {}", settings.api_key), - ) - .send() - .await - .map_err(|error| { - map_openai_image_request_error(format!( - "{failure_context}:查询图片生成任务失败:{error}" - )) - })?; - let poll_status = poll_response.status(); - let poll_text = poll_response.text().await.map_err(|error| { - map_openai_image_request_error(format!( - "{failure_context}:读取图片生成任务响应失败:{error}" - )) - })?; - if !poll_status.is_success() { - return Err(map_openai_image_upstream_error( - poll_status.as_u16(), - poll_text.as_str(), - failure_context, - )); - } - - let poll_json = parse_json_payload(poll_text.as_str(), failure_context)?; - let task_status = find_first_string_by_key(&poll_json.payload, "status") - .or_else(|| find_first_string_by_key(&poll_json.payload, "task_status")) - .unwrap_or_default() - .trim() - .to_ascii_lowercase(); - if matches!(task_status.as_str(), "completed" | "succeeded" | "success") { - let image_urls = extract_image_urls(&poll_json.payload); - if image_urls.is_empty() { - let b64_images = extract_b64_images(&poll_json.payload); - if b64_images.is_empty() { - return Err(AppError::from_status(StatusCode::BAD_GATEWAY).with_details( - json!({ - "provider": "apimart", - "message": format!("{failure_context}:任务成功但未返回图片"), - }), - )); - } - - let mut generated = - images_from_base64(task_id.to_string(), b64_images, candidate_count); - generated.actual_prompt = - find_first_string_by_key(&poll_json.payload, "actual_prompt"); - return Ok(generated); - } - - let mut generated = download_images_from_urls( - http_client, - task_id.to_string(), - image_urls, - candidate_count, - ) - .await?; - generated.actual_prompt = find_first_string_by_key(&poll_json.payload, "actual_prompt"); - return Ok(generated); - } - if matches!( - task_status.as_str(), - "failed" | "error" | "canceled" | "cancelled" | "unknown" - ) { - return Err(map_openai_image_upstream_error( - poll_status.as_u16(), - poll_text.as_str(), - failure_context, - )); - } - sleep(Duration::from_secs(3)).await; - } - - Err( - AppError::from_status(StatusCode::BAD_GATEWAY).with_details(json!({ - "provider": "apimart", - "message": format!("{failure_context}:图片生成超时或未返回图片地址"), - })), - ) -} - async fn download_images_from_urls( http_client: &reqwest::Client, task_id: String, @@ -377,7 +278,7 @@ pub(crate) async fn download_remote_image( if !status.is_success() { return Err( AppError::from_status(StatusCode::BAD_GATEWAY).with_details(json!({ - "provider": "apimart", + "provider": VECTOR_ENGINE_PROVIDER, "message": "下载生成图片失败", "status": status.as_u16(), })), @@ -400,7 +301,7 @@ fn parse_json_payload( .map(|payload| ParsedJsonPayload { payload }) .map_err(|error| { AppError::from_status(StatusCode::BAD_GATEWAY).with_details(json!({ - "provider": "apimart", + "provider": VECTOR_ENGINE_PROVIDER, "message": format!("{failure_context}:解析响应失败:{error}"), "rawExcerpt": truncate_raw(raw_text), })) @@ -409,7 +310,7 @@ fn parse_json_payload( fn map_openai_image_request_error(message: String) -> AppError { AppError::from_status(StatusCode::BAD_GATEWAY).with_details(json!({ - "provider": "apimart", + "provider": VECTOR_ENGINE_PROVIDER, "message": message, })) } @@ -421,14 +322,14 @@ fn map_openai_image_upstream_error( ) -> AppError { let message = parse_api_error_message(raw_text, failure_context); tracing::warn!( - provider = "apimart", + provider = VECTOR_ENGINE_PROVIDER, upstream_status, raw_excerpt = %truncate_raw(raw_text), message, - "APIMart 图片生成上游错误" + "VectorEngine 图片生成上游错误" ); AppError::from_status(StatusCode::BAD_GATEWAY).with_details(json!({ - "provider": "apimart", + "provider": VECTOR_ENGINE_PROVIDER, "message": message, "upstreamStatus": upstream_status, "rawExcerpt": truncate_raw(raw_text), @@ -516,10 +417,10 @@ fn find_first_string_by_key(value: &Value, target_key: &str) -> Option { results.into_iter().next() } -fn extract_task_id(payload: &Value) -> Option { - find_first_string_by_key(payload, "task_id") - .or_else(|| find_first_string_by_key(payload, "taskId")) - .or_else(|| find_first_string_by_key(payload, "id")) +fn extract_generation_id(payload: &Value) -> Option { + find_first_string_by_key(payload, "id") + .or_else(|| find_first_string_by_key(payload, "created")) + .or_else(|| find_first_string_by_key(payload, "request_id")) } fn extract_image_urls(payload: &Value) -> Vec { @@ -542,6 +443,14 @@ fn extract_b64_images(payload: &Value) -> Vec { values } +fn vector_engine_images_generation_url(settings: &OpenAiImageSettings) -> String { + if settings.base_url.ends_with("/v1") { + format!("{}/images/generations", settings.base_url) + } else { + format!("{}/v1/images/generations", settings.base_url) + } +} + fn normalize_downloaded_image_mime_type(content_type: &str) -> String { let mime_type = content_type .split(';') @@ -602,7 +511,7 @@ mod tests { use super::*; #[test] - fn gpt_image_2_request_normalizes_legacy_sizes_and_reference_images() { + fn gpt_image_2_request_uses_vector_engine_contract() { let body = build_openai_image_request_body( "雾海神殿", Some("文字,水印"), @@ -611,11 +520,11 @@ mod tests { &["data:image/png;base64,abcd".to_string()], ); - assert_eq!(body["model"], GPT_IMAGE_2_MODEL); - assert_eq!(body["size"], "16:9"); + assert_eq!(body["model"], VECTOR_ENGINE_GPT_IMAGE_2_MODEL); + assert_eq!(body["size"], "1536x1024"); assert_eq!(body["n"], 2); - assert_eq!(body["official_fallback"], true); - assert_eq!(body["image_urls"][0], "data:image/png;base64,abcd"); + assert!(body.get("official_fallback").is_none()); + assert_eq!(body["image"][0], "data:image/png;base64,abcd"); assert!(body["prompt"].as_str().unwrap_or_default().contains("避免")); } diff --git a/server-rs/crates/api-server/src/puzzle.rs b/server-rs/crates/api-server/src/puzzle.rs index 10a8fb72..17e39ef2 100644 --- a/server-rs/crates/api-server/src/puzzle.rs +++ b/server-rs/crates/api-server/src/puzzle.rs @@ -1,6 +1,6 @@ use std::{ collections::BTreeMap, - time::{Duration, Instant, SystemTime, UNIX_EPOCH}, + time::{Duration, SystemTime, UNIX_EPOCH}, }; use axum::{ @@ -68,7 +68,6 @@ use spacetime_client::{ PuzzleWorkUpsertRecordInput, SpacetimeClientError, }; use std::convert::Infallible; -use tokio::time::sleep; use crate::{ ai_generation_drafts::{AiGenerationDraftContext, AiGenerationDraftWriter}, @@ -80,6 +79,7 @@ use crate::{ auth::AuthenticatedAccessToken, http_error::AppError, llm_model_routing::{CREATION_TEMPLATE_LLM_MODEL, PUZZLE_LEVEL_NAME_VISION_LLM_MODEL}, + openai_image_generation::VECTOR_ENGINE_GPT_IMAGE_2_MODEL, platform_errors::map_oss_error, prompt::puzzle::{ draft::{ @@ -112,8 +112,8 @@ const PUZZLE_IMAGE_GENERATION_POINTS_COST: u64 = 2; const PUZZLE_ENTITY_KIND: &str = "puzzle_work"; #[cfg(test)] const PUZZLE_GENERATED_IMAGE_SIZE: &str = "1024*1024"; -const PUZZLE_APIMART_GENERATED_IMAGE_SIZE: &str = "1:1"; -const PUZZLE_APIMART_GEMINI_RESOLUTION: &str = "1K"; +const PUZZLE_VECTOR_ENGINE_GENERATED_IMAGE_SIZE: &str = "1024x1024"; +const VECTOR_ENGINE_PROVIDER: &str = "vector-engine"; const PUZZLE_LEVEL_NAME_VISION_IMAGE_MAX_SIDE: u32 = 768; pub async fn create_puzzle_agent_session( @@ -941,7 +941,7 @@ pub async fn execute_puzzle_agent_action( Err(error) if should_skip_asset_operation_billing_for_connectivity(&error) => { - // 中文注释:APIMart/OSS 已生成真实图片时,Maincloud 短暂 503 不应让前端看不到本次图片;先返回内存合成快照,待后续操作恢复正常持久化。 + // 中文注释:VectorEngine/OSS 已生成真实图片时,SpacetimeDB 短暂 503 不应让前端看不到本次图片;先返回内存合成快照,待后续操作恢复正常持久化。 tracing::warn!( provider = PUZZLE_AGENT_API_BASE_PROVIDER, session_id = %session.session_id, @@ -3172,7 +3172,7 @@ async fn compile_puzzle_draft_with_initial_cover( .map(|session| (session, false)) .or_else(|error| { if is_spacetimedb_connectivity_app_error(&error) { - // 中文注释:首图已落 OSS 时,Maincloud 短暂不可用先返回本地快照,避免整次 APIMart 生图被判失败。 + // 中文注释:首图已落 OSS 时,SpacetimeDB 短暂不可用先返回本地快照,避免整次 VectorEngine 生图被判失败。 tracing::warn!( provider = PUZZLE_AGENT_API_BASE_PROVIDER, session_id = %compiled_session.session_id, @@ -3271,7 +3271,7 @@ async fn compile_puzzle_draft_with_uploaded_cover( &target_level.picture_description, &draft.summary, ); - // 中文注释:关闭 AI 重绘时不请求 APIMart,也不进入光点扣费流程;上传图直接成为首关正式图候选。 + // 中文注释:关闭 AI 重绘时不请求 VectorEngine,也不进入光点扣费流程;上传图直接成为首关正式图候选。 let candidate_id = format!( "{}-candidate-{}", compiled_session.session_id, @@ -3874,18 +3874,23 @@ fn is_missing_puzzle_form_draft_procedure_error(error: &SpacetimeClientError) -> fn map_puzzle_compile_error(error: SpacetimeClientError) -> AppError { let message = error.to_string(); - let provider = if message.contains("APIMart") + let provider = if message.contains("VectorEngine") + || message.contains("vector-engine") + || message.contains("VECTOR_ENGINE") + || message.contains("APIMart") || message.contains("apimart") || message.contains("APIMART") { - "apimart" + VECTOR_ENGINE_PROVIDER } else if message.contains("OSS") || message.contains("oss") || message.contains("参考图") { "puzzle-assets" } else { "spacetimedb" }; - let status = if provider == "apimart" - && (message.contains("APIMART_API_KEY") + let status = if provider == VECTOR_ENGINE_PROVIDER + && (message.contains("VECTOR_ENGINE_API_KEY") + || message.contains("VECTOR_ENGINE_BASE_URL") + || message.contains("APIMART_API_KEY") || message.contains("APIMART_BASE_URL") || message.contains("未配置")) { @@ -3899,6 +3904,9 @@ fn map_puzzle_compile_error(error: SpacetimeClientError) -> AppError { } else if matches!(error, SpacetimeClientError::Runtime(_)) && (message.contains("生成") || message.contains("上游") + || message.contains("VectorEngine") + || message.contains("vector-engine") + || message.contains("VECTOR_ENGINE") || message.contains("APIMart") || message.contains("apimart") || message.contains("APIMART") diff --git a/server-rs/crates/api-server/src/state.rs b/server-rs/crates/api-server/src/state.rs index 47be20e9..35b785ed 100644 --- a/server-rs/crates/api-server/src/state.rs +++ b/server-rs/crates/api-server/src/state.rs @@ -784,7 +784,7 @@ fn build_creative_agent_gpt5_client( config.apimart_base_url.clone(), api_key.to_string(), platform_agent::CREATIVE_AGENT_GPT5_MODEL.to_string(), - config.apimart_image_request_timeout_ms, + config.llm_request_timeout_ms, 0, config.llm_retry_backoff_ms, )? diff --git a/server-rs/crates/shared-contracts/src/hyper3d.rs b/server-rs/crates/shared-contracts/src/hyper3d.rs new file mode 100644 index 00000000..cd663eca --- /dev/null +++ b/server-rs/crates/shared-contracts/src/hyper3d.rs @@ -0,0 +1,169 @@ +use serde::{Deserialize, Serialize}; +use serde_json::Value; + +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] +#[serde(rename_all = "kebab-case")] +pub enum Hyper3dGenerationMode { + TextToModel, + ImageToModel, +} + +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct Hyper3dTextToModelRequest { + pub prompt: String, + #[serde(default)] + pub negative_prompt: Option, + #[serde(default)] + pub seed: Option, + #[serde(default)] + pub geometry_file_format: Option, + #[serde(default)] + pub material: Option, + #[serde(default)] + pub quality: Option, + #[serde(default)] + pub mesh_mode: Option, + #[serde(default)] + pub addons: Vec, + #[serde(default)] + pub bbox_condition: Option>, + #[serde(default)] + pub preview_render: Option, +} + +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct Hyper3dImageToModelRequest { + #[serde(default)] + pub image_data_urls: Vec, + #[serde(default)] + pub image_urls: Vec, + #[serde(default)] + pub prompt: Option, + #[serde(default)] + pub condition_mode: Option, + #[serde(default)] + pub seed: Option, + #[serde(default)] + pub geometry_file_format: Option, + #[serde(default)] + pub material: Option, + #[serde(default)] + pub quality: Option, + #[serde(default)] + pub mesh_mode: Option, + #[serde(default)] + pub addons: Vec, + #[serde(default)] + pub bbox_condition: Option>, + #[serde(default)] + pub preview_render: Option, +} + +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] +#[serde(rename_all = "camelCase")] +pub struct Hyper3dTaskSubmitResponse { + pub ok: bool, + pub provider: String, + pub mode: Hyper3dGenerationMode, + pub task_uuid: String, + pub subscription_key: String, + pub job_uuids: Vec, + pub message: Option, + pub tier: String, +} + +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] +#[serde(rename_all = "camelCase")] +pub struct Hyper3dTaskStatusRequest { + pub subscription_key: String, +} + +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct Hyper3dTaskStatusResponse { + pub ok: bool, + pub provider: String, + pub status: String, + #[serde(default)] + pub jobs: Vec, + pub raw: Value, +} + +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct Hyper3dJobStatusPayload { + pub uuid: Option, + pub status: String, + pub progress: Option, + pub message: Option, +} + +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] +#[serde(rename_all = "camelCase")] +pub struct Hyper3dDownloadRequest { + pub task_uuid: String, +} + +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct Hyper3dDownloadResponse { + pub ok: bool, + pub provider: String, + #[serde(default)] + pub files: Vec, + pub raw: Value, +} + +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] +#[serde(rename_all = "camelCase")] +pub struct Hyper3dDownloadFilePayload { + pub name: String, + pub url: String, +} + +#[cfg(test)] +mod tests { + use super::*; + use serde_json::json; + + #[test] + fn text_to_model_request_uses_camel_case_fields() { + let payload = serde_json::to_value(Hyper3dTextToModelRequest { + prompt: "低多边形宝箱".to_string(), + negative_prompt: Some("文字".to_string()), + seed: Some(42), + geometry_file_format: Some("glb".to_string()), + material: Some("PBR".to_string()), + quality: Some("medium".to_string()), + mesh_mode: Some("Quad".to_string()), + addons: vec!["HighPack".to_string()], + bbox_condition: Some(vec![1.0, 1.0, 1.0]), + preview_render: Some(true), + }) + .expect("request should serialize"); + + assert_eq!(payload["geometryFileFormat"], json!("glb")); + assert_eq!(payload["meshMode"], json!("Quad")); + assert_eq!(payload["bboxCondition"], json!([1.0, 1.0, 1.0])); + } + + #[test] + fn submit_response_keeps_mode_as_kebab_case() { + let payload = serde_json::to_value(Hyper3dTaskSubmitResponse { + ok: true, + provider: "hyper3d-rodin".to_string(), + mode: Hyper3dGenerationMode::ImageToModel, + task_uuid: "task-1".to_string(), + subscription_key: "sub-1".to_string(), + job_uuids: vec!["job-1".to_string()], + message: Some("submitted".to_string()), + tier: "Gen-2".to_string(), + }) + .expect("response should serialize"); + + assert_eq!(payload["mode"], json!("image-to-model")); + assert_eq!(payload["subscriptionKey"], json!("sub-1")); + } +} diff --git a/server-rs/crates/shared-contracts/src/lib.rs b/server-rs/crates/shared-contracts/src/lib.rs index 26f69f2c..49a4da77 100644 --- a/server-rs/crates/shared-contracts/src/lib.rs +++ b/server-rs/crates/shared-contracts/src/lib.rs @@ -7,6 +7,7 @@ pub mod big_fish; pub mod big_fish_works; pub mod creation_agent_document_input; pub mod creative_agent; +pub mod hyper3d; pub mod llm; pub mod match3d_agent; pub mod match3d_runtime; diff --git a/src/components/platform-entry/PlatformEntryFlowShellImpl.tsx b/src/components/platform-entry/PlatformEntryFlowShellImpl.tsx index 0bb91bb2..1ad96eb7 100644 --- a/src/components/platform-entry/PlatformEntryFlowShellImpl.tsx +++ b/src/components/platform-entry/PlatformEntryFlowShellImpl.tsx @@ -319,6 +319,7 @@ type PuzzleRuntimeReturnStage = | 'puzzle-gallery-detail' | 'work-detail' | 'platform'; +type PuzzleRuntimeAuthMode = 'default' | 'isolated'; type PuzzleOnboardingPhase = 'input' | 'generating' | 'generated'; @@ -387,6 +388,8 @@ const RECOMMEND_RUNTIME_BACKGROUND_AUTH_OPTIONS = { notifyAuthStateChange: false, clearAuthOnUnauthorized: false, }; +const PUBLIC_PUZZLE_RUNTIME_AUTH_OPTIONS = + RECOMMEND_RUNTIME_BACKGROUND_AUTH_OPTIONS; function getPlatformPublicGalleryEntryTime(entry: PlatformPublicGalleryCard) { const rawTime = entry.publishedAt ?? entry.updatedAt; @@ -1568,6 +1571,8 @@ export function PlatformEntryFlowShellImpl({ useState(null); const [puzzleRuntimeReturnStage, setPuzzleRuntimeReturnStage] = useState('puzzle-gallery-detail'); + const [puzzleRuntimeAuthMode, setPuzzleRuntimeAuthMode] = + useState('default'); const [isPuzzleLeaderboardBusy, setIsPuzzleLeaderboardBusy] = useState(false); const submittedPuzzleLeaderboardKeysRef = useRef(new Set()); const [puzzleRun, setPuzzleRun] = useState(null); @@ -2230,6 +2235,7 @@ export function PlatformEntryFlowShellImpl({ setPuzzleWorks((current) => [response.item, ...current]); setSelectedPuzzleDetail(null); setPuzzleRun(null); + setPuzzleRuntimeAuthMode('default'); setPuzzleOnboardingDraft(null); setPuzzleOnboardingPrompt(''); setPuzzleOnboardingPhase('input'); @@ -2268,10 +2274,11 @@ export function PlatformEntryFlowShellImpl({ setPuzzleOnboardingPhase('input'); setPuzzleOnboardingError(null); setPuzzleRun(null); + setPuzzleRuntimeAuthMode('default'); setSelectedPuzzleDetail(null); - platformBootstrap.setPlatformTab('home'); + platformBootstrap.setPlatformTab(authUi?.user ? 'home' : 'category'); setSelectionStage('platform'); - }, [platformBootstrap, setSelectionStage]); + }, [authUi?.user, platformBootstrap, setSelectionStage]); useEffect(() => { if ( @@ -2321,6 +2328,7 @@ export function PlatformEntryFlowShellImpl({ markPuzzleOnboardingSeen(); window.setTimeout(() => { setPuzzleRun(startLocalPuzzleRun(item)); + setPuzzleRuntimeAuthMode('default'); setPuzzleRuntimeReturnStage('platform'); setSelectionStage('puzzle-runtime'); }, PUZZLE_ONBOARDING_GENERATED_DELAY_MS); @@ -2953,6 +2961,7 @@ export function PlatformEntryFlowShellImpl({ const openPuzzleAgentWorkspace = useCallback(async () => { setPuzzleRun(null); + setPuzzleRuntimeAuthMode('default'); setPuzzleOperation(null); setPuzzleGenerationState(null); setPuzzleFormDraftPayload(null); @@ -2997,6 +3006,7 @@ export function PlatformEntryFlowShellImpl({ } setPuzzleRun(null); + setPuzzleRuntimeAuthMode('default'); setPuzzleOperation(null); setPuzzleGenerationState(null); setPuzzleFormDraftPayload(null); @@ -3137,6 +3147,7 @@ export function PlatformEntryFlowShellImpl({ setSelectedPuzzleDetail(null); setPuzzleRuntimeReturnStage('puzzle-gallery-detail'); setPuzzleRun(null); + setPuzzleRuntimeAuthMode('default'); setPuzzleGenerationState(null); setIsPuzzleNextLevelGenerating(false); setPuzzleShelfError(null); @@ -3283,6 +3294,7 @@ export function PlatformEntryFlowShellImpl({ const leavePuzzleFlow = useCallback(() => { setPuzzleOperation(null); setPuzzleRun(null); + setPuzzleRuntimeAuthMode('default'); setPuzzleGenerationState(null); setIsPuzzleNextLevelGenerating(false); setActiveCreativeAgentSessionId(null); @@ -3773,6 +3785,7 @@ export function PlatformEntryFlowShellImpl({ puzzleFlow.setSession(response.session); setPuzzleOperation(null); setPuzzleRun(null); + setPuzzleRuntimeAuthMode('default'); enterCreateTab(); setActiveCreativeAgentSessionId(creativeAgentSession.sessionId); setCreativeDraftEditError(null); @@ -4007,7 +4020,7 @@ export function PlatformEntryFlowShellImpl({ detailItem?: PuzzleWorkSummary, mirrorErrorToPublicDetail = false, levelId?: string | null, - options: { embedded?: boolean } = {}, + options: { embedded?: boolean; authMode?: PuzzleRuntimeAuthMode } = {}, ) => { if (isPuzzleBusy) { return false; @@ -4023,14 +4036,19 @@ export function PlatformEntryFlowShellImpl({ profileId: item.profileId, levelId: levelId ?? null, }; - const { run } = options.embedded - ? await startPuzzleRun( - startRunPayload, - RECOMMEND_RUNTIME_BACKGROUND_AUTH_OPTIONS, - ) - : await startPuzzleRun(startRunPayload); + const authMode = options.embedded + ? 'isolated' + : (options.authMode ?? 'default'); + const { run } = + authMode === 'isolated' + ? await startPuzzleRun( + startRunPayload, + PUBLIC_PUZZLE_RUNTIME_AUTH_OPTIONS, + ) + : await startPuzzleRun(startRunPayload); setSelectedPuzzleDetail(item); setPuzzleRun(run); + setPuzzleRuntimeAuthMode(authMode); setPuzzleRuntimeReturnStage(returnStage); if (!options.embedded) { setSelectionStage('puzzle-runtime'); @@ -4255,6 +4273,7 @@ export function PlatformEntryFlowShellImpl({ const run = startLocalPuzzleRun(item); setSelectedPuzzleDetail(item); setPuzzleRun(run); + setPuzzleRuntimeAuthMode('default'); setPuzzleRuntimeReturnStage('puzzle-result'); setSelectionStage('puzzle-runtime'); } catch (error) { @@ -4496,10 +4515,17 @@ export function PlatformEntryFlowShellImpl({ : await getPuzzleGalleryDetail(currentLevel.profileId).then( (response) => response.item, ); - const { run } = await startPuzzleRun({ + const startRunPayload = { profileId: currentLevel.profileId, levelId: resolvePuzzleRestartLevelId(currentRun, detailItem), - }); + }; + const { run } = + puzzleRuntimeAuthMode === 'isolated' + ? await startPuzzleRun( + startRunPayload, + PUBLIC_PUZZLE_RUNTIME_AUTH_OPTIONS, + ) + : await startPuzzleRun(startRunPayload); setSelectedPuzzleDetail(detailItem); puzzleRunRef.current = run; setPuzzleRun(run); @@ -4513,6 +4539,7 @@ export function PlatformEntryFlowShellImpl({ }, [ isPuzzleBusy, puzzleRun, + puzzleRuntimeAuthMode, resolvePuzzleErrorMessage, selectedPuzzleDetail, setIsPuzzleBusy, @@ -4618,7 +4645,16 @@ export function PlatformEntryFlowShellImpl({ return; } - void submitPuzzleLeaderboard(puzzleRun.runId, payload) + const submitLeaderboardPromise = + puzzleRuntimeAuthMode === 'isolated' + ? submitPuzzleLeaderboard( + puzzleRun.runId, + payload, + PUBLIC_PUZZLE_RUNTIME_AUTH_OPTIONS, + ) + : submitPuzzleLeaderboard(puzzleRun.runId, payload); + + void submitLeaderboardPromise .then(({ run }) => { setPuzzleRun((currentRun) => { if (!currentRun) { @@ -4641,6 +4677,7 @@ export function PlatformEntryFlowShellImpl({ authUi?.user?.displayName, platformBootstrap, puzzleRun, + puzzleRuntimeAuthMode, resolvePuzzleErrorMessage, setPuzzleError, ]); @@ -4678,10 +4715,20 @@ export function PlatformEntryFlowShellImpl({ : getPuzzleGalleryDetail(targetProfileId).then( (response) => response.item, ); + const advancePromise = + puzzleRuntimeAuthMode === 'isolated' + ? advancePuzzleNextLevel( + puzzleRun.runId, + { + targetProfileId, + }, + PUBLIC_PUZZLE_RUNTIME_AUTH_OPTIONS, + ) + : advancePuzzleNextLevel(puzzleRun.runId, { + targetProfileId, + }); const [{ run }, item] = await Promise.all([ - advancePuzzleNextLevel(puzzleRun.runId, { - targetProfileId, - }), + advancePromise, itemPromise, ]); setSelectedPuzzleDetail(item); @@ -4695,7 +4742,14 @@ export function PlatformEntryFlowShellImpl({ return; } - const { run } = await advancePuzzleNextLevel(puzzleRun.runId); + const { run } = + puzzleRuntimeAuthMode === 'isolated' + ? await advancePuzzleNextLevel( + puzzleRun.runId, + {}, + PUBLIC_PUZZLE_RUNTIME_AUTH_OPTIONS, + ) + : await advancePuzzleNextLevel(puzzleRun.runId); setPuzzleRun(run); } catch (error) { setPuzzleError(resolvePuzzleErrorMessage(error, '准备下一关失败。')); @@ -4708,6 +4762,7 @@ export function PlatformEntryFlowShellImpl({ isPuzzleBusy, isPuzzleLeaderboardBusy, puzzleRun, + puzzleRuntimeAuthMode, resolvePuzzleErrorMessage, selectedPuzzleDetail, setIsPuzzleBusy, @@ -4732,6 +4787,7 @@ export function PlatformEntryFlowShellImpl({ puzzleFlow.setSession(response.session); setPuzzleOperation(null); setPuzzleRun(null); + setPuzzleRuntimeAuthMode('default'); enterCreateTab(); setSelectionStage('puzzle-result'); }) @@ -4777,6 +4833,7 @@ export function PlatformEntryFlowShellImpl({ : currentRun; puzzleRunRef.current = null; setPuzzleRun(null); + setPuzzleRuntimeAuthMode('default'); setActiveRecommendRuntimeKind(null); if (closedRun.currentLevel) { @@ -5596,34 +5653,36 @@ export function PlatformEntryFlowShellImpl({ const openRecommendGalleryDetail = useCallback( (entry: PlatformPublicGalleryCard) => { - if (isBigFishGalleryEntry(entry)) { - openPublicWorkDetail(entry); - return; - } + runProtectedAction(() => { + if (isBigFishGalleryEntry(entry)) { + openPublicWorkDetail(entry); + return; + } - if (isPuzzleGalleryEntry(entry)) { - void openPuzzlePublicWorkDetail(entry.profileId, { - tab: platformBootstrap.platformTab, - }); - return; - } + if (isPuzzleGalleryEntry(entry)) { + void openPuzzlePublicWorkDetail(entry.profileId, { + tab: platformBootstrap.platformTab, + }); + return; + } - if (isMatch3DGalleryEntry(entry)) { - openPublicWorkDetail(entry); - return; - } + if (isMatch3DGalleryEntry(entry)) { + openPublicWorkDetail(entry); + return; + } - if (isSquareHoleGalleryEntry(entry)) { - openPublicWorkDetail(entry); - return; - } + if (isSquareHoleGalleryEntry(entry)) { + openPublicWorkDetail(entry); + return; + } - if (isVisualNovelGalleryEntry(entry)) { - void openVisualNovelPublicWorkDetail(entry.profileId); - return; - } + if (isVisualNovelGalleryEntry(entry)) { + void openVisualNovelPublicWorkDetail(entry.profileId); + return; + } - void openRpgPublicWorkDetail(entry); + void openRpgPublicWorkDetail(entry); + }); }, [ openPuzzlePublicWorkDetail, @@ -5631,6 +5690,7 @@ export function PlatformEntryFlowShellImpl({ openRpgPublicWorkDetail, openVisualNovelPublicWorkDetail, platformBootstrap.platformTab, + runProtectedAction, ], ); const openPuzzleDetail = useCallback( @@ -5673,6 +5733,7 @@ export function PlatformEntryFlowShellImpl({ async (item: PuzzleWorkSummary) => { setPuzzleOperation(null); setPuzzleRun(null); + setPuzzleRuntimeAuthMode('default'); setSelectedPuzzleDetail(null); if (!item.sourceSessionId?.trim()) { if (item.publicationStatus === 'published') { @@ -5960,6 +6021,8 @@ export function PlatformEntryFlowShellImpl({ 'work-detail', work, true, + null, + { authMode: 'isolated' }, ); return; } @@ -6462,6 +6525,7 @@ export function PlatformEntryFlowShellImpl({ if ( selectionStage !== 'platform' || platformBootstrap.platformTab !== 'home' || + !platformBootstrap.isAuthenticated || !platformBootstrap.canReadProtectedData || platformBootstrap.isLoadingPlatform ) { @@ -6494,6 +6558,7 @@ export function PlatformEntryFlowShellImpl({ isStartingRecommendEntry, platformBootstrap.canReadProtectedData, platformBootstrap.isLoadingPlatform, + platformBootstrap.isAuthenticated, platformBootstrap.platformTab, recommendRuntimeEntries, selectRecommendRuntimeEntry, @@ -8594,6 +8659,9 @@ export function PlatformEntryFlowShellImpl({ selectedPuzzleDetail.profileId, 'puzzle-gallery-detail', selectedPuzzleDetail, + false, + null, + { authMode: 'isolated' }, ); }} /> diff --git a/src/components/puzzle-result/PuzzleResultView.test.tsx b/src/components/puzzle-result/PuzzleResultView.test.tsx index 063bd17a..afd0f13e 100644 --- a/src/components/puzzle-result/PuzzleResultView.test.tsx +++ b/src/components/puzzle-result/PuzzleResultView.test.tsx @@ -442,6 +442,40 @@ describe('PuzzleResultView', () => { ]); }); + test('keeps publish dialog open and shows backend publish error', () => { + const onExecuteAction = vi.fn(); + + const { rerender } = render( + {}} + onExecuteAction={onExecuteAction} + />, + ); + + fireEvent.click(screen.getByRole('button', { name: /发布/u })); + const dialog = screen.getByRole('dialog', { name: '发布拼图作品' }); + fireEvent.click( + within(dialog).getByRole('button', { name: '发布到广场' }), + ); + + rerender( + {}} + onExecuteAction={onExecuteAction} + />, + ); + + const publishDialog = screen.getByRole('dialog', { + name: '发布拼图作品', + }); + expect(publishDialog).toBeTruthy(); + expect(within(publishDialog).getByText('光点余额不足')).toBeTruthy(); + }); + test('generates six tags after work title and description are filled', () => { const onExecuteAction = vi.fn(); diff --git a/src/components/puzzle-result/PuzzleResultView.tsx b/src/components/puzzle-result/PuzzleResultView.tsx index 2ed3bdc1..8e2ecf58 100644 --- a/src/components/puzzle-result/PuzzleResultView.tsx +++ b/src/components/puzzle-result/PuzzleResultView.tsx @@ -1014,6 +1014,7 @@ function PuzzleLevelDetailDialog({ } function PuzzlePublishDialog({ + actionError, blockers, editState, imageRefreshKey, @@ -1022,6 +1023,7 @@ function PuzzlePublishDialog({ onClose, onPublish, }: { + actionError: string | null; blockers: string[]; editState: DraftEditState; imageRefreshKey: string; @@ -1076,7 +1078,11 @@ function PuzzlePublishDialog({

发布检查
- {publishReady ? ( + {actionError ? ( +
+ {actionError} +
+ ) : publishReady ? (
当前作品已满足发布条件。
@@ -1361,6 +1367,7 @@ function PuzzleWorkInfoTab({ } function PuzzleResultActionBar({ + actionError, editState, imageRefreshKey, isBusy, @@ -1368,6 +1375,7 @@ function PuzzleResultActionBar({ publishBlockers, onPublish, }: { + actionError: string | null; editState: DraftEditState; imageRefreshKey: string; isBusy: boolean; @@ -1376,12 +1384,21 @@ function PuzzleResultActionBar({ onPublish: () => void; }) { const [showPublishDialog, setShowPublishDialog] = useState(false); + const [hasAttemptedPublish, setHasAttemptedPublish] = useState(false); + + const closePublishDialog = () => { + setHasAttemptedPublish(false); + setShowPublishDialog(false); + }; return (
@@ -1671,6 +1692,7 @@ export function PuzzleResultView({ ) : null} { expect(screen.getByTestId('puzzle-board')).toBeTruthy(); }); - expect(startPuzzleRun).toHaveBeenCalledWith({ - profileId: 'puzzle-profile-public-1', - levelId: null, - }); + expect(startPuzzleRun).toHaveBeenCalledWith( + { + profileId: 'puzzle-profile-public-1', + levelId: null, + }, + ISOLATED_RUNTIME_AUTH_OPTIONS, + ); await user.click(document.querySelector('[data-piece-id="piece-0"]')!); await user.click(document.querySelector('[data-piece-id="piece-1"]')!); @@ -3695,6 +3700,7 @@ test('formal puzzle runtime uses frontend move merge logic and backend leaderboa elapsedMs: 18_000, nickname: '测试玩家', }, + ISOLATED_RUNTIME_AUTH_OPTIONS, ); }); @@ -3711,6 +3717,8 @@ test('formal puzzle runtime uses frontend move merge logic and backend leaderboa await waitFor(() => { expect(advancePuzzleNextLevel).toHaveBeenCalledWith( clearedFirstLevel.runId, + {}, + ISOLATED_RUNTIME_AUTH_OPTIONS, ); }); expect( @@ -3875,6 +3883,7 @@ test('formal puzzle similar work keeps current run level progression', async () expect(advancePuzzleNextLevel).toHaveBeenCalledWith( clearedThirdLevel.runId, { targetProfileId: 'puzzle-profile-similar-2' }, + ISOLATED_RUNTIME_AUTH_OPTIONS, ); }); expect(startPuzzleRun).not.toHaveBeenCalled(); diff --git a/src/components/rpg-entry/RpgEntryHomeView.recharge.test.tsx b/src/components/rpg-entry/RpgEntryHomeView.recharge.test.tsx index ce891a8a..aef47b45 100644 --- a/src/components/rpg-entry/RpgEntryHomeView.recharge.test.tsx +++ b/src/components/rpg-entry/RpgEntryHomeView.recharge.test.tsx @@ -310,16 +310,6 @@ const originalMatchMedia = window.matchMedia; const originalRequestAnimationFrame = window.requestAnimationFrame; const originalCancelAnimationFrame = window.cancelAnimationFrame; -function dispatchClientYPointerEvent( - target: HTMLElement, - type: string, - clientY: number, -) { - const event = new Event(type, { bubbles: true, cancelable: true }); - Object.assign(event, { clientY }); - target.dispatchEvent(event); -} - const puzzlePublicEntry = { sourceType: 'puzzle', workId: 'puzzle-work-public-1', @@ -534,6 +524,7 @@ function renderLoggedOutHomeView( | 'onSelectPreviousRecommendEntry' > > = {}, + activeTab: RpgEntryHomeViewProps['activeTab'] = 'home', ) { return render( > = {}, ) { + const authSpies = { + openLoginModal: vi.fn(), + }; + function StatefulLoggedOutHomeView() { const [activeTab, setActiveTab] = - useState('home'); + useState('category'); return ( } + recommendRuntimeContent={ + overrides.recommendRuntimeContent ?? ( +
+ ) + } + activeRecommendEntryKey={overrides.activeRecommendEntryKey} + onSelectNextRecommendEntry={overrides.onSelectNextRecommendEntry} + onSelectPreviousRecommendEntry={overrides.onSelectPreviousRecommendEntry} onOpenLibraryDetail={vi.fn()} onSearchPublicCode={overrides.onSearchPublicCode ?? vi.fn()} /> @@ -659,7 +665,10 @@ function renderStatefulLoggedOutHomeView( ); } - return render(); + return { + ...render(), + openLoginModal: authSpies.openLoginModal, + }; } afterEach(() => { @@ -1111,35 +1120,80 @@ test('public gallery cards hide work code until detail is opened', async () => { expect(onOpenGalleryDetail).toHaveBeenCalledWith(puzzlePublicEntry); }); -test('mobile recommend page renders runtime viewport without bottom work cards', () => { +test('logged out mobile shell defaults to discover tab', () => { + const { container } = renderStatefulLoggedOutHomeView({ + latestEntries: [puzzlePublicEntry], + }); + + const activePanel = container.querySelector('.platform-tab-panel--active'); + expect(activePanel?.id).toBe('platform-tab-panel-category'); + expect(screen.getByPlaceholderText('搜索作品号、名称、作者、描述')).toBeTruthy(); +}); + +test('logged out recommend tab opens login modal and shows cover only', async () => { + const user = userEvent.setup(); + const { container, openLoginModal } = renderStatefulLoggedOutHomeView({ + latestEntries: [puzzlePublicEntry], + activeRecommendEntryKey: 'puzzle:user-2:puzzle-profile-public-1', + }); + const bottomNav = container.querySelector('.platform-bottom-nav'); + if (!bottomNav) { + throw new Error('缺少底部导航'); + } + + await user.click( + within(bottomNav as HTMLElement).getByRole('button', { name: '推荐' }), + ); + + expect(openLoginModal).toHaveBeenCalledTimes(1); + expect(container.querySelector('.platform-recommend-cover-only')).toBeTruthy(); + expect(screen.queryByTestId('recommend-runtime')).toBeNull(); + expect(screen.queryByLabelText('奇幻拼图 作品信息')).toBeNull(); + expect(screen.getAllByText('奇幻拼图').length).toBeGreaterThan(0); +}); + +test('logged out recommend cover opens login modal again', async () => { + const user = userEvent.setup(); const onOpenGalleryDetail = vi.fn(); - renderLoggedOutHomeView(vi.fn(), { + const { openLoginModal } = renderStatefulLoggedOutHomeView({ latestEntries: [puzzlePublicEntry], activeRecommendEntryKey: 'puzzle:user-2:puzzle-profile-public-1', onOpenGalleryDetail, }); + const bottomNav = document.querySelector('.platform-bottom-nav'); + if (!bottomNav) { + throw new Error('缺少底部导航'); + } - expect(screen.getByTestId('recommend-runtime')).toBeTruthy(); - const runtimePanel = document.querySelector('.platform-recommend-runtime-panel'); - expect(runtimePanel).toBeTruthy(); - expect(runtimePanel?.className).not.toContain('bg-black'); - expect(screen.queryByText('一张用于公开分享的拼图作品。')).toBeNull(); + await user.click( + within(bottomNav as HTMLElement).getByRole('button', { name: '推荐' }), + ); + await user.click(screen.getByRole('button', { name: /登录后游玩 奇幻拼图/u })); + + expect(openLoginModal).toHaveBeenCalledTimes(2); + expect(openLoginModal).toHaveBeenLastCalledWith(expect.any(Function)); + expect(onOpenGalleryDetail).not.toHaveBeenCalled(); +}); + +test('logged out mobile recommend page renders cover instead of runtime', () => { + const onOpenGalleryDetail = vi.fn(); + renderLoggedOutHomeView( + vi.fn(), + { + latestEntries: [puzzlePublicEntry], + activeRecommendEntryKey: 'puzzle:user-2:puzzle-profile-public-1', + onOpenGalleryDetail, + }, + 'home', + ); + + expect(screen.queryByTestId('recommend-runtime')).toBeNull(); + expect(document.querySelector('.platform-recommend-cover-only')).toBeTruthy(); expect( document.querySelector('.platform-public-work-card__cover'), ).toBeNull(); - expect(screen.getByText('拼图玩家')).toBeTruthy(); expect(screen.getAllByText('奇幻拼图').length).toBeGreaterThan(0); - expect(screen.getAllByText('20').length).toBeGreaterThan(0); - expect(screen.getAllByText('12').length).toBeGreaterThan(0); - expect(screen.queryByRole('button', { name: '切换到 奇幻拼图' })).toBeNull(); - expect( - screen.queryByRole('button', { name: '查看 奇幻拼图 详情' }), - ).toBeNull(); - expect( - screen.queryByRole('button', { name: '打开 奇幻拼图 详情' }), - ).toBeNull(); - expect(document.querySelector('.platform-recommend-switcher')).toBeNull(); - fireEvent.click(screen.getByLabelText('奇幻拼图 作品信息')); + fireEvent.click(screen.getByRole('button', { name: /登录后游玩 奇幻拼图/u })); expect(onOpenGalleryDetail).not.toHaveBeenCalled(); }); @@ -1151,38 +1205,15 @@ test('mobile recommend loading state is themed instead of hardcoded black', () = recommendRuntimeContent: null, }); - const loadingState = screen.getByText('加载中...'); - expect(loadingState.className).toContain('platform-recommend-runtime-state'); - expect(loadingState.className).not.toContain('bg-black'); + expect(document.querySelector('.platform-recommend-cover-only')).toBeTruthy(); }); -test('mobile recommend meta swipes between public works', () => { - const onSelectNextRecommendEntry = vi.fn(); - const onSelectPreviousRecommendEntry = vi.fn(); - - renderLoggedOutHomeView(vi.fn(), { - latestEntries: [puzzlePublicEntry], - activeRecommendEntryKey: 'puzzle:user-2:puzzle-profile-public-1', - onSelectNextRecommendEntry, - onSelectPreviousRecommendEntry, - }); - - const meta = screen.getByLabelText('奇幻拼图 作品信息'); - dispatchClientYPointerEvent(meta, 'pointerdown', 240); - dispatchClientYPointerEvent(meta, 'pointerup', 180); - expect(onSelectNextRecommendEntry).toHaveBeenCalledTimes(1); - expect(onSelectPreviousRecommendEntry).not.toHaveBeenCalled(); - - dispatchClientYPointerEvent(meta, 'pointerdown', 180); - dispatchClientYPointerEvent(meta, 'pointerup', 240); - expect(onSelectPreviousRecommendEntry).toHaveBeenCalledTimes(1); -}); - -test('active recommend bottom tab selects next work instead of navigating', async () => { +test('logged out active recommend bottom tab selects next work without login', async () => { const user = userEvent.setup(); const onSelectNextRecommendEntry = vi.fn(); + const openLoginModal = vi.fn(); - renderLoggedOutHomeView(vi.fn(), { + renderLoggedOutHomeView(openLoginModal, { latestEntries: [puzzlePublicEntry], activeRecommendEntryKey: 'puzzle:user-2:puzzle-profile-public-1', onSelectNextRecommendEntry, @@ -1191,6 +1222,7 @@ test('active recommend bottom tab selects next work instead of navigating', asyn await user.click(screen.getByRole('button', { name: '下一个' })); expect(onSelectNextRecommendEntry).toHaveBeenCalledTimes(1); + expect(openLoginModal).not.toHaveBeenCalled(); }); test('mobile recommend meta loads real author avatar from public user summary', async () => { @@ -1210,7 +1242,7 @@ test('mobile recommend meta loads real author avatar from public user summary', await waitFor(() => { expect( document - .querySelector('.platform-recommend-work-meta__avatar img') + .querySelector('.platform-recommend-cover-only__author img') ?.getAttribute('src'), ).toBe('data:image/png;base64,AUTHOR'); }); diff --git a/src/components/rpg-entry/RpgEntryHomeView.tsx b/src/components/rpg-entry/RpgEntryHomeView.tsx index 1195e4aa..2ad471b1 100644 --- a/src/components/rpg-entry/RpgEntryHomeView.tsx +++ b/src/components/rpg-entry/RpgEntryHomeView.tsx @@ -561,6 +561,66 @@ function WorldCard({ ); } +function RecommendCoverOnlyCard({ + entry, + authorAvatarUrl, + onClick, +}: { + entry: PlatformPublicGalleryCard; + authorAvatarUrl?: string | null; + onClick: () => void; +}) { + const coverImage = resolvePlatformWorldCoverImage(entry); + const displayName = formatPlatformWorkDisplayName(entry.worldName); + const typeLabel = describePublicGalleryCardKind(entry); + const authorName = entry.authorDisplayName.trim() || '玩家'; + const authorAvatarLabel = getPublicAuthorAvatarLabel(authorName); + const normalizedAuthorAvatarUrl = authorAvatarUrl?.trim() ?? ''; + + return ( + + ); +} + function CreationLibraryCard({ entry, onClick, @@ -3049,9 +3109,9 @@ export function RpgEntryHomeView({ useEffect(() => { if (!visibleTabs.includes(activeTab)) { - onTabChange('home'); + onTabChange(isAuthenticated ? 'home' : 'category'); } - }, [activeTab, onTabChange, visibleTabs]); + }, [activeTab, isAuthenticated, onTabChange, visibleTabs]); useEffect(() => { setVisitedTabs((currentTabs) => { @@ -3705,6 +3765,18 @@ export function RpgEntryHomeView({ ) ?? recommendedFeedEntries[0] ?? null; + const openActiveRecommendEntry = useCallback(() => { + if (!activeRecommendEntry) { + return; + } + + if (!isAuthenticated) { + authUi?.openLoginModal(() => onOpenGalleryDetail(activeRecommendEntry)); + return; + } + + onOpenGalleryDetail(activeRecommendEntry); + }, [activeRecommendEntry, authUi, isAuthenticated, onOpenGalleryDetail]); const selectNextRecommendEntry = useCallback(() => { onSelectNextRecommendEntry?.(); }, [onSelectNextRecommendEntry]); @@ -3787,6 +3859,12 @@ export function RpgEntryHomeView({
正在读取公开作品...
+ ) : !isAuthenticated && activeRecommendEntry ? ( + ) : recommendRuntimeError ? (